UNPKG

@noble/post-quantum

Version:

Auditable & minimal JS implementation of post-quantum cryptography: FIPS 203, 204, 205, Falcon

806 lines (784 loc) 127 kB
/** * Falcon pq-friendly signature algorithm. * Will change in backwards-incompatible way once FIPS-206 gets finalized. * @module */ /*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */ import { rngAesCtrDrbg256 } from '@noble/ciphers/aes.js'; import { chacha20 } from '@noble/ciphers/chacha.js'; import { FFTCore } from '@noble/curves/abstract/fft.js'; import { invert } from '@noble/curves/abstract/modular.js'; import { bytesToNumberLE, numberToHexUnpadded } from '@noble/curves/utils.js'; import { shake256 } from '@noble/hashes/sha3.js'; import { abytes, bytesToHex, createView, hexToBytes, randomBytes, swap32IfBE, u32, u8, } from '@noble/hashes/utils.js'; import { genCrystals } from "./_crystals.js"; import { baswap64If, cleanBytes, getMask, splitCoder, validateSigOpts, validateVerOpts, } from "./utils.js"; /* FIPS-206 would likely improve the situation with spec. Falcon (non-FIPS) spec is terrible. Two main issues: non-deterministic keys & floats. ## Summary - NIST round3 KATs pass - No interop with other JS libraries, because they are incorrect - No recoverPublicKey: it requires s1, which is calculated from public+s2. Sig only has s2. - Code has verify_recover, but it's unused - Mediocre code quality, primarily because it follows implementation-specific (C lib) tidbits - Samplers are fragile ## Non-deterministic keys Falcon spec doesn't provide enough data to re-create keys from KAT vectors. Spec mentions: > This process reduces the maximum sizes of coefficients of F and G > by about 30 bits at each iteration While actual implementation reduces them by 25 bits (scale_k), which is very important detail. There are also various implementation checks not mentioned in spec, like > let's skip this perfectly valid key, because it doesn't fit into our bigint implementation Without these, it's hard to produce correct keys. This means that, unless NIST specifies full process with all operations, **all keys are implementation-specific**. Which means, we cannot use any key derivation schemes here: same seed will return different keys in different implementations. This also complicates testing a lot. If a key succesfully signs a message and other implementations confirm it, there is still zero assurance with regards to quality / entropy of the key. One can create a valid key, which nevertheless doesn't have enough entropy. ## Floats Partially fixed by "fixed point" primitive. Falcon basically impelements floats on top of u64. It's more constant-time, but in JS there is no **fast** u64: - Using bigint backend would drop const-timeness - Using u32 {hi, low} tuples means unnecessary allocations / jit deopt, and is still 4 times slower than Floats Then, there are rounding issues. This is implementation specific. This matters more for C, since 'double' is not neccesarily binary64. In js, floats guaranteed to be IEEE-754 binary64. In theory floats are nice, but since fixed point format is not specified in spec (other that "it is binary64"), this is even more fragile, since it doesn't implement exact full spec of binary64 (two zeros/subnomarls/nans/etc). Those parts should not be used inside falcon, but may cause some differences. Lack of specification is also hard to debug, brings precision loss (a+b+c !== a+c+b): there are no serialized floats, all float arithmetic happens inside of an algorithm, so we can produce same results (small differences rounded at the end). For byte-to-byte result in falcon, one needs to copy implementation-specific details, unspecced. ## CSPRNG NIST KATs randomness situation is bad: 1. aes-drbg generates seed 2. The seed passes CSPRNG into sign, which uses shake256 to produce another seed and nonce 3. Then a separate rejection sampling chacha20 CSPRNG is created, based on that seed. ## Detached vs non-detached The API is different between detached / non-detached signatures, however only non-detached (sm) is included in KAT, so we implement them (crypto_sign_open instead of crypto_sign_verify). */ // Utils // MSB first. Current Falcon uses are byte-aligned only, and outer wrappers must still enforce // exact body lengths / canonical padding because this helper neither flushes nor rejects a final // partial field on its own. const bitsCoderMSB = (newPoly, N, d, c) => { const mask = getMask(d); const bytesLen = d * (N / 8); return { bytesLen, encode: (poly) => { if (poly.length !== N) throw new Error(`wrong length: expected ${N}, got ${poly.length}`); const r = new Uint8Array(bytesLen); for (let i = 0, buf = 0, bufLen = 0, pos = 0; i < poly.length; i++) { buf = (buf << d) | (c.encode(poly[i]) & mask); bufLen += d; for (; bufLen >= 8; bufLen -= 8) r[pos++] = (buf >>> (bufLen - 8)) & 0xff; } return r; }, decode: (bytes) => { const r = newPoly(N); for (let i = 0, buf = 0, bufLen = 0, pos = 0; i < bytes.length; i++) { buf = (buf << 8) | bytes[i]; bufLen += 8; for (; bufLen >= d; bufLen -= d) r[pos++] = c.decode((buf >>> (bufLen - d)) & mask); } return r; }, }; }; // Adds a single leading tag byte. Exact body validation is delegated to `restCoder.decode()`. // encode() zeroizes the temporary encoded body after copying, so wrapped encoders must return // owned scratch bytes rather than caller-owned buffers. const headerCoder = (tag, restCoder) => { const coder = restCoder; return { bytesLen: 1 + coder.bytesLen, encode(value) { const body = coder.encode(value); const out = new Uint8Array(1 + body.length); out[0] = tag; out.set(body, 1); cleanBytes(body); return out; }, decode(data) { if (data[0] !== tag) throw new Error(`wrong tag: expected ${tag}, got 0x${data[0]}`); return coder.decode(data.subarray(1)); }, }; }; // Fun, but overengineered. Hoping FIPS would fix this. // Falcon-specific Golomb-Rice compressed format: // Vec<[1bit sign, 7 bit low, array(1 terminated).length==high <<7]>. // decode() returns only coefficients, so callers must still enforce exact consumed length / // canonical framing around the payload. const compCoder = (n) => { const LIMIT = 2047; return { encode(data) { // Algorithm 17: Compress(s, slen) (Page 47) // Require: A polynomial s = Σ sᵢxⁱ ∈ Z[x] of degree < n, a string bitlength slen // Ensure: A compressed representation str of s of slen bits, or ⊥ // 1: str ← {} ▷ str is the empty string // 2: for i from 0 to n-1 do ▷ At each step, str ← (str||strᵢ), where strᵢ encodes sᵢ // 3: str ← (str||b), where b = 1 if sᵢ < 0, b = 0 otherwise ▷ Encode the sign of sᵢ // 4: str ← (str||b₆b₅...b₀), where bⱼ = (|sᵢ| >> j) & 0x1 // ▷ Encode in binary the low bits of |sᵢ| // 5: k ← |sᵢ| >> 7 // 6: str ← (str||0ᵏ1) ▷ Encode in unary the high bits of |sᵢ| // 7: if |str| > slen then // 8: str ← ⊥ ▷ Abort if str is too long // 9: else // 10: str ← (str||0^{slen-|str|}) ▷ Pad str to slen bits // 11: return str if (data.length !== n) throw new Error('wrong length'); const res = []; let buf = 0; let bufLen = 0; const writeBits = (n, v) => { bufLen += n; buf = (buf << n) | v; // flush buffer if bigger than byte for (; bufLen >= 8; buf &= getMask(bufLen)) { bufLen -= 8; res.push((buf >>> bufLen) & 0xff); } }; for (let i = 0; i < n; i++) { let v = data[i]; if (!Number.isInteger(v) || v < -LIMIT || v > LIMIT) throw new Error(`data[${i}]=${v} out of range`); const sign = v < 0 ? 1 : 0; v = Math.abs(v); writeBits(1, sign); writeBits(7, v & 0b0111_1111); // low writeBits((v >>> 7) + 1, 1); // high (unary) } if (bufLen > 0) res.push((buf << (8 - bufLen)) & 0xff); return new Uint8Array(res); }, decode(data) { // Algorithm 18: Decompress(str, slen), (Page 48) // Require: A bitstring str = (str[i])_{i=0,...,slen-1}, a bitlength slen // Ensure: A polynomial s = Σ sᵢxⁱ ∈ Z[x], or ⊥ // 1: if |str| ≠ slen then ▷ Enforce fixed bitlength // 2: return ⊥ // 3: for i from 0 to (n-1) do // 4: s'ᵢ ← Σ_{j=0 to 6} 2⁶⁻ʲ · str[1 + j] ▷ We recover the lowest bits of |sᵢ|. // 5: k ← 0 // 6: while str[8 + k] = 0 do ▷ We recover the highest bits of |sᵢ|. // 7: k ← k + 1 // 8: sᵢ ← (-1)^{str[0]} · (s'ᵢ + 2⁷k) ▷ We recompute sᵢ. // 9: if (sᵢ = 0) and (str[0] = 1) then ▷ Enforce unique encoding if sᵢ = 0 // 10: return ⊥ // 11: str ← str with first 9 + k bits removed ▷ We remove the bits of str that encode sᵢ. // 12: if str contains any non-zero bits then ▷ Enforce trailing bits at 0 // 13: return ⊥ // 14: return s = Σ_{i=0}^{n-1} sᵢxⁱ const res = new Int16Array(n); let buf = 0; let bufLen = 0; let pos = 0; const readBits = (n) => { for (; bufLen < n && pos < data.length; bufLen += 8) buf = (buf << 8) | data[pos++]; if (bufLen < n) throw new Error(`end of buffer: len=${bufLen} buf=${buf} lastByte=${data[pos]}`); bufLen -= n; const val = buf >>> bufLen; buf &= getMask(bufLen); return val; }; for (let resPos = 0; resPos < n; resPos++) { const sign = readBits(1); const low = readBits(7); let high = 0; for (; !readBits(1); high++) ; const v = low | (high << 7); if (sign && v === 0) throw new Error('negative zero encoding'); if (v > LIMIT) throw new Error(`limit: ${v} > ${LIMIT}`); res[resPos] = sign ? -v : v; } if (buf) throw new Error('non-empty accumulator'); return res; }, }; }; // Falcon padded-signature helper. encode() assumes `data.length <= len`; decode() strips trailing // zero padding and returns a subarray view, so it is not a generic byte-string codec. const pad = (len) => ({ encode(data) { const res = new Uint8Array(len); res.set(data); return res; }, decode(data) { let end = data.length; while (end > 0 && data[end - 1] === 0) end--; return data.subarray(0, end); }, }); // Zero complex-polynomial temporaries in place. Requires fully initialized `{ re, im }` entries. const cleanCPoly = (...list) => { for (const p of list) { for (let i = 0; i < p.length; i++) { p[i].re = 0; p[i].im = 0; } } }; // Generic complex helper used by Falcon's FFT code. Current audited use relies on // add/sub/mul/conj/scale/magSqSum/neg; inv() is intentionally unimplemented. function getComplex(field) { const F = field; return { lift: (x) => { // Reuse existing complex objects verbatim; callers that need isolation must clone first. if (x.re !== undefined && x.im !== undefined) return x; return { re: x, im: F.ZERO }; }, add: (a, b) => ({ re: F.add(a.re, b.re), im: F.add(a.im, b.im), }), sub: (a, b) => ({ re: F.sub(a.re, b.re), im: F.sub(a.im, b.im), }), mul: (a, b) => ({ re: F.sub(F.mul(a.re, b.re), F.mul(a.im, b.im)), im: F.add(F.mul(a.re, b.im), F.mul(a.im, b.re)), }), div: (a, b) => { const denom = F.add(F.mul(b.re, b.re), F.mul(b.im, b.im)); return { re: F.div(F.add(F.mul(a.re, b.re), F.mul(a.im, b.im)), denom), im: F.div(F.sub(F.mul(a.im, b.re), F.mul(a.re, b.im)), denom), }; }, neg: (a) => ({ re: F.neg(a.re), im: F.neg(a.im) }), conj: (a) => ({ re: a.re, im: F.neg(a.im) }), scale: (a, x) => ({ re: F.mul(a.re, x), im: F.mul(a.im, x), }), // a.re * a.re + a.im * a.im + b.re * b.re + b.im * b.im; magSqSum: (a, b) => F.add(F.add(F.add(F.mul(a.re, a.re), F.mul(a.im, a.im)), F.mul(b.re, b.re)), F.mul(b.im, b.im)), eql: (a, b) => F.eql(a.re, b.re) && F.eql(a.im, b.im), clone: (a) => ({ re: a.re, im: a.im }), inv: () => { throw new Error('not implemented'); }, }; } // Falcon real-polynomial FFT layout: [...re, ...im]. Requires an even-length flat array and // copies into fresh JS objects / arrays instead of creating views. const ComplexArr = { decode(lst) { const N = lst.length; const hn = N >> 1; const len = lst.length; if (len === 0) return []; if (len % 2 !== 0) throw new Error('Array length must be even to pair real and imaginary parts.'); const res = []; for (let i = 0; i < hn; i++) { res.push({ re: lst[i], im: lst[i + hn] }); } return res; }, encode(lst) { const re = []; const im = []; for (const i of lst) { re.push(i.re); im.push(i.im); } return [...re, ...im]; }, }; // Precomputed root-table layout [re[0], im[0], re[1], im[1], ...]. Used for `COMPLEX_ROOTS`, // not Falcon's packed polynomial FFT layout; encode() is currently unused. // decode() / encode() copy between the flat root table // and detached `{ re, im }` objects; they never create aliasing views. const ComplexArrInterleaved = { decode(lst) { const len = lst.length; if (len === 0) return []; if (len % 2 !== 0) throw new Error('Array length must be even to pair real and imaginary parts.'); const res = []; // Iterate through the list, taking two elements at a time for (let i = 0; i < len; i += 2) { res.push({ re: lst[i], im: lst[i + 1] }); } return res; }, encode(lst) { const res = []; for (const complexNum of lst) { res.push(complexNum.re); res.push(complexNum.im); } return res; }, }; // Alias a Float64Array as bytes for the root-table hash pin; not a portable serialization. const u8f = (arr) => new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength); // Alias bytes as Float64Array lanes. Falcon's exact binary64 tables are stored as little-endian // payload bytes, so BE runtimes must decode lane-by-lane instead of aliasing host-endian floats. // Copy/truncate to whole 8-byte lanes first // so BE byte swaps cannot mutate caller-owned bytes // or read a partial float. const f64a = (arr) => new Float64Array(baswap64If(Uint8Array.from(arr.subarray(0, Math.floor(arr.byteLength / 8) * 8))).buffer); // Exact big-endian binary64 hex helper for constants. Only decode() is currently used; malformed // inputs fail through lower-level hex / DataView checks instead of an explicit wrapper guard. const Float = /* @__PURE__ */ Object.freeze({ encode(n) { const bytes = new Uint8Array(8); const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); view.setFloat64(0, n, false); return bytesToHex(bytes); }, decode(s) { const bytes = hexToBytes(s); const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); return view.getFloat64(0, false); }, }); // Decode a 64-bit bigint bit pattern into the exact binary64 value. const f64b = (n) => Float.decode(numberToHexUnpadded(n)); // Constants const EMPTY_CHACHA20_BLOCK = /** @__PURE__ */ new Uint8Array(64); // Falcon's randomized hashing salt r is always 320 bits / 40 bytes, and the same width // also drives the detached and attached signature wire formats. const NONCELEN = 40; // Falcon's public modulus q = 12289 is also the NTT parameter chosen in round 3. const Q = 12289; // 12 * 1024 + 1 // Falcon's midpoint floor(q/2); the only live use is the mirrored G-reconstruction reduction below. const Qhalf = Q >> 1; const QBig = BigInt(Q); //const R = 4091; // 2^16 mod q // This 16-bit Montgomery kernel uses R = 2^16, so mul(x, R2) converts x into Montgomery form. const R2 = 10952; // 2^32 mod q // falcon.pdf page 55 says "1/q mod 2^16", // but the reduction formula and the round-3 Falcon code both require -1/q. const Q0I = 12287; // -1/q mod 2^16 const F_INV_Q = 1.0 / Q; const F_MINUS_INV_Q = -F_INV_Q; // Round-3 bigint keygen keeps these tables coupled: MAX_BL_SMALL and MAX_BL_LARGE are measured // 31-bit word bounds, and BITLENGTH is the measured avg/stddev heuristic. Edits must recheck the // next-depth relation and the current 31 * wordCount headroom used by reduce(). const MAX_BL_SMALL = [1, 1, 2, 2, 4, 7, 14, 27, 53, 106, 209]; // Unreduced F/G word bounds for reduce(); the same round-3 source also couples this table to the // top-10-word floating approximation there, // so it must stay in sync with MAX_BL_SMALL and BITLENGTH. const MAX_BL_LARGE = [2, 2, 5, 7, 12, 21, 40, 78, 157, 308]; // Exact binary64 encoding of Falcon's Gram-Schmidt keygen bound (1.17^2) * q = 16822.4121. const BNORM_MAX = f64b(BigInt('4670353323383631276')); // Measured round-3 bigint-keygen heuristic, not a normative Falcon parameter table or proof bound. const BITLENGTH = [ { avg: 4, std: 0 }, { avg: 11, std: 1 }, { avg: 24, std: 1 }, { avg: 50, std: 1 }, { avg: 102, std: 1 }, { avg: 202, std: 2 }, { avg: 401, std: 4 }, { avg: 794, std: 5 }, { avg: 1577, std: 8 }, { avg: 3138, std: 13 }, { avg: 6308, std: 25 }, ]; // First entry is P(x = 0); the remaining entries are conditional tail thresholds scaled by 2^63. // Smaller Falcon dimensions reuse the N = 1024, q = 12289 table by summing 2^(10-logn) draws. // The trailing 0 sentinel guarantees gaussSingle() // always selects a tail bucket when x = 0 is missed. const gauss_1024_12289 = [ 1283868770400643928n, 6416574995475331444n, 4078260278032692663n, 2353523259288686585n, 1227179971273316331n, 575931623374121527n, 242543240509105209n, 91437049221049666n, 30799446349977173n, 9255276791179340n, 2478152334826140n, 590642893610164n, 125206034929641n, 23590435911403n, 3948334035941n, 586753615614n, 77391054539n, 9056793210n, 940121950n, 86539696n, 7062824n, 510971n, 32764n, 1862n, 94n, 4n, 0n, ]; // Exact binary64 1/sigma payloads from round-3 fpr.h. Nearby decimal spellings round 1 ULP low in // JS, so keep these as decoded bit patterns and recheck the raw payloads after edits. const INV_SIGMA = /* @__PURE__ */ Object.freeze([ 0.0, // unused f64b(BigInt('4574611497772390042')), f64b(BigInt('4574501679055810265')), f64b(BigInt('4574396282908341804')), f64b(BigInt('4574245855758572086')), f64b(BigInt('4574103865040221165')), f64b(BigInt('4573969550563515544')), f64b(BigInt('4573842244705920822')), f64b(BigInt('4573721358406441454')), f64b(BigInt('4573606369665796042')), f64b(BigInt('4573496814039276259')), ]); // Exact binary64 sigma_min constants from round-3 fpr.h indexed by logn; despite one PQClean // summary comment, these are sigma_min itself, not 1/sigma_min, which is why this table stays // separate from INV_SIGMA. const SIGMA_MIN = /* @__PURE__ */ Object.freeze([ 0.0, // unused f64b(BigInt('4607707126469777035')), f64b(BigInt('4607777455861499430')), f64b(BigInt('4607846828256951418')), f64b(BigInt('4607949175006100261')), f64b(BigInt('4608049571757433526')), f64b(BigInt('4608148125896792003')), f64b(BigInt('4608244935301382692')), f64b(BigInt('4608340089478362016')), f64b(BigInt('4608433670533905013')), f64b(BigInt('4608525754002622308')), ]); // Falcon Table 3.1 RCDT values for chi, split into 24-bit limbs; storage is [high, mid, low], // so gaussian0() intentionally compares them against v0, v1, v2 in reverse order. The final // RCDT[18] = 0 row is omitted because the algorithm iterates only over i = 0..17. const GAUSS0 = new Uint32Array([ 10745844, 3068844, 3741698, 5559083, 1580863, 8248194, 2260429, 13669192, 2736639, 708981, 4421575, 10046180, 169348, 7122675, 4136815, 30538, 13063405, 7650655, 4132, 14505003, 7826148, 417, 16768101, 11363290, 31, 8444042, 8086568, 1, 12844466, 265321, 0, 1232676, 13644283, 0, 38047, 9111839, 0, 870, 6138264, 0, 14, 12545723, 0, 0, 3104126, 0, 0, 28824, 0, 0, 198, 0, 0, 1, ]); // Inclusive floor(beta^2) signature-acceptance bounds indexed by logn; the Falcon PDF publishes // only Falcon-512 and Falcon-1024 directly, while the smaller rows are mirrored from the round-3 // NIST submission package. const L2BOUND = [ 0, // unused 101498, 208714, 428865, 892039, 1852696, 3842630, 7959734, 16468416, 34034726, 70265242, ]; // 32kb in hex. // Could be 4x smaller by using 2 bytes per root. However, that would mean using sin/cos. // Different JS engines give different sin / cos result, which means the result would be unreliable. // See "COMPLEX ROOT GENERATION FOR FALCON" in tests. // Those exact roots are taken from the round-3 Falcon submission, preserving its original // bit-reversed order here and remapping it only later for FFTCore. const COMPLEX_ROOTS = /** @__PURE__ */ (() => { const roots = f64a(hexToBytes('000000000000000000000000000000000000000000000080000000000000f03fcd3b7f669ea0e63fcd3b7f66' + '9ea0e63fcd3b7f669ea0e6bfcd3b7f669ea0e63f468d32cf6b90ed3f63a9aea6e27dd83f63a9aea6e27dd8bf' + '468d32cf6b90ed3f63a9aea6e27dd83f468d32cf6b90ed3f468d32cf6b90edbf63a9aea6e27dd83fb05cf7cf' + '9762ef3f0ba6693cb8f8c83f0ba6693cb8f8c8bfb05cf7cf9762ef3fc868ae393bc7e13fa3a10e29669bea3f' + 'a3a10e29669beabfc868ae393bc7e13fa3a10e29669bea3fc868ae393bc7e13fc868ae393bc7e1bfa3a10e29' + '669bea3f0ba6693cb8f8c83fb05cf7cf9762ef3fb05cf7cf9762efbf0ba6693cb8f8c83f2625d1a38dd8ef3f' + '2cb429bca617b93f2cb429bca617b9bf2625d1a38dd8ef3fd61d0925f34ce43f4117156b80bce83f4117156b' + '80bce8bfd61d0925f34ce43fb1bd80f1b238ec3f3bf606385d2bde3f3bf606385d2bdebfb1bd80f1b238ec3f' + '069fd52e0694d23fda2dc656419fee3fda2dc656419feebf069fd52e0694d23fda2dc656419fee3f069fd52e' + '0694d23f069fd52e0694d2bfda2dc656419fee3f3bf606385d2bde3fb1bd80f1b238ec3fb1bd80f1b238ecbf' + '3bf606385d2bde3f4117156b80bce83fd61d0925f34ce43fd61d0925f34ce4bf4117156b80bce83f2cb429bc' + 'a617b93f2625d1a38dd8ef3f2625d1a38dd8efbf2cb429bca617b93f7e6d79e321f6ef3f14d80df1651fa93f' + '14d80df1651fa9bf7e6d79e321f6ef3fa0ec8c34697de53fafaf6a22dfb5e73fafaf6a22dfb5e7bfa0ec8c34' + '697de53f73c73cf47aedec3fc05ce109105ddb3fc05ce109105ddbbf73c73cf47aedec3fdd1fab759a8fd53f' + 'e586f6042121ee3fe586f6042121eebfdd1fab759a8fd53fd73092fb7e0aef3f1b5f217bf919cf3f1b5f217b' + 'f919cfbfd73092fb7e0aef3feeff22998773e03f3e6e19458372eb3f3e6e19458372ebbfeeff22998773e03f' + '4187f347e0b3e93f3570e1fcf70fe33f3570e1fcf70fe3bf4187f347e0b3e93f3a618e6e10c8c23f17a5087f' + '55a7ef3f17a5087f55a7efbf3a618e6e10c8c23f17a5087f55a7ef3f3a618e6e10c8c23f3a618e6e10c8c2bf' + '17a5087f55a7ef3f3570e1fcf70fe33f4187f347e0b3e93f4187f347e0b3e9bf3570e1fcf70fe33f3e6e1945' + '8372eb3feeff22998773e03feeff22998773e0bf3e6e19458372eb3f1b5f217bf919cf3fd73092fb7e0aef3f' + 'd73092fb7e0aefbf1b5f217bf919cf3fe586f6042121ee3fdd1fab759a8fd53fdd1fab759a8fd5bfe586f604' + '2121ee3fc05ce109105ddb3f73c73cf47aedec3f73c73cf47aedecbfc05ce109105ddb3fafaf6a22dfb5e73f' + 'a0ec8c34697de53fa0ec8c34697de5bfafaf6a22dfb5e73f14d80df1651fa93f7e6d79e321f6ef3f7e6d79e3' + '21f6efbf14d80df1651fa93f0dcd846088fdef3f7e66a3f75521993f7e66a3f7552199bf0dcd846088fdef3f' + 'df2c1d55b710e63f96ffef37082de73f96ffef37082de7bfdf2c1d55b710e63f3ac94dd13441ed3f8aeda843' + '79efd93f8aeda84379efd9bf3ac94dd13441ed3f9f45fa308508d73f3cc2ccb613dbed3f3cc2ccb613dbedbf' + '9f45fa308508d73f89e564acf338ef3f634f7e6a820bcc3f634f7e6a820bccbf89e564acf338ef3f234b1b54' + 'b31ee13f000215580a09eb3f000215580a09ebbf234b1b54b31ee13f822746a0a729ea3fdf12dd4c056de23f' + 'df12dd4c056de2bf822746a0a729ea3fc63f8b4414e2c53fa94b71fa6487ef3fa94b71fa6487efbfc63f8b44' + '14e2c53fd39fe17064c2ef3f0e73a9564e56bf3f0e73a9564e56bfbfd39fe17064c2ef3fb9502029faafe33f' + 'fb639249223ae93ffb639249223ae9bfb9502029faafe33f2a956facc0d7eb3fba9af8dba48bdf3fba9af8db' + 'a48bdfbf2a956facc0d7eb3f77f6b162d211d13f634968e740d7ee3f634968e740d7eebf77f6b162d211d13f' + '12e148ec8862ee3f016617945c13d43f016617945c13d4bf12e148ec8862ee3f5ec431996ec6dc3ff5113421' + '4b95ec3ff51134214b95ecbf5ec431996ec6dc3f6e97ff0b0e3be83fe9e5e3bbcae6e43fe9e5e3bbcae6e4bf' + '6e97ff0b0e3be83ff619ce9220d5b23f3a8801adcde9ef3f3a8801adcde9efbff619ce9220d5b23f3a8801ad' + 'cde9ef3ff619ce9220d5b23ff619ce9220d5b2bf3a8801adcde9ef3fe9e5e3bbcae6e43f6e97ff0b0e3be83f' + '6e97ff0b0e3be8bfe9e5e3bbcae6e43ff51134214b95ec3f5ec431996ec6dc3f5ec431996ec6dcbff5113421' + '4b95ec3f016617945c13d43f12e148ec8862ee3f12e148ec8862eebf016617945c13d43f634968e740d7ee3f' + '77f6b162d211d13f77f6b162d211d1bf634968e740d7ee3fba9af8dba48bdf3f2a956facc0d7eb3f2a956fac' + 'c0d7ebbfba9af8dba48bdf3ffb639249223ae93fb9502029faafe33fb9502029faafe3bffb639249223ae93f' + '0e73a9564e56bf3fd39fe17064c2ef3fd39fe17064c2efbf0e73a9564e56bf3fa94b71fa6487ef3fc63f8b44' + '14e2c53fc63f8b4414e2c5bfa94b71fa6487ef3fdf12dd4c056de23f822746a0a729ea3f822746a0a729eabf' + 'df12dd4c056de23f000215580a09eb3f234b1b54b31ee13f234b1b54b31ee1bf000215580a09eb3f634f7e6a' + '820bcc3f89e564acf338ef3f89e564acf338efbf634f7e6a820bcc3f3cc2ccb613dbed3f9f45fa308508d73f' + '9f45fa308508d7bf3cc2ccb613dbed3f8aeda84379efd93f3ac94dd13441ed3f3ac94dd13441edbf8aeda843' + '79efd93f96ffef37082de73fdf2c1d55b710e63fdf2c1d55b710e6bf96ffef37082de73f7e66a3f75521993f' + '0dcd846088fdef3f0dcd846088fdefbf7e66a3f75521993fdb929b1662ffef3f84c7defcd121893f84c7defc' + 'd12189bfdb929b1662ffef3f3d78f0251959e63fafa8ea5444e7e63fafa8ea5444e7e6bf3d78f0251959e63f' + '8be6c9736169ed3fd793bc632a37d93fd793bc632a37d9bf8be6c9736169ed3fe7cc1d31a9c3d73f9ba03862' + '52b6ed3f9ba0386252b6edbfe7cc1d31a9c3d73f2d2f0b3b604eef3f5104b025a082ca3f5104b025a082cabf' + '2d2f0b3b604eef3f49dbde634d73e13f11d5219ebcd2ea3f11d5219ebcd2eabf49dbde634d73e13fe2fa021b' + '0963ea3f59eb3399791ae23f59eb3399791ae2bfe2fa021b0963ea3f31bf50ded96dc73f7720a1a39975ef3f' + '7720a1a39975efbf31bf50ded96dc73f7ba66dfd15ceef3fd5c29ec78537bc3fd5c29ec78537bcbf7ba66dfd' + '15ceef3fd4564553d9fee33f0d94efa3ccfbe83f0d94efa3ccfbe8bfd4564553d9fee33f49557226c408ec3f' + 'd678ef5219dcde3fd678ef5219dcdebf49557226c408ec3f3edb4c3f44d3d13f740bdfc8d8bbee3f740bdfc8' + 'd8bbeebf3edb4c3f44d3d13f0dd14cab7b81ee3f5281e1c21054d33f5281e1c21054d3bf0dd14cab7b81ee3f' + '89e3865b7779dd3f9b7388348b67ec3f9b7388348b67ecbf89e3865b7779dd3fbf2eba0f407ce83f39099b9b' + '449ae43f39099b9b449ae4bfbf2eba0f407ce83f19a49a0ad0f6b53f095bbdfccae1ef3f095bbdfccae1efbf' + '19a49a0ad0f6b53fad718e6595f0ef3fe020f8796e65af3fe020f8796e65afbfad718e6595f0ef3f9655a392' + '8232e53f711757e3ecf8e73f711757e3ecf8e7bf9655a3928232e53f5cfcfcf3f0c1ec3fe71e01d84912dc3f' + 'e71e01d84912dcbf5cfcfcf3f0c1ec3f6ae77842e2d1d43f7ec12b4b6a42ee3f7ec12b4b6a42eebf6ae77842' + 'e2d1d43fc273e4a378f1ee3faefd370eb84fd03faefd370eb84fd0bfc273e4a378f1ee3fb73e4c87fc1ce03f' + 'd2903567aaa5eb3fd2903567aaa5ebbfb73e4c87fc1ce03f42d7c7f47e77e93ff35906b15860e33ff35906b1' + '5860e3bf42d7c7f47e77e93f77f5dacef039c13f41d7957179b5ef3f41d7957179b5efbf77f5dacef039c13f' + '9b09c924f997ef3f5a3e29b17655c43f5a3e29b17655c4bf9b09c924f997ef3feaf3fa25dbbee23f94af29ef' + '43efe93f94af29ef43efe9bfeaf3fa25dbbee23f1257f53e4d3eeb3f8f895d4d70c9e03f8f895d4d70c9e0bf' + '1257f53e4d3eeb3f114345e54f93cd3fda3a76f75222ef3fda3a76f75222efbf114345e54f93cd3f2bbe2d62' + 'aefeed3fc6273fdd7d4cd63fc6273fdd7d4cd6bf2bbe2d62aefeed3fca3f6d2bc8a6da3fdc353e74e717ed3f' + 'dc353e74e717edbfca3f6d2bc8a6da3f6172035fe771e73f8c0165be7bc7e53f8c0165be7bc7e5bf6172035f' + 'e771e73fcd55947565d8a23f5df7feef72faef3f5df7feef72faefbfcd55947565d8a23f5df7feef72faef3f' + 'cd55947565d8a23fcd55947565d8a2bf5df7feef72faef3f8c0165be7bc7e53f6172035fe771e73f6172035f' + 'e771e7bf8c0165be7bc7e53fdc353e74e717ed3fca3f6d2bc8a6da3fca3f6d2bc8a6dabfdc353e74e717ed3f' + 'c6273fdd7d4cd63f2bbe2d62aefeed3f2bbe2d62aefeedbfc6273fdd7d4cd63fda3a76f75222ef3f114345e5' + '4f93cd3f114345e54f93cdbfda3a76f75222ef3f8f895d4d70c9e03f1257f53e4d3eeb3f1257f53e4d3eebbf' + '8f895d4d70c9e03f94af29ef43efe93feaf3fa25dbbee23feaf3fa25dbbee2bf94af29ef43efe93f5a3e29b1' + '7655c43f9b09c924f997ef3f9b09c924f997efbf5a3e29b17655c43f41d7957179b5ef3f77f5dacef039c13f' + '77f5dacef039c1bf41d7957179b5ef3ff35906b15860e33f42d7c7f47e77e93f42d7c7f47e77e9bff35906b1' + '5860e33fd2903567aaa5eb3fb73e4c87fc1ce03fb73e4c87fc1ce0bfd2903567aaa5eb3faefd370eb84fd03f' + 'c273e4a378f1ee3fc273e4a378f1eebfaefd370eb84fd03f7ec12b4b6a42ee3f6ae77842e2d1d43f6ae77842' + 'e2d1d4bf7ec12b4b6a42ee3fe71e01d84912dc3f5cfcfcf3f0c1ec3f5cfcfcf3f0c1ecbfe71e01d84912dc3f' + '711757e3ecf8e73f9655a3928232e53f9655a3928232e5bf711757e3ecf8e73fe020f8796e65af3fad718e65' + '95f0ef3fad718e6595f0efbfe020f8796e65af3f095bbdfccae1ef3f19a49a0ad0f6b53f19a49a0ad0f6b5bf' + '095bbdfccae1ef3f39099b9b449ae43fbf2eba0f407ce83fbf2eba0f407ce8bf39099b9b449ae43f9b738834' + '8b67ec3f89e3865b7779dd3f89e3865b7779ddbf9b7388348b67ec3f5281e1c21054d33f0dd14cab7b81ee3f' + '0dd14cab7b81eebf5281e1c21054d33f740bdfc8d8bbee3f3edb4c3f44d3d13f3edb4c3f44d3d1bf740bdfc8' + 'd8bbee3fd678ef5219dcde3f49557226c408ec3f49557226c408ecbfd678ef5219dcde3f0d94efa3ccfbe83f' + 'd4564553d9fee33fd4564553d9fee3bf0d94efa3ccfbe83fd5c29ec78537bc3f7ba66dfd15ceef3f7ba66dfd' + '15ceefbfd5c29ec78537bc3f7720a1a39975ef3f31bf50ded96dc73f31bf50ded96dc7bf7720a1a39975ef3f' + '59eb3399791ae23fe2fa021b0963ea3fe2fa021b0963eabf59eb3399791ae23f11d5219ebcd2ea3f49dbde63' + '4d73e13f49dbde634d73e1bf11d5219ebcd2ea3f5104b025a082ca3f2d2f0b3b604eef3f2d2f0b3b604eefbf' + '5104b025a082ca3f9ba0386252b6ed3fe7cc1d31a9c3d73fe7cc1d31a9c3d7bf9ba0386252b6ed3fd793bc63' + '2a37d93f8be6c9736169ed3f8be6c9736169edbfd793bc632a37d93fafa8ea5444e7e63f3d78f0251959e63f' + '3d78f0251959e6bfafa8ea5444e7e63f84c7defcd121893fdb929b1662ffef3fdb929b1662ffefbf84c7defc' + 'd121893f928a8e85d8ffef3f710067fef021793f710067fef02179bf928a8e85d8ffef3f10af9184f77ce63f' + '7582c1730dc4e63f7582c1730dc4e6bf10af9184f77ce63ff9ecb8020b7ded3fb0a4c82ea5dad83fb0a4c82e' + 'a5dad8bff9ecb8020b7ded3fc4aa4eb0e320d83f888966a983a3ed3f888966a983a3edbfc4aa4eb0e320d83f' + '849e78b1a258ef3f6643dcf2cbbdc93f6643dcf2cbbdc9bf849e78b1a258ef3fb8b9f2095a9de13fd4c01659' + '32b7ea3fd4c0165932b7eabfb8b9f2095a9de13f9de69f52587fea3f1b86bc8bf0f0e13f1b86bc8bf0f0e1bf' + '9de69f52587fea3fc6649ce86633c83fb7bbf57d3f6cef3fb7bbf57d3f6cefbfc6649ce86633c83f840b2214' + '79d3ef3f035c4924b7a7ba3f035c4924b7a7babf840b221479d3ef3fb16b8e17ff25e43fcc98163345dce83f' + 'cc98163345dce8bfb16b8e17ff25e43fb071a93fde20ec3f1451f8eae083de3f1451f8eae083debfb071a93f' + 'de20ec3f71bbc3abbb33d23f8ea8e7e8b2adee3f8ea8e7e8b2adeebf71bbc3abbb33d23ff2f71d368490ee3f' + '8703ecda22f4d23f8703ecda22f4d2bff2f71d368490ee3f58cc81148fd2dd3f07692b014250ec3f07692b01' + '4250ecbf58cc81148fd2dd3faad44d9a7e9ce83f4773981bb573e43f4773981bb573e4bfaad44d9a7e9ce83f' + '215b5d6a5887b73f56f4f19f53ddef3f56f4f19f53ddefbf215b5d6a5887b73f5c578d0f83f3ef3fe3d7c012' + '8d42ac3fe3d7c0128d42acbf5c578d0f83f3ef3f375197381058e53fb23dc36c83d7e73fb23dc36c83d7e7bf' + '375197381058e53ff6328b89d9d7ec3f01bd0423cfb7db3f01bd0423cfb7dbbff6328b89d9d7ec3f243caf80' + 'd830d53f25ce70e8ea31ee3f25ce70e8ea31eebf243caf80d830d53fec950b0c22feee3ff9eddf1adcdccf3f' + 'f9eddf1adcdccfbfec950b0c22feee3f1a22ae265648e03fe90475d2388ceb3fe90475d2388cebbf1a22ae26' + '5648e03f220dd82ecf95e93f578e0c0d4038e33f578e0c0d4038e3bf220dd82ecf95e93fcf7becd41601c23f' + 'bbcf468e8eaeef3fbbcf468e8eaeefbfcf7becd41601c23fc8b2ad55ce9fef3f148dcdb0db8ec33f148dcdb0' + 'db8ec3bfc8b2ad55ce9fef3f17eae8e380e7e23fd580eaf5b1d1e93fd580eaf5b1d1e9bf17eae8e380e7e23f' + '051492fe8958eb3fe1c51774909ee03fe1c51774909ee0bf051492fe8958eb3f1b1a101eca56ce3f5d20f753' + '8f16ef3f5d20f7538f16efbf1b1a101eca56ce3fac8029ca0c10ee3f93a69e3727eed53f93a69e3727eed5bf' + 'ac8029ca0c10ee3f09407f6c0d02db3f92bdb2fed402ed3f92bdb2fed402edbf09407f6c0d02db3fe5554f57' + '0094e73f50725d2a8da2e53f50725d2a8da2e5bfe5554f570094e73f43cd90d200fca53fdf81dbda71f8ef3f' + 'df81dbda71f8efbf43cd90d200fca53ff8d3f11d25fcef3f01cfd13137699f3f01cfd13137699fbff8d3f11d' + '25fcef3f7470839534ece53f8dd2a88d944fe73f8dd2a88d944fe7bf7470839534ece53f9fefe020b22ced3f' + 'e5a1de27414bda3fe5a1de27414bdabf9fefe020b22ced3f177ec77d9daad63fda47def705eded3fda47def7' + '05ededbf177ec77d9daad63f9d9a08c9c92def3f86b212b38ccfcc3f86b212b38ccfccbf9d9a08c9c92def3f' + '7e8e2abb26f4e03fb4130047cd23eb3fb4130047cd23ebbf7e8e2abb26f4e03f37f9baea950cea3fa89c6227' + '0796e23fa89c62270796e2bf37f9baea950cea3ff2c59785df1bc53fdb41aeffd58fef3fdb41aeffd58fefbf' + 'f2c59785df1bc53f8641e41716bcef3f1d83ba47a072c03f1d83ba47a072c0bf8641e41716bcef3f22ebdf85' + '4188e33fd76d8ee4ef58e93fd76d8ee4ef58e9bf22ebdf854188e33fea8093c4d7beeb3f1012e74bf6e2df3f' + '1012e74bf6e2dfbfea8093c4d7beeb3f90dbdbcfd9b0d03fbc9d5ae282e4ee3fbc9d5ae282e4eebf90dbdbcf' + 'd9b0d03ffc9f72049f52ee3f541057a5b872d43f541057a5b872d4bffc9f72049f52ee3f0b0097497f6cdc3f' + '00b9a069c1abec3f00b9a069c1abecbf0b0097497f6cdc3fcc7ab5331b1ae83f9ba0599fc00ce53f9ba0599f' + 'c00ce5bfcc7ab5331b1ae83fb309d7340144b13fc473b6ec58edef3fc473b6ec58edefbfb309d7340144b13f' + '40392eaff3e5ef3f962027791166b43f962027791166b4bf40392eaff3e5ef3f0400ec45a1c0e43fcc58e91a' + 'c55be83fcc58e91ac55be8bf0400ec45a1c0e43ff33c23528e7eec3f5bdbe9e81620dd3f5bdbe9e81620ddbf' + 'f33c23528e7eec3fb71404faceb3d33f44976adb2772ee3f44976adb2772eebfb71404faceb3d33f84bfc3d3' + 'b2c9ee3f775176d7a072d13f775176d7a072d1bf84bfc3d3b2c9ee3f67d03f960534df3fdd7753e164f0eb3f' + 'dd7753e164f0ebbf67d03f960534df3fa29dd46f161be93f4483c53882d7e33f4483c53882d7e3bfa29dd46f' + '161be93fc99faecb0ec7bd3f21b7fe6c64c8ef3f21b7fe6c64c8efbfc99faecb0ec7bd3f6e3de629a67eef3f' + 'b24af60413a8c63fb24af60413a8c6bf6e3de629a67eef3f1fac98fbd543e23fc89a11c87846ea3fc89a11c8' + '7846eabf1fac98fbd543e23f74143cb404eeea3feb6c33af1549e13feb6c33af1549e1bf74143cb404eeea3f' + '22673def3247cb3fdd92ff85d043ef3fdd92ff85d043efbf22673def3247cb3f600241cbd7c8ed3ff618240f' + '3466d73ff618240f3466d7bf600241cbd7c8ed3fffbd41617193d93fb13ee9526f55ed3fb13ee9526f55edbf' + 'ffbd41617193d93f7a6d17b3420ae73fe91b1ca30335e63fe91b1ca30335e6bf7a6d17b3420ae73ffd0ee3bb' + '36d9923fa1514bb49cfeef3fa1514bb49cfeefbffd0ee3bb36d9923fa1514bb49cfeef3ffd0ee3bb36d9923f' + 'fd0ee3bb36d992bfa1514bb49cfeef3fe91b1ca30335e63f7a6d17b3420ae73f7a6d17b3420ae7bfe91b1ca3' + '0335e63fb13ee9526f55ed3fffbd41617193d93fffbd41617193d9bfb13ee9526f55ed3ff618240f3466d73f' + '600241cbd7c8ed3f600241cbd7c8edbff618240f3466d73fdd92ff85d043ef3f22673def3247cb3f22673def' + '3247cbbfdd92ff85d043ef3feb6c33af1549e13f74143cb404eeea3f74143cb404eeeabfeb6c33af1549e13f' + 'c89a11c87846ea3f1fac98fbd543e23f1fac98fbd543e2bfc89a11c87846ea3fb24af60413a8c63f6e3de629' + 'a67eef3f6e3de629a67eefbfb24af60413a8c63f21b7fe6c64c8ef3fc99faecb0ec7bd3fc99faecb0ec7bdbf' + '21b7fe6c64c8ef3f4483c53882d7e33fa29dd46f161be93fa29dd46f161be9bf4483c53882d7e33fdd7753e1' + '64f0eb3f67d03f960534df3f67d03f960534dfbfdd7753e164f0eb3f775176d7a072d13f84bfc3d3b2c9ee3f' + '84bfc3d3b2c9eebf775176d7a072d13f44976adb2772ee3fb71404faceb3d33fb71404faceb3d3bf44976adb' + '2772ee3f5bdbe9e81620dd3ff33c23528e7eec3ff33c23528e7eecbf5bdbe9e81620dd3fcc58e91ac55be83f' + '0400ec45a1c0e43f0400ec45a1c0e4bfcc58e91ac55be83f962027791166b43f40392eaff3e5ef3f40392eaf' + 'f3e5efbf962027791166b43fc473b6ec58edef3fb309d7340144b13fb309d7340144b1bfc473b6ec58edef3f' + '9ba0599fc00ce53fcc7ab5331b1ae83fcc7ab5331b1ae8bf9ba0599fc00ce53f00b9a069c1abec3f0b009749' + '7f6cdc3f0b0097497f6cdcbf00b9a069c1abec3f541057a5b872d43ffc9f72049f52ee3ffc9f72049f52eebf' + '541057a5b872d43fbc9d5ae282e4ee3f90dbdbcfd9b0d03f90dbdbcfd9b0d0bfbc9d5ae282e4ee3f1012e74b' + 'f6e2df3fea8093c4d7beeb3fea8093c4d7beebbf1012e74bf6e2df3fd76d8ee4ef58e93f22ebdf854188e33f' + '22ebdf854188e3bfd76d8ee4ef58e93f1d83ba47a072c03f8641e41716bcef3f8641e41716bcefbf1d83ba47' + 'a072c03fdb41aeffd58fef3ff2c59785df1bc53ff2c59785df1bc5bfdb41aeffd58fef3fa89c62270796e23f' + '37f9baea950cea3f37f9baea950ceabfa89c62270796e23fb4130047cd23eb3f7e8e2abb26f4e03f7e8e2abb' + '26f4e0bfb4130047cd23eb3f86b212b38ccfcc3f9d9a08c9c92def3f9d9a08c9c92defbf86b212b38ccfcc3f' + 'da47def705eded3f177ec77d9daad63f177ec77d9daad6bfda47def705eded3fe5a1de27414bda3f9fefe020' + 'b22ced3f9fefe020b22cedbfe5a1de27414bda3f8dd2a88d944fe73f7470839534ece53f7470839534ece5bf' + '8dd2a88d944fe73f01cfd13137699f3ff8d3f11d25fcef3ff8d3f11d25fcefbf01cfd13137699f3fdf81dbda' + '71f8ef3f43cd90d200fca53f43cd90d200fca5bfdf81dbda71f8ef3f50725d2a8da2e53fe5554f570094e73f' + 'e5554f570094e7bf50725d2a8da2e53f92bdb2fed402ed3f09407f6c0d02db3f09407f6c0d02dbbf92bdb2fe' + 'd402ed3f93a69e3727eed53fac8029ca0c10ee3fac8029ca0c10eebf93a69e3727eed53f5d20f7538f16ef3f' + '1b1a101eca56ce3f1b1a101eca56cebf5d20f7538f16ef3fe1c51774909ee03f051492fe8958eb3f051492fe' + '8958ebbfe1c51774909ee03fd580eaf5b1d1e93f17eae8e380e7e23f17eae8e380e7e2bfd580eaf5b1d1e93f' + '148dcdb0db8ec33fc8b2ad55ce9fef3fc8b2ad55ce9fefbf148dcdb0db8ec33fbbcf468e8eaeef3fcf7becd4' + '1601c23fcf7becd41601c2bfbbcf468e8eaeef3f578e0c0d4038e33f220dd82ecf95e93f220dd82ecf95e9bf' + '578e0c0d4038e33fe90475d2388ceb3f1a22ae265648e03f1a22ae265648e0bfe90475d2388ceb3ff9eddf1a' + 'dcdccf3fec950b0c22feee3fec950b0c22feeebff9eddf1adcdccf3f25ce70e8ea31ee3f243caf80d830d53f' + '243caf80d830d5bf25ce70e8ea31ee3f01bd0423cfb7db3ff6328b89d9d7ec3ff6328b89d9d7ecbf01bd0423' + 'cfb7db3fb23dc36c83d7e73f375197381058e53f375197381058e5bfb23dc36c83d7e73fe3d7c0128d42ac3f' + '5c578d0f83f3ef3f5c578d0f83f3efbfe3d7c0128d42ac3f56f4f19f53ddef3f215b5d6a5887b73f215b5d6a' + '5887b7bf56f4f19f53ddef3f4773981bb573e43faad44d9a7e9ce83faad44d9a7e9ce8bf4773981bb573e43f' + '07692b014250ec3f58cc81148fd2dd3f58cc81148fd2ddbf07692b014250ec3f8703ecda22f4d23ff2f71d36' + '8490ee3ff2f71d368490eebf8703ecda22f4d23f8ea8e7e8b2adee3f71bbc3abbb33d23f71bbc3abbb33d2bf' + '8ea8e7e8b2adee3f1451f8eae083de3fb071a93fde20ec3fb071a93fde20ecbf1451f8eae083de3fcc981633' + '45dce83fb16b8e17ff25e43fb16b8e17ff25e4bfcc98163345dce83f035c4924b7a7ba3f840b221479d3ef3f' + '840b221479d3efbf035c4924b7a7ba3fb7bbf57d3f6cef3fc6649ce86633c83fc6649ce86633c8bfb7bbf57d' + '3f6cef3f1b86bc8bf0f0e13f9de69f52587fea3f9de69f52587feabf1b86bc8bf0f0e13fd4c0165932b7ea3f' + 'b8b9f2095a9de13fb8b9f2095a9de1bfd4c0165932b7ea3f6643dcf2cbbdc93f849e78b1a258ef3f849e78b1' + 'a258efbf6643dcf2cbbdc93f888966a983a3ed3fc4aa4eb0e320d83fc4aa4eb0e320d8bf888966a983a3ed3f' + 'b0a4c82ea5dad83ff9ecb8020b7ded3ff9ecb8020b7dedbfb0a4c82ea5dad83f7582c1730dc4e63f10af9184' + 'f77ce63f10af9184f77ce6bf7582c1730dc4e63f710067fef021793f928a8e85d8ffef3f928a8e85d8ffefbf' + '710067fef021793f021d6221f6ffef3fbaa4ccbef821693fbaa4ccbef82169bf021d6221f6ffef3f719ca1ea' + 'd18ee63f9ce22fed5cb2e63f9ce22fed5cb2e6bf719ca1ead18ee63f4fa44584c486ed3f44edd5864bacd83f' + '44edd5864bacd8bf4fa44584c486ed3f3f90f3aa6a4fd83f463d8bdd009aed3f463d8bdd009aedbf3f90f3aa' + '6a4fd83f5d6843eda65def3ffa2ab6e9495bc93ffa2ab6e9495bc9bf5d6843eda65def3fbf73131750b2e13f' + '8eb92c7a54a9ea3f8eb92c7a54a9eabfbf73131750b2e13fd25a546e678dea3f7248dc641bdce13f7248dc64' + '1bdce1bfd25a546e678dea3f0418c4271796c83fee3c88567567ef3fee3c88567567efbf0418c4271796c83f' + '9e5ca72d0dd6ef3f5ca824ebb6dfb93f5ca824ebb6dfb9bf9e5ca72d0dd6ef3f80432a5b7f39e43f55461875' + '6acce83f554618756acce8bf80432a5b7f39e43ff1e33149d12cec3f25d83c6da857de3f25d83c6da857debf' + 'f1e33149d12cec3fba545599e663d23f0058e69383a6ee3f0058e69383a6eebfba545599e663d23f306b0136' + 'ec97ee3f2045954e1ac4d23f2045954e1ac4d2bf306b0136ec97ee3fde41a966fffedd3f04c041318344ec3f' + '04c041318344ecbfde41a966fffedd3f881dde1e87ace83fa2322b695a60e43fa2322b695a60e4bf881dde1e' + '87ace83fa130c112874fb83f8c531475fadaef3f8c531475fadaefbfa130c112874fb83fd3beb154dcf4ef3f' + '17835fbd01b1aa3f17835fbd01b1aabfd3beb154dcf4ef3f9f649751c36ae53f33d3e29cb8c6e73f33d3e29c' + 'b8c6e7bf9f649751c36ae53f60a09927b3e2ec3f9356fd14788adb3f9356fd14788adbbf60a09927b3e2ec3f' + 'b467f4124060d53f7a1939448f29ee3f7a1939448f29eebfb467f4124060d53f8c73cf145a04ef3f0238bd80' + '747bcf3f0238bd80747bcfbf8c73cf145a04ef3fb7b831ecf35de03fe992e786667feb3fe992e786667febbf' + 'b7b831ecf35de03fb2062ba4dfa4e93f1fa649ec2124e33f1fa649ec2124e3bfb2062ba4dfa4e93f0934fd4d' + '9964c23fdcfd0ccbfbaaef3fdcfd0ccbfbaaefbf0934fd4d9964c23f91177aac9ba3ef3fa71645f97b2bc33f' + 'a71645f97b2bc3bf91177aac9ba3ef3f1510444bc2fbe23fc275f010d1c2e93fc275f010d1c2e9bf1510444b' + 'c2fbe23f47bcfd148f65eb3f8cb032201189e03f8cb032201189e0bf47bcfd148f65eb3f48e32d466bb8ce3f' + '5f8f89bc9010ef3f5f8f89bc9010efbf48e32d466bb8ce3fd966dc2fa018ee3fb6b39d8be7bed53fb6b39d8b' + 'e7bed5bfd966dc2fa018ee3f7219b31d972fdb3f7b46cee830f8ec3f7b46cee830f8ecbf7219b31d972fdb3f' + 'd297bf07f7a4e73fdf23f7d50190e53fdf23f7d50190e5bfd297bf07f7a4e73f864687a5ba8da73f64911bbb' + '53f7ef3f64911bbb53f7efbf864687a5ba8da73f79a6e29ce0fcef3f1d3be54c4f459c3f1d3be54c4f459cbf' + '79a6e29ce0fcef3f106ae5bd7cfee53f4299078e553ee73f4299078e553ee7bf106ae5bd7cfee53fdcfbcb7b' + 'fc36ed3fc00ab543651dda3fc00ab543651ddabfdcfbcb7bfc36ed3fb60c8a6398d9d63f818d6d0f16e4ed3f' + '818d6d0f16e4edbfb60c8a6398d9d63ff0ae3a5a6833ef3fdd745d53906dcc3fdd745d53906dccbff0ae3a5a' + '6833ef3f57a9d0487209e13ff5a24c2a7416eb3ff5a24c2a7416ebbf57a9d0487209e13f5ea7c0d2261bea3f' + 'ba3c4def8b81e23fba3c4def8b81e2bf5ea7c0d2261bea3fdecb5486007fc53f784bcb37a78bef3f784bcb37' + 'a78befbfdecb5486007fc53f888d0a0f47bfef3f5bb86fade80ec03f5bb86fade80ec0bf888d0a0f47bfef3f' + '2930d6e3239ce33f6c4aace39049e93f6c4aace39049e9bf2930d6e3239ce33f27230dcb54cbeb3fded2245c' + '57b7df3fded2245c57b7dfbf27230dcb54cbeb3fce49174e5be1d03f5186076aebddee3f5186076aebddeebf' + 'ce49174e5be1d03fd36704559d5aee3ff03689dc1043d43ff03689dc1043d4bfd36704559d5aee3f895386c3' + '7f99dc3f49c4b9198fa0ec3f49c4b9198fa0ecbf895386c37f99dc3fff45f5139c2ae83f86a4cc25ccf9e43f' + '86a4cc25ccf9e4bfff45f5139c2ae83f4d44ed74960cb23f0f4130259debef3f0f4130259debefbf4d44ed74' + '960cb23f602d4885eae7ef3f99a2c5129f9db33f99a2c5129f9db3bf602d4885eae7ef3f7f9f586dbcd3e43f' + 'fa83af11714be83ffa83af11714be8bf7f9f586dbcd3e43f139c0287f589ec3f21cde1ae4bf3dc3f21cde1ae' + '4bf3dcbf139c0287f589ec3f71c26ee99be3d33fa7535dc5616aee3fa7535dc5616aeebf71c26ee99be3d33f' + '0990995e83d0ee3f7893c6ef3e42d13f7893c6ef3e42d1bf0990995e83d0ee3fa3cd56e6de5fdf3fc1541161' + '1be4eb3fc15411611be4ebbfa3cd56e6de5fdf3f15a8c51fa42ae93f18c58149c4c3e33f18c58149c4c3e3bf' + '15a8c51fa42ae93f3faae4fdb78ebe3ff69a7d3b6ec5ef3ff69a7d3b6ec5efbf3faae4fdb78ebe3f0cc6404a' + '0f83ef3f0d831d831a45c63f0d831d831a45c6bf0cc6404a0f83ef3f1071bb4c7358e23fc63b594a1838ea3f' + 'c63b594a1838eabf1071bb4c7358e23fb6579fd88ffbea3f4f25eecfe933e13f4f25eecfe933e1bfb6579fd8' + '8ffbea3fad5df13463a9cb3f65bc1bbc6b3eef3f65bc1bbc6b3eefbfad5df13463a9cb3f5a918af3fed1ed3f' + '921026c96337d73f921026c96337d7bf5a918af3fed1ed3ff2f90d447dc1d93f2475181b5b4bed3f2475181b' + '5b4bedbff2f90d447dc1d93fbf410e96ac1be73fff22ec4fe422e63fff22ec4fe422e6bfbf410e96ac1be73f' + '26b2fa214dfd953f77cb70681cfeef3f77cb70681cfeefbf26b2fa214dfd953fd13bc54309ffef3fcb97b96a' + '296a8f3fcb97b96a296a8fbfd13bc54309ffef3f5b537f431547e63f755bc999caf8e63f755bc999caf8e6bf' + '5b537f431547e63f7f8a8872715fed3f8f94abb75565d93f8f94abb75565d9bf7f8a8872715fed3faedf13e6' + 'f594d73f9a7595439ebfed3f9a7595439ebfedbfaedf13e6f594d73fb4abbc062249ef3fabb9f3d5f1e4ca3f' + 'abb9f3d5f1e4cabfb4abbc062249ef3fbce2dbe4365ee13fefec45f368e0ea3fefec45f368e0eabfbce2dbe4' + '365ee13f23f59010c954ea3fe2132c662d2fe23fe2132c662d2fe2bf23f59010c954ea3fffc4088dfd0ac73f' + '2a321a9c297aef3f2a321a9c297aefbfffc4088dfd0ac73f5443910347cbef3fc17d303b53ffbc3fc17d303b' + '53ffbcbf5443910347cbef3f8006beea33ebe33ffe5e5743790be93ffe5e5743790be9bf8006beea33ebe33f' + '47b1a1259dfceb3ffef7bf061908df3ffef7bf061908dfbf47b1a1259dfceb3f43f2e8fbf7a2d13fb2f61a4b' + 'cfc2ee3fb2f61a4bcfc2eebf43f2e8fbf7a2d13f5a16a529db79ee3fabb653e3f583d33fabb653e3f583d3bf' + '5a16a529db79ee3f9d60a82bd04cdd3fd7aa9e891573ec3fd7aa9e891573ecbf9d60a82bd04cdd3f95a19a1d' + '0a6ce83ff122675179ade43ff122675179ade4bf95a19a1d0a6ce83f0a4d4d4a772eb53f86d8e92be9e3ef3f' + '86d8e92be9e3efbf0a4d4d4a772eb53f9161820201efef3f6430464e617bb03f6430464e617bb0bf91618202' + '01efef3fa69ad91ca81fe53ffa526e758b09e83ffa526e758b09e8bfa69ad91ca81fe53f99da000ae2b6ec3f' + '293126476d3fdc3f293126476d3fdcbf99da000ae2b6ec3ff3821bd153a2d43f5ece81ff8d4aee3f5ece81ff' + '8d4aeebff3821bd153a2d43f44a5504c07ebee3f1e66eb054e80d03f1e66eb054e80d0bf44a5504c07ebee3f' + 'e1822bc84007e03f0dc4b6a049b2eb3f0dc4b6a049b2ebbfe1822bc84007e03fe17fbd423f68e93f8d7f811b' + '5374e33f8d7f811b5374e3bfe17fbd423f68e93f8667b2bc4dd6c03fb7ad668dd1b8ef3fb7ad668dd1b8efbf' + '8667b2bc4dd6c03f08ac854ff193ef3f88fa797fb1b8c43f88fa797fb1b8c4bf08ac854ff193ef3f58eb7ae8' + '76aae23fde4931f1f4fde93fde4931f1f4fde9bf58eb7ae876aae23ff37bf3a51531eb3fb6c44bb8d0dee03f' + 'b6c44bb8d0dee0bff37bf3a51531eb3feebd2c4d7731cd3fce0946fc1728ef3fce0946fc1728efbfeebd2c4d' + '7731cd3f9ca59b6ae3f5ed3fcb63ad9c947bd63fcb63ad9c947bd6bf9ca59b6ae3f5ed3f1bf3dbd30c79da3f' + 'e1a4e5c65522ed3fe1a4e5c65522edbf1bf3dbd30c79da3f6447302cc560e73f5c343ee7ded9e53f5c343ee7' + 'ded9e5bf6447302cc560e73f7fc142db8546a13faefd25e455fbef3faefd25e455fbefbf7fc142db8546a13f' + '14c008427cf9ef3f7961f86f396aa43f7961f86f396aa4bf14c008427cf9ef3f48744f260bb5e53f5bb3901b' + 'fb82e73f5bb3901bfb82e7bf48744f260bb5e53fb9d2592f670ded3f09dc5c1273d4da3f09dc5c1273d4dabf' + 'b9d2592f670ded3f02c2885c591dd63f540f28d96607ee3f540f28d96607eebf02c2885c591dd63f084728be' + '7a1cef3f9a09013f16f5cd3f9a09013f16f5cdbf084728be7a1cef3fec858f8705b4e03f2579de09744beb3f' + '2579de09744bebbfec858f8705b4e03f7224b4ed82e0e93fb89b4ed333d3e23fb89b4ed333d3e2bf7224b4ed' + '82e0e93f9348db572ff2c33f29defb7ced9bef3f29defb7ced9befbf9348db572ff2c33f4dd581c60db2ef3f' + 'e724be40899dc13fe724be40899dc1bf4dd581c60db2ef3fe14dc152524ce33f947545f1ae86e93f947545f1' + 'ae86e9bfe14dc152524ce33f5e15d91ffa98eb3f96bded55ae32e03f96bded55ae32e0bf5e15d91ffa98eb3f' + 'd2fdb906181fd03fc0a31ce5d6f7ee3fc0a31ce5d6f7eebfd2fdb906181fd03f85ce75ec333aee3f487019dc' + '6301d53f487019dc6301d5bf85ce75ec333aee3fd9c0ff1715e5db3fa0dec220eeccec3fa0dec220eeccecbf' + 'd9c0ff1715e5db3f8636b0873fe8e73ffc9d15f54f45e53ffc9d15f54f45e5bf8636b0873fe8e73fc98e80f9' + '06d4ad3fed31e11416f2ef3fed31e11416f2efbfc98e80f906d4ad3f0733f72299dfef3f29b1793e1bbfb63f' + '29b1793e1bbfb6bf0733f72299dfef3fff9160300387e43fa11b48e7668ce83fa11b48e7668ce8bfff916030' + '0387e43f5af8fe59ef5bec3fd910fa5c0ca6dd3fd910fa5c0ca6ddbf5af8fe59ef5bec3fafba38b61f24d33f' + '2560ad5b0989ee3f2560ad5b0989eebfafba38b61f24d33f11885b51cfb4ee3fbe27d7838503d23fbe27d783' + '8503d2bf11885b51cfb4ee3f2056f29506b0de3f575e46dcd914ec3f575e46dcd914ecbf2056f29506b0de3f' + '496c489b10ece83f8c103d667212e43f8c10