Solution de cbothamy pour Pain in the Hash

crypto symétrique

9 mai 2025

Table des matières

Dans ce chall du FCSC2023, on nous demande d’entrer 55 messages maximum au format hexa, dont les sha256 xorés font 0. Il ressemble un peu au chall de la même année Le hash qui rit.

Analyse

Dans “le hash qui rit”, on devait trouver au maximum 256 messages dont les hashs xorés faisait une certaine valeur arbitraire. Dans ma solution, j’avais conjecturé qu’en moyenne on trouvait des solutions avec 128 messages. Ici, on passe à un autre niveau, on nous demande maintenant de trouver 55 messages dont les hash xorés font 0.

On peut se référer au write-up du challenge “le hash qui rit” pour la méthode de sélection des hashs pour obtenir un xor arbitraire. Pour obtenir 0, on cherche juste des solutions qui donnent une valeur donnée, valeur qu’on ajoutera à l’ensemble des solutions.

Mais, tirer au hasard des messages en espérant trouver des solutions avec 54 éléments (plus un élement arbitraire pour la valeur cible) ne va pas nous mener très loin (oui, j’ai testé…) Il faut donc trouver une solution pour diminiuer l’entropie des hashs de notre pool dans lequel on va tirer les hashs au hasard…

Ce qui m’a mis sur la piste, c’est qu’on nous demande cette fois de trouver des messages dont les sha256 xorés font 0. Pourquoi 0 ? Il existe un endroit où des gens ont réalisé des centaines de milliers de milliards de calculs de sha256 afin d’obtenir des hashs comportant beaucoup de 0… Ce sont les mineurs de bitcoin… En effet, la difficulté du minage des bitcoins, c’est justement de trouver des sha256 qui commencent par une longue liste de 0 !

Par exemple le sh256 du bloc 785000 est :

00000000000000000004fec580dc9c4beb7a68890c2282ad818f0e93f2b48bbd

La blockchain nous donne des messages et des sha256 (plus précisement des sha256 de sha256) idéaux pour ce challenge.

Solution

On peut se réferer à https://en.bitcoin.it/wiki/Block_hashing_algorithm pour les détails de l’algorithm de hash de la blockchain bitcoin.

J’ai donc téléchargé du site https://blockstream.info/api/block-height/ environ 285000 sha256 de la blockchain bitcoin, à partir du bloc 500000 jusqu’au bloc 785000 pour me remettre dans les conditions d’avril 2023. J’ai ensuite identifié 268 sha256 comportant les 86 premiers bits à 0.

$ head -5 hashes.txt
000000000000000000000043c81703f5a38817c2d3d766a06af1cb36d973c2b6
000000000000000000000326dd645754ea43973bcd512bcbdcedef20ceb07a8d
00000000000000000000002d99f38e592520813d7e7b7b205c5db2880669ef05
00000000000000000000011679cdea1ace135eb018393b0124c22e270a028210
000000000000000000000286cbd921a54936827080c4893cb3f5d3a92f42bd86

Ce qui nous permet de chercher des solutions d’équations à 170 inconnues. Avec un peu de persévérance, on devrait trouver une solution de 54 éléments, plus un élement cible.

Ce que fait le programme suivant.

import os
import sys
import numpy as np
import galois
import itertools
import random

GF = galois.GF(2)

hashlen_bytes = 256//8
hashlen_zerobits=86
hashlen_bits = (hashlen_bytes * 8) - hashlen_zerobits

wanted_size = 54

def hash_as_bits(hash: bytes) -> list:
    n = int(hash, 16)
    fullbits = [1 if n & 1<<pos else 0 for pos in range(hashlen_bytes*8)]
    return fullbits[:hashlen_bits]

## Getting random values can lead to unsolvable equations.
def prepare_equations(hashes):
    A = GF([hash_as_bits(h) for h in hashes]).transpose()
    
    if np.linalg.matrix_rank(A) == hashlen_bits:   
        return True, A
    return False, None

def find_solutions(A, target):

    b = GF(hash_as_bits(target))

    # Solve equations
    x = np.linalg.solve(A, b)

    # x will tell us if value should be included in the solution
    return x

infile=sys.argv[1]

with open(infile) as f:
    hashes=f.read().splitlines()

print(f"Searching for a {wanted_size} elements solution from values of {infile}")
while True:
    hashlist = random.sample(hashes, hashlen_bits)

    ok, A = prepare_equations(hashlist)
    if not ok :
        continue

    for target in hashes:
        if target in hashlist:
            continue

        solutions=find_solutions(A, target)
        size=np.count_nonzero(solutions)

        if size == wanted_size:
            print(f"{wanted_size} elements solution found :")
            for column, bit in enumerate(solutions):
                if int(bit):
                    print(hashlist[column])

            print(target)
            sys.exit()

Quelques heures plus tard, cette solution apparait :

00000000000000000000026d649d0451361f09ddc6ec842585f7b34236cb90e3
00000000000000000000015904ca174b3a91c0a21bc56bc9c2256e8ebd28ca83
0000000000000000000000befb5a97a29ea51ab3c576ec3d119266522692b2b3
0000000000000000000002464f8786d0e92ff5da52f6968984536b4b60335368
0000000000000000000002f43a820df8c871b58980d4f9539a4a883c0e1eaea0
0000000000000000000000f5fbf360acbc188ee6d6b8ad89e4b413d4d255c16d
000000000000000000000316b0152e43d7ee5b6864f68187aee5535a3c999dc4
000000000000000000000069f1b863743ddcdc52ad1db175bb0a7852450453cb
0000000000000000000002e93d78a5e86a2869de8b2dd251b47fa3a6ccbef877
0000000000000000000001a5f726f94055b464f48ba2d3892498e78ad003dbea
00000000000000000000008b80d0529bc20ac2c580f85acd2aed0baf5b82bdf5
0000000000000000000000d3d08a1ebdc54f4aa79d148f8f5b4b1d2445ad2ecb
0000000000000000000002098822b5299710bacd35b2b1e4e23dc50dba517708
0000000000000000000002cf5dc2fae7b543ba10652f9d0bf13e1aa2d6e8096b
0000000000000000000003f23af552266fb2a01d7d8311b24a696a5caaea0ff6
0000000000000000000003e238f2ce2598ee57191ed2cd13a990b6257528b7e4
00000000000000000000002d142973ed07a220bf571360b70b90f4f0a1e739ce
00000000000000000000015b80b2a0848ccefdf5fbf7ad5a24e90f157d751a5a
00000000000000000000031bfa9eb40013b2f114fb7219eae992831ef842555f
0000000000000000000000d0dc5c7e6b5de835f33170326b1df3c963d8d8bdb5
0000000000000000000000f3c07c18949f337fd5dafc908d3b72bd55684b08d2
00000000000000000000038e62bee0a5224228f24dacbec3dc18367bf53d1333
00000000000000000000002d99f38e592520813d7e7b7b205c5db2880669ef05
00000000000000000000014e5dc331e8841eb0f0aed8fd22602bfaf93f7a7640
0000000000000000000000c87731a1d688f3671d44ef862fe0ee2e74e1a845df
00000000000000000000005bd08c9d7691c94c85885985a268b9612491789f96
00000000000000000000024ebf57a2ef0e6bc0b6e42a3f5bcdfb06f204b75116
0000000000000000000002cad71ad3f9056b26ac1b8a77152908fbfcb5732c16
000000000000000000000132b2092267bd8d9325414627c2d0dc3ff5b4e7b2b2
0000000000000000000000250fae6b97e3241d86c65fb5be489875c49032b25b
0000000000000000000003d6533e68faff05636c8cdc0d2b5d68e83dac42176c
00000000000000000000037a40b2ce3fc431cb4a06bebbeb1c9c353893d17cc9
0000000000000000000002e393abfa5f93eaa615b120971402075acaeb5ef633
000000000000000000000377a5a441a41c571dc813076b7e6d97838a062febad
000000000000000000000119207079777b4746167dad0369b4e8f4f053a7e97c
0000000000000000000000e4f4e1ebe93f66e36622b3c72c76b090afbdb94c34
0000000000000000000003ca6d834f403d8aa26f83d502e90ad26189ebf04d27
00000000000000000000000351eae66e03ae45aad1be8f94d1ca5d6fe98b6efa
00000000000000000000033e104780885770e1c55c0432f9f26295ee2f2f6c01
0000000000000000000001bc0f20af28f38472c0eb221137fce12b089c9cd82c
00000000000000000000038d574928738f725a5d41cc67a1cb7566364ed38409
0000000000000000000001906fd2657c2aa87d80a1dd31af693c7507cd31179f
00000000000000000000032a7c267b31b189318ed0f54b9e3f39af186d039d28
000000000000000000000318becb75616410f95c624ef77effb2f9d37d290a9c
0000000000000000000000fa42dc4b10b4d4a505dca5e5f3c5bd7f0d77f71f08
0000000000000000000002c6acac63f69dbc223991fc62d170e6804d83ba9067
00000000000000000000027a631c10317920c8e2c599eaf0713ba69998893525
000000000000000000000310cee30d440c52b5fc399bcdfb094add76b39a8969
000000000000000000000305b5cfcb88f926bb6996263199fcbe35765fec2fc8
0000000000000000000001a31078c4871462ef013fa23ff8b8e67c892e726501
0000000000000000000001df06e4dcd045f0b3d155e6d87bd686fff4a6db2de0
000000000000000000000326dd645754ea43973bcd512bcbdcedef20ceb07a8d
0000000000000000000000a773ce38733b4f36cf2e52581e055e3a4216291256
000000000000000000000207f132d716e2d37fd538c6d91702dfa82a2f827b52
0000000000000000000000db2867af8040293a4a38db547883a053e0852d85f3

Ces 55 sha256 correspondent aux blocs 585370, 716689, 754218, 685650, 622957, 742280, 767653, 722123, 536940, 684822, 593568, 740948, 763576, 707715, 720974, 545100, 679848, 638817, 695896, 718396, 643496, 774041, 751055, 619919, 643346, 773095, 709469, 723599, 637882, 679468, 754800, 557507, 656501, 674498, 764624, 708610, 617809, 768824, 599973, 595237, 775458, 605463, 654882, 541629, 780016, 734419, 561098, 713931, 667136, 718874, 670719, 703446, 622125, 738100, 600225.

On récupère les 55 headers d’origine de la blockchain via l’url https://blockstream.info/api/block/#hash#/header :

0000c0203e66fccb531ef28052639781f523cf74f92f0af583531b000000000000000000a9411472206b97995a59742af12f5f76a4493fa663f551487bb39e8da0712ce102252b5d9b0d1f176c5de798
00c0af2a632e2c3c3cb9f7e269a86b619295f63f4e8d409439aa01000000000000000000c3462aa130762b7fb51293a17ec3f05a85a9ad7b8da6f14f141e75007c5aa38a6d3dd061ab980b1730518e72
0440ba2e0f632a3f0985826fd1079a464d29a71398bdf0e9763e08000000000000000000fe1d780ba75fd37e694d7c61530a4458733f66a39f33808c574b228fe9dbcc919140236394c808173a3e05f5
040020204f41057879733109fa3281a1906b49d919acbf293dd90300000000000000000023750d7e7476376bc9776be2a9bf4ed4613939e5fcee4493a70361290bb235c30cd6b4607b5f0d177c69a17e
0000c0204b11dcbffb0efa6e596bc86279fd0aa2b8a69b6bc64107000000000000000000f0186cb448802474314cbd9290f2e62ff4888911aaf2334b1e424289fad280137a2e7c5e413b141744e616ab
0000e0206070a097d956944cc7d95eda15bf2573ae54c76eba130900000000000000000096c9047d486a7693282ab98f9e7c3be7821c4ca7f729f111b0100e0ee03d4bcce808b762cc84091726f1ea7b
0000002064f23ffb855781e56377cfc54a43bed3f6ea114c8006050000000000000000005435bbaf1e8028feac82996679ea1b39ac646e05071ac7197a99bc93b6b87b7f633f9c633038081755453c02
0400e0209161c535b98123464e7f2c3356bb3012e5916821e25402000000000000000000ee8d248b67949ad512f43c0906bb598211a92a55792601aa6e2e02a5459d2dbb1c700062b48b0a1711962817
00000020cb5e514cc04859f206afe81c823c3410ddd8eaea37ba14000000000000000000835e704d000bb42a31332cff36eb271a294f224a439ce22cfc2289197db40fdd5ea4745ba70d2c172b8a6683
0400c02082d51f4f5aa66f521f32bd67dfbb69cd44480e9815b8070000000000000000002c4535c5c77969f878ef4a99dc5dadabb9c436089108d28e10ed2fcfdd3a178ec614ac60e93c0b1712cc18ac
00000020bc068ae826c876d3530c2ac7e468e9f8c2a0e7807c53050000000000000000001ac36f585c00430d8893e18f2b2f7ef5dc50fea7d318bf29b498f541d8fdce60aaad725d3e211a171f0fb5d6
000040202f4670183aec3c6bd99a1861c7cb62c14c6cdb47581509000000000000000000df7acf80aa41d7ab18a591baec9e5fa162fbbdf80b99e6b95dd1c55baef3e5414731aa626a4b091774525a9f
00000032f97b73d227dd57c1e2f8d1dfa8e37a994ab3ca18882a04000000000000000000514ed8fe719515b552ab656e7304d0cc140927c5f2a06acbc697f4ee3e19b769572e766312a807173758a0d0
04008020fdc3af3c04c5c719aae2451c07f799c2fa86d3bab8c703000000000000000000e6c1d617c2ed17e2af6f674a65aa08dbbe80f8b1369543c0e3997fc82c8c727afee47f61cffe0c17aa440750
0400ff3f0f608c64e16023e3f0d035225694b6b47cdfeb1fb1f006000000000000000000e5531f511da72374e8134d6b8a7355b33c3dcbab631569697b935513f4258f8125def56180900a17bddc47d4
000000207e414848f3b63a831f16342296956c7bb84069ce628217000000000000000000d48aba88cd0e1c8cb7d7e9be5dee2a98da9cca7e4db0bd52194a082c7a2b4e6dd15fbd5b91c125171f71d4f7
00e0ff3f5379ce881e00313bfa6c1799d7c0311062888afcb5840b00000000000000000051de688486185a6c16fc8031d099e26c292dc6d5ead6c18a89851e6f7a576e76323a7e6093ef0b170065a0c4
0000002009923b526f928e76076fdfe0ba4cf4d6ced40fcbf5e90c000000000000000000b9579ac5f815642c696801054ce3c1c92c566407905929455f471093afc37e32071e0a5f19d5111762459285
0400e0206ceafd196e2f6490f939262950e4091c406842ca612f010000000000000000000c462bd876f833e4a86b681dd8282978311ee89133c18c24dd2c4341eea463b5281b19610b18121784c7b981
04e0ff2f7fe9caf23f8a3bd07c0b3f9d6ccf5ba1b248be788f470200000000000000000029040c23314fadc091322793ca635636788d46bcff726246bb68211a31efee821996df618b8c0b179db42d0f
00000020fa92deedef721c7f61cc0145b433f81791fd9ea6ecd503000000000000000000be748cc6ae367a6add1785aea25b467e88623425b8acfc28f562930d0f49616d78e6345fac9b10170894937c
0000e020040e90374293ae69ca8eb1eced1a23cbfc5aaa63d66906000000000000000000f00827f99aef7e98252bedfcac9d0b2e5e66408ebf618798205b57685fca7532e645d563e27c07172bd98eab
00200020b47ac882bcc050ebb1058d7321b2aae459fed8a537bb0900000000000000000083dc4ad7c7ef00644d1ca4e7ec338649098ba8d607e398558c8c99da5a1a03dea261076388ed09175ba9d22a
00e00020909dec8ed55316f147bd89b71729495efed865c12614110000000000000000004caf9171d4a60a68eadd2defeba86766c55bb26887eb2f118bbf1e500baec54d8cb95d5ebc2c1217514afa41
00e0ff27b1ec236feabec542882384bb46f4c6cf9b07d562731a09000000000000000000b35bab8444be47174c65d44e85be123e2adb4bf274a5ae5e3ebd37a773c5f39fddb1335fac9b101727a6952a
00000020567e0dd1e335fb0fd501caa6646c3df6f6fd79a624cb00000000000000000000daf3a4ada904dd7cd4d35988d703486ad6ed95e5dc2f54caeea15ba6220518427c4dcd63e27c07174f7cc4a3
0000a020ae0fb6fccd71cbbb65dd42926d3c1a0ba1038b4003b70b0000000000000000005820c620059f8e11abffe8a559aa754c36db6ae506b7aa3fe0f2adf94091e13c3d428f61cffe0c172a28b971
04e0ff3f067ca4c2f84ca9ad1087666aaa521c2193a76d7ab2250200000000000000000019e92e7e141ac9ea44801a6708e194d00bc54773feef83e7cfcd5075d132611625ee0c62b48b0a1701d01d51
0000002006068461b0c94ee660e986cd436dc3acfb6f34acd7a00f00000000000000000019486b41f26e8295a2d7f9656d10ae62162944cbc848253ceda74d0c00702e10fa49025f19d51117db0feb2b
0000c0208a9a8b087a9c46066a059e1ae0efdee374ba46648ae901000000000000000000ad442d283e40a6b8a86b4c657d6ec18d61132620ee541dd6c7e94248979d16905768796093ef0b179ade8738
000000209b19fa4da2512320f23a6174352c70c2e2aebbf8770d0200000000000000000070e5f37c2202621b7ddf8d58d8f808912bcd4c00c8466b602f86a6a1de7cecf5ce61286394c808171d590b3e
000080203d8bd10ffde05ca61d66ce357527f0b1327e54947fa01c0000000000000000006fdcfbf150241322bba9aa4583649e5df56ff0bcb27f882f77b7feb95e389d34e8e7335ca5183217483b0f09
00004020b76a252b12098d665dc0cba0966a5efdf18ad679614e1000000000000000000069be21f381e48ebbd30ea99b2e22786b19b2b5fda3e86d5a7f08aed047e348fae85eac5f33c41017d501f26d
0000c020a79ad0457a18633097f11100acaa49da36cb9e27c5fb050000000000000000007c64de5b3b50c4ec780386093a6e0f03ee2028c933dc3f720f34e0d202c5f9ab76524d608c1f0d1797f9d8f6
00600020b9f6d4453c8bb6a92bf81ede0ce42a32319f96356fcb020000000000000000009c8a9064fa502512d68bfc1a9981f406b49023380f96dfb4419550e105b56731eb398063159e07178d707d01
04008020b725b1cca7fc4b8c3d5f800c9200cb5ca63b7cd8d72603000000000000000000a412d411acff6d02640d6b666b4300f63a2b0ad3ddd7b69072447900eabd5a70b3a38761cffe0c176e5022f2
000060205da77b51eeca9bc68ef24b0ee12e5238ada3d458157205000000000000000000929491652b2f899bfb70138ab91cecf570edb8b9a94a77712536c0411e5291c818ca4a5ed41a12173ad9cdb8
008009208220c1d6d948c227c13e81fcf33209cd324d50f51d19070000000000000000000f20c3011b2af3241ecacb016cc6c17de3a89963b8379bd5a27ab32492791a912ee8a76390f5071736fb1589
00e0ff3f85bdcbcb2680b4014fd44eadc63c58c195103116df19150000000000000000005fd907863c9ad2237728a703e5b457b6b5e40f5b25a8107fcf7af01d85da817c7a18aa5d5ca3151720068c12
0000c0206e31ada133502a78b6ae4e2d5f5f5911ccb0ba5ffab61200000000000000000043d24fc36cc0e5f67f4bf537ee223891dffc4726fa4b58ea70ea702c2f667f523157805df5ab17178eb21149
0000602069a99762c5491e2a1bcbf9cdbb5570cd7ebb4db5f8bb04000000000000000000d269957c9375b164ec43a8e34ef920adee9216d988f4a5ecc382a1873c7737169b86e2632027071724d4685a
0000c020310af39af5a93ab95c8de1e1a7857ce6a1b0a980d9db04000000000000000000b8bc9aeb222b3f2f4a0d03d0120c0064782cb6b73de076b23a2c1dd30e9f3275b61fdd5d3eb215175cd4b918
000000202bea384be6e617abea9491099e4252c22d62d44124440a0000000000000000004618e00ae33c84459b0f891000e78449712599ba1c4edf59caf5c131247465ddf8659d5f4e130e1764b52d08
00000020bb01b4c7adbba223e9c85dc6669e692e55955796424e0b000000000000000000e6d28c420565a7458e0b1d953747a513656ad61b5d77a87213df98d5dbc1caa0850e9e5ba1192817a916ac94
00600020a726afb59c15d267c07b315ddfffa6beec51833034150400000000000000000014d75c6877c39be27f270fc8f51a85e002f033dd98298e0537c81586d9fca332bce70964a38906172fb4122e
0020002099d583953d62f091f4acc09a4699b568d74eb912cecc03000000000000000000501b80f8d8ce3aa10fdd1b1a0c7bc95bdfa87d5558c00602b8f93ac11deec99930a16e6275720917dd7e8b35
00000020395d42201022937da512f8e118d3f485c5a9ec88572d240000000000000000000e4ae891c20ae70d864a181ffcb6e6c71f3d7db6a415675d3a6542e7beea8b2c7692545c356830170005ea96
0000e0206a1199474860b0e01e585c9899f5581db633bad8e81a020000000000000000005c7c645d1302494239bba700e9b91655076a19e4d63143df029c3a74756e7d676ad2b6611fa20b1769098bbc
0000c020c37cfc307451e1da1c02ef3e89743b2a406d55e428f208000000000000000000b1fa72f1eb995a20c3e0e4272890ad68e0b9bc46338944bb8993651958c2b03e91770a60a1a80d174585a20d
00004020b2329d029d5033de442369cbfbf19ae07f220ff679f0090000000000000000000581e0cfb8f5204efff890ca5afc0977182b4393c630640a2e54e45d5b7d7b27a057e3618b8c0b1705ab4e75
00e0ff272c92408aee38228204a7d0cfe8262bec31dca78f4366050000000000000000003d58e9378d4a9cf6219a6809ddb2235f2cfbe16fa63384a473dff76ffc289e0b236e2a60b9210d17ec1757d6
0400c020731a2002befe1838d317a5835b652bb79ed1af8e7e380c00000000000000000083690ab05d200d15b5021c8e6e0c5f86d3fc373589ee8b3613000c0e07990e9d9d765a61ebd00e170f4ea61e
00008020d83c84d868ea8107be190430e693218d26f7357b14dc0000000000000000000098e0da3269e71e17036cebf330aff9443357c1e026b35278d5e73e5f32f3642543aa725e19011117c44812f6
0000a02051ddda48f0696229399242e03e3c24036445b9ecd51d0700000000000000000016fd3d73f9d1950ee7f792d0e7c10697d210dfce43e768780e34dfe24ff3434a10919062206a09176f7e9069
000000203413f44a1278a8b85ced9dc344ab65f671b95c480c39010000000000000000002808469fec1ee28acb2fa8327c46e78a27072f8368de0d33e8e13235beb5ad525584ac5d5ca315171895949b

Reste une petite conversion à faire, car les hashes de la blockchain bitcoin sont des sha256 des sha256 des headers :

import hashlib
import sys
from binascii import unhexlify, hexlify

infile=sys.argv[1]

with open(infile) as f:
    headers=f.read().splitlines()

for header_hex in headers:
	header_bin = unhexlify(header_hex)
	hash = hashlib.sha256(header_bin).digest()
	print(hexlify(hash).decode("utf-8"))

Ce qui donne les chaines suivantes à entrer pour le challenge :

50e8e9cd4b1465d0fe23bcfa6c7cfbc0980e8d7a21bc13eeee7c2c5fe9aadc82
74e50ef85931ec5d72b7976752208a05aa98522d8fdb86eec24b9f43c4b94de7
ea5ac1f22808dd46d5dd4ffe586a799069db1ff833949e56df341ef220719fbf
28acec141446c8d99bfa1341eee0151ef5943bded1eadc75b1631bc9a6445586
ccbb562062c3f3deaf257020cfe5496093c88a508d47ec51a42d56261c5a47a6
5c0d02bbf13954253ee6b10734c621935a1720f18a05e8c7ea7a13032167b195
ec47fbfeeebffda795d969feda30dc6b5c82f8d140b245fecc3ff7caa4e1d5bc
329c99d19fd0e0577395bcb89b307c373d50ecb1afcc41c9fb793884875a8cc5
dd496eb4d62162e09bb4e6f47a8d3c3af3de2e3a57cf664f0b7a8a9c68727140
b07a51b4608c196901aa707e1dede7ccb74b66575940370d0adc9dd5a5389208
26d467b4c119142fb510c2916157b1fc56ec2e36ec2dc936d45f465c5f297969
a2a5ae3020a10de51e56d9e177fbd68e96abc5fd2a9eeef2b2608ebee19bfabf
432d094fec4e0a198113b4ec541f401f63f14fa9e2940d9f0311904360d843ba
0daa61d8eca5b9ae8cb588cb0c2e7f69a6083b9e5f01e3f6d72cca30dcbaa451
013d8c370c53c79f5c79bca83db15e905e6d134bd207590d4c9c94597b71c874
e327147a7802d4a686c347022f9f3bd49886bf83cbc62c17170be46f4c72451b
7015cc2a3b2815517f181ce9a603ab92f2a0edea00315dd981736e681c567f96
429786338d7017e76dac02af9e6fb9eb77e579cc53c53f3ed15afc5ee1df2c0b
e723449195411dcfcafcc4301c614894e73b81b5fb5befd6800d48dcbeaa954b
ac4521f0d0b0863dcc028fe782964cb9d8c4fefe36e8156508d533e235858062
faaec7f1d280b96c6739ae1447eb99d8a2cec6be1e5b792b6e6c0511f587c556
82a48ca1280898b2237112470fea381178605f707e55509fc29b43f48090d678
69d9aaf6c9fb00504bb95e448ae3e202df76bfae73b4f6c5510acc200c55c0a7
30b97ff311a5abb0012e9fd4a5850a6532112638ee8bece23a1ecabce509218a
0dc01e64d631d8d1a1a6abc19638c650a7f2de7e0467b591cf24b1975b19f87f
57e41049e065c32f0494c246e5bdc8104134b82abc02ae7d71de162b99a8ed66
6b21c103e1cf58f612b5ef021a6a8b63fae8e9761491b04c265f3943a6ed5cfb
a1662aa02ac23596adae5e8b3824b1b0f7b20b0b998e30e645cdb21b5c2512a2
d3d89ba165d57fb99c7d7c8a646ee2d563622a8dd81bd807bbe3eecb7ddbf982
b9f1771b4d43e33b3a48f8801ce53371760748db8ee7f939ec71f5ba397fd3cd
bd2254590b8bfe90f6b6d762e0678739401d7e872e1d78778378bc580d639a69
8399070a4f582a764be758d4f6c17b835860417e5f313a6627c25e624044eeef
042e281b5c16a4e68c0132c3f36cbfd15a048644d038950a27b9e2e9b1665701
2ff7c0a687b6dd673a0b0e4dbeae29f5485d47a69c3919bf5f82995e90ec1e43
c1e12ece7b7fa71a0be429222e78ac460c2e233e32d8b3a36234b44800d60717
4ecccce5693313864819b6b90955db7629f9cfc05901c82a23c4668a9ebba80d
3532752cbea921e98815ba681e7fef2d4796d85f767b5910daec0dd4be83a240
8c3282bb484b94efcfbffc363a62f6faf61485364aa7394841e98495b6eca151
a7892c0919e2f64f38709f879415962824bb1f6c5478082daa83980d3f8f0fd0
affa8b28b055ee7fb95c4ab5ff855c5f25f3e37d00e6e42916f721746e73e8a5
96af2432601831647460675701c263dabff56dc7fff22bb3155b17c5172be9c9
8a10aa3ac0241fdafd7b75c92055a648ed85c4635288c74caf0ac279dd671356
aca447569f34b38f5c8c9e3e9c0e2eab5c7f5c3f486863f4d2db11391abe99c9
43ba024d347937546a7ac7883d077cc5debdb91e66417ddf809de03378318eef
beea8130d8c17de6a82e23699f94a69016ec95ce35b60a504c3813c196fd358e
def3f8623214912739efd014469d4d12e4587c4fc6ee5db6679b08836c93a7bf
2cd87b5be90a6118f7d70c0189c6383911bf0d63a1734464144d38aee0c798ce
e7ff5709858579107d16add50869bea66a522af9bd32a59ea87b7987bc986e98
076d7d33bf05d9f12eadfd7f84415c78d0700a93ec65195713ba129b5c9fe738
e9aedabca92e8506f54203d33d9931fe282a15a2b9cb2a9daaae20c4ad8fb236
ae28abdbff378a072878661a5bc190a53c9d521e16355bde4b9be88c3c5ce53a
9221b58c393622f3a0ba09dc6da3ac725b75f24030df2631d0fa6b7737405a1d
bcb2dbd5052bbcad953d60f541cfe08d6103b1fc978cea7cc50a607005a8f826
767b958bbaf13552579474511c15e9bb782e1b000ef001749477e65b500cce2b
0494ffa8271f021a7d15e7ca0acab5d9f9878bfdc241c02c23a6302e78b1f408