diff --git a/vlib/crypto/ed25519/LICENSE b/vlib/crypto/ed25519/LICENSE new file mode 100644 index 0000000000..7b4ab47212 --- /dev/null +++ b/vlib/crypto/ed25519/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 blackshirt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vlib/crypto/ed25519/README.md b/vlib/crypto/ed25519/README.md new file mode 100644 index 0000000000..01b04de434 --- /dev/null +++ b/vlib/crypto/ed25519/README.md @@ -0,0 +1,6 @@ +README +----- + +This module implements `ed25519` public key digital signature algorithm for V Language ported
+from `Go` version of `crypto.ed25519`. +See [Ed25519](http://ed25519.cr.yp.to/) for more detail about `ed25519`. \ No newline at end of file diff --git a/vlib/crypto/ed25519/ed25519.v b/vlib/crypto/ed25519/ed25519.v new file mode 100644 index 0000000000..af9abe7bdc --- /dev/null +++ b/vlib/crypto/ed25519/ed25519.v @@ -0,0 +1,181 @@ +module ed25519 + +import crypto.rand +import crypto.sha512 +import crypto.internal.subtle +import crypto.ed25519.internal.edwards25519 + +// public_key_size is the sizeof public keys in bytes +pub const public_key_size = 32 + +// private_key_size is the sizeof private keys in bytes +pub const private_key_size = 64 + +// signature_size is the size of signatures generated and verified by this modules, in bytes. +pub const signature_size = 64 + +// seed_size is the size of private key seeds in bytes +pub const seed_size = 32 + +// `PublicKey` is Ed25519 public keys. +pub type PublicKey = []byte + +// `equal` reports whether p and x have the same value. +pub fn (p PublicKey) equal(x []byte) bool { + return subtle.constant_time_compare(p, PublicKey(x)) == 1 +} + +// `PrivateKey` is Ed25519 private keys +pub type PrivateKey = []byte + +// seed returns the private key seed corresponding to priv. RFC 8032's private keys correspond to seeds +// in this module. +pub fn (priv PrivateKey) seed() []byte { + mut seed := []byte{len: ed25519.seed_size} + copy(seed, priv[..32]) + return seed +} + +// `public_key` returns the []byte corresponding to priv. +pub fn (priv PrivateKey) public_key() []byte { + assert priv.len == ed25519.private_key_size + mut publickey := []byte{len: ed25519.public_key_size} + copy(publickey, priv[32..]) + return PublicKey(publickey) +} + +// currentyly x not `crypto.PrivateKey` +pub fn (priv PrivateKey) equal(x []byte) bool { + return subtle.constant_time_compare(priv, PrivateKey(x)) == 1 +} + +// `sign` signs the given message with priv. +pub fn (priv PrivateKey) sign(message []byte) ?[]byte { + /* + if opts.HashFunc() != crypto.Hash(0) { + return nil, errors.New("ed25519: cannot sign hashed message") + }*/ + + return sign(priv, message) +} + +// `sign `signs the message with privatekey and returns a signature +pub fn sign(privatekey PrivateKey, message []byte) ?[]byte { + mut signature := []byte{len: ed25519.signature_size} + sign_generic(signature, privatekey, message) ? + return signature +} + +fn sign_generic(signature []byte, privatekey []byte, message []byte) ? { + if privatekey.len != ed25519.private_key_size { + panic('ed25519: bad private key length: $privatekey.len') + } + seed, publickey := privatekey[..ed25519.seed_size], privatekey[ed25519.seed_size..] + + mut h := sha512.sum512(seed) + mut s := edwards25519.new_scalar() + s.set_bytes_with_clamping(h[..32]) ? + mut prefix := h[32..] + + mut mh := sha512.new() + mh.write(prefix) ? + mh.write(message) ? + + mut msg_digest := []byte{cap: sha512.size} + msg_digest = mh.sum(msg_digest) + + mut r := edwards25519.new_scalar() + r.set_uniform_bytes(msg_digest) ? + + mut rr := edwards25519.Point{} + rr.scalar_base_mult(mut r) + + mut kh := sha512.new() + kh.write(rr.bytes()) ? + kh.write(publickey) ? + kh.write(message) ? + + mut hram_digest := []byte{cap: sha512.size} + hram_digest = kh.sum(hram_digest) + mut k := edwards25519.new_scalar() + k.set_uniform_bytes(hram_digest) ? + + mut ss := edwards25519.new_scalar() + ss.multiply_add(k, s, r) + + copy(signature[..32], rr.bytes()) + copy(signature[32..], ss.bytes()) +} + +// `verify` reports whether sig is a valid signature of message by publickey. +pub fn verify(publickey PublicKey, message []byte, sig []byte) ?bool { + if publickey.len != ed25519.public_key_size { + return error('ed25519: bad public key length: $publickey.len') + } + + if sig.len != ed25519.signature_size || sig[63] & 224 != 0 { + return false + } + + mut aa := edwards25519.Point{} + aa.set_bytes(publickey) ? + + mut kh := sha512.new() + kh.write(sig[..32]) ? + kh.write(publickey) ? + kh.write(message) ? + + mut hram_digest := []byte{cap: sha512.size} + hram_digest = kh.sum(hram_digest) + + mut k := edwards25519.new_scalar() + k.set_uniform_bytes(hram_digest) ? + + mut ss := edwards25519.new_scalar() + ss.set_canonical_bytes(sig[32..]) ? + + // [S]B = R + [k]A --> [k](-A) + [S]B = R + mut minus_a := edwards25519.Point{} + minus_a.negate(aa) + mut rr := edwards25519.Point{} + rr.vartime_double_scalar_base_mult(k, minus_a, ss) + + return subtle.constant_time_compare(sig[..32], rr.bytes()) == 1 +} + +// `generate_key` generates a public/private key pair entropy using `crypto.rand`. +pub fn generate_key() ?(PublicKey, PrivateKey) { + mut seed := rand.bytes(ed25519.seed_size) ? + + privatekey := new_key_from_seed(seed) + publickey := []byte{len: ed25519.public_key_size} + copy(publickey, privatekey[32..]) + + return publickey, privatekey +} + +// `new_key_from_seed` calculates a private key from a seed. private keys of RFC 8032 +// correspond to seeds in this module +pub fn new_key_from_seed(seed []byte) PrivateKey { + // Outline the function body so that the returned key can be stack-allocated. + privatekey := []byte{len: ed25519.private_key_size} + new_key_from_seed_generic(privatekey, seed) + return PrivateKey(privatekey) +} + +fn new_key_from_seed_generic(privatekey []byte, seed []byte) { + if seed.len != ed25519.seed_size { + panic('ed25519: bad seed length: $seed.len') + } + + mut h := sha512.sum512(seed) + mut s := edwards25519.new_scalar() + s.set_bytes_with_clamping(h[..32]) or { panic(err.msg) } + mut aa := edwards25519.Point{} + aa.scalar_base_mult(mut s) + + mut publickey := aa.bytes() + + copy(privatekey, seed) + copy(privatekey[32..], publickey) +} diff --git a/vlib/crypto/ed25519/examples/example.v b/vlib/crypto/ed25519/examples/example.v new file mode 100644 index 0000000000..32a358ee12 --- /dev/null +++ b/vlib/crypto/ed25519/examples/example.v @@ -0,0 +1,41 @@ +module main + +import encoding.hex +import encoding.base64 +import crypto.ed25519 + +// adapted from https://asecuritysite.com/signatures/ed25519 +fn main() { + msg := 'Hello Girl' + + publ, priv := ed25519.generate_key() or { panic(err.msg) } + + m := msg.bytes() + + sig := ed25519.sign(priv, m) or { panic(err.msg) } + + println('=== Message ===') + println('Msg: $msg \nHash: $m') + + println('=== Public key ===') + println('Public key (Hex): ${hex.encode(publ)}') + println(' Public key (Base64): ${base64.encode(publ)}') + + println('=== Private key ===') + println('Private key: $priv.seed().hex()') // priv[0:32] + println(' Private key (Base64): ${base64.encode(priv.seed())}') // priv[0:32] + println(' Private key (Base64) Full key: ${base64.encode(priv)}') + println(' Private key (Full key in Hex): ${hex.encode(priv)}') + + println('=== signature (R,s) ===') + println('signature: R=${sig[0..32].hex()} s=${sig[32..64].hex()}') + println(' signature (Base64)=${base64.encode(sig)}') + + rtn := ed25519.verify(publ, m, sig) or { panic(err.msg) } + + if rtn { + println('Signature verified :$rtn') + } else { + println('signature does not verify :${!rtn}') + } +} diff --git a/vlib/crypto/ed25519/internal/ed25519_test.v b/vlib/crypto/ed25519/internal/ed25519_test.v new file mode 100644 index 0000000000..fba36dd1fb --- /dev/null +++ b/vlib/crypto/ed25519/internal/ed25519_test.v @@ -0,0 +1,203 @@ +module main + +// NB: this should be in vlib/crypto/ed25519/ed25519_test.v +// but is currently one folder below, because of a V parser/symbol registration bug. +// TODO: move this test back to vlib/crypto/ed25519/ed25519_test.v +import os +import sync.pool +import encoding.hex +import crypto.ed25519 + +const vexe = os.getenv('VEXE') + +const vroot = os.dir(vexe) + +const testdata = os.join_path(vroot, 'vlib/crypto/ed25519/testdata') + +const contents = os.read_lines(os.join_path(testdata, 'sign.input')) or { panic(err) } + +/* +struct ZeroReader {} + +fn (z ZeroReader) read(mut buf []byte) ?int { + for i, _ in buf { + buf[i] = 0 + } + return buf.len +} +*/ + +fn test_sign_verify() ? { + // mut zero := ZeroReader{} + public, private := ed25519.generate_key() ? + + message := 'test message'.bytes() + sig := ed25519.sign(private, message) ? + res := ed25519.verify(public, message, sig) or { false } + assert res == true + + wrongmessage := 'wrong message'.bytes() + res2 := ed25519.verify(public, wrongmessage, sig) ? + assert res2 == false +} + +fn test_equal() ? { + public, private := ed25519.generate_key() ? + + assert public.equal(public) == true + + // This is not AVAILABLE + /* + if !public.Equal(crypto.Signer(private).Public()) { + t.Errorf("private.Public() is not Equal to public: %q", public) + }*/ + assert private.equal(private) == true + + otherpub, otherpriv := ed25519.generate_key() ? + assert public.equal(otherpub) == false + + assert private.equal(otherpriv) == false +} + +fn test_malleability() ? { + // https://tools.ietf.org/html/rfc8032#section-5.1.7 adds an additional test + // that s be in [0, order). This prevents someone from adding a multiple of + // order to s and obtaining a second valid signature for the same message. + msg := [byte(0x54), 0x65, 0x73, 0x74] + sig := [byte(0x7c), 0x38, 0xe0, 0x26, 0xf2, 0x9e, 0x14, 0xaa, 0xbd, 0x05, 0x9a, 0x0f, 0x2d, + 0xb8, 0xb0, 0xcd, 0x78, 0x30, 0x40, 0x60, 0x9a, 0x8b, 0xe6, 0x84, 0xdb, 0x12, 0xf8, 0x2a, + 0x27, 0x77, 0x4a, 0xb0, 0x67, 0x65, 0x4b, 0xce, 0x38, 0x32, 0xc2, 0xd7, 0x6f, 0x8f, 0x6f, + 0x5d, 0xaf, 0xc0, 0x8d, 0x93, 0x39, 0xd4, 0xee, 0xf6, 0x76, 0x57, 0x33, 0x36, 0xa5, 0xc5, + 0x1e, 0xb6, 0xf9, 0x46, 0xb3, 0x1d] + publickey := [byte(0x7d), 0x4d, 0x0e, 0x7f, 0x61, 0x53, 0xa6, 0x9b, 0x62, 0x42, 0xb5, 0x22, + 0xab, 0xbe, 0xe6, 0x85, 0xfd, 0xa4, 0x42, 0x0f, 0x88, 0x34, 0xb1, 0x08, 0xc3, 0xbd, 0xae, + 0x36, 0x9e, 0xf5, 0x49, 0xfa] + // verify should fail on provided bytes + res := ed25519.verify(publickey, msg, sig) or { false } + assert res == false +} + +fn works_check_on_sign_input_string(item string) bool { + // this is core part of the tests sign input + parts := item.split(':') // []string + + if parts.len != 5 { + return false + } + // assert parts.len == 5 + privbytes := hex.decode(parts[0]) or { panic(err.msg) } + pubkey := hex.decode(parts[1]) or { panic(err.msg) } + msg := hex.decode(parts[2]) or { panic(err.msg) } + mut sig := hex.decode(parts[3]) or { panic(err.msg) } + + if pubkey.len != ed25519.public_key_size { + return false + } + // assert pubkey.len == public_key_size + + sig = sig[..ed25519.signature_size] + mut priv := []byte{len: ed25519.private_key_size} + copy(priv[..], privbytes) + copy(priv[32..], pubkey) + + sig2 := ed25519.sign(priv[..], msg) or { panic(err.msg) } + if sig != sig2[..] { + return false + } + + res := ed25519.verify(pubkey, msg, sig2) or { panic(err.msg) } + // assert res == true + if !res { + return false + } + + priv2 := ed25519.new_key_from_seed(priv[..32]) + if ed25519.PrivateKey(priv[..]) != priv2 { + return false + } + + pubkey2 := priv2.public_key() + if pubkey != pubkey2 { + return false + } + + seed2 := priv2.seed() + if priv[0..32] != seed2 { + return false + } + + return true +} + +fn worker_for_string_content(p &pool.PoolProcessor, idx int, worker_id int) &SignResult { + item := p.get_item(idx) + // println('worker_s worker_id: $worker_id | idx: $idx ') + res := works_check_on_sign_input_string(item) + mut sr := &SignResult{ + item: item + result: res + } + return sr +} + +struct SignResult { +mut: + item string + result bool +} + +// This test read a lot of entries in `testdata/sign.input` +// so, maybe need a long time to finish. +// be quiet and patient +fn test_input_from_djb_ed25519_crypto_sign_input_with_syncpool() ? { + // contents := os.read_lines('testdata/sign.input') or { panic(err.msg) } //[]string + mut pool_s := pool.new_pool_processor( + callback: worker_for_string_content + maxjobs: 4 + ) + pool_s.work_on_items(contents) + for i, x in pool_s.get_results() { + // println("i: $i = $x.result") + assert x.result == true + } +} + +// same as above, but without sync.pool +/* +fn test_input_from_djb_ed25519_crypto_sign_input_without_syncpool() ? { + // contents := os.read_lines('testdata/sign.input') or { panic(err.msg) } //[]string + for i, item in ed25519.contents { + parts := item.split(':') // []string + // println(parts) + /* + if parts.len != 5 { + lg.fatal('not contains len 5') + }*/ + assert parts.len == 5 + privbytes := hex.decode(parts[0]) ? + pubkey := hex.decode(parts[1]) ? + msg := hex.decode(parts[2]) ? + mut sig := hex.decode(parts[3]) ? + assert pubkey.len == public_key_size + + sig = sig[..signature_size] + mut priv := []byte{len: ed25519.private_key_size} + copy(priv[..], privbytes) + copy(priv[32..], pubkey) + + sig2 := ed25519.sign(priv[..], msg) ? + assert sig == sig2[..] + + res := ed25519.verify(pubkey, msg, sig2) ? + assert res == true + + priv2 := new_key_from_seed(priv[..32]) + assert priv[..] == priv2 + + pubkey2 := priv2.public_key() + assert pubkey == pubkey2 + + seed2 := priv2.seed() + assert priv[0..32] == seed2 + } +}*/ diff --git a/vlib/crypto/ed25519/internal/edwards25519/README.md b/vlib/crypto/ed25519/internal/edwards25519/README.md new file mode 100644 index 0000000000..43a1bf3742 --- /dev/null +++ b/vlib/crypto/ed25519/internal/edwards25519/README.md @@ -0,0 +1,19 @@ +README +------- + +This module provides arithmetic primitives operations that are useful to implement +cryptographic schemes over curve edwards25519, includes: +1. Arithmetic functions for point addition, doubling, negation, scalar multiplication + with an arbitrary point, with the base point, etc. +2. Arithmetic functions dealing with scalars modulo the prime order L of the base point. + +This modules was port of Golang `edwards25519` library from [edwards25519](https://github.com/FiloSottile/edwards25519) to the V language. + + +About Edwards25519 +------------------ + +Twisted Edwards curves are a familly of elliptic curves allowing complete addition +formulas without any special case and no point at infinity. +Curve edwards25519 is based on prime 2^255 - 19 for efficient implementation. +Equation and parameters are given in RFC 7748. \ No newline at end of file diff --git a/vlib/crypto/ed25519/internal/edwards25519/edwards25519.v b/vlib/crypto/ed25519/internal/edwards25519/edwards25519.v new file mode 100644 index 0000000000..7d976845f8 --- /dev/null +++ b/vlib/crypto/ed25519/internal/edwards25519/edwards25519.v @@ -0,0 +1 @@ +module edwards25519 diff --git a/vlib/crypto/ed25519/internal/edwards25519/element.v b/vlib/crypto/ed25519/internal/edwards25519/element.v new file mode 100644 index 0000000000..2a48c26458 --- /dev/null +++ b/vlib/crypto/ed25519/internal/edwards25519/element.v @@ -0,0 +1,733 @@ +module edwards25519 + +import math.bits +import math.unsigned +import encoding.binary +import crypto.internal.subtle + +// embedded unsigned.Uint128 +struct Uint128 { + unsigned.Uint128 +} + +// Element represents an element of the edwards25519 GF(2^255-19). Note that this +// is not a cryptographically secure group, and should only be used to interact +// with edwards25519.Point coordinates. +// +// This type works similarly to math/big.Int, and all arguments and receivers +// are allowed to alias. +// +// The zero value is a valid zero element. +struct Element { +mut: + // An element t represents the integer + // t.l0 + t.l1*2^51 + t.l2*2^102 + t.l3*2^153 + t.l4*2^204 + // + // Between operations, all limbs are expected to be lower than 2^52. + l0 u64 + l1 u64 + l2 u64 + l3 u64 + l4 u64 +} + +const ( + mask_low_51_bits = u64((1 << 51) - 1) + fe_zero = Element{ + l0: 0 + l1: 0 + l2: 0 + l3: 0 + l4: 0 + } + fe_one = Element{ + l0: 1 + l1: 0 + l2: 0 + l3: 0 + l4: 0 + } + // sqrt_m1 is 2^((p-1)/4), which squared is equal to -1 by Euler's Criterion. + sqrt_m1 = Element{ + l0: 1718705420411056 + l1: 234908883556509 + l2: 2233514472574048 + l3: 2117202627021982 + l4: 765476049583133 + } +) + +// mul_64 returns a * b. +fn mul_64(a u64, b u64) Uint128 { + hi, lo := bits.mul_64(a, b) + return Uint128{ + lo: lo + hi: hi + } +} + +// add_mul_64 returns v + a * b. +fn add_mul_64(v Uint128, a u64, b u64) Uint128 { + mut hi, lo := bits.mul_64(a, b) + low, carry := bits.add_64(lo, v.lo, 0) + hi, _ = bits.add_64(hi, v.hi, carry) + return Uint128{ + lo: low + hi: hi + } +} + +// shift_right_by_51 returns a >> 51. a is assumed to be at most 115 bits. +fn shift_right_by_51(a Uint128) u64 { + return (a.hi << (64 - 51)) | (a.lo >> 51) +} + +fn fe_mul_generic(a Element, b Element) Element { + a0 := a.l0 + a1 := a.l1 + a2 := a.l2 + a3 := a.l3 + a4 := a.l4 + + b0 := b.l0 + b1 := b.l1 + b2 := b.l2 + b3 := b.l3 + b4 := b.l4 + + // Limb multiplication works like pen-and-paper columnar multiplication, but + // with 51-bit limbs instead of digits. + // + // a4 a3 a2 a1 a0 x + // b4 b3 b2 b1 b0 = + // ------------------------ + // a4b0 a3b0 a2b0 a1b0 a0b0 + + // a4b1 a3b1 a2b1 a1b1 a0b1 + + // a4b2 a3b2 a2b2 a1b2 a0b2 + + // a4b3 a3b3 a2b3 a1b3 a0b3 + + // a4b4 a3b4 a2b4 a1b4 a0b4 = + // ---------------------------------------------- + // r8 r7 r6 r5 r4 r3 r2 r1 r0 + // + // We can then use the reduction identity (a * 2²⁵⁵ + b = a * 19 + b) to + // reduce the limbs that would overflow 255 bits. r5 * 2²⁵⁵ becomes 19 * r5, + // r6 * 2³⁰⁶ becomes 19 * r6 * 2⁵¹, etc. + // + // Reduction can be carried out simultaneously to multiplication. For + // example, we do not compute r5: whenever the result of a multiplication + // belongs to r5, like a1b4, we multiply it by 19 and add the result to r0. + // + // a4b0 a3b0 a2b0 a1b0 a0b0 + + // a3b1 a2b1 a1b1 a0b1 19×a4b1 + + // a2b2 a1b2 a0b2 19×a4b2 19×a3b2 + + // a1b3 a0b3 19×a4b3 19×a3b3 19×a2b3 + + // a0b4 19×a4b4 19×a3b4 19×a2b4 19×a1b4 = + // -------------------------------------- + // r4 r3 r2 r1 r0 + // + // Finally we add up the columns into wide, overlapping limbs. + + a1_19 := a1 * 19 + a2_19 := a2 * 19 + a3_19 := a3 * 19 + a4_19 := a4 * 19 + + // r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1) + mut r0 := mul_64(a0, b0) + r0 = add_mul_64(r0, a1_19, b4) + r0 = add_mul_64(r0, a2_19, b3) + r0 = add_mul_64(r0, a3_19, b2) + r0 = add_mul_64(r0, a4_19, b1) + + // r1 = a0×b1 + a1×b0 + 19×(a2×b4 + a3×b3 + a4×b2) + mut r1 := mul_64(a0, b1) + r1 = add_mul_64(r1, a1, b0) + r1 = add_mul_64(r1, a2_19, b4) + r1 = add_mul_64(r1, a3_19, b3) + r1 = add_mul_64(r1, a4_19, b2) + + // r2 = a0×b2 + a1×b1 + a2×b0 + 19×(a3×b4 + a4×b3) + mut r2 := mul_64(a0, b2) + r2 = add_mul_64(r2, a1, b1) + r2 = add_mul_64(r2, a2, b0) + r2 = add_mul_64(r2, a3_19, b4) + r2 = add_mul_64(r2, a4_19, b3) + + // r3 = a0×b3 + a1×b2 + a2×b1 + a3×b0 + 19×a4×b4 + mut r3 := mul_64(a0, b3) + r3 = add_mul_64(r3, a1, b2) + r3 = add_mul_64(r3, a2, b1) + r3 = add_mul_64(r3, a3, b0) + r3 = add_mul_64(r3, a4_19, b4) + + // r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0 + mut r4 := mul_64(a0, b4) + r4 = add_mul_64(r4, a1, b3) + r4 = add_mul_64(r4, a2, b2) + r4 = add_mul_64(r4, a3, b1) + r4 = add_mul_64(r4, a4, b0) + + // After the multiplication, we need to reduce (carry) the five coefficients + // to obtain a result with limbs that are at most slightly larger than 2⁵¹, + // to respect the Element invariant. + // + // Overall, the reduction works the same as carryPropagate, except with + // wider inputs: we take the carry for each coefficient by shifting it right + // by 51, and add it to the limb above it. The top carry is multiplied by 19 + // according to the reduction identity and added to the lowest limb. + // + // The largest coefficient (r0) will be at most 111 bits, which guarantees + // that all carries are at most 111 - 51 = 60 bits, which fits in a u64. + // + // r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1) + // r0 < 2⁵²×2⁵² + 19×(2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵²) + // r0 < (1 + 19 × 4) × 2⁵² × 2⁵² + // r0 < 2⁷ × 2⁵² × 2⁵² + // r0 < 2¹¹¹ + // + // Moreover, the top coefficient (r4) is at most 107 bits, so c4 is at most + // 56 bits, and c4 * 19 is at most 61 bits, which again fits in a u64 and + // allows us to easily apply the reduction identity. + // + // r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0 + // r4 < 5 × 2⁵² × 2⁵² + // r4 < 2¹⁰⁷ + // + + c0 := shift_right_by_51(r0) + c1 := shift_right_by_51(r1) + c2 := shift_right_by_51(r2) + c3 := shift_right_by_51(r3) + c4 := shift_right_by_51(r4) + + rr0 := r0.lo & edwards25519.mask_low_51_bits + c4 * 19 + rr1 := r1.lo & edwards25519.mask_low_51_bits + c0 + rr2 := r2.lo & edwards25519.mask_low_51_bits + c1 + rr3 := r3.lo & edwards25519.mask_low_51_bits + c2 + rr4 := r4.lo & edwards25519.mask_low_51_bits + c3 + + // Now all coefficients fit into 64-bit registers but are still too large to + // be passed around as a Element. We therefore do one last carry chain, + // where the carries will be small enough to fit in the wiggle room above 2⁵¹. + mut v := Element{ + l0: rr0 + l1: rr1 + l2: rr2 + l3: rr3 + l4: rr4 + } + // v.carryPropagate() + // using `carry_propagate_generic()` instead + v = v.carry_propagate_generic() + return v +} + +// carryPropagate brings the limbs below 52 bits by applying the reduction +// identity (a * 2²⁵⁵ + b = a * 19 + b) to the l4 carry. +fn (mut v Element) carry_propagate_generic() Element { + c0 := v.l0 >> 51 + c1 := v.l1 >> 51 + c2 := v.l2 >> 51 + c3 := v.l3 >> 51 + c4 := v.l4 >> 51 + + v.l0 = v.l0 & edwards25519.mask_low_51_bits + c4 * 19 + v.l1 = v.l1 & edwards25519.mask_low_51_bits + c0 + v.l2 = v.l2 & edwards25519.mask_low_51_bits + c1 + v.l3 = v.l3 & edwards25519.mask_low_51_bits + c2 + v.l4 = v.l4 & edwards25519.mask_low_51_bits + c3 + return v +} + +fn fe_square_generic(a Element) Element { + l0 := a.l0 + l1 := a.l1 + l2 := a.l2 + l3 := a.l3 + l4 := a.l4 + + // Squaring works precisely like multiplication above, but thanks to its + // symmetry we get to group a few terms together. + // + // l4 l3 l2 l1 l0 x + // l4 l3 l2 l1 l0 = + // ------------------------ + // l4l0 l3l0 l2l0 l1l0 l0l0 + + // l4l1 l3l1 l2l1 l1l1 l0l1 + + // l4l2 l3l2 l2l2 l1l2 l0l2 + + // l4l3 l3l3 l2l3 l1l3 l0l3 + + // l4l4 l3l4 l2l4 l1l4 l0l4 = + // ---------------------------------------------- + // r8 r7 r6 r5 r4 r3 r2 r1 r0 + // + // l4l0 l3l0 l2l0 l1l0 l0l0 + + // l3l1 l2l1 l1l1 l0l1 19×l4l1 + + // l2l2 l1l2 l0l2 19×l4l2 19×l3l2 + + // l1l3 l0l3 19×l4l3 19×l3l3 19×l2l3 + + // l0l4 19×l4l4 19×l3l4 19×l2l4 19×l1l4 = + // -------------------------------------- + // r4 r3 r2 r1 r0 + // + // With precomputed 2×, 19×, and 2×19× terms, we can compute each limb with + // only three mul_64 and four Add64, instead of five and eight. + + l0_2 := l0 * 2 + l1_2 := l1 * 2 + + l1_38 := l1 * 38 + l2_38 := l2 * 38 + l3_38 := l3 * 38 + + l3_19 := l3 * 19 + l4_19 := l4 * 19 + + // r0 = l0×l0 + 19×(l1×l4 + l2×l3 + l3×l2 + l4×l1) = l0×l0 + 19×2×(l1×l4 + l2×l3) + mut r0 := mul_64(l0, l0) + r0 = add_mul_64(r0, l1_38, l4) + r0 = add_mul_64(r0, l2_38, l3) + + // r1 = l0×l1 + l1×l0 + 19×(l2×l4 + l3×l3 + l4×l2) = 2×l0×l1 + 19×2×l2×l4 + 19×l3×l3 + mut r1 := mul_64(l0_2, l1) + r1 = add_mul_64(r1, l2_38, l4) + r1 = add_mul_64(r1, l3_19, l3) + + // r2 = l0×l2 + l1×l1 + l2×l0 + 19×(l3×l4 + l4×l3) = 2×l0×l2 + l1×l1 + 19×2×l3×l4 + mut r2 := mul_64(l0_2, l2) + r2 = add_mul_64(r2, l1, l1) + r2 = add_mul_64(r2, l3_38, l4) + + // r3 = l0×l3 + l1×l2 + l2×l1 + l3×l0 + 19×l4×l4 = 2×l0×l3 + 2×l1×l2 + 19×l4×l4 + mut r3 := mul_64(l0_2, l3) + r3 = add_mul_64(r3, l1_2, l2) + r3 = add_mul_64(r3, l4_19, l4) + + // r4 = l0×l4 + l1×l3 + l2×l2 + l3×l1 + l4×l0 = 2×l0×l4 + 2×l1×l3 + l2×l2 + mut r4 := mul_64(l0_2, l4) + r4 = add_mul_64(r4, l1_2, l3) + r4 = add_mul_64(r4, l2, l2) + + c0 := shift_right_by_51(r0) + c1 := shift_right_by_51(r1) + c2 := shift_right_by_51(r2) + c3 := shift_right_by_51(r3) + c4 := shift_right_by_51(r4) + + rr0 := r0.lo & edwards25519.mask_low_51_bits + c4 * 19 + rr1 := r1.lo & edwards25519.mask_low_51_bits + c0 + rr2 := r2.lo & edwards25519.mask_low_51_bits + c1 + rr3 := r3.lo & edwards25519.mask_low_51_bits + c2 + rr4 := r4.lo & edwards25519.mask_low_51_bits + c3 + + mut v := Element{ + l0: rr0 + l1: rr1 + l2: rr2 + l3: rr3 + l4: rr4 + } + v = v.carry_propagate_generic() + return v +} + +// zero sets v = 0, and returns v. +fn (mut v Element) zero() Element { + v = edwards25519.fe_zero + return v +} + +// one sets v = 1, and returns v. +fn (mut v Element) one() Element { + v = edwards25519.fe_one + return v +} + +// reduce reduces v modulo 2^255 - 19 and returns it. +fn (mut v Element) reduce() Element { + v = v.carry_propagate_generic() + + // After the light reduction we now have a edwards25519 element representation + // v < 2^255 + 2^13 * 19, but need v < 2^255 - 19. + + // If v >= 2^255 - 19, then v + 19 >= 2^255, which would overflow 2^255 - 1, + // generating a carry. That is, c will be 0 if v < 2^255 - 19, and 1 otherwise. + mut c := (v.l0 + 19) >> 51 + c = (v.l1 + c) >> 51 + c = (v.l2 + c) >> 51 + c = (v.l3 + c) >> 51 + c = (v.l4 + c) >> 51 + + // If v < 2^255 - 19 and c = 0, this will be a no-op. Otherwise, it's + // effectively applying the reduction identity to the carry. + v.l0 += 19 * c + + v.l1 += v.l0 >> 51 + v.l0 = v.l0 & edwards25519.mask_low_51_bits + v.l2 += v.l1 >> 51 + v.l1 = v.l1 & edwards25519.mask_low_51_bits + v.l3 += v.l2 >> 51 + v.l2 = v.l2 & edwards25519.mask_low_51_bits + v.l4 += v.l3 >> 51 + v.l3 = v.l3 & edwards25519.mask_low_51_bits + // no additional carry + v.l4 = v.l4 & edwards25519.mask_low_51_bits + + return v +} + +// Add sets v = a + b, and returns v. +fn (mut v Element) add(a Element, b Element) Element { + v.l0 = a.l0 + b.l0 + v.l1 = a.l1 + b.l1 + v.l2 = a.l2 + b.l2 + v.l3 = a.l3 + b.l3 + v.l4 = a.l4 + b.l4 + // Using the generic implementation here is actually faster than the + // assembly. Probably because the body of this function is so simple that + // the compiler can figure out better optimizations by inlining the carry + // propagation. + return v.carry_propagate_generic() +} + +// Subtract sets v = a - b, and returns v. +fn (mut v Element) subtract(a Element, b Element) Element { + // We first add 2 * p, to guarantee the subtraction won't underflow, and + // then subtract b (which can be up to 2^255 + 2^13 * 19). + v.l0 = (a.l0 + 0xFFFFFFFFFFFDA) - b.l0 + v.l1 = (a.l1 + 0xFFFFFFFFFFFFE) - b.l1 + v.l2 = (a.l2 + 0xFFFFFFFFFFFFE) - b.l2 + v.l3 = (a.l3 + 0xFFFFFFFFFFFFE) - b.l3 + v.l4 = (a.l4 + 0xFFFFFFFFFFFFE) - b.l4 + return v.carry_propagate_generic() +} + +// `negate` sets v = -a, and returns v. +fn (mut v Element) negate(a Element) Element { + return v.subtract(edwards25519.fe_zero, a) +} + +// invert sets v = 1/z mod p, and returns v. +// +// If z == 0, invert returns v = 0. +fn (mut v Element) invert(z Element) Element { + // Inversion is implemented as exponentiation with exponent p − 2. It uses the + // same sequence of 255 squarings and 11 multiplications as [Curve25519]. + mut z2 := Element{} + mut z9 := Element{} + mut z11 := Element{} + mut z2_5_0 := Element{} + mut z2_10_0 := Element{} + mut z2_20_0 := Element{} + mut z2_50_0 := Element{} + mut z2_100_0 := Element{} + mut t := Element{} + + z2.square(z) // 2 + t.square(z2) // 4 + t.square(t) // 8 + z9.multiply(t, z) // 9 + z11.multiply(z9, z2) // 11 + t.square(z11) // 22 + z2_5_0.multiply(t, z9) // 31 = 2^5 - 2^0 + + t.square(z2_5_0) // 2^6 - 2^1 + for i := 0; i < 4; i++ { + t.square(t) // 2^10 - 2^5 + } + z2_10_0.multiply(t, z2_5_0) // 2^10 - 2^0 + + t.square(z2_10_0) // 2^11 - 2^1 + for i := 0; i < 9; i++ { + t.square(t) // 2^20 - 2^10 + } + z2_20_0.multiply(t, z2_10_0) // 2^20 - 2^0 + + t.square(z2_20_0) // 2^21 - 2^1 + for i := 0; i < 19; i++ { + t.square(t) // 2^40 - 2^20 + } + t.multiply(t, z2_20_0) // 2^40 - 2^0 + + t.square(t) // 2^41 - 2^1 + for i := 0; i < 9; i++ { + t.square(t) // 2^50 - 2^10 + } + z2_50_0.multiply(t, z2_10_0) // 2^50 - 2^0 + + t.square(z2_50_0) // 2^51 - 2^1 + for i := 0; i < 49; i++ { + t.square(t) // 2^100 - 2^50 + } + z2_100_0.multiply(t, z2_50_0) // 2^100 - 2^0 + + t.square(z2_100_0) // 2^101 - 2^1 + for i := 0; i < 99; i++ { + t.square(t) // 2^200 - 2^100 + } + t.multiply(t, z2_100_0) // 2^200 - 2^0 + + t.square(t) // 2^201 - 2^1 + for i := 0; i < 49; i++ { + t.square(t) // 2^250 - 2^50 + } + t.multiply(t, z2_50_0) // 2^250 - 2^0 + + t.square(t) // 2^251 - 2^1 + t.square(t) // 2^252 - 2^2 + t.square(t) // 2^253 - 2^3 + t.square(t) // 2^254 - 2^4 + t.square(t) // 2^255 - 2^5 + + return v.multiply(t, z11) // 2^255 - 21 +} + +// square sets v = x * x, and returns v. +fn (mut v Element) square(x Element) Element { + v = fe_square_generic(x) + return v +} + +// multiply sets v = x * y, and returns v. +fn (mut v Element) multiply(x Element, y Element) Element { + v = fe_mul_generic(x, y) + return v +} + +// mul_51 returns lo + hi * 2⁵¹ = a * b. +fn mul_51(a u64, b u32) (u64, u64) { + mh, ml := bits.mul_64(a, u64(b)) + lo := ml & edwards25519.mask_low_51_bits + hi := (mh << 13) | (ml >> 51) + return lo, hi +} + +// pow_22523 set v = x^((p-5)/8), and returns v. (p-5)/8 is 2^252-3. +fn (mut v Element) pow_22523(x Element) Element { + mut t0, mut t1, mut t2 := Element{}, Element{}, Element{} + + t0.square(x) // x^2 + t1.square(t0) // x^4 + t1.square(t1) // x^8 + t1.multiply(x, t1) // x^9 + t0.multiply(t0, t1) // x^11 + t0.square(t0) // x^22 + t0.multiply(t1, t0) // x^31 + t1.square(t0) // x^62 + for i := 1; i < 5; i++ { // x^992 + t1.square(t1) + } + t0.multiply(t1, t0) // x^1023 -> 1023 = 2^10 - 1 + t1.square(t0) // 2^11 - 2 + for i := 1; i < 10; i++ { // 2^20 - 2^10 + t1.square(t1) + } + t1.multiply(t1, t0) // 2^20 - 1 + t2.square(t1) // 2^21 - 2 + for i := 1; i < 20; i++ { // 2^40 - 2^20 + t2.square(t2) + } + t1.multiply(t2, t1) // 2^40 - 1 + t1.square(t1) // 2^41 - 2 + for i := 1; i < 10; i++ { // 2^50 - 2^10 + t1.square(t1) + } + t0.multiply(t1, t0) // 2^50 - 1 + t1.square(t0) // 2^51 - 2 + for i := 1; i < 50; i++ { // 2^100 - 2^50 + t1.square(t1) + } + t1.multiply(t1, t0) // 2^100 - 1 + t2.square(t1) // 2^101 - 2 + for i := 1; i < 100; i++ { // 2^200 - 2^100 + t2.square(t2) + } + t1.multiply(t2, &t1) // 2^200 - 1 + t1.square(t1) // 2^201 - 2 + for i := 1; i < 50; i++ { // 2^250 - 2^50 + t1.square(t1) + } + t0.multiply(t1, t0) // 2^250 - 1 + t0.square(t0) // 2^251 - 2 + t0.square(t0) // 2^252 - 4 + return v.multiply(t0, x) // 2^252 - 3 -> x^(2^252-3) +} + +// sqrt_ratio sets r to the non-negative square root of the ratio of u and v. +// +// If u/v is square, sqrt_ratio returns r and 1. If u/v is not square, sqrt_ratio +// sets r according to Section 4.3 of draft-irtf-cfrg-ristretto255-decaf448-00, +// and returns r and 0. +fn (mut r Element) sqrt_ratio(u Element, v Element) (Element, int) { + mut a, mut b := Element{}, Element{} + + // r = (u * v3) * (u * v7)^((p-5)/8) + v2 := a.square(v) + uv3 := b.multiply(u, b.multiply(v2, v)) + uv7 := a.multiply(uv3, a.square(v2)) + r.multiply(uv3, r.pow_22523(uv7)) + + mut check := a.multiply(v, a.square(r)) // check = v * r^2 + + mut uneg := b.negate(u) + correct_sign_sqrt := check.equal(u) + flipped_sign_sqrt := check.equal(uneg) + flipped_sign_sqrt_i := check.equal(uneg.multiply(uneg, edwards25519.sqrt_m1)) + + rprime := b.multiply(r, edwards25519.sqrt_m1) // r_prime = SQRT_M1 * r + // r = CT_selected(r_prime IF flipped_sign_sqrt | flipped_sign_sqrt_i ELSE r) + r.selected(rprime, r, flipped_sign_sqrt | flipped_sign_sqrt_i) + + r.absolute(r) // Choose the nonnegative square root. + return r, correct_sign_sqrt | flipped_sign_sqrt +} + +// mask_64_bits returns 0xffffffff if cond is 1, and 0 otherwise. +fn mask_64_bits(cond int) u64 { + // in go, `^` operates on bit mean NOT, flip bit + // in v, its a ~ bitwise NOT + return ~(u64(cond) - 1) +} + +// selected sets v to a if cond == 1, and to b if cond == 0. +fn (mut v Element) selected(a Element, b Element, cond int) Element { + // see above notes + m := mask_64_bits(cond) + v.l0 = (m & a.l0) | (~m & b.l0) + v.l1 = (m & a.l1) | (~m & b.l1) + v.l2 = (m & a.l2) | (~m & b.l2) + v.l3 = (m & a.l3) | (~m & b.l3) + v.l4 = (m & a.l4) | (~m & b.l4) + return v +} + +// is_negative returns 1 if v is negative, and 0 otherwise. +fn (mut v Element) is_negative() int { + return int(v.bytes()[0] & 1) +} + +// absolute sets v to |u|, and returns v. +fn (mut v Element) absolute(u Element) Element { + mut e := Element{} + mut uk := u + return v.selected(e.negate(uk), uk, uk.is_negative()) +} + +// set sets v = a, and returns v. +fn (mut v Element) set(a Element) Element { + v = a + return v +} + +// set_bytes sets v to x, where x is a 32-byte little-endian encoding. If x is +// not of the right length, SetUniformBytes returns nil and an error, and the +// receiver is unchanged. +// +// Consistent with RFC 7748, the most significant bit (the high bit of the +// last byte) is ignored, and non-canonical values (2^255-19 through 2^255-1) +// are accepted. Note that this is laxer than specified by RFC 8032. +fn (mut v Element) set_bytes(x []byte) ?Element { + if x.len != 32 { + return error('edwards25519: invalid edwards25519 element input size') + } + + // Bits 0:51 (bytes 0:8, bits 0:64, shift 0, mask 51). + v.l0 = binary.little_endian_u64(x[0..8]) + v.l0 &= edwards25519.mask_low_51_bits + // Bits 51:102 (bytes 6:14, bits 48:112, shift 3, mask 51). + v.l1 = binary.little_endian_u64(x[6..14]) >> 3 + v.l1 &= edwards25519.mask_low_51_bits + // Bits 102:153 (bytes 12:20, bits 96:160, shift 6, mask 51). + v.l2 = binary.little_endian_u64(x[12..20]) >> 6 + v.l2 &= edwards25519.mask_low_51_bits + // Bits 153:204 (bytes 19:27, bits 152:216, shift 1, mask 51). + v.l3 = binary.little_endian_u64(x[19..27]) >> 1 + v.l3 &= edwards25519.mask_low_51_bits + // Bits 204:251 (bytes 24:32, bits 192:256, shift 12, mask 51). + // Note: not bytes 25:33, shift 4, to avoid overread. + v.l4 = binary.little_endian_u64(x[24..32]) >> 12 + v.l4 &= edwards25519.mask_low_51_bits + + return v +} + +// bytes returns the canonical 32-byte little-endian encoding of v. +pub fn (mut v Element) bytes() []byte { + // This function is outlined to make the allocations inline in the caller + // rather than happen on the heap. + // out := v.bytes_generic() + return v.bytes_generic() +} + +fn (mut v Element) bytes_generic() []byte { + mut out := []byte{len: 32} + + v = v.reduce() + + mut buf := []byte{len: 8} + idxs := [v.l0, v.l1, v.l2, v.l3, v.l4] + for i, l in idxs { + bits_offset := i * 51 + binary.little_endian_put_u64(mut buf, l << u32(bits_offset % 8)) + for j, bb in buf { + off := bits_offset / 8 + j + if off >= out.len { + break + } + out[off] |= bb + } + } + + return out +} + +// equal returns 1 if v and u are equal, and 0 otherwise. +fn (mut v Element) equal(ue Element) int { + mut u := ue + sa := u.bytes() + sv := v.bytes() + return subtle.constant_time_compare(sa, sv) +} + +// swap swaps v and u if cond == 1 or leaves them unchanged if cond == 0, and returns v. +fn (mut v Element) swap(mut u Element, cond int) { + // mut u := ue + m := mask_64_bits(cond) + mut t := m & (v.l0 ^ u.l0) + v.l0 ^= t + u.l0 ^= t + t = m & (v.l1 ^ u.l1) + v.l1 ^= t + u.l1 ^= t + t = m & (v.l2 ^ u.l2) + v.l2 ^= t + u.l2 ^= t + t = m & (v.l3 ^ u.l3) + v.l3 ^= t + u.l3 ^= t + t = m & (v.l4 ^ u.l4) + v.l4 ^= t + u.l4 ^= t +} + +// mult_32 sets v = x * y, and returns v. +fn (mut v Element) mult_32(x Element, y u32) Element { + x0lo, x0hi := mul_51(x.l0, y) + x1lo, x1hi := mul_51(x.l1, y) + x2lo, x2hi := mul_51(x.l2, y) + x3lo, x3hi := mul_51(x.l3, y) + x4lo, x4hi := mul_51(x.l4, y) + v.l0 = x0lo + 19 * x4hi // carried over per the reduction identity + v.l1 = x1lo + x0hi + v.l2 = x2lo + x1hi + v.l3 = x3lo + x2hi + v.l4 = x4lo + x3hi + // The hi portions are going to be only 32 bits, plus any previous excess, + // so we can skip the carry propagation. + return v +} + +fn swap_endianness(mut buf []byte) []byte { + for i := 0; i < buf.len / 2; i++ { + buf[i], buf[buf.len - i - 1] = buf[buf.len - i - 1], buf[i] + } + return buf +} diff --git a/vlib/crypto/ed25519/internal/edwards25519/element_test.v b/vlib/crypto/ed25519/internal/edwards25519/element_test.v new file mode 100644 index 0000000000..85a7902004 --- /dev/null +++ b/vlib/crypto/ed25519/internal/edwards25519/element_test.v @@ -0,0 +1,464 @@ +module edwards25519 + +import os +import rand +import math.bits +import math.big +import encoding.hex + +const github_job = os.getenv('GITHUB_JOB') + +fn testsuite_begin() { + if edwards25519.github_job != '' { + // ensure that the CI does not run flaky tests: + rand.seed([u32(0xffff24), 0xabcd]) + } +} + +fn (mut v Element) str() string { + return hex.encode(v.bytes()) +} + +const mask_low_52_bits = (u64(1) << 52) - 1 + +fn generate_field_element() Element { + return Element{ + l0: rand.u64() & edwards25519.mask_low_52_bits + l1: rand.u64() & edwards25519.mask_low_52_bits + l2: rand.u64() & edwards25519.mask_low_52_bits + l3: rand.u64() & edwards25519.mask_low_52_bits + l4: rand.u64() & edwards25519.mask_low_52_bits + } +} + +// weirdLimbs can be combined to generate a range of edge-case edwards25519 elements. +// 0 and -1 are intentionally more weighted, as they combine well. +const ( + two_to_51 = u64(1) << 51 + two_to_52 = u64(1) << 52 + weird_limbs_51 = [ + u64(0), + 0, + 0, + 0, + 1, + 19 - 1, + 19, + 0x2aaaaaaaaaaaa, + 0x5555555555555, + two_to_51 - 20, + two_to_51 - 19, + two_to_51 - 1, + two_to_51 - 1, + two_to_51 - 1, + two_to_51 - 1, + ] + weird_limbs_52 = [ + u64(0), + 0, + 0, + 0, + 0, + 0, + 1, + 19 - 1, + 19, + 0x2aaaaaaaaaaaa, + 0x5555555555555, + two_to_51 - 20, + two_to_51 - 19, + two_to_51 - 1, + two_to_51 - 1, + two_to_51 - 1, + two_to_51 - 1, + two_to_51 - 1, + two_to_51 - 1, + two_to_51, + two_to_51 + 1, + two_to_52 - 19, + two_to_52 - 1, + ] +) + +fn generate_weird_field_element() Element { + return Element{ + l0: edwards25519.weird_limbs_52[rand.intn(edwards25519.weird_limbs_52.len)] + l1: edwards25519.weird_limbs_51[rand.intn(edwards25519.weird_limbs_51.len)] + l2: edwards25519.weird_limbs_51[rand.intn(edwards25519.weird_limbs_51.len)] + l3: edwards25519.weird_limbs_51[rand.intn(edwards25519.weird_limbs_51.len)] + l4: edwards25519.weird_limbs_51[rand.intn(edwards25519.weird_limbs_51.len)] + } +} + +fn (e Element) generate_element() Element { + if rand.intn(2) == 0 { + return generate_weird_field_element() + } + return generate_field_element() +} + +fn is_in_bounds(x Element) bool { + return bits.len_64(x.l0) <= 52 && bits.len_64(x.l1) <= 52 && bits.len_64(x.l2) <= 52 + && bits.len_64(x.l3) <= 52 && bits.len_64(x.l4) <= 52 +} + +fn carry_gen(a [5]u64) bool { + mut t1 := Element{a[0], a[1], a[2], a[3], a[4]} + mut t2 := Element{a[0], a[1], a[2], a[3], a[4]} + + t1.carry_propagate_generic() + t2.carry_propagate_generic() + + return t1 == t2 && is_in_bounds(t2) +} + +fn test_carry_propagate_generic() { + // closures not supported on windows + for i := 0; i <= 10; i++ { + els := [rand.u64(), rand.u64(), rand.u64(), rand.u64(), + rand.u64()]! + p := carry_gen(els) + assert p == true + } + res := carry_gen([u64(0xffffffffffffffff), 0xffffffffffffffff, 0xffffffffffffffff, + 0xffffffffffffffff, 0xffffffffffffffff]!) + assert res == true +} + +fn test_fe_mul_generic() { + for i in 0 .. 20 { + el := Element{} + a := el.generate_element() + b := el.generate_element() + a1 := a + a2 := a + + b1 := b + b2 := b + + a1b1 := fe_mul_generic(a1, b1) + a2b2 := fe_mul_generic(a2, b2) + assert a1b1 == a2b2 && is_in_bounds(a1b1) && is_in_bounds(a2b2) + } +} + +fn test_fe_square_generic() { + for i in 0 .. 20 { + a := generate_field_element() + + a1 := a + a2 := a + + a11 := fe_square_generic(a1) + a22 := fe_square_generic(a2) + assert a11 == a22 && is_in_bounds(a11) && is_in_bounds(a22) + } +} + +struct SqrtRatioTest { + u string + v string + was_square int + r string +} + +fn test_sqrt_ratio() ? { + // From draft-irtf-cfrg-ristretto255-decaf448-00, Appendix A.4. + + tests := [ + // If u is 0, the function is defined to return (0, TRUE), even if v + // is zero. Note that where used in this package, the denominator v + // is never zero. + SqrtRatioTest{'0000000000000000000000000000000000000000000000000000000000000000', '0000000000000000000000000000000000000000000000000000000000000000', 1, '0000000000000000000000000000000000000000000000000000000000000000'}, + // 0/1 == 0² + SqrtRatioTest{'0000000000000000000000000000000000000000000000000000000000000000', '0100000000000000000000000000000000000000000000000000000000000000', 1, '0000000000000000000000000000000000000000000000000000000000000000'}, + // If u is non-zero and v is zero, defined to return (0, FALSE). + SqrtRatioTest{'0100000000000000000000000000000000000000000000000000000000000000', '0000000000000000000000000000000000000000000000000000000000000000', 0, '0000000000000000000000000000000000000000000000000000000000000000'}, + // 2/1 is not square in this edwards25519. + SqrtRatioTest{'0200000000000000000000000000000000000000000000000000000000000000', '0100000000000000000000000000000000000000000000000000000000000000', 0, '3c5ff1b5d8e4113b871bd052f9e7bcd0582804c266ffb2d4f4203eb07fdb7c54'}, + // 4/1 == 2² + SqrtRatioTest{'0400000000000000000000000000000000000000000000000000000000000000', '0100000000000000000000000000000000000000000000000000000000000000', 1, '0200000000000000000000000000000000000000000000000000000000000000'}, + // 1/4 == (2⁻¹)² == (2^(p-2))² per Euler's theorem + SqrtRatioTest{'0100000000000000000000000000000000000000000000000000000000000000', '0400000000000000000000000000000000000000000000000000000000000000', 1, 'f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f'}, + ] + + for i, tt in tests { + mut elu := Element{} + mut elv := Element{} + mut elw := Element{} + mut elg := Element{} + + u := elu.set_bytes(hex.decode(tt.u) ?) ? + v := elv.set_bytes(hex.decode(tt.v) ?) ? + want := elw.set_bytes(hex.decode(tt.r) ?) ? + mut got, was_square := elg.sqrt_ratio(u, v) + + assert got.equal(want) != 0 + assert was_square == tt.was_square + // if got.Equal(want) == 0 || wasSquare != tt.wasSquare { + // t.Errorf("%d: got (%v, %v), want (%v, %v)", i, got, wasSquare, want, tt.wasSquare) + // } + } +} + +fn test_set_bytes_normal() ? { + for i in 0 .. 15 { + mut el := Element{} + mut random_inp := rand.bytes(32) ? + + el = el.set_bytes(random_inp.clone()) ? + random_inp[random_inp.len - 1] &= (1 << 7) - 1 + // assert f1(random_inp, el) == true + + assert random_inp == el.bytes() + assert is_in_bounds(el) == true + } +} + +fn test_set_bytes_reduced() { + mut fe := Element{} + mut r := Element{} + mut random_inp := rand.bytes(32) or { return } + + fe.set_bytes(random_inp) or { return } + r.set_bytes(fe.bytes()) or { return } + + assert fe == r +} + +// Check some fixed vectors from dalek +struct FeRTTest { +mut: + fe Element + b []byte +} + +fn test_set_bytes_from_dalek_test_vectors() ? { + mut tests := [ + FeRTTest{ + fe: Element{358744748052810, 1691584618240980, 977650209285361, 1429865912637724, 560044844278676} + b: [byte(74), 209, 69, 197, 70, 70, 161, 222, 56, 226, 229, 19, 112, 60, 25, 92, 187, + 74, 222, 56, 50, 153, 51, 233, 40, 74, 57, 6, 160, 185, 213, 31] + }, + FeRTTest{ + fe: Element{84926274344903, 473620666599931, 365590438845504, 1028470286882429, 2146499180330972} + b: [byte(199), 23, 106, 112, 61, 77, 216, 79, 186, 60, 11, 118, 13, 16, 103, 15, 42, + 32, 83, 250, 44, 57, 204, 198, 78, 199, 253, 119, 146, 172, 3, 122] + }, + ] + for _, mut tt in tests { + b := tt.fe.bytes() + mut el := Element{} + mut fe := el.set_bytes(tt.b) ? + + assert b == tt.b + assert fe.equal(tt.fe) == 1 + } +} + +fn test_equal() { + mut x := Element{1, 1, 1, 1, 1} + y := Element{5, 4, 3, 2, 1} + + mut eq1 := x.equal(x) + assert eq1 == 1 + + eq1 = x.equal(y) + assert eq1 == 0 +} + +fn test_invert() ? { + mut x := Element{1, 1, 1, 1, 1} + mut one := Element{1, 0, 0, 0, 0} + mut xinv := Element{} + mut r := Element{} + + xinv.invert(x) + r.multiply(x, xinv) + r.reduce() + + assert one == r + bytes := rand.bytes(32) or { return err } + + x.set_bytes(bytes) ? + + xinv.invert(x) + r.multiply(x, xinv) + r.reduce() + + assert one == r + + zero := Element{} + x.set(zero) + + xx := xinv.invert(x) + assert xx == xinv + assert xinv.equal(zero) == 1 + // s := if num % 2 == 0 { 'even' } else { 'odd' } +} + +fn test_mult_32() { + for j in 0 .. 10 { + mut x := Element{} + mut t1 := Element{} + y := u32(0) + for i := 0; i < 100; i++ { + t1.mult_32(x, y) + } + mut ty := Element{} + ty.l0 = u64(y) + mut t2 := Element{} + for i := 0; i < 100; i++ { + t2.multiply(x, ty) + } + assert t1.equal(t2) == 1 && is_in_bounds(t1) && is_in_bounds(t2) + } +} + +fn test_selected_and_swap() { + a := Element{358744748052810, 1691584618240980, 977650209285361, 1429865912637724, 560044844278676} + b := Element{84926274344903, 473620666599931, 365590438845504, 1028470286882429, 2146499180330972} + + mut c := Element{} + mut d := Element{} + + c.selected(a, b, 1) + d.selected(a, b, 0) + + assert c.equal(a) == 1 + assert d.equal(b) == 1 + + c.swap(mut d, 0) + assert c.equal(a) == 1 + assert d.equal(b) == 1 + + c.swap(mut d, 1) + assert c.equal(b) == 1 + assert d.equal(a) == 1 +} + +// Tests self-consistency between multiply and Square. +fn test_consistency_between_mult_and_square() { + mut x := Element{1, 1, 1, 1, 1} + mut x2 := Element{} + mut x2sq := Element{} + + x2.multiply(x, x) + x2sq.square(x) + + assert x2 == x2sq + + bytes := rand.bytes(32) or { return } + x.set_bytes(bytes) or { return } + x2.multiply(x, x) + x2sq.square(x) + + assert x2 == x2sq +} + +// to_big_integer returns v as a big.Integer. +fn (mut v Element) to_big_integer() big.Integer { + buf := v.bytes() + return big.integer_from_bytes(buf) +} + +// from_big_integer sets v = n, and returns v. The bit length of n must not exceed 256. +fn (mut v Element) from_big_integer(n big.Integer) ?Element { + if n.binary_str().len > 32 * 8 { + return error('invalid edwards25519 element input size') + } + mut bytes, _ := n.bytes() + swap_endianness(mut bytes) // SHOULD I SWAP IT? + v.set_bytes(bytes) ? + + return v +} + +fn (mut v Element) from_decimal_string(s string) ?Element { + num := big.integer_from_string(s) ? + + v = v.from_big_integer(num) ? + return v +} + +fn test_bytes_big_equivalence() ? { + mut inp := rand.bytes(32) ? + el := Element{} + mut fe := el.generate_element() + mut fe1 := el.generate_element() + + fe.set_bytes(inp) or { panic(err.msg) } + inp[inp.len - 1] &= (1 << 7) - 1 // mask the most significant bit + + mut b := big.integer_from_bytes(swap_endianness(mut inp)) // need swap_endianness + fe1.from_big_integer(b) or { panic(err.msg) } // do swap_endianness internally + + assert fe == fe1 + + mut buf := []byte{len: 32} // pad with zeroes + fedtobig := fe1.to_big_integer() + mut fedbig_bytes, _ := fedtobig.bytes() + copy(buf, fedbig_bytes) // does not need to do swap_endianness + + assert fe.bytes() == buf && is_in_bounds(fe) && is_in_bounds(fe1) + // assert big_equivalence(inp, fe, fe1) == true +} + +fn test_decimal_constants() ? { + sqrtm1string := '19681161376707505956807079304988542015446066515923890162744021073123829784752' + mut el := Element{} + mut exp := el.from_decimal_string(sqrtm1string) ? + + assert sqrt_m1.equal(exp) == 1 + + dstring := '37095705934669439343138083508754565189542113879843219016388785533085940283555' + exp = el.from_decimal_string(dstring) ? + mut d := d_const + + assert d.equal(exp) == 1 +} + +fn test_mul_64_to_128() { + mut a := u64(5) + mut b := u64(5) + mut r := mul_64(a, b) + + assert r.lo == 0x19 + assert r.hi == 0 + + a = u64(18014398509481983) // 2^54 - 1 + b = u64(18014398509481983) // 2^54 - 1 + r = mul_64(a, b) + + assert r.lo == 0xff80000000000001 && r.hi == 0xfffffffffff + + a = u64(1125899906842661) + b = u64(2097155) + r = mul_64(a, b) + r = add_mul_64(r, a, b) + r = add_mul_64(r, a, b) + r = add_mul_64(r, a, b) + r = add_mul_64(r, a, b) + + assert r.lo == 16888498990613035 && r.hi == 640 +} + +fn test_multiply_distributes_over_add() { + for i in 0 .. 10 { + el := Element{} + x := el.generate_element() + y := el.generate_element() + z := el.generate_element() + mut t1 := Element{} + t1.add(x, y) + t1.multiply(t1, z) + + // Compute t2 = x*z + y*z + mut t2 := Element{} + mut t3 := Element{} + t2.multiply(x, z) + t3.multiply(y, z) + t2.add(t2, t3) + assert t1.equal(t2) == 1 && is_in_bounds(t1) && is_in_bounds(t2) + } +} diff --git a/vlib/crypto/ed25519/internal/edwards25519/extra.v b/vlib/crypto/ed25519/internal/edwards25519/extra.v new file mode 100644 index 0000000000..b9f73d60e4 --- /dev/null +++ b/vlib/crypto/ed25519/internal/edwards25519/extra.v @@ -0,0 +1,352 @@ +module edwards25519 + +// extended_coordinates returns v in extended coordinates (X:Y:Z:T) where +// x = X/Z, y = Y/Z, and xy = T/Z as in https://eprint.iacr.org/2008/522. +fn (mut v Point) extended_coordinates() (Element, Element, Element, Element) { + // This function is outlined to make the allocations inline in the caller + // rather than happen on the heap. Don't change the style without making + // sure it doesn't increase the inliner cost. + mut e := []Element{len: 4} + x, y, z, t := v.extended_coordinates_generic(mut e) + return x, y, z, t +} + +fn (mut v Point) extended_coordinates_generic(mut e []Element) (Element, Element, Element, Element) { + check_initialized(v) + x := e[0].set(v.x) + y := e[1].set(v.y) + z := e[2].set(v.z) + t := e[3].set(v.t) + return x, y, z, t +} + +// Given k > 0, set s = s**(2*i). +fn (mut s Scalar) pow2k(k int) { + for i := 0; i < k; i++ { + s.multiply(s, s) + } +} + +// set_extended_coordinates sets v = (X:Y:Z:T) in extended coordinates where +// x = X/Z, y = Y/Z, and xy = T/Z as in https://eprint.iacr.org/2008/522. +// +// If the coordinates are invalid or don't represent a valid point on the curve, +// set_extended_coordinates returns nil and an error and the receiver is +// unchanged. Otherwise, set_extended_coordinates returns v. +fn (mut v Point) set_extended_coordinates(x Element, y Element, z Element, t Element) ?Point { + if !is_on_curve(x, y, z, t) { + return error('edwards25519: invalid point coordinates') + } + v.x.set(x) + v.y.set(y) + v.z.set(z) + v.t.set(t) + return v +} + +fn is_on_curve(x Element, y Element, z Element, t Element) bool { + mut lhs := Element{} + mut rhs := Element{} + + mut xx := Element{} + xx.square(x) + + mut yy := Element{} + yy.square(y) + + mut zz := Element{} + zz.square(z) + // zz := new(Element).Square(Z) + + mut tt := Element{} + tt.square(t) + // tt := new(Element).Square(T) + // -x² + y² = 1 + dx²y² + // -(X/Z)² + (Y/Z)² = 1 + d(T/Z)² + // -X² + Y² = Z² + dT² + lhs.subtract(yy, xx) + mut d := d_const + rhs.multiply(d, tt) + rhs.add(rhs, zz) + if lhs.equal(rhs) != 1 { + return false + } + // xy = T/Z + // XY/Z² = T/Z + // XY = TZ + lhs.multiply(x, y) + rhs.multiply(t, z) + return lhs.equal(rhs) == 1 +} + +// `bytes_montgomery` converts v to a point on the birationally-equivalent +// Curve25519 Montgomery curve, and returns its canonical 32 bytes encoding +// according to RFC 7748. +// +// Note that bytes_montgomery only encodes the u-coordinate, so v and -v encode +// to the same value. If v is the identity point, bytes_montgomery returns 32 +// zero bytes, analogously to the X25519 function. +pub fn (mut v Point) bytes_montgomery() []byte { + // This function is outlined to make the allocations inline in the caller + // rather than happen on the heap. + mut buf := [32]byte{} + return v.bytes_montgomery_generic(mut buf) +} + +fn (mut v Point) bytes_montgomery_generic(mut buf [32]byte) []byte { + check_initialized(v) + + // RFC 7748, Section 4.1 provides the bilinear map to calculate the + // Montgomery u-coordinate + // + // u = (1 + y) / (1 - y) + // + // where y = Y / Z. + + mut y := Element{} + mut recip := Element{} + mut u := Element{} + + y.multiply(v.y, y.invert(v.z)) // y = Y / Z + recip.invert(recip.subtract(fe_one, &y)) // r = 1/(1 - y) + u.multiply(u.add(fe_one, y), recip) // u = (1 + y)*r + + return copy_field_element(mut buf, mut u) +} + +// `mult_by_cofactor` sets v = 8 * p, and returns v. +pub fn (mut v Point) mult_by_cofactor(p Point) Point { + check_initialized(p) + mut result := ProjectiveP1{} + mut pp := ProjectiveP2{} + pp.from_p3(p) + result.double(pp) + pp.from_p1(result) + result.double(pp) + pp.from_p1(result) + result.double(pp) + return v.from_p1(result) +} + +// `invert` sets s to the inverse of a nonzero scalar v, and returns s. +// +// If t is zero, invert returns zero. +pub fn (mut s Scalar) invert(t Scalar) Scalar { + // Uses a hardcoded sliding window of width 4. + mut table := [8]Scalar{} + mut tt := Scalar{} + tt.multiply(t, t) + table[0] = t + for i := 0; i < 7; i++ { + table[i + 1].multiply(table[i], tt) + } + // Now table = [t**1, t**3, t**7, t**11, t**13, t**15] + // so t**k = t[k/2] for odd k + + // To compute the sliding window digits, use the following Sage script: + + // sage: import itertools + // sage: def sliding_window(w,k): + // ....: digits = [] + // ....: while k > 0: + // ....: if k % 2 == 1: + // ....: kmod = k % (2**w) + // ....: digits.append(kmod) + // ....: k = k - kmod + // ....: else: + // ....: digits.append(0) + // ....: k = k // 2 + // ....: return digits + + // Now we can compute s roughly as follows: + + // sage: s = 1 + // sage: for coeff in reversed(sliding_window(4,l-2)): + // ....: s = s*s + // ....: if coeff > 0 : + // ....: s = s*t**coeff + + // This works on one bit at a time, with many runs of zeros. + // The digits can be collapsed into [(count, coeff)] as follows: + + // sage: [(len(list(group)),d) for d,group in itertools.groupby(sliding_window(4,l-2))] + + // Entries of the form (k, 0) turn into pow2k(k) + // Entries of the form (1, coeff) turn into a squaring and then a table lookup. + // We can fold the squaring into the previous pow2k(k) as pow2k(k+1). + + s = table[1 / 2] + s.pow2k(127 + 1) + s.multiply(s, table[1 / 2]) + s.pow2k(4 + 1) + s.multiply(s, table[9 / 2]) + s.pow2k(3 + 1) + s.multiply(s, table[11 / 2]) + s.pow2k(3 + 1) + s.multiply(s, table[13 / 2]) + s.pow2k(3 + 1) + s.multiply(s, table[15 / 2]) + s.pow2k(4 + 1) + s.multiply(s, table[7 / 2]) + s.pow2k(4 + 1) + s.multiply(s, table[15 / 2]) + s.pow2k(3 + 1) + s.multiply(s, table[5 / 2]) + s.pow2k(3 + 1) + s.multiply(s, table[1 / 2]) + s.pow2k(4 + 1) + s.multiply(s, table[15 / 2]) + s.pow2k(4 + 1) + s.multiply(s, table[15 / 2]) + s.pow2k(4 + 1) + s.multiply(s, table[7 / 2]) + s.pow2k(3 + 1) + s.multiply(s, table[3 / 2]) + s.pow2k(4 + 1) + s.multiply(s, table[11 / 2]) + s.pow2k(5 + 1) + s.multiply(s, table[11 / 2]) + s.pow2k(9 + 1) + s.multiply(s, table[9 / 2]) + s.pow2k(3 + 1) + s.multiply(s, table[3 / 2]) + s.pow2k(4 + 1) + s.multiply(s, table[3 / 2]) + s.pow2k(4 + 1) + s.multiply(s, table[3 / 2]) + s.pow2k(4 + 1) + s.multiply(s, table[9 / 2]) + s.pow2k(3 + 1) + s.multiply(s, table[7 / 2]) + s.pow2k(3 + 1) + s.multiply(s, table[3 / 2]) + s.pow2k(3 + 1) + s.multiply(s, table[13 / 2]) + s.pow2k(3 + 1) + s.multiply(s, table[7 / 2]) + s.pow2k(4 + 1) + s.multiply(s, table[9 / 2]) + s.pow2k(3 + 1) + s.multiply(s, table[15 / 2]) + s.pow2k(4 + 1) + s.multiply(s, table[11 / 2]) + + return s +} + +// `multi_scalar_mult` sets v = sum(scalars[i] * points[i]), and returns v. +// +// Execution time depends only on the lengths of the two slices, which must match. +pub fn (mut v Point) multi_scalar_mult(scalars []Scalar, points []Point) Point { + if scalars.len != points.len { + panic('edwards25519: called multi_scalar_mult with different size inputs') + } + check_initialized(...points) + + mut sc := scalars.clone() + // Proceed as in the single-base case, but share doublings + // between each point in the multiscalar equation. + + // Build lookup tables for each point + mut tables := []ProjLookupTable{len: points.len} + for i, _ in tables { + tables[i].from_p3(points[i]) + } + // Compute signed radix-16 digits for each scalar + // digits := make([][64]int8, len(scalars)) + mut digits := [][]i8{len: sc.len, init: []i8{len: 64, cap: 64}} + + for j, _ in digits { + digits[j] = sc[j].signed_radix16() + } + + // Unwrap first loop iteration to save computing 16*identity + mut multiple := ProjectiveCached{} + mut tmp1 := ProjectiveP1{} + mut tmp2 := ProjectiveP2{} + // Lookup-and-add the appropriate multiple of each input point + for k, _ in tables { + tables[k].select_into(mut multiple, digits[k][63]) + tmp1.add(v, multiple) // tmp1 = v + x_(j,63)*Q in P1xP1 coords + v.from_p1(tmp1) // update v + } + tmp2.from_p3(v) // set up tmp2 = v in P2 coords for next iteration + for r := 62; r >= 0; r-- { + tmp1.double(tmp2) // tmp1 = 2*(prev) in P1xP1 coords + tmp2.from_p1(tmp1) // tmp2 = 2*(prev) in P2 coords + tmp1.double(tmp2) // tmp1 = 4*(prev) in P1xP1 coords + tmp2.from_p1(tmp1) // tmp2 = 4*(prev) in P2 coords + tmp1.double(tmp2) // tmp1 = 8*(prev) in P1xP1 coords + tmp2.from_p1(tmp1) // tmp2 = 8*(prev) in P2 coords + tmp1.double(tmp2) // tmp1 = 16*(prev) in P1xP1 coords + v.from_p1(tmp1) // v = 16*(prev) in P3 coords + // Lookup-and-add the appropriate multiple of each input point + for s, _ in tables { + tables[s].select_into(mut multiple, digits[s][r]) + tmp1.add(v, multiple) // tmp1 = v + x_(j,i)*Q in P1xP1 coords + v.from_p1(tmp1) // update v + } + tmp2.from_p3(v) // set up tmp2 = v in P2 coords for next iteration + } + return v +} + +// `vartime_multiscalar_mult` sets v = sum(scalars[i] * points[i]), and returns v. +// +// Execution time depends on the inputs. +pub fn (mut v Point) vartime_multiscalar_mult(scalars []Scalar, points []Point) Point { + if scalars.len != points.len { + panic('edwards25519: called VarTimeMultiScalarMult with different size inputs') + } + check_initialized(...points) + + // Generalize double-base NAF computation to arbitrary sizes. + // Here all the points are dynamic, so we only use the smaller + // tables. + + // Build lookup tables for each point + mut tables := []NafLookupTable5{len: points.len} + for i, _ in tables { + tables[i].from_p3(points[i]) + } + mut sc := scalars.clone() + // Compute a NAF for each scalar + // mut nafs := make([][256]int8, len(scalars)) + mut nafs := [][]i8{len: sc.len, init: []i8{len: 256, cap: 256}} + for j, _ in nafs { + nafs[j] = sc[j].non_adjacent_form(5) + } + + mut multiple := ProjectiveCached{} + mut tmp1 := ProjectiveP1{} + mut tmp2 := ProjectiveP2{} + tmp2.zero() + + // Move from high to low bits, doubling the accumulator + // at each iteration and checking whether there is a nonzero + // coefficient to look up a multiple of. + // + // Skip trying to find the first nonzero coefficent, because + // searching might be more work than a few extra doublings. + // k == i, l == j + for k := 255; k >= 0; k-- { + tmp1.double(tmp2) + + for l, _ in nafs { + if nafs[l][k] > 0 { + v.from_p1(tmp1) + tables[l].select_into(mut multiple, nafs[l][k]) + tmp1.add(v, multiple) + } else if nafs[l][k] < 0 { + v.from_p1(tmp1) + tables[l].select_into(mut multiple, -nafs[l][k]) + tmp1.sub(v, multiple) + } + } + + tmp2.from_p1(tmp1) + } + + v.from_p2(tmp2) + return v +} diff --git a/vlib/crypto/ed25519/internal/edwards25519/extra_test.v b/vlib/crypto/ed25519/internal/edwards25519/extra_test.v new file mode 100644 index 0000000000..957f01f63b --- /dev/null +++ b/vlib/crypto/ed25519/internal/edwards25519/extra_test.v @@ -0,0 +1,204 @@ +module edwards25519 + +import os +import rand +import encoding.hex + +const github_job = os.getenv('GITHUB_JOB') + +fn testsuite_begin() { + if edwards25519.github_job != '' { + // ensure that the CI does not run flaky tests: + rand.seed([u32(0xffff24), 0xabcd]) + } +} + +// test_bytes_montgomery tests the set_bytes_with_clamping+bytes_montgomery path +// equivalence to curve25519.X25519 for basepoint scalar multiplications. +// +// Note that you can't actually implement X25519 with this package because +// there is no SetBytesMontgomery, and it would not be possible to implement +// it properly: points on the twist would get rejected, and the Scalar returned +// by set_bytes_with_clamping does not preserve its cofactor-clearing properties. +// +// Disabled curve25519 not available yet, but maybe can use own curve25519 +/* +fn fn_mon(scalar [32]byte) bool { + mut s := new_scalar().set_bytes_with_clamping(scalar[..]) + p := (&Point{}).scalar_base_mult(s) + got := p.bytes_montgomery() + want, _ := curve25519.X25519(scalar[..], curve25519.Basepoint) + return bytes.equal(got, want) + } + +fn test_bytes_montgomery() { + /* f := fn(scalar [32]byte) bool { + s := new_scalar().set_bytes_with_clamping(scalar[..]) + p := (&Point{}).scalar_base_mult(s) + got := p.bytes_montgomery() + want, _ := curve25519.X25519(scalar[..], curve25519.Basepoint) + return bytes.equal(got, want) + } */ + if err := quick.Check(f, nil); err != nil { + t.Error(err) + } +}*/ + +fn test_bytes_montgomery_sodium() ? { + // Generated with libsodium.js 1.0.18 + // crypto_sign_keypair().pubkey + pubkey := '3bf918ffc2c955dc895bf145f566fb96623c1cadbe040091175764b5fde322c0' + mut p := Point{} + p.set_bytes(hex.decode(pubkey) ?) ? + + // crypto_sign_ed25519_pk_to_curve25519(pubkey) + want := 'efc6c9d0738e9ea18d738ad4a2653631558931b0f1fde4dd58c436d19686dc28' + got := hex.encode(p.bytes_montgomery()) + assert got == want +} + +fn test_bytes_montgomery_infinity() { + mut p := new_identity_point() + want := '0000000000000000000000000000000000000000000000000000000000000000' + got := hex.encode(p.bytes_montgomery()) + + assert got == want +} + +const ( + loworder_string = '26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85' + loworder_bytes = hex.decode(loworder_string) or { panic(err.msg) } +) + +fn fn_cofactor(mut data []byte) bool { + if data.len != 64 { + panic('err.msg') + } + mut loworder := Point{} + loworder.set_bytes(edwards25519.loworder_bytes) or { panic(err.msg) } + + mut s := new_scalar() + mut p := Point{} + mut p8 := Point{} + s.set_uniform_bytes(data) or { panic(err.msg) } + p.scalar_base_mult(mut s) + p8.mult_by_cofactor(p) + + assert check_on_curve(p8) == true + + // 8 * p == (8 * s) * B + mut sc := Scalar{ + s: [32]byte{} + } + sc.s[0] = byte(0x08) + s.multiply(s, sc) + mut pp := Point{} + pp.scalar_base_mult(mut s) + if p8.equal(pp) != 1 { + return false + } + + // 8 * p == 8 * (loworder + p) + pp.add(p, loworder) + pp.mult_by_cofactor(pp) + if p8.equal(pp) != 1 { + return false + } + + // 8 * p == p + p + p + p + p + p + p + p + pp.set(new_identity_point()) + for i := 0; i < 8; i++ { + pp.add(pp, p) + } + return p8.equal(pp) == 1 +} + +fn test_mult_by_cofactor() ? { + mut loworder := Point{} + mut data := rand.bytes(64) ? + + assert fn_cofactor(mut data) == true +} + +fn invert_works(mut xinv Scalar, x NotZeroScalar) bool { + xinv.invert(x) + mut check := Scalar{} + check.multiply(x, xinv) + return check == sc_one && is_reduced(xinv) +} + +fn test_scalar_invert() { + nsc := generate_notzero_scalar(5) or { panic(err.msg) } + mut xsc := generate_scalar(5) or { panic(err.msg) } + assert invert_works(mut xsc, nsc) == true + + mut zero := new_scalar() + mut xx := new_scalar() + xx.invert(zero) + assert xx.equal(zero) == 1 +} + +fn test_multiscalarmultmatchesbasemult() { + for i in 0 .. 6 { + x := generate_scalar(100) or { panic(err.msg) } + y := generate_scalar(5) or { panic(err.msg) } + z := generate_scalar(2) or { panic(err.msg) } + assert multiscalarmultmatchesbasemult(x, y, z) == true + } +} + +fn multiscalarmultmatchesbasemult(xx Scalar, yy Scalar, zz Scalar) bool { + mut x := xx + mut y := yy + mut z := zz + + mut p := Point{} + mut q1 := Point{} + mut q2 := Point{} + mut q3 := Point{} + mut check := Point{} + mut b := new_generator_point() + + p.multi_scalar_mult([x, y, z], [b, b, b]) + + q1.scalar_base_mult(mut x) + q2.scalar_base_mult(mut y) + q3.scalar_base_mult(mut z) + check.add(q1, q2) + check.add(check, q3) + + check_on_curve(p, check, q1, q2, q3) + return p.equal(check) == 1 +} + +fn vartime_multiscala_rmultmatches_basemult(xx Scalar, yy Scalar, zz Scalar) bool { + mut x := xx + mut y := yy + mut z := zz + mut p := Point{} + mut q1 := Point{} + mut q2 := Point{} + mut q3 := Point{} + mut check := Point{} + mut b := new_generator_point() + + p.vartime_multiscalar_mult([x, y, z], [b, b, b]) + + q1.scalar_base_mult(mut x) + q2.scalar_base_mult(mut y) + q3.scalar_base_mult(mut z) + check.add(q1, q2) + check.add(check, q3) + + check_on_curve(p, check, q1, q2, q3) + return p.equal(check) == 1 +} + +fn test_vartimemultiscalarmultmatchesbasemult() { + for i in 0 .. 5 { + x := generate_scalar(100) or { panic(err.msg) } + y := generate_scalar(5) or { panic(err.msg) } + z := generate_scalar(2) or { panic(err.msg) } + assert vartime_multiscala_rmultmatches_basemult(x, y, z) == true + } +} diff --git a/vlib/crypto/ed25519/internal/edwards25519/point.v b/vlib/crypto/ed25519/internal/edwards25519/point.v new file mode 100644 index 0000000000..dc558e9839 --- /dev/null +++ b/vlib/crypto/ed25519/internal/edwards25519/point.v @@ -0,0 +1,551 @@ +module edwards25519 + +const ( + // d is a constant in the curve equation. + d_bytes = [byte(0xa3), 0x78, 0x59, 0x13, 0xca, 0x4d, 0xeb, 0x75, 0xab, 0xd8, 0x41, 0x41, + 0x4d, 0x0a, 0x70, 0x00, 0x98, 0xe8, 0x79, 0x77, 0x79, 0x40, 0xc7, 0x8c, 0x73, 0xfe, 0x6f, + 0x2b, 0xee, 0x6c, 0x03, 0x52] + id_bytes = [byte(1), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0] + gen_bytes = [byte(0x58), 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66] + d_const = d_const_generate() or { panic(err.msg) } + d2_const = d2_const_generate() or { panic(err.msg) } + // id_point is the point at infinity. + id_point = id_point_generate() or { panic(err.msg) } + // generator point + gen_point = generator() or { panic(err.msg) } +) + +fn d_const_generate() ?Element { + mut v := Element{} + v.set_bytes(edwards25519.d_bytes) ? + return v +} + +fn d2_const_generate() ?Element { + mut v := Element{} + v.add(edwards25519.d_const, edwards25519.d_const) + return v +} + +// id_point_generate is the point at infinity. +fn id_point_generate() ?Point { + mut p := Point{} + p.set_bytes(edwards25519.id_bytes) ? + return p +} + +// generator is the canonical curve basepoint. See TestGenerator for the +// correspondence of this encoding with the values in RFC 8032. +fn generator() ?Point { + mut p := Point{} + p.set_bytes(edwards25519.gen_bytes) ? + return p +} + +// Point types. + +struct ProjectiveP1 { +mut: + x Element + y Element + z Element + t Element +} + +struct ProjectiveP2 { +mut: + x Element + y Element + z Element +} + +// Point represents a point on the edwards25519 curve. +// +// This type works similarly to math/big.Int, and all arguments and receivers +// are allowed to alias. +// +// The zero value is NOT valid, and it may be used only as a receiver. +pub struct Point { +mut: + // The point is internally represented in extended coordinates (x, y, z, T) + // where x = x/z, y = y/z, and xy = T/z per https://eprint.iacr.org/2008/522. + x Element + y Element + z Element + t Element + // Make the type not comparable (i.e. used with == or as a map key), as + // equivalent points can be represented by different Go values. + // _ incomparable +} + +fn check_initialized(points ...Point) { + for _, p in points { + if p.x == fe_zero && p.y == fe_zero { + panic('edwards25519: use of uninitialized Point') + } + } +} + +struct ProjectiveCached { +mut: + ypx Element // y + x + ymx Element // y - x + z Element + t2d Element +} + +struct AffineCached { +mut: + ypx Element // y + x + ymx Element // y - x + t2d Element +} + +fn (mut v ProjectiveP2) zero() ProjectiveP2 { + v.x.zero() + v.y.one() + v.z.one() + return v +} + +// set_bytes sets v = x, where x is a 32-byte encoding of v. If x does not +// represent a valid point on the curve, set_bytes returns nil and an error and +// the receiver is unchanged. Otherwise, set_bytes returns v. +// +// Note that set_bytes accepts all non-canonical encodings of valid points. +// That is, it follows decoding rules that match most implementations in +// the ecosystem rather than RFC 8032. +pub fn (mut v Point) set_bytes(x []byte) ?Point { + // Specifically, the non-canonical encodings that are accepted are + // 1) the ones where the edwards25519 element is not reduced (see the + // (*edwards25519.Element).set_bytes docs) and + // 2) the ones where the x-coordinate is zero and the sign bit is set. + // + // This is consistent with crypto/ed25519/internal/edwards25519. Read more + // at https://hdevalence.ca/blog/2020-10-04-its-25519am, specifically the + // "Canonical A, R" section. + mut el0 := Element{} + y := el0.set_bytes(x) or { return error('edwards25519: invalid point encoding length') } + + // -x² + y² = 1 + dx²y² + // x² + dx²y² = x²(dy² + 1) = y² - 1 + // x² = (y² - 1) / (dy² + 1) + + // u = y² - 1 + mut el1 := Element{} + y2 := el1.square(y) + mut el2 := Element{} + u := el2.subtract(y2, fe_one) + + // v = dy² + 1 + mut el3 := Element{} + mut vv := el3.multiply(y2, edwards25519.d_const) + vv = vv.add(vv, fe_one) + + // x = +√(u/v) + mut el4 := Element{} + mut xx, was_square := el4.sqrt_ratio(u, vv) + if was_square == 0 { + return error('edwards25519: invalid point encoding') + } + + // selected the negative square root if the sign bit is set. + mut el5 := Element{} + xx_neg := el5.negate(xx) + xx.selected(xx_neg, xx, int(x[31] >> 7)) + + v.x.set(xx) + v.y.set(y) + v.z.one() + v.t.multiply(xx, y) // xy = T / z + + return v +} + +// `set` sets v = u, and returns v. +pub fn (mut v Point) set(u Point) Point { + v = u + return v +} + +// `new_identity_point` returns a new Point set to the identity. +pub fn new_identity_point() Point { + mut p := Point{} + return p.set(edwards25519.id_point) +} + +// `new_generator_point` returns a new Point set to the canonical generator. +pub fn new_generator_point() Point { + mut p := Point{} + return p.set(edwards25519.gen_point) +} + +fn (mut v ProjectiveCached) zero() ProjectiveCached { + v.ypx.one() + v.ymx.one() + v.z.one() + v.t2d.zero() + return v +} + +fn (mut v AffineCached) zero() AffineCached { + v.ypx.one() + v.ymx.one() + v.t2d.zero() + return v +} + +// Encoding. + +// `bytes` returns the canonical 32-byte encoding of v, according to RFC 8032, +// Section 5.1.2. +pub fn (mut v Point) bytes() []byte { + // This function is outlined to make the allocations inline in the caller + // rather than happen on the heap. + mut buf := [32]byte{} + return v.bytes_generic(mut buf) +} + +fn (mut v Point) bytes_generic(mut buf [32]byte) []byte { + check_initialized(v) + + mut zinv := Element{} + mut x := Element{} + mut y := Element{} + zinv.invert(v.z) // zinv = 1 / z + x.multiply(v.x, zinv) // x = x / z + y.multiply(v.y, zinv) // y = y / z + + mut out := copy_field_element(mut buf, mut y) + unsafe { + // out[31] |= byte(x.is_negative() << 7) //original one + out[31] |= byte(x.is_negative() * 128) // x << 7 == x * 2^7 + } + return out +} + +fn copy_field_element(mut buf [32]byte, mut v Element) []byte { + // this fail in test + /* + copy(buf[..], v.bytes()) + return buf[..] + */ + + // this pass the test + mut out := []byte{len: 32} + for i := 0; i <= buf.len - 1; i++ { + out[i] = v.bytes()[i] + } + + return out +} + +// Conversions. + +fn (mut v ProjectiveP2) from_p1(p ProjectiveP1) ProjectiveP2 { + v.x.multiply(p.x, p.t) + v.y.multiply(p.y, p.z) + v.z.multiply(p.z, p.t) + return v +} + +fn (mut v ProjectiveP2) from_p3(p Point) ProjectiveP2 { + v.x.set(p.x) + v.y.set(p.y) + v.z.set(p.z) + return v +} + +fn (mut v Point) from_p1(p ProjectiveP1) Point { + v.x.multiply(p.x, p.t) + v.y.multiply(p.y, p.z) + v.z.multiply(p.z, p.t) + v.t.multiply(p.x, p.y) + return v +} + +fn (mut v Point) from_p2(p ProjectiveP2) Point { + v.x.multiply(p.x, p.z) + v.y.multiply(p.y, p.z) + v.z.square(p.z) + v.t.multiply(p.x, p.y) + return v +} + +fn (mut v ProjectiveCached) from_p3(p Point) ProjectiveCached { + v.ypx.add(p.y, p.x) + v.ymx.subtract(p.y, p.x) + v.z.set(p.z) + v.t2d.multiply(p.t, edwards25519.d2_const) + return v +} + +fn (mut v AffineCached) from_p3(p Point) AffineCached { + v.ypx.add(p.y, p.x) + v.ymx.subtract(p.y, p.x) + v.t2d.multiply(p.t, edwards25519.d2_const) + + mut invz := Element{} + invz.invert(p.z) + v.ypx.multiply(v.ypx, invz) + v.ymx.multiply(v.ymx, invz) + v.t2d.multiply(v.t2d, invz) + return v +} + +// (Re)addition and subtraction. + +// `add` sets v = p + q, and returns v. +pub fn (mut v Point) add(p Point, q Point) Point { + check_initialized(p, q) + mut pc := ProjectiveCached{} + mut p1 := ProjectiveP1{} + qcached := pc.from_p3(q) + + result := p1.add(p, qcached) + return v.from_p1(result) +} + +// `subtract` sets v = p - q, and returns v. +pub fn (mut v Point) subtract(p Point, q Point) Point { + check_initialized(p, q) + mut pc := ProjectiveCached{} + mut p1 := ProjectiveP1{} + qcached := pc.from_p3(q) + result := p1.sub(p, qcached) + return v.from_p1(result) +} + +fn (mut v ProjectiveP1) add(p Point, q ProjectiveCached) ProjectiveP1 { + // var ypx, ymx, pp, mm, tt2d, zz2 edwards25519.Element + mut ypx := Element{} + mut ymx := Element{} + mut pp := Element{} + mut mm := Element{} + mut tt2d := Element{} + mut zz2 := Element{} + + ypx.add(p.y, p.x) + ymx.subtract(p.y, p.x) + + pp.multiply(ypx, q.ypx) + mm.multiply(ymx, q.ymx) + tt2d.multiply(p.t, q.t2d) + zz2.multiply(p.z, q.z) + + zz2.add(zz2, zz2) + + v.x.subtract(pp, mm) + v.y.add(pp, mm) + v.z.add(zz2, tt2d) + v.t.subtract(zz2, tt2d) + return v +} + +fn (mut v ProjectiveP1) sub(p Point, q ProjectiveCached) ProjectiveP1 { + mut ypx := Element{} + mut ymx := Element{} + mut pp := Element{} + mut mm := Element{} + mut tt2d := Element{} + mut zz2 := Element{} + + ypx.add(p.y, p.x) + ymx.subtract(p.y, p.x) + + pp.multiply(&ypx, q.ymx) // flipped sign + mm.multiply(&ymx, q.ypx) // flipped sign + tt2d.multiply(p.t, q.t2d) + zz2.multiply(p.z, q.z) + + zz2.add(zz2, zz2) + + v.x.subtract(pp, mm) + v.y.add(pp, mm) + v.z.subtract(zz2, tt2d) // flipped sign + v.t.add(zz2, tt2d) // flipped sign + return v +} + +fn (mut v ProjectiveP1) add_affine(p Point, q AffineCached) ProjectiveP1 { + mut ypx := Element{} + mut ymx := Element{} + mut pp := Element{} + mut mm := Element{} + mut tt2d := Element{} + mut z2 := Element{} + + ypx.add(p.y, p.x) + ymx.subtract(p.y, p.x) + + pp.multiply(&ypx, q.ypx) + mm.multiply(&ymx, q.ymx) + tt2d.multiply(p.t, q.t2d) + + z2.add(p.z, p.z) + + v.x.subtract(pp, mm) + v.y.add(pp, mm) + v.z.add(z2, tt2d) + v.t.subtract(z2, tt2d) + return v +} + +fn (mut v ProjectiveP1) sub_affine(p Point, q AffineCached) ProjectiveP1 { + mut ypx := Element{} + mut ymx := Element{} + mut pp := Element{} + mut mm := Element{} + mut tt2d := Element{} + mut z2 := Element{} + + ypx.add(p.y, p.x) + ymx.subtract(p.y, p.x) + + pp.multiply(ypx, q.ymx) // flipped sign + mm.multiply(ymx, q.ypx) // flipped sign + tt2d.multiply(p.t, q.t2d) + + z2.add(p.z, p.z) + + v.x.subtract(pp, mm) + v.y.add(pp, mm) + v.z.subtract(z2, tt2d) // flipped sign + v.t.add(z2, tt2d) // flipped sign + return v +} + +// Doubling. + +fn (mut v ProjectiveP1) double(p ProjectiveP2) ProjectiveP1 { + // var xx, yy, zz2, xplusysq edwards25519.Element + mut xx := Element{} + mut yy := Element{} + mut zz2 := Element{} + mut xplusysq := Element{} + + xx.square(p.x) + yy.square(p.y) + zz2.square(p.z) + zz2.add(zz2, zz2) + xplusysq.add(p.x, p.y) + xplusysq.square(xplusysq) + + v.y.add(yy, xx) + v.z.subtract(yy, xx) + + v.x.subtract(xplusysq, v.y) + v.t.subtract(zz2, v.z) + return v +} + +// Negation. + +// `negate` sets v = -p, and returns v. +pub fn (mut v Point) negate(p Point) Point { + check_initialized(p) + v.x.negate(p.x) + v.y.set(p.y) + v.z.set(p.z) + v.t.negate(p.t) + return v +} + +// `equal` returns 1 if v is equivalent to u, and 0 otherwise. +pub fn (mut v Point) equal(u Point) int { + check_initialized(v, u) + + mut t1 := Element{} + mut t2 := Element{} + mut t3 := Element{} + mut t4 := Element{} + + t1.multiply(v.x, u.z) + t2.multiply(u.x, v.z) + t3.multiply(v.y, u.z) + t4.multiply(u.y, v.z) + + return t1.equal(t2) & t3.equal(t4) +} + +// Constant-time operations + +// selected sets v to a if cond == 1 and to b if cond == 0. +fn (mut v ProjectiveCached) selected(a ProjectiveCached, b ProjectiveCached, cond int) ProjectiveCached { + v.ypx.selected(a.ypx, b.ypx, cond) + v.ymx.selected(a.ymx, b.ymx, cond) + v.z.selected(a.z, b.z, cond) + v.t2d.selected(a.t2d, b.t2d, cond) + return v +} + +// selected sets v to a if cond == 1 and to b if cond == 0. +fn (mut v AffineCached) selected(a AffineCached, b AffineCached, cond int) AffineCached { + v.ypx.selected(a.ypx, b.ypx, cond) + v.ymx.selected(a.ymx, b.ymx, cond) + v.t2d.selected(a.t2d, b.t2d, cond) + return v +} + +// cond_neg negates v if cond == 1 and leaves it unchanged if cond == 0. +fn (mut v ProjectiveCached) cond_neg(cond int) ProjectiveCached { + mut el := Element{} + v.ypx.swap(mut v.ymx, cond) + v.t2d.selected(el.negate(v.t2d), v.t2d, cond) + return v +} + +// cond_neg negates v if cond == 1 and leaves it unchanged if cond == 0. +fn (mut v AffineCached) cond_neg(cond int) AffineCached { + mut el := Element{} + v.ypx.swap(mut v.ymx, cond) + v.t2d.selected(el.negate(v.t2d), v.t2d, cond) + return v +} + +fn check_on_curve(points ...Point) bool { + for p in points { + mut xx := Element{} + mut yy := Element{} + mut zz := Element{} + mut zzzz := Element{} + xx.square(p.x) + yy.square(p.y) + zz.square(p.z) + zzzz.square(zz) + // -x² + y² = 1 + dx²y² + // -(X/Z)² + (Y/Z)² = 1 + d(X/Z)²(Y/Z)² + // (-X² + Y²)/Z² = 1 + (dX²Y²)/Z⁴ + // (-X² + Y²)*Z² = Z⁴ + dX²Y² + mut lhs := Element{} + mut rhs := Element{} + lhs.subtract(yy, xx) + lhs.multiply(lhs, zz) + rhs.multiply(edwards25519.d_const, xx) + rhs.multiply(rhs, yy) + rhs.add(rhs, zzzz) + + if lhs.equal(rhs) != 1 { + return false + } + /* + if lhs.equal(rhs) != 1 { + lg.error('X, Y, and Z do not specify a point on the curve\nX = $p.x \nY = $p.y\nZ = $p.z') + }*/ + + // xy = T/Z + lhs.multiply(p.x, p.y) + rhs.multiply(p.z, p.t) + /* + if lhs.equal(rhs) != 1 { + lg.error('point $i is not valid\nX = $p.x\nY = $p.y\nZ = $p.z') + }*/ + if lhs.equal(rhs) != 1 { + return false + } + } + return true +} diff --git a/vlib/crypto/ed25519/internal/edwards25519/point_test.v b/vlib/crypto/ed25519/internal/edwards25519/point_test.v new file mode 100644 index 0000000000..fabf08dd1f --- /dev/null +++ b/vlib/crypto/ed25519/internal/edwards25519/point_test.v @@ -0,0 +1,125 @@ +module edwards25519 + +import encoding.hex + +const zero_point = Point{fe_zero, fe_zero, fe_zero, fe_zero} + +fn test_invalid_encodings() ? { + // An invalid point, that also happens to have y > p. + invalid := 'efffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f' + inv_bytes := hex.decode(invalid) or { panic(err) } + mut p := new_generator_point() + + out := p.set_bytes(inv_bytes) or { edwards25519.zero_point } + assert out == edwards25519.zero_point + // assert p.equal(bgp) == 1 //not makes sense when error + + assert check_on_curve(p) == true +} + +fn test_add_sub_neg_on_basepoint() ? { + bgp := new_generator_point() + mut idp := new_identity_point() + mut checklhs := Point{} + mut checkrhs := Point{} + + checklhs.add(bgp, bgp) + + mut proj_p1 := ProjectiveP1{} + mut proj_p2 := ProjectiveP2{} + + tmp_p2 := proj_p2.from_p3(bgp) + tmp_p1 := proj_p1.double(tmp_p2) + checkrhs.from_p1(tmp_p1) + + assert checklhs.equal(checkrhs) == 1 + assert check_on_curve(checklhs, checkrhs) == true + + checklhs.subtract(bgp, bgp) + mut p0 := Point{} + bneg := p0.negate(bgp) + checkrhs.add(bgp, bneg) + + assert checklhs.equal(checkrhs) == 1 + assert idp.equal(checklhs) == 1 + assert idp.equal(checkrhs) == 1 + assert check_on_curve(checklhs, checkrhs, bneg) == true +} + +struct NonCanonicalTest { + name string + encoding string + canonical string +} + +fn test_non_canonical_points() ? { + tests := [ + // Points with x = 0 and the sign bit set. With x = 0 the curve equation + // gives y² = 1, so y = ±1. 1 has two valid encodings. + NonCanonicalTest{'y=1,sign-', '0100000000000000000000000000000000000000000000000000000000000080', '0100000000000000000000000000000000000000000000000000000000000000'}, + NonCanonicalTest{'y=p+1,sign-', 'eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '0100000000000000000000000000000000000000000000000000000000000000'}, + NonCanonicalTest{'y=p-1,sign-', 'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 'ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f'}, + // Non-canonical y encodings with values 2²⁵⁵-19 (p) to 2²⁵⁵-1 (p+18). + NonCanonicalTest{'y=p,sign+', 'edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '0000000000000000000000000000000000000000000000000000000000000000'}, + NonCanonicalTest{'y=p,sign-', 'edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '0000000000000000000000000000000000000000000000000000000000000080'}, + NonCanonicalTest{'y=p+1,sign+', 'eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '0100000000000000000000000000000000000000000000000000000000000000'}, + // "y=p+1,sign-" is already tested above. + // p+2 is not a valid y-coordinate. + NonCanonicalTest{'y=p+3,sign+', 'f0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '0300000000000000000000000000000000000000000000000000000000000000'}, + NonCanonicalTest{'y=p+3,sign-', 'f0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '0300000000000000000000000000000000000000000000000000000000000080'}, + NonCanonicalTest{'y=p+4,sign+', 'f1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '0400000000000000000000000000000000000000000000000000000000000000'}, + NonCanonicalTest{'y=p+4,sign-', 'f1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '0400000000000000000000000000000000000000000000000000000000000080'}, + NonCanonicalTest{'y=p+5,sign+', 'f2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '0500000000000000000000000000000000000000000000000000000000000000'}, + NonCanonicalTest{'y=p+5,sign-', 'f2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '0500000000000000000000000000000000000000000000000000000000000080'}, + NonCanonicalTest{'y=p+6,sign+', 'f3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '0600000000000000000000000000000000000000000000000000000000000000'}, + NonCanonicalTest{'y=p+6,sign-', 'f3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '0600000000000000000000000000000000000000000000000000000000000080'}, + // p+7 is not a valid y-coordinate. + // p+8 is not a valid y-coordinate. + NonCanonicalTest{'y=p+9,sign+', 'f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '0900000000000000000000000000000000000000000000000000000000000000'}, + NonCanonicalTest{'y=p+9,sign-', 'f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '0900000000000000000000000000000000000000000000000000000000000080'}, + NonCanonicalTest{'y=p+10,sign+', 'f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '0a00000000000000000000000000000000000000000000000000000000000000'}, + NonCanonicalTest{'y=p+10,sign-', 'f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '0a00000000000000000000000000000000000000000000000000000000000080'}, + // p+11 is not a valid y-coordinate. + // p+12 is not a valid y-coordinate. + // p+13 is not a valid y-coordinate. + NonCanonicalTest{'y=p+14,sign+', 'fbffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '0e00000000000000000000000000000000000000000000000000000000000000'}, + NonCanonicalTest{'y=p+14,sign-', 'fbffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '0e00000000000000000000000000000000000000000000000000000000000080'}, + NonCanonicalTest{'y=p+15,sign+', 'fcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '0f00000000000000000000000000000000000000000000000000000000000000'}, + NonCanonicalTest{'y=p+15,sign-', 'fcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '0f00000000000000000000000000000000000000000000000000000000000080'}, + NonCanonicalTest{'y=p+16,sign+', 'fdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '1000000000000000000000000000000000000000000000000000000000000000'}, + NonCanonicalTest{'y=p+16,sign-', 'fdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '1000000000000000000000000000000000000000000000000000000000000080'}, + // p+17 is not a valid y-coordinate. + NonCanonicalTest{'y=p+18,sign+', 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f', '1200000000000000000000000000000000000000000000000000000000000000'}, + NonCanonicalTest{'y=p+18,sign-', 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', '1200000000000000000000000000000000000000000000000000000000000080'}, + ] + for tt in tests { + // t.Run(tt.name, func(t *testing.T) { + // p1, err := new(Point).SetBytes(decodeHex(tt.encoding)) + mut p1 := Point{} + p1.set_bytes(hex.decode(tt.encoding) ?) ? + + // p2, err := new(Point).SetBytes(decodeHex(tt.canonical)) + mut p2 := Point{} + p2.set_bytes(hex.decode(tt.canonical) ?) ? + + assert p1.equal(p2) == 1 + assert p1.bytes() == p2.bytes() + assert hex.encode(p1.bytes()) == tt.canonical // NEED FIX! + + assert check_on_curve(p1, p2) == true + } +} + +fn test_generator() { + // These are the coordinates of B from RFC 8032, Section 5.1, converted to + // little endian hex. + x := '1ad5258f602d56c9b2a7259560c72c695cdcd6fd31e2a4c0fe536ecdd3366921' + y := '5866666666666666666666666666666666666666666666666666666666666666' + mut b := new_generator_point() + + assert hex.encode(b.x.bytes()) == x + assert hex.encode(b.y.bytes()) == y + assert b.z.equal(fe_one) == 1 + // Check that t is correct. + assert check_on_curve(b) == true +} diff --git a/vlib/crypto/ed25519/internal/edwards25519/scalar.v b/vlib/crypto/ed25519/internal/edwards25519/scalar.v new file mode 100644 index 0000000000..30f6d05f98 --- /dev/null +++ b/vlib/crypto/ed25519/internal/edwards25519/scalar.v @@ -0,0 +1,1173 @@ +module edwards25519 + +import rand +import encoding.binary +import crypto.internal.subtle + +// A Scalar is an integer modulo +// +// l = 2^252 + 27742317777372353535851937790883648493 +// +// which is the prime order of the edwards25519 group. +// +// This type works similarly to math/big.Int, and all arguments and +// receivers are allowed to alias. +// +// The zero value is a valid zero element. +struct Scalar { +mut: + // s is the Scalar value in little-endian. The value is always reduced + // between operations. + s [32]byte +} + +const ( + sc_zero = Scalar{ + s: [byte(0), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0]! + } + + sc_one = Scalar{ + s: [byte(1), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0]! + } + + sc_minus_one = Scalar{ + s: [byte(236), 211, 245, 92, 26, 99, 18, 88, 214, 156, 247, 162, 222, 249, 222, 20, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16]! + } +) + +// `new_scalar` return new zero scalar +pub fn new_scalar() Scalar { + return Scalar{} +} + +// `add` sets s = x + y mod l, and returns s. +pub fn (mut s Scalar) add(x Scalar, y Scalar) Scalar { + // s = 1 * x + y mod l + sc_mul_add(mut s.s, edwards25519.sc_one.s, x.s, y.s) + return s +} + +// `multiply_add` sets s = x * y + z mod l, and returns s. +pub fn (mut s Scalar) multiply_add(x Scalar, y Scalar, z Scalar) Scalar { + sc_mul_add(mut s.s, x.s, y.s, z.s) + return s +} + +// `subtract` sets s = x - y mod l, and returns s. +pub fn (mut s Scalar) subtract(x Scalar, y Scalar) Scalar { + // s = -1 * y + x mod l + sc_mul_add(mut s.s, edwards25519.sc_minus_one.s, y.s, x.s) + return s +} + +// `negate` sets s = -x mod l, and returns s. +pub fn (mut s Scalar) negate(x Scalar) Scalar { + // s = -1 * x + 0 mod l + sc_mul_add(mut s.s, edwards25519.sc_minus_one.s, x.s, edwards25519.sc_zero.s) + return s +} + +// `multiply` sets s = x * y mod l, and returns s. +pub fn (mut s Scalar) multiply(x Scalar, y Scalar) Scalar { + // s = x * y + 0 mod l + sc_mul_add(mut s.s, x.s, y.s, edwards25519.sc_zero.s) + return s +} + +// `set` sets s = x, and returns s. +pub fn (mut s Scalar) set(x Scalar) Scalar { + s = x + return s +} + +// `set_uniform_bytes` sets s to an uniformly distributed value given 64 uniformly +// distributed random bytes. If x is not of the right length, set_uniform_bytes +// returns an error, and the receiver is unchanged. +pub fn (mut s Scalar) set_uniform_bytes(x []byte) ?Scalar { + if x.len != 64 { + return error('edwards25519: invalid set_uniform_bytes input length') + } + mut wide_bytes := []byte{len: 64} + copy(wide_bytes, x) + // for i, item in x { + // wide_bytes[i] = item + //} + sc_reduce(mut s.s, mut wide_bytes) + return s +} + +// `set_canonical_bytes` sets s = x, where x is a 32-byte little-endian encoding of +// s, and returns s. If x is not a canonical encoding of s, set_canonical_bytes +// returns an error, and the receiver is unchanged. +pub fn (mut s Scalar) set_canonical_bytes(x []byte) ?Scalar { + if x.len != 32 { + return error('invalid scalar length') + } + // mut bb := []byte{len:32} + mut ss := Scalar{} + for i, item in x { + ss.s[i] = item + } + + //_ := copy(ss.s[..], x) //its not working + if !is_reduced(ss) { + return error('invalid scalar encoding') + } + s.s = ss.s + return s +} + +// `is_reduced` returns whether the given scalar is reduced modulo l. +fn is_reduced(s Scalar) bool { + for i := s.s.len - 1; i >= 0; i-- { + if s.s[i] > edwards25519.sc_minus_one.s[i] { + return false + } + if s.s[i] < edwards25519.sc_minus_one.s[i] { + return true + } + /* + switch { + case s.s[i] > sc_minus_one.s[i]: + return false + case s.s[i] < sc_minus_one.s[i]: + return true + } + */ + } + return true +} + +// `set_bytes_with_clamping` applies the buffer pruning described in RFC 8032, +// Section 5.1.5 (also known as clamping) and sets s to the result. The input +// must be 32 bytes, and it is not modified. If x is not of the right length, +// `set_bytes_with_clamping` returns an error, and the receiver is unchanged. +// +// Note that since Scalar values are always reduced modulo the prime order of +// the curve, the resulting value will not preserve any of the cofactor-clearing +// properties that clamping is meant to provide. It will however work as +// expected as long as it is applied to points on the prime order subgroup, like +// in Ed25519. In fact, it is lost to history why RFC 8032 adopted the +// irrelevant RFC 7748 clamping, but it is now required for compatibility. +pub fn (mut s Scalar) set_bytes_with_clamping(x []byte) ?Scalar { + // The description above omits the purpose of the high bits of the clamping + // for brevity, but those are also lost to reductions, and are also + // irrelevant to edwards25519 as they protect against a specific + // implementation bug that was once observed in a generic Montgomery ladder. + if x.len != 32 { + return error('edwards25519: invalid set_bytes_with_clamping input length') + } + + mut wide_bytes := []byte{len: 64, cap: 64} + copy(wide_bytes, x) + // for i, item in x { + // wide_bytes[i] = item + //} + wide_bytes[0] &= 248 + wide_bytes[31] &= 63 + wide_bytes[31] |= 64 + sc_reduce(mut s.s, mut wide_bytes) + return s +} + +// `bytes` returns the canonical 32-byte little-endian encoding of s. +pub fn (mut s Scalar) bytes() []byte { + mut buf := []byte{len: 32} + copy(buf, s.s[..]) + return buf +} + +// `equal` returns 1 if s and t are equal, and 0 otherwise. +pub fn (s Scalar) equal(t Scalar) int { + return subtle.constant_time_compare(s.s[..], t.s[..]) +} + +// sc_mul_add and sc_reduce are ported from the public domain, “ref10” +// implementation of ed25519 from SUPERCOP. +fn load3(inp []byte) i64 { + mut r := i64(inp[0]) + r |= i64(inp[1]) * 256 // << 8 + r |= i64(inp[2]) * 65536 // << 16 + return r +} + +fn load4(inp []byte) i64 { + mut r := i64(inp[0]) + r |= i64(inp[1]) * 256 + r |= i64(inp[2]) * 65536 + r |= i64(inp[3]) * 16777216 + return r +} + +// Input: +// a[0]+256*a[1]+...+256^31*a[31] = a +// b[0]+256*b[1]+...+256^31*b[31] = b +// c[0]+256*c[1]+...+256^31*c[31] = c +// +// Output: +// s[0]+256*s[1]+...+256^31*s[31] = (ab+c) mod l +// where l = 2^252 + 27742317777372353535851937790883648493. +fn sc_mul_add(mut s [32]byte, a [32]byte, b [32]byte, c [32]byte) { + a0 := 2097151 & load3(a[..]) + a1 := 2097151 & (load4(a[2..]) >> 5) + a2 := 2097151 & (load3(a[5..]) >> 2) + a3 := 2097151 & (load4(a[7..]) >> 7) + a4 := 2097151 & (load4(a[10..]) >> 4) + a5 := 2097151 & (load3(a[13..]) >> 1) + a6 := 2097151 & (load4(a[15..]) >> 6) + a7 := 2097151 & (load3(a[18..]) >> 3) + a8 := 2097151 & load3(a[21..]) + a9 := 2097151 & (load4(a[23..]) >> 5) + a10 := 2097151 & (load3(a[26..]) >> 2) + a11 := (load4(a[28..]) >> 7) + b0 := 2097151 & load3(b[..]) + b1 := 2097151 & (load4(b[2..]) >> 5) + b2 := 2097151 & (load3(b[5..]) >> 2) + b3 := 2097151 & (load4(b[7..]) >> 7) + b4 := 2097151 & (load4(b[10..]) >> 4) + b5 := 2097151 & (load3(b[13..]) >> 1) + b6 := 2097151 & (load4(b[15..]) >> 6) + b7 := 2097151 & (load3(b[18..]) >> 3) + b8 := 2097151 & load3(b[21..]) + b9 := 2097151 & (load4(b[23..]) >> 5) + b10 := 2097151 & (load3(b[26..]) >> 2) + b11 := (load4(b[28..]) >> 7) + c0 := 2097151 & load3(c[..]) + c1 := 2097151 & (load4(c[2..]) >> 5) + c2 := 2097151 & (load3(c[5..]) >> 2) + c3 := 2097151 & (load4(c[7..]) >> 7) + c4 := 2097151 & (load4(c[10..]) >> 4) + c5 := 2097151 & (load3(c[13..]) >> 1) + c6 := 2097151 & (load4(c[15..]) >> 6) + c7 := 2097151 & (load3(c[18..]) >> 3) + c8 := 2097151 & load3(c[21..]) + c9 := 2097151 & (load4(c[23..]) >> 5) + c10 := 2097151 & (load3(c[26..]) >> 2) + c11 := (load4(c[28..]) >> 7) + + mut carry := [23]i64{} // original one + // mut carry := [23]u64{} + + mut s0 := c0 + a0 * b0 + mut s1 := c1 + a0 * b1 + a1 * b0 + mut s2 := c2 + a0 * b2 + a1 * b1 + a2 * b0 + mut s3 := c3 + a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0 + mut s4 := c4 + a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0 + mut s5 := c5 + a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0 + mut s6 := c6 + a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0 + mut s7 := c7 + a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + a6 * b1 + a7 * b0 + mut s8 := c8 + a0 * b8 + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 + a6 * b2 + a7 * b1 + + a8 * b0 + mut s9 := c9 + a0 * b9 + a1 * b8 + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 + a6 * b3 + a7 * b2 + + a8 * b1 + a9 * b0 + mut s10 := c10 + a0 * b10 + a1 * b9 + a2 * b8 + a3 * b7 + a4 * b6 + a5 * b5 + a6 * b4 + + a7 * b3 + a8 * b2 + a9 * b1 + a10 * b0 + mut s11 := c11 + a0 * b11 + a1 * b10 + a2 * b9 + a3 * b8 + a4 * b7 + a5 * b6 + a6 * b5 + + a7 * b4 + a8 * b3 + a9 * b2 + a10 * b1 + a11 * b0 + mut s12 := a1 * b11 + a2 * b10 + a3 * b9 + a4 * b8 + a5 * b7 + a6 * b6 + a7 * b5 + a8 * b4 + + a9 * b3 + a10 * b2 + a11 * b1 + mut s13 := a2 * b11 + a3 * b10 + a4 * b9 + a5 * b8 + a6 * b7 + a7 * b6 + a8 * b5 + a9 * b4 + + a10 * b3 + a11 * b2 + mut s14 := a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 + a9 * b5 + a10 * b4 + + a11 * b3 + mut s15 := a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 + a10 * b5 + a11 * b4 + mut s16 := a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5 + mut s17 := a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6 + mut s18 := a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7 + mut s19 := a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8 + mut s20 := a9 * b11 + a10 * b10 + a11 * b9 + mut s21 := a10 * b11 + a11 * b10 + mut s22 := a11 * b11 + + mut s23 := i64(0) // original + // mut s23 := u64(0) + + // carry[0] = (s0 + (1048576)) >> 21 + carry[0] = (s0 + (1048576)) >> 21 + s1 += carry[0] + s0 -= carry[0] * 2097152 + carry[2] = (s2 + (1048576)) >> 21 + s3 += carry[2] + s2 -= carry[2] * 2097152 + carry[4] = (s4 + (1048576)) >> 21 + s5 += carry[4] + s4 -= carry[4] * 2097152 + carry[6] = (s6 + (1048576)) >> 21 + s7 += carry[6] + s6 -= carry[6] * 2097152 + carry[8] = (s8 + (1048576)) >> 21 + s9 += carry[8] + s8 -= carry[8] * 2097152 + carry[10] = (s10 + (1048576)) >> 21 + s11 += carry[10] + s10 -= carry[10] * 2097152 + carry[12] = (s12 + (1048576)) >> 21 + s13 += carry[12] + s12 -= carry[12] * 2097152 + carry[14] = (s14 + (1048576)) >> 21 + s15 += carry[14] + s14 -= carry[14] * 2097152 + carry[16] = (s16 + (1048576)) >> 21 + s17 += carry[16] + s16 -= carry[16] * 2097152 + carry[18] = (s18 + (1048576)) >> 21 + s19 += carry[18] + s18 -= carry[18] * 2097152 + carry[20] = (s20 + (1048576)) >> 21 + s21 += carry[20] + s20 -= carry[20] * 2097152 + carry[22] = (s22 + (1048576)) >> 21 + s23 += carry[22] + s22 -= carry[22] * 2097152 + + carry[1] = (s1 + (1048576)) >> 21 + s2 += carry[1] + s1 -= carry[1] * 2097152 + carry[3] = (s3 + (1048576)) >> 21 + s4 += carry[3] + s3 -= carry[3] * 2097152 + carry[5] = (s5 + (1048576)) >> 21 + s6 += carry[5] + s5 -= carry[5] * 2097152 + carry[7] = (s7 + (1048576)) >> 21 + s8 += carry[7] + s7 -= carry[7] * 2097152 + carry[9] = (s9 + (1048576)) >> 21 + s10 += carry[9] + s9 -= carry[9] * 2097152 + carry[11] = (s11 + (1048576)) >> 21 + s12 += carry[11] + s11 -= carry[11] * 2097152 + carry[13] = (s13 + (1048576)) >> 21 + s14 += carry[13] + s13 -= carry[13] * 2097152 + carry[15] = (s15 + (1048576)) >> 21 + s16 += carry[15] + s15 -= carry[15] * 2097152 + carry[17] = (s17 + (1048576)) >> 21 + s18 += carry[17] + s17 -= carry[17] * 2097152 + carry[19] = (s19 + (1048576)) >> 21 + s20 += carry[19] + s19 -= carry[19] * 2097152 + carry[21] = (s21 + (1048576)) >> 21 + s22 += carry[21] + s21 -= carry[21] * 2097152 + + s11 += s23 * 666643 + s12 += s23 * 470296 + s13 += s23 * 654183 + s14 -= s23 * 997805 + s15 += s23 * 136657 + s16 -= s23 * 683901 + s23 = 0 + + s10 += s22 * 666643 + s11 += s22 * 470296 + s12 += s22 * 654183 + s13 -= s22 * 997805 + s14 += s22 * 136657 + s15 -= s22 * 683901 + s22 = 0 + + s9 += s21 * 666643 + s10 += s21 * 470296 + s11 += s21 * 654183 + s12 -= s21 * 997805 + s13 += s21 * 136657 + s14 -= s21 * 683901 + s21 = 0 + + s8 += s20 * 666643 + s9 += s20 * 470296 + s10 += s20 * 654183 + s11 -= s20 * 997805 + s12 += s20 * 136657 + s13 -= s20 * 683901 + s20 = 0 + + s7 += s19 * 666643 + s8 += s19 * 470296 + s9 += s19 * 654183 + s10 -= s19 * 997805 + s11 += s19 * 136657 + s12 -= s19 * 683901 + s19 = 0 + + s6 += s18 * 666643 + s7 += s18 * 470296 + s8 += s18 * 654183 + s9 -= s18 * 997805 + s10 += s18 * 136657 + s11 -= s18 * 683901 + s18 = 0 + + carry[6] = (s6 + (1048576)) >> 21 + s7 += carry[6] + s6 -= carry[6] * 2097152 + carry[8] = (s8 + (1048576)) >> 21 + s9 += carry[8] + s8 -= carry[8] * 2097152 + carry[10] = (s10 + (1048576)) >> 21 + s11 += carry[10] + s10 -= carry[10] * 2097152 + carry[12] = (s12 + (1048576)) >> 21 + s13 += carry[12] + s12 -= carry[12] * 2097152 + carry[14] = (s14 + (1048576)) >> 21 + s15 += carry[14] + s14 -= carry[14] * 2097152 + carry[16] = (s16 + (1048576)) >> 21 + s17 += carry[16] + s16 -= carry[16] * 2097152 + + carry[7] = (s7 + (1048576)) >> 21 + s8 += carry[7] + s7 -= carry[7] * 2097152 + carry[9] = (s9 + (1048576)) >> 21 + s10 += carry[9] + s9 -= carry[9] * 2097152 + carry[11] = (s11 + (1048576)) >> 21 + s12 += carry[11] + s11 -= carry[11] * 2097152 + carry[13] = (s13 + (1048576)) >> 21 + s14 += carry[13] + s13 -= carry[13] * 2097152 + carry[15] = (s15 + (1048576)) >> 21 + s16 += carry[15] + s15 -= carry[15] * 2097152 + + s5 += s17 * 666643 + s6 += s17 * 470296 + s7 += s17 * 654183 + s8 -= s17 * 997805 + s9 += s17 * 136657 + s10 -= s17 * 683901 + s17 = 0 + + s4 += s16 * 666643 + s5 += s16 * 470296 + s6 += s16 * 654183 + s7 -= s16 * 997805 + s8 += s16 * 136657 + s9 -= s16 * 683901 + s16 = 0 + + s3 += s15 * 666643 + s4 += s15 * 470296 + s5 += s15 * 654183 + s6 -= s15 * 997805 + s7 += s15 * 136657 + s8 -= s15 * 683901 + s15 = 0 + + s2 += s14 * 666643 + s3 += s14 * 470296 + s4 += s14 * 654183 + s5 -= s14 * 997805 + s6 += s14 * 136657 + s7 -= s14 * 683901 + s14 = 0 + + s1 += s13 * 666643 + s2 += s13 * 470296 + s3 += s13 * 654183 + s4 -= s13 * 997805 + s5 += s13 * 136657 + s6 -= s13 * 683901 + s13 = 0 + + s0 += s12 * 666643 + s1 += s12 * 470296 + s2 += s12 * 654183 + s3 -= s12 * 997805 + s4 += s12 * 136657 + s5 -= s12 * 683901 + s12 = 0 + + carry[0] = (s0 + (1048576)) >> 21 + s1 += carry[0] + s0 -= carry[0] * 2097152 + carry[2] = (s2 + (1048576)) >> 21 + s3 += carry[2] + s2 -= carry[2] * 2097152 + carry[4] = (s4 + (1048576)) >> 21 + s5 += carry[4] + s4 -= carry[4] * 2097152 + carry[6] = (s6 + (1048576)) >> 21 + s7 += carry[6] + s6 -= carry[6] * 2097152 + carry[8] = (s8 + (1048576)) >> 21 + s9 += carry[8] + s8 -= carry[8] * 2097152 + carry[10] = (s10 + (1048576)) >> 21 + s11 += carry[10] + s10 -= carry[10] * 2097152 + + carry[1] = (s1 + (1048576)) >> 21 + s2 += carry[1] + s1 -= carry[1] * 2097152 + carry[3] = (s3 + (1048576)) >> 21 + s4 += carry[3] + s3 -= carry[3] * 2097152 + carry[5] = (s5 + (1048576)) >> 21 + s6 += carry[5] + s5 -= carry[5] * 2097152 + carry[7] = (s7 + (1048576)) >> 21 + s8 += carry[7] + s7 -= carry[7] * 2097152 + carry[9] = (s9 + (1048576)) >> 21 + s10 += carry[9] + s9 -= carry[9] * 2097152 + carry[11] = (s11 + (1048576)) >> 21 + s12 += carry[11] + s11 -= carry[11] * 2097152 + + s0 += s12 * 666643 + s1 += s12 * 470296 + s2 += s12 * 654183 + s3 -= s12 * 997805 + s4 += s12 * 136657 + s5 -= s12 * 683901 + s12 = 0 + + carry[0] = s0 >> 21 + s1 += carry[0] + s0 -= carry[0] * 2097152 + carry[1] = s1 >> 21 + s2 += carry[1] + s1 -= carry[1] * 2097152 + carry[2] = s2 >> 21 + s3 += carry[2] + s2 -= carry[2] * 2097152 + carry[3] = s3 >> 21 + s4 += carry[3] + s3 -= carry[3] * 2097152 + carry[4] = s4 >> 21 + s5 += carry[4] + s4 -= carry[4] * 2097152 + carry[5] = s5 >> 21 + s6 += carry[5] + s5 -= carry[5] * 2097152 + carry[6] = s6 >> 21 + s7 += carry[6] + s6 -= carry[6] * 2097152 + carry[7] = s7 >> 21 + s8 += carry[7] + s7 -= carry[7] * 2097152 + carry[8] = s8 >> 21 + s9 += carry[8] + s8 -= carry[8] * 2097152 + carry[9] = s9 >> 21 + s10 += carry[9] + s9 -= carry[9] * 2097152 + carry[10] = s10 >> 21 + s11 += carry[10] + s10 -= carry[10] * 2097152 + carry[11] = s11 >> 21 + s12 += carry[11] + s11 -= carry[11] * 2097152 + + s0 += s12 * 666643 + s1 += s12 * 470296 + s2 += s12 * 654183 + s3 -= s12 * 997805 + s4 += s12 * 136657 + s5 -= s12 * 683901 + s12 = 0 + + carry[0] = s0 >> 21 + s1 += carry[0] + s0 -= carry[0] * 2097152 + carry[1] = s1 >> 21 + s2 += carry[1] + s1 -= carry[1] * 2097152 + carry[2] = s2 >> 21 + s3 += carry[2] + s2 -= carry[2] * 2097152 + carry[3] = s3 >> 21 + s4 += carry[3] + s3 -= carry[3] * 2097152 + carry[4] = s4 >> 21 + s5 += carry[4] + s4 -= carry[4] * 2097152 + carry[5] = s5 >> 21 + s6 += carry[5] + s5 -= carry[5] * 2097152 + carry[6] = s6 >> 21 + s7 += carry[6] + s6 -= carry[6] * 2097152 + carry[7] = s7 >> 21 + s8 += carry[7] + s7 -= carry[7] * 2097152 + carry[8] = s8 >> 21 + s9 += carry[8] + s8 -= carry[8] * 2097152 + carry[9] = s9 >> 21 + s10 += carry[9] + s9 -= carry[9] * 2097152 + carry[10] = s10 >> 21 + s11 += carry[10] + s10 -= carry[10] * 2097152 + + s[0] = byte(s0 >> 0) + s[1] = byte(s0 >> 8) + s[2] = byte((s0 >> 16) | (s1 * 32)) + s[3] = byte(s1 >> 3) + s[4] = byte(s1 >> 11) + s[5] = byte((s1 >> 19) | (s2 * 4)) + s[6] = byte(s2 >> 6) + s[7] = byte((s2 >> 14) | (s3 * 128)) + s[8] = byte(s3 >> 1) + s[9] = byte(s3 >> 9) + s[10] = byte((s3 >> 17) | (s4 * 16)) + s[11] = byte(s4 >> 4) + s[12] = byte(s4 >> 12) + s[13] = byte((s4 >> 20) | (s5 * 2)) + s[14] = byte(s5 >> 7) + s[15] = byte((s5 >> 15) | (s6 * 64)) + s[16] = byte(s6 >> 2) + s[17] = byte(s6 >> 10) + s[18] = byte((s6 >> 18) | (s7 * 8)) + s[19] = byte(s7 >> 5) + s[20] = byte(s7 >> 13) + s[21] = byte(s8 >> 0) + s[22] = byte(s8 >> 8) + s[23] = byte((s8 >> 16) | (s9 * 32)) + s[24] = byte(s9 >> 3) + s[25] = byte(s9 >> 11) + s[26] = byte((s9 >> 19) | (s10 * 4)) + s[27] = byte(s10 >> 6) + s[28] = byte((s10 >> 14) | (s11 * 128)) + s[29] = byte(s11 >> 1) + s[30] = byte(s11 >> 9) + s[31] = byte(s11 >> 17) +} + +// Input: +// s[0]+256*s[1]+...+256^63*s[63] = s +// +// Output: +// s[0]+256*s[1]+...+256^31*s[31] = s mod l +// where l = 2^252 + 27742317777372353535851937790883648493. +fn sc_reduce(mut out [32]byte, mut s []byte) { + assert out.len == 32 + assert s.len == 64 + mut s0 := 2097151 & load3(s[..]) + mut s1 := 2097151 & (load4(s[2..]) >> 5) + mut s2 := 2097151 & (load3(s[5..]) >> 2) + mut s3 := 2097151 & (load4(s[7..]) >> 7) + mut s4 := 2097151 & (load4(s[10..]) >> 4) + mut s5 := 2097151 & (load3(s[13..]) >> 1) + mut s6 := 2097151 & (load4(s[15..]) >> 6) + mut s7 := 2097151 & (load3(s[18..]) >> 3) + mut s8 := 2097151 & load3(s[21..]) + mut s9 := 2097151 & (load4(s[23..]) >> 5) + mut s10 := 2097151 & (load3(s[26..]) >> 2) + mut s11 := 2097151 & (load4(s[28..]) >> 7) + mut s12 := 2097151 & (load4(s[31..]) >> 4) + mut s13 := 2097151 & (load3(s[34..]) >> 1) + mut s14 := 2097151 & (load4(s[36..]) >> 6) + mut s15 := 2097151 & (load3(s[39..]) >> 3) + mut s16 := 2097151 & load3(s[42..]) + mut s17 := 2097151 & (load4(s[44..]) >> 5) + mut s18 := 2097151 & (load3(s[47..]) >> 2) + mut s19 := 2097151 & (load4(s[49..]) >> 7) + mut s20 := 2097151 & (load4(s[52..]) >> 4) + mut s21 := 2097151 & (load3(s[55..]) >> 1) + mut s22 := 2097151 & (load4(s[57..]) >> 6) + mut s23 := (load4(s[60..]) >> 3) + + s11 += s23 * 666643 + s12 += s23 * 470296 + s13 += s23 * 654183 + s14 -= s23 * 997805 + s15 += s23 * 136657 + s16 -= s23 * 683901 + s23 = 0 + + s10 += s22 * 666643 + s11 += s22 * 470296 + s12 += s22 * 654183 + s13 -= s22 * 997805 + s14 += s22 * 136657 + s15 -= s22 * 683901 + s22 = 0 + + s9 += s21 * 666643 + s10 += s21 * 470296 + s11 += s21 * 654183 + s12 -= s21 * 997805 + s13 += s21 * 136657 + s14 -= s21 * 683901 + s21 = 0 + + s8 += s20 * 666643 + s9 += s20 * 470296 + s10 += s20 * 654183 + s11 -= s20 * 997805 + s12 += s20 * 136657 + s13 -= s20 * 683901 + s20 = 0 + + s7 += s19 * 666643 + s8 += s19 * 470296 + s9 += s19 * 654183 + s10 -= s19 * 997805 + s11 += s19 * 136657 + s12 -= s19 * 683901 + s19 = 0 + + s6 += s18 * 666643 + s7 += s18 * 470296 + s8 += s18 * 654183 + s9 -= s18 * 997805 + s10 += s18 * 136657 + s11 -= s18 * 683901 + s18 = 0 + + mut carry := [17]i64{} // original one + // mut carry := [17]u64{} + + carry[6] = (s6 + (1048576)) >> 21 + s7 += carry[6] + s6 -= carry[6] * 2097152 + carry[8] = (s8 + (1048576)) >> 21 + s9 += carry[8] + s8 -= carry[8] * 2097152 + carry[10] = (s10 + (1048576)) >> 21 + s11 += carry[10] + s10 -= carry[10] * 2097152 + carry[12] = (s12 + (1048576)) >> 21 + s13 += carry[12] + s12 -= carry[12] * 2097152 + carry[14] = (s14 + (1048576)) >> 21 + s15 += carry[14] + s14 -= carry[14] * 2097152 + carry[16] = (s16 + (1048576)) >> 21 + s17 += carry[16] + s16 -= carry[16] * 2097152 + + carry[7] = (s7 + (1048576)) >> 21 + s8 += carry[7] + s7 -= carry[7] * 2097152 + carry[9] = (s9 + (1048576)) >> 21 + s10 += carry[9] + s9 -= carry[9] * 2097152 + carry[11] = (s11 + (1048576)) >> 21 + s12 += carry[11] + s11 -= carry[11] * 2097152 + carry[13] = (s13 + (1048576)) >> 21 + s14 += carry[13] + s13 -= carry[13] * 2097152 + carry[15] = (s15 + (1048576)) >> 21 + s16 += carry[15] + s15 -= carry[15] * 2097152 + + s5 += s17 * 666643 + s6 += s17 * 470296 + s7 += s17 * 654183 + s8 -= s17 * 997805 + s9 += s17 * 136657 + s10 -= s17 * 683901 + s17 = 0 + + s4 += s16 * 666643 + s5 += s16 * 470296 + s6 += s16 * 654183 + s7 -= s16 * 997805 + s8 += s16 * 136657 + s9 -= s16 * 683901 + s16 = 0 + + s3 += s15 * 666643 + s4 += s15 * 470296 + s5 += s15 * 654183 + s6 -= s15 * 997805 + s7 += s15 * 136657 + s8 -= s15 * 683901 + s15 = 0 + + s2 += s14 * 666643 + s3 += s14 * 470296 + s4 += s14 * 654183 + s5 -= s14 * 997805 + s6 += s14 * 136657 + s7 -= s14 * 683901 + s14 = 0 + + s1 += s13 * 666643 + s2 += s13 * 470296 + s3 += s13 * 654183 + s4 -= s13 * 997805 + s5 += s13 * 136657 + s6 -= s13 * 683901 + s13 = 0 + + s0 += s12 * 666643 + s1 += s12 * 470296 + s2 += s12 * 654183 + s3 -= s12 * 997805 + s4 += s12 * 136657 + s5 -= s12 * 683901 + s12 = 0 + + carry[0] = (s0 + (1048576)) >> 21 + s1 += carry[0] + s0 -= carry[0] * 2097152 + carry[2] = (s2 + (1048576)) >> 21 + s3 += carry[2] + s2 -= carry[2] * 2097152 + carry[4] = (s4 + (1048576)) >> 21 + s5 += carry[4] + s4 -= carry[4] * 2097152 + carry[6] = (s6 + (1048576)) >> 21 + s7 += carry[6] + s6 -= carry[6] * 2097152 + carry[8] = (s8 + (1048576)) >> 21 + s9 += carry[8] + s8 -= carry[8] * 2097152 + carry[10] = (s10 + (1048576)) >> 21 + s11 += carry[10] + s10 -= carry[10] * 2097152 + + carry[1] = (s1 + (1048576)) >> 21 + s2 += carry[1] + s1 -= carry[1] * 2097152 + carry[3] = (s3 + (1048576)) >> 21 + s4 += carry[3] + s3 -= carry[3] * 2097152 + carry[5] = (s5 + (1048576)) >> 21 + s6 += carry[5] + s5 -= carry[5] * 2097152 + carry[7] = (s7 + (1048576)) >> 21 + s8 += carry[7] + s7 -= carry[7] * 2097152 + carry[9] = (s9 + (1048576)) >> 21 + s10 += carry[9] + s9 -= carry[9] * 2097152 + carry[11] = (s11 + (1048576)) >> 21 + s12 += carry[11] + s11 -= carry[11] * 2097152 + + s0 += s12 * 666643 + s1 += s12 * 470296 + s2 += s12 * 654183 + s3 -= s12 * 997805 + s4 += s12 * 136657 + s5 -= s12 * 683901 + s12 = 0 + + carry[0] = s0 >> 21 + s1 += carry[0] + s0 -= carry[0] * 2097152 + carry[1] = s1 >> 21 + s2 += carry[1] + s1 -= carry[1] * 2097152 + carry[2] = s2 >> 21 + s3 += carry[2] + s2 -= carry[2] * 2097152 + carry[3] = s3 >> 21 + s4 += carry[3] + s3 -= carry[3] * 2097152 + carry[4] = s4 >> 21 + s5 += carry[4] + s4 -= carry[4] * 2097152 + carry[5] = s5 >> 21 + s6 += carry[5] + s5 -= carry[5] * 2097152 + carry[6] = s6 >> 21 + s7 += carry[6] + s6 -= carry[6] * 2097152 + carry[7] = s7 >> 21 + s8 += carry[7] + s7 -= carry[7] * 2097152 + carry[8] = s8 >> 21 + s9 += carry[8] + s8 -= carry[8] * 2097152 + carry[9] = s9 >> 21 + s10 += carry[9] + s9 -= carry[9] * 2097152 + carry[10] = s10 >> 21 + s11 += carry[10] + s10 -= carry[10] * 2097152 + carry[11] = s11 >> 21 + s12 += carry[11] + s11 -= carry[11] * 2097152 + + s0 += s12 * 666643 + s1 += s12 * 470296 + s2 += s12 * 654183 + s3 -= s12 * 997805 + s4 += s12 * 136657 + s5 -= s12 * 683901 + s12 = 0 + + carry[0] = s0 >> 21 + s1 += carry[0] + s0 -= carry[0] * 2097152 + carry[1] = s1 >> 21 + s2 += carry[1] + s1 -= carry[1] * 2097152 + carry[2] = s2 >> 21 + s3 += carry[2] + s2 -= carry[2] * 2097152 + carry[3] = s3 >> 21 + s4 += carry[3] + s3 -= carry[3] * 2097152 + carry[4] = s4 >> 21 + s5 += carry[4] + s4 -= carry[4] * 2097152 + carry[5] = s5 >> 21 + s6 += carry[5] + s5 -= carry[5] * 2097152 + carry[6] = s6 >> 21 + s7 += carry[6] + s6 -= carry[6] * 2097152 + carry[7] = s7 >> 21 + s8 += carry[7] + s7 -= carry[7] * 2097152 + carry[8] = s8 >> 21 + s9 += carry[8] + s8 -= carry[8] * 2097152 + carry[9] = s9 >> 21 + s10 += carry[9] + s9 -= carry[9] * 2097152 + carry[10] = s10 >> 21 + s11 += carry[10] + s10 -= carry[10] * 2097152 + + out[0] = byte(s0 >> 0) + out[1] = byte(s0 >> 8) + out[2] = byte((s0 >> 16) | (s1 * 32)) + out[3] = byte(s1 >> 3) + out[4] = byte(s1 >> 11) + out[5] = byte((s1 >> 19) | (s2 * 4)) + out[6] = byte(s2 >> 6) + out[7] = byte((s2 >> 14) | (s3 * 128)) + out[8] = byte(s3 >> 1) + out[9] = byte(s3 >> 9) + out[10] = byte((s3 >> 17) | (s4 * 16)) + out[11] = byte(s4 >> 4) + out[12] = byte(s4 >> 12) + out[13] = byte((s4 >> 20) | (s5 * 2)) + out[14] = byte(s5 >> 7) + out[15] = byte((s5 >> 15) | (s6 * 64)) + out[16] = byte(s6 >> 2) + out[17] = byte(s6 >> 10) + out[18] = byte((s6 >> 18) | (s7 * 8)) + out[19] = byte(s7 >> 5) + out[20] = byte(s7 >> 13) + out[21] = byte(s8 >> 0) + out[22] = byte(s8 >> 8) + out[23] = byte((s8 >> 16) | (s9 * 32)) + out[24] = byte(s9 >> 3) + out[25] = byte(s9 >> 11) + out[26] = byte((s9 >> 19) | (s10 * 4)) + out[27] = byte(s10 >> 6) + out[28] = byte((s10 >> 14) | (s11 * 128)) + out[29] = byte(s11 >> 1) + out[30] = byte(s11 >> 9) + out[31] = byte(s11 >> 17) +} + +// non_adjacent_form computes a width-w non-adjacent form for this scalar. +// +// w must be between 2 and 8, or non_adjacent_form will panic. +fn (mut s Scalar) non_adjacent_form(w u32) []i8 { + // This implementation is adapted from the one + // in curve25519-dalek and is documented there: + // https://github.com/dalek-cryptography/curve25519-dalek/blob/f630041af28e9a405255f98a8a93adca18e4315b/src/scalar.rs#L800-L871 + if s.s[31] > 127 { + panic('scalar has high bit set illegally') + } + if w < 2 { + panic('w must be at least 2 by the definition of NAF') + } else if w > 8 { + panic('NAF digits must fit in i8') + } + + mut naf := []i8{len: 256} + mut digits := [5]u64{} + + for i := 0; i < 4; i++ { + digits[i] = binary.little_endian_u64(s.s[i * 8..]) + } + + width := u64(1 << w) + window_mask := u64(width - 1) + + mut pos := u32(0) + mut carry := u64(0) + for pos < 256 { + idx_64 := pos / 64 + idx_bit := pos % 64 + mut bitbuf := u64(0) + if idx_bit < 64 - w { + // This window's bits are contained in a single u64 + bitbuf = digits[idx_64] >> idx_bit + } else { + // Combine the current 64 bits with bits from the next 64 + bitbuf = (digits[idx_64] >> idx_bit) | (digits[1 + idx_64] << (64 - idx_bit)) + } + + // Add carry into the current window + window := carry + (bitbuf & window_mask) + + if window & 1 == 0 { + // If the window value is even, preserve the carry and continue. + // Why is the carry preserved? + // If carry == 0 and window & 1 == 0, + // then the next carry should be 0 + // If carry == 1 and window & 1 == 0, + // then bit_buf & 1 == 1 so the next carry should be 1 + pos += 1 + continue + } + + if window < width / 2 { + carry = 0 + naf[pos] = i8(window) + } else { + carry = 1 + naf[pos] = i8(window) - i8(width) + } + + pos += w + } + return naf +} + +fn (mut s Scalar) signed_radix16() []i8 { + if s.s[31] > 127 { + panic('scalar has high bit set illegally') + } + + mut digits := []i8{len: 64} + + // Compute unsigned radix-16 digits: + for i := 0; i < 32; i++ { + digits[2 * i] = i8(s.s[i] & 15) + digits[2 * i + 1] = i8((s.s[i] >> 4) & 15) + } + + // Recenter coefficients: + for i := 0; i < 63; i++ { + mut carry := (digits[i] + 8) >> 4 + + // digits[i] -= unsafe { carry * 16 } // original one + digits[i] -= unsafe { carry * 16 } // carry * 16 == carry * + + digits[i + 1] += carry + } + + return digits +} + +// utility function +// generate returns a valid (reduced modulo l) Scalar with a distribution +// weighted towards high, low, and edge values. +fn generate_scalar(size int) ?Scalar { + /* + s := scZero + diceRoll := rand.Intn(100) + switch { + case diceRoll == 0: + case diceRoll == 1: + s = scOne + case diceRoll == 2: + s = scMinusOne + case diceRoll < 5: + // Generate a low scalar in [0, 2^125). + rand.Read(s.s[:16]) + s.s[15] &= (1 * 32) - 1 + case diceRoll < 10: + // Generate a high scalar in [2^252, 2^252 + 2^124). + s.s[31] = 1 * 16 + rand.Read(s.s[:16]) + s.s[15] &= (1 * 16) - 1 + default: + // Generate a valid scalar in [0, l) by returning [0, 2^252) which has a + // negligibly different distribution (the former has a 2^-127.6 chance + // of being out of the latter range). + rand.Read(s.s[:]) + s.s[31] &= (1 * 16) - 1 + } + return reflect.ValueOf(s) + */ + mut s := edwards25519.sc_zero + diceroll := rand.intn(100) + match true { + /* + case diceroll == 0: + case diceroll == 1: + */ + diceroll == 0 || diceroll == 1 { + s = edwards25519.sc_one + } + diceroll == 2 { + s = edwards25519.sc_minus_one + } + diceroll < 5 { + // rand.Read(s.s[:16]) // read random bytes and fill buf + // using builtin rand.read([]buf) + rand.read(mut s.s[..16]) + // buf := rand.read(s.s[..16].len) ? + // copy(s.s[..16], buf) + + /* + for i, item in buf { + s.s[i] = item + } + */ + s.s[15] &= (1 * 32) - 1 + // generate a low scalar in [0, 2^125). + } + diceroll < 10 { + // generate a high scalar in [2^252, 2^252 + 2^124). + s.s[31] = 1 * 16 + // Read generates len(p) random bytes and writes them into p + // rand.Read(s.s[:16]) + rand.read(mut s.s[..16]) + // buf := rand.read(s.s[..16].len) ? + // copy(s.s[..16], buf) + + /* + for i, item in buf { + s.s[i] = item + } + */ + s.s[15] &= (1 * 16) - 1 + } + else { + // generate a valid scalar in [0, l) by returning [0, 2^252) which has a + // negligibly different distribution (the former has a 2^-127.6 chance + // of being out of the latter range). + // rand.Read(s.s[:]) + rand.read(mut s.s[..]) + // buf := crand.read(s.s.len) ? + // copy(s.s[..], buf) + + /* + for i, item in buf { + s.s[i] = item + } + */ + s.s[31] &= (1 * 16) - 1 + } + } + return s +} + +type NotZeroScalar = Scalar + +fn generate_notzero_scalar(size int) ?NotZeroScalar { + mut s := Scalar{} + for s == edwards25519.sc_zero { + s = generate_scalar(size) ? + } + return NotZeroScalar(s) +} diff --git a/vlib/crypto/ed25519/internal/edwards25519/scalar_alias_test.v b/vlib/crypto/ed25519/internal/edwards25519/scalar_alias_test.v new file mode 100644 index 0000000000..ad73c77b2d --- /dev/null +++ b/vlib/crypto/ed25519/internal/edwards25519/scalar_alias_test.v @@ -0,0 +1,106 @@ +module edwards25519 + +fn check_aliasing_onearg(f fn (mut v Scalar, x Scalar) Scalar, mut v Scalar, x Scalar) bool { + x1, mut v1 := x, x + + // Calculate a reference f(x) without aliasing. + mut out := f(mut v, x) + if out != v || !is_reduced(out) { + return false + } + + // Test aliasing the argument and the receiver. + out2 := f(mut v1, v1) + if out2 != v1 || v1 != v || !is_reduced(out2) { + return false + } + + // Ensure the arguments was not modified. + return x == x1 +} + +fn negate_aliasing(mut v Scalar, x Scalar) Scalar { + // mut t := v + return v.negate(x) +} + +fn test_check_aliasing_oneargs() ? { + x := generate_notzero_scalar(10) ? + mut v := generate_notzero_scalar(10) ? + out := check_aliasing_onearg(negate_aliasing, mut v, x) + assert out == true +} + +fn multiply_aliasing(mut v Scalar, x Scalar, y Scalar) Scalar { + return v.multiply(x, y) +} + +fn add_aliasing(mut v Scalar, x Scalar, y Scalar) Scalar { + return v.add(x, y) +} + +fn subtract_aliasing(mut v Scalar, x Scalar, y Scalar) Scalar { + return v.subtract(x, y) +} + +fn test_check_aliasing_twoargs() ? { + fn_with_twoargs := [add_aliasing, multiply_aliasing, subtract_aliasing] + for f in fn_with_twoargs { + mut v := generate_notzero_scalar(10) ? + x := generate_notzero_scalar(10) ? + y := generate_notzero_scalar(10) ? + out := check_aliasing_twoargs(f, mut v, x, y) + assert out == true + } +} + +fn check_aliasing_twoargs(f fn (mut v Scalar, x Scalar, y Scalar) Scalar, mut v Scalar, x Scalar, y Scalar) bool { + x1, y1, mut v1 := x, y, Scalar{} + + // Calculate a reference f(x, y) without aliasing. + mut out := f(mut v, x, y) + if out != v || !is_reduced(out) { + return false + } + + // Test aliasing the first argument and the receiver. + v1 = x + out2 := f(mut v1, v1, y) + if out2 != v1 || v1 != v || !is_reduced(out2) { + return false + } + // Test aliasing the second argument and the receiver. + v1 = y + out3 := f(mut v1, x, v1) + if out3 != v1 || v1 != v || !is_reduced(out3) { + return false + } + + // Calculate a reference f(x, x) without aliasing. + out4 := f(mut v, x, x) + if out4 != v || !is_reduced(out4) { + return false + } + + // Test aliasing the first argument and the receiver. + v1 = x + out5 := f(mut v1, v1, x) + if out5 != v1 || v1 != v || !is_reduced(out5) { + return false + } + // Test aliasing the second argument and the receiver. + v1 = x + out6 := f(mut v1, x, v1) + if out6 != v1 || v1 != v || !is_reduced(out6) { + return false + } + // Test aliasing both arguments and the receiver. + v1 = x + out7 := f(mut v1, v1, v1) + if out7 != v1 || v1 != v || !is_reduced(out7) { + return false + } + + // Ensure the arguments were not modified. + return x == x1 && y == y1 +} diff --git a/vlib/crypto/ed25519/internal/edwards25519/scalar_test.v b/vlib/crypto/ed25519/internal/edwards25519/scalar_test.v new file mode 100644 index 0000000000..12d1c6fcf6 --- /dev/null +++ b/vlib/crypto/ed25519/internal/edwards25519/scalar_test.v @@ -0,0 +1,210 @@ +module edwards25519 + +import os +import rand +import encoding.hex +import math.big + +const github_job = os.getenv('GITHUB_JOB') + +fn testsuite_begin() { + if edwards25519.github_job != '' { + // ensure that the CI does not run flaky tests: + rand.seed([u32(0xffff24), 0xabcd]) + } +} + +fn test_scalar_equal() { + assert sc_one.equal(sc_minus_one) != 1 + + assert sc_minus_one.equal(sc_minus_one) != 0 +} + +fn test_scalar_non_adjacent_form() { + mut s := Scalar{ + s: [byte(0x1a), 0x0e, 0x97, 0x8a, 0x90, 0xf6, 0x62, 0x2d, 0x37, 0x47, 0x02, 0x3f, 0x8a, + 0xd8, 0x26, 0x4d, 0xa7, 0x58, 0xaa, 0x1b, 0x88, 0xe0, 0x40, 0xd1, 0x58, 0x9e, 0x7b, + 0x7f, 0x23, 0x76, 0xef, 0x09]! + } + expected_naf := [i8(0), 13, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, -9, 0, 0, 0, 0, -11, + 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0, 0, 0, 0, 9, 0, 0, 0, 0, -5, 0, 0, 0, 0, 0, 0, 3, 0, 0, + 0, 0, 11, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, -9, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, 9, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, -15, 0, 0, 0, 0, -7, 0, 0, + 0, 0, -9, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, -11, 0, 0, 0, + 0, -7, 0, 0, 0, 0, -13, 0, 0, 0, 0, 11, 0, 0, 0, 0, -9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + -15, 0, 0, 0, 0, 1, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 13, 0, 0, + 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, -9, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, + 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, -15, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 15, 0, 0, 0, 0, 15, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0] + + snaf := s.non_adjacent_form(5) + for i := 0; i < 256; i++ { + assert expected_naf[i] == snaf[i] + } +} + +fn addlike_subneg(x Scalar, y Scalar) bool { + // Compute t1 = x - y + mut t1 := Scalar{} + t1.subtract(x, y) + + // Compute t2 = -y + x + mut t2 := Scalar{} + t2.negate(y) + t2.add(t2, x) + + return t1 == t2 && is_reduced(t1) +} + +fn test_scalar_add_like_subneg() { + for i in 0 .. 15 { + x := generate_scalar(1000) or { panic(err.msg) } + y := generate_scalar(1000) or { panic(err.msg) } + assert addlike_subneg(x, y) == true + } +} + +fn fg(sc Scalar) bool { + return is_reduced(sc) +} + +fn test_scalar_generate() ? { + for i in 0 .. 15 { + sc := generate_scalar(1000) or { panic(err.msg) } + + assert fg(sc) == true + } +} + +// +fn test_scalar_set_canonical_bytes() ? { + for i in 0 .. 10 { + mut buf := rand.bytes(32) or { panic(err.msg) } + mut sc := generate_scalar(1000) or { panic(err.msg) } + buf[buf.len - 1] &= (1 << 4) - 1 + sc = sc.set_canonical_bytes(buf) or { panic(err.msg) } + + assert buf[..] == sc.bytes() + assert is_reduced(sc) + } +} + +fn test_scalar_set_canonical_bytes_round_trip() ? { + for i in 0 .. 10 { + mut sc1 := generate_scalar(2) ? + mut sc2 := generate_scalar(6) ? + sc2.set_canonical_bytes(sc1.bytes()) or { panic(err.msg) } + + assert sc1 == sc2 + } +} + +const ( + sc_error = Scalar{ + s: [32]byte{init: (byte(-1))} + } +) + +fn test_scalar_set_canonical_bytes_on_noncanonical_value() ? { + mut b := sc_minus_one.s + b[31] += 1 + + mut s := sc_one + out := s.set_canonical_bytes(b[..]) or { edwards25519.sc_error } // set_canonical_bytes shouldn't worked on a non-canonical value" + assert out == edwards25519.sc_error + assert s == sc_one +} + +fn test_scalar_set_uniform_bytes() ? { + // mod, _ := new(big.Integer).SetString("27742317777372353535851937790883648493", 10) + mut mod := big.integer_from_string('27742317777372353535851937790883648493') ? + // mod.Add(mod, new(big.Integer).Lsh(big.NewInt(1), 252)) + mod = mod + big.integer_from_i64(1).lshift(252) + + mut sc := generate_scalar(100) ? + inp := rand.bytes(64) ? + + sc.set_uniform_bytes(inp[..]) ? + assert is_reduced(sc) == true + + scbig := bigint_from_le_bytes(sc.s[..]) + inbig := bigint_from_le_bytes(inp) + // return inbig.Mod(inbig, mod).Cmp(scbig) == 0 + _, m := inbig.div_mod(mod) + assert m.abs_cmp(scbig) == 0 // NEED FIX +} + +fn bigint_from_le_bytes(b []byte) big.Integer { + mut bc := b.clone() + buf := swap_endianness(mut bc) // WITHOUT THIS, some test would fail + bg := big.integer_from_bytes(buf) + return bg +} + +fn test_scalar_set_bytes_with_clamping() { + // Generated with libsodium.js 1.0.18 crypto_scalarmult_ed25519_base. + /* + random := "633d368491364dc9cd4c1bf891b1d59460face1644813240a313e61f2c88216e" + s, _ := new(Scalar).SetBytesWithClamping(decodeHex(random)) + p := new(Point).ScalarBaseMult(s) + want := "1d87a9026fd0126a5736fe1628c95dd419172b5b618457e041c9c861b2494a94" + if got := hex.EncodeToString(p.Bytes()); got != want { + t.Errorf("random: got %q, want %q", got, want) + }*/ + random := '633d368491364dc9cd4c1bf891b1d59460face1644813240a313e61f2c88216e' + random_bytes := hex.decode(random) or { panic(err.msg) } + + mut s0 := Scalar{} + s0.set_bytes_with_clamping(random_bytes) or { panic(err.msg) } + + mut p0 := Point{} + p0.scalar_base_mult(mut s0) + + want0 := '1d87a9026fd0126a5736fe1628c95dd419172b5b618457e041c9c861b2494a94' + got0 := hex.encode(p0.bytes()) + + assert got0 == want0 + + zero := '0000000000000000000000000000000000000000000000000000000000000000' + mut s1 := Scalar{} + zero_bytes := hex.decode(zero) or { panic(err.msg) } + s1.set_bytes_with_clamping(zero_bytes) or { panic(err.msg) } + mut p1 := Point{} + p1.scalar_base_mult(mut s1) + + want1 := '693e47972caf527c7883ad1b39822f026f47db2ab0e1919955b8993aa04411d1' + got1 := hex.encode(p1.bytes()) + assert want1 == got1 + + one := 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + mut s2 := Scalar{} + mut one_bytes := hex.decode(one) or { panic(err.msg) } + s2.set_bytes_with_clamping(one_bytes) or { panic(err.msg) } + mut p2 := Point{} + p2.scalar_base_mult(mut s2) + + want2 := '12e9a68b73fd5aacdbcaf3e88c46fea6ebedb1aa84eed1842f07f8edab65e3a7' + got2 := hex.encode(p2.bytes()) + + assert want2 == got2 +} + +fn test_scalar_multiply_distributes_over_add() ? { + x := generate_scalar(100) ? + y := generate_scalar(100) ? + z := generate_scalar(100) ? + + // Compute t1 = (x+y)*z + mut t1 := Scalar{} + t1.add(x, y) + t1.multiply(t1, z) + + // Compute t2 = x*z + y*z + mut t2 := Scalar{} + mut t3 := Scalar{} + t2.multiply(x, z) + t3.multiply(y, z) + t2.add(t2, t3) + + assert t1 == t2 && is_reduced(t1) && is_reduced(t3) +} diff --git a/vlib/crypto/ed25519/internal/edwards25519/scalarmult.v b/vlib/crypto/ed25519/internal/edwards25519/scalarmult.v new file mode 100644 index 0000000000..455d52c0b3 --- /dev/null +++ b/vlib/crypto/ed25519/internal/edwards25519/scalarmult.v @@ -0,0 +1,229 @@ +module edwards25519 + +import sync + +struct BasepointTablePrecomp { +mut: + table []AffineLookupTable + initonce sync.Once +} + +// `basepoint_table` is a set of 32 affineLookupTables, where table i is generated +// from 256i * basepoint. It is precomputed the first time it's used. +fn basepoint_table() []AffineLookupTable { + mut bpt := &BasepointTablePrecomp{ + table: []AffineLookupTable{len: 32} + initonce: sync.new_once() + } + + // replaced to use do_with_param on newest sync lib + /* + bpt.initonce.do(fn [mut bpt] () { + mut p := new_generator_point() + for i := 0; i < 32; i++ { + bpt.table[i].from_p3(p) + for j := 0; j < 8; j++ { + p.add(p, p) + } + } + })*/ + bpt.initonce.do_with_param(fn (mut o BasepointTablePrecomp) { + mut p := new_generator_point() + for i := 0; i < 32; i++ { + o.table[i].from_p3(p) + for j := 0; j < 8; j++ { + p.add(p, p) + } + } + }, bpt) + return bpt.table +} + +// `scalar_base_mult` sets v = x * B, where B is the canonical generator, and +// returns v. +// +// The scalar multiplication is done in constant time. +pub fn (mut v Point) scalar_base_mult(mut x Scalar) Point { + mut bpt_table := basepoint_table() + + // Write x = sum(x_i * 16^i) so x*B = sum( B*x_i*16^i ) + // as described in the Ed25519 paper + // + // Group even and odd coefficients + // x*B = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B + // + x_1*16^1*B + x_3*16^3*B + ... + x_63*16^63*B + // x*B = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B + // + 16*( x_1*16^0*B + x_3*16^2*B + ... + x_63*16^62*B) + // + // We use a lookup table for each i to get x_i*16^(2*i)*B + // and do four doublings to multiply by 16. + digits := x.signed_radix16() + + mut multiple := AffineCached{} + mut tmp1 := ProjectiveP1{} + mut tmp2 := ProjectiveP2{} + + // Accumulate the odd components first + v.set(new_identity_point()) + for i := 1; i < 64; i += 2 { + bpt_table[i / 2].select_into(mut multiple, digits[i]) + tmp1.add_affine(v, multiple) + v.from_p1(tmp1) + } + + // Multiply by 16 + tmp2.from_p3(v) // tmp2 = v in P2 coords + tmp1.double(tmp2) // tmp1 = 2*v in P1xP1 coords + tmp2.from_p1(tmp1) // tmp2 = 2*v in P2 coords + tmp1.double(tmp2) // tmp1 = 4*v in P1xP1 coords + tmp2.from_p1(tmp1) // tmp2 = 4*v in P2 coords + tmp1.double(tmp2) // tmp1 = 8*v in P1xP1 coords + tmp2.from_p1(tmp1) // tmp2 = 8*v in P2 coords + tmp1.double(tmp2) // tmp1 = 16*v in P1xP1 coords + v.from_p1(tmp1) // now v = 16*(odd components) + + // Accumulate the even components + for j := 0; j < 64; j += 2 { + bpt_table[j / 2].select_into(mut multiple, digits[j]) + tmp1.add_affine(v, multiple) + v.from_p1(tmp1) + } + + return v +} + +// `scalar_mult` sets v = x * q, and returns v. +// +// The scalar multiplication is done in constant time. +pub fn (mut v Point) scalar_mult(mut x Scalar, q Point) Point { + check_initialized(q) + + mut table := ProjLookupTable{} + table.from_p3(q) + + // Write x = sum(x_i * 16^i) + // so x*Q = sum( Q*x_i*16^i ) + // = Q*x_0 + 16*(Q*x_1 + 16*( ... + Q*x_63) ... ) + // <------compute inside out--------- + // + // We use the lookup table to get the x_i*Q values + // and do four doublings to compute 16*Q + digits := x.signed_radix16() + + // Unwrap first loop iteration to save computing 16*identity + mut multiple := ProjectiveCached{} + mut tmp1 := ProjectiveP1{} + mut tmp2 := ProjectiveP2{} + table.select_into(mut multiple, digits[63]) + + v.set(new_identity_point()) + tmp1.add(v, multiple) // tmp1 = x_63*Q in P1xP1 coords + for i := 62; i >= 0; i-- { + tmp2.from_p1(tmp1) // tmp2 = (prev) in P2 coords + tmp1.double(tmp2) // tmp1 = 2*(prev) in P1xP1 coords + tmp2.from_p1(tmp1) // tmp2 = 2*(prev) in P2 coords + tmp1.double(tmp2) // tmp1 = 4*(prev) in P1xP1 coords + tmp2.from_p1(tmp1) // tmp2 = 4*(prev) in P2 coords + tmp1.double(tmp2) // tmp1 = 8*(prev) in P1xP1 coords + tmp2.from_p1(tmp1) // tmp2 = 8*(prev) in P2 coords + tmp1.double(tmp2) // tmp1 = 16*(prev) in P1xP1 coords + v.from_p1(tmp1) // v = 16*(prev) in P3 coords + table.select_into(mut multiple, digits[i]) + tmp1.add(v, multiple) // tmp1 = x_i*Q + 16*(prev) in P1xP1 coords + } + v.from_p1(tmp1) + return v +} + +struct BasepointNaftablePrecomp { +mut: + table NafLookupTable8 + initonce sync.Once +} + +fn basepoint_naf_table() NafLookupTable8 { + mut bnft := &BasepointNaftablePrecomp{} + bnft.initonce.do_with_param(fn (mut o BasepointNaftablePrecomp) { + o.table.from_p3(new_generator_point()) + }, bnft) + return bnft.table +} + +// `vartime_double_scalar_base_mult` sets v = a * A + b * B, where B is the canonical +// generator, and returns v. +// +// Execution time depends on the inputs. +pub fn (mut v Point) vartime_double_scalar_base_mult(xa Scalar, aa Point, xb Scalar) Point { + check_initialized(aa) + + // Similarly to the single variable-base approach, we compute + // digits and use them with a lookup table. However, because + // we are allowed to do variable-time operations, we don't + // need constant-time lookups or constant-time digit + // computations. + // + // So we use a non-adjacent form of some width w instead of + // radix 16. This is like a binary representation (one digit + // for each binary place) but we allow the digits to grow in + // magnitude up to 2^{w-1} so that the nonzero digits are as + // sparse as possible. Intuitively, this "condenses" the + // "mass" of the scalar onto sparse coefficients (meaning + // fewer additions). + + mut bp_naftable := basepoint_naf_table() + mut atable := NafLookupTable5{} + atable.from_p3(aa) + // Because the basepoint is fixed, we can use a wider NAF + // corresponding to a bigger table. + mut a := xa + mut b := xb + anaf := a.non_adjacent_form(5) + bnaf := b.non_adjacent_form(8) + + // Find the first nonzero coefficient. + mut i := 255 + for j := i; j >= 0; j-- { + if anaf[j] != 0 || bnaf[j] != 0 { + break + } + } + + mut multa := ProjectiveCached{} + mut multb := AffineCached{} + mut tmp1 := ProjectiveP1{} + mut tmp2 := ProjectiveP2{} + tmp2.zero() + + // Move from high to low bits, doubling the accumulator + // at each iteration and checking whether there is a nonzero + // coefficient to look up a multiple of. + for ; i >= 0; i-- { + tmp1.double(tmp2) + + // Only update v if we have a nonzero coeff to add in. + if anaf[i] > 0 { + v.from_p1(tmp1) + atable.select_into(mut multa, anaf[i]) + tmp1.add(v, multa) + } else if anaf[i] < 0 { + v.from_p1(tmp1) + atable.select_into(mut multa, -anaf[i]) + tmp1.sub(v, multa) + } + + if bnaf[i] > 0 { + v.from_p1(tmp1) + bp_naftable.select_into(mut multb, bnaf[i]) + tmp1.add_affine(v, multb) + } else if bnaf[i] < 0 { + v.from_p1(tmp1) + bp_naftable.select_into(mut multb, -bnaf[i]) + tmp1.sub_affine(v, multb) + } + + tmp2.from_p1(tmp1) + } + + v.from_p2(tmp2) + return v +} diff --git a/vlib/crypto/ed25519/internal/edwards25519/scalarmult_test.v b/vlib/crypto/ed25519/internal/edwards25519/scalarmult_test.v new file mode 100644 index 0000000000..a68bfe58b7 --- /dev/null +++ b/vlib/crypto/ed25519/internal/edwards25519/scalarmult_test.v @@ -0,0 +1,185 @@ +module edwards25519 + +const ( + dalek_scalar = Scalar{[byte(219), 106, 114, 9, 174, 249, 155, 89, 69, 203, 201, 93, 92, 116, + 234, 187, 78, 115, 103, 172, 182, 98, 62, 103, 187, 136, 13, 100, 248, 110, 12, 4]!} + dsc_basepoint = [byte(0xf4), 0xef, 0x7c, 0xa, 0x34, 0x55, 0x7b, 0x9f, 0x72, 0x3b, 0xb6, 0x1e, + 0xf9, 0x46, 0x9, 0x91, 0x1c, 0xb9, 0xc0, 0x6c, 0x17, 0x28, 0x2d, 0x8b, 0x43, 0x2b, 0x5, + 0x18, 0x6a, 0x54, 0x3e, 0x48] +) + +fn dalek_scalar_basepoint() Point { + mut p := Point{} + p.set_bytes(edwards25519.dsc_basepoint) or { panic(err.msg) } + return p +} + +fn test_scalar_mult_small_scalars() { + mut z := Scalar{} + mut p := Point{} + mut b := new_generator_point() + mut i := new_identity_point() + p.scalar_mult(mut z, b) + + assert i.equal(p) == 1 + assert check_on_curve(p) == true + + z = Scalar{[byte(1), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0]!} + p.scalar_mult(mut z, b) + + assert b.equal(p) == 1 + assert check_on_curve(p) == true +} + +fn test_scalar_mult_vs_dalek() { + mut p := Point{} + mut b := new_generator_point() + mut dsc := edwards25519.dalek_scalar + p.scalar_mult(mut dsc, b) + mut ds := dalek_scalar_basepoint() + assert ds.equal(p) == 1 + + assert check_on_curve(p) == true +} + +fn test_scalar_base_mult_vs_dalek() { + mut p := Point{} + mut dsc := edwards25519.dalek_scalar + p.scalar_base_mult(mut dsc) + mut ds := dalek_scalar_basepoint() + assert ds.equal(p) == 1 + + assert check_on_curve(p) +} + +fn test_vartime_double_basemult_vs_dalek() { + mut p := Point{} + mut z := Scalar{} + b := new_generator_point() + p.vartime_double_scalar_base_mult(edwards25519.dalek_scalar, b, z) + + mut ds := dalek_scalar_basepoint() + assert ds.equal(p) == 1 + assert check_on_curve(p) + + p.vartime_double_scalar_base_mult(z, b, edwards25519.dalek_scalar) + + assert ds.equal(p) == 1 + assert check_on_curve(p) +} + +fn test_scalar_mult_distributes_over_add() { + mut x := generate_scalar(100) or { panic(err.msg) } + mut y := generate_scalar(100) or { panic(err.msg) } + mut z := Scalar{} + + z.add(x, y) + + mut p := Point{} + mut q := Point{} + mut r := Point{} + mut check := Point{} + mut b := new_generator_point() + + p.scalar_mult(mut x, b) + q.scalar_mult(mut y, b) + r.scalar_mult(mut z, b) + check.add(p, q) + + assert check_on_curve(p, q, r, check) == true + assert check.equal(r) == 1 +} + +fn test_scalarmult_non_identity_point() ? { + // Check whether p.ScalarMult and q.ScalaBaseMult give the same, + // when p and q are originally set to the base point. + + mut x := generate_scalar(5000) ? + + mut p := Point{} + mut q := Point{} + mut b := new_generator_point() + p.set(b) + q.set(b) + + p.scalar_mult(mut x, b) + q.scalar_base_mult(mut x) + + assert check_on_curve(p, q) == true + + assert p.equal(q) == 1 +} + +fn test_basepoint_table_generation() { + // The basepoint table is 32 affineLookupTables, + // corresponding to (16^2i)*B for table i. + bptable := basepoint_table() + b := new_generator_point() + mut tmp1 := ProjectiveP1{} + mut tmp2 := ProjectiveP2{} + mut tmp3 := Point{} + tmp3.set(b) + mut table := []AffineLookupTable{len: 32} + for i := 0; i < 32; i++ { + // Build the table + table[i].from_p3(tmp3) + + // Assert equality with the hardcoded one + assert table[i] == bptable[i] + + // Set p = (16^2)*p = 256*p = 2^8*p + tmp2.from_p3(tmp3) + for j := 0; j < 7; j++ { + tmp1.double(tmp2) + tmp2.from_p1(tmp1) + } + tmp1.double(tmp2) + tmp3.from_p1(tmp1) + + assert check_on_curve(tmp3) == true + } +} + +fn test_scalar_mult_matches_base_mult() { + mut x := generate_scalar(100) or { panic(err.msg) } + b := new_generator_point() + mut p := Point{} + mut q := Point{} + + p.scalar_mult(mut x, b) + q.scalar_base_mult(mut x) + + assert check_on_curve(p, q) == true + assert p.equal(q) == 1 +} + +fn test_basepoint_naf_table_generation() { + mut table := NafLookupTable8{} + b := new_generator_point() + + table.from_p3(b) + + bnt := basepoint_naf_table() + assert table == bnt +} + +fn test_vartime_double_scalar_base_mult() { + mut x := generate_scalar(100) or { panic(err.msg) } + mut y := generate_scalar(100) or { panic(err.msg) } + b := new_generator_point() + + mut p := Point{} + mut q1 := Point{} + mut q2 := Point{} + mut check := Point{} + + p.vartime_double_scalar_base_mult(x, b, y) + + q1.scalar_base_mult(mut x) + q2.scalar_base_mult(mut y) + check.add(q1, q2) + + assert check_on_curve(p, check, q1, q2) == true + assert p.equal(check) == 1 +} diff --git a/vlib/crypto/ed25519/internal/edwards25519/table.v b/vlib/crypto/ed25519/internal/edwards25519/table.v new file mode 100644 index 0000000000..d8ae7937ba --- /dev/null +++ b/vlib/crypto/ed25519/internal/edwards25519/table.v @@ -0,0 +1,127 @@ +module edwards25519 + +import crypto.internal.subtle + +// A precomputed lookup table for fixed-base, constant-time scalar muls. +struct AffineLookupTable { +mut: + points [8]AffineCached +} + +// A dynamic lookup table for variable-base, constant-time scalar muls. +struct ProjLookupTable { +mut: + points [8]ProjectiveCached +} + +// A dynamic lookup table for variable-base, variable-time scalar muls. +struct NafLookupTable5 { +mut: + points [8]ProjectiveCached +} + +// A precomputed lookup table for fixed-base, variable-time scalar muls. +struct NafLookupTable8 { +mut: + points [64]AffineCached +} + +// Constructors. + +// Builds a lookup table at runtime. Fast. +fn (mut v ProjLookupTable) from_p3(q Point) { + // Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q + // This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q + v.points[0].from_p3(q) + mut tmp_p3 := Point{} + mut tmp_p1 := ProjectiveP1{} + for i := 0; i < 7; i++ { + // Compute (i+1)*Q as Q + i*Q and convert to a ProjCached + // This is needlessly complicated because the API has explicit + // recievers instead of creating stack objects and relying on RVO + v.points[i + 1].from_p3(tmp_p3.from_p1(tmp_p1.add(q, v.points[i]))) + } +} + +// This is not optimised for speed; fixed-base tables should be precomputed. +fn (mut v AffineLookupTable) from_p3(q Point) { + // Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q + // This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q + v.points[0].from_p3(q) + mut tmp_p3 := Point{} + mut tmp_p1 := ProjectiveP1{} + for i := 0; i < 7; i++ { + // Compute (i+1)*Q as Q + i*Q and convert to AffineCached + v.points[i + 1].from_p3(tmp_p3.from_p1(tmp_p1.add_affine(q, v.points[i]))) + } +} + +// Builds a lookup table at runtime. Fast. +fn (mut v NafLookupTable5) from_p3(q Point) { + // Goal: v.points[i] = (2*i+1)*Q, i.e., Q, 3Q, 5Q, ..., 15Q + // This allows lookup of -15Q, ..., -3Q, -Q, 0, Q, 3Q, ..., 15Q + v.points[0].from_p3(q) + mut q2 := Point{} + q2.add(q, q) + mut tmp_p3 := Point{} + mut tmp_p1 := ProjectiveP1{} + for i := 0; i < 7; i++ { + v.points[i + 1].from_p3(tmp_p3.from_p1(tmp_p1.add(q2, v.points[i]))) + } +} + +// This is not optimised for speed; fixed-base tables should be precomputed. +fn (mut v NafLookupTable8) from_p3(q Point) { + v.points[0].from_p3(q) + mut q2 := Point{} + q2.add(q, q) + mut tmp_p3 := Point{} + mut tmp_p1 := ProjectiveP1{} + for i := 0; i < 63; i++ { + v.points[i + 1].from_p3(tmp_p3.from_p1(tmp_p1.add_affine(q2, v.points[i]))) + } +} + +// Selectors. + +// Set dest to x*Q, where -8 <= x <= 8, in constant time. +fn (mut v ProjLookupTable) select_into(mut dest ProjectiveCached, x i8) { + // Compute xabs = |x| + xmask := x >> 7 + xabs := byte((x + xmask) ^ xmask) + + dest.zero() + for j := 1; j <= 8; j++ { + // Set dest = j*Q if |x| = j + cond := subtle.constant_time_byte_eq(xabs, byte(j)) + dest.selected(&v.points[j - 1], dest, cond) + } + // Now dest = |x|*Q, conditionally negate to get x*Q + dest.cond_neg(int(xmask & 1)) +} + +// Set dest to x*Q, where -8 <= x <= 8, in constant time. +fn (mut v AffineLookupTable) select_into(mut dest AffineCached, x i8) { + // Compute xabs = |x| + xmask := x >> 7 + xabs := byte((x + xmask) ^ xmask) + + dest.zero() + for j := 1; j <= 8; j++ { + // Set dest = j*Q if |x| = j + cond := subtle.constant_time_byte_eq(xabs, byte(j)) + dest.selected(v.points[j - 1], dest, cond) + } + // Now dest = |x|*Q, conditionally negate to get x*Q + dest.cond_neg(int(xmask & 1)) +} + +// Given odd x with 0 < x < 2^4, return x*Q (in variable time). +fn (mut v NafLookupTable5) select_into(mut dest ProjectiveCached, x i8) { + dest = v.points[x / 2] +} + +// Given odd x with 0 < x < 2^7, return x*Q (in variable time). +fn (mut v NafLookupTable8) select_into(mut dest AffineCached, x i8) { + dest = v.points[x / 2] +} diff --git a/vlib/crypto/ed25519/internal/edwards25519/table_test.v b/vlib/crypto/ed25519/internal/edwards25519/table_test.v new file mode 100644 index 0000000000..92807eb6d1 --- /dev/null +++ b/vlib/crypto/ed25519/internal/edwards25519/table_test.v @@ -0,0 +1,121 @@ +module edwards25519 + +fn test_proj_lookup_table() { + mut table := ProjLookupTable{} + b := new_generator_point() + table.from_p3(b) + + mut tmp1 := ProjectiveCached{} + mut tmp2 := ProjectiveCached{} + mut tmp3 := ProjectiveCached{} + + table.select_into(mut tmp1, 6) + table.select_into(mut tmp2, -2) + table.select_into(mut tmp3, -4) + + // Expect T1 + T2 + T3 = identity + mut acc_p1 := ProjectiveP1{} + mut acc_p3 := new_identity_point() + + acc_p1.add(acc_p3, tmp1) + acc_p3.from_p1(acc_p1) + acc_p1.add(acc_p3, tmp2) + acc_p3.from_p1(acc_p1) + acc_p1.add(acc_p3, tmp3) + acc_p3.from_p1(acc_p1) + + assert acc_p3.equal(id_point) == 1 +} + +fn test_affine_lookup_table() { + mut table := AffineLookupTable{} + b := new_generator_point() + table.from_p3(b) + + mut tmp1 := AffineCached{} + mut tmp2 := AffineCached{} + mut tmp3 := AffineCached{} + + table.select_into(mut tmp1, 3) + table.select_into(mut tmp2, -7) + table.select_into(mut tmp3, 4) + // Expect T1 + T2 + T3 = identity + + mut acc_p1 := ProjectiveP1{} + mut acc_p3 := new_identity_point() + + acc_p1.add_affine(acc_p3, tmp1) + acc_p3.from_p1(acc_p1) + acc_p1.add_affine(acc_p3, tmp2) + acc_p3.from_p1(acc_p1) + acc_p1.add_affine(acc_p3, tmp3) + acc_p3.from_p1(acc_p1) + + assert acc_p3.equal(id_point) == 1 +} + +fn test_naf_lookup_table5() { + mut table := NafLookupTable5{} + b := new_generator_point() + table.from_p3(b) + + mut tmp1 := ProjectiveCached{} + mut tmp2 := ProjectiveCached{} + mut tmp3 := ProjectiveCached{} + mut tmp4 := ProjectiveCached{} + + table.select_into(mut tmp1, 9) + table.select_into(mut tmp2, 11) + table.select_into(mut tmp3, 7) + table.select_into(mut tmp4, 13) + // Expect T1 + T2 = T3 + T4 + + mut acc_p1 := ProjectiveP1{} + mut lhs := new_identity_point() + mut rhs := new_identity_point() + + acc_p1.add(lhs, tmp1) + lhs.from_p1(acc_p1) + acc_p1.add(lhs, tmp2) + lhs.from_p1(acc_p1) + + acc_p1.add(rhs, tmp3) + rhs.from_p1(acc_p1) + acc_p1.add(rhs, tmp4) + rhs.from_p1(acc_p1) + + assert lhs.equal(rhs) == 1 +} + +fn test_naf_lookup_table8() { + mut table := NafLookupTable8{} + b := new_generator_point() + table.from_p3(b) + + mut tmp1 := AffineCached{} + mut tmp2 := AffineCached{} + mut tmp3 := AffineCached{} + mut tmp4 := AffineCached{} + + table.select_into(mut tmp1, 49) + table.select_into(mut tmp2, 11) + table.select_into(mut tmp3, 35) + table.select_into(mut tmp4, 25) + // Expect T1 + T2 = T3 + T4 + + mut acc_p1 := ProjectiveP1{} + mut lhs := new_identity_point() + mut rhs := new_identity_point() + + acc_p1.add_affine(lhs, tmp1) + lhs.from_p1(acc_p1) + acc_p1.add_affine(lhs, tmp2) + lhs.from_p1(acc_p1) + + acc_p1.add_affine(rhs, tmp3) + rhs.from_p1(acc_p1) + acc_p1.add_affine(rhs, tmp4) + rhs.from_p1(acc_p1) + + assert lhs.equal(rhs) == 1 +} diff --git a/vlib/crypto/ed25519/testdata/sign.input b/vlib/crypto/ed25519/testdata/sign.input new file mode 100644 index 0000000000..4234759c56 --- /dev/null +++ b/vlib/crypto/ed25519/testdata/sign.input @@ -0,0 +1,128 @@ +9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a:d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a::e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b: +4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c:3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c:72:92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c0072: +c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025:fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025:af82:6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40aaf82: +0d4a05b07352a5436e180356da0ae6efa0345ff7fb1572575772e8005ed978e9e61a185bcef2613a6c7cb79763ce945d3b245d76114dd440bcf5f2dc1aa57057:e61a185bcef2613a6c7cb79763ce945d3b245d76114dd440bcf5f2dc1aa57057:cbc77b:d9868d52c2bebce5f3fa5a79891970f309cb6591e3e1702a70276fa97c24b3a8e58606c38c9758529da50ee31b8219cba45271c689afa60b0ea26c99db19b00ccbc77b: +6df9340c138cc188b5fe4464ebaa3f7fc206a2d55c3434707e74c9fc04e20ebbc0dac102c4533186e25dc43128472353eaabdb878b152aeb8e001f92d90233a7:c0dac102c4533186e25dc43128472353eaabdb878b152aeb8e001f92d90233a7:5f4c8989:124f6fc6b0d100842769e71bd530664d888df8507df6c56dedfdb509aeb93416e26b918d38aa06305df3095697c18b2aa832eaa52edc0ae49fbae5a85e150c075f4c8989: +b780381a65edf8b78f6945e8dbec7941ac049fd4c61040cf0c324357975a293ce253af0766804b869bb1595be9765b534886bbaab8305bf50dbc7f899bfb5f01:e253af0766804b869bb1595be9765b534886bbaab8305bf50dbc7f899bfb5f01:18b6bec097:b2fc46ad47af464478c199e1f8be169f1be6327c7f9a0a6689371ca94caf04064a01b22aff1520abd58951341603faed768cf78ce97ae7b038abfe456aa17c0918b6bec097: +78ae9effe6f245e924a7be63041146ebc670dbd3060cba67fbc6216febc44546fbcfbfa40505d7f2be444a33d185cc54e16d615260e1640b2b5087b83ee3643d:fbcfbfa40505d7f2be444a33d185cc54e16d615260e1640b2b5087b83ee3643d:89010d855972:6ed629fc1d9ce9e1468755ff636d5a3f40a5d9c91afd93b79d241830f7e5fa29854b8f20cc6eecbb248dbd8d16d14e99752194e4904d09c74d639518839d230089010d855972: +691865bfc82a1e4b574eecde4c7519093faf0cf867380234e3664645c61c5f7998a5e3a36e67aaba89888bf093de1ad963e774013b3902bfab356d8b90178a63:98a5e3a36e67aaba89888bf093de1ad963e774013b3902bfab356d8b90178a63:b4a8f381e70e7a:6e0af2fe55ae377a6b7a7278edfb419bd321e06d0df5e27037db8812e7e3529810fa5552f6c0020985ca17a0e02e036d7b222a24f99b77b75fdd16cb05568107b4a8f381e70e7a: +3b26516fb3dc88eb181b9ed73f0bcd52bcd6b4c788e4bcaf46057fd078bee073f81fb54a825fced95eb033afcd64314075abfb0abd20a970892503436f34b863:f81fb54a825fced95eb033afcd64314075abfb0abd20a970892503436f34b863:4284abc51bb67235:d6addec5afb0528ac17bb178d3e7f2887f9adbb1ad16e110545ef3bc57f9de2314a5c8388f723b8907be0f3ac90c6259bbe885ecc17645df3db7d488f805fa084284abc51bb67235: +edc6f5fbdd1cee4d101c063530a30490b221be68c036f5b07d0f953b745df192c1a49c66e617f9ef5ec66bc4c6564ca33de2a5fb5e1464062e6d6c6219155efd:c1a49c66e617f9ef5ec66bc4c6564ca33de2a5fb5e1464062e6d6c6219155efd:672bf8965d04bc5146:2c76a04af2391c147082e33faacdbe56642a1e134bd388620b852b901a6bc16ff6c9cc9404c41dea12ed281da067a1513866f9d964f8bdd24953856c50042901672bf8965d04bc5146: +4e7d21fb3b1897571a445833be0f9fd41cd62be3aa04040f8934e1fcbdcacd4531b2524b8348f7ab1dfafa675cc538e9a84e3fe5819e27c12ad8bbc1a36e4dff:31b2524b8348f7ab1dfafa675cc538e9a84e3fe5819e27c12ad8bbc1a36e4dff:33d7a786aded8c1bf691:28e4598c415ae9de01f03f9f3fab4e919e8bf537dd2b0cdf6e79b9e6559c9409d9151a4c40f083193937627c369488259e99da5a9f0a87497fa6696a5dd6ce0833d7a786aded8c1bf691: +a980f892db13c99a3e8971e965b2ff3d41eafd54093bc9f34d1fd22d84115bb644b57ee30cdb55829d0a5d4f046baef078f1e97a7f21b62d75f8e96ea139c35f:44b57ee30cdb55829d0a5d4f046baef078f1e97a7f21b62d75f8e96ea139c35f:3486f68848a65a0eb5507d:77d389e599630d934076329583cd4105a649a9292abc44cd28c40000c8e2f5ac7660a81c85b72af8452d7d25c070861dae91601c7803d656531650dd4e5c41003486f68848a65a0eb5507d: +5b5a619f8ce1c66d7ce26e5a2ae7b0c04febcd346d286c929e19d0d5973bfef96fe83693d011d111131c4f3fbaaa40a9d3d76b30012ff73bb0e39ec27ab18257:6fe83693d011d111131c4f3fbaaa40a9d3d76b30012ff73bb0e39ec27ab18257:5a8d9d0a22357e6655f9c785:0f9ad9793033a2fa06614b277d37381e6d94f65ac2a5a94558d09ed6ce922258c1a567952e863ac94297aec3c0d0c8ddf71084e504860bb6ba27449b55adc40e5a8d9d0a22357e6655f9c785: +940c89fe40a81dafbdb2416d14ae469119869744410c3303bfaa0241dac57800a2eb8c0501e30bae0cf842d2bde8dec7386f6b7fc3981b8c57c9792bb94cf2dd:a2eb8c0501e30bae0cf842d2bde8dec7386f6b7fc3981b8c57c9792bb94cf2dd:b87d3813e03f58cf19fd0b6395:d8bb64aad8c9955a115a793addd24f7f2b077648714f49c4694ec995b330d09d640df310f447fd7b6cb5c14f9fe9f490bcf8cfadbfd2169c8ac20d3b8af49a0cb87d3813e03f58cf19fd0b6395: +9acad959d216212d789a119252ebfe0c96512a23c73bd9f3b202292d6916a738cf3af898467a5b7a52d33d53bc037e2642a8da996903fc252217e9c033e2f291:cf3af898467a5b7a52d33d53bc037e2642a8da996903fc252217e9c033e2f291:55c7fa434f5ed8cdec2b7aeac173:6ee3fe81e23c60eb2312b2006b3b25e6838e02106623f844c44edb8dafd66ab0671087fd195df5b8f58a1d6e52af42908053d55c7321010092748795ef94cf0655c7fa434f5ed8cdec2b7aeac173: +d5aeee41eeb0e9d1bf8337f939587ebe296161e6bf5209f591ec939e1440c300fd2a565723163e29f53c9de3d5e8fbe36a7ab66e1439ec4eae9c0a604af291a5:fd2a565723163e29f53c9de3d5e8fbe36a7ab66e1439ec4eae9c0a604af291a5:0a688e79be24f866286d4646b5d81c:f68d04847e5b249737899c014d31c805c5007a62c0a10d50bb1538c5f35503951fbc1e08682f2cc0c92efe8f4985dec61dcbd54d4b94a22547d24451271c8b000a688e79be24f866286d4646b5d81c: +0a47d10452ae2febec518a1c7c362890c3fc1a49d34b03b6467d35c904a8362d34e5a8508c4743746962c066e4badea2201b8ab484de5c4f94476ccd2143955b:34e5a8508c4743746962c066e4badea2201b8ab484de5c4f94476ccd2143955b:c942fa7ac6b23ab7ff612fdc8e68ef39:2a3d27dc40d0a8127949a3b7f908b3688f63b7f14f651aacd715940bdbe27a0809aac142f47ab0e1e44fa490ba87ce5392f33a891539caf1ef4c367cae54500cc942fa7ac6b23ab7ff612fdc8e68ef39: +f8148f7506b775ef46fdc8e8c756516812d47d6cfbfa318c27c9a22641e56f170445e456dacc7d5b0bbed23c8200cdb74bdcb03e4c7b73f0a2b9b46eac5d4372:0445e456dacc7d5b0bbed23c8200cdb74bdcb03e4c7b73f0a2b9b46eac5d4372:7368724a5b0efb57d28d97622dbde725af:3653ccb21219202b8436fb41a32ba2618c4a133431e6e63463ceb3b6106c4d56e1d2ba165ba76eaad3dc39bffb130f1de3d8e6427db5b71938db4e272bc3e20b7368724a5b0efb57d28d97622dbde725af: +77f88691c4eff23ebb7364947092951a5ff3f10785b417e918823a552dab7c7574d29127f199d86a8676aec33b4ce3f225ccb191f52c191ccd1e8cca65213a6b:74d29127f199d86a8676aec33b4ce3f225ccb191f52c191ccd1e8cca65213a6b:bd8e05033f3a8bcdcbf4beceb70901c82e31:fbe929d743a03c17910575492f3092ee2a2bf14a60a3fcacec74a58c7334510fc262db582791322d6c8c41f1700adb80027ecabc14270b703444ae3ee7623e0abd8e05033f3a8bcdcbf4beceb70901c82e31: +ab6f7aee6a0837b334ba5eb1b2ad7fcecfab7e323cab187fe2e0a95d80eff1325b96dca497875bf9664c5e75facf3f9bc54bae913d66ca15ee85f1491ca24d2c:5b96dca497875bf9664c5e75facf3f9bc54bae913d66ca15ee85f1491ca24d2c:8171456f8b907189b1d779e26bc5afbb08c67a:73bca64e9dd0db88138eedfafcea8f5436cfb74bfb0e7733cf349baa0c49775c56d5934e1d38e36f39b7c5beb0a836510c45126f8ec4b6810519905b0ca07c098171456f8b907189b1d779e26bc5afbb08c67a: +8d135de7c8411bbdbd1b31e5dc678f2ac7109e792b60f38cd24936e8a898c32d1ca281938529896535a7714e3584085b86ef9fec723f42819fc8dd5d8c00817f:1ca281938529896535a7714e3584085b86ef9fec723f42819fc8dd5d8c00817f:8ba6a4c9a15a244a9c26bb2a59b1026f21348b49:a1adc2bc6a2d980662677e7fdff6424de7dba50f5795ca90fdf3e96e256f3285cac71d3360482e993d0294ba4ec7440c61affdf35fe83e6e04263937db93f1058ba6a4c9a15a244a9c26bb2a59b1026f21348b49: +0e765d720e705f9366c1ab8c3fa84c9a44370c06969f803296884b2846a652a47fae45dd0a05971026d410bc497af5be7d0827a82a145c203f625dfcb8b03ba8:7fae45dd0a05971026d410bc497af5be7d0827a82a145c203f625dfcb8b03ba8:1d566a6232bbaab3e6d8804bb518a498ed0f904986:bb61cf84de61862207c6a455258bc4db4e15eea0317ff88718b882a06b5cf6ec6fd20c5a269e5d5c805bafbcc579e2590af414c7c227273c102a10070cdfe80f1d566a6232bbaab3e6d8804bb518a498ed0f904986: +db36e326d676c2d19cc8fe0c14b709202ecfc761d27089eb6ea4b1bb021ecfa748359b850d23f0715d94bb8bb75e7e14322eaf14f06f28a805403fbda002fc85:48359b850d23f0715d94bb8bb75e7e14322eaf14f06f28a805403fbda002fc85:1b0afb0ac4ba9ab7b7172cddc9eb42bba1a64bce47d4:b6dcd09989dfbac54322a3ce87876e1d62134da998c79d24b50bd7a6a797d86a0e14dc9d7491d6c14a673c652cfbec9f962a38c945da3b2f0879d0b68a9213001b0afb0ac4ba9ab7b7172cddc9eb42bba1a64bce47d4: +c89955e0f7741d905df0730b3dc2b0ce1a13134e44fef3d40d60c020ef19df77fdb30673402faf1c8033714f3517e47cc0f91fe70cf3836d6c23636e3fd2287c:fdb30673402faf1c8033714f3517e47cc0f91fe70cf3836d6c23636e3fd2287c:507c94c8820d2a5793cbf3442b3d71936f35fe3afef316:7ef66e5e86f2360848e0014e94880ae2920ad8a3185a46b35d1e07dea8fa8ae4f6b843ba174d99fa7986654a0891c12a794455669375bf92af4cc2770b579e0c507c94c8820d2a5793cbf3442b3d71936f35fe3afef316: +4e62627fc221142478aee7f00781f817f662e3b75db29bb14ab47cf8e84104d6b1d39801892027d58a8c64335163195893bfc1b61dbeca3260497e1f30371107:b1d39801892027d58a8c64335163195893bfc1b61dbeca3260497e1f30371107:d3d615a8472d9962bb70c5b5466a3d983a4811046e2a0ef5:836afa764d9c48aa4770a4388b654e97b3c16f082967febca27f2fc47ddfd9244b03cfc729698acf5109704346b60b230f255430089ddc56912399d1122de70ad3d615a8472d9962bb70c5b5466a3d983a4811046e2a0ef5: +6b83d7da8908c3e7205b39864b56e5f3e17196a3fc9c2f5805aad0f5554c142dd0c846f97fe28585c0ee159015d64c56311c886eddcc185d296dbb165d2625d6:d0c846f97fe28585c0ee159015d64c56311c886eddcc185d296dbb165d2625d6:6ada80b6fa84f7034920789e8536b82d5e4678059aed27f71c:16e462a29a6dd498685a3718b3eed00cc1598601ee47820486032d6b9acc9bf89f57684e08d8c0f05589cda2882a05dc4c63f9d0431d6552710812433003bc086ada80b6fa84f7034920789e8536b82d5e4678059aed27f71c: +19a91fe23a4e9e33ecc474878f57c64cf154b394203487a7035e1ad9cd697b0d2bf32ba142ba4622d8f3e29ecd85eea07b9c47be9d64412c9b510b27dd218b23:2bf32ba142ba4622d8f3e29ecd85eea07b9c47be9d64412c9b510b27dd218b23:82cb53c4d5a013bae5070759ec06c3c6955ab7a4050958ec328c:881f5b8c5a030df0f75b6634b070dd27bd1ee3c08738ae349338b3ee6469bbf9760b13578a237d5182535ede121283027a90b5f865d63a6537dca07b44049a0f82cb53c4d5a013bae5070759ec06c3c6955ab7a4050958ec328c: +1d5b8cb6215c18141666baeefcf5d69dad5bea9a3493dddaa357a4397a13d4de94d23d977c33e49e5e4992c68f25ec99a27c41ce6b91f2bfa0cd8292fe962835:94d23d977c33e49e5e4992c68f25ec99a27c41ce6b91f2bfa0cd8292fe962835:a9a8cbb0ad585124e522abbfb40533bdd6f49347b55b18e8558cb0:3acd39bec8c3cd2b44299722b5850a0400c1443590fd4861d59aae7496acb3df73fc3fdf7969ae5f50ba47dddc435246e5fd376f6b891cd4c2caf5d614b6170ca9a8cbb0ad585124e522abbfb40533bdd6f49347b55b18e8558cb0: +6a91b3227c472299089bdce9356e726a40efd840f11002708b7ee55b64105ac29d084aa8b97a6b9bafa496dbc6f76f3306a116c9d917e681520a0f914369427e:9d084aa8b97a6b9bafa496dbc6f76f3306a116c9d917e681520a0f914369427e:5cb6f9aa59b80eca14f6a68fb40cf07b794e75171fba96262c1c6adc:f5875423781b66216cb5e8998de5d9ffc29d1d67107054ace3374503a9c3ef811577f269de81296744bd706f1ac478caf09b54cdf871b3f802bd57f9a6cb91015cb6f9aa59b80eca14f6a68fb40cf07b794e75171fba96262c1c6adc: +93eaa854d791f05372ce72b94fc6503b2ff8ae6819e6a21afe825e27ada9e4fb16cee8a3f2631834c88b670897ff0b08ce90cc147b4593b3f1f403727f7e7ad5:16cee8a3f2631834c88b670897ff0b08ce90cc147b4593b3f1f403727f7e7ad5:32fe27994124202153b5c70d3813fdee9c2aa6e7dc743d4d535f1840a5:d834197c1a3080614e0a5fa0aaaa808824f21c38d692e6ffbd200f7dfb3c8f44402a7382180b98ad0afc8eec1a02acecf3cb7fde627b9f18111f260ab1db9a0732fe27994124202153b5c70d3813fdee9c2aa6e7dc743d4d535f1840a5: +941cac69fb7b1815c57bb987c4d6c2ad2c35d5f9a3182a79d4ba13eab253a8ad23be323c562dfd71ce65f5bba56a74a3a6dfc36b573d2f94f635c7f9b4fd5a5b:23be323c562dfd71ce65f5bba56a74a3a6dfc36b573d2f94f635c7f9b4fd5a5b:bb3172795710fe00054d3b5dfef8a11623582da68bf8e46d72d27cece2aa:0f8fad1e6bde771b4f5420eac75c378bae6db5ac6650cd2bc210c1823b432b48e016b10595458ffab92f7a8989b293ceb8dfed6c243a2038fc06652aaaf16f02bb3172795710fe00054d3b5dfef8a11623582da68bf8e46d72d27cece2aa: +1acdbb793b0384934627470d795c3d1dd4d79cea59ef983f295b9b59179cbb283f60c7541afa76c019cf5aa82dcdb088ed9e4ed9780514aefb379dabc844f31a:3f60c7541afa76c019cf5aa82dcdb088ed9e4ed9780514aefb379dabc844f31a:7cf34f75c3dac9a804d0fcd09eba9b29c9484e8a018fa9e073042df88e3c56:be71ef4806cb041d885effd9e6b0fbb73d65d7cdec47a89c8a994892f4e55a568c4cc78d61f901e80dbb628b86a23ccd594e712b57fa94c2d67ec266348785077cf34f75c3dac9a804d0fcd09eba9b29c9484e8a018fa9e073042df88e3c56: +8ed7a797b9cea8a8370d419136bcdf683b759d2e3c6947f17e13e2485aa9d420b49f3a78b1c6a7fca8f3466f33bc0e929f01fba04306c2a7465f46c3759316d9:b49f3a78b1c6a7fca8f3466f33bc0e929f01fba04306c2a7465f46c3759316d9:a750c232933dc14b1184d86d8b4ce72e16d69744ba69818b6ac33b1d823bb2c3:04266c033b91c1322ceb3446c901ffcf3cc40c4034e887c9597ca1893ba7330becbbd8b48142ef35c012c6ba51a66df9308cb6268ad6b1e4b03e70102495790ba750c232933dc14b1184d86d8b4ce72e16d69744ba69818b6ac33b1d823bb2c3: +f2ab396fe8906e3e5633e99cabcd5b09df0859b516230b1e0450b580b65f616c8ea074245159a116aa7122a25ec16b891d625a68f33660423908f6bdc44f8c1b:8ea074245159a116aa7122a25ec16b891d625a68f33660423908f6bdc44f8c1b:5a44e34b746c5fd1898d552ab354d28fb4713856d7697dd63eb9bd6b99c280e187:a06a23d982d81ab883aae230adbc368a6a9977f003cebb00d4c2e4018490191a84d3a282fdbfb2fc88046e62de43e15fb575336b3c8b77d19ce6a009ce51f50c5a44e34b746c5fd1898d552ab354d28fb4713856d7697dd63eb9bd6b99c280e187: +550a41c013f79bab8f06e43ad1836d51312736a9713806fafe6645219eaa1f9daf6b7145474dc9954b9af93a9cdb34449d5b7c651c824d24e230b90033ce59c0:af6b7145474dc9954b9af93a9cdb34449d5b7c651c824d24e230b90033ce59c0:8bc4185e50e57d5f87f47515fe2b1837d585f0aae9e1ca383b3ec908884bb900ff27:16dc1e2b9fa909eefdc277ba16ebe207b8da5e91143cde78c5047a89f681c33c4e4e3428d5c928095903a811ec002d52a39ed7f8b3fe1927200c6dd0b9ab3e048bc4185e50e57d5f87f47515fe2b1837d585f0aae9e1ca383b3ec908884bb900ff27: +19ac3e272438c72ddf7b881964867cb3b31ff4c793bb7ea154613c1db068cb7ef85b80e050a1b9620db138bfc9e100327e25c257c59217b601f1f6ac9a413d3f:f85b80e050a1b9620db138bfc9e100327e25c257c59217b601f1f6ac9a413d3f:95872d5f789f95484e30cbb0e114028953b16f5c6a8d9f65c003a83543beaa46b38645:ea855d781cbea4682e350173cb89e8619ccfddb97cdce16f9a2f6f6892f46dbe68e04b12b8d88689a7a31670cdff409af98a93b49a34537b6aa009d2eb8b470195872d5f789f95484e30cbb0e114028953b16f5c6a8d9f65c003a83543beaa46b38645: +ca267de96c93c238fafb1279812059ab93ac03059657fd994f8fa5a09239c821017370c879090a81c7f272c2fc80e3aac2bc603fcb379afc98691160ab745b26:017370c879090a81c7f272c2fc80e3aac2bc603fcb379afc98691160ab745b26:e05f71e4e49a72ec550c44a3b85aca8f20ff26c3ee94a80f1b431c7d154ec9603ee02531:ac957f82335aa7141e96b59d63e3ccee95c3a2c47d026540c2af42dc9533d5fd81827d1679ad187aeaf37834915e75b147a9286806c8017516ba43dd051a5e0ce05f71e4e49a72ec550c44a3b85aca8f20ff26c3ee94a80f1b431c7d154ec9603ee02531: +3dff5e899475e7e91dd261322fab09980c52970de1da6e2e201660cc4fce7032f30162bac98447c4042fac05da448034629be2c6a58d30dfd578ba9fb5e3930b:f30162bac98447c4042fac05da448034629be2c6a58d30dfd578ba9fb5e3930b:938f0e77621bf3ea52c7c4911c5157c2d8a2a858093ef16aa9b107e69d98037ba139a3c382:5efe7a92ff9623089b3e3b78f352115366e26ba3fb1a416209bc029e9cadccd9f4affa333555a8f3a35a9d0f7c34b292cae77ec96fa3adfcaadee2d9ced8f805938f0e77621bf3ea52c7c4911c5157c2d8a2a858093ef16aa9b107e69d98037ba139a3c382: +9a6b847864e70cfe8ba6ab22fa0ca308c0cc8bec7141fbcaa3b81f5d1e1cfcfc34ad0fbdb2566507a81c2b1f8aa8f53dccaa64cc87ada91b903e900d07eee930:34ad0fbdb2566507a81c2b1f8aa8f53dccaa64cc87ada91b903e900d07eee930:838367471183c71f7e717724f89d401c3ad9863fd9cc7aa3cf33d3c529860cb581f3093d87da:2ab255169c489c54c732232e37c87349d486b1eba20509dbabe7fed329ef08fd75ba1cd145e67b2ea26cb5cc51cab343eeb085fe1fd7b0ec4c6afcd9b979f905838367471183c71f7e717724f89d401c3ad9863fd9cc7aa3cf33d3c529860cb581f3093d87da: +575be07afca5d063c238cd9b8028772cc49cda34471432a2e166e096e2219efc94e5eb4d5024f49d7ebf79817c8de11497dc2b55622a51ae123ffc749dbb16e0:94e5eb4d5024f49d7ebf79817c8de11497dc2b55622a51ae123ffc749dbb16e0:33e5918b66d33d55fe717ca34383eae78f0af82889caf6696e1ac9d95d1ffb32cba755f9e3503e:58271d44236f3b98c58fd7ae0d2f49ef2b6e3affdb225aa3ba555f0e11cc53c23ad19baf24346590d05d7d5390582082cf94d39cad6530ab93d13efb3927950633e5918b66d33d55fe717ca34383eae78f0af82889caf6696e1ac9d95d1ffb32cba755f9e3503e: +15ffb45514d43444d61fcb105e30e135fd268523dda20b82758b1794231104411772c5abc2d23fd2f9d1c3257be7bc3c1cd79cee40844b749b3a7743d2f964b8:1772c5abc2d23fd2f9d1c3257be7bc3c1cd79cee40844b749b3a7743d2f964b8:da9c5559d0ea51d255b6bd9d7638b876472f942b330fc0e2b30aea68d77368fce4948272991d257e:6828cd7624e793b8a4ceb96d3c2a975bf773e5ff6645f353614058621e58835289e7f31f42dfe6af6d736f2644511e320c0fa698582a79778d18730ed3e8cb08da9c5559d0ea51d255b6bd9d7638b876472f942b330fc0e2b30aea68d77368fce4948272991d257e: +fe0568642943b2e1afbfd1f10fe8df87a4236bea40dce742072cb21886eec1fa299ebd1f13177dbdb66a912bbf712038fdf73b06c3ac020c7b19126755d47f61:299ebd1f13177dbdb66a912bbf712038fdf73b06c3ac020c7b19126755d47f61:c59d0862ec1c9746abcc3cf83c9eeba2c7082a036a8cb57ce487e763492796d47e6e063a0c1feccc2d:d59e6dfcc6d7e3e2c58dec81e985d245e681acf6594a23c59214f7bed8015d813c7682b60b3583440311e72a8665ba2c96dec23ce826e160127e18132b030404c59d0862ec1c9746abcc3cf83c9eeba2c7082a036a8cb57ce487e763492796d47e6e063a0c1feccc2d: +5ecb16c2df27c8cf58e436a9d3affbd58e9538a92659a0f97c4c4f994635a8cada768b20c437dd3aa5f84bb6a077ffa34ab68501c5352b5cc3fdce7fe6c2398d:da768b20c437dd3aa5f84bb6a077ffa34ab68501c5352b5cc3fdce7fe6c2398d:56f1329d9a6be25a6159c72f12688dc8314e85dd9e7e4dc05bbecb7729e023c86f8e0937353f27c7ede9:1c723a20c6772426a670e4d5c4a97c6ebe9147f71bb0a415631e44406e290322e4ca977d348fe7856a8edc235d0fe95f7ed91aefddf28a77e2c7dbfd8f552f0a56f1329d9a6be25a6159c72f12688dc8314e85dd9e7e4dc05bbecb7729e023c86f8e0937353f27c7ede9: +d599d637b3c30a82a9984e2f758497d144de6f06b9fba04dd40fd949039d7c846791d8ce50a44689fc178727c5c3a1c959fbeed74ef7d8e7bd3c1ab4da31c51f:6791d8ce50a44689fc178727c5c3a1c959fbeed74ef7d8e7bd3c1ab4da31c51f:a7c04e8ba75d0a03d8b166ad7a1d77e1b91c7aaf7befdd99311fc3c54a684ddd971d5b3211c3eeaff1e54e:ebf10d9ac7c96108140e7def6fe9533d727646ff5b3af273c1df95762a66f32b65a09634d013f54b5dd6011f91bc336ca8b355ce33f8cfbec2535a4c427f8205a7c04e8ba75d0a03d8b166ad7a1d77e1b91c7aaf7befdd99311fc3c54a684ddd971d5b3211c3eeaff1e54e: +30ab8232fa7018f0ce6c39bd8f782fe2e159758bb0f2f4386c7f28cfd2c85898ecfb6a2bd42f31b61250ba5de7e46b4719afdfbc660db71a7bd1df7b0a3abe37:ecfb6a2bd42f31b61250ba5de7e46b4719afdfbc660db71a7bd1df7b0a3abe37:63b80b7956acbecf0c35e9ab06b914b0c7014fe1a4bbc0217240c1a33095d707953ed77b15d211adaf9b97dc:9af885344cc7239498f712df80bc01b80638291ed4a1d28baa5545017a72e2f65649ccf9603da6eb5bfab9f5543a6ca4a7af3866153c76bf66bf95def615b00c63b80b7956acbecf0c35e9ab06b914b0c7014fe1a4bbc0217240c1a33095d707953ed77b15d211adaf9b97dc: +0ddcdc872c7b748d40efe96c2881ae189d87f56148ed8af3ebbbc80324e38bdd588ddadcbcedf40df0e9697d8bb277c7bb1498fa1d26ce0a835a760b92ca7c85:588ddadcbcedf40df0e9697d8bb277c7bb1498fa1d26ce0a835a760b92ca7c85:65641cd402add8bf3d1d67dbeb6d41debfbef67e4317c35b0a6d5bbbae0e034de7d670ba1413d056f2d6f1de12:c179c09456e235fe24105afa6e8ec04637f8f943817cd098ba95387f9653b2add181a31447d92d1a1ddf1ceb0db62118de9dffb7dcd2424057cbdff5d41d040365641cd402add8bf3d1d67dbeb6d41debfbef67e4317c35b0a6d5bbbae0e034de7d670ba1413d056f2d6f1de12: +89f0d68299ba0a5a83f248ae0c169f8e3849a9b47bd4549884305c9912b46603aba3e795aab2012acceadd7b3bd9daeeed6ff5258bdcd7c93699c2a3836e3832:aba3e795aab2012acceadd7b3bd9daeeed6ff5258bdcd7c93699c2a3836e3832:4f1846dd7ad50e545d4cfbffbb1dc2ff145dc123754d08af4e44ecc0bc8c91411388bc7653e2d893d1eac2107d05:2c691fa8d487ce20d5d2fa41559116e0bbf4397cf5240e152556183541d66cf753582401a4388d390339dbef4d384743caa346f55f8daba68ba7b9131a8a6e0b4f1846dd7ad50e545d4cfbffbb1dc2ff145dc123754d08af4e44ecc0bc8c91411388bc7653e2d893d1eac2107d05: +0a3c1844e2db070fb24e3c95cb1cc6714ef84e2ccd2b9dd2f1460ebf7ecf13b172e409937e0610eb5c20b326dc6ea1bbbc0406701c5cd67d1fbde09192b07c01:72e409937e0610eb5c20b326dc6ea1bbbc0406701c5cd67d1fbde09192b07c01:4c8274d0ed1f74e2c86c08d955bde55b2d54327e82062a1f71f70d536fdc8722cdead7d22aaead2bfaa1ad00b82957:87f7fdf46095201e877a588fe3e5aaf476bd63138d8a878b89d6ac60631b3458b9d41a3c61a588e1db8d29a5968981b018776c588780922f5aa732ba6379dd054c8274d0ed1f74e2c86c08d955bde55b2d54327e82062a1f71f70d536fdc8722cdead7d22aaead2bfaa1ad00b82957: +c8d7a8818b98dfdb20839c871cb5c48e9e9470ca3ad35ba2613a5d3199c8ab2390d2efbba4d43e6b2b992ca16083dbcfa2b322383907b0ee75f3e95845d3c47f:90d2efbba4d43e6b2b992ca16083dbcfa2b322383907b0ee75f3e95845d3c47f:783e33c3acbdbb36e819f544a7781d83fc283d3309f5d3d12c8dcd6b0b3d0e89e38cfd3b4d0885661ca547fb9764abff:fa2e994421aef1d5856674813d05cbd2cf84ef5eb424af6ecd0dc6fdbdc2fe605fe985883312ecf34f59bfb2f1c9149e5b9cc9ecda05b2731130f3ed28ddae0b783e33c3acbdbb36e819f544a7781d83fc283d3309f5d3d12c8dcd6b0b3d0e89e38cfd3b4d0885661ca547fb9764abff: +b482703612d0c586f76cfcb21cfd2103c957251504a8c0ac4c86c9c6f3e429fffd711dc7dd3b1dfb9df9704be3e6b26f587fe7dd7ba456a91ba43fe51aec09ad:fd711dc7dd3b1dfb9df9704be3e6b26f587fe7dd7ba456a91ba43fe51aec09ad:29d77acfd99c7a0070a88feb6247a2bce9984fe3e6fbf19d4045042a21ab26cbd771e184a9a75f316b648c6920db92b87b:58832bdeb26feafc31b46277cf3fb5d7a17dfb7ccd9b1f58ecbe6feb979666828f239ba4d75219260ecac0acf40f0e5e2590f4caa16bbbcd8a155d347967a60729d77acfd99c7a0070a88feb6247a2bce9984fe3e6fbf19d4045042a21ab26cbd771e184a9a75f316b648c6920db92b87b: +84e50dd9a0f197e3893c38dbd91fafc344c1776d3a400e2f0f0ee7aa829eb8a22c50f870ee48b36b0ac2f8a5f336fb090b113050dbcc25e078200a6e16153eea:2c50f870ee48b36b0ac2f8a5f336fb090b113050dbcc25e078200a6e16153eea:f3992cde6493e671f1e129ddca8038b0abdb77bb9035f9f8be54bd5d68c1aeff724ff47d29344391dc536166b8671cbbf123:69e6a4491a63837316e86a5f4ba7cd0d731ecc58f1d0a264c67c89befdd8d3829d8de13b33cc0bf513931715c7809657e2bfb960e5c764c971d733746093e500f3992cde6493e671f1e129ddca8038b0abdb77bb9035f9f8be54bd5d68c1aeff724ff47d29344391dc536166b8671cbbf123: +b322d46577a2a991a4d1698287832a39c487ef776b4bff037a05c7f1812bdeeceb2bcadfd3eec2986baff32b98e7c4dbf03ff95d8ad5ff9aa9506e5472ff845f:eb2bcadfd3eec2986baff32b98e7c4dbf03ff95d8ad5ff9aa9506e5472ff845f:19f1bf5dcf1750c611f1c4a2865200504d82298edd72671f62a7b1471ac3d4a30f7de9e5da4108c52a4ce70a3e114a52a3b3c5:c7b55137317ca21e33489ff6a9bfab97c855dc6f85684a70a9125a261b56d5e6f149c5774d734f2d8debfc77b721896a8267c23768e9badb910eef83ec25880219f1bf5dcf1750c611f1c4a2865200504d82298edd72671f62a7b1471ac3d4a30f7de9e5da4108c52a4ce70a3e114a52a3b3c5: +960cab5034b9838d098d2dcbf4364bec16d388f6376d73a6273b70f82bbc98c05e3c19f2415acf729f829a4ebd5c40e1a6bc9fbca95703a9376087ed0937e51a:5e3c19f2415acf729f829a4ebd5c40e1a6bc9fbca95703a9376087ed0937e51a:f8b21962447b0a8f2e4279de411bea128e0be44b6915e6cda88341a68a0d818357db938eac73e0af6d31206b3948f8c48a447308:27d4c3a1811ef9d4360b3bdd133c2ccc30d02c2f248215776cb07ee4177f9b13fc42dd70a6c2fed8f225c7663c7f182e7ee8eccff20dc7b0e1d5834ec5b1ea01f8b21962447b0a8f2e4279de411bea128e0be44b6915e6cda88341a68a0d818357db938eac73e0af6d31206b3948f8c48a447308: +eb77b2638f23eebc82efe45ee9e5a0326637401e663ed029699b21e6443fb48e9ef27608961ac711de71a6e2d4d4663ea3ecd42fb7e4e8627c39622df4af0bbc:9ef27608961ac711de71a6e2d4d4663ea3ecd42fb7e4e8627c39622df4af0bbc:99e3d00934003ebafc3e9fdb687b0f5ff9d5782a4b1f56b9700046c077915602c3134e22fc90ed7e690fddd4433e2034dcb2dc99ab:18dc56d7bd9acd4f4daa78540b4ac8ff7aa9815f45a0bba370731a14eaabe96df8b5f37dbf8eae4cb15a64b244651e59d6a3d6761d9e3c50f2d0cbb09c05ec0699e3d00934003ebafc3e9fdb687b0f5ff9d5782a4b1f56b9700046c077915602c3134e22fc90ed7e690fddd4433e2034dcb2dc99ab: +b625aa89d3f7308715427b6c39bbac58effd3a0fb7316f7a22b99ee5922f2dc965a99c3e16fea894ec33c6b20d9105e2a04e2764a4769d9bbd4d8bacfeab4a2e:65a99c3e16fea894ec33c6b20d9105e2a04e2764a4769d9bbd4d8bacfeab4a2e:e07241dbd3adbe610bbe4d005dd46732a4c25086ecb8ec29cd7bca116e1bf9f53bfbf3e11fa49018d39ff1154a06668ef7df5c678e6a:01bb901d83b8b682d3614af46a807ba2691358feb775325d3423f549ff0aa5757e4e1a74e9c70f9721d8f354b319d4f4a1d91445c870fd0ffb94fed64664730de07241dbd3adbe610bbe4d005dd46732a4c25086ecb8ec29cd7bca116e1bf9f53bfbf3e11fa49018d39ff1154a06668ef7df5c678e6a: +b1c9f8bd03fe82e78f5c0fb06450f27dacdf716434db268275df3e1dc177af427fc88b1f7b3f11c629be671c21621f5c10672fafc8492da885742059ee6774cf:7fc88b1f7b3f11c629be671c21621f5c10672fafc8492da885742059ee6774cf:331da7a9c1f87b2ac91ee3b86d06c29163c05ed6f8d8a9725b471b7db0d6acec7f0f702487163f5eda020ca5b493f399e1c8d308c3c0c2:4b229951ef262f16978f7914bc672e7226c5f8379d2778c5a2dc0a2650869f7acfbd0bcd30fdb0619bb44fc1ae5939b87cc318133009c20395b6c7eb98107701331da7a9c1f87b2ac91ee3b86d06c29163c05ed6f8d8a9725b471b7db0d6acec7f0f702487163f5eda020ca5b493f399e1c8d308c3c0c2: +6d8cdb2e075f3a2f86137214cb236ceb89a6728bb4a200806bf3557fb78fac6957a04c7a5113cddfe49a4c124691d46c1f9cdc8f343f9dcb72a1330aeca71fda:57a04c7a5113cddfe49a4c124691d46c1f9cdc8f343f9dcb72a1330aeca71fda:7f318dbd121c08bfddfeff4f6aff4e45793251f8abf658403358238984360054f2a862c5bb83ed89025d2014a7a0cee50da3cb0e76bbb6bf:a6cbc947f9c87d1455cf1a708528c090f11ecee4855d1dbaadf47454a4de55fa4ce84b36d73a5b5f8f59298ccf21992df492ef34163d87753b7e9d32f2c3660b7f318dbd121c08bfddfeff4f6aff4e45793251f8abf658403358238984360054f2a862c5bb83ed89025d2014a7a0cee50da3cb0e76bbb6bf: +47adc6d6bf571ee9570ca0f75b604ac43e303e4ab339ca9b53cacc5be45b2ccba3f527a1c1f17dfeed92277347c9f98ab475de1755b0ab546b8a15d01b9bd0be:a3f527a1c1f17dfeed92277347c9f98ab475de1755b0ab546b8a15d01b9bd0be:ce497c5ff5a77990b7d8f8699eb1f5d8c0582f70cb7ac5c54d9d924913278bc654d37ea227590e15202217fc98dac4c0f3be2183d133315739:4e8c318343c306adbba60c92b75cb0569b9219d8a86e5d57752ed235fc109a43c2cf4e942cacf297279fbb28675347e08027722a4eb7395e00a17495d32edf0bce497c5ff5a77990b7d8f8699eb1f5d8c0582f70cb7ac5c54d9d924913278bc654d37ea227590e15202217fc98dac4c0f3be2183d133315739: +3c19b50b0fe47961719c381d0d8da9b9869d312f13e3298b97fb22f0af29cbbe0f7eda091499625e2bae8536ea35cda5483bd16a9c7e416b341d6f2c83343612:0f7eda091499625e2bae8536ea35cda5483bd16a9c7e416b341d6f2c83343612:8ddcd63043f55ec3bfc83dceae69d8f8b32f4cdb6e2aebd94b4314f8fe7287dcb62732c9052e7557fe63534338efb5b6254c5d41d2690cf5144f:efbd41f26a5d62685516f882b6ec74e0d5a71830d203c231248f26e99a9c6578ec900d68cdb8fa7216ad0d24f9ecbc9ffa655351666582f626645395a31fa7048ddcd63043f55ec3bfc83dceae69d8f8b32f4cdb6e2aebd94b4314f8fe7287dcb62732c9052e7557fe63534338efb5b6254c5d41d2690cf5144f: +34e1e9d539107eb86b393a5ccea1496d35bc7d5e9a8c5159d957e4e5852b3eb00ecb2601d5f7047428e9f909883a12420085f04ee2a88b6d95d3d7f2c932bd76:0ecb2601d5f7047428e9f909883a12420085f04ee2a88b6d95d3d7f2c932bd76:a6d4d0542cfe0d240a90507debacabce7cbbd48732353f4fad82c7bb7dbd9df8e7d9a16980a45186d8786c5ef65445bcc5b2ad5f660ffc7c8eaac0:32d22904d3e7012d6f5a441b0b4228064a5cf95b723a66b048a087ecd55920c31c204c3f2006891a85dd1932e3f1d614cfd633b5e63291c6d8166f3011431e09a6d4d0542cfe0d240a90507debacabce7cbbd48732353f4fad82c7bb7dbd9df8e7d9a16980a45186d8786c5ef65445bcc5b2ad5f660ffc7c8eaac0: +49dd473ede6aa3c866824a40ada4996c239a20d84c9365e4f0a4554f8031b9cf788de540544d3feb0c919240b390729be487e94b64ad973eb65b4669ecf23501:788de540544d3feb0c919240b390729be487e94b64ad973eb65b4669ecf23501:3a53594f3fba03029318f512b084a071ebd60baec7f55b028dc73bfc9c74e0ca496bf819dd92ab61cd8b74be3c0d6dcd128efc5ed3342cba124f726c:d2fde02791e720852507faa7c3789040d9ef86646321f313ac557f4002491542dd67d05c6990cdb0d495501fbc5d5188bfbb84dc1bf6098bee0603a47fc2690f3a53594f3fba03029318f512b084a071ebd60baec7f55b028dc73bfc9c74e0ca496bf819dd92ab61cd8b74be3c0d6dcd128efc5ed3342cba124f726c: +331c64da482b6b551373c36481a02d8136ecadbb01ab114b4470bf41607ac57152a00d96a3148b4726692d9eff89160ea9f99a5cc4389f361fed0bb16a42d521:52a00d96a3148b4726692d9eff89160ea9f99a5cc4389f361fed0bb16a42d521:20e1d05a0d5b32cc8150b8116cef39659dd5fb443ab15600f78e5b49c45326d9323f2850a63c3808859495ae273f58a51e9de9a145d774b40ba9d753d3:22c99aa946ead39ac7997562810c01c20b46bd610645bd2d56dcdcbaacc5452c74fbf4b8b1813b0e94c30d808ce5498e61d4f7ccbb4cc5f04dfc6140825a960020e1d05a0d5b32cc8150b8116cef39659dd5fb443ab15600f78e5b49c45326d9323f2850a63c3808859495ae273f58a51e9de9a145d774b40ba9d753d3: +5c0b96f2af8712122cf743c8f8dc77b6cd5570a7de13297bb3dde1886213cce20510eaf57d7301b0e1d527039bf4c6e292300a3a61b4765434f3203c100351b1:0510eaf57d7301b0e1d527039bf4c6e292300a3a61b4765434f3203c100351b1:54e0caa8e63919ca614b2bfd308ccfe50c9ea888e1ee4446d682cb5034627f97b05392c04e835556c31c52816a48e4fb196693206b8afb4408662b3cb575:06e5d8436ac7705b3a90f1631cdd38ec1a3fa49778a9b9f2fa5ebea4e7d560ada7dd26ff42fafa8ba420323742761aca6904940dc21bbef63ff72daab45d430b54e0caa8e63919ca614b2bfd308ccfe50c9ea888e1ee4446d682cb5034627f97b05392c04e835556c31c52816a48e4fb196693206b8afb4408662b3cb575: +bf5ba5d6a49dd5ef7b4d5d7d3e4ecc505c01f6ccee4c54b5ef7b40af6a4541401be034f813017b900d8990af45fad5b5214b573bd303ef7a75ef4b8c5c5b9842:1be034f813017b900d8990af45fad5b5214b573bd303ef7a75ef4b8c5c5b9842:16152c2e037b1c0d3219ced8e0674aee6b57834b55106c5344625322da638ecea2fc9a424a05ee9512d48fcf75dd8bd4691b3c10c28ec98ee1afa5b863d1c36795ed18105db3a9aabd9d2b4c1747adbaf1a56ffcc0c533c1c0faef331cdb79d961fa39f880a1b8b1164741822efb15a7259a465bef212855751fab66a897bfa211abe0ea2f2e1cd8a11d80e142cde1263eec267a3138ae1fcf4099db0ab53d64f336f4bcd7a363f6db112c0a2453051a0006f813aaf4ae948a2090619374fa58052409c28ef76225687df3cb2d1b0bfb43b09f47f1232f790e6d8dea759e57942099f4c4bd3390f28afc2098244961465c643fc8b29766af2bcbc5440b86e83608cfc937be98bb4827fd5e6b689adc2e26513db531076a6564396255a09975b7034dac06461b255642e3a7ed75fa9fc265011f5f6250382a84ac268d63ba64:279cace6fdaf3945e3837df474b28646143747632bede93e7a66f5ca291d2c24978512ca0cb8827c8c322685bd605503a5ec94dbae61bbdcae1e49650602bc0716152c2e037b1c0d3219ced8e0674aee6b57834b55106c5344625322da638ecea2fc9a424a05ee9512d48fcf75dd8bd4691b3c10c28ec98ee1afa5b863d1c36795ed18105db3a9aabd9d2b4c1747adbaf1a56ffcc0c533c1c0faef331cdb79d961fa39f880a1b8b1164741822efb15a7259a465bef212855751fab66a897bfa211abe0ea2f2e1cd8a11d80e142cde1263eec267a3138ae1fcf4099db0ab53d64f336f4bcd7a363f6db112c0a2453051a0006f813aaf4ae948a2090619374fa58052409c28ef76225687df3cb2d1b0bfb43b09f47f1232f790e6d8dea759e57942099f4c4bd3390f28afc2098244961465c643fc8b29766af2bcbc5440b86e83608cfc937be98bb4827fd5e6b689adc2e26513db531076a6564396255a09975b7034dac06461b255642e3a7ed75fa9fc265011f5f6250382a84ac268d63ba64: +65de297b70cbe80980500af0561a24db50001000125f4490366d8300d3128592ba8e2ad929bdcea538741042b57f2067d3153707a453770db9f3c4ca75504d24:ba8e2ad929bdcea538741042b57f2067d3153707a453770db9f3c4ca75504d24:131d8f4c2c94b153565b86592e770c987a443461b39aa2408b29e213ab057affc598b583739d6603a83fef0afc514721db0e76f9bd1b72b98c565cc8881af5747c0ba6f58c53dd2377da6c0d3aa805620cc4e75d52aabcba1f9b2849e08bd1b6b92e6f06615b814519606a02dc65a8609f5b29e9c2af5a894f7116ef28cfd1e7b76b64061732f7a5a3f8aa4c2e569e627a3f9749aa597be49d6b94436c352dd5fa7b83c92d2610faa32095ca302152d91a3c9776750e758ee8e9e402c6f5385eaa5df23850e54beb1be437a416c7115ed6aa6de13b55482532787e0bee34b83f3084406765635497c931b62a0518f1fbc2b891dc7262c7c6b67eda594fa530d74c9329bad5be94c287fbcde53aa80272b83322613d9368e5904076fdbcc88b2c0e59c10b02c448e00d1b3e7a9c9640feffb9523a8a60e1d83f04a4b8df69153b:7a9b736b01cc92a3349f1a3c32dbd91959825394ff443c567405e899c8185ce8fad9500e1fce89d95a6253c00477435acf04bff993de1b00495def0834ee1f07131d8f4c2c94b153565b86592e770c987a443461b39aa2408b29e213ab057affc598b583739d6603a83fef0afc514721db0e76f9bd1b72b98c565cc8881af5747c0ba6f58c53dd2377da6c0d3aa805620cc4e75d52aabcba1f9b2849e08bd1b6b92e6f06615b814519606a02dc65a8609f5b29e9c2af5a894f7116ef28cfd1e7b76b64061732f7a5a3f8aa4c2e569e627a3f9749aa597be49d6b94436c352dd5fa7b83c92d2610faa32095ca302152d91a3c9776750e758ee8e9e402c6f5385eaa5df23850e54beb1be437a416c7115ed6aa6de13b55482532787e0bee34b83f3084406765635497c931b62a0518f1fbc2b891dc7262c7c6b67eda594fa530d74c9329bad5be94c287fbcde53aa80272b83322613d9368e5904076fdbcc88b2c0e59c10b02c448e00d1b3e7a9c9640feffb9523a8a60e1d83f04a4b8df69153b: +0826e7333324e7ec8c764292f6015d4670e9b8d7c4a89e8d909e8ef435d18d15ffb2348ca8a018058be71d1512f376f91e8b0d552581254e107602217395e662:ffb2348ca8a018058be71d1512f376f91e8b0d552581254e107602217395e662:7f9e3e2f03c9df3d21b990f5a4af8295734afe783accc34fb1e9b8e95a0fd837af7e05c13cda0de8fadac9205265a0792b52563bdc2fee766348befcc56b88bbb95f154414fb186ec436aa62ea6fcabb11c017a9d2d15f67e595980e04c9313bc94fbc8c1134c2f40332bc7e311ac1ce11b505f8572ada7fbe196fba822d9a914492fa7185e9f3bea4687200a524c673a1cdf87eb3a140dcdb6a8875613488a2b00adf7175341c1c257635fa1a53a3e21d60c228399eea0991f112c60f653d7148e2c5ceb98f940831f070db1084d79156cc82c46bc9b8e884f3fa81be2da4cdda46bcaa24cc461f76ee647bb0f0f8c15ac5daa795b945e6f85bb310362e48d8095c782c61c52b481b4b002ad06ea74b8d306eff71abf21db710a8913cbe48332be0a0b3f31e0c7a6eba85ce33f357c7aeccd30bfb1a6574408b66fe404d31c3c5:4bac7fabec8724d81ab09ae130874d70b5213492104372f601ae5abb10532799373c4dad215876441f474e2c006be37c3c8f5f6f017d0870414fd276a8f428087f9e3e2f03c9df3d21b990f5a4af8295734afe783accc34fb1e9b8e95a0fd837af7e05c13cda0de8fadac9205265a0792b52563bdc2fee766348befcc56b88bbb95f154414fb186ec436aa62ea6fcabb11c017a9d2d15f67e595980e04c9313bc94fbc8c1134c2f40332bc7e311ac1ce11b505f8572ada7fbe196fba822d9a914492fa7185e9f3bea4687200a524c673a1cdf87eb3a140dcdb6a8875613488a2b00adf7175341c1c257635fa1a53a3e21d60c228399eea0991f112c60f653d7148e2c5ceb98f940831f070db1084d79156cc82c46bc9b8e884f3fa81be2da4cdda46bcaa24cc461f76ee647bb0f0f8c15ac5daa795b945e6f85bb310362e48d8095c782c61c52b481b4b002ad06ea74b8d306eff71abf21db710a8913cbe48332be0a0b3f31e0c7a6eba85ce33f357c7aeccd30bfb1a6574408b66fe404d31c3c5: +00ad6227977b5f38ccda994d928bba9086d2daeb013f8690db986648b90c1d4591a4ea005752b92cbebf99a8a5cbecd240ae3f016c44ad141b2e57ddc773dc8e:91a4ea005752b92cbebf99a8a5cbecd240ae3f016c44ad141b2e57ddc773dc8e:cb5bc5b98b2efce43543e91df041e0dbb53ed8f67bf0f197c52b2211e7a45e2e1ec818c1a80e10abf6a43535f5b79d974d8ae28a2295c0a6521763b607d5103c6aef3b2786bd5afd7563695660684337bc3090739fb1cd53a9d644139b6d4caec75bda7f2521fbfe676ab45b98cb317aa7ca79fc54a3d7c578466a6aa64e434e923465a7f211aa0c61681bb8486e90206a25250d3fdae6fb03299721e99e2a914910d91760089b5d281e131e6c836bc2de08f7e02c48d323c647e9536c00ec1039201c0362618c7d47aa8e7b9715ffc439987ae1d31154a6198c5aa11c128f4082f556c99baf103ecadc3b2f3b2ec5b469623bc03a53caf3814b16300aedbda538d676d1f607102639db2a62c446707ce6469bd873a0468225be88b0aef5d4020459b94b32fe2b0133e92e7ba54dd2a5397ed85f966ab39ed0730cca8e7dacb8a336:dc501db79fd782bc88cae792557d5d273f9ba560c7d90037fe84ac879d684f612a77452c4443e95c07b8be192c35769b17bbdfca42280de796d92119d833670dcb5bc5b98b2efce43543e91df041e0dbb53ed8f67bf0f197c52b2211e7a45e2e1ec818c1a80e10abf6a43535f5b79d974d8ae28a2295c0a6521763b607d5103c6aef3b2786bd5afd7563695660684337bc3090739fb1cd53a9d644139b6d4caec75bda7f2521fbfe676ab45b98cb317aa7ca79fc54a3d7c578466a6aa64e434e923465a7f211aa0c61681bb8486e90206a25250d3fdae6fb03299721e99e2a914910d91760089b5d281e131e6c836bc2de08f7e02c48d323c647e9536c00ec1039201c0362618c7d47aa8e7b9715ffc439987ae1d31154a6198c5aa11c128f4082f556c99baf103ecadc3b2f3b2ec5b469623bc03a53caf3814b16300aedbda538d676d1f607102639db2a62c446707ce6469bd873a0468225be88b0aef5d4020459b94b32fe2b0133e92e7ba54dd2a5397ed85f966ab39ed0730cca8e7dacb8a336: +1521c6dbd6f724de73eaf7b56264f01035c04e01c1f3eb3cbe83efd26c439ada2f61a26ffb68ba4f6e141529dc2617e8531c7151404808093b4fa7fedaea255d:2f61a26ffb68ba4f6e141529dc2617e8531c7151404808093b4fa7fedaea255d:3e3c7c490788e4b1d42f5cbcae3a9930bf617ebdff447f7be2ac2ba7cd5bcfc015760963e6fe5b956fb7cdb35bd5a17f5429ca664f437f08753a741c2bc8692b71a9115c582a25b2f74d329854d60b7817c079b3523aaff8793c2f72fff8cd10592c54e738df1d6452fb72da131c6731ea5c953c62ea177ac1f4735e5154477387109afae15f3ed6eeb08606e28c81d4386f03b9376924b6ef8d221ee29547f82a7ede48e1dc17723e3d42171eeaf96ac84bedc2a01dd86f4d085734fd69f91b5263e439083ff0318536adff4147308e3aafd1b58bb74f6fb0214a46fdcd3524f18df5a719ce57319e791b4ea606b499bfa57a60e707f94e18f1fed22f91bc79e6364a843f9cbf93825c465e9cae9072bc9d3ec4471f21ab2f7e99a633f587aac3db78ae9666a89a18008dd61d60218554411a65740ffd1ae3adc06595e3b7876407b6:a817ed23ec398a128601c1832dc6af7643bf3a5f517bcc579450fdb4759028f4966164125f6ebd0d6bf86ff298a39c766d0c21fdb0cbfdf81cd0eb1f03cd8a083e3c7c490788e4b1d42f5cbcae3a9930bf617ebdff447f7be2ac2ba7cd5bcfc015760963e6fe5b956fb7cdb35bd5a17f5429ca664f437f08753a741c2bc8692b71a9115c582a25b2f74d329854d60b7817c079b3523aaff8793c2f72fff8cd10592c54e738df1d6452fb72da131c6731ea5c953c62ea177ac1f4735e5154477387109afae15f3ed6eeb08606e28c81d4386f03b9376924b6ef8d221ee29547f82a7ede48e1dc17723e3d42171eeaf96ac84bedc2a01dd86f4d085734fd69f91b5263e439083ff0318536adff4147308e3aafd1b58bb74f6fb0214a46fdcd3524f18df5a719ce57319e791b4ea606b499bfa57a60e707f94e18f1fed22f91bc79e6364a843f9cbf93825c465e9cae9072bc9d3ec4471f21ab2f7e99a633f587aac3db78ae9666a89a18008dd61d60218554411a65740ffd1ae3adc06595e3b7876407b6: +17e5f0a8f34751babc5c723ecf339306992f39ea065ac140fcbc397d2dd32c4b4f1e23cc0f2f69c88ef9162ab5f8c59fb3b8ab2096b77e782c63c07c8c4f2b60:4f1e23cc0f2f69c88ef9162ab5f8c59fb3b8ab2096b77e782c63c07c8c4f2b60:c0fad790024019bd6fc08a7a92f5f2ac35cf6432e2eaa53d482f6e1204935336cb3ae65a63c24d0ec6539a10ee18760f2f520537774cdec6e96b55536011daa8f8bcb9cdaf6df5b34648448ac7d7cb7c6bd80d67fbf330f8765297766046a925ab52411d1604c3ed6a85173040125658a32cf4c854ef2813df2be6f3830e5eee5a6163a83ca8849f612991a31e9f88028e50bf8535e11755fad029d94cf25959f6695d09c1ba4315d40f7cf51b3f8166d02faba7511ecd8b1dded5f10cd6843455cff707ed225396c61d0820d20ada70d0c3619ff679422061c9f7c76e97d5a37af61fd62212d2dafc647ebbb979e61d9070ec03609a07f5fc57d119ae64b7a6ef92a5afae660a30ed48d702cc3128c633b4f19060a0578101729ee979f790f45bdbb5fe1a8a62f01a61a31d61af07030450fa0417323e9407bc76e73130e7c69d62e6a7:efe2cb63fe7b4fc98946dc82fb6998e741ed9ce6b9c1a93bb45bc0a7d8396d7405282b43fe363ba5b23589f8e1fae130e157ce888cd72d053d0cc19d257a4300c0fad790024019bd6fc08a7a92f5f2ac35cf6432e2eaa53d482f6e1204935336cb3ae65a63c24d0ec6539a10ee18760f2f520537774cdec6e96b55536011daa8f8bcb9cdaf6df5b34648448ac7d7cb7c6bd80d67fbf330f8765297766046a925ab52411d1604c3ed6a85173040125658a32cf4c854ef2813df2be6f3830e5eee5a6163a83ca8849f612991a31e9f88028e50bf8535e11755fad029d94cf25959f6695d09c1ba4315d40f7cf51b3f8166d02faba7511ecd8b1dded5f10cd6843455cff707ed225396c61d0820d20ada70d0c3619ff679422061c9f7c76e97d5a37af61fd62212d2dafc647ebbb979e61d9070ec03609a07f5fc57d119ae64b7a6ef92a5afae660a30ed48d702cc3128c633b4f19060a0578101729ee979f790f45bdbb5fe1a8a62f01a61a31d61af07030450fa0417323e9407bc76e73130e7c69d62e6a7: +0cd7aa7d605e44d5ffb97966b2cb93c189e4c5a85db87fad7ab8d62463c59b594889855fe4116b4913927f47f2273bf559c3b394a983631a25ae597033185e46:4889855fe4116b4913927f47f2273bf559c3b394a983631a25ae597033185e46:28a55dda6cd0844b6577c9d6da073a4dc35cbc98ac158ab54cf88fd20cc87e83c4bba2d74d82ce0f4854ec4db513de400465aaa5eee790bc84f16337072d3a91cde40d6e0df1ba0cc0645f5d5cbbb642381d7b9e211d25267a8acf77d1edb69c3a630f5b133d24f046a81bf22ff03b31d8447e12c3f7b77114a70cbd20bbd08b0b3827a6bbcf90409e344447a7fbc59bdd97d729071f8d71dcc33e6ef2cbab1d411edf13734db1dd9703276f5eb2d6aa2cb8952dd6712bfae809ce08c3aa502b8135713fac0a9c25b1d45b6a5831e02421bba65b81a596efa24b0576bd1dc7fdfb49be762875e81bd540722bc06140b9aa2ef7b84a801e41ded68d4546ac4873d9e7ced649b64fadaf0b5c4b6eb8d036315233f4326ca01e03393050cd027c24f67303fb846bd2c6b3dba06bed0d59a36289d24bd648f7db0b3a81346612593e3ddd18c557:bf9115fd3d02706e398d4bf3b02a82674ff3041508fd39d29f867e501634b9261f516a794f98738d7c7013a3f2f858ffdd08047fb6bf3dddfb4b4f4cbeef300328a55dda6cd0844b6577c9d6da073a4dc35cbc98ac158ab54cf88fd20cc87e83c4bba2d74d82ce0f4854ec4db513de400465aaa5eee790bc84f16337072d3a91cde40d6e0df1ba0cc0645f5d5cbbb642381d7b9e211d25267a8acf77d1edb69c3a630f5b133d24f046a81bf22ff03b31d8447e12c3f7b77114a70cbd20bbd08b0b3827a6bbcf90409e344447a7fbc59bdd97d729071f8d71dcc33e6ef2cbab1d411edf13734db1dd9703276f5eb2d6aa2cb8952dd6712bfae809ce08c3aa502b8135713fac0a9c25b1d45b6a5831e02421bba65b81a596efa24b0576bd1dc7fdfb49be762875e81bd540722bc06140b9aa2ef7b84a801e41ded68d4546ac4873d9e7ced649b64fadaf0b5c4b6eb8d036315233f4326ca01e03393050cd027c24f67303fb846bd2c6b3dba06bed0d59a36289d24bd648f7db0b3a81346612593e3ddd18c557: +33371d9e892f9875052ac8e325ba505e7477c1ace24ba7822643d43d0acef3de35929bded27c249c87d8b8d82f59260a575327b546c3a167c69f5992d5b8e006:35929bded27c249c87d8b8d82f59260a575327b546c3a167c69f5992d5b8e006:27a32efba28204be59b7ff5fe488ca158a91d5986091ecc4458b49e090dd37cbfede7c0f46186fabcbdff78d2844155808efffd873ed9c9261526e04e4f7050b8d7bd267a0fe3d5a449378d54a4febbd2f26824338e2aaaf35a32ff0f62504bda5c2e44abc63159f336cf25e6bb40ddb7d8825dff18fd51fc01951eaedcd33707007e1203ca58b4f7d242f8166a907e099932c001bfb1ec9a61e0ef2da4e8446af208201315d69681710d425d2400c387d7b9df321a4aec602b9c656c3e2310bff8756d18b802134b15604f4edc111149a9879e31241dd34f702f4c349617b13529769a772f5e52a89c098e0dca5920667893a250061b17991626eb9319298685be46b6a8b68422444fa5a36bcf3a687e2eccb9322c87dc80165da898930850b98fc863cada1aa99c6d61c451b9ccf4874c7f0e75b0a0c602f044812c71765adaf02025395b0:985ca446ddc007827cc8f2852cbd8115ef8c5975e9d7ce96d74dfed859aa14a4c15254006bea5e08359efe2625d715e0897ee5a16f151203be5010418637de0527a32efba28204be59b7ff5fe488ca158a91d5986091ecc4458b49e090dd37cbfede7c0f46186fabcbdff78d2844155808efffd873ed9c9261526e04e4f7050b8d7bd267a0fe3d5a449378d54a4febbd2f26824338e2aaaf35a32ff0f62504bda5c2e44abc63159f336cf25e6bb40ddb7d8825dff18fd51fc01951eaedcd33707007e1203ca58b4f7d242f8166a907e099932c001bfb1ec9a61e0ef2da4e8446af208201315d69681710d425d2400c387d7b9df321a4aec602b9c656c3e2310bff8756d18b802134b15604f4edc111149a9879e31241dd34f702f4c349617b13529769a772f5e52a89c098e0dca5920667893a250061b17991626eb9319298685be46b6a8b68422444fa5a36bcf3a687e2eccb9322c87dc80165da898930850b98fc863cada1aa99c6d61c451b9ccf4874c7f0e75b0a0c602f044812c71765adaf02025395b0: +beedb8073df58f8c1bffbdbd77ec7decb2c82a9babecefc0331507bdc2c2a7e7b27e908b805e296fc30d2e474b060cd50c0f6f520b3671712183bd89d4e733e9:b27e908b805e296fc30d2e474b060cd50c0f6f520b3671712183bd89d4e733e9:35ca57f0f915e5209d54ea4b871ffb585354df1b4a4a1796fbe4d6227d3e1aba5171ed0391a79e83e24d82fdafd15c17b28bf6c94d618c74d65264e58faaacd2902872fdd0efa22e8d2d7ce8e3b8197f0c3615b0a385235fa9fd8e4564ee6e6b1650b4cfb94d872c805c32d4f3a18f966461d3adbb605fa525884f8eb197627396ba4d995d78ac02948a0eaabb58519b9a8e2e7985cd1de2c71d8918d96a0168660ce17cddf364e3ec0d4bd90f2104751a1927ee1d23f3e7a69840ed040b00e5f6e4866ec58813149cc382aebf6162608c79574d553f47230e924a0ef1ebf55d8e1a52abb62a2d7ac86027c7c03cc83fa1949da29e2f3037ab986fd2fffe650e3149babae5a50b1ee9696f3babec72e29697c82422814d272085500fd837fe3c7a973ef4c169af12dd7f02700620bb045bdbf84623f326350570b3cadbc9aea4200b28287e17ab:8c890cccadc7760e1e82e43c44b3dc0b685a48b479ae13cc0a6b0557d0fb1cbabba63d2a96843412ea8d36c50acbf52b92cfb2dce49dc48af6ddcf8ee47a860835ca57f0f915e5209d54ea4b871ffb585354df1b4a4a1796fbe4d6227d3e1aba5171ed0391a79e83e24d82fdafd15c17b28bf6c94d618c74d65264e58faaacd2902872fdd0efa22e8d2d7ce8e3b8197f0c3615b0a385235fa9fd8e4564ee6e6b1650b4cfb94d872c805c32d4f3a18f966461d3adbb605fa525884f8eb197627396ba4d995d78ac02948a0eaabb58519b9a8e2e7985cd1de2c71d8918d96a0168660ce17cddf364e3ec0d4bd90f2104751a1927ee1d23f3e7a69840ed040b00e5f6e4866ec58813149cc382aebf6162608c79574d553f47230e924a0ef1ebf55d8e1a52abb62a2d7ac86027c7c03cc83fa1949da29e2f3037ab986fd2fffe650e3149babae5a50b1ee9696f3babec72e29697c82422814d272085500fd837fe3c7a973ef4c169af12dd7f02700620bb045bdbf84623f326350570b3cadbc9aea4200b28287e17ab: +9184ef618816832592bc8eb35f4ffd4ff98dfbf7776c90f2aad212ce7e03351e687b7726010d9bde2c90e573cd2a2a702ff28c4a2af70afc7315c94d575601e5:687b7726010d9bde2c90e573cd2a2a702ff28c4a2af70afc7315c94d575601e5:729eb7e54a9d00c58617af18c345b8dc6e5b4e0f57de2f3c02e54a2ec8f1425ec2e240775b5ab0c10f84ac8bafda4584f7e21c655faecd8030a98906bd68398f26b5d58d92b6cf045e9bd9743c74c9a342ec61ce57f37b981eac4d8bf034608866e985bb68686a68b4a2af88b992a2a6d2dc8ce88bfb0a36cf28bbab7024abfa2bea53313b66c906f4f7cf66970f540095bd0104aa4924dd82e15413c22679f847e48cd0c7ec1f677e005fec0177fbd5c559fc39add613991fbaeae4d24d39d309ef74647f8192cc4c62d0642028c76a1b951f6bc9639deb91ecc08be6043f2109705a42c7eae712649d91d96ccbbfb63d8d0dd6dd112160f61361ecdc6793929ca9aef9ab56944a6fa4a7df1e279eaf58ce8323a9cf62c94279fff7440fbc936baa61489c999330badcb9fc0e184bc5093f330cbb242f71fb378738fea10511dd438364d7f76bcc:b3c24e75132c563475422d5ea412b5c1e8e6e5ea1c08ead1393c412da134c9a1638284ea7e2ca032fe3d3e32a9066a8c8839903f6ef46e966bb5e492d8c2aa00729eb7e54a9d00c58617af18c345b8dc6e5b4e0f57de2f3c02e54a2ec8f1425ec2e240775b5ab0c10f84ac8bafda4584f7e21c655faecd8030a98906bd68398f26b5d58d92b6cf045e9bd9743c74c9a342ec61ce57f37b981eac4d8bf034608866e985bb68686a68b4a2af88b992a2a6d2dc8ce88bfb0a36cf28bbab7024abfa2bea53313b66c906f4f7cf66970f540095bd0104aa4924dd82e15413c22679f847e48cd0c7ec1f677e005fec0177fbd5c559fc39add613991fbaeae4d24d39d309ef74647f8192cc4c62d0642028c76a1b951f6bc9639deb91ecc08be6043f2109705a42c7eae712649d91d96ccbbfb63d8d0dd6dd112160f61361ecdc6793929ca9aef9ab56944a6fa4a7df1e279eaf58ce8323a9cf62c94279fff7440fbc936baa61489c999330badcb9fc0e184bc5093f330cbb242f71fb378738fea10511dd438364d7f76bcc: +354e13152ee1fe748a1252204c6527bdc1b1eb2eb53678150e6359924708d812d45ff6c5fb83e7bb9669aa8960deb7dbc665c988439b6c9ef672c6811dc8bcf6:d45ff6c5fb83e7bb9669aa8960deb7dbc665c988439b6c9ef672c6811dc8bcf6:8e5fccf66b1ba6169cb685733d9d0e0190361c90bcab95c163285a97fe356d2bdcde3c9380268805a384d063da09ccd9969cc3ff7431e60a8e9f869cd62faa0e356151b280bc526e577c2c538c9a724dc48bf88b70321d7e1eeedb3c4af706748c942e67bdabdb41bec2977b1523069e31e29b76300288f88a51b384b80cc2526f1679340ddec3881f5cd28b0378d9cd0a812b68dd3f68f7a23e1b54bee7466ac765cf38df04d67441dfa498c4bffc52045fa6d2dbcdbfa33dfaa77644ffccef0decdb6790c70a0d734ec287cc338cb5a909c0055189301169c4f7702c05c0911a27b16ef9ed934fa6a0ca7b13e413523422535647968030edc40cd73e7d6b345b7581f438316d68e3cd292b846d3f4f7c4862bc7e6b3fb89a27f6f60cd7db2e34ec9aae1013fe37acff8ad888cb9a593ef5e621eae5186c58b31dcfde22870e336d33f440f6b8d49a:de2b46e65f3decef34332e500f2e11306fbdcf1be85a1c1ee68ba3045dcec2c7be608d22927da1f44c0e2083ae622cf3c29d893887994efcfa2ca594f5051f038e5fccf66b1ba6169cb685733d9d0e0190361c90bcab95c163285a97fe356d2bdcde3c9380268805a384d063da09ccd9969cc3ff7431e60a8e9f869cd62faa0e356151b280bc526e577c2c538c9a724dc48bf88b70321d7e1eeedb3c4af706748c942e67bdabdb41bec2977b1523069e31e29b76300288f88a51b384b80cc2526f1679340ddec3881f5cd28b0378d9cd0a812b68dd3f68f7a23e1b54bee7466ac765cf38df04d67441dfa498c4bffc52045fa6d2dbcdbfa33dfaa77644ffccef0decdb6790c70a0d734ec287cc338cb5a909c0055189301169c4f7702c05c0911a27b16ef9ed934fa6a0ca7b13e413523422535647968030edc40cd73e7d6b345b7581f438316d68e3cd292b846d3f4f7c4862bc7e6b3fb89a27f6f60cd7db2e34ec9aae1013fe37acff8ad888cb9a593ef5e621eae5186c58b31dcfde22870e336d33f440f6b8d49a: +7ff62d4b3c4d99d342d4bb401d726b21e99f4ef592149fc311b68761f5567ff67fdfdb9eca29d3f01d9486d7e112ce03aa37b91326a4283b9c03999c5eda099a:7fdfdb9eca29d3f01d9486d7e112ce03aa37b91326a4283b9c03999c5eda099a:99c44c796572a4823fc6c3807730839173774c05dbfc1492ed0d00509a95a1de37274b3135ed0456a1718e576597dc13f2a2ab37a45c06cbb4a2d22afad4d5f3d90ab3d8da4dcdaa06d44f2219088401c5dceee26055c4782f78d7d63a380608e1bef89eeef338c2f0897da106fafce2fb2ebc5db669c7c172c9cfe77d3109d239fe5d005c8ee751511b5a88317c729b0d8b70b52f6bd3cda2fe865c77f36e4f1b635f336e036bd718bec90ee78a802811510c4058c1ba364017253aa842922e1dd7d7a0f0fc9c69e43fc4eaeffaaf1ae5fa5d2d73b43079617baba030923fe5b13d2c1c4fe6fac3f2db74e2020a734b6121a0302fce820ba0580ce6135348fdf0632e0008df03ee112168f5cfa0037a26a1f69b1f1317edf2a3ab367455a77e00691215d7aa3133c2159d3da2b134cf04f0defbf07a6064011e64dd14d4f8f064356655428804c2771a:058f79927fbf6178724815c7b11c63baaa90bcc15d7272be082f8a9141861c816433055f6cf6491424853f9ec78bb91ace913a93411b4e5ed58bc4ba5715c60a99c44c796572a4823fc6c3807730839173774c05dbfc1492ed0d00509a95a1de37274b3135ed0456a1718e576597dc13f2a2ab37a45c06cbb4a2d22afad4d5f3d90ab3d8da4dcdaa06d44f2219088401c5dceee26055c4782f78d7d63a380608e1bef89eeef338c2f0897da106fafce2fb2ebc5db669c7c172c9cfe77d3109d239fe5d005c8ee751511b5a88317c729b0d8b70b52f6bd3cda2fe865c77f36e4f1b635f336e036bd718bec90ee78a802811510c4058c1ba364017253aa842922e1dd7d7a0f0fc9c69e43fc4eaeffaaf1ae5fa5d2d73b43079617baba030923fe5b13d2c1c4fe6fac3f2db74e2020a734b6121a0302fce820ba0580ce6135348fdf0632e0008df03ee112168f5cfa0037a26a1f69b1f1317edf2a3ab367455a77e00691215d7aa3133c2159d3da2b134cf04f0defbf07a6064011e64dd14d4f8f064356655428804c2771a: +6cabadd03f8a2e6ebab96a74f80e18164e4d1b6baa678f5a82e25604af989aaf2a4a3179564194e00100c18bc35351d8b135bbae5b32b28fce1d7b6766ca4b32:2a4a3179564194e00100c18bc35351d8b135bbae5b32b28fce1d7b6766ca4b32:279f78cf3b9ccfc6e1b01e1a82f50ed172e9a8e1e702bb15661dd7dc3a456ff7a7a7fdfb081db3867079630c7f70fd753292ec60ecbf50632e9aa45b996505c66e6dc3c6ae892e21b6a8705e4bbae8f16a3378554b31fdb0139dcd15c96a8a7e4b88756a86d18db5dc74fd7691197dd88e2c7d5df52b049344cdc477c9cd7e89eda99ccfb1d00814d0152b9654df3279372ca5f18b1c946f2894a76b079ddb1c3cd61fbb969aeec9193a6b88fb7d136c07f9821e5c1074b4e93bcaf6fa14d0d1d7e1707589d77ec1337206e53a1f06cc26672ff95c13d5ff444766931ba30a0afdcdadd2098e9c41fd87a3f23cd16dbb0efbf8092ce33e327f42610990e1cee6cb8e54951aa081e69765ae4009aeed758e768de50c23d9a22b4a06dc4d19fc8cbd0cdef4c983461755d0a3b5d6a9c12253e09568339ff7e5f78c5fdf7ec89f9186a621a8c0eed11b67022e:4e65c6c1d493045e8a9250e397c1d1d30ffed24db66a8961aa458f8f0fcb760c39fe8657d7ab8f84000b96d519717cff71f926522c1efec7f8b2624eae55f60c279f78cf3b9ccfc6e1b01e1a82f50ed172e9a8e1e702bb15661dd7dc3a456ff7a7a7fdfb081db3867079630c7f70fd753292ec60ecbf50632e9aa45b996505c66e6dc3c6ae892e21b6a8705e4bbae8f16a3378554b31fdb0139dcd15c96a8a7e4b88756a86d18db5dc74fd7691197dd88e2c7d5df52b049344cdc477c9cd7e89eda99ccfb1d00814d0152b9654df3279372ca5f18b1c946f2894a76b079ddb1c3cd61fbb969aeec9193a6b88fb7d136c07f9821e5c1074b4e93bcaf6fa14d0d1d7e1707589d77ec1337206e53a1f06cc26672ff95c13d5ff444766931ba30a0afdcdadd2098e9c41fd87a3f23cd16dbb0efbf8092ce33e327f42610990e1cee6cb8e54951aa081e69765ae4009aeed758e768de50c23d9a22b4a06dc4d19fc8cbd0cdef4c983461755d0a3b5d6a9c12253e09568339ff7e5f78c5fdf7ec89f9186a621a8c0eed11b67022e: +0fa0c32c3ae34be51b92f91945405981a8e202488558a8e220c288c7d6a5532dd6aee62bd91fc9453635ffcc02b2f38dcab13285140380580ccdff0865df0492:d6aee62bd91fc9453635ffcc02b2f38dcab13285140380580ccdff0865df0492:53f44be0e5997ff07264cb64ba1359e2801def8755e64a2362bddaf597e672d021d34fface6d97e0f2b1f6ae625fd33d3c4f6e9ff7d0c73f1da8defb23f324975e921bb2473258177a16612567edf7d5760f3f3e3a6d26aaabc5fde4e2043f73fa70f128020933b1ba3b6bd69498e9503ea670f1ed880d3651f2e4c59e79cabc86e9b703394294112d5d8e213c317423b525a6df70106a9d658a262028b5f45100cb77d1150d8fe461eed434f241015f3276ad7b09a291b4a7f35e3c30051cbf13b1d4a7fa0c81a50f939e7c49673afdc87883c9e3e61f5a1df03755470fda74bf23ea88676b258a97a280d5f90b52b714b596035bae08c8d0fe6d94f8949559b1f27d7116cf59dd3cfbf18202a09c13f5c4fbc8d97225492887d32870c2297e34debd9876d6d01ac27a16b088b079079f2b20feb02537cda314c43cb2dca371b9df37ed11ec97e1a7a6993a:7e9ab85ee94fe4b35dcb545329a0ef25923de5c9dc23e7df1a7e77ab0dcfb89e03f4e785ca6429cb2b0df50da6230f733f00f33a45c4e576cd40bdb84f1ae00153f44be0e5997ff07264cb64ba1359e2801def8755e64a2362bddaf597e672d021d34fface6d97e0f2b1f6ae625fd33d3c4f6e9ff7d0c73f1da8defb23f324975e921bb2473258177a16612567edf7d5760f3f3e3a6d26aaabc5fde4e2043f73fa70f128020933b1ba3b6bd69498e9503ea670f1ed880d3651f2e4c59e79cabc86e9b703394294112d5d8e213c317423b525a6df70106a9d658a262028b5f45100cb77d1150d8fe461eed434f241015f3276ad7b09a291b4a7f35e3c30051cbf13b1d4a7fa0c81a50f939e7c49673afdc87883c9e3e61f5a1df03755470fda74bf23ea88676b258a97a280d5f90b52b714b596035bae08c8d0fe6d94f8949559b1f27d7116cf59dd3cfbf18202a09c13f5c4fbc8d97225492887d32870c2297e34debd9876d6d01ac27a16b088b079079f2b20feb02537cda314c43cb2dca371b9df37ed11ec97e1a7a6993a: +7b06f88026fa86f39fce2426f67cc5996bedd0cfc4b5ebb1b5e3edbb47e080aa3f1469ee6a2e7867e2e9012d402cf5a4861497c01df879a1deb1c539830b58de:3f1469ee6a2e7867e2e9012d402cf5a4861497c01df879a1deb1c539830b58de:71175d4e21721297d9176d817f4e785d9600d923f987fe0b26fd79d33a5ea5d1e818b71f0f92b8c73afddabdcc27f6d16e26aafa874cfd77a00e06c36b041487582bb933760f88b419127345776ea418f83522254fed33819bc5c95f8f8404cc144ebf1486c88515409d3433aaf519d9920f5256e629419e9a95580a35b069b8d25533dfcbc98ad36404a951808e01378c03266326d120046975fde07daef3266caacd821c1403499d7fdf17c033c8d8c3f28f162b5f09dfdaca06285f00c6cb986dfdf5151aa6639608b5b13e78d65a4368585b16138754fbd113835a686cd066c2b89bb0953c24d50e77bf0fc457c1e0fcf5d44da8db9a88f062be3b688d5cdcff1d1c00e81ec9d413882295b341fee8fa427dc109adeb5f284eec202f1bef115bf96b1782d3ccdeb682b69bf92d170c007d5df80e1ed962f677dc24a145a1e4e829e8dec0104e5f78365944:42f133e34e3eb7032a133ed781537ec62e44a5ce8381e5e0bf9e13a914a4b2c757811d6d3b1e86672424ea4230d10f7c610abb7069e61e319b4066a2bd7bc90071175d4e21721297d9176d817f4e785d9600d923f987fe0b26fd79d33a5ea5d1e818b71f0f92b8c73afddabdcc27f6d16e26aafa874cfd77a00e06c36b041487582bb933760f88b419127345776ea418f83522254fed33819bc5c95f8f8404cc144ebf1486c88515409d3433aaf519d9920f5256e629419e9a95580a35b069b8d25533dfcbc98ad36404a951808e01378c03266326d120046975fde07daef3266caacd821c1403499d7fdf17c033c8d8c3f28f162b5f09dfdaca06285f00c6cb986dfdf5151aa6639608b5b13e78d65a4368585b16138754fbd113835a686cd066c2b89bb0953c24d50e77bf0fc457c1e0fcf5d44da8db9a88f062be3b688d5cdcff1d1c00e81ec9d413882295b341fee8fa427dc109adeb5f284eec202f1bef115bf96b1782d3ccdeb682b69bf92d170c007d5df80e1ed962f677dc24a145a1e4e829e8dec0104e5f78365944: +c3f5e149968a24f4de9119531975f443015ccca305d7119ed4749e8bf6d94fc739aaccdb948a4038538a4588322f806bb129b5876c4bec51271afe4f49690045:39aaccdb948a4038538a4588322f806bb129b5876c4bec51271afe4f49690045:c46370e37f2e0cadcf93402f1f0cb048f52881ba750b7a43f56ab11ce348732fb57e7f9aaf8dfcbe455e14e983c248d026a27e7f148d5db5a53f94635702b895127771047a876d14107386c5e0ff8933345bbd7a936d990d33efa28c2ec4e4864ffd2ff576f7c88f954cfc1c459e883bb712dae3cdf6632066f1f4d13a509615b3360cadc5a307f23e52a51b40a6feebe0b18d0e9ee4e348f33cd81a8def222f6a59b12861d335bd9af85cc004be46f1d3a424f4870ae9dc587e5a4ade136b9370649348c33ac3bf1febeebffea37085ed59cac9d9e696470b234609e9a10a9d431ff91e69cb5135fd117ff58a36539744ebe70cea6973c00c7a4d57b62f4a7136d731b8e46ff18ec0ed69070031905075d8541d568cfce6eeb76242b7819a7b6a93552111bb88f165527cfa6966d39fcbe0a7dea008e39c7a3e577ab307cd1d0ea326833d52654e172955f3fcd4:5fa2b531677b00b85b0a313cbd479f55f4ab3ec5cfce5e454d2b74176ccc3399c899f9d6b51ed4c1e76185ac9fe730c4b4014044f7041185bc3c85722eb2ea02c46370e37f2e0cadcf93402f1f0cb048f52881ba750b7a43f56ab11ce348732fb57e7f9aaf8dfcbe455e14e983c248d026a27e7f148d5db5a53f94635702b895127771047a876d14107386c5e0ff8933345bbd7a936d990d33efa28c2ec4e4864ffd2ff576f7c88f954cfc1c459e883bb712dae3cdf6632066f1f4d13a509615b3360cadc5a307f23e52a51b40a6feebe0b18d0e9ee4e348f33cd81a8def222f6a59b12861d335bd9af85cc004be46f1d3a424f4870ae9dc587e5a4ade136b9370649348c33ac3bf1febeebffea37085ed59cac9d9e696470b234609e9a10a9d431ff91e69cb5135fd117ff58a36539744ebe70cea6973c00c7a4d57b62f4a7136d731b8e46ff18ec0ed69070031905075d8541d568cfce6eeb76242b7819a7b6a93552111bb88f165527cfa6966d39fcbe0a7dea008e39c7a3e577ab307cd1d0ea326833d52654e172955f3fcd4: +42305c9302f45ea6f87e26e2208fd94b3c4ad037b1b6c83cf6677aa1096a013c3b97b1f11ce45ba46ffbb25b76bfc5ad7b77f90cc69ed76115dea4029469d587:3b97b1f11ce45ba46ffbb25b76bfc5ad7b77f90cc69ed76115dea4029469d587:d110828d449198d675e74e8e39439fd15e75bf2cc1f430abfb245836885bafc420f754b89d2fbbf6dd3490792e7a4f766073cfe3b302d089831ace869e2730fde45c2121ec3ef217aa9c43fa7cc7e9ed0a01ad9f1d2fc3613638ca9fc193c98b37455bf5dbf8f38b64708dfdca6c21f0975f1017c5da5f6434bda9f033cec2a631ab50318e017b170b240bf01eb8b36c7e1cb59e7736ac34444208132a8f59e4f313d65d849c6a4fdf13e20ecaee3823e589a171b39b2489497b06e6ff58c2c9f1dc5d3aa3bd10e6443e22d42d07b783f79fd43a46e1cde314b663a95f7246dea131fcd46d1dc333c5454f86b2c4e2e424dea405cc2230d4dcd39a2eab2f92845cf6a7994192063f1202749ef52dcb96f2b79ed6a98118ca0b99ba2285490860eb4c61ab78b9ddc6acc7ad883fa5e96f9d029171223abf7573e36230e0a81f6c1311151473ee264f4b842e923dcb3b:18d05e5d01668e83f40fa3bbee28b388acf318d1b0b5ad668c672f345c8eda14c2f884cd2a9039459ce0810bc5b580fe70d3964a43edb49e73a6ff914bbf040cd110828d449198d675e74e8e39439fd15e75bf2cc1f430abfb245836885bafc420f754b89d2fbbf6dd3490792e7a4f766073cfe3b302d089831ace869e2730fde45c2121ec3ef217aa9c43fa7cc7e9ed0a01ad9f1d2fc3613638ca9fc193c98b37455bf5dbf8f38b64708dfdca6c21f0975f1017c5da5f6434bda9f033cec2a631ab50318e017b170b240bf01eb8b36c7e1cb59e7736ac34444208132a8f59e4f313d65d849c6a4fdf13e20ecaee3823e589a171b39b2489497b06e6ff58c2c9f1dc5d3aa3bd10e6443e22d42d07b783f79fd43a46e1cde314b663a95f7246dea131fcd46d1dc333c5454f86b2c4e2e424dea405cc2230d4dcd39a2eab2f92845cf6a7994192063f1202749ef52dcb96f2b79ed6a98118ca0b99ba2285490860eb4c61ab78b9ddc6acc7ad883fa5e96f9d029171223abf7573e36230e0a81f6c1311151473ee264f4b842e923dcb3b: +c57a43dcd7bab8516009546918d71ad459b7345efdca8d4f19929875c839d7222083b444236b9ab31d4e00c89d55c6260fee71ac1a47c4b5ba227404d382b82d:2083b444236b9ab31d4e00c89d55c6260fee71ac1a47c4b5ba227404d382b82d:a4f6d9c281cf81a28a0b9e77499aa24bde96cc1264374491c008294ee0af6f6e4bbb686396f59068d358e30fe9992db0c6f16680a1c71e27a4a907ac607d39bdc3258c7956482fb37996f4beb3e5051b8148019a1c256e2ee999ebc8ce64c54e07fedb4fbd8953ebd93b7d69ce5a0082edd6209d12d3619b4fd2eae916461f72a4ce727157251a19209bbff9fbdbd289436f3fcacc6b4e1318521a47839cba4b14f7d7a21e7b5d6b6a753d5804afcd2b1eb7779b92abab8afa8aa4fa51caec0b85dcd0fc2a0676036d3f56630a831ffeb502861dd89161c708a9c006c73c930ce5b94756426ff18aa112fb4eb9a68500b48d4eedbd4167b6ffd0a11d49443a173ce9d949436748fc0634f06bb08b8f3423f4463dba7b4d199b64df578117f0a2645f0b2a1e2ada27d286f76733f25b82ed1d48a5c3898d4ad621e50ed9060daad40a39532e4d1bf162ce36804d5d4e2d:1edef9bc036971f1fa88edf45393c802e6c1a1631c8a06871a09a320821dce40beca97e53a0361a955a4c6d60b8ca8e400c81340911ccb4f56284041cdbb1804a4f6d9c281cf81a28a0b9e77499aa24bde96cc1264374491c008294ee0af6f6e4bbb686396f59068d358e30fe9992db0c6f16680a1c71e27a4a907ac607d39bdc3258c7956482fb37996f4beb3e5051b8148019a1c256e2ee999ebc8ce64c54e07fedb4fbd8953ebd93b7d69ce5a0082edd6209d12d3619b4fd2eae916461f72a4ce727157251a19209bbff9fbdbd289436f3fcacc6b4e1318521a47839cba4b14f7d7a21e7b5d6b6a753d5804afcd2b1eb7779b92abab8afa8aa4fa51caec0b85dcd0fc2a0676036d3f56630a831ffeb502861dd89161c708a9c006c73c930ce5b94756426ff18aa112fb4eb9a68500b48d4eedbd4167b6ffd0a11d49443a173ce9d949436748fc0634f06bb08b8f3423f4463dba7b4d199b64df578117f0a2645f0b2a1e2ada27d286f76733f25b82ed1d48a5c3898d4ad621e50ed9060daad40a39532e4d1bf162ce36804d5d4e2d: +2dddb6b8fd04fa90ece1a709f8418f2e5d0c9c43afe7cfce19e6ad15a73476f78059de6a7c4776489ecc2e7d707ffce30285bf30a23f78d72db49cfd6ed0d492:8059de6a7c4776489ecc2e7d707ffce30285bf30a23f78d72db49cfd6ed0d492:474baa590a4cd72d5424e51d8257b3d44325bc4c5063a0033c86ebbe99ed7212184c19944d082a115379dd4cece973faa0bca6485bd25f3744a719e70aa0291e1b5a96e637c140616a98263357c76b6eb0083fe51414e386870d0fdc7dd9abe4ff6fb5bbf1e7b15dac3e08e2615f655c3104ceb32a4cc2c9e9c43cf282d346ac253ccc46b635ae040973b49735720ffb890469a567c5824e0c00d7ccd5509a718092a906461c4d6163eaf422418f5fc6e009fc3f529ac61a2f89bb8e0ed45d940c4c2331ff8d8e1d6d58d417d8fc2656a02e8701aee75aed918724eebe4a2cf4744c5c401e217023df68a6f6a0228bd05a679a697d8de7036b9ed269090d3c65486afb91e27954eb15b964665ede7ad008f12fb3a9d0e69c13b4254f43819e0818a4195f68b8a38ae81f3fcb1879c95ab4cd0ffc38e381089260cca967ace5a085b457ab5eb363852101377570f9ac9e38:c634ea7bf72e895a2e796e2834201415b8b45e05e045559284eb9052c0e84f62a5a9f0c9764f7576788c7228b19ef517c195497325a48a9344b147c12fd75509474baa590a4cd72d5424e51d8257b3d44325bc4c5063a0033c86ebbe99ed7212184c19944d082a115379dd4cece973faa0bca6485bd25f3744a719e70aa0291e1b5a96e637c140616a98263357c76b6eb0083fe51414e386870d0fdc7dd9abe4ff6fb5bbf1e7b15dac3e08e2615f655c3104ceb32a4cc2c9e9c43cf282d346ac253ccc46b635ae040973b49735720ffb890469a567c5824e0c00d7ccd5509a718092a906461c4d6163eaf422418f5fc6e009fc3f529ac61a2f89bb8e0ed45d940c4c2331ff8d8e1d6d58d417d8fc2656a02e8701aee75aed918724eebe4a2cf4744c5c401e217023df68a6f6a0228bd05a679a697d8de7036b9ed269090d3c65486afb91e27954eb15b964665ede7ad008f12fb3a9d0e69c13b4254f43819e0818a4195f68b8a38ae81f3fcb1879c95ab4cd0ffc38e381089260cca967ace5a085b457ab5eb363852101377570f9ac9e38: +5547f1004baedfce5cfc0850b05302374aad24f6163994ecd751df3af3c106207ce620787385ee1951ac49a77352ee0d6f8c5cd47df74e9e3216a6324fc7cf7f:7ce620787385ee1951ac49a77352ee0d6f8c5cd47df74e9e3216a6324fc7cf7f:a6c17eeb5b8066c2cd9a89667317a945a0c7c96996e77ae854c509c6cd0631e922ad04503af87a3c4628adafed7600d071c078a22e7f64bda08a362b38b26ca15006d38acf532d0dedea4177a2d33f06956d80e963848ec791b2762fa99449b4f1a1ed9b3f2580be3ac7d7f52fb14421d6222ba76f807750c6cbb0b16f0895fc73d9dfc587e1a9e5d1e58375fbab705b8f0c1fd7df8b3ad446f2f08459e7ed1af59556fbc966dc249c1cf604f3e677c8a09d4363608774bf3811bef0642748c55c516c7a580fa3499050acb30eed870d0d91174cb623e98c3ad121cf81f04e57d49b008424a98a31eeaaf5f38e000f903d48d215ed52f862d636a5a73607de85760167267efe30f8a26ebc5aa0c09f5b258d3361ca69d1d7ee07b59648179ab2170ec50c07f6616f216872529421a6334a4a1ed3d2671ef47bc9a92afb58314e832db8a9003408a0487503fe4f67770dd4b6:29df3ad589009c667baa5e72dabb4e53cb7876de4e7efe5cc21ead7fa878db57f97c1103ddb39a861eb88653c1d4ec3b4306e4584b47b8bc90423119e7e4af00a6c17eeb5b8066c2cd9a89667317a945a0c7c96996e77ae854c509c6cd0631e922ad04503af87a3c4628adafed7600d071c078a22e7f64bda08a362b38b26ca15006d38acf532d0dedea4177a2d33f06956d80e963848ec791b2762fa99449b4f1a1ed9b3f2580be3ac7d7f52fb14421d6222ba76f807750c6cbb0b16f0895fc73d9dfc587e1a9e5d1e58375fbab705b8f0c1fd7df8b3ad446f2f08459e7ed1af59556fbc966dc249c1cf604f3e677c8a09d4363608774bf3811bef0642748c55c516c7a580fa3499050acb30eed870d0d91174cb623e98c3ad121cf81f04e57d49b008424a98a31eeaaf5f38e000f903d48d215ed52f862d636a5a73607de85760167267efe30f8a26ebc5aa0c09f5b258d3361ca69d1d7ee07b59648179ab2170ec50c07f6616f216872529421a6334a4a1ed3d2671ef47bc9a92afb58314e832db8a9003408a0487503fe4f67770dd4b6: +3dd7203c237aefe9e38a201ff341490179905f9f100828da18fcbe58768b5760f067d7b2ff3a957e8373a7d42ef0832bcda84ebf287249a184a212a94c99ea5b:f067d7b2ff3a957e8373a7d42ef0832bcda84ebf287249a184a212a94c99ea5b:db28ed31ac04b0c2decee7a6b24fc9a082cc262ca7ccf2a247d6372ec3e9120ecedb4542ea593fea30335c5ab9dd318a3b4fd5834299cf3f53d9ef46137b273c390ec3c26a0b4470d0d94b77d82cae4b24587837b167bb7f8166710baeb3ee70af797316cb7d05fa57e468ae3f0bd449404d8528808b41fcca62f5e0a2aa5d8f3acab008cc5f6e5ab02777bdcde87f0a10ef06a4bb37fe02c94815cf76bfb8f5cdd865cc26dcb5cf492edfd547b535e2e6a6d8540956dcba62cfea19a9474406e934337e454270e01036ac45793b6b8aceda187a08d56a2ce4e98f42ea375b101a6b9fcb4231d171aa463eeb43586a4b82a387bcddaf71a80fd5c1f7292efc2bd8e70c11eaa817106061b6c461c4883d613cc06c7e2a03f73d90fc55cdc07265eefd36be72270383d6c676cae37c93691f1ae3d927b3a1cd963e4229757ae5231eea73a9f71515628305410ac2593b325cc631:4c036935a96abc0d050d907bedbe9946fb97439f039c742e051ccf09add7df44d17da98c2ca01bdc2424da1e4debf347f8fff48ac8030d2cc07f9575c044be04db28ed31ac04b0c2decee7a6b24fc9a082cc262ca7ccf2a247d6372ec3e9120ecedb4542ea593fea30335c5ab9dd318a3b4fd5834299cf3f53d9ef46137b273c390ec3c26a0b4470d0d94b77d82cae4b24587837b167bb7f8166710baeb3ee70af797316cb7d05fa57e468ae3f0bd449404d8528808b41fcca62f5e0a2aa5d8f3acab008cc5f6e5ab02777bdcde87f0a10ef06a4bb37fe02c94815cf76bfb8f5cdd865cc26dcb5cf492edfd547b535e2e6a6d8540956dcba62cfea19a9474406e934337e454270e01036ac45793b6b8aceda187a08d56a2ce4e98f42ea375b101a6b9fcb4231d171aa463eeb43586a4b82a387bcddaf71a80fd5c1f7292efc2bd8e70c11eaa817106061b6c461c4883d613cc06c7e2a03f73d90fc55cdc07265eefd36be72270383d6c676cae37c93691f1ae3d927b3a1cd963e4229757ae5231eea73a9f71515628305410ac2593b325cc631: +282775df9ebbd7c5a65f3a2b096e36ee64a8f8ea719da77758739e4e7476111da2b49646033a13937cad6b0e914e3cec54989c252ca5643d076555d8c55e56e0:a2b49646033a13937cad6b0e914e3cec54989c252ca5643d076555d8c55e56e0:14cc50c2973ea9d0187a73f71cb9f1ce07e739e049ec2b27e6613c10c26b73a2a966e01ac3be8b505aeaad1485c1c2a3c6c2b00f81b9e5f927b73bfd498601a7622e8544837aad02e72bf72196dc246902e58af253ad7e025e3666d3bfc46b5b02f0eb4a37c9554992abc8651de12fd813177379bb0ce172cd8aaf937f979642bc2ed7c7a430cb14c3cd3101b9f6b91ee3f542acdf017f8c2116297f4564768f4db95dad8a9bcdc8da4d8fb13ef6e2da0b1316d3c8c2f3ed836b35fe2fd33effb409e3bc1b0f85225d2a1de3bfc2d20563946475c4d7ca9fddbaf59ad8f8961d287ae7dd803e7af1fa612329b1bdc04e225600ae731bc01ae0925aed62ac50d46086f3646cf47b072f0d3b044b36f85cec729a8bb2b92883ca4dfb34a8ee8a0273b31af50982bb6131bfa11d55504b1f6f1a0a00438ca26d8ab4f48bcddc9d5a38851abede4151d5b70d720732a00abea2c8b979:15763973859402907d8dcb86adc24a2a168ba3abf2246173d6348afed51ef60b0c0edeff4e10bcef4c6e5778c8bc1f5e9ee0237373445b455155d23de127a20214cc50c2973ea9d0187a73f71cb9f1ce07e739e049ec2b27e6613c10c26b73a2a966e01ac3be8b505aeaad1485c1c2a3c6c2b00f81b9e5f927b73bfd498601a7622e8544837aad02e72bf72196dc246902e58af253ad7e025e3666d3bfc46b5b02f0eb4a37c9554992abc8651de12fd813177379bb0ce172cd8aaf937f979642bc2ed7c7a430cb14c3cd3101b9f6b91ee3f542acdf017f8c2116297f4564768f4db95dad8a9bcdc8da4d8fb13ef6e2da0b1316d3c8c2f3ed836b35fe2fd33effb409e3bc1b0f85225d2a1de3bfc2d20563946475c4d7ca9fddbaf59ad8f8961d287ae7dd803e7af1fa612329b1bdc04e225600ae731bc01ae0925aed62ac50d46086f3646cf47b072f0d3b044b36f85cec729a8bb2b92883ca4dfb34a8ee8a0273b31af50982bb6131bfa11d55504b1f6f1a0a00438ca26d8ab4f48bcddc9d5a38851abede4151d5b70d720732a00abea2c8b979: +4730a5cf9772d7d6665ba787bea4c95252e6ecd63ec62390547bf100c0a46375f9f094f7cc1d40f1926b5b22dce465784468b20ab349bc6d4fdf78d0042bbc5b:f9f094f7cc1d40f1926b5b22dce465784468b20ab349bc6d4fdf78d0042bbc5b:e7476d2e668420e1b0fadfbaa54286fa7fa890a87b8280e26078152295e1e6e55d1241435cc430a8693bb10cde4643f59cbfcc256f45f5090c909a14c7fc49d37bfc25af11e8f4c83f4c32d4aabf43b20fa382bb6622a1848f8ffc4dff3408bb4ec7c67a35b4cdaee5e279c0fc0a66093a9f36a60fdd65e6334a804e845c8530b6fda363b5640337d027243ccfb3c177f43e717896e46ead7f72ca06aa0ff1e77247121baf48be9a445f729ca1390fc46151cbd33fcbd7373f27a6ba55c92cbf6945b09b44b9a4e5800d403070ae66048997b2197f02181a097e563f9b9acc841139258a258bc610d3bd891637356b2edc8c184c35c65af91aaf7b1c16d74a5f5f862548139254ecf550631d5f8849afdb5b64cf366ff2633a93f3a18c39b5150245fb5f33c9e4e2d94af6963a70b88f9e7e519f8fa2a0f2e3749de883d0e6f052a949d0fc7153a8693f6d801d7352eb2f7a465c0e:552c7347bdfe131646ce0932d82a36d2c1b76d7c30ee890e0592e19f9d18b9a56f48d7a9b68c017da6b550c943af4a907baf317e419fbbc96f6cf4bfad42de00e7476d2e668420e1b0fadfbaa54286fa7fa890a87b8280e26078152295e1e6e55d1241435cc430a8693bb10cde4643f59cbfcc256f45f5090c909a14c7fc49d37bfc25af11e8f4c83f4c32d4aabf43b20fa382bb6622a1848f8ffc4dff3408bb4ec7c67a35b4cdaee5e279c0fc0a66093a9f36a60fdd65e6334a804e845c8530b6fda363b5640337d027243ccfb3c177f43e717896e46ead7f72ca06aa0ff1e77247121baf48be9a445f729ca1390fc46151cbd33fcbd7373f27a6ba55c92cbf6945b09b44b9a4e5800d403070ae66048997b2197f02181a097e563f9b9acc841139258a258bc610d3bd891637356b2edc8c184c35c65af91aaf7b1c16d74a5f5f862548139254ecf550631d5f8849afdb5b64cf366ff2633a93f3a18c39b5150245fb5f33c9e4e2d94af6963a70b88f9e7e519f8fa2a0f2e3749de883d0e6f052a949d0fc7153a8693f6d801d7352eb2f7a465c0e: +2770aadd1d123e9547832dfb2a837eba089179ef4f23abc4a53f2a714e423ee23c5fbb07530dd3a20ff35a500e3708926310fed8a899690232b42c15bd86e5dc:3c5fbb07530dd3a20ff35a500e3708926310fed8a899690232b42c15bd86e5dc:a5cc2055eba3cf6f0c6332c1f2ab5854870913b03ff7093bc94f335add44332231d9869f027d82efd5f1227144ab56e3222dc3ddccf062d9c1b0c1024d9b416dfa3ee8a7027923003465e0ffaefb75b9f29dc6bcf213adc5e318fd8ba93a7aa5bfb495de9d7c5e1a196cd3a2d7721f8ba785aa9052a1811c7fcc8f93932765059cab9c9b718945895ef26f3ac048d4cabf91a9e6aa83ac14d43156827837914eb763a23cba53f60f150f4b70203ec1833ff105849457a8da7327661fb23a554164e05fcf0146b10674964be6f6aa0acc94c41ad57180e5180d199bd9102f55d740e81789b15671bbd0670e6de5d97e1ae626d8a0ebc32c8fd9d24737274e47d2dd5941a272e72a598928ad109cde937bf248d57f5d2942983c51e2a89f8f054d5c48dfad8fcf1ffa97f7de6a3a43ca15fc6720efaec69f0836d84223f9776d111ec2bbc69b2dfd58be8ca12c072164b718cd7c246d64:f267715e9a84c7314f2d5869ef4ab8d2149a13f7e8e1c728c423906293b49ce6283454dd1c7b04741df2eabedc4d6ab1397dc95a679df04d2c17d66c79bb7601a5cc2055eba3cf6f0c6332c1f2ab5854870913b03ff7093bc94f335add44332231d9869f027d82efd5f1227144ab56e3222dc3ddccf062d9c1b0c1024d9b416dfa3ee8a7027923003465e0ffaefb75b9f29dc6bcf213adc5e318fd8ba93a7aa5bfb495de9d7c5e1a196cd3a2d7721f8ba785aa9052a1811c7fcc8f93932765059cab9c9b718945895ef26f3ac048d4cabf91a9e6aa83ac14d43156827837914eb763a23cba53f60f150f4b70203ec1833ff105849457a8da7327661fb23a554164e05fcf0146b10674964be6f6aa0acc94c41ad57180e5180d199bd9102f55d740e81789b15671bbd0670e6de5d97e1ae626d8a0ebc32c8fd9d24737274e47d2dd5941a272e72a598928ad109cde937bf248d57f5d2942983c51e2a89f8f054d5c48dfad8fcf1ffa97f7de6a3a43ca15fc6720efaec69f0836d84223f9776d111ec2bbc69b2dfd58be8ca12c072164b718cd7c246d64: +4fdab7c1600e70114b11f533242376af7614b4d5da046ac4bedea21d8a361598a25c9a94d6e4ecd95a4bd6805f762eb1c457a8d45d243238b1839cbba8f441cc:a25c9a94d6e4ecd95a4bd6805f762eb1c457a8d45d243238b1839cbba8f441cc:da405890d11a872c119dab5efcbff61e931f38eccca457edc626d3ea29ed4fe3154fafec1444da74343c06ad90ac9d17b511bcb73bb49d90bafb7c7ea800bd58411df1275c3cae71b700a5dab491a4261678587956aa4a219e1ac6dd3fb2cb8c46197218e726dc7ed234526a6b01c0d72cb93ab3f4f38a08e5940b3f61a72ad2789a0532000fac1d2d2e3ad632ac8b62bb3ff5b99d53597bf4d44b19674924df9b3db3d0253f74627ccab30031c85e291c58b5fa9167522a46746fc307036745d4f9817786e5d300e6c5d503125fea01dec3e3fedbf3861ca2627a0518fb2b24e5a7a014178719e9b345f7b249ce3a413280c8deb674f59a25be92a8ab6400c7c52b0728ae34e22b2ec200c1cbaba2ccd8af29249d17af60c36007a722fc80258a7bebab1cdaad7462a8b7588c2f7e27c6d07afcf60117fed11bd6859e75e3b4fcee3981881e95dd116827dd4b369af069d3c8f2676f8a:5075c090cfbeb6b01802af7f4da5aa4f434d5ee2f3530eebb75c85e08621f83edc08aa96693894a4277633ba81e19e9e55af5c495daa5e1a6f8cbb79c01c7207da405890d11a872c119dab5efcbff61e931f38eccca457edc626d3ea29ed4fe3154fafec1444da74343c06ad90ac9d17b511bcb73bb49d90bafb7c7ea800bd58411df1275c3cae71b700a5dab491a4261678587956aa4a219e1ac6dd3fb2cb8c46197218e726dc7ed234526a6b01c0d72cb93ab3f4f38a08e5940b3f61a72ad2789a0532000fac1d2d2e3ad632ac8b62bb3ff5b99d53597bf4d44b19674924df9b3db3d0253f74627ccab30031c85e291c58b5fa9167522a46746fc307036745d4f9817786e5d300e6c5d503125fea01dec3e3fedbf3861ca2627a0518fb2b24e5a7a014178719e9b345f7b249ce3a413280c8deb674f59a25be92a8ab6400c7c52b0728ae34e22b2ec200c1cbaba2ccd8af29249d17af60c36007a722fc80258a7bebab1cdaad7462a8b7588c2f7e27c6d07afcf60117fed11bd6859e75e3b4fcee3981881e95dd116827dd4b369af069d3c8f2676f8a: +264504604e70d72dc4474dbb34913e9c0f806dfe18c7879a41762a9e4390ec61eb2b518ce7dc71c91f3665581651fd03af84c46bf1fed2433222353bc7ec511d:eb2b518ce7dc71c91f3665581651fd03af84c46bf1fed2433222353bc7ec511d:901d70e67ed242f2ec1dda813d4c052cfb31fd00cfe5446bf3b93fdb950f952d94ef9c99d1c264a6b13c3554a264beb97ed20e6b5d66ad84db5d8f1de35c496f947a23270954051f8e4dbe0d3ef9ab3003dd47b859356cecb81c50affa68c15dadb5f864d5e1bb4d3bada6f3aba1c83c438d79a94bfb50b43879e9cef08a2bfb22fad943dbf7683779746e31c486f01fd644905048b112ee258042153f46d1c7772a0624bcd6941e9062cfda75dc8712533f4057335c298038cbca29ebdb560a295a88339692808eb3481fd9735ea414f620c143b2133f57bb64e44778a8ca70918202d157426102e1dfc0a8f7b1ae487b74f02792633154dfe74caa1b7088fda22fa8b9bc354c585f1567706e2955493870f54169e0d7691159df43897961d24a852ea970c514948f3b48f71ee586e72ec78db820f253e08db84f6f312c4333bd0b732fe75883507783e9a1fd4fbab8e5870f9bf7ad58aa:eea439a00f7e459b402b835150a779eed171ab971bd1b58dcc7f9386dadd583de8dc69e267121dde41f0f9493d450b16219cdf3c22f09482ce402fe17ca49e08901d70e67ed242f2ec1dda813d4c052cfb31fd00cfe5446bf3b93fdb950f952d94ef9c99d1c264a6b13c3554a264beb97ed20e6b5d66ad84db5d8f1de35c496f947a23270954051f8e4dbe0d3ef9ab3003dd47b859356cecb81c50affa68c15dadb5f864d5e1bb4d3bada6f3aba1c83c438d79a94bfb50b43879e9cef08a2bfb22fad943dbf7683779746e31c486f01fd644905048b112ee258042153f46d1c7772a0624bcd6941e9062cfda75dc8712533f4057335c298038cbca29ebdb560a295a88339692808eb3481fd9735ea414f620c143b2133f57bb64e44778a8ca70918202d157426102e1dfc0a8f7b1ae487b74f02792633154dfe74caa1b7088fda22fa8b9bc354c585f1567706e2955493870f54169e0d7691159df43897961d24a852ea970c514948f3b48f71ee586e72ec78db820f253e08db84f6f312c4333bd0b732fe75883507783e9a1fd4fbab8e5870f9bf7ad58aa: +2ca7447a3668b748b1fd3d52d2080d30e34d397bb2846caf8f659ac168788ca5ab331cd40a31d0173c0c8c1c17002532807bf89e3edb6d34c2dd8294632b9fbc:ab331cd40a31d0173c0c8c1c17002532807bf89e3edb6d34c2dd8294632b9fbc:a82bcd9424bffda0f2f5e9eae17835dbe468f61b785aab82934737a91c5f602cb7c617cdffe87cad726a4972e15a7b8ee147f062d2a5a4d89706b571fa8aa2b95981c78abeaaae86203fa2c0e07297406ea8c27111a86dbe1d5a7c3b7ae930904d9890f6d4abebd1412a73ad5feea64acf065d3e63b5cbe20cf20bbd2d8b94f9053ed5f66633482530124446605918de66455e8cf4b101a127233c4e27d5d55bf95bd3195d0340d43531fc75faf8dded5275bf89750de838fd10c31745be4ca41fa871cb0f9b016706a1a7e3c44bb90ac7a8ad51e272389292fd6c98ad7a069e76e3f5f3e0cc770b9e9b35a765d0d93712d7cdabd17e5d01dd8183af4ad9365db0a0fa41381fce60a081df1c5ab0f8c18f95a7a8b582dfff7f149ea579df0623b33b7508f0c663f01e3a2dcd9dfbee51cc615220fdaffdab51bdae42cb9f7fa9e3b7c69cc8ada5ccd642529ba514fdc54fcf2720b8f5d08b95:f93ada15ae9cd2b54f26f86f0c28392aed5eb6b6b44d01a4e33a54e7da37c38e8d53366f73fd85be642e4ec81236d163f0d025e76c8bbdd65d43df49f09c1f01a82bcd9424bffda0f2f5e9eae17835dbe468f61b785aab82934737a91c5f602cb7c617cdffe87cad726a4972e15a7b8ee147f062d2a5a4d89706b571fa8aa2b95981c78abeaaae86203fa2c0e07297406ea8c27111a86dbe1d5a7c3b7ae930904d9890f6d4abebd1412a73ad5feea64acf065d3e63b5cbe20cf20bbd2d8b94f9053ed5f66633482530124446605918de66455e8cf4b101a127233c4e27d5d55bf95bd3195d0340d43531fc75faf8dded5275bf89750de838fd10c31745be4ca41fa871cb0f9b016706a1a7e3c44bb90ac7a8ad51e272389292fd6c98ad7a069e76e3f5f3e0cc770b9e9b35a765d0d93712d7cdabd17e5d01dd8183af4ad9365db0a0fa41381fce60a081df1c5ab0f8c18f95a7a8b582dfff7f149ea579df0623b33b7508f0c663f01e3a2dcd9dfbee51cc615220fdaffdab51bdae42cb9f7fa9e3b7c69cc8ada5ccd642529ba514fdc54fcf2720b8f5d08b95: +494ea9bcce26885b7d17d1fc114448f239f0ce46e5f247b4c999fa86296924726901e5efae57536ba5fdd96b59657359065f25d391a1aa8cdc0d38bb5d53c139:6901e5efae57536ba5fdd96b59657359065f25d391a1aa8cdc0d38bb5d53c139:3badbfa5f5a8aa2cce0a60e686cdce654d24452f98fd54872e7395b39464380a0e185557ea134d095730864f4254d3dd946970c10c804fcc0899dfa024205be0f80b1c75449523324fe6a0751e47b4ff4822b8c33e9eaf1d1d96e0de3d4acd89696b7fcc03d49f92f82b9725700b350db1a87615369545561b8599f5ea920a310a8bafc0e8d7468cbf6f3820e943594afdd5166e4e3309dddd7694ef67e694f34fc62724ff96ac3364176f34e8a02b4cf569db5b8f77d58512aedabf0bcd1c2df12db3a9473f948c5c3243309aae46c49efd088b60f31a8a72ad7e5a35acc5d89fa66807eb5d3ba9cdf08d4753cb85089ee36f5c96b432b6928352afad58012225d6157f9e3611426df921b6d1d8374628a63031e9ffb90e42ffbba021f174f68503155430152c9155dc98ffa26c4fab065e1f8e4622c2f28a8cb043110b617441140f8e20adc16f799d1d5096b1f50532be5042d21b81ea46c7:548a093a680361b7dc56f14503b55eeec3b3f4fd4ca99d6aedce0830f7f4ae2f7328539b34c48fc9760922333dae9c7c017e7db73b8faa6c06be05e347992b063badbfa5f5a8aa2cce0a60e686cdce654d24452f98fd54872e7395b39464380a0e185557ea134d095730864f4254d3dd946970c10c804fcc0899dfa024205be0f80b1c75449523324fe6a0751e47b4ff4822b8c33e9eaf1d1d96e0de3d4acd89696b7fcc03d49f92f82b9725700b350db1a87615369545561b8599f5ea920a310a8bafc0e8d7468cbf6f3820e943594afdd5166e4e3309dddd7694ef67e694f34fc62724ff96ac3364176f34e8a02b4cf569db5b8f77d58512aedabf0bcd1c2df12db3a9473f948c5c3243309aae46c49efd088b60f31a8a72ad7e5a35acc5d89fa66807eb5d3ba9cdf08d4753cb85089ee36f5c96b432b6928352afad58012225d6157f9e3611426df921b6d1d8374628a63031e9ffb90e42ffbba021f174f68503155430152c9155dc98ffa26c4fab065e1f8e4622c2f28a8cb043110b617441140f8e20adc16f799d1d5096b1f50532be5042d21b81ea46c7: +00d735ebaee75dd579a40dfd82508274d01a1572df99b811d5b01190d82192e4ba02517c0fdd3e2614b3f7bf99ed9b492b80edf0495d230f881730ea45bc17c4:ba02517c0fdd3e2614b3f7bf99ed9b492b80edf0495d230f881730ea45bc17c4:59c0b69af95d074c88fdc8f063bfdc31b5f4a9bc9cecdffa8128e01e7c1937dde5eb0570b51b7b5d0a67a3555b4cdce2bca7a31a4fe8e1d03ab32b4035e6dadbf1532059ee01d3d9a7633a0e706a1154cab22a07cd74c06a3cb601244cf3cf35a35c3100ba47f31372a2da65dcff0d7a80a1055d8aa99212e899aad7f02e949e6fee4d3c9cefa85069eaff1f6ad06fc300c871ab82b2bedb934d20875c2a263242cdb7f9be192a8710b24c7ea98d43daec8baa5553c678a38f0e0adf7d3ff2dcc799a1dbad6eab1c3d9458a9db922f02e75cfab9d65c7336dae71895d5bb15cac203f2b38b9996c410f8655ad22d3c091c20b7f926d45e780128f19747462abc5c58932fbb9e0bc62d53868802f1b083f183b8a1f9434986d5cf97c04e2f3e145730cba98779c7fed0cab1c05d5e4653c6c3f6736260bc78ee4372862ffe9e90371d762c7432781f35ced884a4baca05653ef25f25a6f3d5628308:dcdc54611937d2bd06cacd9818b3be15ce7425427a75f50d197a337a3b8ba6714ef48866f243bd5ac7415e914517a2c1c5a953f432b99db0e620d64f74eb850559c0b69af95d074c88fdc8f063bfdc31b5f4a9bc9cecdffa8128e01e7c1937dde5eb0570b51b7b5d0a67a3555b4cdce2bca7a31a4fe8e1d03ab32b4035e6dadbf1532059ee01d3d9a7633a0e706a1154cab22a07cd74c06a3cb601244cf3cf35a35c3100ba47f31372a2da65dcff0d7a80a1055d8aa99212e899aad7f02e949e6fee4d3c9cefa85069eaff1f6ad06fc300c871ab82b2bedb934d20875c2a263242cdb7f9be192a8710b24c7ea98d43daec8baa5553c678a38f0e0adf7d3ff2dcc799a1dbad6eab1c3d9458a9db922f02e75cfab9d65c7336dae71895d5bb15cac203f2b38b9996c410f8655ad22d3c091c20b7f926d45e780128f19747462abc5c58932fbb9e0bc62d53868802f1b083f183b8a1f9434986d5cf97c04e2f3e145730cba98779c7fed0cab1c05d5e4653c6c3f6736260bc78ee4372862ffe9e90371d762c7432781f35ced884a4baca05653ef25f25a6f3d5628308: +8c34b905440b61911d1d8137c53d46a1a76d4609af973e18eb4c5709295627bbb69a8b2fdf5c20e734c2ffb294bc8ae1011d664f11afe7fbc471925cf72fa99d:b69a8b2fdf5c20e734c2ffb294bc8ae1011d664f11afe7fbc471925cf72fa99d:30b57a389b48a0beb1a48432bff6b314bded79c4a1763a5acb57cea1bfb4c6d016cf090f5bd05bbd114e33ae7c17782dfa264f46c45f8c599c603016fe9ff05b6b5a99e92fe713a4cd5c41b292ed2bb2e9cf33a440542e821ec82cbf665c3f02e3dc337d7fdb58e31b27cb2954541468814698510df18c85c81fad12db11ec6b966f4930da5646b991db97445097da30dab61cda53a41083cb96add19de6c5eec323bca9d3530e38c00b35af7360077601be6ac97f3030f930a27b90fe8b6911bae389065adc15e1882300e2a003274d23182d5efd5ba4b9130c07bd5c65fecb8b5cb7eb38836b318befdfd77de4d6ca0181f77ae5740891683225f549dd8426145c97c5818c319f7ab2d868e1a41ceab64c085116069897bf2ca3667652406155ed0646431b6de1ccc03b4279ae4d326679265dce82048e7298e1f87fcec0768ac0f5d8ff84f7210be54d411af8edea7217f4e59413121e148c60da:3e0b72073dc9375eedcca6c4fc1cd315938a050c92716bd2284f4629a962beec0b7d7cf16ab923d58f5b90d3901a8e5c75c8f17dab9998e007d8c49511973d0e30b57a389b48a0beb1a48432bff6b314bded79c4a1763a5acb57cea1bfb4c6d016cf090f5bd05bbd114e33ae7c17782dfa264f46c45f8c599c603016fe9ff05b6b5a99e92fe713a4cd5c41b292ed2bb2e9cf33a440542e821ec82cbf665c3f02e3dc337d7fdb58e31b27cb2954541468814698510df18c85c81fad12db11ec6b966f4930da5646b991db97445097da30dab61cda53a41083cb96add19de6c5eec323bca9d3530e38c00b35af7360077601be6ac97f3030f930a27b90fe8b6911bae389065adc15e1882300e2a003274d23182d5efd5ba4b9130c07bd5c65fecb8b5cb7eb38836b318befdfd77de4d6ca0181f77ae5740891683225f549dd8426145c97c5818c319f7ab2d868e1a41ceab64c085116069897bf2ca3667652406155ed0646431b6de1ccc03b4279ae4d326679265dce82048e7298e1f87fcec0768ac0f5d8ff84f7210be54d411af8edea7217f4e59413121e148c60da: +77a83e18c9f000eeff7deeac959ecba2206c0aa39d2f0e2aed5729482a7a022962b1b316135596bfbca6037ed847c61fb7f09fa36ce90abb7789b86f768b59dd:62b1b316135596bfbca6037ed847c61fb7f09fa36ce90abb7789b86f768b59dd:f3d5fa2acaefd858f1df26e03059cdcbc2468ad74afc993d0db9c4cde4113f8d55c7da71d38ba06520531c61fddb5f33d5f0353be2376e580711be45c0a30b1fa01b55e228c6fa35e3f95b67909fc7df3fd464d93d661a926f9d11f7550c17fbcc3496526e8f10e0c8916677b2be5b319b688f21e81aaa9482e5c93e64ce8c437b9c1e14fefed70a3fee568811dc31cadab3d5b220254465336dc4d97a3bd096b5e065e0cfbe82849e2c1905aca486533f0da7a61f1e9a55b8e2a83262deeb59f2b13d3a8aef5700845b83b25ae2183c0ddac0ce42f8d25674cb0d0d220a6de7c1858bb07d59a3372344d944602aa451d2b937db0fe6feca0beba81721fc361ea7509e2b6d397e1c191b56f54ab436d0d27ab4c061bd661ad1a4452387e8735754d07fa7ef4d4548b172582425b299046e6301b5ba6b914418f149cf722e10bde2e0d41700f12c8429fc897b7819da92292240cd45565458c9a7b29c12:1eaad8420ac12c99ac1ff4476678e3cbbe94da6a797f174664d5ee0f641433fb1e7cb2f5613e10805df8654cd8e0d45d96230932bc7f20b04eae836435134309f3d5fa2acaefd858f1df26e03059cdcbc2468ad74afc993d0db9c4cde4113f8d55c7da71d38ba06520531c61fddb5f33d5f0353be2376e580711be45c0a30b1fa01b55e228c6fa35e3f95b67909fc7df3fd464d93d661a926f9d11f7550c17fbcc3496526e8f10e0c8916677b2be5b319b688f21e81aaa9482e5c93e64ce8c437b9c1e14fefed70a3fee568811dc31cadab3d5b220254465336dc4d97a3bd096b5e065e0cfbe82849e2c1905aca486533f0da7a61f1e9a55b8e2a83262deeb59f2b13d3a8aef5700845b83b25ae2183c0ddac0ce42f8d25674cb0d0d220a6de7c1858bb07d59a3372344d944602aa451d2b937db0fe6feca0beba81721fc361ea7509e2b6d397e1c191b56f54ab436d0d27ab4c061bd661ad1a4452387e8735754d07fa7ef4d4548b172582425b299046e6301b5ba6b914418f149cf722e10bde2e0d41700f12c8429fc897b7819da92292240cd45565458c9a7b29c12: +73b03373ef1fd849005ecd6270dd9906f19f4439e40376cdbc520902bc976812663719e08ba3ba1666f6069a3f54991866b18cc6be41991b02eb3026ff9e155f:663719e08ba3ba1666f6069a3f54991866b18cc6be41991b02eb3026ff9e155f:d5c2deaba795c30aba321bc7de6996f0d90e4d05c747fb4dae8f3451895def6e16e72f38eace756f36635f8fb0b72a3a0c1f54663817a94d4fd346f835ab0e657f001a6f2cecb86d0825bd02639254f7f7f38ca99dbb86c64a633f73baf933aae3563281f4005e2d0e7cec9fbde8e588a957e211068be65b3d3d35bf4e8d5bb3478333df9ced9b2abaf48697994a145e9321499fc5ee560f4fbb6849e1ae8eb3d1de0083a21a03f6a6b28176f0130d3895e50e75e3d7d0947a7bc2c5b9ff69895d27791442ba8d0f2180712b567f712ea912f3b0d92c19342e0106ff1d87b46ad33af300b90855ba9769d366e79425d98e4de19905a04577707cbe625b84691781cd26bf62260b4a8bd605f77af6f970e1b3a112e8918344bd0d8d2e41dfd2ce9895b0246e50887aa3a577ff73be4b6ae60feb0ca36f6a5f8171ed209e5c566529c0940d9b4bd744ccee56e54a9a0c6e4da520dd315c2872b02db563703e:a40abe98fc69da8a1ff9ff5c2cca93632e975980ee8b82c3c376022d6524ab736d01b072f2b681b5f1cd3ea067012ed6d074e949c42327a366caa9e4750a3c08d5c2deaba795c30aba321bc7de6996f0d90e4d05c747fb4dae8f3451895def6e16e72f38eace756f36635f8fb0b72a3a0c1f54663817a94d4fd346f835ab0e657f001a6f2cecb86d0825bd02639254f7f7f38ca99dbb86c64a633f73baf933aae3563281f4005e2d0e7cec9fbde8e588a957e211068be65b3d3d35bf4e8d5bb3478333df9ced9b2abaf48697994a145e9321499fc5ee560f4fbb6849e1ae8eb3d1de0083a21a03f6a6b28176f0130d3895e50e75e3d7d0947a7bc2c5b9ff69895d27791442ba8d0f2180712b567f712ea912f3b0d92c19342e0106ff1d87b46ad33af300b90855ba9769d366e79425d98e4de19905a04577707cbe625b84691781cd26bf62260b4a8bd605f77af6f970e1b3a112e8918344bd0d8d2e41dfd2ce9895b0246e50887aa3a577ff73be4b6ae60feb0ca36f6a5f8171ed209e5c566529c0940d9b4bd744ccee56e54a9a0c6e4da520dd315c2872b02db563703e: +eab179e41ed5c889ffe6aabdc054faf1307c395e46e313e17a14fe01023ffa3086f34746d3f7a01ddbe322f1aca56d22856d38733a3a6900bb08e776450ec803:86f34746d3f7a01ddbe322f1aca56d22856d38733a3a6900bb08e776450ec803:971095cebe5031530224387c5c31966e389b8566390054cf45264b44e18964b7be52c33c4ffb259af16283438fa15dd66bc7791b7533ef10cb0beab524a6437626f4cc74512851adcc2fb129055a482c61107383fb7c5241831d5551634eef0dc0b8f9053a00971aa8fa1ae0898e4b481b6707e97c0f942040b339d92fc17bbade74675af243d8b2dafb15b1db55d12415b85f3037291930ab61600ba3431f8eb425be4491614728af101e81c091f348bc5ffd1bde6ae6cad5c15b3aa7358078cc4effb54a86e7f0e0c55e4cfe0a54605ed443fdf2aaba016585da617e77341d52889d75dd540d39fe8b7993ed705cfddea0cb0d5a731d6bfcdb816afaff47e963eedebdf241af5593353d6d401a34f029a8cdeb1904cc2caa4f9635cc2ba6b7b1a29da625ffc383be2f5a8f1fa4f39b2d4b4f4c2d8838ce258a04d4a120493fdf07f68c0ffd1c16b768a35c55fea2cac696b5c20efc10865cde8a64627dcd:143cb28027c2f82e375e5f340e7fe6e60ce7bd51000b49c74168af85e26ed2ed630ed2672090164cc54b052da694ebdd21a21b3053f4dcfd7895ea5f6c8aa80d971095cebe5031530224387c5c31966e389b8566390054cf45264b44e18964b7be52c33c4ffb259af16283438fa15dd66bc7791b7533ef10cb0beab524a6437626f4cc74512851adcc2fb129055a482c61107383fb7c5241831d5551634eef0dc0b8f9053a00971aa8fa1ae0898e4b481b6707e97c0f942040b339d92fc17bbade74675af243d8b2dafb15b1db55d12415b85f3037291930ab61600ba3431f8eb425be4491614728af101e81c091f348bc5ffd1bde6ae6cad5c15b3aa7358078cc4effb54a86e7f0e0c55e4cfe0a54605ed443fdf2aaba016585da617e77341d52889d75dd540d39fe8b7993ed705cfddea0cb0d5a731d6bfcdb816afaff47e963eedebdf241af5593353d6d401a34f029a8cdeb1904cc2caa4f9635cc2ba6b7b1a29da625ffc383be2f5a8f1fa4f39b2d4b4f4c2d8838ce258a04d4a120493fdf07f68c0ffd1c16b768a35c55fea2cac696b5c20efc10865cde8a64627dcd: +fbf146ebd51075570ec51ac410ae9f391db75b610ada6362b4dbd949656cfb66be7c2f5b21d746c8ea3245ce6f268e9da74e00fa85c9c475260c68fa1af6361f:be7c2f5b21d746c8ea3245ce6f268e9da74e00fa85c9c475260c68fa1af6361f:cd7ad4f17fcff73acc402dc102d09079b29aaf2a0f4b27cf6beeb1e2b23d19ab47deb3ae1becd68861ea279c46691738f4fff47c43047c4f8b56b6bbcc3fde0723d44120dcd307a6310dc4f366b8f3cd52db19b8266a487f7872391c45fe0d3248a7abf2c20022d3769547f683067dcc363cd22fd7cda3cadc15804056f0e2aa2b795008c598be7a961805e6df291ba3041c47ff5640275f46e6ae82092d21abcbcfba11e730216008822de3ce462400596da79f7ae5d1df8389112ad98868fa94fb0546bfe6a67aa8d28c4d32072d2eadd6256255f18c2382e662dfa922a680e06a43622c4871d27d1807f7b2703070c83db8dd929c06038b2183cb8e2b9ec4c778d7ecf9e9ffac77fa7737b055feac2e7982aeeec0b72f1bbca2424e1a844bbac79cb2e7400f81dc449d0560b521a7c16bb4167e6696586058a9b8ed2e5116690b77f2a17e5c0b16a83dcbd2e24552293e258b32ba7f844944379342698627:6768006fe0f201b217dd10eb05d4b82adcfeb2ecfc8373c3308f4150394811eb60491881a2e53d1289d96478e18a64c34b2a19832cdccfd96a2e4a0c469fdc0bcd7ad4f17fcff73acc402dc102d09079b29aaf2a0f4b27cf6beeb1e2b23d19ab47deb3ae1becd68861ea279c46691738f4fff47c43047c4f8b56b6bbcc3fde0723d44120dcd307a6310dc4f366b8f3cd52db19b8266a487f7872391c45fe0d3248a7abf2c20022d3769547f683067dcc363cd22fd7cda3cadc15804056f0e2aa2b795008c598be7a961805e6df291ba3041c47ff5640275f46e6ae82092d21abcbcfba11e730216008822de3ce462400596da79f7ae5d1df8389112ad98868fa94fb0546bfe6a67aa8d28c4d32072d2eadd6256255f18c2382e662dfa922a680e06a43622c4871d27d1807f7b2703070c83db8dd929c06038b2183cb8e2b9ec4c778d7ecf9e9ffac77fa7737b055feac2e7982aeeec0b72f1bbca2424e1a844bbac79cb2e7400f81dc449d0560b521a7c16bb4167e6696586058a9b8ed2e5116690b77f2a17e5c0b16a83dcbd2e24552293e258b32ba7f844944379342698627: +dff0eb6b426dea2fd33c1d3fc24df9b31b486facb7edb8502954a3e8da99d9fdc245085ece69fb9aa560d0c27fdb634f7a840d41d8463660fbe82483b0f3cc3a:c245085ece69fb9aa560d0c27fdb634f7a840d41d8463660fbe82483b0f3cc3a:e7c9e313d86160f4c74aa0ae07369ee22b27f81b3f69097affae28dae48483fb52a5c062306b59610f5cdbff6332b1960cd6f2b8f7b41578c20f0bc9637a0fdfc739d61f699a573f1c1a0b49294506cf4487965e5bb07bbf81803cb3d5cb3829c66c4bee7fc800ede216150934d277dea50edb097b992f11bb669fdf140bf6ae9fec46c3ea32f888fde9d154ea84f01c51265a7d3fef6eefc1ccdbffd1e2c897f05546a3b1ca11d9517cd667c660ec3960f7a8e5e80202a78d3a388b92f5c1dee14ae6acf8e17c841c9557c35a2eeced6e6af6372148e483ccd06c8fe344924e1019fb91cbf7941b9a176a073415867210670410c5dbd0ac4a50e6c0a509ddfdc555f60d696d41c77db8e6c84d5181f872755e64a721b061fcd68c463db4d32c9e01ea501267de22879d7fc12c8ca0379edb45abaa6e64dda2af6d40ccf24fbebad7b5a8d3e52007945ecd3ddc1e3efeb522581ac80e98c863ba0c590a3ed95cd1:6b48b10f545ddb7a89cd5829f4e5b20146cf6bc96e550d06f65de8bdae7ccdded26cd630f86c9266bccf88e924033e04f83a54f8290d7f734cf8673cca8f9703e7c9e313d86160f4c74aa0ae07369ee22b27f81b3f69097affae28dae48483fb52a5c062306b59610f5cdbff6332b1960cd6f2b8f7b41578c20f0bc9637a0fdfc739d61f699a573f1c1a0b49294506cf4487965e5bb07bbf81803cb3d5cb3829c66c4bee7fc800ede216150934d277dea50edb097b992f11bb669fdf140bf6ae9fec46c3ea32f888fde9d154ea84f01c51265a7d3fef6eefc1ccdbffd1e2c897f05546a3b1ca11d9517cd667c660ec3960f7a8e5e80202a78d3a388b92f5c1dee14ae6acf8e17c841c9557c35a2eeced6e6af6372148e483ccd06c8fe344924e1019fb91cbf7941b9a176a073415867210670410c5dbd0ac4a50e6c0a509ddfdc555f60d696d41c77db8e6c84d5181f872755e64a721b061fcd68c463db4d32c9e01ea501267de22879d7fc12c8ca0379edb45abaa6e64dda2af6d40ccf24fbebad7b5a8d3e52007945ecd3ddc1e3efeb522581ac80e98c863ba0c590a3ed95cd1: +9f32958c7679b90fd5036056a75ec2eb2f56ec1effc7c012461dc89a3a1674201d7269dcb6d1f584e662d4ce251de0aba290ef78b97d448afb1e5333f1976d26:1d7269dcb6d1f584e662d4ce251de0aba290ef78b97d448afb1e5333f1976d26:a56ba86c71360504087e745c41627092ad6b49a71e9daa5640e1044bf04d4f071ad728779e95d1e2460584e6f0773545da82d4814c9189a120f12f3e3819813e5b240d0f26436f70ee353b4d20cea54a1460b5b8f1008d6f95f3aa2d8f1e908fced50d624e3a096938b9353854b96da463a2798a5a312ec790842c10c446e3350c764bf5c972593b9987bf23256daa8894d47f22e85b97607e66fc08a12c789c4746080368d321bb9015a1155b65523ad8e99bb989b44eac756b0734acd7c6357c70b59743246d1652d91b0f9896965141345b9945cf34980452f3502974edb76b9c785fb0f4395266b055f3b5db8aab68e9d7102a1cd9ee3d142504f0e88b282e603a738e051d98de05d1fcc65b5f7e99c4111cc0aec489abd0ecad311bfc13e7d1653b9c31e81c998037f959d5cd980835aa0e0b09bcbed634391151da02bc01a36c9a5800afb984163a7bb815edbc0226eda0595c724ca9b3f8a71178f0d20a5a:9881a5763bdb259a3fefbba3d957162d6c70b804fa94ab613406a6ec42505b8789465ca1a9a33e1895988842270c55e5bdd5483f6b17b31781b593507a6c1808a56ba86c71360504087e745c41627092ad6b49a71e9daa5640e1044bf04d4f071ad728779e95d1e2460584e6f0773545da82d4814c9189a120f12f3e3819813e5b240d0f26436f70ee353b4d20cea54a1460b5b8f1008d6f95f3aa2d8f1e908fced50d624e3a096938b9353854b96da463a2798a5a312ec790842c10c446e3350c764bf5c972593b9987bf23256daa8894d47f22e85b97607e66fc08a12c789c4746080368d321bb9015a1155b65523ad8e99bb989b44eac756b0734acd7c6357c70b59743246d1652d91b0f9896965141345b9945cf34980452f3502974edb76b9c785fb0f4395266b055f3b5db8aab68e9d7102a1cd9ee3d142504f0e88b282e603a738e051d98de05d1fcc65b5f7e99c4111cc0aec489abd0ecad311bfc13e7d1653b9c31e81c998037f959d5cd980835aa0e0b09bcbed634391151da02bc01a36c9a5800afb984163a7bb815edbc0226eda0595c724ca9b3f8a71178f0d20a5a: +f86d6f766f88b00717b7d6327eb26cf3ceeba5385184426f9cfd8295e2421ff2cb1d250504754183704dbe21c323d66f9f9011758f6d8dab6f597b199662145b:cb1d250504754183704dbe21c323d66f9f9011758f6d8dab6f597b199662145b:da8423a6b7a18f20aa1f90ed2331b17b24067c40175bc25d8109e21d87ac00528eb3b2f66a2b52dc7ef2f8cecb75c76099cfa23db8da897043ba1cce31e2dfea46075f5e073203eaeb3d62c84c107b6dab33a14eaf149aa61850c15f5a58d88a15aba9196f9e495e8dbecbcf7e8444f5dd72a08a099d7f6209990b562974ea829ef11d29a920e3a799d0d92cb50d50f817631ab09de97c31e9a05f4d78d649fcd93a83752078ab3bb0e16c564d4fb07ca923c0374ba5bf1eea7e73668e135031feafcbb47cbc2ae30ec16a39b9c337e0a62eecdd80c0b7a04924ac3972da4fa9299c14b5a53d37b08bf02268b3bac9ea9355090eeb04ad87bee0593ba4e4443dda38a97afbf2db9952df63f178f3b4c52bcc132be8d9e26881213abdeb7e1c44c4061548909f0520f0dd7520fc408ea28c2cebc0f53063a2d30570e05350e52b390dd9b67662984847be9ad9b4cd50b069ffd29dd9c62ef14701f8d012a4a70c8431cc:ec61c0b292203a8f1d87235ede92b74723c8d23408423773ae50b1e9bc4464e03e446da9dce4c39f6dd159bea26c009ed00120bc36d4a247dc0d24bcefcc110cda8423a6b7a18f20aa1f90ed2331b17b24067c40175bc25d8109e21d87ac00528eb3b2f66a2b52dc7ef2f8cecb75c76099cfa23db8da897043ba1cce31e2dfea46075f5e073203eaeb3d62c84c107b6dab33a14eaf149aa61850c15f5a58d88a15aba9196f9e495e8dbecbcf7e8444f5dd72a08a099d7f6209990b562974ea829ef11d29a920e3a799d0d92cb50d50f817631ab09de97c31e9a05f4d78d649fcd93a83752078ab3bb0e16c564d4fb07ca923c0374ba5bf1eea7e73668e135031feafcbb47cbc2ae30ec16a39b9c337e0a62eecdd80c0b7a04924ac3972da4fa9299c14b5a53d37b08bf02268b3bac9ea9355090eeb04ad87bee0593ba4e4443dda38a97afbf2db9952df63f178f3b4c52bcc132be8d9e26881213abdeb7e1c44c4061548909f0520f0dd7520fc408ea28c2cebc0f53063a2d30570e05350e52b390dd9b67662984847be9ad9b4cd50b069ffd29dd9c62ef14701f8d012a4a70c8431cc: +a5b34cefab9479df8389d7e6f6c146aa8affb0bec837f78af64624a145cc344e7b0f4f24d9972bc6fe83826c52716ad1e0d7d19f123858cb3e99fa636ac9631a:7b0f4f24d9972bc6fe83826c52716ad1e0d7d19f123858cb3e99fa636ac9631a:e21e98af6c2bac70557eb0e864da2c2b4d6c0a39a059d3477251f6178a39676f4749e7fbea623f148a43a8b0fe0610506fa658abd2f5fa39198f2636b724db22d1aebc2ab07b2b6dbffdee8cece81e1af1493ec1964e16bf86ab258ca0feb77e3c8717e44038abe152c14be15660bf93b2d48d92c4ed7074d2494210621bcf204fba88c654d5ffe01e1a53d08f70bb237089dc807216ff6a85dbec3102237d42590778acf6c1dc566d5a2bb9a63bc21c329c272e5965baeeb0fe891de3cc8cbfa8e541a8881df68942e7ff8dc656bd08575f6aaf924a176d663b1a1f43574d11768c701b269561e55438dbebfd443d2115cb933d1cde4a915b54c325c27f499ef02bd012ff1f9a36390922887600fe712bcdc23eb5974a305372ad52951f83f0e58cc49e289841621917f1fcb0235147240dae4cf3b99b6ac6d8de94efe7c4436714508bcd0114c56068ff1b7c16d51bd906437874d6549ab5d8087896872ec8a09d7412:2fbd899d72b6d39e4f45b8b62cbbd5f3c0acb1ad8540913fa585877e91ccfef7bee50a4b0f9fedf5cc1e0d1953ad399c8389a93391e1b7c929af6d6f3b796c08e21e98af6c2bac70557eb0e864da2c2b4d6c0a39a059d3477251f6178a39676f4749e7fbea623f148a43a8b0fe0610506fa658abd2f5fa39198f2636b724db22d1aebc2ab07b2b6dbffdee8cece81e1af1493ec1964e16bf86ab258ca0feb77e3c8717e44038abe152c14be15660bf93b2d48d92c4ed7074d2494210621bcf204fba88c654d5ffe01e1a53d08f70bb237089dc807216ff6a85dbec3102237d42590778acf6c1dc566d5a2bb9a63bc21c329c272e5965baeeb0fe891de3cc8cbfa8e541a8881df68942e7ff8dc656bd08575f6aaf924a176d663b1a1f43574d11768c701b269561e55438dbebfd443d2115cb933d1cde4a915b54c325c27f499ef02bd012ff1f9a36390922887600fe712bcdc23eb5974a305372ad52951f83f0e58cc49e289841621917f1fcb0235147240dae4cf3b99b6ac6d8de94efe7c4436714508bcd0114c56068ff1b7c16d51bd906437874d6549ab5d8087896872ec8a09d7412: +ad75c9ce299c4d59393367d77a4c9f8df8dcec765c6dbd25b527fb7669913604b9910548fe6312a119c9993eebcfb9dc90030ffb0e4de2b7ccd23cbeb4fef71b:b9910548fe6312a119c9993eebcfb9dc90030ffb0e4de2b7ccd23cbeb4fef71b:62fc5ab67deb1fee9ab6cca3b88a1df1e589f0fd4a88f4aa7738948761fe84372c5b18e4655220c1d84d52acad32e229a5c756c20fc62fe4b4b4e5fd7077ae4ed5397aa796f2307ceedb6505b39297856f4aeb5e70938e36ee24a0ac7d9868306f6b53910623b7dc89a6672ad738576ed5d88831dd338321c8902bc2061f65e94d452fdfa0dc665cefb92308e52301bd4627006b363d06b775a395914d8c863e95a00d6893f3376134c429f56478145e4456f7a12d65bb2b8965d728cb2ddbb708f7125c237095a92195d92fa727a372f3545ae701f3808fee802c8967a76e8a940e55fb2d810bfb47ada156f0eda1829b159cf05c7f36cf3847d7b21de84c3dc0fe658347f79396a01139a508b60022db1c0e5aeef47e445e66f783e62c96597bdb16f209c08a9132c7573136170ee3ebf24261265a89fb4f10333375e20b33ab7403464f5249461c6853c5fddb9f58af816892910393a7077b799fdc3489720998feea86:6b7ef27bcfbf2b714985033764fccff555e3f5bc44610d6c8c62117cb3831a07f4a8bddb0eaed1d46b0289b15de1aa4dcc17d71be96a09e66ba4dc4627c7870562fc5ab67deb1fee9ab6cca3b88a1df1e589f0fd4a88f4aa7738948761fe84372c5b18e4655220c1d84d52acad32e229a5c756c20fc62fe4b4b4e5fd7077ae4ed5397aa796f2307ceedb6505b39297856f4aeb5e70938e36ee24a0ac7d9868306f6b53910623b7dc89a6672ad738576ed5d88831dd338321c8902bc2061f65e94d452fdfa0dc665cefb92308e52301bd4627006b363d06b775a395914d8c863e95a00d6893f3376134c429f56478145e4456f7a12d65bb2b8965d728cb2ddbb708f7125c237095a92195d92fa727a372f3545ae701f3808fee802c8967a76e8a940e55fb2d810bfb47ada156f0eda1829b159cf05c7f36cf3847d7b21de84c3dc0fe658347f79396a01139a508b60022db1c0e5aeef47e445e66f783e62c96597bdb16f209c08a9132c7573136170ee3ebf24261265a89fb4f10333375e20b33ab7403464f5249461c6853c5fddb9f58af816892910393a7077b799fdc3489720998feea86: +1ced574529b9b416977e92eb39448a8717cac2934a243a5c44fb44b73ccc16da85e167d5f062fee82014f3c8b1beaed8eefb2c22d8649c424b86b21b11eb8bda:85e167d5f062fee82014f3c8b1beaed8eefb2c22d8649c424b86b21b11eb8bda:1b3b953cce6d15303c61ca707609f70e7250f6c0deba56a8ce522b5986689651cdb848b842b2229661b8eeabfb8570749ed6c2b10a8fbf515053b5ea7d7a9228349e4646f9505e198029fec9ce0f38e4e0ca73625842d64caf8ced070a6e29c743586aa3db6d82993ac71fd38b783162d8fe04ffd0fa5cbc381d0e219c91937df6c973912fc02fda5377312468274c4bee6dca7f79c8b544861ed5babcf5c50e1473491be01708ac7c9ff58f1e40f855497ce9d7cc47b9410f2edd00f6496740243b8d03b2f5fa742b9c630867f77ac42f2b62c14e5ebddc7b647a05fff43670745f2851eff4909f5d27d57ae87f61e965ee60fdf97724c59267f2610b7ad5de919856d64d7c212659ce8656149b6a6d29d8f92b312be50b6e2a431d36ae022b00a6fe360e3af65432899c43be0427e36d21cfec81f21aa53b33db5ed2c37da8f96ac3e7dc67a1de37546cf7de1008c7e1adbe0f34fa7eb2434d94e6a13f4cf86a98d497622f:e0303aefe08a77738dcc657afbb9b835ed279613a53c73fdc5ddbfb350e5cff4d6c9bb43dc07c95bf4e23b64c40f8804c7169952e3c8d59a7197241bfed0740f1b3b953cce6d15303c61ca707609f70e7250f6c0deba56a8ce522b5986689651cdb848b842b2229661b8eeabfb8570749ed6c2b10a8fbf515053b5ea7d7a9228349e4646f9505e198029fec9ce0f38e4e0ca73625842d64caf8ced070a6e29c743586aa3db6d82993ac71fd38b783162d8fe04ffd0fa5cbc381d0e219c91937df6c973912fc02fda5377312468274c4bee6dca7f79c8b544861ed5babcf5c50e1473491be01708ac7c9ff58f1e40f855497ce9d7cc47b9410f2edd00f6496740243b8d03b2f5fa742b9c630867f77ac42f2b62c14e5ebddc7b647a05fff43670745f2851eff4909f5d27d57ae87f61e965ee60fdf97724c59267f2610b7ad5de919856d64d7c212659ce8656149b6a6d29d8f92b312be50b6e2a431d36ae022b00a6fe360e3af65432899c43be0427e36d21cfec81f21aa53b33db5ed2c37da8f96ac3e7dc67a1de37546cf7de1008c7e1adbe0f34fa7eb2434d94e6a13f4cf86a98d497622f: +f0790d93e2d3b84f61ef4c807147aba410e415e72b71b0d61d01026fed99da3defdf649fb033cf328e0b287796f8a25e9c6e2e871b33c2c21a4028a8a25a4b28:efdf649fb033cf328e0b287796f8a25e9c6e2e871b33c2c21a4028a8a25a4b28:7973e9f32d74805992eb65da0d637335e50eff0ce68ea2d1f3a02de704492b9cfbe7e7ba96fdb42bb821a513d73fc60402e92c855deaed73ffeaf70952029062c833e14ec1b14f144e2207f6a0e727e5a7e3cbab27d5972970f69518a15b093e740cc0ce11bf5248f0826b8a98bde8bf2c7082c97aff158d08371118c89021cc3974ae8f76d86673c3f824b62c79c4b41f40eaa8943738f03300f68cbe175468eb235a9ff0e6537f8714e97e8f08ca444e41191063b5fabd156e85dcf66606b81dad4a95065584b3e0658c20a706eaf4a0777da4d2e0cd2a0fca60109c2b4403db3f03cd4781c1fbb0272202bcb11687808c50cb98f64b7f3fd3d43333bb5a061b9e377090abb1e0a885cb26b73c163e63ff6451ff2f4ec8249c7e152bd03973a1e964e2b5b235281a938399a112a24529e383a560dc50bb1b622ad74ef35658dcb10ffe022568ac3ffae5b465a8ed7643e8561b352ee9944a35d882c712b187788a0abae5a22f:08773a6a78762cbb1e25fcbb29139941bdf16f4e09a1fa08fc701f32f933edd74c0ae983c12a0a5b020b6bcf44bb719dde8ed0781a8298265640e1608c98b3017973e9f32d74805992eb65da0d637335e50eff0ce68ea2d1f3a02de704492b9cfbe7e7ba96fdb42bb821a513d73fc60402e92c855deaed73ffeaf70952029062c833e14ec1b14f144e2207f6a0e727e5a7e3cbab27d5972970f69518a15b093e740cc0ce11bf5248f0826b8a98bde8bf2c7082c97aff158d08371118c89021cc3974ae8f76d86673c3f824b62c79c4b41f40eaa8943738f03300f68cbe175468eb235a9ff0e6537f8714e97e8f08ca444e41191063b5fabd156e85dcf66606b81dad4a95065584b3e0658c20a706eaf4a0777da4d2e0cd2a0fca60109c2b4403db3f03cd4781c1fbb0272202bcb11687808c50cb98f64b7f3fd3d43333bb5a061b9e377090abb1e0a885cb26b73c163e63ff6451ff2f4ec8249c7e152bd03973a1e964e2b5b235281a938399a112a24529e383a560dc50bb1b622ad74ef35658dcb10ffe022568ac3ffae5b465a8ed7643e8561b352ee9944a35d882c712b187788a0abae5a22f: +4cb9df7ce6fae9d62ba09e8eb70e4c969bdeafcb5ec7d7024326e6603b0621bf018069dd0eb44055a35cd8c77c37ca9fb1ad2417271385e134b2f4e81f52033c:018069dd0eb44055a35cd8c77c37ca9fb1ad2417271385e134b2f4e81f52033c:14627d6ea0e7895460759476dc74c42800ceef994327518151490d9df23067914e44788a12768ccb25471b9c3ba9d14fb436dcba38429b3a0456877763c49175d0e082683e07a9058f3685c6279307b2303d1221b9c29793d8a4877f6df51587384dadf751c5f7bfbd207d519622c37b51ceeee2c20d8269f8cb88d3fe43d6d434d5bbd0e203c1532d97ba552147227496c87f67b50bb76193add0144df1c176657585408362ca2ed04ad62acf1c25e341dfd1498d85b4b1349a8b0b9b02c43523c55853419bfed37d5a2cdf17dfbf1a3bd7759d6ae180f9d27dcd9a8933e29a7c0a30771eea7c2e0fa242925d2336dce585629057d844323964f6d3d11ff0b3f829a3be8c9f0468a6823d8e70ab5a2da21e15fa8b041a29812222e9c30b2bd9a12d1fdee6f87876e8ce81009637a8bb2236129a47ca74289ee4aad429ffe29f47430241ca8cc3848b7200fd6e1470651a9a0a6f72c9033e831df051408a6260f65cbaf6e012b18e:e33c07836c537d6bfbd0f4592d6e35b163499ba78dc7ffcec565d04f9a7db781943e29e6ce76763e9baddf57437fd9c6b03239a6e6850e4502a356c2e12c370514627d6ea0e7895460759476dc74c42800ceef994327518151490d9df23067914e44788a12768ccb25471b9c3ba9d14fb436dcba38429b3a0456877763c49175d0e082683e07a9058f3685c6279307b2303d1221b9c29793d8a4877f6df51587384dadf751c5f7bfbd207d519622c37b51ceeee2c20d8269f8cb88d3fe43d6d434d5bbd0e203c1532d97ba552147227496c87f67b50bb76193add0144df1c176657585408362ca2ed04ad62acf1c25e341dfd1498d85b4b1349a8b0b9b02c43523c55853419bfed37d5a2cdf17dfbf1a3bd7759d6ae180f9d27dcd9a8933e29a7c0a30771eea7c2e0fa242925d2336dce585629057d844323964f6d3d11ff0b3f829a3be8c9f0468a6823d8e70ab5a2da21e15fa8b041a29812222e9c30b2bd9a12d1fdee6f87876e8ce81009637a8bb2236129a47ca74289ee4aad429ffe29f47430241ca8cc3848b7200fd6e1470651a9a0a6f72c9033e831df051408a6260f65cbaf6e012b18e: +a136e009d53e5ef59d0946bc175663a86bc0fcd29eadd95cfc9d266037b1e4fb9c1806ec0454f58314eb8397d64287dee386640d8491aba364607688841715a0:9c1806ec0454f58314eb8397d64287dee386640d8491aba364607688841715a0:a49d1c3d49e13c2eda56868a8824aa9f8d2bf72f21955ebafd07b3bdc8e924de20936cee513d8a64a47173a3bd659eff1accff8244b26aae1a0c27fa891bf4d85e8fb1b76a6cab1e7f74c89ee07bb40d714326f09b3fd40632fad208ea816f9072028c14b5b54ecc1c5b7fc809e7e0786e2f11495e76017eb62aa4563f3d00ee84348d9838cd17649f6929a6d206f60e6fc82e0c3464b27e0e6abd22f4469bdfd4cb54f77e329b80f71bf42129ec13c9dfe192adfaa42ee3ddeeda385816fbad5f411938c63b560f4ecd94534be7d98725cd94c99ce492f0f069ba0ec08f877a7812ef27ae19d7a77be63f66bcf8d6cf3a1a61fc9cfef104c7462a21ca7f03afb5bb1ac8c75124b554e8d044b810d95ff8c9dd09a34484d8c4b6c95f95c3c22823f52ce844293724d5259191f1ba0929e2acdbb8b9a7a8adf0c52e78acdfdf057b0985881afbed4dbebdebbdae0a2b63bd4e90f96afdcbbd78f506309f9bdb650013cb73faed73904e:bc094ba91c115dee15d753361a75f3f03d6af45c92157e95dbe8d32194b6c5ce72b9dc66f73df12dca0b639f3e791d478616a1f8d7359a42c8eae0dda16b1606a49d1c3d49e13c2eda56868a8824aa9f8d2bf72f21955ebafd07b3bdc8e924de20936cee513d8a64a47173a3bd659eff1accff8244b26aae1a0c27fa891bf4d85e8fb1b76a6cab1e7f74c89ee07bb40d714326f09b3fd40632fad208ea816f9072028c14b5b54ecc1c5b7fc809e7e0786e2f11495e76017eb62aa4563f3d00ee84348d9838cd17649f6929a6d206f60e6fc82e0c3464b27e0e6abd22f4469bdfd4cb54f77e329b80f71bf42129ec13c9dfe192adfaa42ee3ddeeda385816fbad5f411938c63b560f4ecd94534be7d98725cd94c99ce492f0f069ba0ec08f877a7812ef27ae19d7a77be63f66bcf8d6cf3a1a61fc9cfef104c7462a21ca7f03afb5bb1ac8c75124b554e8d044b810d95ff8c9dd09a34484d8c4b6c95f95c3c22823f52ce844293724d5259191f1ba0929e2acdbb8b9a7a8adf0c52e78acdfdf057b0985881afbed4dbebdebbdae0a2b63bd4e90f96afdcbbd78f506309f9bdb650013cb73faed73904e: +ff0f1c57dd884fbeea6e2917282b79ba67f8a6851267b9f4636dafda33bd2b5bfef6378ad12a7c252fa6eb742b05064b41530ff019dc680ab544c027ea2836e7:fef6378ad12a7c252fa6eb742b05064b41530ff019dc680ab544c027ea2836e7:522a5e5eff5b5e98fad6878a9d72df6eb318622610a1e1a48183f5590ecef5a6df671b28be91c88cdf7ae2881147fe6c37c28b43f64cf981c455c59e765ce94e1b6491631deaeef6d1da9ebca88643c77f83eae2cfdd2d97f604fe45081d1be5c4ae2d875996b8b6fecd707d3fa219a93ba0488e55247b405e330cfb97d31a1361c9b2084bdb13fb0c058925db8c3c649c9a3e937b533cc6310fa3b16126fb3cc9bb2b35c5c8300015488a30fadca3c8871fa70dfdc7055bf8e631f20c9b2528311e324a7c4edd5462079f3441c9ecf55fa999e731372344fdc0d413e417aaa001a1b2d3d9bc000fec1b02bd7a88a812d9d8a66f9464764c070c93041eefb17ce74eff6d4aff75f0cbf6a789a9ecde74abe33130fca0da853aa7c3313ada3f0ae2f595c6796a93685e729dd18a669d6381825ab3f36a391e7525b2a807a52fa5ec2a030a8cf3b77337ac41fceb580e845eed655a48b547238c2e8137c92f8c27e585caad3106eee3814a:d5008486726cce330a29dd7e4d7474d735798201afd1206feb869a112e5b43523c06976761be3cf9b2716378273c94f93572a7d2b8982634e0755c632b449008522a5e5eff5b5e98fad6878a9d72df6eb318622610a1e1a48183f5590ecef5a6df671b28be91c88cdf7ae2881147fe6c37c28b43f64cf981c455c59e765ce94e1b6491631deaeef6d1da9ebca88643c77f83eae2cfdd2d97f604fe45081d1be5c4ae2d875996b8b6fecd707d3fa219a93ba0488e55247b405e330cfb97d31a1361c9b2084bdb13fb0c058925db8c3c649c9a3e937b533cc6310fa3b16126fb3cc9bb2b35c5c8300015488a30fadca3c8871fa70dfdc7055bf8e631f20c9b2528311e324a7c4edd5462079f3441c9ecf55fa999e731372344fdc0d413e417aaa001a1b2d3d9bc000fec1b02bd7a88a812d9d8a66f9464764c070c93041eefb17ce74eff6d4aff75f0cbf6a789a9ecde74abe33130fca0da853aa7c3313ada3f0ae2f595c6796a93685e729dd18a669d6381825ab3f36a391e7525b2a807a52fa5ec2a030a8cf3b77337ac41fceb580e845eed655a48b547238c2e8137c92f8c27e585caad3106eee3814a: +0bc6af64de5709d3dbc28f7ef6d3fe28b6de529f08f5857ccb910695de454f56fb491fc900237bdc7e9a119f27150cd911935cd3628749ff40ef41f3955bc8ac:fb491fc900237bdc7e9a119f27150cd911935cd3628749ff40ef41f3955bc8ac:ac7886e4f4172a22c95e8eea37437b375d72accedcee6cc6e816763301a2d8ef4d6f31a2c1d635818b7026a395ce0dafd71c5180893af76b7ea056c972d680eca01dcbdbae6b26f1c5f33fc988b824fbbe00cacc316469a3bae07aa7c8885af7f65f42e75cef94dbb9aab4825143c85070e7716b7612f64ef0b0166011d23eb5654aa098b02d8d71e57c8fa17bff2fe97dc8193177eadc09fb192d80aa92afa98720d4614817ff3c39d3acce18906fa3de09618931d0d7a60c4429cbfa20cf165c947929ac293ae6c06e7e8f25f1264291e3e1c98f5d93e6ecc2389bc60dbbf4a621b132c552a99c95d26d8d1af61138b570a0de4b497ebe8051c7273a98e6e7876d0b327503af3cb2cc4091ce1925cb2f2957f4ec56ee90f8a09dd57d6e83067a356a4cfe65b1b7a4465da2ab133b0efb5e7d4dbb811bcbbde712afbf0f7dd3f326222284b8c74eac7ad6257fa8c632b7da2559a6266e91e0ef90dbb0aa968f75376b693fcaa5da342221:dbc7134d1cd6b0813b53352714b6df939498e91cf37c324337d9c088a1b998347d26185b430900412929e4f63e910379fc42e355a4e98f6fee27dafad1957206ac7886e4f4172a22c95e8eea37437b375d72accedcee6cc6e816763301a2d8ef4d6f31a2c1d635818b7026a395ce0dafd71c5180893af76b7ea056c972d680eca01dcbdbae6b26f1c5f33fc988b824fbbe00cacc316469a3bae07aa7c8885af7f65f42e75cef94dbb9aab4825143c85070e7716b7612f64ef0b0166011d23eb5654aa098b02d8d71e57c8fa17bff2fe97dc8193177eadc09fb192d80aa92afa98720d4614817ff3c39d3acce18906fa3de09618931d0d7a60c4429cbfa20cf165c947929ac293ae6c06e7e8f25f1264291e3e1c98f5d93e6ecc2389bc60dbbf4a621b132c552a99c95d26d8d1af61138b570a0de4b497ebe8051c7273a98e6e7876d0b327503af3cb2cc4091ce1925cb2f2957f4ec56ee90f8a09dd57d6e83067a356a4cfe65b1b7a4465da2ab133b0efb5e7d4dbb811bcbbde712afbf0f7dd3f326222284b8c74eac7ad6257fa8c632b7da2559a6266e91e0ef90dbb0aa968f75376b693fcaa5da342221: +2f5e83bd5b412e71ae3e9084cd369efcc79bf6037c4b174dfd6a11fb0f5da218a22a6da29a5ef6240c49d8896e3a0f1a4281a266c77d383ee6f9d25ffacbb872:a22a6da29a5ef6240c49d8896e3a0f1a4281a266c77d383ee6f9d25ffacbb872:b766273f060ef3b2ae3340454a391b426bc2e97264f8674553eb00dd6ecfdd59b611d8d662929fec710d0e462020e12cdbf9c1ec8858e85671acf8b7b14424ce92079d7d801e2ad9acac036bc8d2dfaa72aa839bff30c0aa7e414a882c00b645ff9d31bcf5a54382def4d0142efa4f06e823257ff132ee968cdc6738c53f53b84c8df76e9f78dd5056cf3d4d5a80a8f84e3edec48520f2cb4583e708539355ef7aa86fb5a0e87a94dcf14f30a2cca568f139d9ce59eaf459a5c5916cc8f20b26aaf6c7c029379aedb05a07fe585ccac60307c1f58ca9f859157d06d06baa394aace79d51b8cb38cfa2598141e245624e5ab9b9d68731173348905315bf1a5ad61d1e8adaeb810e4e8a86d7c13537b0be860ab2ed35b73399b8808aa91d750f77943f8a8b7e89fdb50728aa3dbbd8a41a6e00756f438c9b9e9d55872df5a9068add8a972b7e43edad9ced2237ca1367be4b7cdb66a54ea12eef129471158610eaf28f99f7f686557dcdf644ea:9f80922bc8db32d0cc43f9936affebe7b2bc35a5d82277cd187b5d50dc7fc4c4832fffa34e9543806b485c04548e7c75429425e14d55d91fc1052efd8667430bb766273f060ef3b2ae3340454a391b426bc2e97264f8674553eb00dd6ecfdd59b611d8d662929fec710d0e462020e12cdbf9c1ec8858e85671acf8b7b14424ce92079d7d801e2ad9acac036bc8d2dfaa72aa839bff30c0aa7e414a882c00b645ff9d31bcf5a54382def4d0142efa4f06e823257ff132ee968cdc6738c53f53b84c8df76e9f78dd5056cf3d4d5a80a8f84e3edec48520f2cb4583e708539355ef7aa86fb5a0e87a94dcf14f30a2cca568f139d9ce59eaf459a5c5916cc8f20b26aaf6c7c029379aedb05a07fe585ccac60307c1f58ca9f859157d06d06baa394aace79d51b8cb38cfa2598141e245624e5ab9b9d68731173348905315bf1a5ad61d1e8adaeb810e4e8a86d7c13537b0be860ab2ed35b73399b8808aa91d750f77943f8a8b7e89fdb50728aa3dbbd8a41a6e00756f438c9b9e9d55872df5a9068add8a972b7e43edad9ced2237ca1367be4b7cdb66a54ea12eef129471158610eaf28f99f7f686557dcdf644ea: +722a2da50e42c11a61c9afac7be1a2fed2267d650f8f7d8e5bc706b807c1b91dfd0b964562f823721e649c3fedb432a76f91e0aead7c61d35f95ed7726d78589:fd0b964562f823721e649c3fedb432a76f91e0aead7c61d35f95ed7726d78589:173e8bb885e1f9081404acac999041d2ecfcb73f945e0db36e631d7cd1ab999eb717f34bf07874bf3d34e2530eb6085f4a9f88ae1b0f7d80f221456a8e9a8890b91a50192deaaacc0a1a615a87841e2c5a9e057957af6e48e78cc86198e32e7aa24dcf6cffa329bc72606d65b11682c8ba736cce22a05785df1146331e41609cf9ca711cf464958297138b58a9073f3bbf06ad8a85d135de66652104d88b49d27ad41e59bcc44c7fab68f53f0502e293ffcabaaf755927dfdffbfde3b35c080b5de4c8b785f4da64ef357bc0d1466a6a96560c3c4f3e3c0b563a003f5f95f237171bce1a001771a04ede7cdd9b8ca770fd36ef90e9fe0000a8d7685fd153cc7282de95920a8f8f0898d00bf0c6c933fe5bb9653ff146c4e2acd1a2e0c23c1244844dacf8652716302c2032f9c114679ed26b3ee3ab4a7b18bc4e3071f0977db57cd0ac68c0727a09b4f125fb64af2850b26c8a484263334e2da902d744737044e79ab1cf5b2f93a022b63d40cd:c2695a57172aaa31bd0890f231ca8eeec0287a87172669a899ad0891cea4c47579b50420e791cdec8c182c8a0e8dde21b2480b0cfd8111e28e5603347a352d04173e8bb885e1f9081404acac999041d2ecfcb73f945e0db36e631d7cd1ab999eb717f34bf07874bf3d34e2530eb6085f4a9f88ae1b0f7d80f221456a8e9a8890b91a50192deaaacc0a1a615a87841e2c5a9e057957af6e48e78cc86198e32e7aa24dcf6cffa329bc72606d65b11682c8ba736cce22a05785df1146331e41609cf9ca711cf464958297138b58a9073f3bbf06ad8a85d135de66652104d88b49d27ad41e59bcc44c7fab68f53f0502e293ffcabaaf755927dfdffbfde3b35c080b5de4c8b785f4da64ef357bc0d1466a6a96560c3c4f3e3c0b563a003f5f95f237171bce1a001771a04ede7cdd9b8ca770fd36ef90e9fe0000a8d7685fd153cc7282de95920a8f8f0898d00bf0c6c933fe5bb9653ff146c4e2acd1a2e0c23c1244844dacf8652716302c2032f9c114679ed26b3ee3ab4a7b18bc4e3071f0977db57cd0ac68c0727a09b4f125fb64af2850b26c8a484263334e2da902d744737044e79ab1cf5b2f93a022b63d40cd: +5fe9c3960ed5bd374cc94d42357e6a24dc7e3060788f726365defacf13cd12da0ce7b155c8b20ebdaacdc2aa23627e34b1f9ace980650a2530c7607d04814eb4:0ce7b155c8b20ebdaacdc2aa23627e34b1f9ace980650a2530c7607d04814eb4:c9490d83d9c3a9370f06c91af001685a02fe49b5ca667733fff189eee853ec1667a6c1b6c787e9244812d2d532866ab74dfc870d6f14033b6bcd39852a3900f8f08cd95a74cb8cbe02b8b8b51e993a06adfebd7fc9854ae5d29f4df9642871d0c5e470d903cfbcbd5adb3275628f28a80bf8c0f0376687dae673bf7a8547e80d4a9855ae2572fc2b205dc8a198016ddc9b50995f5b39f368f540504a551803d6dd5f874828e5541ded052894d9e2dc5e6aa351087e790c0dd5d9c4decb217e4db81c98a184b264e6daeac0f11e074cae2bfc899f54b419c65dcc22664a915fbfffac35cee0f286eb7b144933db933e16c4bcb650d537722489de236373fd8d65fc86118b6def37ca4608bc6ce927b65436ffda7f02bfbf88b045ae7d2c2b45a0b30c8f2a04df953221088c555fe9a5df260982a3d64df194ee952fa9a98c31b96493db6180d13d67c36716f95f8c0bd7a039ad990667ca34a83ac1a18c37dd7c7736aa6b9b6fc2b1ac0ce119ef77:379f9c54c413af0d192e9bc736b29da9d521e7ba7841d309f9bcc1e742ec4308fe9f7ba51e0b22aed487cb4aa3913b9bebfb3aacd38f4039f9bbbebe1ad80002c9490d83d9c3a9370f06c91af001685a02fe49b5ca667733fff189eee853ec1667a6c1b6c787e9244812d2d532866ab74dfc870d6f14033b6bcd39852a3900f8f08cd95a74cb8cbe02b8b8b51e993a06adfebd7fc9854ae5d29f4df9642871d0c5e470d903cfbcbd5adb3275628f28a80bf8c0f0376687dae673bf7a8547e80d4a9855ae2572fc2b205dc8a198016ddc9b50995f5b39f368f540504a551803d6dd5f874828e5541ded052894d9e2dc5e6aa351087e790c0dd5d9c4decb217e4db81c98a184b264e6daeac0f11e074cae2bfc899f54b419c65dcc22664a915fbfffac35cee0f286eb7b144933db933e16c4bcb650d537722489de236373fd8d65fc86118b6def37ca4608bc6ce927b65436ffda7f02bfbf88b045ae7d2c2b45a0b30c8f2a04df953221088c555fe9a5df260982a3d64df194ee952fa9a98c31b96493db6180d13d67c36716f95f8c0bd7a039ad990667ca34a83ac1a18c37dd7c7736aa6b9b6fc2b1ac0ce119ef77: +ec2fa541ac14b414149c3825eaa7001b795aa1957d4040dda92573904afa7ee471b363b2408404d7beecdef1e1f511bb6084658b532f7ea63d4e3f5f01c61d31:71b363b2408404d7beecdef1e1f511bb6084658b532f7ea63d4e3f5f01c61d31:2749fc7c4a729e0e0ad71b5b74eb9f9c534ebd02ffc9df4374d813bdd1ae4eb87f1350d5fdc563934515771763e6c33b50e64e0cd114573031d2186b6eca4fc802cddc7cc51d92a61345a17f6ac38cc74d84707a5156be9202dee3444652e79bae7f0d31bd17567961f65dd01a8e4bee38331938ce4b2b550691b99a4bc3c072d186df4b3344a5c8fbfbb9fd2f355f6107e410c3d0c798b68d3fb9c6f7ab5fe27e70871e86767698fe35b77ead4e435a9402cc9ed6a2657b059be0a21003c048bbf5e0ebd93cbb2e71e923cf5c728d1758cd817ad74b454a887126d653b95a7f25e5293b768c9fc5a9c35a2372e3741bc90fd66301427b10824bb4b1e9110bfba84c21a40eb8fed4497e91dc3ffd0438c514c0a8cb4cac6ad0256bf11d5aa7a9c7c00b669b015b0bf81425a21413e2ffb6edc0bd78e385c44fd74558e511c2c25fee1fec18d3990b8690300fa711e93d9854668f0187065e76e7113ae763c30ddd86720b5546a6c3c6f1c43bc67b14:84d18d56f964e3776759bba92c510c2b6d574555c3cddade212da90374554991e7d77e278d63e34693e1958078cc3685f8c41c1f5342e351899638ef612114012749fc7c4a729e0e0ad71b5b74eb9f9c534ebd02ffc9df4374d813bdd1ae4eb87f1350d5fdc563934515771763e6c33b50e64e0cd114573031d2186b6eca4fc802cddc7cc51d92a61345a17f6ac38cc74d84707a5156be9202dee3444652e79bae7f0d31bd17567961f65dd01a8e4bee38331938ce4b2b550691b99a4bc3c072d186df4b3344a5c8fbfbb9fd2f355f6107e410c3d0c798b68d3fb9c6f7ab5fe27e70871e86767698fe35b77ead4e435a9402cc9ed6a2657b059be0a21003c048bbf5e0ebd93cbb2e71e923cf5c728d1758cd817ad74b454a887126d653b95a7f25e5293b768c9fc5a9c35a2372e3741bc90fd66301427b10824bb4b1e9110bfba84c21a40eb8fed4497e91dc3ffd0438c514c0a8cb4cac6ad0256bf11d5aa7a9c7c00b669b015b0bf81425a21413e2ffb6edc0bd78e385c44fd74558e511c2c25fee1fec18d3990b8690300fa711e93d9854668f0187065e76e7113ae763c30ddd86720b5546a6c3c6f1c43bc67b14: +6132692a5ef27bf476b1e991e6c431a8c764f1aebd470282db3321bb7cb09c207a2d166184f9e5f73bea454486b041ceb5fc2314a7bd59cb718e79f0ec989d84:7a2d166184f9e5f73bea454486b041ceb5fc2314a7bd59cb718e79f0ec989d84:a9c0861665d8c2de06f9301da70afb27b3024b744c6b38b24259294c97b1d1cb4f0dcf7575a8ed454e2f0980f50313a77363415183fe9677a9eb1e06cb6d34a467cb7b0758d6f55c564b5ba15603e202b18856d89e72a23ab07d8853ff77da7aff1caebd7959f2c710ef31f5078a9f2cdae92641a1cc5f74d0c143ec42afbaa5f378a9e10d5bf74587fa5f49c156233247dafd3929acde888dc684337e40cdc5932e7eb73ffcc90b85c0ad460416691aefbd7efd07b657c350946a0e366b37a6c8089aba5c5fe3bbca064afbe9d47fbc83914af1cb43c2b2efa98e0a43be32ba823202001def36817251b65f9b0506cef6683642a46ed612f8ca81ee97bb04d317b517343ade2b77126d1f02a87b7604c8653b6748cf5488fa6d43df809faa19e69292d38c5d397dd8e20c7af7c5334ec977f5010a0f7cb5b89479ca06db4d12627f067d6c42186a6b1f8742f36ae709ba720e3cd898116666d81b190b9b9d2a72202cb690a03f3310429a71dc048cde:eb677f3347e1a1ea929efdf62bf9105a6c8f4993033b4f6d03cb0dbf9c742b270704e383ab7c0676bdb1ad0ce9b16673083c9602ec10ae1dd98e8748b336440ba9c0861665d8c2de06f9301da70afb27b3024b744c6b38b24259294c97b1d1cb4f0dcf7575a8ed454e2f0980f50313a77363415183fe9677a9eb1e06cb6d34a467cb7b0758d6f55c564b5ba15603e202b18856d89e72a23ab07d8853ff77da7aff1caebd7959f2c710ef31f5078a9f2cdae92641a1cc5f74d0c143ec42afbaa5f378a9e10d5bf74587fa5f49c156233247dafd3929acde888dc684337e40cdc5932e7eb73ffcc90b85c0ad460416691aefbd7efd07b657c350946a0e366b37a6c8089aba5c5fe3bbca064afbe9d47fbc83914af1cb43c2b2efa98e0a43be32ba823202001def36817251b65f9b0506cef6683642a46ed612f8ca81ee97bb04d317b517343ade2b77126d1f02a87b7604c8653b6748cf5488fa6d43df809faa19e69292d38c5d397dd8e20c7af7c5334ec977f5010a0f7cb5b89479ca06db4d12627f067d6c42186a6b1f8742f36ae709ba720e3cd898116666d81b190b9b9d2a72202cb690a03f3310429a71dc048cde: +f219b2101164aa9723bde3a7346f68a35061c01f9782072580ba32df903ba891f66b920d5aa1a6085495a1480539beba01ffe60e6a6388d1b2e8eda23355810e:f66b920d5aa1a6085495a1480539beba01ffe60e6a6388d1b2e8eda23355810e:015577d3e4a0ec1ab25930106343ff35ab4f1e0a8a2d844aadbb70e5fc5348ccb679c2295c51d702aaae7f6273ce70297b26cb7a253a3db94332e86a15b4a64491232791f7a8b082ee2834af30400e804647a532e9c454d2a0a7320130ab6d4d860073a34667ac25b7e5e2747ba9f5c94594fb68377ae260369c40713b4e32f23195bf91d3d7f1a2719bf408aad8d8a347b112e84b118817cb06513344021763035272a7db728a0ccdaa949c61715d0764140b3e8c01d20ff1593c7f2d55c4e82a1c0cb1ea58442bf80a741bca91f58ab0581b498ee9fe3c92ca654148ef75313543d1aff382befe1a93b02190ce0102175158e2071d02bacad8dbe9fb940fcb610c105ad52c80feb1ec4e524f4c0ec7983e9ce696fa4fcf4bf0514b8f0432b17d5448fc426fea2b01ac7b26c2aed769927534da22576fc1bba726e9d65be01b59f60a648ace2fc3e5e275789fa637cbbd84be3d6ac24457a6292cd656c7b569a52ffea7916b8d04b4f4a75be7ac95142f:17f0127ca3bafa5f4ee959cd60f772be87a0034961517e39a0a1d0f4b9e26db1336e60c82b352c4cbacdbbd11771c3774f8cc5a1a795d6e4f4ebd51def36770b015577d3e4a0ec1ab25930106343ff35ab4f1e0a8a2d844aadbb70e5fc5348ccb679c2295c51d702aaae7f6273ce70297b26cb7a253a3db94332e86a15b4a64491232791f7a8b082ee2834af30400e804647a532e9c454d2a0a7320130ab6d4d860073a34667ac25b7e5e2747ba9f5c94594fb68377ae260369c40713b4e32f23195bf91d3d7f1a2719bf408aad8d8a347b112e84b118817cb06513344021763035272a7db728a0ccdaa949c61715d0764140b3e8c01d20ff1593c7f2d55c4e82a1c0cb1ea58442bf80a741bca91f58ab0581b498ee9fe3c92ca654148ef75313543d1aff382befe1a93b02190ce0102175158e2071d02bacad8dbe9fb940fcb610c105ad52c80feb1ec4e524f4c0ec7983e9ce696fa4fcf4bf0514b8f0432b17d5448fc426fea2b01ac7b26c2aed769927534da22576fc1bba726e9d65be01b59f60a648ace2fc3e5e275789fa637cbbd84be3d6ac24457a6292cd656c7b569a52ffea7916b8d04b4f4a75be7ac95142f: +fc180035aec0f5ede7bda93bf77ade7a81ed06de07ee2e3aa8576be81608610a4f215e948cae243ee3143b80282ad792c780d2a6b75060ca1d290ca1a8e3151f:4f215e948cae243ee3143b80282ad792c780d2a6b75060ca1d290ca1a8e3151f:b5e8b01625664b222339e0f05f93a990ba48b56ae65439a17520932df011721e284dbe36f98631c066510098a68d7b692a3863e99d58db76ca5667c8043cb10bd7abbaf506529fbb23a5166be038affdb9a234c4f4fcf43bddd6b8d2ce772dd653ed115c095e232b269dd4888d2368cb1c66be29dd383fca67f66765b296564e37555f0c0e484504c591f006ea8533a12583ad2e48318ff6f324ecaf804b1bae04aa896743e67ef61ca383d58e42acfc6410de30776e3ba262373b9e1441943955101a4e768231ad9c6529eff6118dde5df02f94b8d6df2d99f27863b517243a579e7aaff311ea3a0282e47ca876fabc2280fce7adc984dd0b30885b1650f1471dfcb0522d49fec7d042f32a93bc368f076006ea01ec1c7412bf66f62dc88de2c0b74701a5614e855e9fa728fb1f1171385f96afbde70dea02e9aa94dc21848c26302b50ae91f9693a1864e4e095ae03cdc22ad28a0eb7db596779246712fab5f5da327efec3e79612de0a6ccaa536759b8e:a43a71c3a19c35660dae6f31a254b8c0ea3593fc8fca74d13640012b9e9473d4afe070db01e7fb399bf4ca6070e062180011285a67dd6858b761e46c6bd32004b5e8b01625664b222339e0f05f93a990ba48b56ae65439a17520932df011721e284dbe36f98631c066510098a68d7b692a3863e99d58db76ca5667c8043cb10bd7abbaf506529fbb23a5166be038affdb9a234c4f4fcf43bddd6b8d2ce772dd653ed115c095e232b269dd4888d2368cb1c66be29dd383fca67f66765b296564e37555f0c0e484504c591f006ea8533a12583ad2e48318ff6f324ecaf804b1bae04aa896743e67ef61ca383d58e42acfc6410de30776e3ba262373b9e1441943955101a4e768231ad9c6529eff6118dde5df02f94b8d6df2d99f27863b517243a579e7aaff311ea3a0282e47ca876fabc2280fce7adc984dd0b30885b1650f1471dfcb0522d49fec7d042f32a93bc368f076006ea01ec1c7412bf66f62dc88de2c0b74701a5614e855e9fa728fb1f1171385f96afbde70dea02e9aa94dc21848c26302b50ae91f9693a1864e4e095ae03cdc22ad28a0eb7db596779246712fab5f5da327efec3e79612de0a6ccaa536759b8e: +a2836a65427912122d25dcdfc99d7046fe9b53d5c1bb23617f11890e94ca93ed8c12bda214c8abb2286acffbf8112425040aab9f4d8bb7870b98da0159e882f1:8c12bda214c8abb2286acffbf8112425040aab9f4d8bb7870b98da0159e882f1:813d6061c56eae0ff53041c0244aa5e29e13ec0f3fb428d4beb8a99e04bca8c41bddb0db945f487efe38f2fc14a628fafa2462f860e4e34250eb4e93f139ab1b74a2614519e41ee2403be427930ab8bc82ec89ceafb60905bd4ddbbd13bdb19654314fc92373140b962e2258e038d71b9ec66b84ef8319e03551cb707e747f6c40ad476fbefdce71f3a7b67a1af1869bc6440686e7e0855e4f369d1d88b8099fba54714678627bba1aff41e7707bc97eddf890b0c08dce3e9800d24c6f61092ce28d481b5dea5c096c55d72f8946009131fb968e2bc8a054d825adab76740dcf0d758c8bf54ff38659e71b32bfe2e615aaabb0f5293085649cf60b9847bc62011ce3878af628984a5840a4ad5dae3702db367da0f8a165fed0517eb5c442b0145330241b97eeca733ba6688b9c129a61cd1236aff0e27bcf98c28b0fbeea55a3d7c7193d644b2749f986bd46af8938e8faaeafbd9cec3612ab005bd7c3eeafe9a31279ca6102560666ba16136ff1452f850adb:e6a9a6b436559a4320c45c0c2c4a2aedecb90d416d52c82680ac7330d062aebef3e9ac9f2c5ffa455c9be113013a2b282e5600fd306435ada83b1e48ba2a3605813d6061c56eae0ff53041c0244aa5e29e13ec0f3fb428d4beb8a99e04bca8c41bddb0db945f487efe38f2fc14a628fafa2462f860e4e34250eb4e93f139ab1b74a2614519e41ee2403be427930ab8bc82ec89ceafb60905bd4ddbbd13bdb19654314fc92373140b962e2258e038d71b9ec66b84ef8319e03551cb707e747f6c40ad476fbefdce71f3a7b67a1af1869bc6440686e7e0855e4f369d1d88b8099fba54714678627bba1aff41e7707bc97eddf890b0c08dce3e9800d24c6f61092ce28d481b5dea5c096c55d72f8946009131fb968e2bc8a054d825adab76740dcf0d758c8bf54ff38659e71b32bfe2e615aaabb0f5293085649cf60b9847bc62011ce3878af628984a5840a4ad5dae3702db367da0f8a165fed0517eb5c442b0145330241b97eeca733ba6688b9c129a61cd1236aff0e27bcf98c28b0fbeea55a3d7c7193d644b2749f986bd46af8938e8faaeafbd9cec3612ab005bd7c3eeafe9a31279ca6102560666ba16136ff1452f850adb: +f051af426d0c3282fafc8bf912ade1c24211a95ad200e1eef549320e1cb1a252fa87955e0ea13dde49d83dc22e63a2bdf1076725c2cc7f93c76511f28e7944f2:fa87955e0ea13dde49d83dc22e63a2bdf1076725c2cc7f93c76511f28e7944f2:b48d9f84762b3bcc66e96d76a616fa8fe8e01695251f47cfc1b7b17d60dc9f90d576ef64ee7d388504e2c9079638165a889696471c989a876f8f13b63b58d531fea4dd1229fc631668a047bfae2da281feae1b6de3ebe280abe0a82ee00fbfdc22ce2d10e06a0492ff1404dfc094c40b203bf55721dd787ed4e91d5517aaf58d3bdd35d44a65ae6ba75619b339b650518cefcc17493de27a3b5d41788f87edbde72610f181bf06e208e0eb7cdfe881d91a2d6cc77aa19c0fcf330fedb44675d800eb8cff9505d8887544a503cbe373c4847b19e8f3995726efd6649858595c57ccaf0cbc9eb25de83ba046bc9f1838ac7b8953dd81b81ac0f68d0e9338cb55402552afb6bc16949351b926d151a82efc695e8d7da0dd55099366789718ccbf36030bd2c3c109399be26cdb8b9e2a155f3b2cb1bfa71ab69a23625a4ac118fe91cb2c19788cf52a71d730d576b421d96982a51a2991daec440cda7e6cc3282b8312714278b819bfe2387eb96aa91d40173034f428:b8f713578a64466719aceb432fce302a87cf066bf3e102a350616921a840964bfc7e685d8fd17455ac3eb4861edcb8979d35e3a4bd82a078cd707721d733400eb48d9f84762b3bcc66e96d76a616fa8fe8e01695251f47cfc1b7b17d60dc9f90d576ef64ee7d388504e2c9079638165a889696471c989a876f8f13b63b58d531fea4dd1229fc631668a047bfae2da281feae1b6de3ebe280abe0a82ee00fbfdc22ce2d10e06a0492ff1404dfc094c40b203bf55721dd787ed4e91d5517aaf58d3bdd35d44a65ae6ba75619b339b650518cefcc17493de27a3b5d41788f87edbde72610f181bf06e208e0eb7cdfe881d91a2d6cc77aa19c0fcf330fedb44675d800eb8cff9505d8887544a503cbe373c4847b19e8f3995726efd6649858595c57ccaf0cbc9eb25de83ba046bc9f1838ac7b8953dd81b81ac0f68d0e9338cb55402552afb6bc16949351b926d151a82efc695e8d7da0dd55099366789718ccbf36030bd2c3c109399be26cdb8b9e2a155f3b2cb1bfa71ab69a23625a4ac118fe91cb2c19788cf52a71d730d576b421d96982a51a2991daec440cda7e6cc3282b8312714278b819bfe2387eb96aa91d40173034f428: +a103e92672c65f81ea5da1fff1a4038788479e941d503a756f4a755201a57c1dee63a5b69641217acbaf3339da829ec071b9931e5987153514d30140837a7af4:ee63a5b69641217acbaf3339da829ec071b9931e5987153514d30140837a7af4:b1984e9eec085d524c1eb3b95c89c84ae085be5dc65c326e19025e1210a1d50edbbba5d1370cf15d68d687eb113233e0fba50f9433c7d358773950c67931db8296bbcbecec888e87e71a2f7579fad2fa162b85fb97473c456b9a5ce2956676969c7bf4c45679085b62f2c224fc7f458794273f6d12c5f3e0d06951824d1cca3e2f904559ed28e2868b366d79d94dc98667b9b5924268f3e39b1291e5abe4a758f77019dacbb22bd8196e0a83a5677658836e96ca5635055a1e63d65d036a68d87ac2fd283fdda390319909c5cc7680368848873d597f298e0c6172308030ffd452bb1363617b316ed7cd949a165dc8abb53f991aef3f3e9502c5dfe4756b7c6bfdfe89f5e00febdd6afb0402818f11cf8d1d5864fe9da1b86e39aa935831506cf2400ea7ed75bd9533b23e202fe875d7d9638c89d11cb2d6e6021ae6bd27c7754810d35cd3a61494f27b16fc794e2cd2f0d3453ada933865db78c579571f8fc5c5c6be8eaffce6a852e5b3b1c524c49313d427abcb:2aa2035c2ce5b5e6ae161e168f3ad0d6592bcf2c4a049d3ed342fceb56be9c7cb372027573ae0178e8878ebefca7b030327b8aad41857de58cb78e1a00cbac05b1984e9eec085d524c1eb3b95c89c84ae085be5dc65c326e19025e1210a1d50edbbba5d1370cf15d68d687eb113233e0fba50f9433c7d358773950c67931db8296bbcbecec888e87e71a2f7579fad2fa162b85fb97473c456b9a5ce2956676969c7bf4c45679085b62f2c224fc7f458794273f6d12c5f3e0d06951824d1cca3e2f904559ed28e2868b366d79d94dc98667b9b5924268f3e39b1291e5abe4a758f77019dacbb22bd8196e0a83a5677658836e96ca5635055a1e63d65d036a68d87ac2fd283fdda390319909c5cc7680368848873d597f298e0c6172308030ffd452bb1363617b316ed7cd949a165dc8abb53f991aef3f3e9502c5dfe4756b7c6bfdfe89f5e00febdd6afb0402818f11cf8d1d5864fe9da1b86e39aa935831506cf2400ea7ed75bd9533b23e202fe875d7d9638c89d11cb2d6e6021ae6bd27c7754810d35cd3a61494f27b16fc794e2cd2f0d3453ada933865db78c579571f8fc5c5c6be8eaffce6a852e5b3b1c524c49313d427abcb: +d47c1b4b9e50cbb71fd07d096d91d87213d44b024373044761c4822f9d9df880f4e1cb86c8ca2cfee43e58594a8778436d3ea519704e00c1bbe48bbb1c9454f8:f4e1cb86c8ca2cfee43e58594a8778436d3ea519704e00c1bbe48bbb1c9454f8:88d7009d51de3d337eef0f215ea66ab830ec5a9e6823761c3b92ad93ea341db92ece67f4ef4ceb84194ae6926c3d014b2d59781f02e0b32f9a611222cb9a5850c6957cb8079ae64e0832a1f05e5d1a3c572f9d08f1437f76bb3b83b52967c3d48c3576848891c9658d4959eb80656d26cdba0810037c8a18318ff122f8aa8985c773cb317efa2f557f1c3896bcb162df5d87681bb787e7813aa2dea3b0c564d646a92861f444ca1407efbac3d12432cbb70a1d0eaffb11741d3718fedee2b83036189a6fc45a52f74fa487c18fd264a7945f6c9e44b011f5d86613f1939b19f4f4fdf53234057be3f005ad64eebf3c8ffb58cb40956c4336df01d4424b706a0e561d601708d12485e21bcb6d799d8d1d044b400064ec0944501406e70253947006cabbdb2dd6bd8cee4497653d9113a44d4de9b68d4c526fca0b9b0c18fe50fb917fdd9a914fb816108a73a6b3fff9e654e69c9cfe02b05c6c1b9d15c4e65cf31018b8100d784633ee1888eee3572aafa6f189ea22d0:627e7ca7e34ed6331d62b9541c1ea9a9292be7b0a65d805e266b5122272a82db7d765acc7e2a290d685804922f91ed04a3c382c03ff21a1768f584413c4e5f0088d7009d51de3d337eef0f215ea66ab830ec5a9e6823761c3b92ad93ea341db92ece67f4ef4ceb84194ae6926c3d014b2d59781f02e0b32f9a611222cb9a5850c6957cb8079ae64e0832a1f05e5d1a3c572f9d08f1437f76bb3b83b52967c3d48c3576848891c9658d4959eb80656d26cdba0810037c8a18318ff122f8aa8985c773cb317efa2f557f1c3896bcb162df5d87681bb787e7813aa2dea3b0c564d646a92861f444ca1407efbac3d12432cbb70a1d0eaffb11741d3718fedee2b83036189a6fc45a52f74fa487c18fd264a7945f6c9e44b011f5d86613f1939b19f4f4fdf53234057be3f005ad64eebf3c8ffb58cb40956c4336df01d4424b706a0e561d601708d12485e21bcb6d799d8d1d044b400064ec0944501406e70253947006cabbdb2dd6bd8cee4497653d9113a44d4de9b68d4c526fca0b9b0c18fe50fb917fdd9a914fb816108a73a6b3fff9e654e69c9cfe02b05c6c1b9d15c4e65cf31018b8100d784633ee1888eee3572aafa6f189ea22d0: +fc0c32c5eb6c71ea08dc2b300cbcef18fdde3ea20f68f21733237b4ddaab900e47c37d8a080857eb8777a6c0a9a5c927303faf5c320953b5de48e462e12d0062:47c37d8a080857eb8777a6c0a9a5c927303faf5c320953b5de48e462e12d0062:a7b1e2db6bdd96b3d51475603537a76b42b04d7ebd24fe515a887658e4a352e22109335639a59e2534811f4753b70209d0e4698e9d926088826c14689681ea00fa3a2fcaa0047ced3ef287e6172502b215e56497614d86b4cb26bcd77a2e172509360ee58893d01c0d0fb4d4abfe4dbd8d2a2f54190fa2f731c1ceac6829c3ddc9bfb2ffd70c57ba0c2b22d2326fbfe7390db8809f73547ff47b86c36f2bf7454e678c4f1c0fa870bd0e30bbf3278ec8d0c5e9b64aff0af64babc19b70f4cf9a41cb8f95d3cde24f456ba3571c8f021d38e591dec05cb5d1ca7b48f9da4bd734b069a9fd106500c1f408ab7fe8e4a6e6f3ed64da0ed24b01e33df8475f95fa9ed71d04dd30b3cd823755a3401bf5afae10ee7e18ec6fe637c3793fd434b48d7145130447e00299101052558b506554ec9c399f62941c3f414cbc352caa345b930adecfaddac91ee53d1451a65e06201026325de07c931f69bba868a7c87ee23c604ec6794332917dfe2c5b69669b659706917f71eddf96:6887c6e2b98a82af5ee3dfa7ca2cb25d9c10745620a82956acba85cb57c8ec24279fa42f092359a1b6bbeafba050f14b6288209e6ef7bc1e0a2b872c1138f305a7b1e2db6bdd96b3d51475603537a76b42b04d7ebd24fe515a887658e4a352e22109335639a59e2534811f4753b70209d0e4698e9d926088826c14689681ea00fa3a2fcaa0047ced3ef287e6172502b215e56497614d86b4cb26bcd77a2e172509360ee58893d01c0d0fb4d4abfe4dbd8d2a2f54190fa2f731c1ceac6829c3ddc9bfb2ffd70c57ba0c2b22d2326fbfe7390db8809f73547ff47b86c36f2bf7454e678c4f1c0fa870bd0e30bbf3278ec8d0c5e9b64aff0af64babc19b70f4cf9a41cb8f95d3cde24f456ba3571c8f021d38e591dec05cb5d1ca7b48f9da4bd734b069a9fd106500c1f408ab7fe8e4a6e6f3ed64da0ed24b01e33df8475f95fa9ed71d04dd30b3cd823755a3401bf5afae10ee7e18ec6fe637c3793fd434b48d7145130447e00299101052558b506554ec9c399f62941c3f414cbc352caa345b930adecfaddac91ee53d1451a65e06201026325de07c931f69bba868a7c87ee23c604ec6794332917dfe2c5b69669b659706917f71eddf96: +a8d73d639a23cc6a967ef31bcabb5d063e53e1eab8fcc7cab9bc3a17fde9c2f88daa9f4c8b1a44691bf44521f2f7ca45dc7fc61f6a4ce6f98faa41c2a74977d1:8daa9f4c8b1a44691bf44521f2f7ca45dc7fc61f6a4ce6f98faa41c2a74977d1:fd1fac3d53313b11acd29f5a83ac11896dab2530fa47865b2295c0d99dd67c36ed8e5fa549150c794c5549efb5c1d69114d5d607b23285b7212afaab57846a54ae67b9e880e07b6586607cecf6d4eed516a3a75511fe367d88eb871e6d71b7d6aa1367a01421b1088fc2d75e44954b73625c52da8a3a183c60be9da6050f59a453caa53520593671728d431877bfaac913a765fb6a56b75290b2a8aaac34afb9217ba1b0d5850ba0fdabf80969def0feee794ceb60614e3368e63ef20e4c32d341ec9b0328ea9fe139207ed7a626ff08943b415233db7cfcc845c9b63121d4ed52ec3748ab6a1f36b2103c7dc7e9303acea4ba8af7a3e07184fb491e891ede84f0dc41cadc3973028e879acd2031afc29a16092868e2c7f539fc1b792edab195a25ab9830661346b39ef53915de4af52c421eaf172e9da76a08c283a52df907f705d7e8599c5baae0c2af380c1bb46f93484a03f28374324b278992b50b7afa02552cafa503f034f8d866e9b720271dd68ccb685a85fffd1:c4dcef1a2453939b364b340250c3129431431d5ba3f47670ab07ce680c69bf28b678627c76a6360fc40dc109aa7dea371b825e46134f624572182acf3957e70ffd1fac3d53313b11acd29f5a83ac11896dab2530fa47865b2295c0d99dd67c36ed8e5fa549150c794c5549efb5c1d69114d5d607b23285b7212afaab57846a54ae67b9e880e07b6586607cecf6d4eed516a3a75511fe367d88eb871e6d71b7d6aa1367a01421b1088fc2d75e44954b73625c52da8a3a183c60be9da6050f59a453caa53520593671728d431877bfaac913a765fb6a56b75290b2a8aaac34afb9217ba1b0d5850ba0fdabf80969def0feee794ceb60614e3368e63ef20e4c32d341ec9b0328ea9fe139207ed7a626ff08943b415233db7cfcc845c9b63121d4ed52ec3748ab6a1f36b2103c7dc7e9303acea4ba8af7a3e07184fb491e891ede84f0dc41cadc3973028e879acd2031afc29a16092868e2c7f539fc1b792edab195a25ab9830661346b39ef53915de4af52c421eaf172e9da76a08c283a52df907f705d7e8599c5baae0c2af380c1bb46f93484a03f28374324b278992b50b7afa02552cafa503f034f8d866e9b720271dd68ccb685a85fffd1: +79c7dcb7d59a8df6b2b2ba0413059d89680995c20e916da01b8f067dc60cdeb4298743c73918bd556b28f8d4824a09b814752a7aeae7ee04875c53f4d6b108d9:298743c73918bd556b28f8d4824a09b814752a7aeae7ee04875c53f4d6b108d9:5fe202f5b33b7788810d2508a13b3114d69b8596e6eacda05a04a2eb597fa3279c208b5a5b65daacb699f144e1d660e78e139b578331abec5c3c35334454f03e832c8d6e2984df5d450ecb5d33582a78808a9c78f26ebcd1244ef52e3fa6dca115c1f0cb56e38eae0e5b39f5fd863dffd0b2fb5b958f2d739db312fc667a17b031c4c9f8c5a2ad577984cc4146c437580efd2152173fe0d5782cc2ae9831a8d9a04177256018ff7631e0b0d8a99cb28f008b320421e27a74c31359188663456d85e098c1ebd281701097b6ae5a871e5ccc02058a501416cb91c12cef5be6f1914370e563f1a1b2aa41f4b8ee84cd32a1d509e529787d14a445438d807ecd620e2fa26de0da6426864784d4a28f54103e609283b99ee9b2b699c980bbb7882c3ea68ddc90802ac232f2c8e84291987bf3c5240921b59cfa214969317673d0be7f34b1ca0e15ea73c7175401ce550be106b49e62f8db68695e740e0f3a3556a19f3c8e6b91ac1cc23e863fcd0f0d9eb7047aa631e0d2eb9bcc6b:7b7cbe44c771e4371bae13b0722babcc1064155732962f407cba2acd35381d42210bece822f4681121fd4dab745a1f3077922fba1a78045b712902baccac660e5fe202f5b33b7788810d2508a13b3114d69b8596e6eacda05a04a2eb597fa3279c208b5a5b65daacb699f144e1d660e78e139b578331abec5c3c35334454f03e832c8d6e2984df5d450ecb5d33582a78808a9c78f26ebcd1244ef52e3fa6dca115c1f0cb56e38eae0e5b39f5fd863dffd0b2fb5b958f2d739db312fc667a17b031c4c9f8c5a2ad577984cc4146c437580efd2152173fe0d5782cc2ae9831a8d9a04177256018ff7631e0b0d8a99cb28f008b320421e27a74c31359188663456d85e098c1ebd281701097b6ae5a871e5ccc02058a501416cb91c12cef5be6f1914370e563f1a1b2aa41f4b8ee84cd32a1d509e529787d14a445438d807ecd620e2fa26de0da6426864784d4a28f54103e609283b99ee9b2b699c980bbb7882c3ea68ddc90802ac232f2c8e84291987bf3c5240921b59cfa214969317673d0be7f34b1ca0e15ea73c7175401ce550be106b49e62f8db68695e740e0f3a3556a19f3c8e6b91ac1cc23e863fcd0f0d9eb7047aa631e0d2eb9bcc6b: +b9ced0412593fefed95e94ac965e5b23ff9d4b0e797db02bf497994d3b793e60c1629a723189959337f5535201e5d395ba0a03ea8c17660d0f8b6f6e6404bb12:c1629a723189959337f5535201e5d395ba0a03ea8c17660d0f8b6f6e6404bb12:555bb39c1899d57cabe428064c2d925f5fc4cf7059b95fb89a8e9e3a7e426c6c922d9e4d76984ea2383cabb4f2befd89c1f20eaa8a00dbe787cfa70ae2ae6aa90331cbbe580fa5a02184ed05e6c8e89d576af28aeeaf7c4e2500f358a00971a0a75920e854849bf332142975404f598c32e96982043d992bcd1a4fe819bb5634ad03467afc4ce05073f88ba1ba4ae8653a04665cf3f71690fe13343885bc5ebc0e5e62d882f43b7c68900ac9438bf4a81ce90169ec129ee63e2c675a1a5a67e27cc798c48cc23f51078f463b3b7cc14e3bcfd2e9b82c75240934cbdc50c4308f282f193122995606f40135100a291c55afdf8934eb8b61d81421674124dec3b88f9a73110a9e616f5b826b9d343f3ac0e9d7bdf4fd8b648b40f0098b3897a3a1cd65a64570059b8bc5c6743883074c88623c1f5a88c58969e21c692aca236833d3470b3eb09815e1138e9d0650c390eee977422193b00918be8a97cc6199b451b05b5730d1d13358cf74610678f7ac7f7895cc2efc456e03873b:f1b797ded8a6942b12626848340fb719fcddafd98f33e2992d357bfdd35933c7ac561e5b2f939464338c5666854ca885c4d046eb2c54e48a1b5ed266ad34de05555bb39c1899d57cabe428064c2d925f5fc4cf7059b95fb89a8e9e3a7e426c6c922d9e4d76984ea2383cabb4f2befd89c1f20eaa8a00dbe787cfa70ae2ae6aa90331cbbe580fa5a02184ed05e6c8e89d576af28aeeaf7c4e2500f358a00971a0a75920e854849bf332142975404f598c32e96982043d992bcd1a4fe819bb5634ad03467afc4ce05073f88ba1ba4ae8653a04665cf3f71690fe13343885bc5ebc0e5e62d882f43b7c68900ac9438bf4a81ce90169ec129ee63e2c675a1a5a67e27cc798c48cc23f51078f463b3b7cc14e3bcfd2e9b82c75240934cbdc50c4308f282f193122995606f40135100a291c55afdf8934eb8b61d81421674124dec3b88f9a73110a9e616f5b826b9d343f3ac0e9d7bdf4fd8b648b40f0098b3897a3a1cd65a64570059b8bc5c6743883074c88623c1f5a88c58969e21c692aca236833d3470b3eb09815e1138e9d0650c390eee977422193b00918be8a97cc6199b451b05b5730d1d13358cf74610678f7ac7f7895cc2efc456e03873b: +81da168f02d46bb87cda845da43f8a6cba2c016878d6f49c6f061a60f155a04aaff86e98093ca4c71b1b804c5fe451cfdf868250dea30345fa4b89bb09b6a53b:aff86e98093ca4c71b1b804c5fe451cfdf868250dea30345fa4b89bb09b6a53b:6bc6726a34a64aae76ab08c92b179e54ff5d2e65eb2c6c659ae8703cc245cbc2cf45a12b22c468ae61fd9a6627ad0626c9b1e5af412cb483eaee1db11b29f0a510c13e38020e09ae0eee762537a3e9d1a0c7b033d097fdc1f4f82629a9de9ef38da1cf96a940357d5f2e0e7e8dbc29db728a1e6aad876e5e053113d06420272b87cf0c40dfe03a544de96c7aea13ba0029b57b48d99dcc6a650492d78c4cdd1b28e1a115a7e3e7a7cb21333d4ff80858dfb67782c16354b8716596560d7d8e389eb15a052a0bf5d16eb54fb3e4973ad4984e72a187f5347d5b262c32b1647e42b6a53837096cc78c2a05ce1c6e12493a03f1a667584cb97f4fcd57ee944c65b7eed25f7ae0f3f6cede173fdfacf5af1db143730d18096664914ba4cfc6966f392022781c66a9417ca2680b51f63e4fba424ecfdbc6a2f01787d0e7484f8a8ab390aeaa6d1f7ed325d82feaa1692a4984fae43da87329b045da8f0a4f56b695aa935de152ce0385153720979a2b7006d405fcb0fba09e23b85fd19b:4aaca947e3f22cc8b8588ee030ace8f6b5f5711c2974f20cc18c3b655b07a5bc1366b59a1708032d12cae01ab794f8cbcc1a330874a75035db1d69422d2fc00c6bc6726a34a64aae76ab08c92b179e54ff5d2e65eb2c6c659ae8703cc245cbc2cf45a12b22c468ae61fd9a6627ad0626c9b1e5af412cb483eaee1db11b29f0a510c13e38020e09ae0eee762537a3e9d1a0c7b033d097fdc1f4f82629a9de9ef38da1cf96a940357d5f2e0e7e8dbc29db728a1e6aad876e5e053113d06420272b87cf0c40dfe03a544de96c7aea13ba0029b57b48d99dcc6a650492d78c4cdd1b28e1a115a7e3e7a7cb21333d4ff80858dfb67782c16354b8716596560d7d8e389eb15a052a0bf5d16eb54fb3e4973ad4984e72a187f5347d5b262c32b1647e42b6a53837096cc78c2a05ce1c6e12493a03f1a667584cb97f4fcd57ee944c65b7eed25f7ae0f3f6cede173fdfacf5af1db143730d18096664914ba4cfc6966f392022781c66a9417ca2680b51f63e4fba424ecfdbc6a2f01787d0e7484f8a8ab390aeaa6d1f7ed325d82feaa1692a4984fae43da87329b045da8f0a4f56b695aa935de152ce0385153720979a2b7006d405fcb0fba09e23b85fd19b: +af2e60da0f29bb1614fc3f193cc353331986b73f3f9a0aec9421b9473d6a4b6ac8bfe2835822199c6127b806fabeef0cb9ff59f3c81ff0cb89c556f55106af6a:c8bfe2835822199c6127b806fabeef0cb9ff59f3c81ff0cb89c556f55106af6a:7dbb77b88bda94f344416a06b096566c6e8b393931a8243a6cab75c361fde7dc536aec40cded83296a89e8c3bef7d787cfc49401a7b9183f138d5000619ff073c05e2f841d6008358f10a2da7dcfac3d4d70c20d2ec34c7b6d5cd1a734d6bbb11c5fd8d2bce32ac810ef82b4188aa8ea3cfc3032233dc0e2600e9db6e18bc22b10044a31c15baceaf5554de89d2a3466807f244414d080ff2963956c6e83c8e144ed0066088b476ddcb564403447d9159f9089aba2b4d5575c4d8ae66fc8690e7349ed40832e6369c024563ec493bfcc0fc9ac787ac841397fe133167283d80c42f006a99d39e82979da3fa9334bd9ede0d14b41b7466bcebbe8171bc804a645d3723274a1b92bf82fd993358744de92441903d436fd47f23d40052a3829367f202f0553b5e49b76c5e03fa6ce7c3cf5eeb21de967bec4dd355925384ebf96697e823762bac4d43a767c241a4cef724a970d00ff3a8ab3b83eed840075c74e90f306e330013260962161e9d0910de183622ce9a6b8d5144280550fc7:50f9f941a8da9f6240f76d2fa3b06dd6b2292ed32d1c05218097d34d8a19dfe553f76ae3c6b4a2ed20852128461540decf418f52d38e64037eec7771bd1afe007dbb77b88bda94f344416a06b096566c6e8b393931a8243a6cab75c361fde7dc536aec40cded83296a89e8c3bef7d787cfc49401a7b9183f138d5000619ff073c05e2f841d6008358f10a2da7dcfac3d4d70c20d2ec34c7b6d5cd1a734d6bbb11c5fd8d2bce32ac810ef82b4188aa8ea3cfc3032233dc0e2600e9db6e18bc22b10044a31c15baceaf5554de89d2a3466807f244414d080ff2963956c6e83c8e144ed0066088b476ddcb564403447d9159f9089aba2b4d5575c4d8ae66fc8690e7349ed40832e6369c024563ec493bfcc0fc9ac787ac841397fe133167283d80c42f006a99d39e82979da3fa9334bd9ede0d14b41b7466bcebbe8171bc804a645d3723274a1b92bf82fd993358744de92441903d436fd47f23d40052a3829367f202f0553b5e49b76c5e03fa6ce7c3cf5eeb21de967bec4dd355925384ebf96697e823762bac4d43a767c241a4cef724a970d00ff3a8ab3b83eed840075c74e90f306e330013260962161e9d0910de183622ce9a6b8d5144280550fc7: +605f90b53d8e4a3b48b97d745439f2a0807d83b8502e8e2979f03e8d376ac9feaa3fae4cfa6f6bfd14ba0afa36dcb1a2656f36541ad6b3e67f1794b06360a62f:aa3fae4cfa6f6bfd14ba0afa36dcb1a2656f36541ad6b3e67f1794b06360a62f:3bcdcac292ac9519024aaecee2b3e999ff5d3445e9f1eb60940f06b91275b6c5db2722ed4d82fe89605226530f3e6b0737b308cde8956184944f388a80042f6cba274c0f7d1192a0a96b0da6e2d6a61b76518fbee555773a414590a928b4cd545fccf58172f35857120eb96e75c5c8ac9ae3add367d51d34ac403446360ec10f553ea9f14fb2b8b78cba18c3e506b2f04097063a43b2d36431cce02caf11c5a4db8c821752e52985d5af1bfbf4c61572e3fadae3ad424acd81662ea5837a1143b9669391d7b9cfe230cffb3a7bb03f6591c25a4f01c0d2d4aca3e74db1997d3739c851f0327db919ff6e77f6c8a20fdd3e1594e92d01901ab9aef194fc893e70d78c8ae0f480001a515d4f9923ae6278e8927237d05db23e984c92a683882f57b1f1882a74a193ab6912ff241b9ffa662a0d47f29205f084dbde845baaeb5dd36ae6439a437642fa763b57e8dbe84e55813f0151e97e5b9de768b234b8db15c496d4bfcfa1388788972bb50ce030bc6e0ccf4fa7d00d343782f6ba8de0:dd0212e63288cbe14a4569b4d891da3c7f92727c5e7f9a801cf9d6827085e7095b669d7d45f882ca5f0745dccd24d87a57181320191e5b7a47c3f7f2dccbd7073bcdcac292ac9519024aaecee2b3e999ff5d3445e9f1eb60940f06b91275b6c5db2722ed4d82fe89605226530f3e6b0737b308cde8956184944f388a80042f6cba274c0f7d1192a0a96b0da6e2d6a61b76518fbee555773a414590a928b4cd545fccf58172f35857120eb96e75c5c8ac9ae3add367d51d34ac403446360ec10f553ea9f14fb2b8b78cba18c3e506b2f04097063a43b2d36431cce02caf11c5a4db8c821752e52985d5af1bfbf4c61572e3fadae3ad424acd81662ea5837a1143b9669391d7b9cfe230cffb3a7bb03f6591c25a4f01c0d2d4aca3e74db1997d3739c851f0327db919ff6e77f6c8a20fdd3e1594e92d01901ab9aef194fc893e70d78c8ae0f480001a515d4f9923ae6278e8927237d05db23e984c92a683882f57b1f1882a74a193ab6912ff241b9ffa662a0d47f29205f084dbde845baaeb5dd36ae6439a437642fa763b57e8dbe84e55813f0151e97e5b9de768b234b8db15c496d4bfcfa1388788972bb50ce030bc6e0ccf4fa7d00d343782f6ba8de0: +9e2c3d189838f4dd52ef0832886874c5ca493983ddadc07cbc570af2ee9d6209f68d3b81e73557ee1f08bd2d3f46a4718256a0f3cd8d2e03eb8fe882aab65c69:f68d3b81e73557ee1f08bd2d3f46a4718256a0f3cd8d2e03eb8fe882aab65c69:19485f5238ba82eadf5eff14ca75cd42e5d56fea69d5718cfb5b1d40d760899b450e66884558f3f25b7c3de9afc4738d7ac09da5dd4689bbfac07836f5e0be432b1ddcf1b1a075bc9815d0debc865d90bd5a0c5f5604d9b46ace816c57694ecc3d40d8f84df0ede2bc4d577775a027f725de0816f563fa88f88e077720ebb6ac02574604819824db7474d4d0b22cd1bc05768e0fb867ca1c1a7b90b34ab7a41afc66957266ac0c915934aaf31c0cf6927a4f03f23285e6f24afd5813849bb08c203ac2d0336dcbf80d77f6cf7120edfbcdf181db107ec8e00f32449c1d3f5c049a92694b4ea2c6ebe5e2b0f64b5ae50ad3374d246b3270057e724a27cf263b633ab65ecb7f5c266b8007618b10ac9ac83db0febc04fd863d9661ab6e58494766f71b9a867c5a7a4555f667c1af2e54588f162a41ce756407cc4161d607b6e0682980934caa1bef036f7330d9eef01ecc553583fee5994e533a46ca916f60f8b961ae01d20f7abf0df6141b604de733c636b42018cd5f1d1ef4f84cee40fc:38a31b6b465084738262a26c065fe5d9e2886bf9dd35cde05df9bad0cc7db401c750aa19e66090bce25a3c721201e60502c8c10454346648af065eab0ee7d80f19485f5238ba82eadf5eff14ca75cd42e5d56fea69d5718cfb5b1d40d760899b450e66884558f3f25b7c3de9afc4738d7ac09da5dd4689bbfac07836f5e0be432b1ddcf1b1a075bc9815d0debc865d90bd5a0c5f5604d9b46ace816c57694ecc3d40d8f84df0ede2bc4d577775a027f725de0816f563fa88f88e077720ebb6ac02574604819824db7474d4d0b22cd1bc05768e0fb867ca1c1a7b90b34ab7a41afc66957266ac0c915934aaf31c0cf6927a4f03f23285e6f24afd5813849bb08c203ac2d0336dcbf80d77f6cf7120edfbcdf181db107ec8e00f32449c1d3f5c049a92694b4ea2c6ebe5e2b0f64b5ae50ad3374d246b3270057e724a27cf263b633ab65ecb7f5c266b8007618b10ac9ac83db0febc04fd863d9661ab6e58494766f71b9a867c5a7a4555f667c1af2e54588f162a41ce756407cc4161d607b6e0682980934caa1bef036f7330d9eef01ecc553583fee5994e533a46ca916f60f8b961ae01d20f7abf0df6141b604de733c636b42018cd5f1d1ef4f84cee40fc: +575f8fb6c7465e92c250caeec1786224bc3eed729e463953a394c9849cba908f71bfa98f5bea790ff183d924e6655cea08d0aafb617f46d23a17a657f0a9b8b2:71bfa98f5bea790ff183d924e6655cea08d0aafb617f46d23a17a657f0a9b8b2:2cc372e25e53a138793064610e7ef25d9d7422e18e249675a72e79167f43baf452cbacb50182faf80798cc38597a44b307a536360b0bc1030f8397b94cbf147353dd2d671cb8cab219a2d7b9eb828e9635d2eab6eb08182cb03557783fd282aaf7b471747c84acf72debe4514524f8447bafccccec0a840feca9755ff9adb60301c2f25d4e3ba621df5ad72100c45d7a4b91559c725ab56bb29830e35f5a6faf87db23001f11ffba9c0c15440302065827a7d7aaaeab7b446abce333c0d30c3eae9c9da63eb1c0391d4269b12c45b660290611ac29c91dbd80dc6ed302a4d191f2923922f032ab1ac10ca7323b5241c5751c3c004ac39eb1267aa10017ed2dac6c934a250dda8cb06d5be9f563b827bf3c8d95fd7d2a7e7cc3acbee92538bd7ddfba3ab2dc9f791fac76cdf9cd6a6923534cf3e067108f6aa03e320d954085c218038a70cc768b972e49952b9fe171ee1be2a52cd469b8d36b84ee902cd9410db2777192e90070d2e7c56cb6a45f0a839c78c219203b6f1b33cb4504c6a7996427741e6874cf45c5fa5a38765a1ebf1796ce16e63ee509612c40f088cbceffa3affbc13b75a1b9c02c61a180a7e83b17884fe0ec0f2fe57c47e73a22f753eaf50fca655ebb19896b827a3474911c67853c58b4a78fd085a23239b9737ef8a7baff11ddce5f2cae0543f8b45d144ae6918b9a75293ec78ea618cd2cd08c971301cdfa0a9275c1bf441d4c1f878a2e733ce0a33b6ecdacbbf0bdb5c3643fa45a013979cd01396962897421129a88757c0d88b5ac7e44fdbd938ba4bc37de4929d53751fbb43d4e09a80e735244acada8e6749f77787f33763c7472df52934591591fb226c503c8be61a920a7d37eb1686b62216957844c43c484e58745775553:903b484cb24bc503cdced844614073256c6d5aa45f1f9f62c7f22e5649212bc1d6ef9eaa617b6b835a6de2beff2faac83d37a4a5fc5cc3b556f56edde2651f022cc372e25e53a138793064610e7ef25d9d7422e18e249675a72e79167f43baf452cbacb50182faf80798cc38597a44b307a536360b0bc1030f8397b94cbf147353dd2d671cb8cab219a2d7b9eb828e9635d2eab6eb08182cb03557783fd282aaf7b471747c84acf72debe4514524f8447bafccccec0a840feca9755ff9adb60301c2f25d4e3ba621df5ad72100c45d7a4b91559c725ab56bb29830e35f5a6faf87db23001f11ffba9c0c15440302065827a7d7aaaeab7b446abce333c0d30c3eae9c9da63eb1c0391d4269b12c45b660290611ac29c91dbd80dc6ed302a4d191f2923922f032ab1ac10ca7323b5241c5751c3c004ac39eb1267aa10017ed2dac6c934a250dda8cb06d5be9f563b827bf3c8d95fd7d2a7e7cc3acbee92538bd7ddfba3ab2dc9f791fac76cdf9cd6a6923534cf3e067108f6aa03e320d954085c218038a70cc768b972e49952b9fe171ee1be2a52cd469b8d36b84ee902cd9410db2777192e90070d2e7c56cb6a45f0a839c78c219203b6f1b33cb4504c6a7996427741e6874cf45c5fa5a38765a1ebf1796ce16e63ee509612c40f088cbceffa3affbc13b75a1b9c02c61a180a7e83b17884fe0ec0f2fe57c47e73a22f753eaf50fca655ebb19896b827a3474911c67853c58b4a78fd085a23239b9737ef8a7baff11ddce5f2cae0543f8b45d144ae6918b9a75293ec78ea618cd2cd08c971301cdfa0a9275c1bf441d4c1f878a2e733ce0a33b6ecdacbbf0bdb5c3643fa45a013979cd01396962897421129a88757c0d88b5ac7e44fdbd938ba4bc37de4929d53751fbb43d4e09a80e735244acada8e6749f77787f33763c7472df52934591591fb226c503c8be61a920a7d37eb1686b62216957844c43c484e58745775553: