diff --git a/Makefile.am b/Makefile.am index 570791a64b6c6..291e09c535160 100644 --- a/Makefile.am +++ b/Makefile.am @@ -269,6 +269,8 @@ EXTRA_DIST += \ test/util/data/txcreatesignv1.hex \ test/util/data/txcreatesignv1.json \ test/util/data/txcreatesignv2.hex \ + test/util/data/txcreatemultisig4_elements.hex \ + test/util/data/txcreatemultisig4_elements.json \ test/util/rpcauth-test.py CLEANFILES = $(OSX_DMG) $(BITCOIN_WIN_INSTALLER) diff --git a/README.md b/README.md index 55b85da977abb..a5ebc8441476b 100644 --- a/README.md +++ b/README.md @@ -74,3 +74,7 @@ Translations are periodically pulled from Transifex and merged into the git repo pull from Transifex would automatically overwrite them again. Translators should also subscribe to the [mailing list](https://groups.google.com/forum/#!forum/bitcoin-translators). + +Secure Reporting +------------------ +See [our vulnerability reporting guide](SECURITY.md) \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000000..b183d770c64df --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,894 @@ +# Security + +The maintainers of Elements take security very seriously and are committed to addressing any disclosed security vulnerabilities quickly and carefully. If you find a security vulnerability, please report it to us following the steps described here. + +## Reporting a Vulnerability + +Privately and confidentially send us a description of the vulnerability that you have discovered using an encrypted and authenticated channel. PGP encrypted email is preferred. Our contact information is given below. + +In your report, please include as much information as you can, including: + +* a description of the vulnerability and how it could be exploited +* its potential impact (e.g. privacy leak, denial of service, theft of funds) +* steps or code for reproducing it +* a proposed patch for remedying it + +Also, provide us with a secure means to contact you with any follow up questions we might have. + +## Considerations + +Please take care not to violate the privacy of users in your report. For example, stack traces or exploit scripts sent to us should never contain private keys or personally identifiable information. + +Give us at least one week to investigate the vulnerability you found and up to 90 days to fix it. Also, please give us reasonable advanced notice if at any point you intend to disclose the vulnerability to anyone else. + +In general, please investigate and report bugs in a way that makes a reasonable, good faith effort not to be disruptive or harmful to us, this software's users, or the users of dependent projects. + +We will take care to inform the maintainers of dependent projects. + +## How to Contact Us + +Greg Sanders +gsanders@blockstream.com + + +``` +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mQINBFWn84sBEADrLv0m7garpyq7m8e6g52JaW2RqpkAHt5PARe5BFj1nrAz5FGY +eAMWEBEKZOnUCf7hwYZv4PCuWi8Z56QNI3MaxILAAxDG2OyfRvdCnVTFm5H8DNmQ +juiW6GYOa9BzKGwrBxDwspIIeo96qjcmHKZyqusoEfuJi48X+vxCVlOxM6OYtUnb +H7FeCDYUuNUrruuhNFgidf0bo3g7NxOmzAfEQQ/gvlGJIHgIRwIyrfMi8NiOy7tG +9pEW3kesCijYwzkI8d5gcodPCoGQ73iS9nCLpg+FGF3d8+Lt61xt3YrLTgbxfUb2 +4id7vbCuHVnoQSKEL1c7X8Fd39QW82S78+hxjIci1WdJTQSCpnAYS6U9kfKDE9tv +ZJcm/TO3F7hCiH1XUbBn9zmQVp1njgtaKLM4Vsq8l4d+M/FQYZVnRIN3Ohjsz7Xw +gL1wSMWYxRnGzFjAVTJsJPdXlCGMeX03zolyPal5K6o3Uw9xQE39cN3zlxH86vP3 +LuwHLvIUKLSDtqS91JI2sVxZwdwwyKtxcld/H1JEMv5hHJ8/ZrIDy0Brohl0VrC2 +obienxmYr8K7WieyVaQdGZ4S9/HyX8cZW/YLBNA1oMwa4Lcgo4GZMYsC3+w9PSAw +Q5NoDWDOuZy7eHlyjUWGWmgqWXFc/EAR5fdB41Ji0V9XpZI8Dk2zHlJpMwARAQAB +tCZHcmVnb3J5IFNhbmRlcnMgPGdzYW5kZXJzODdAZ21haWwuY29tPokCOgQTAQIA +JAIbAwIeAQIXgAIZAQUCV0cVpwULCQgHAwUVCgkICwUWAgMBAAAKCRBr4s7RSpkX +vOMYD/4jkg7juqKpg3i+UuP9sa6WQ0RFhAofNurHcC3YkmPG7XtgboRKsEKZh4yQ +ZoKRd6JqDaZ8ukfW0xiGWmhanTYlcEUpG/KXcQGpZe9wTTBjOLAx5zO0CqIhP87B +hiXGW2RP5j5JyQJITOutmxSVdr+GuyMq5rAzBY/a+OmYoN01pJlrt2O4jELovM92 +9YJY/oh7RFEkSG9F+zUE27vR9jbc2QwqnTpNVWx1qkuMKJIHHoDet0dJX/xwjx6d +tMaCNucYTPcEwZ2jzRgYdV/D1N0KGWsLiWNDg/9YxwPXgL8nHLQBjLEDm56fKqu7 +ZPtn8P77uNaSAkxviVY4Dh7P59Nj9u78C3Aiumynr5fpHUPlrX1a5bgGRT/UkIZY +JdkPteC4QCgF9q+Gi5bwyXuIJJ2bVO4kwmdWYxQeJ7TSxJQjo1fNacHZ9JLr5GoG +IPkmqrqEg9pU883MW2r8yIKLfdLZvz7CM/TV9OfNCAnpqFZ1OOmn3XHdmRWZ10UV +FsM0LdLLqMtN5vxRqQEBYIc+XySX873ynL2R2/P8LLpQkdxTaPxlTzaXB6pwT75i +HP4uQ7VRSSRbok0seto2J/H+Pf37OPubhRFeLnOYU5QsGRvDRX4wePC8feYUtxD9 +fs/Wy6lyG7Tu7cy6lCwl8Rm+QbBwfG2brDxTL57iH8BJ6yZhKIkBHAQTAQgABgUC +Vf2XiAAKCRBPdE2h9fFTgQZwCACUDs14VONs8RgL16UBrtpRwnps4qxE2Q2T00xb +8GW2SipECGr+SLmX6yx/FcL6SM2c9D0kSrZl4IaCjyQn99UG7sTjvnyj+LKAb0BT +fLCxco9Y57CXhq07MoS9i9b15VPBN7MVU4h4sShrHKZDOPkvimkQhNCZcgM6RgW2 +lOQQuwIVPR1P1X/1cGmvt0GCsVW6/wxCe+g0vB3He28YoX6oIo6iZLrSv8XRLyWu +jYkQHUGd3h8vv0M47XZPsZmSq3SRJrfOWBl1Uz7SkL/7orGvmdcXrxtccx+/tSV0 +NVbn/glCAB8n/Pqb9iv7GAmvAtruNl+8y8JP/SAGTmLYcmrviEYEEhEIAAYFAlX9 +w5QACgkQrIWTYrBBO/qfYACgh27wC+yQtDW16pi2NNxSCY6meQsAoIyrRc/YkVZ2 +HxuQJyd9jnlyCiAhiQIcBBABCAAGBQJWCoHvAAoJELGnDk+NzQNmIwsQAKPryApg +a8wy9CGkiQaw/YIzRXBYiUKyoEDmC5XYvQ+wK95pVW1xZSXsF0W0qkmRpt9m3mfe +feGiFo3dGStjySyGDNtINzI7UE+rq86XUkiezwR2mQ9Qn0NxXmbQ3ix9PZKv4SEj +6JOv4Dg8uJ/v36DexY04Sp/vZ9iyakR+XaExJbZ6UBQzAUkmWVqEfWjljU5KqHRq +h97jeFvivqO9Nyip1mWZ0ZkCmSQwgDIR8u23HpxESrqY80jYsrBaCsKuDdehCNjo +Mo2og85gPC6sU9/gccZlBZed0vjJuKAG7HfCUEYeFZIQDi+Fsrfl2qyV4207uxXO +GN44m9Pff4T/5k8pnfOQwlMYa8ThL/3z4kyQ3nufltuiFp0Y3pk3neSPWEyZgiXM +YD5bdDPN2LL0qgNausHRd4Aj07irMhILD82A9yq7bW9kLYDNOAttB2uDJxgzpavI +BiSwQp7kgV4MXSUTyDIO6ouHYT7vjSfn07FO3G82ADBzx+iyiOlQu9WZTRRfEhZn +fpgx5kT/kyupc/x9WrKmAKU3sK6ClovDaCDFnhjm8cVos16a3SCpVqxXEyXv+10R +ZY3nUGN7bDKuYdwUE//JzdrhcjsZ+bFxrYUFdEM+rm7DvJc2UnOtbzboVNpkazwH +d4MDnNQpjaOtTF38R+uKZeislzvK2aB8053XiQEcBBABAgAGBQJXRbMeAAoJEJhP +EMx3Fp/Sqe4H/2zVVARVrWz105LMc1V/Eispq+mBDwxqfuccTDtfLDdaesjNOz9A +KrRataTGfy1Ioc4BMETdOixs6TemiL/bhA6+5Gbj2t5cLjt2E+4TFVpE5mtbHlJw +IvqVcKzwpKSGkz5yYdMZo73iRnGiYoEorfPj7Wl67d0suzgt7aAaDZgqmKTushWI +PbIfA0TgSBhck0bBXMG76oioNoJtJ5hoc6/M1v4+xOBcgv2Qjs/1Ez+G+ScZspCs +1gC+YigwKL2Elkx2DrJHoKcjJRVXOmfryiwKCOJzsXUZNUOtSI59YSI6MH7efMg+ +tZyEb2aZRh/1F15WVhaSMK+syfdya3CbNwKJARwEEAEKAAYFAldEkKoACgkQdIEL +ASNGyaZP+Af/f255kB2bXMPqxxiK2a4noaQhWo0LnLMX7geVYOMcbvsZFMq0rZ1l +16XUJxl7kCog2m9QoElDHOMpZSMJXcyFsWSEgObRZPrgip50u/nevKqoMUT7kMGZ +xBeq7478gfh3FZTcoP4s9pY/8riVuyjwKbxfBxBVzxx3ASAMU7VsKGcmwbi59iLY +WcYZWUbT79rW5Yts2gxPD74m2txHhocgVs9uTrwCH3O8GWKlo/jrA/wqRPlN9EYs +EFXrjZ4DmJjLs3fMI43HN6VaVz1paS+kI0s0+CWV4hiGeCLfB2h9cMYOaoGCn/90 +z0sypkdsiUyReInrHQ10T0yjzstxHQ6cOYkCHAQQAQIABgUCV0HiswAKCRCGD+uA +TmaTIFn4D/469kAgZgXTgcIDgX1MKPnxXcPxjfllmyoBsDOuYXk97YoBo4n55nvT +kv6cHR1wbISg8Y/OsFK1AABpvbOgJ7Kp+a3P1yXdoFZ08RDl2Frffe5BXEQ8EC+1 +o4eCtsgjHBjUscX3dJHGaqXorIocJ8csugJYoE9+0A7ZUB8XS0RXH6kZ3lLge8Bj +zYkXUNhzG1gjlbDlZM6tvKZKowUQbvkra6IO9jZhTksWWsmEsnhlpA1t6BsiRxNH +VMf0CdltirBr1JD50zxnc2panOBJWOCCSjJV08eIv2zTCMW0r3y1Cf6EK2dtsWI+ +JE32DVk14bATPekCeRyFgVONBHgn6vBIemvgUYrkEF+EDNLAye+s4domOfeupmZj +MJ+Y2LUOSRLRoArEzScznoiHuNI9zz60fjbe8clxesXCEXA5VAzhH/v12EW2e8hX +Y4CEc85YwngP/osWm072+jE9aPa/uvUYB5a4zG8fDlu7yrV/8V15COtnJFR/ZDet +NIQc719+j7plPW1EgHAwwF+JkZiuZsr8R9dwAOSliq/cQkV5pXWOePt/l8QeBsVB +M3nNmXqv1oQ+BxZrdM484v/UTLqBJVhRtZpftOsWIspLT4LBHMubdIrirtzVm0w2 +vj+zzmYhCVixtDf2RflEtQBWIQGZ/isH/yxBBkaVO6J8K8Wsws1MR4kCHAQQAQoA +BgUCV0XbegAKCRDFJCoas5NlFzUUEACax+m1K7/bHPtcccGVIvoPg2WtM46sMFEN +KxwwViHRu8hDVtPCO7VMWGiWUjI+MF7upE7hg1l/us5851aI7Ho7tvidcLPdl3yl +f++F32ay4O8dIUHiNnDDYcHrrlhxFkWxfCE8hHIu36uk3rNXALLdNwfzJZr18TtK +052QLGr8xg4eUdfVnr5SF7oQ8cfMCcldJ9vXXkC6t0JdsNeCr9Haawv0fv8FFYsE +T4OqJSEwn23x9sRkyDAwv5BshS4GD9VGlwp8S21i88OoPPTzhf3T6A7KVIxD0B1Z +3dqKFw5INTx7hmNRlWjYXrZhUxCLkcNMEAoVzMYMEpqkgJ7AroIeHoZfUfCqyrDN +OekMxMZqI8E3br2ObaBTpNj2WJGP9bSSa1hREblrvxHyOo/J0SlU0KSLVnvbW+Fy +hzkKkancs/Lv1uPdwVlgxyY7xHRJG+IVudpw/kXQv1EReBaKKuPMDDsCJdwmKCeX +ejEz4lSFQgpWCUSBnKtmBpMT+yvi8aciZQJ8lkIPXZkBka55BYMG1W9LH9RZK2gq +RA8mcr4ia6GyHcpLAHBGzkV1THVTi7nNpdljQufNninaTfYwAIvN5EMAr7IUnGc1 +sPtC0m4HdOFc+dEVZYGEgIGjbvbfxm0d5haPfBTtW/N+f3c2E2KaO0uDy76Qpc7x +D2HB0ov6/okCHAQSAQoABgUCVz+OwwAKCRDAwHYTL/p2lbvvD/9n52hiAk2Fku29 +Lso9jdRHsDVY+0no7vEQmqDQ8r/d8STVDEdK9BTh+jynB4XrBQLKgjRRj/e504Kc +v94aTeemhjngXof0gvzx6w3DAaa/v71gIVED9uyTxkX2/CJLhVy8NWe/Bs6VCbYA +kITxUMheWLeiuyLKevFVV19OohYyW1X4XBaBxmT08Pjuu0N1WR3zixbNabHq7VeO +FlttJwVCAg8EDhkPAZitqaITjbOfjLcfImDWickrW6yE9QcA0ljnoPJwXfQDcnc0 +9TZB0pm0rq6R0L39zK9KXBcKWWYGo5uwwprKP4LVw2fAyfUbAXD0BLaSj6Pp0+lu +T2Iut2BdScvfYwfO7Xd2a7vQzUrBt2+NxfLAMoM5uFtpZ/Ip6zBvjqiyep2u/qUs +lo/++eGKsnI+yiHKnqXegfjBoap9ED6l5rO+qjRmuQXCd6oBfNkTtDhe+/0++0X3 +QDZR1dPpitRGEA5vprBgdMkW1ayhsWkhUDSdlTUCpNb7Wor9SEoE80H67aafyADO +Ql9X3r3lQeKFI/a7yQrviQKExisgANQwpzImi0GbgHAP9Qp04abdw1lE3P/IWycB +c0wo/4CJUmMxGSMI4izE6wcETmVj6GuqIS0GUqKFt1EdJuNZfW2n7cUftclWahFb +eYtM0phYNQUSUN6cKyRzIroPkUswk4kCOAQTAQIAIgUCVf1qxgIbAwYLCQgHAwIG +FQgCCQoLBBYCAwECHgECF4AACgkQa+LO0UqZF7x/+Q//a8pa1FLLG/3aZXDv+FAb +pZTpcuwY+knAUD2waOk3ZFTpz15WykEAsbtCnIokAhdWqqvNb/6GdFNHT37dMbrP +I30OQdvDwXQcsCw4aRuQb9IEESS4MBP6AcmgfJjmH3lX88PsvFKgHIkm+1wgCwjB +fgIsHlo86GP8AoVOUULxwi0JFITuwikfCLUWzKDa5vyv5bOo6IHIZAk+qNQlQn7c +kQF28G6v1wBdMziwiHhlfqkxAe38SqOUAkZYe2oKQgI03EBuYCZ02p/CMTMK+BTO +2Jd/dw1NYQfVufASdTrYcZdp30GtWCFyIrSdBK1SY0m7V+k3G9wEMEB4tPgbhOSc +j1eG0WY/31D4QRnYHH33Fy+fugyFg5YcoCsHe4mRM8XNrTzNbXdreSjgM5TaHS3V +289PSScH+GerIZVIl9MZtmKY6WtMLGS0HSv4KDYCfraoNtOyufaWVB2jXna/Myfl +wKDEws65+lRNX3i6VHdfemUl3ucUNWPYYWR+9NjA2l6gKBvk0aA90dV2Ar5LImm3 +DfQEq5n7Yd7gbmWpKfQQ23jvOikLqRdGnRp8PVRhPtp2EWwwC3+PJ3O2DXrveOLD +aoXnY4xqslY3fMOuQX5uiSpOGnP+yEbfsGFOhAQtFYk2b0PXgJeaxCaLNiieLfqS +NkZ/XjUgpEf9PSqMwaD4VEmJAhwEEAECAAYFAldLSFYACgkQNkiogvQxa5sC6g// +ZxTMy49UEOEWilk642NHM/9RJxZDHG6WkxrjEbdC3NDTlqzx/3KuBDRM3XpLZKdo ++3bgLo+/wgs/8FsC+6NgqTSd0+yEtafWHmnxnDgMlgoeBbkK1pak9BK9ieU4biQC +jZEvZh/dGdkP09lZDmt2xmRIP2MZIFDedH7C/3sJoxiaeQiVzbB7TwBCk/kMZwAB +LAoaJDrYKBBg36wtbracfM6EBq1Gfmw7096WV0uizlOKI9Q5mp+FTcCncIpyTkKh +JINV8A9kvTIqQRZxC1KVH8V9LSslqygEAuSBWMt0YVg/F1Tb/DtXr9Zw9FGC3AIC ++0jc2Y6eCUWygxedXPMLxyamd11ZfY26xg/cxUF1yrO+Pn7h4uorU6kybXY2OjRN +FNjHPVmSL7T9DuFVZZsQ2LGla+5gmzc9ob9kvi3jOZX4PlrHyMAM2+BGAZaOYqxu +V8C6IcFK2e4YxMz5p37eFrjjlJnUdV+3m113P/fCrJqBu1gJS0jaA8tRjWHZvPJX +3l72fG8uQEFARiRHNJ7pOgUHdm62jBDQpvbWkZbagzXHsZRqYQdjXgRT8aMeGLT1 +8aT3mTwF+y249E5lK1fDxqKL0oYSZz8ixIbEOqZ00j8lca9LcZexflJQnXOHt5wD +LU4F08Z2wtun6u1C2qL1wF074ZbO2id8G4Cu4k3LZQGJARwEEAECAAYFAlfwQ0kA +CgkQf6sRQmfk+gRKGwf/fzo2rNkbNuIypxkYjOHhrdJKWO5uFYujejkFGI0b6TBa +FX0SUg29A+fGEtLLQZoJJ4uuL5cTkcol4DwoK2oE0USXV3Y92zDuZ4kUEt9zNlGf +shzoj6IVPXDP8Pt61/BF9LMrfdLWkFG5rFnJ3HOJl+cnRPhHnEKjRJ1a0cRWwdvx +/+j4hC+ic1dmvl90/YyBiAiSroTK5niYWUxCtOMWYb37JMxkpsUjcHpeEJ8rKFlV +B+v5JnbxQf93v6bfvwho3OPGnZ8ccxIKYmmZeXM6PW/RCSy86Q9wYNOrpS8/wQm4 +/BXfH4jznItUh6PRFNlFb8JGRJCmw6D1JevqfQUsJokCHAQQAQgABgUCWZcyiAAK +CRAXVlcy4I5eQSapD/4x5FQzGPCJdwyfqjyy09XCND/iwzLguHwcQw83V/6p7/84 +IxEbHFvyaU35XCiifp+Pnna9nRxKnX7yqKdTtquWvEHZelJtMgNnqZw7hZ9QNo5Z +3/SyStroj0jrmyV65Fqzpcn9bT6EIj7J7RwU/xb9lAqt77W+cad6YxhBoPTofy1u +uQnm3fK/D5jqydOjydtVhBOf3cx3NLi0iQy1wG3KJLqXZml6/nVeifybDG9fR7Yk +CCuwXpLGsY0cKChz4DdVNnWYOdE+uXoD4aWCxqGZpVXicQxoo6MgUbA+7dkFBU6F +Lg25ZAXQ2uFvj/sg6fXcJyZID6P9Cj3gExEZ3g1OS2fBdN3Pab9LKqjQfFRVKrn/ +VhHYji1vDlGLA6Q7jaosl09p3OWcKNeOaiD6nL2o0gweDYF8BE+wvh1XpNmiVLYn +n/h2ZTMtz7vb/f1g1TPbKGYOJhqWjlnmEPRr7ryc/ZVD28w/69UKCmmclgQhlgXv +ftOZ4KbPX8WAjVUHG6enUsuxE5Yj/bFBgpQryngBH3lw6f5mOaPnQUVZ2Hn7Iwv9 +KdOd6GhqiSwI3t0ZXOboiu6VRgYrgbeWMd8VHdfilPiISHQdPvkKJq9eTJXEY5ef +LalUY4geril11foJUYPez5drI7FonzZLwkUqXUYl6dX4m2ueWM49JnmhVA11uokC +MwQQAQgAHRYhBDX0raYj65/jo7x+9nugNcpbkBcTBQJaoDV4AAoJEHugNcpbkBcT +a7IP/1fhVYb06JC28jmzk1EsQDy/nDyyNXL58xpydM7HQG9KlWT7Cpa5KPSSNb+7 +ifPaXPse34FNdu5xj6/5H5+G04Olq9smfMfRoD/ziNvqRdu4+SxSbqjCy2LgAx3B +m0SO92v9vGG2RDJITZIdKJX77SNeluLMn1Ktdj3nxXMmwI3qkqi1OvWSZBfs6JlK +HOb95I23/LSfH55SZ/X8qHVqJi1EKNaYy1r7XU1VsXUYn0x+q6QWJX/ZjJmSY9LL +0N24CsbwJSDQjHKAzyknB/BOepwwqTQWwLml6DOO6Xz4yBSBrDubJUFXiYIPgV69 +UaDQeRen7NDi3mWj7FMtUT/ctHonbv/h+LZcfNZHsux1Ar0rJmmAOJoZ9jv3fZ5q +sGBhaAutKDBYE1+w/jKg4Q9wr+2qPTOftFU5Lbom1kPNgFIkv4Sy8dDxFUWFL3Sj +UwretmFe7LuxAasK4hO+1abQU1E56x/Ebvl/8wnhtJh9Ngbiv2zh2iMCQTrRDoJy +qEeF0K04zZu4oplRuZ+3p9g8RdYqwnJdL2O4A/Qt0t6C39L0JHqL8BIKAhL6u8kU +ZTpub7fBMv5VvQVXSB+uTPnWa2s0H+rLIvlZRB74DzLIZgh/W+llKlPUV9fxedBX +/2SjUMExtK9YInY7klDslu+bFdFeM2/WYnnPZH129iLP6jjdiQI5BBABCAAjFiEE +YGhbMKoYM6vJRiwoX+CNGjph/s0FAlqnItMFgweHcQAACgkQX+CNGjph/s2wJhAA +noA67vSOtkIu0IG0nSW6KXnGvRadDVZYhFnLZld8nVGlfdDhxW4XN6YXHAje4ws5 +SfCtJRzigvVyZ1B4Y8ItnZsZmm/ijZPXidNqCCWa4sM55v4Qir4s9QzOu3eCMLwm +rdt5o4ug6GdKJKOv90EPTMxESKxe2Wtl9m+6EO4NHuyWbdsNcmfk+YlXykjg73x8 +atCPkIeivtRRy6aMoPwxBGkDHqZySr2TtbOP5I7tkExYn3epCjLGtmkRer0BvSyz +b64QOeYA8FLGWlDauzmq0aamB+ZKw0EEniKVBS3yp6SYVSS92FNtqsOLkqwupPBc +js6TcNjNmtjZLvoI7TWqZZqqacn5oqLgwk+rs6R3bpGylDNvj8dd/e4LlAg2guK/ +8GmSLoqyLq4GhF9sAaWJPU7M/YXYClLYmiBlKC5rONHSXLfopgFg0CfnU+1Qme+g ++kb0tQ6iUSFqYLDCTsjOBAZVt4uwaJPao8692b8D0ppfqZtyaW2gFo4kblsS5o0Q +4as8AL9qB6ukz7YFBCTz2t5YV6tXIkd6hGEHWa12VLyqiRg+Sq9Rq/srIVn96Xxf +ajcHxzC0FIRfpICDRPPj/t0sBJ4YiQG2giNDHHpLv80SADX0JnX+AJL+C1M2UxfW +EBXkCsjIrbVaJeSV8IZJYQL0nt2acrkOHDvXJJ+x906JAjMEEAEIAB0WIQSvkXMY +uMQtESchYl0VfvysvGSEIgUCWqc1DwAKCRAVfvysvGSEIsApD/9SJNUy4v4S1E1D +DvGV4E/sxlDqS2CW1CAajliXikP83pmyB32WyUwc4TbJYmVc8h6a0wnNxCEbUwMn +wU9H4ntiKcJBoSQgG8sJLfF1zQ0WYyUFwrWo0ZuD3WW2wTB9Mp89V4THNFqVrO3U +GSKb7OruR/+gjLMToYs1OdfSDuRIP3awXqoakfcRg7ep0QM+T/P4QwRjNPmEeWgo +U5025uKRiFDG3yGMEpbYTMJOwIJmnpFI3ZDH2XodI3yBmWIHnB5TvFt8FPPJdZ8L +rPNShPLYQXCJmMbNAI248sU0nTG4r8I3sGl5VdXwfRgjWxsBDFTAO82oo+txnmag +Sj12BeV9IvyAilnM5HyOis61Vj7E61FrTE6URYj8Utne7irpsz7gUgHoH2pEX5fR +MxnpEoESMiQaHkwnXjr6mXHo/+z2fzOkFcGdIeOb2oe0APmlag1aV/XwDDEYTUEr +dA058x7KewOHpnt8cGYw2Fb7saeye7pP2A2SlNE+nCHDNryqY9mzXC0a4P2b2yr6 +Vn66QqXzxBmRZ7Uj/FpO3Q1+KfFhVxdA0ceFF+GwQY4PLT1IIs2/3AeGTkISL6J4 +XCCTDQWiPHBou09g99xuNMhRxaGEPtZjVKIl8tNUX3fdHE7RV0OCVt8D0xDBm8mc +a4ZhDcF1hNiHvf3fETLyKg2G9F3++4kCHAQQAQIABgUCWqbx9QAKCRDTABFuHIda +PXbqD/wN30C4bLMSRBGTwtcBO+iT6kw8fVkVZS1T0BLlj+iYmEKvmbATlOg4gbkA +aMOF5kB/dg0PI4dXtYBpVB0+6P+3sO6iuulQANN5hJd+wbcK33w9NO/MCKE6iyuV +CHL8NeljA9FsGE/oBHEzvcI7sx/V/UKafZh6GFYX3TUx18ayu3FhQWVw4GI3rwu7 +zTW3G1lKf9ctVA/XKlcuUAYqaWOrZnLfxpoLoHnPLIBbaDNWJ+zH9IlGZvV5Cr+V +9Ggg3VtwAJ0hMhJwvsi23BwNPKpSWa67MPWzDPYMmZrkwWbTq/bIR4WvCt4iewhi +eUHfMh1VOxBTxXMlZ9XkyB0Qfj+rKmy7T5G+jE4cLXnldj6o5u2U0oSjPQ7iQwUh +xBhYD087ZiEreHzB4EVl8sbnXz7zC0G5Z3RCJY4BJtHOtIOfnNdUWgbHNlmNAXv9 +8Z1PoB/zxG/TNzepq+oGetPShucBpJTbW1V9DKodNj9SVm+OroyYA38EpRvBOKsz +5sRiDP/Ac/Tls8qWkE6eCU1ipusDNJfGTLC0cVjYA06xgjfVILuYuOlWKRvtd5Gl +8weo/O+uqj++JuxEXzHQVL8/cGnbNokF1v8bn0XVeXRDl1bwD4L+Dpft6Eniv+2t +4Hu3CB1Vqk8CCPfpDieggwxP3CNED6CNtmIjtF9pIAdWbbhkpIkCMwQQAQoAHRYh +BOQUqhIZ/VMY2Selhm/RX0sWRlDMBQJaptvMAAoJEG/RX0sWRlDMIuAQAMUM33wz ++1F00OtniQGq+RoD9nCpRBtrAdb7zKOuxCmfs6r+gobS4D8m8xFYLxHcIw4GGxVP +eHnmhmMOuY+4YaKE7mbIFfikZw5c4zE+0xfHrsNNRwkjrlywzPKaY4vbsehfFS6M +Q37SN4VbXsZJHslrtfkru0YkttRGhul5b7Yk8TT3cHNU5Imrk/0sDviqitSrgQQM +vMmg1H1+6+RFtmG9y9+JEJFRfSAafyHjfasnrseY1XIFA9FQMJSNqPOjMiC6tHd4 +r/yYmz8J05UvVtC7GI114WKVSPqvub2vk0jsstHrgJP6+OhIVd9Ke0qSi7BVJD12 +pIt2oXR+4aIElTrNDi5RLW5Gc1Vah19Y3OHpD4oqk6I0DRPEc+b6X65axMWB+CiW +Nb6d8v8MSMmzxdfTulHiSlJHNcz9aZcrUL14zxgO6muei5bBMDntCRyJHFNr7BL2 +d7gw29srTWcploXJI6xgAvDrGW7vhIdt0MBGomuXZgSY2wT625Q7c3QdKOasVwxQ +qDR5YnYIhkRbYlf8Sgu6b0PSgSi4aZYkp+mPwHmoanQpd8TGX+6kLYsxnodzvyRk +dQ353EuyEMSMbn6t70SJooxvVKx2KQ0ISlJ1O8Bcjg1Owc6t6VfmG/MfTAneZWnB +Wkb2NYwENHiRutfL6pSrtCmCiGc4QEfa+WGDiQIcBBABCgAGBQJapaweAAoJEKJt +bZ/giO1YIAcP/ieBbsmAtCCXjIW57gExNziWk6SVwUX1XuROyytRHC71afT4U8Uq +YQpDrOYJCTX8Q6vH8U5P2WST01pTEpM7YuMmgikaqjcuxSR7ProFel/TS1apViXf +GtkZ+aVM3ttaonNnzlvGISLt/7CCSb+Uci5QUzpJwWA4XydyU5VEN6VVnqGyeAna +FIoA74+6GrUYhK5/Ne3OJsDo1jgI8Dj2uP7dnr2Fa+s7w+FRE5bDvY+MkwTkNQxr +edZ3SPhrh6xBTm4v+HpETXD0bEUBz9pCwk0p57iehYwFEJr5H6fJTpZB03XUy5WY +ur69xYtR+Z2ND8JxbTh8PakIZZ7ul1KyHiVAzkiwZ98c+DYzfLksVg9cztSXa7a9 +3noSLHaHD+b0Ks4kgY7CsNdqnyBuh7N6Q2lrHdqjCNBqgRpUmgP0sV8Xtv1q/S/+ +EbekL/lyiLtH2mMvqQGTig8H2FzQ8Y86/sAfP3x9++dUxyL1qHLr+uN4y78kaEFR +Sq4CU/LvuA7ZmEyahe3opnYaQt5ceQuZp3I69QpyTti4t+x9ZI7m3wK/QLXZW2ik +YY9/v/dVauLpj3F58kOrWfVYFbpdKxdnr2GHe/n51COLdcHt6iUqqQzyrTauWMXP +y82r/fRLFBZFo0szzJpH8u/SAOvOMRynmHgAFXzsEbwiiGgR1Nge3RiLiQIzBBMB +CAAdFiEExCr/fGGz5EoUVM01V692LbM1MyIFAlqheVEACgkQV692LbM1MyK1QA/9 +Gv3fFcdENQ23/OzuBOO5ZMhZOUOApxgbC2m56OBllK8czCRcI6e8SVM5jHbP/m47 +ugaAum5AAsrA8aCyF+XZRvuqPuFf4zsSmJzrXGdw7p7qkJ+ItpJOmsn/oKna8lGv +irUJ1rKkrOUOio7PegvLHgGqU65tShdhVcjv8jx0A1bwb/ZE5rDhAddW5rhrXAh8 +aDS4T/3r0OZkZNHnFa0b20VGfWbFuevpaYJbnw4Ngj871mmG75ASv7xUxtb/l7Sc +RD5T5sBJi9ffsbUA6EjajYosQUHDqOA48vhmPrm9WOZ46OJfLWBUXJ4YKoFZLePZ +zdZXX9nmNdFPC2YrE6Kavu7S4TnWT1kUUYv+NErEZYELmUNU8XW5EBamKrtPzBGw +SzCHGDmwnPwBG1vCdJR4GS//1xmFVg/gqtOTclhF3OdWx9kQ/3pjBacdCG1ovz9H +/5JANZ3cbuR0IumwRHyoOC9yYeKreMYZ/A9Kh21UxfScyENCrHcmSHvGosSCNLls +rmBvOAABgR9ci2UjxCGsY7am4k/FwkQ4WGoLC11o4tXifxFJOTPmJmwaq8H8BriH +Ky74hxbuI8G+r8XvbOihMuQJAPzYuuvqGeluEHSjbRg6XQnjoATnNx41zPlhgsjK +O8lWMp0k7KVFpcjm/ht/HTdrmtgObQOdJIWCtpb3c32JAjMEEwEKAB0WIQTtm996 +1qVeIy6EUkJX/5vbzDAQCQUCWqA8/gAKCRBX/5vbzDAQCQnxD/4rBzysVWRfr9BH +8+4pCv3/D/Vdnqsp6g899QFx0esYdEEwZbgcfmxAdVjVXy3kaAXn31Oiv0kZXz1I +J1JyA2eIsWt9Glku7jTa3tU6NB4JYVxpKFx44lLyzzbYVCUxzn+favb8zN7b1sN7 +VRlL91kfRiw4RnfWBP7Hvd0s1x8mc8dzikYd89xp7PksPwxispEKWMSyDIMMqLB4 +ykSGgv0554hdzPDKOPcTtL1BbW9UqQHLqAk3Qdot4Rbkz5fIXo87IqwtBi8VbQUg +RGPyrFEr8p7upt9NCfZ5hIWmOY1NQTrYerUfCv6g2aep4m+kbb5zdfipZ3RJs8BN +yF+c7qb77Nh1s3PawesnQmXnwIh00HQvQbbrHMCELkV+ZT+FtKaN/vxs2kwSagAO +P+wfkEPvosNVpnU9JpNFjTf2iesnMFn40XJksF+Ua+GuZaLxdoXWIq+CA0raoZMP +Ek1SrOYHPuMgC1LDpd6KW5RavYxCddNQF1opbbC4dEPavJmX+qU6i4HhWeteO9za +D99GhZ980pE4hJOjaCnpQhA6ttlTnEVUAJbfPcfpLE49wjBJK5CUSaM8KJ5M6sGs +PIGQXXXf3D42S1H90UFpWHP7teEWMjUhBnpg4gC3BpSUOoishALGRPS20RudASqs +V7/FOgYPGe0pyvPHcMsVwhbsf1BWPLQxa2V5YmFzZS5pby9ncmVnX3NhbmRlcnMg +PGdyZWdfc2FuZGVyc0BrZXliYXNlLmlvPokCNwQTAQoAIQIbAwIeAQIXgAUCV0cV +rgULCQgHAwUVCgkICwUWAgMBAAAKCRBr4s7RSpkXvOwCD/4u9fywuVVRMsbs5qJZ +AjM86PnPQ6cfPvGaKfN6/ncB/NRBcRJ1NyTRQqHfIefSPllS+sjbfgy2vSPWIaIf +EEaA6mMgCsjl+8Isrsjt56mmspo4GMfC6Bvx0l6yOe0Spuomy28vVHoxo15lNuIq +o7yia/1RltI35Gs9LAjrhuIeZx3G2tckgFjfcEdgnQ4R427HnYH0puOatDyKGnYR +UDpTisTvlTUgNWKiZM2UQSWgvAlIa085lj6Ep5LvInezq0+VGB8pDpeHkfvlCOOD +32H3BBfzMsDWBb73W7uR0JHIg8c8IqDG22NDPEwU1pQ6xnKIXkoAEnTC0iouSq5O +BM65PMhapDbupaFvsnMyeoZIhiTg0lrzRYos8lPE5v4yEhoeJrl5wNaH4akx7fLP +X7eJl3BYUDqsxngc/3agPrmiq56hxfTcERg4obeVvuUUP/JfUU7gp78d1y7MN5LG +/xjYAb5vAia28AjYaeusrQngdiPkqAhDaB79VYsl3SbisNo7mGaQGdEc+v8zUlwi +3JrM75H5ucXhg5BqT/oMOv6KQG24RHVEqlZMi5oIB/lzdUDgrZxh8x0aJT5TdqYg +mMleb+VOrW5SXDxdo/osygSIc2xnvmR5RhKbyvhVbdtsFLN7F98OG2/ucz6pn3QW +9VZCJnhHOU5iN6z2GyqJoVczxIkBHAQQAQgABgUCVfsDBwAKCRB60KkcQL0AkUyo +B/40dpnbngqnCsXAl3WyDQYWyK07v9xTp8Zb64J2MSWfFqg3S+AEgDcArxHdeDFf +vvR3a6jss3D9btv6KwsNsVsWXFfQE4f2TRtG1IDTdjkggIcmKyF1MsxzFRbVPsZ7 +4P1M/mBiZnyNMkV4WM2s8ITf2BgXq4M78Q+kZWZgUOO0cpnRpBuRQ0B9Pyvh8R6/ +KCRddEJTGtWl1n/IjrPHU2Ak3gBzMU0Gsdzom/s+eVU6+xPGC5iPsSHUwASXFWBN +dfBYxKudLoViQuWr+Kw4gR+kvH4Yqeo75EeJyhXGzFf8pQ5Y+G2/RPuILpHbe22H +JkBuSeEVdvck3FP1NeLKigSEiQEcBBMBCAAGBQJV/ZdyAAoJEE90TaH18VOBeJ4H +/RlJN0F/9A6ssphxhG0Jn96PDclYNwEc5folg+STXQqW0w/W863tpERb4mjdlnYy +9orrTVTQ3jZPVTypGog51msNsGR2hVuJTdIg6SEza06QRqab6eiOV6WBk+eZZnQW +ly/6bS1Z5BudTZABDcHCvuctcWwFl7bdJRpyR7VQlO0NSEd/fa2WT937OHNiLSXk +yb1CF7uzfSOfODVo+d7E2NwUBq6YoGS62OJtsTJ5/KR3TdVIsVQxkGbdKxG32Lpj +ZB61LPu5V0orvxiVbhVVy3xK8413lqqgZ8KRLVyu1nJiSfJ4sU6fYRNmQzPMdNQ1 +EMvr4XU3tj7+sqVoCRddN0yJBBwEEAECAAYFAlX8lpIACgkQvQKUJCH0iJ9iHyAA +gNGNLftHh+6+EBjsGgi+nqcfQiXqk/hZ0D78L7uB33JgtEFbrJ+xmAiqXBATteRV +f0sRQa6UQHY3hJEUz7uzmsZR6Lp52Jpy4A9N6fr4EnvTVPuuuPuA1pjGY4fVLepO +frgfraqLBKVkNk3sTULfyTcnvZuZqWVsKMTaWqI9QYlI8IK/cwuBFjPJsB9cnv2T +FmlTisQzq2t9BA19IqN3K6DG4UNAs80jbIbqPeqEbbuv66AKYf9+v4fSdgKL30YF +/4VntapIhHCwQ2uCFCIyn5TQjhN45yXTQuBJ/mNcdc9cIAGhhQB9+5UJEtf6Ru7j +rvIvR0WTcNXXuebMiwoj/WmB/ig7ywN/Yh79q0md+EipCLxnHAIKwbLGDzOsq+lv +yaCZ64PVLvbVNL8ArYhse55u7jCEMyoo5tGDraW0Io+QGPzZGXHz9flSOoRtKQje +n8yEdnupFK8lg0uc7+U13ABkWT0/4m6nLrNPJiCVrmyfHkYyGwnj59r+zor2ejJs +Y25F+gUczfTb1qqCjOZ43Aq1eugv0PYJq6pjSU8vJohr1gkLFc8NWt6KTsiGkrAF +lDPsr2NQeJ4O3J+LStdG/q6jgACEzaoSljURZSUwlozacksYG8yLhUUDX3dl724R +CQyerKknoTzBmZNfIB+GxHvP6bofbJgY0JNM6Pk36FRfTzmnklodMFwNRRlMFv+W +j5UCouR9cPqdjzv8poVN5vRQKD/G+x8sGczcurR2z1rbl7EEu0SzdxfAI0s+xaOY +7Ug7lRg+zlmGweqLL/P0upG/f2v8ofvVUgRl6KdQ5IMpnmMbYD3X4qElRwR78f0w +QAXQHWgWlfvjU7cKEqMtAwjxmq2cGwGafPSqqOtzMWcw/wvpOf7olJcBTugg45UJ +W6NZhahNpUehH8ybxpynWigziu/NZ83cpuYqT8sGc0aL8ADJ73NPYIyuqhnJybeh +5ov4ZlV6iVvTGmAz8WJbCt40pekEOjvTPoR0bQmpSaW42qfSDUMj8o/7MAm6dr67 +913HJIldFPQgkbzr79vz5OS03SupRwf8D4Kl2JExDajvPtrmQNSknozcqZoDflQr +PbUZh8YQV3DlBak9UYJmiaEPtxW9OpBYQLXVVsvsaCcP+xe3OVZ/pWQxCQgZhbhd +Iy3TaFqa7C3YxCcY23zFg33QKuwV0ejb+mdh7Z+o6RyDCVI/BsTvC8ur1R+SpqVF +PGbparu4EhAvLps9r1cmLSwrrWLpg9HfdrYqW5gNxhCqI6oNj92BjHMlFUgENRmK +RkVnP5VfOFI5G0zxHmWoAEjK5WiYTJ/IMwOHkAsCqv4HgMjD6XqwXuBSQncOpJdj +fZNCJbltbmT0/QNtPSxm+4hGBBIRCAAGBQJV/cOUAAoJEKyFk2KwQTv6AYoAoJwy +CjY8kJ/KGevLqtU9Ji2tIxPVAKCw23Jk9Ir6q5FtPRey9nXjqlVOKIkCHAQQAQgA +BgUCVgqB7wAKCRCxpw5Pjc0DZsG0D/97Li3BNKNEwkEOsB9cIotBZkyL0iEUdtMQ +2aPQeBs09rrm0+zRroMDhYopEpU7krtQxGhQKWeougFxthi0iXgleWjCl1S+ZDu8 +0vEYjogrWqF0xGSVQ+fIxQ5kJaJ+4LzGpwrA9+Jo9VQO50kTHGsTpfI3AvYqdMvf +MgGIafr/IkuuO2y7yFS1t3iZO5torqybaLaKRAqzxSi5cjXJOs1e1DNkc/+PFSfT +1VtBBroz00e3ip8D2TEEo1Chros1Xfg24lekWnVl/XS4tqjetmVs+4wkMjCR7KAa +m2XvfaLgQkZgw6yeB4lRBndlF7oOedYc6wuvzJILw+o0VZTQkLOk9c9rdgtx94Tz +bSHx/otzTObDncIvUhH3fHblRIJ/N7c1vu8rnZB7QpFAYnHX2cJHNBt4s6cjiPqW +zj3CEOwBEmFQOArwcqtnmI+3VEyU7wfnVyPTjJwhSPa6bB/sZySNJeVcZwh0HbIB +BXZYvH+NxsBs+tUZAt8LCAKSAC299CuEcMpVKRCHPN8Si9noWccsAlR6v6geCUh2 +M3pxH1xu3Cm2b2kLlKXMhb1zsCUQHEDn5YR6E1dCkeitH03ZlqFE9OeOztevv3vJ +3W4ZUm9WVQCUI9eBQZ4e7XJaHKvd4FPcwWBeHJV5Iz+DQk8TbUttJU7MtyW5fJCN +Fow6Ybwg9IkCHAQQAQoABgUCV0XbegAKCRDFJCoas5NlF1xcD/9XeOZsy1ty+Ztd +ZORTIEoGoH3lklC+hQcw85m6M9jb5MG/5YejBRlnWce/JcdG0vqj7gV/6jqSEd/n +Po9cBdlel2g4+kHESQ6OYe1Nu4g5f5F0XRnPs+4X2WABzbwXAQFqC5X9tm7kwLru +CaVtvz4vll0b7CollYXMzVWCwaqDD00xRkvdFVx5oEtDh76J1h26ZH/gJTTX9Ilt +YIeOzjs3YVdDEXPNRyoDMn+Fu0B39GLbvREXsBDi9zkrcFBjPK9n8j+vf6YenmJv +NUnYsAogGVxkBHxywGK5A++RwZsnVPMcDQtjuCSrpIAoaQjFH6mHI5zAFx0DflTU +GJrRuwH1+aTNrICq1aDAp7OAQvfbog/T79b5a876XTJjynOaePbChdZ4/2lysYVK +Rietecw8pQyi6kiAozE7aYSEUW7ouSQCfeTXAw+NBhkz935LyM0RcaFO4ZY+h/wS +Ds6E99fIEkVwgShj6otsFC+csE6fdfk6B9ptySGH8TXoWAUKzulbEwSOdzv4YVkL +/sLZpIVXNKcltj5cWzFRY81jmD8sdPCTAdhoSbd5JGe9VT05XARsPULyja+kddJO +MSmfXgYYS+LOEeVqvHo7+ZJFOx1E4l2TKJNahJQjxycWOQE1epzq36+oNFqhGa1/ ++er3KU5NgK82wiovXGolZXsXKxxvK4kCHAQSAQoABgUCVz+OtAAKCRDAwHYTL/p2 +lZGlD/wNJznBCjWJfrTJrnABpFLaxleudPXKWexh4JVGYTy+eUz6He+FhVEXViW2 +DVHXRD4KwZb2RxWKF38d66e1BZDUWM2Nq7D1/rB2LQ/IuRPE9vfuROK8l7AKbD+j +WnekBQ/5IHufnZgaTWILVM/+/kG3mh9xoUQopV/WyAOJIZ+SUxkFG7WaPWBBc5+Q +5nm9xIqwqjBC/0TVPy6IzPW6pXfhC8x9yQQXle2HNnkPbsE1xs5dfqi0OAihxIKS +qG8CjlgVje/nrRMkQ8UG1zJh8dHr4wkMbn6z732ea7hVEqZiNTHV4Y9i8xerl5re +wlZDwW5C5BNRhKinO0LXGgEzeewHeu3QACWoBjqkWvP8kHNWPw/pUgtulcHnFj7q +O+EwQbPr7sB9hCAnwu/czJfrasmAL/04JCNCCD0hHvQkNe/lJ/BgzCiTYWxKMccF ++cVXAJc1PuEOXQ94A7NytBkSmx2miNZr4AEosN1Ujggb4thlh2yxdbkv/Xx7XAft +Nm0/M0YhOj+96T40HTVNa1KNyWCADv8fAeVrQQVYTl1C/Cxseh4qMw6WEbT3utoP +7RuvHF0JW6zOnkM9bYMjC7fg5456eRqxwD7BEHw3jrKNvKp1pnvbJNhEoFemgvYM +nPAgqtcIZa9Go0JmUkInWBN7muvLBAv6NljDZOQGBto73V+LaIkCNAQTAQoAHgUC +VafziwIbAwMLCQcDFQoIAh4BAheAAxYCAQIZAQAKCRBr4s7RSpkXvBT1D/oDqkcJ +cBlSgvUUqiZK3T8OAmT2qfyKlb7RKcsp9IUL/ttssDXl7YQq1fk42DxToFi9gfjq +p6ILVQhBWCJDn0U1uoo0HyhAS3eAGqDwOp77czg1RDDd2Irpe6H84p6rF1oJwY1M +/JNqXuQNiK1jCbqHeFm780YK+1mbQkL+uuU1cTqmB4/NKuCV1w1ymK54QVnL2mKN +fE3qmlbzmOV+Xnyy0i8usIfr8hmGSlJWDehgdW3NOm1AiJVQ+Ey9eGDDU+GAGVYh +4qAk8Z1CzBYNM/3li5UI2xmfwzPkR3FlWBnsaONlLMz99nNVlYonw7bcSyHsuR/Y +G0Sz16xz21qtleKweS4xGhI3PuSbykVqHcYx4R/tmvoHtCV5wCjum/QtSF1FmqYj +ZU2rYBbf07ouTTa3j7Ob+kaJmf3rc/1mdOBv+ST96qu5IokZ6uaDAXYq3L0lqdZY +BuO77T/ieo1lVxR1cd5bk7ugBatW1ZT/iMH6TJvplklK6qtWJd36KHnrAxldzHFM +K5xEMHyzfFa1vOTigQC7/5o+1tKQyoEd8r4e4MRoCh+1478ONHyY9VfJ430ezeL2 +SIQFzbwSpmZ+uq8BH7foy4oy3QgoAMvHE2Cjsf/GBVIjBTRv7FYDLue26gTg0xPl +LUyHUs1E9Vhe91C026e01d+g+sv6qViDGUBm1okBHAQQAQIABgUCV/BDSQAKCRB/ +qxFCZ+T6BNNcB/9q3thQQgkGc1f0rTq+3WYqt7lP3TA6rzE9XmJHcKnNdo2quI9n +5SFk2HAO6eOLzltkM4KtDyNhpR0wCHJst8HpU1eI5EaxNrgE7J5MoLRTwzA+2fkz +aGJUCQREDT9SGlaEWdP9hCxpflP9po7Yk0U5gDtaViQbOxX9RCyv8HTb1ZT9y7nz ++ojTo2ztnibiOw5g18hNYuPg6aWyN8+GeRZbGlp3X9ueU1jp+HEvyEQypZh9MdkC +5LwzQ/BBw8VgfOZR2pkDep+d+X0seB0WAT5bN+O2OMXwzNkrHhHtgNNdLGlR9o33 +pRtZdeYWKgSBsU4ZH7KBx8v02TGToAT4zDOpiQIcBBABCAAGBQJZlzKWAAoJEBdW +VzLgjl5BY9sP/2P+LAASZppQzx2UgzaOv4FyHqcNfAoDmGJYSb+hUfSxk3h1ppoM +fPcprXFeTHnB33UWAvLvZ9GEQuBx3zJJL8EA78nUEH5GNSc63fA98wbegkaHhSZL +3bFo9k2SOEZJjGyjYpzh/PKy9f0DkgC9fEpjCdqL/pz+8BI5d7EUXl3eWmT0MoSl +1HILpu5W5/UjmGB4VP2SmOuqTn7paSlCXPnACJz7Ji0TbHOq+xjvbwGSgU2bTEFq +5AtbPls1BNfeAtGfsa4aWTTv1fPgeW/QICERwyykoeYgPf3xp2kwVv1IRSq2evFU +WLAWAOYdtUL1p2CFReJjvDez4TZBZyL6vx+J7YlRFq6d4brk0zrxVianu3kIFDBx +3OfZuoaDCmVNHRaX1FIvspcpmh8O5+5/BOMcjdHrvLbSVAlzpLSh4Y3NsSvyuNYd +/DpDMz2VNnSj3zsWn06elLfxl99D5/MP9jjqq+m7N0zBSrTfNbJoFqFz6Jb1/IBc +5nCfuFUSrFiqUgjOGMjenvv6uwWsXZkpWW+W86j8rcGrQvnPIaTJjbpXLvB9sNyQ +p/9BAFFAi1HuhIBrvl17NhqLXC+GO/MnfAuvyA6Dupm8B96jiMtzi0foKgtfX491 +aL/5glcqR2d2H91FdOdsfTGRK7f1wRMNUtZk+cz+dAhv4Hn39ADQn8M1iQIzBBAB +CAAdFiEENfStpiPrn+OjvH72e6A1yluQFxMFAlqgNX8ACgkQe6A1yluQFxMBaA/6 +Ag1Tzl/609/k49TMSKuU1xfi7XtoVbk3QBlvEHUFgFv9NVO067YtrUE5zLBHJ8pr +1DMqSt0ahIZg7gT0Z7H6yXX2wzUQ7OSgqU3R7c40WTOS5tCm4tK/Ybge037OBydM +jtu9dOs6pquhlB7XtaM5cKMOs/M8DueBSpAMnkC6jhmUa+xwNtEFYn+mrs9O1I9w +aD/ML+1/jMC6GbWQCRI7rfmLcV56YXQdce/IZi3NVp9xaMOLv5FhLXPZ2VrgPEJt +xWkZ8V4UGi32gjNpY8mXyeKhMD8/6GjcsBnOx/QBqBI813Yr/82X+hRBGJfiRBIE +UFVqpRWl+Y0Z+vnkanR8jduZTJPHlLryMs1oV9Nla6AAeDpNblZ0a7PwSkw7CyTd +JUhbID3Dan6uvCG7KeNKbYcPKASAvu60PsPjdpvOEHqEL2MLzmGP5jkUHpupJR+I +i+Pmg2e4MDhTIIDIdgezTDW3fmxnSpDZi/G1PMyB1I5b0PKuJAryBAW5Xk59+KGt +/OcltpVq3zRqzUN5kZOWHwC6BLwLcun86vmD3AVk84elxalBTk9RfH9JxRV3FAfE +F9WDi/L8WQh0aGxuclpUNPBbngDGQkTl3ejL3I3fEkee4kfPc/+h7oqT5migTt/X +VCmWe5woMygyPUQ6OV2DHWidJOnEQPXTq6lyUOuAGViJAjkEEAEIACMWIQRgaFsw +qhgzq8lGLChf4I0aOmH+zQUCWqci1AWDB4dxAAAKCRBf4I0aOmH+zZAmEAClWZtv +k4oi2HHbSPhZeHN/K96ly7ctZjzlM7eANNC8pjxprqRbbLWKiGAIQ4iRkEMl5ZTp +80KermjErxAVd+hCaOOCygyzlcjD/zG8pakX+wBAguON7/TbVDTkCCgQ9EO840XR +DcWBZprFiApmAp9rNc5FT0rvCF6aFV2rJBYBP0U/eKxJsVlAVH9OaphF5vTlN2Mp +uFcnvnncNL3FGITjG6viKH26wrVY9jSIINn1scXVjcShyISud9u+85h1nzTODmmW +0nMKevdKO0arT5LhMlGilFI05ngVteKbLDWJF+DKQ7STpAXr6ilAoPq7tqrL6Rzb +8Jm5epTWioe+QecsaWY9lZFtVEU40IkoErpluk49jliMiPLNgaX8jjK6LKNUHG9Z +VP/h/0VG57EfHw7ctuuGiTkiJe8881cn18zIH3zsou7HxsyyjMnDcRAL5g02bIsn +uBbrLEm4n6iRTTBXBoCyNAUQ2t4Pwl2pGWfues07qV7QFz0hOC5rzn8y4kMFBds/ +841pm0nmLuNuCmbg0CBAQ6D0QXLrBYKx3LtuAQTThfdNzLFGqpV3yOZJtD4lSbq/ +bwbL7kYcX4ue4MFCuP3/7HyafLSR4Iml1u69wB/8APYE6vnxkQ+f64uCIrQSj/0y +Kne4uswTq7H3tbSaRPmaQj27YiF7l+rUISuJ24kCMwQQAQgAHRYhBK+Rcxi4xC0R +JyFiXRV+/Ky8ZIQiBQJapzUPAAoJEBV+/Ky8ZIQim+IP/i+f52eaMv9+rkevMJqB +dD/XhAkRgPBRN31FAW1jo2LfbzgKIof54UplDi+Ci4njCQh+DOSZLKq9RTFtkV55 +uy1zoKaX7sBtzn9HOUXvHQyndE1uXP2ZOgJ2bMMd1ahDXGHiyEof0+LLLEsdQFPk +R7ro+4zQdCFQGT7eZ5tITzOnt9KXVfPBuD7kqRTzqGqwr6FA1eSljGkUppRb10F9 +pcwBGNKiMoMYXacmTl9VSazWK6TmrsPBELTiQwVEayER8S76yWNbJxE3jOB3vbU1 +Jk1HpWVOQ7MObpaYiASQcXS/5w9FTVZMhm9SMhgAP6W+Hn7tUSd1UVh4XwUkuWqB +mZ9GMr8F9Z4ZBwPibyxnBA4XdtZ0s/HWUReDoNa3+vJpc4oViA04/TvjLR+tfUkh +wcT0pGzdmHSelRiCXMDP0raNx66vBcfbG2UXKTtGq+O0vCKxfEQt8awK6t9tX+bz +Hi/qNopCcOuGJkT74UaXKw9pqH9GhFDcPOzfaUJTypeoAvI40xQ/8+6SwByaeoHK +n5EQtPodx6PSNK38CNvzLlwCMRIDZTkCpD5W0JjH2Yzth5eN0zzbFrziWjukcAl1 +7i7s78CkHbrBAuCI5KE+5dIcRLmHazvUItDQjzA0QG2R3iJ13TsuWpHb82+3BfPB +9Vy0nQa23r+Kgrsxhj1MC3o0iQIcBBABAgAGBQJapvH1AAoJENMAEW4ch1o9Wj0P +/A3Ag+lA9AAm4JlCPgswHUgJCLfhPNG0V9G5mbNQGaeHGzVwMoLW9s2iNhthHbMe +VhVoYsWcuZxNm3PRPvy1vlAoaietRqvqZI0VY6Df/SEhhQkWLIV8Zst0KrdQg+rp +3RxsDdyyqoEQK2+M1ZtP8Hdm/kXSpgtxpzhUpCy2P9WgrryFPmLPfLzeA83q02Ps +V9kUdYX8DLWSW2MOIP3gc4TXu9kYpE35aOlAResDF0X0kXhivfvoi7VLaHJj3Y4p +tsKpC7NKiqmLb648Z9Hg5tAUsJhCGmP9yeV5ELxg7pfcFJYzwP45XEhIxZ4UyoZ+ +8fL9lRRy5KdOLpWF24VbnihpOYgfVuiyZgSLKCHmME+KOrgBFT88sOm58fkQwE6D +tdVmZlETNb2jQXVuf2iqblykHr5xiHeZ5nQvqU3Pihz/EHuskVQTEUexfv2bGmhu +RsHkO2yK8fvyJyHeyX3iwdNRNgXcCOixdEfzCscFUclINpe8PegsIDckXCquP0fd +sMwIjKhE/yjxGe0Kq3Hts+X20Cdx91MJEFHX5GABHbhlX1LGO8I7b4H/YUpc7Bq6 +D7fXId7NvEFz4H/TzaGu7fCoaLLZgmg33gO0VYWuyhPBRPDaVj7Yaxzs+xvFH+yj +FFh1/iGkyiAFtce4tq+iSfjwLM1kfMsgSodOzmTzXGdxiQIcBBABCgAGBQJapawe +AAoJEKJtbZ/giO1Y3lgQALDBiFGkgt+Jp8CGDcItj4p4MeHv5c2illerOo4MKiyP +1gx79BwTLWoLc1CZiJB9C2ZaKpbpq7yLp61rHM3Bp/F7+UArhJPvYMZ1y1SAK9C3 +Abbg1dY4oirhMXfbidUhmETG3hA60rNQeE6a9yGKUdTAZCc6B9eIec1NoOSSRvRH +GvvwMo3Zps5k+ChXNYFKN97etCZ2z7xl03SA/hWeK4N+w6GYPRKSYIToEoIuGz/o +qkMesALNSSm9D8KlYyK9kktSqgoPan+TQ7uyowsX0g48GYCsojUrzp7NvH/1Wprs +/j+blVEkqqNNbBdhumPfuX6LpBGJd6EJ9MmjMRWSnoR3y8xJbcOUOAS9+WCuyoUP +Q3XRlo+ze0nFBXMXwq8B4tDjW1t/nEHMR4VgF/yyuneQwguD7yte69v8Kb0EL2TX +TBaY7TDW4CmyhZNWFTwGw4j6dxu4xYa2MPF8/YnnSSKrHFcuNJClI62ulFo/Vi5/ +GdHj85faOgz84b9ojGJGDqm8VekJvxpOKAFHTlp2HbyLMpF8ywS9YUA8+5sg2XjC +mE6d2Lsxq8j9gx0E1ZCRws+6GuNTl4Jyqxz6H6Vub9K4+Yrq9me0K0qDC26UxscP +uXTjbqamLkSMDdXSVk2sZryUyKKyE54h9f7aROD6VshK8Z6y/x2H7ybDps+A5yoj +iQIzBBMBCAAdFiEExCr/fGGz5EoUVM01V692LbM1MyIFAlqheVEACgkQV692LbM1 +MyIk/g//RrWeNo/H2Sc7QY7V+h+jH2NsOKcIVLkCUSNTnFW4ySDG8BPmTOQJNwOE +W8MzwRmKiYE7CZFHK2ren+inoAkl7o8Y4/2nCGykQr3VPU7fyPYhDz3B3lM/aeWd +Mj3ofDk+jbd+MMaU3Pay36yazoNWOVRMv7zMyyDzd5tTtce4rE8OrhSysRMiX3nw +ieoftVw/5jTDYD9HU6ZG1rKx1naNGjfkzNX7iaC4WUS389tMYf2GrWOIMrc+/y31 +7sAm3Y2WtjVi9EXmjuYmXcmbud6Ssny4bJrbnnAdNkYfxNUx+0EXKlIUJjNR04i2 +s2SQ3scZcxxwQPj0T77iT7kctAC2/ziUDORGlN5XxMFqQmTNA6U0m/hxihD+Ljoo +8wUgYXJbSWhFkV84eiX7rusYz7opKAtHq7J5e+pehHdT0r2v4ppcP3sSEp6pHmjX +aTB9EVux2npgIvXPKTn4OUQHzfqv0M0scosTUqAgb80ML1MQnoG5WY6kz9Be1u/p +KRxgSEoqReBiNyYvlLMb9Ym863Bx9RCWLZ4dmQ6QiJnzv1VKrW9iYEUkRMehoCA4 +dPYWyWBRWTy32+emMh7025CkrUm28a/32AOvKpYAGDhsXYHKFo0ncES/aNteZn47 +wsIUAzdMVOWm9nfo0r24AEL8iTKaBAmVOH+nCZvqV4G/bV5ZgIeJAjMEEwEKAB0W +IQTtm9961qVeIy6EUkJX/5vbzDAQCQUCWqA8/wAKCRBX/5vbzDAQCSilEAC865k6 +fgE80Ns2Fa3ul2rdMSUeYDYdnEoiQd078NdXTLq0oK+nqhXovAjHy+SI1ZMrr9uq +leOAU98sKqfNBD/xa8e1Njj91bLxEDeIegAvVqtctQqIOwNYGDM1Tfx6ZGEBttlQ +Y71YKCF11yAflUWWow5mWZwtEri4TStgJn08dbgdFVp98gPyNtz5gcFOiJgdXg56 +x29jBABRWyqSOHCME36hKVm2frBox9hWLBc4MhL5sniY5oAYZ/TXfdycQ9YvkWvk +jEh18H5GpyXgPwFSvhKlXuNuJ/hCXfpOqnZHbothRi69U5iylsp2ZLRIPqcs66LP +7ZlRjQwMg+qzhtDRMVb571Zx4O+ZRXx8pLY+vPqv43x4xSMDnQMAgiGObC3SfIKM +DPG426s1J94K5LGM3wHiiq6s40fDCmR4dcEN9iRodE0wOkdPOIRHMYSghFd1cABc +u1u6NY7sd6BltahsYCnD+EAwZeVkaQLL8YyPu9scYWxAb6qdpTl0xwLg+LpVbUKi +s5hvVQe2MKzR215l5f7RFFZsAhrkEbfaVwxYi5IlDxA0E/f6jMdbD6ys8zSCIooK +wU4sUDSVeDhWH9rin7VbGosBkVPyq88eyN27nUeC7xPnW5eU4mU2+KI1K6LjTuTl +aSAjP1AcnPVifycx3LseHBG3qOnfYOpcTkDq2bQpR3JlZ29yeSBTYW5kZXJzIDxn +c2FuZGVyc0BibG9ja3N0cmVhbS5pbz6JAjcEEwECACECGwMCHgECF4AFAldHFa4F +CwkIBwMFFQoJCAsFFgIDAQAACgkQa+LO0UqZF7wjAw//QswGm5kJsIx6QIbxHmJq +j3rHc+J+na4R/Mog3vnP9ZsEBEPZc+N8ZQNcPx/4jh3Yjewtqlrq1kRwSJJ0GGsP +bY6zlRFBrXPK/5dvUaT34FN+QmCzbEstZV6f1Omnqbz7b2N7HK344zvAEuxeuGfP +P3hdj3ExnmcJHeOj7PmvAAza+FlEgId0hATUuC9/JPW+dTwi7NrQWXCFSyJMDiZM +Yqe/uxL03BxpshGpodeP535vyI2ztgJBBglb6EhaMP4eYiOWpHqTRmF952PX4/f/ +L9A0T/MdUvCO5ZEZwdmx4tALF+LlKYcIDm+KCxNylR9MUhvVDUSKJ3QzDW0vXKl9 +/2vebZGKFgAML8PQrZMPVTrEC70Xgbcwa7OBmxTMjbxxFjfoJ41Cd0YRpTEvPPAr +3kKsQI0M29UIyUyJsF9r+DuDvqa7EegehrhCpj56szNamDGD+txgqVk/6AsZfDCc +4GDmd+ckez6B4ecxgygNg3vODpKdOQE7ejFmzOG+eyh79y1+F2maB/AmPjP09IPa +g6xhdMXfAzpH+xfUL6yVqf672sYqeDu1wF+SWTd3+Qpx7eSeMp+tEKPNjP3M33YL +Xwy9m1tWvGDjWtPBzNM6FZV0wxH7x1wbozud9zsJXuzGrNdNqo/TLH+B1vIa+uRz +mrphBumItH+1y1oYSuupDnSJAhwEEAEIAAYFAlYKge8ACgkQsacOT43NA2bAXA/9 +FRxt6+4DlWhL7VIPe+mFZUTwoqsqy110DNtzM7Wgy1OhPqVn6nND4grzzSA0xlHZ +8sYme4x1NVzs/uubboKhPSZsrELii6drTUa2rq//epyzwij1jcidKuOGsodvHkcL +wZB0MlXEKqB1JxVCYKLlFLsn7Dx35p+HDPou3ndmw+/1vhfZASjfJOwgKyXAxFLx +8t05jjp7VS1fPVKXKbmoQAFWA5NB5mBfZysB6MLVVO+btSkBSx+WoQeEIRbs6aTx +5WBWrdheftVsmms1+LjASaI11ItEy6H4DmuVRpF+uN6DLXhShJ9WGbBo/Ykrf7FT +SpSpkNydGnuW0ZFGQlbWs7IMIfl3mR+n3qi4KJg5oYyOqp/6Lulo/COacx7nIwSK +zOOF3V6iyTxTpZ03KyY/76zY/ybNbYD3W54yp2UQXXQxe//lRw9ydacOb/ZL1D4T +1hdmbuAp4UCz6bSRYiky++FuwsdU1CzOBUXbiQIUNMXfW3NHkMi7EwekaTngyJUP +Cgep56fhO8dOnxrKQKwN1LQTEDHn1W+o8qwx5JqJ5rPAWSK9VAk1/8lN+w7V4vyz +eednWIey7RisXE86wcTIhJ12fAyuiHFPSw5Wap0/TL1FrHVyH6hYEsx3llrbLEde +EMmrhvPcEqDik0pIRqBtG0AIE8gOAMpESKXWs67yTWCJAhwEEAEKAAYFAldF23oA +CgkQxSQqGrOTZRc2mA//SxhTGsBUqoxcQ9i4NtAuZPg3+dhAIFrOOf02niFGXpDL +lEmDhZ4J9nUU67u2Xx422x7WAj1xSauqdJziO3gMXxG8HTFYRNW9g4geN4SfsSVY +zpOk9Y62Oz4wjfwjufeduzpEAdCZRqFK7aPymv/1fyadbla2Ul2fBt+5WL7JUyiW +GZNjj/VL1s703x4IozTXzfzrxhd9f62oOE7yvTwLwUVa0tvWLs7GHLvWAvTkKff5 +khmaw8WkmfQZldYMbVZXdpqm9IcjjIlTZhIrmaOLqcoNYvuCMu3+BLMEc6M/eMQn +y0MxXdeaDX2VJ05V2T5MIsuXQfhMIgMY75N96snx3ZlNDEXgjzl1rVaCi+qGZ0s8 +uPtfC+fCgDsUzLIZcArNgqtaVSpzV4/i9XOmu4mz/eLpCCA4Ot39+FvW9nJctShE +VadH4oNPnzf9WUaHdCpb6TB4g5w8AtjP6znILjjX61CaljbwgO7puBfSAZI14Drv +iZJrxE128Ymflqbvg+Cvcxj1B9DGNCcbn/41QbADyjH+hodIb91LnEXK8tJoesJ2 +sCPNQ8oYXo42bvRXtN9k8Xv6h2phReOywSs4eefjAU63vOmS5QBryjafsLhk5bRC +Lc2f5vWYzv/ESZkrF3ZHmJn7h7P0xLc7LtVlObzqRFNjLYr+96sCeDOGhb/RwYiJ +AhwEEgEKAAYFAlc/jsMACgkQwMB2Ey/6dpULEQ//QHGk0jxZhZ9/0WfLlk6STU5x +OGOujzkHHUTUtV+cXqj2dYGAqKhU0iTkQfn+iJUJbi6i+B4tKhndrd4KF2EW2S+o +PPvulTjSBUNBjAZbi4I7+HOAhBamqgIGUAPOHk7j7QyTvhy1OF9JZO1Bll46DsgJ +YISi2FGw+06s1R7me1JU6ZKkk+rTZoei1p9idGbfya0PMWtvcJPZFLtoQLqjLibM +sfE5sYPrjXCTHP9sxgFbWXIEgxjSM6OLWZPkp9OcnXEV1u+yJDrqYQ4PX83eNBqc +gQ9WrXJz4UE7UtiyPeicW2eY+Hk12ffnZUWcbKy4tFbynOvHYVqDRwJAzbw1+zqU +5a0hzERCfjSL/IqWMmTbe3LQHK+RuE9yBiVpLUmGX/srexDdTaV0CBPrKl+tDfsS +jUnaNDB12KDmPOfASNnlJxKY1cEf+xLmqgw7wesFhrjwJUBplUfKHOeQ8VUdeLGj +ZUp45+zrItiZT9bl5d1IhNZhpNHjbC0oUHQ7xxrU+r7UXtPcnqA18jG5uzgF2AlV +4eLixOF2PT1tLWyl62FEB8MgeKjcABUgInrenYpWG2yxkO0s5tAicl+zMQYdJ9Ap +PqEP3NrjaNlaXaI0qts4xbP3AjneVQMVA8i3sRhNlEGooAXxWr/IpGmCnHWPzvgf +wCfoLGrHOHMf8fHLd2CJAjgEEwECACIFAlYC3OoCGwMGCwkIBwMCBhUIAgkKCwQW +AgMBAh4BAheAAAoJEGviztFKmRe84SYP/3kk2hdP4mLxsRWy4DmKA0Gy6dL8LC2A +Z3Lkv8cvp/KDVqXwO7B+cYgQ9O/0oDqJ2dm5J3pYZgyY4wu8h9a8hc6FbBuOkRCP +u6Yl945QJK9tqErJ5ad/vto8hI80neWCtCRUCDrtkdqwi+glR9kR+8Z9H5snLExi +VIGTFyqWEVBxZ5fwSckJnziqlJrrnIN+2+/LVKw98E66cAcbGFoeb3WSEiH5tD0C +Jmqmd7L5YLohoT07LTfI2XSZJYx/apzxqVew6quwVZjOYX1pjzECsYrVWmorHhz/ +IMRABR7JU8H6PoY9ffZh8cr0EKsZ0iKViCfgA+O96xfvvS619vDcSfcX2ncysBfr +Xh2X1fux1iZFpGqJ1mtVzV00c0fejRq+EcES4ppKh9soDajfB/qYSHrJzZk6hp8V +ovfrPx1rqCLndE6qdU17kNX6IvWnOMVPSrXLqy+Pr9xpbAKftXX7cZcoID3Ozm6d +Jr+0voKi9ylIQ+jiJ61wSK+8Zwem23GNdnkIcdXu9ape3fsFPkhknvSnKroAHG5e +sMv8K/4irxbIFlXmQuodHMURgssJFL9e72TXMZe8wUipZRfAt3D8dTe6dg514fnW +ma9UA4DmqfH588LfmbIg2yoc5L+Dfb/uhxS9yHlhziS5x3u80k1c2NuJFl3/jPpY +8psGMhIVOVKpiQEcBBABAgAGBQJX8ENJAAoJEH+rEUJn5PoEg5IH/3VJFS1NxMvF +CVMP7Pw8uGCVDmee7q5iLSY/DALtLLi4h76/vuFN5Fmn4NXcLjdDC0W2Q1NtbimX +RFgt5w3C0i+gbA+NJqZ0mnK54T33GuadZvF7lrThTf3XdLHN9LXrfpw3imCgoEMr +NgMSeWMg6hcQIYe0CJCXfAVdPaQF6UZ5nCeKDdXm4HA1q57fdNeFA8AbFHBSum67 +uuYl4Vu1SskDszn8tEnNZ5fz1qwlzgZYaf1UHeB5kj0WxfeqdFJfHazGO6hwyRS7 +wd7QAz0O++J1X5iBYtOC2qC+bwPhxDB2Uix29q8U9DLYgnLKIwXnhLua5eEG3RdF +ERw1/xI64AWJAhwEEAEIAAYFAlmXMpAACgkQF1ZXMuCOXkEPyhAAmUJwP3CTUSdW +4Ew2n7e04GpXnGO50eT6x+gjNHodB0mFfDHmuD9ObtCSer0AJcj3gu1lIfeWmWx1 +HZzJm6pQcUkdYycZGmvTgiqF3rzpoGfphDT7DjwPisA48D4FfgnrB5ILiZ+rA7mF +kysVKq9vCmTv+yN2YoozwzOY1X0+1gI4N8FcFkBNH0a9ti+Np36u5l4VLEZdOMJm +7CsyMkF39C20AqUFPQGZuM2bE9eSPn+mALnf8hQWnsViXLqJ8JTY6uKUycgKv5AI +j6+nEy7mjwlAOgGkNZXY2UsAHuqUxEmWRHGxirYUaY8iSguNb2l849H/5yjlVoH/ +2Y1PXVg1bizD7HcFqmldldSr2EbUV32hs+P/9kPmoTIOlXHIL3IGXiv3VXzeFuci +CpFnjlkfbocYNiXbo/R20TVvAOYesv79S9v8Wu1pEydo6jJGL2oG7RKIxIAtFfg3 +L+T8DfWLkmLDa6aeDxCbyZNThjKtBYO7soFF402XuI0kgK4+Asm6RG3q6xcYRt2f +Atx/NAZ1oJ4c8ACK4Dq3f/a8TFaOjEai6hNIlYiNOvNxpsGYrN056s83Vk7pkve8 +VVPGDoPk8+50FLIkvOj1HNoEgzYm3GVbbNe9ZBlL1SIhOzSE2N8J9d27jU9czFV3 +QZvWpNQYYqRrpCW7H9eeEvjtrfCs7kiJAjMEEAEIAB0WIQQ19K2mI+uf46O8fvZ7 +oDXKW5AXEwUCWqA1ewAKCRB7oDXKW5AXE9+YD/4sO7Yk4RYrdpCgYXBmcKZg5SBM +Hzo3l6jtg3LPhjS6dvqvyi3cYjhIWOaVpBPR5eA12b79WI9/HIdhrU8iiYN+MLHJ +GufpyN8mRIPmUY7Yb9FyeLb/17c8mYqm/1pS7qv2qqht8TslzsSM2SZcRVBrbDmc +VwMQWmZMnDyGwdHKS07VyUDRfL88OJoXQcUapTQ7w4c7DWXxp/vU/4SARqot9vcS +grbNpcQ4mwdD69ExsuJsRS8H44J/P4mDI8D6S45SjuLFjzJsE1D/lMeVJOwD9cGz +fc/LYBrxX8fSOpZ9ExUH56WVTXhvs3u4nFed6oaMX0yWxxEtLvoXULk8zb49VIo4 +dujXvZ3dbl9mGqLPiay69ZHIJlkhiO+80//yqh8TrM2KdfnGvpRTpGB5FelyM6kS +1ZSKNW4z3Lw82YdpMuHptfSMSyyVUzXaWh2Nl+hI4/zgGBWuVtihAq+WswKDNxYp +zr1unt/L+xtdhXpb8IzIix0qosQixj4ozXQk8BmirFipULTF5+QgAT/XB2wYU9pI +/+6145zdxi7KVFj4zAiiXsJdDg21ZtjFgK5aeHjcjCDdMlf2W1SEMqCgUMhbDNND +YovXyalxt9hxCiRnhC6k31UE2Jw3F3MZCN24ZpBQFzApwBMPgJgsntDXbwH0oOXK +O/j9/VbNbVScJmIjZ4kBMwQQAQoAHRYhBA5MoSvha+aRVvVAyZhPEMx3Fp/SBQJa +pXi4AAoJEJhPEMx3Fp/Si/IIAK254P9VNb1/ce7A0qjWrO7Za5CucXiFNJFBswac +dTdxTvtDk8+RjoZK35FyPRzpXvX4x2t6dV4ZAK/LlMEg/UTHSMC3XmdjraNK25XM +LT/IsXxB4VzeXFcf0/3Qr7SgfCfGz2cCTlMOM8fLvyaHVCRCgvU9s9QiU1ZCn+Ab +qIs2qScePCPjlI54a5+39YC9CblOMkrgtBv3JdABIZ5UfBxO23gUYqzM6c2LkNx0 +kzFd970Oni+ZWR1tPO9IY3Z9EVyZtfAKTsXJ13etTigJOp5HtfnVEgdrlhgk+wrI +dk5ZPFQxM1LgBwxhve4bf37Ah0gl9npwD41o8OZlu6LYtUqJAjkEEAEIACMWIQRg +aFswqhgzq8lGLChf4I0aOmH+zQUCWqci0wWDB4dxAAAKCRBf4I0aOmH+zeL2EACh +P4BLZ4W0RccQ/VwhM2MJEfa0rwwrm/lsKDkIG8VxHqWnmBHwukvo2Wpl5S7I/l4N +qy1wgmGXxANojVrJZo90eNiDDEbxYUkY5RuLr0edVqUxt+BTxSrNh7xf0jfM2eko +GUhlUAEUHoMtftV2eQEXhCBqp6y3ZYEJNGU7GByYs6KV8Hr1fkjlTSt4K6XOHd49 +Svt8tTFf5ccugMQQeWcsg4S++/D8vOl8XkurCuM1ytBJcSDUoA6jghS7O3GqH0G5 +dubjh63UfQ1xQEt2Sno+aQ2IKDGhmMyi+yXkw0GqD/nvfonZtcaCx6pE63L/Zaz3 +BWVqRGus4EaqAXrFuPI5jDSJuSwDQe4jP80c9O1Jp4DojwdTJcopEnG3aQgFYois +aJON2FcPJAGQWxWYt9udKmdHM1rx3zho/CJEKWZ9UmND91Z5qFKf1lnFNWXv7tnY +WhWrCtk1hugijvIJ7xOBAVVdvydOXZfI5juep0VgflOtS9c0eMgPRVe0YFM14BQV +2SPW59y1R1gB5OfwG+0GQxSv2JsGTchf/x0KyFamYU8+NVzmAoebTQ8fvQ/Re5yy +KtK2WWFaohKoEUuWPfIxoNobt0Ju3iZrFDvDmmSBDopLnglMgSDfd4gN4eNfYlyS +bSuo2mcYeYvwWceC/eY7m7QfgmXHx51zd8W/jprIk4kCMwQQAQgAHRYhBK+Rcxi4 +xC0RJyFiXRV+/Ky8ZIQiBQJapzUPAAoJEBV+/Ky8ZIQiGAIP/RZO5iL31M+uIsb7 +/4j9JP8bcU5vEVA5nyEqjqIAblVppKDH+rFdE8d+cf7AIgeBGRcM3HA/WwEhWCOO ++cx9AF+9H1T+Q2Kh5kZjWLMQcJo5dbRqB4SyB043/r7k6tF24E+5/RR4xTYr0fft +Qs9B5m4LYl9g5Bsz7TQ1w7HwkvyFvgrtpe0hVLPiE9XWC07a26IpRywZRB2u13eg +SKKPmHuUcXa1m8MI1/Dr0P9IV0pEJer8v3XDaRLCBrh0ZqyDIGt0LxMNc+PMi9Dh +9x8tF/PQuTeMQ+FKM+FFEyvJMuOnmL6QvxU3yT9Q6JeIcE0oSA9YjEjAjKxA1tRQ +msbHmaOIVy9kZFEe/gel00ilkp26b6CA2k+Gs1UrZu91YeO5aV5pGRy1dQItFGnD +Y2sZJmLEdrHhMcK/4v6Bbr/D837uF6pBB5WawvnmasNp8ssNLJBkpIspJoW6aaKp +ySm0PN339ymzmZr96yZG2s6xt4TwGXv+8rqGCkYE6xZMx/H8OmGbn8tm0qKq3rcH +xVVrEh5Gg7BoRt4k1TxZpt363DZsnRRm1Ww3g4q9i9w9wOUQrmJNxlpqzJPFCDDH +PCJXDTvlU7lNIYLWoCWgX4U0zZbZqQHbsAXi4SaVmBO1n9bkGtPZPLnr01+uh+LZ +tyY4CcIqfcfkmObH9e+F+w1UY2OpiQIcBBABAgAGBQJapvH1AAoJENMAEW4ch1o9 +6/8QAKnJYwKgfb0zN2N+IZEC5O9os/zdm6Z00SWSL5Fj6ALTp6Z2WWT8oatvpglQ +4qTj102DKzZN33Kksaxj9WejxEQDDAA6GsI6eZj6Iv6/u2u16SRtu/hoXwbguaYF +9nlz5LgiB+heBGErRlvE1FA7G7m0MbXtgfWZxRYmKUIE7U2GuePukmkfcO1fr7TB +mQBtfWVUy3TZFuOHAsXawQVWubVQ4g2QqP/Qp73dfvpOGru36GgfMfi5srPsS9hy +up3dQUAlGRRhdyjEgJuvRNCCjFOd+vYYzhM/qUoZGefsDXEsRqlj/ptKub9R3ZQn +9MIpAebhRJoEOF+TMzCBiJi5i1xu30XAm0oRhz6KRgVfqSm8WRVC+oMw3Y9vQPgk +W82PyqbLmzI8pOXO1zXpm4TNubosZiBHhlEZ3YCiHdELfD2GjF0e+MjDFB9kGpRO +542CmcPp0j7QxJM0V/bakYo81n6vCXWBA6DD4/DHmqNZMjmlSYEpFgHc/a0dKA0q +sX6tTUAOg4HMpk61to6pnbogX1HGaec6zfrf7erxuCPz85iISZJ9ryg1rxpNFWZn +FggIos5JdJ2M9ohV1lbK1veqYXhPWYD4HwWMMBCAbj2CW8QpMHM9JO62xSwX9nC0 +FS+4iHKYKvjh9tvH9Aq/wlmwzHY4S87Mk2ulMeRh/qbUwuLriQIcBBABCgAGBQJa +paweAAoJEKJtbZ/giO1Y0b0QANB3RCruv52m9HI64o1GNaASG2kBTtQAmpqPx4hj +TFY1WqgEJhF2j1NJ9kQIg+5VbvDOyilI3TVWL+ptGiha2S0iwn20JrHdmrXieLDv +IBsTeYg2egC06EGRsLcHOSahS5dpMNJf0J6Aggt2Ov7iIxKQojgjO3yLhzBlhlMd +Hl9wONcwyapJFnHXhw6ZagD6u+pvCbLWqKAOqEFN9MPpLuouP6R9+hJqgeRd6aDd +WLGV/9Uic0yedQWUUtLT7Y++a+2pYQcDME23f7+Z9aAe3udMwMwz9UPzcf7fKCae +8uTXRtInjl4i+7eAkfPL5hgm1EJXdwh8HkeytyTjw1bSMHAgG637UtuoXHPWLen1 +tsllEz6s3afXqtBgm8Kq8Q0b7QxJfoV2jA3Yc1uipZcSaBfUpqMfjm7oUbAvQxuS +GFw07mW6VM4vYtON2LubYiJi6U5ZF1XOepNWk5vaOb10X6trIQ2qL+qBaSSk/RGJ +N9oLYF8Q2aDPeGcZpwUhHHAv1cr9I74ZunosllzO0Pj4Jvbsim09YgJM5Ck+STow +RJEVa89QL5r/RmPfag6XAXWYuodzYAoFlRmq90XvKuJ3YfBdH/wgEJ6NfCNWQl4W +Vp41+UMhDB4TQGjdxaDaTIR5d4A6W7MNoZkVE+3TGSLkUUj5gIoy+TNWu9z0ZPFv +hYAsiQIzBBMBCAAdFiEExCr/fGGz5EoUVM01V692LbM1MyIFAlqheVEACgkQV692 +LbM1MyJ5nw//Q6JqB8n3b/I70635n1B7zmGA2u8rKjZQ2lZi4g0t49taWlMKjVZy +5oa64UQWWu0Nn3PcLZVQhPsJe2RNpEU0GYC7mUrazvnotdQe0JA/K0/8WLgXpcuB +B/8jojkpFMKcw0LQv1ebvy3vd2iKiGprjbHbdJiqtDFv1qthLcwPNsAk32k1G1WP +69XzkKL8ULrcLP40uDHqTSu165uzk5LOX0V31U2AiY2EFH1uqMSYrrOjgAKGJ4bn +Z+P0PvUFVzQiL3vuwxwWw3Vsg34uJGvprOpsa/r49YObLdCgvER1OyLIuFAWNfBJ +Dc9jKJa9Ze4feuJMSxCPsv53f93506CQ20bGQ4fXOhq5/dZNMnygAHPkrFaSBkmD +6sx27amVT/4Vr6g8p6SZFRISggTivBvoAz8IWSiJXDeb/CrpCEtpac+46/PW9TRB +8D/+nXyIaoVmcp23T+Mo9nbYEgoL9EfVi3Uv6BXTDHb3AruKxINWrmimvF4F+9rm +NHv8prBIFpennLOpHaQV1p5Dn74rL+r8P1a7aGjjk70v56TdN9ruwU1lrjjvnRS7 +Nd3w62YAiyh9z+BvxGy/GrUATAGJLhmltQSeGhn3UR6aQtsOZUHbehD8NXvwyzVC +cOJV0yDuEisldPnni20qDBkc3w6zfaUwlR4oAfu031lVk3lbje62q2GJAjMEEwEK +AB0WIQTtm9961qVeIy6EUkJX/5vbzDAQCQUCWqA8/gAKCRBX/5vbzDAQCYrID/9R +04RUtvl/AS8eSgeJyun/ZdzOfWf7mpzzOIZCICZgMugq26h44nEY3AYR+M0C39/O +q9RD149DHSO3UbkE8UDzhOOBkD7yCOuWQIoPanTiCrA3yhA13BwRNJA6H+KEo2BM +4yhd161UQJdLlPOBfKAxLrQWqP1D4bHdYZA2+4EsTXyIpd4NEOIq8I1DyU6yw+HE +9i4bdCKIDIQO9Bv/xmjzn7Q061jbUcsgCC1P1xiWYA80u5uG95/gNLEpOXJLYd3l +2hjUWhFsQD0TyJfXD/+eTT8XQiMh5AicbJspGhnFYBJkuSdS89FiHybgOzXjhh4r +0nEU/YWELhbYKsG15sr539k1yi+0n41TtGGcirwWI2VpvBiGNgJkFjKM27jgsmF6 +fj6ic6pzKJXc88VRy/cf+xWTeD8WJoZEjw/R+SqxGnsiCHNuIIUZsAJcSNKDEpIf +VPZpKePcNpOH8M/0/zyhGNtdehTaK+PC82wVtZxW6We0C0W8PEN36C0PGSdyJTYc +T4j9gxukbz6eZKfm7nP3CWqwS7ETK7Xr11GhG86EYhKU8vMBDbFXdT9elee5DXMp +9yo9uLSWl8/Qpo0Jx31u4RE1p1gQULpR/DkM8PkY0TkmJdP8rVL1Ct7gV5TX8gfX +nINBoE7B33EpYfKPwNeiZtGxitpTK9kNc+I5OqDHjbQqR3JlZ29yeSBTYW5kZXJz +IDxnc2FuZGVyc0BibG9ja3N0cmVhbS5jb20+iQI3BBMBAgAhAhsDAh4BAheABQJX +RxWuBQsJCAcDBRUKCQgLBRYCAwEAAAoJEGviztFKmRe8oV4QAM8+ZVy0UnLm+4SM +zi+Krqu3m+H/V7CVVBo/qY5gsLOtI83cD582Mjv99BpQXqRWT2yKAOt3bwr8nS7l +rigtm77TckeSMDPVTKBsPl6yQcCwlLG3zvDIZFepe9tCMJk5zZHYVJaUzr/PQR3I +Elv4AeVBSyygogYC3y8rl/DK5qFnkVvsIZL+CnfYWAtv2Py6o9xKbG6HNYJ5dNLd +szI94tKdPUf3XcktpqX1KtRLyuaUl6gFOzJ4qD1HZJ2wlhtpwQQeNN6Zu1cnFXMX +BPC9n6rrQVeDBJ0OVIZVMvpFU9fX8DUCvnBxVg0ZQym3aolvG3+RZv4jUYnsIwcT +aHNEXB70K+kQYu33J956l2nCZOmbCGOx3K5s2tpka+vB5LQxJQ+Sz6+DfempFupX +gnG9a0BwhLnmA7YatOxNWZpn6YdCe9BVZU8/qiRec13Iqu/Z3gsFSkP97MSLh60/ +1U/2NAgHdvTxctiaLJqCndOZMhLlrm+VQUUKaBK4sy/Ks3xgeW67DtzwuWRkzFn3 +o33ewuAfG342yoSaLOJfIhYenIAjca88nNvhxCDX8wzBIVNg/6JRSLWNYegWpeYI +kKSf73qNWs0Tp1pakAcyx6kfKUDb9UBVPInve5feRaQ1fCqynNVeBvyU5MY8erAH +99sdoFfNcLazxzv4z3WfsVm0VTNEiQIcBBABCAAGBQJWCoHvAAoJELGnDk+NzQNm +SpUP/iJTeS0ewm6W7p8JTrtGynlDHmb4kPZFais0ZoHGX3T+bBI2V52Jii1k85x6 +m3uDFAxEySYXXC6L/aif4hJEN/30MFhnClSfbUFAnTASCk869m66JCfJySj/or7k +ACbvzXXiN7utgmrH+pjoYgjPL38Tl+gnUFzRqHoN0+4ac2Tv4HCC4VEpyPP3Ba9I +uNrUN98sgmYrsiR84UAKm6Fn/BUvwzVuBNHkmLiB1GotzjmApchh/LXzZdiG2EFH +S9Zecplt5MHKueXeQcCL/uE+6VvU4Bn/o9aLzk5j59Z8cWEjn0KmoItwdco4ki9w +mubUf0UiJe8bG8UkZYpmjqdGbqWaLmpPU8QRaByCA64drGCB1SFToVlBsJHQ2WNc +Rgwcuf/QbO6naFpIXVQnrBnDPnZ3idUeWSC/lCEVTUou6QwFYhi3OtIal5t4Ienm +j1JJl+7yuuCwA7tHPQvYO6sqp8vRGN3nIwNvTPpWSjiZg0Wo+3sJPGZ76iP/zqIx +uvInJoxJ34jFL0A5/5hZXyn6keniaMldZ9e71yQKPTK+foOOXmPWOJtUzOl/BwLf +hm9cAqmuMoxlFbl0Z71yESHEnFXbGwyMkuxKRtkcpmA/xxF3XGzZs1Qt8zMofoUL +iXr3BCq2ryko5GYspVYh0aU0fCrgmHpRiKN6atK1DLyiXehIiQIcBBABCgAGBQJX +Rdt6AAoJEMUkKhqzk2UXB+gP/1llVVQklWi7rl8vqrRf1vt8GRhThrHm3637/PpW +fEqBBkz6AF7A3OngMp8HtWDh5iI7Psbg8LoADLu0PsTYJZeJxLyRilR1XFcAnmfK +KizSadStqkjuLsHdsGRcw3+/SRL1BEkxlwpGMs+YQwBhkAf5kbKKmg42HAaecMRn +qqYrhOIDQye72IXvi95bs0bvO2en7dkGy0BmwYD+wj0RWQWPjXNWdMD27cqRqjXy +AuInJ6P0EENTRMM36Fz9i0sDQ+mOmdAiNqQRJUyPjciWF8UXXBc2LvdL9hrmAffH +53dWcXIcUDs5rmFShh0trFTsWs7VDeeQscPkNh4eFMETp0zrJm+D7XtqfEiMq2cg +JJwTj0UA+4GHKm4XletwaKQCTeDCMIkp4QxQ30y8EhhNlbNICn5bZ2Dkxnt1m2AI +Ps80V6pLbgx2pgLH9va7t8VtzzY2uQngmdwMM4q5ELAOYwznny2jQteG3aOL64Og +wreBk1ppD6Gtkd2qVhgsHq1aFKYsCw3pnJhOZuyajlARLHONUuJIfe5JOkl9f1Ur +iFspVz58UdjCtWTkuBry0NzNwp5P2KeBW0JDNDiechg6FWHGQ4usxShC+KsVOgzD +YRfR1HmzzLY47uZRZ5WyGDok4NFHcoM/AfacIU4mtufDzLgXB9VD/vBrHreyHkdb +KnwMiQIcBBIBCgAGBQJXP47DAAoJEMDAdhMv+naVYhgQAKmiRnDNzWsKa0j84W30 +v0tV4sdEeQc5Pkp2aYGqYWnTXfznLuV92cbeqgKUBvk5+Pe6IsNPEGeKYm+wrHFi +XePSW0EXDeg9Hc5NAIGbaxrg8mL17K+2svxDtHqRpDRZUuJcC5lo0Ym2a9v3wWLb +/Vl9o7dYBMZNt4J+BD+23kF7XIIMfs7/HCvndFeiwRAi8dEkq4rMWeov5kn9xlQC ++sTZQraXPKTtAubGsbrPgcAv2wAOZiFk83YShkMNjjOp7FFw6DjJRIDmnn50goZ5 +rMziS12dZhFL9Fg8syc+SeBP7RWaNHZ2ajX2my6DPvXF4Oqo25pdLVgHufWG96nV ++szR2M8wRSdxs0kzbR2AMpjBcPZvQBQldCYPwZWXNVZJe/5C/wW0Y7pN+3oiQsDS +9e6HWOZuVpxDuw6HP/sF0aH1mKcSVQ5Hm9crfFgjO1RTHqU2lnbs5dKLsT5/S9s/ +MEtZpRbslNaOKmuAU7zouCReUXjc2d0/ozWiP60iJGoRUQS3bhfDtp4DUuYz4n7d +eZXt2e/dHUE4vGGhDaxk436lcC863Iq7k6vybBhHYQgWimL4cMln7fYwWzPvno6l +Kcd0+KTXyeV5IjVAivi4mJFHiuI911c5mUX7bGfYN6/2qhX7weoBAuEQ202STNvc +u0aDkr2B381D7qcxU+GRa2JLiQI4BBMBAgAiBQJWAtzIAhsDBgsJCAcDAgYVCAIJ +CgsEFgIDAQIeAQIXgAAKCRBr4s7RSpkXvP6vD/4uTBFGfETfLFVlVAdLE+Q0uAth +t+6Ce4/gVxX8gx1hwVnLb9bA0PdaWfdqggelKP59hbCuIlNpOiTWqO3vOI6syU7J +D60O0XxU/4bTDTyyKg8df3vyxaVdAGwa7xC/jhJZ98LdHH+68Cq7sPASJ+Tnxj3r +n7+QA8/CMwoIP3EXZQbipKUpo/2hNxHOf9FcvThZPFJoirl8n/7QrEfgl2oSryyc +DKl5SirGUZcfafs1RrWk4JOce7eDmK1o8AtYrnHQKjGOCZhwGUDjU1QPJT4yAD5o +cRDuPnrl9Q3VNe72Hsp6JirJwyxv1X2JddyW8/kJZ5Z774eJMvmMM2CC9buPMT7l +ZMeaCEp2xLUcmBrFgRBbNySSaLzJpDMeMPhWPuWL2tTOoaSpyIVcRII9WmaWrZO9 +bFq3p+x1YX8r9gklNc/qV4ECrEM+YH66AIcEq6e50oBxsSShgW9IgcWocoAMwffU +QhA3vUzcEaa7fEKoDnEpxYlO+3NZHrylV4jQLnLCVCuoXgOX4c6yHZ0k5XPPTYpr +9GnxGRzc82O158gm1m4zJYeFnnF/k/Vtyg/MrGHVm3vIgM27NsbenWr3dgaeGGbf +q2XSf7mAYZNIdCFn0OZ9SHaT04JHCYvafjaBa1MYQ1JaNsabN+V5jB1AAt9MjJ5h +UE90il5DYjmajUqbT4kBHAQQAQIABgUCV/BDSQAKCRB/qxFCZ+T6BB8WB/9MLEMS +UkyhXVWRRrc29WgqjPhOuyNDeFzym96b3Ug5eC7al8hJTWBVtBtegUSYYUTYrSYm +apENsKBfzBivBAYWyfUsrt13ItQYDLelJWAK3N4pZ7kzslC/wG8W58bfKmsxMh8n +5lqA83i9FiHOymDSG4uONFoYZ++RABLhptMtnar0xAKeW97NdD7bq3Es1nObAemJ +D1PKNbhk7qCbX18lxCZJUpIW0jOl23gzs1hhPXnKB8tWjj9rgJnIStpZAqTMTevf +1mnlrWWrOiHPAr8uFbkOG4tMsOD6X6/jm1COsKUWhGgtsnviUK2bMsfYmnHol+5a +7+dRQcoKswS81kKuiQIcBBABCAAGBQJZlzKUAAoJEBdWVzLgjl5B9B4QALF0+Ylv +iBCzSThqIV4cvI/Za09X9I90DxA/1K5hTWY5PIQ7mXAcB8PcP8OKXphSMSEcYuBx +CACl0FdMveZgKGOQVvWrHSNoXd5k4DfdE4viVL7T2q1sJ7/c4KnKX10UGDSKJukC +S4xFF1EVGQ5Gh9VkGO54FtFQ22kUbHDzqLVcTQM4dW+M/vaJqiVQH8p1xxOJbd1w +duiqp2nAZwxe2vPaV9wMBMU+VqqYcoBw7sQ4azF/39ZPLPNyLju7oOHhS2MnZ5nO +bul70Io1Vokp9idQ1E+GqLKaam+FOPmiN9Qfhx+D3xkwFNfEHOFDMb0yxnaCsAlm +EOISvq8fBmnh4LNm0Ms7SGtudyNZCBmC9nfG4Y3mBFcMYj9DwgVkV7D4Q2Cgx0OL +HAivVllqG5h6GYwKdCAdogtSfk5yViNfDUABh/XtTaKJ7MP30UHI6oi5YkFuo5L/ +NyNc35a86VG1shGlj9CiPeFoKaveIMrr1udRlNnPqrlUsoLpg1B4Swn4srY1ti95 +K0mFSsow0vsrQjzpnFGcFmA2awqwkJRFlT4/xDxyn8BHoGAq7HkY5itBIHE1+9Vx +bxF1D6GRzt5VkOc2SzTv6HMiw9tU4NLjGo2hSI3I40ksP3yS60GNg8U+JCs5z1Xm +Sc1o4akB+F02UuaQfIrUZrdFXxs18WV3bSuuiQIyBBABCAAdFiEENfStpiPrn+Oj +vH72e6A1yluQFxMFAlqgNXwACgkQe6A1yluQFxNRmQ/4y5dtLcQ34acImV3B97y0 +vikvah/NymgUVd49K+kI8EYdf2/lIIm/V+mvkW/CvgCpzlCMXlhvzhAQFYwjQ3SN +iEWm3lPT95JFsb/A+4t5ZndTG21/T+4sR/3E7lOlAifsQ5N4rItLNfGol/HcF9RK +dvGB8+fByeSOySAOfF45clOX1aEjW4fA4mxvdG4+MDKJmw+SKx59J+5dyIyCwBRx +nzxefsNVS/8fMu0bR3HTSzEE6x0Nb0thyJRfZ/D2gj9v/szkRTpq4/VdblEq6XR4 +/MYlAdAPWD9qWoDaLTF0M6v2fRu0ZYf0jatG4XK9v4K5zOyDoMHGs78y1otbVhBd +/rfrpda19vInKn4yfhFOz7D/uqu+75r33E8/nxIgNbtf1UJHB1pB6/nI/aLIK0pD +v6VkHYCtG/ZVmH+f217BHKlHajJvhrKe0rARxo+1U0BrD2NwT9sWeSytoTv6hWX0 +Ck86v7qN+ZBSqEvxURb4BIFdKr4erWa+HiHmxMYjHsegFABsautE0Sih3mLiAOz6 +Hvu/wlFQX4tIOJiekLACfXALJDrt85k9TgFyhA5vdzMy/vAjKB21SglrCMZG4OTJ +uXkGeGpsZWpIv7vzHev3Ljv8hBpZKs8TkA1MNbm3sBYfhNq2bgQ17uFNO4OHlwpF +726PJMbvhYJREl0h/ZkMEokBMwQQAQoAHRYhBA5MoSvha+aRVvVAyZhPEMx3Fp/S +BQJapXi4AAoJEJhPEMx3Fp/SXloIALL8WFn0I6xVOMpXya9K4emVrpYop0Gh2OuV +hjzU1blTT1B+w8/hz3E+gisR5UuYzSYOo3eA8sIlcS/lb+BI0UsqSY5aQAckBs/4 +z4zeLlyn2gwN5aSwpMh4xsA2HhdRAuNRnHq+RAO6Xqj9MyiXg0sZgcnnJty/ERpM +f6wZYpRu0aQUG2qWH9mKsPJ980/qernYpvpC8ji07f0AXtlqP40fWKi0O9Wu589B +YrEYd+zF5bAokkT69FPHC7Po+GmJN55sEmjpyPR4nyC5A00bi/zIiQ/bRLH08OgX +TokihvnsEqCbDi2i08eLwA7Mx+cNAYvNR94S61CzNbDgz6jhVGyJAjkEEAEIACMW +IQRgaFswqhgzq8lGLChf4I0aOmH+zQUCWqci1AWDB4dxAAAKCRBf4I0aOmH+zXop +D/9m3KH4dU8Us5aEsFW2ovcy9meU7UEEfz+pB92wjmbgn9ANhk0DREDqjew7CyCq +9sQyxQaiuV7Y7iSm9mdfC6dWg0skbyGKvNb8vAyi6h6lMtkXNvKPmqdLXBER+5nT +/0z9i0eNlieyMXcbM/FJf9JHBaDplqwCz8c2fBh9eMXrNzZoqqJYNC7Ry2ks4+6g +9TOZP63pCNXgumzvEc6ggxwbZOQJqHmNAVGCWuxwNw+MzA9Zyal6RtvI5O0f4m2n +SIGiDUgByExT3h6yq1pYQ1Jc5otsrZnExvMtdsd3Z4Qt+msw+Lt6Fce3IPkJvOM2 +4hQ1oZswB3cMwJfBy0OyVNEcEb+QW9BUZS3fr/1K48dHVQF8DN69B8jZJWhaPMXL +b86Xjf0jkpPLQpLKyfB2kgtbX1MfbA0Pfp5BMtur07EgjaVelNAsh9S+pfDa+NNX +KsXcATeWvlAlsacHlzDEUU2ctEHIAt79/breImLP3zVBG8XPuasje8Ahlr46roYF +VTbI4v87XBJXZ/o3Dt/IJN6Zyce+1qlGOPn9WKeZtPVKeUDxzVuYh2T2V8/2VLU3 +kBI8JEAKaYW4+9UYGudOH3p9JW7OoNwl/GAE30MTgBKErqANe9mmttCk7Eh+cm4k +HaKhsAaeD0DCe1QpFfTUjOtIq0md2AhQQHYUdJAGIL9Wb4kCMwQQAQgAHRYhBK+R +cxi4xC0RJyFiXRV+/Ky8ZIQiBQJapzUPAAoJEBV+/Ky8ZIQizf8P/2PjGqd0G7Cb +2YYsy75yxnOL5E+JNKg6sgZiCMtpMcIqX9xxXJ4P2OIw86c9wVufgYaIMs90qoLV +a81eKm0m6Ak3s+zOtemqohtKgf8bYsfqJxS8E93TF/8CahNOCqb5M7yG/ByAxiOm +HnfSU1FDDOivjnzwFhvf44arE6LpPyjbFqenprNZfA8oO9A3fGgLcEIKfrN20Xlv +Cy5TClSz1452RyQop46uXkX63+UodNxHNZjfZ0oY59k8QZStITlrZQkJRZngfbNG +/qQL3+yJ6TsMVwMi/ojYXLddtSn7Y0uoBuSDMEh4093VJ4W9FkBviBTHnaKNnuBx +LXSB+gF9BjckWza3Zjmb4J/qa491t8owrGNM+31hDyeIHjekTB4ge7DSiOUDjQ2k +ACz1NM1B4hiqyYyEOOjUqofzJyiAKmMGuIB+6e5KR56Cm58nNz3kSicvCtDJd4p0 +csuXSv0wLAynR8LAFuGSRWuPmVfOEfubiha23DKhYhDX5/pM11yEGJq03w0UwmCF +q8WG5fvKPbuzOPxhVOyMgTuu8t10nqj3N2kCsOCN2EE91mv9XfVBX/ACKLO4YLtl +i9azRU8SyaCBVrx+UAFvOLOYYgxIccY1O/RBZ9NcM8Xa0YwCBY829BrPZ5gzL8JH +K621vsyqg+WnZc0QDsHel6xNrBvqoTdGiQIcBBABAgAGBQJapvH1AAoJENMAEW4c +h1o9a3MP/32FQruC2vrAp5+Z7Bj9tQFRXoTbbhHcvbYpgcxE2E6GkFZsOwynZPMd +QgZmYpVe4zV/dc8daXc6EBtK1lfjveJktKDJYQP0l8468aRBZ8yDlWYDIVP3lz5m +OcTPB7COjThj0/u4hynfv94XLfxTY8Npagr7BQv6zwQyp9/XQK1RsSP25bJWCRmQ +Ucyc1OMeUw6Cdf5bAtFhnaL9NGmb3OOwPslR2BoiZnQWDPCd+pZusE9OGUkZYs29 +uSHIaeK4zXVlY0BDEirTQxRO5kHz3lCOZHX/S4bkMRpHp4b6zAq/acjbjIrl+3VD +d0J7WfRH+5yhJJpE+J9NH6EZm+ObSaXSI8hR84ejwARS4mOXuaEB0wk+tObWxXUe +CPYxx2UfBFB6RMPQXdMKFFnqZ2/b0I74zS7+WnrGbjWlS0fazjISUcL/Mn0CilyY +NPgGiqwQRyay1APFrN6ipcfKmOO5wKqlKCRZDoOLVmBiAVSZqjdgz37WyI4Dnj1f +xfUjkQylK7VxNHLIpFMFL9rvxk7Ik1M7o9hz7BaYXzpPOJCBB/UimlOKSULQD26O +9dL99fn37l9No96/6MTwzCfh/d75zZYbeRQv4ig4hvPFv+rrxZbt3TWUrr98eq1H +tW1n2TeoghN4nA9wiOpSqqs9JekwfLggcvMn1ktTp7qUezBZIx6UiQIcBBABCgAG +BQJapaweAAoJEKJtbZ/giO1YjxQQAKjSifwKVg+FG9LRQ3w/LzPPf07xAetStHzp +7NTK3OHkGHY8Iov9miFolhBk5xwWTlmUxLQwFDDzlSzwkSPXJ8PRyBkGo8OSL45n +BZEPxFXAKKJDRT4ZxBxo1td2zhGhQ2x5sEucE8MfN4C2zaFElWPgcPKXS0n6WEr0 +U1geRHp1ZZD/gVoZRGKBj4SonJ5synpcA0JmeNc0DjzOYVIxExBGkpfc1/u9VfH4 +yMqwg9o7/eB0Q12KYcaqHmpWY6h4i/YUopUW2XTOM1wVumgAcDi+sxybmSoRfMXb +zr8CwPtahIr/uoK9SvRHQ064q0ZoMeEf0vTWPP9NNrKG06h6mUQWSIcOZPLj1CNr ++iN5slTClrhxBzVG/jc+PjEhpyoC6sP3PdoPezuAzj+KzujpJNigt6I8QHsUKo26 +Ei1MIeuNmbvGj7RNC4jCNMFQADCekxOxqC1Ty7MEIK/iQb9/cg8cZQ075Q76lv3+ +FYi59x8QT3DwDgReLONvnf5iHtX6302mCDQvr/WTcgdR3zkx0Y45dz8djmHY9Qa+ +e1Ew2M3rFJDHmeXZzchpFuGw6fKduIJwIiZJAxMM1mwliyL6hvOz2Wixl0OZCa0E +STKSBpmuNG3KusZIJbunrTJvmx4zYTma6vkcVZgkVnOX36SopS6I1NeIu80fbZXm +MRoJ1sGqiQIzBBMBCAAdFiEExCr/fGGz5EoUVM01V692LbM1MyIFAlqheVEACgkQ +V692LbM1MyK7pRAAlOeXKbPti8YXz5tG+ih2gVX9MacDYSIJb7tic5GdBmckN1O4 +YsmsaXk+ckXt5Cy78m2psT3aCV+w8fRV9Twny9caA2/ZTRbbSXoENXrdzwlAf54M +jmAWEzvIXLK4hXXbpXu2hoDrS3hZDSrWV6r+7c3rqwBHAMRc4PLjkFjPVAyhD0c2 +xIcaPoneexuvgMyeqCa3AoLOtCynZpzGwas6i57M8UvdANnH6uo+NPLR4GB1aY8Q +P/neqyi4+MqhZF8I/AEbYbsufCxpY9txbMJaOPTZHH6aqlPeEPxVpnXxIpzk9Xdf +rouTfzNE2X3rP1FHrOcHYkb2mXSuS/35g7pnAltniheSJfLY/wj9npGrZW5JjC9c +FCe+yf8C/sGmjnY4wK3zuU/8J8xXpua5cFchtIRMcgy2CitNZ0K9ZcYR0vLPxFxc +yI+HbhktKr7J4VBQHkqDCctPixJvqhT9/V/QZ0LU+Qi5vt9U8uqiBljY9yDmM4Ca +fhTGYMvJSsYeGyNXCWhYMVjXjqdGcuFwkdC24ErUU4Rxd9h43l91dHtRFdOlNcZR +EalIVjvkq8IBo1Wu2QisppmkopR608lefbgbpTeGioZ3s1eaW/xY9Fjqc73COlFC +jiBHgDvzJVkWfqlJvkCOxz0zirkhFrp5ztqQTiVpZ3UNREwqzboXv1E/O7OJAjME +EwEKAB0WIQTtm9961qVeIy6EUkJX/5vbzDAQCQUCWqA8/wAKCRBX/5vbzDAQCUYB +D/9uN1/epavezg3Ngddzxxs5VHiz410Li1ybyEx681NbbmX0cfBFEOQmUT6WZHzP +xMXatLIoNOqvYIqXintZ1s+xI8Pntq4LJOSgtCEFER5eshU0AThsrfO0FGKq2qon +HCt5RZ+1A0WsgyW4WXyHCStm0CNE9Pw0SFsc4/zrYlOYBgTKBpZQ4rWYHZo3ER+2 +8vpHKijyUmMAgpix/HhL9sTklhk08/Is5oCkO8Ds9LVBv5G0nZYytUlLTp31Dfop +xsiSO3FI4MXFATUlEgdhgZwKiOwLi+gJ2h5i8d8oBstZ1q2zr1j1IQXTrqoogEvw +LfbfZ8ZvjtwM+UgbzE5VauON06RSHNDtvDoTifDj9or3u7cawtiTgVJ35H8KusTe +BlC8KAlA7QjLtdGE0IhqTfY3NzE9ni/KtPc4+GMsNCFygOuZq773dqcwekmIt7Te +I8dsskiz1KXmD8BSThqcXsx4ZbxD5VlDsKiSaSiNHaV1I28Mb8pZkSMv6r7PL88r +PH6+CFiMWTrIoVVGXoqQFm2Ka6nVNPGAOr6dv6X9uCRwfCu01O8fxKFsK9ScwZRr +FIfjJ6eH3Nige6T2SV2Jf29WPqAlfkJNUkUcj0GgbbS2sY+LiE1EALpqBH3qb4cU +gjzW/yAWg9u9DTBlwz9sXkPpHcBh/W4Kr8ypVwQ/zcaNs7kBDQRVp/OLAQgAprG5 +CHRskpBJrg4pVUrjtPYF/5sOFi7zGqAhsXsmWTKLgRPmb7VKaT7hcDoFEMPVgOEX +WGfK1csf2qCoTrzCVALTTDlVYmEXctQ1I7O57bQq56RF1yzGxpydWQP+c+qpvQD8 +Sax/xqIyPmtgrxU1BQNwB1sudjyE8LrXy2ZKM7/8LNwbZdk6+mNP6QyYHMN8XklU +1cZari5rXMXxxxXNOVTToHYDI4P9B/NPpPoa42xIJ9kmwfWpzT/W45p7yAHwOHtR +UZo0B6EyqC5vB1cOPBVruBADXi5tWTCxPexN1mif7iQQw3ibLXgmXjwRyLm5L6Hr +5yJc7OTjNIXdw6ooTwARAQABiQNEBBgBCgAPBQJVp/OLBQkPCZwAAhsMASkJEGvi +ztFKmRe8wF0gBBkBCgAGBQJVp/OLAAoJEL6KgWWH/15frhUH/2TC3enmmt93W+Sa +4yVwBSYzg0xLyi1Doa9i15xTisj9qG0SlC7erDPv2QqAImF4E+/tmWmpUOcJbMeR +K/H8zhYjkJXw3RJRyflFt3c4PHTDYcSKXj784TFNlfTlqMFDLtEcKVOq1OBTMjnn +di7a8cTpfHBLA4GUFnh+0Ad20hd4NP4v4aDWyMhgOg5XcbvC3GoMIgtwsoM3spbJ +wpKDT3DSdfI0fRgkxLCjh+AWtGDMU7jfNgwdP8aoO/YrQ6Xw/SleoaFXZzopJWjN +ZnRDMNq2okzFCCzfRIRif+PRSxoffftQ9MKLgm45Je5FfUNX1c0EhPqolbe7CX6l +0J1y3bLNuRAA6ZoMbFUiau4xUwxlTT+6P3tZ4I5Yi9bH+PgRTBpdvQ9ernvRqFr2 +jdAHmbq9yXs+3Lggqp3STwNvthnnsnusGbVgqurOnduNGZHz6L2DLx+3fdXbSU5V +BPuzLbqemEgtzBviHM5LH0rx5b6vA3KnKfIIC9pKlCJNfIfGR08hP/n3eT4jmK/J +RIr080Y5MqB8JnDFgkLJMOlW8AtPVWQvVXPbjfk9LQ179D1oihE4hocUyO4x9eWY +ZwGOhQHUyTwv06eZ67p00COtnJ2Cc0d72R3ddHrQwQ4eN0KBXBMq/VrJJdqU0qI3 +Vp04SO0mO5jcvo9mupSUVYM365fhqYKbdNrRTIty11A8Dgghv9QfasFSPHj8cUqR +d9tAlH5A4taV8180bJP0YoNxVsOWRjgXQzxgqi/DDklLYGBD5Bz7wu1U6z7xdkti +0a+m5qcZblsExmCiMcE8DhsNgpNBRe7mUaeHd7ETw+6guI/CzWWiEmwrEB0eJ6GF +WyX3VnhKR09U//mEpt3C/xk0faG/+/cUWuZRS8UoVAojPuICXVAOgGD6iUjRBnU4 +tMQkbcvxR8OTG2I5TGPFdKCnBNULQqFUQrxa4RffKHhgdPdes7dqRR+kqa71tJ5s +kE3Lqdc4F4FTqUPuWzLjOd+FonCABF3+p+dRr+6917hmnqCvaMQpnji5AQ0EVafz +iwEIAPM2TTfR5kMdWMFNLYEzhqE9SByXGT6PAezjn46sGBfZRNyXAInny5uDDbAX +bOyy4YfoUNlNqP2u5w7xnSD4CENl1sihQQ79IkH3DraFT4yJB2Xpe6WITm+T8rYE +ymd/w4AbY6B4/ZxPLjaknzweyyrv8uP8hR4q2V97S+JVryauCeUsvHsQv49y//1p +Ov4c979PVnxata0TxuLa6+VkuFYVeJ4LhAxw2pl3FMoGZuGbiLp0D0krHqXcRPVO +8p7IjV1C0ZSDvR3nabEl2zJtxI2Q4Ezf8XD8chFBlNM6jalDjmfeNhANOmCKhE6E +EH/P8NNSSJto7mkQ1CLFfK7DHU0AEQEAAYkDRAQYAQoADwUCVafziwUJDwmcAAIb +IgEpCRBr4s7RSpkXvMBdIAQZAQoABgUCVafziwAKCRDz9o4thqSP2x1yB/4tASi3 +4p3qznwqB9zeufIw5x199go6tdi/uSCgw8e8g4kfcgS4SE1Lne5qKd0OKvevJVln +Di+B2u0QWqhbgJl/MznIpdH+GOYGGb6b/VnQloJSZctjfEZ3wO6htxpjUSAdi36Z +JbMbC1Rt2XG5yeEYdXEufjjzlar56Wub1t1IqkVhTFEbWCMSeUVPPsiZ7oWzdNAg +T3GFAf6qdeI5BjZj4FzNQb0Xmpnf8O1fygSZ+DxP2obzuwRoIqAkmLPMmu3ZpNX8 +ZSfkqnNqDhVL1EXipkAStNTCQElk0phOtxYiwiYcl8yqGdIDo/nHYU4Ree9/u1HH +l416t+g2HB3MBdPo4zMP/2g9NWn/Ew7UduzngGAcsPqTiepVilRR+7Ep3yeQc5s4 +CiR1SEVjV8BfDPupOHERwgZdMMQziAe3AQGGn29QO3sQnQJDLP/WwDABIuCE4N7p +Yf1lKm6oJ5SBQAc1BzjqqAV0tZSrtIHYarPCcaJBvs8KpWPpvR+XszDkk/2TfrfU +WGBJCUmazSU/gyS7FzWLq884PsbGugJ440VZHnn7UfMcUKRpaPzjUOmzNhuKw8Cv +9f6V2JL/zTrOhtf4YXst70WxcMLGER7zAGu0k8lbm0aUV48CE7fmpseQUST8EOA2 +UAEzewF2ejzajsO4v6V/kRbxV94qbZhjq/lpr8LGYEQmEa4MrEqbDcpuh0AT5Sz7 +N+YLuFbTULYlRWr7b+/IqT4h2aoRoP0fySWDIudP6YI88GhHWKCA05SEQbJiF3ik +f4PA3WSvVRHbX7MG1BQTgqzPIYKWJcUp54kouIsYr5xdYTmkskjCNbL996cyXlF2 +Tzr2VhMvOOUqPaChY7hXbcwfB7B/DH4vfmX1gnWPRRwhBR27xO5ntahvYZQJcxX2 +gpTTPn5RJ35yoDRSitb/eaZ0UrIw4OxAYkU3C51VxyAnDxYaq8LBWpz7h0jbPCNT +LmUR3edNAlDWF/cBqdcS6LBA4wrbKy/Xz6glNivztxxrBeg18QMg2FK7NEwGBMBV +=bKfQ +-----END PGP PUBLIC KEY BLOCK----- +``` + +## Escalation + +If there is no response from the authors within 3 days, please escalate your vulnerability report by emailing security@blockstream.com using this PGP key: + + +``` +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +lTHPj4G8vH5F/zIFb+uQNSlKzuH+ +=8mAH +-----END PGP PUBLIC KEY BLOCK----- +``` + +## Acknowledgement + +Your help in maintaining and improving the security of this software is deeply appreciated. When the vulnerability you have reported is patched here and in downstream dependencies, we will publicly disclose it. Let us know if you would like to remain anonymous or would like to be acknowledged. We would be happy to acknowledge your contributions to closing the vulnerability you found in the release notes, source code and/or documentation in this repository. diff --git a/configure.ac b/configure.ac index 6d48bed030135..491d17284a0b3 100644 --- a/configure.ac +++ b/configure.ac @@ -256,7 +256,7 @@ if test "x$enable_debug" = xyes; then # Prefer -Og, fall back to -O0 if that is unavailable. AX_CHECK_COMPILE_FLAG( [-Og], - [[DEBUG_CXXFLAGS="$DEBUG_CXXFLAGS -Og"]], + [[DEBUG_CXXFLAGS="$DEBUG_CXXFLAGS -O0"]], [AX_CHECK_COMPILE_FLAG([-O0],[[DEBUG_CXXFLAGS="$DEBUG_CXXFLAGS -O0"]],,[[$CXXFLAG_WERROR]])], [[$CXXFLAG_WERROR]]) @@ -1459,7 +1459,7 @@ if test x$need_bundled_univalue = xyes; then AC_CONFIG_SUBDIRS([src/univalue]) fi -ac_configure_args="${ac_configure_args} --disable-shared --with-pic --with-bignum=no --enable-module-recovery --disable-jni --enable-experimental --enable-module-whitelist --enable-module-rangeproof --enable-module-generator" +ac_configure_args="${ac_configure_args} --disable-shared --with-pic --with-bignum=no --enable-module-recovery --disable-jni --enable-experimental --enable-module-whitelist --enable-module-rangeproof --enable-module-generator --enable-module-surjectionproof --enable-module-ecdh" AC_CONFIG_SUBDIRS([src/secp256k1]) AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am index a2d67258c9b5c..e9470bdbbc1ef 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -92,8 +92,12 @@ endif BITCOIN_CORE_H = \ addrdb.h \ addrman.h \ + asset.h \ + assetsdir.h \ base58.h \ bech32.h \ + blech32.h \ + blind.h \ bloom.h \ blockencodings.h \ blockfilter.h \ @@ -111,6 +115,7 @@ BITCOIN_CORE_H = \ compat/endian.h \ compat/sanity.h \ compressor.h \ + confidential_validation.h \ consensus/consensus.h \ consensus/tx_verify.h \ core_io.h \ @@ -126,6 +131,7 @@ BITCOIN_CORE_H = \ interfaces/handler.h \ interfaces/node.h \ interfaces/wallet.h \ + issuance.h \ key.h \ key_io.h \ keystore.h \ @@ -223,12 +229,14 @@ libbitcoin_server_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_server_a_SOURCES = \ addrdb.cpp \ addrman.cpp \ + assetsdir.cpp \ bloom.cpp \ blockencodings.cpp \ blockfilter.cpp \ block_proof.cpp \ chain.cpp \ checkpoints.cpp \ + confidential_validation.cpp \ consensus/tx_verify.cpp \ httprpc.cpp \ httpserver.cpp \ @@ -290,6 +298,7 @@ endif libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) libbitcoin_wallet_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_wallet_a_SOURCES = \ + assetsdir.cpp \ interfaces/wallet.cpp \ wallet/coincontrol.cpp \ wallet/crypter.cpp \ @@ -358,6 +367,7 @@ libbitcoin_consensus_a_SOURCES = \ amount.h \ arith_uint256.cpp \ arith_uint256.h \ + asset.cpp \ consensus/merkle.cpp \ consensus/merkle.h \ consensus/params.h \ @@ -367,6 +377,8 @@ libbitcoin_consensus_a_SOURCES = \ prevector.h \ primitives/block.cpp \ primitives/block.h \ + primitives/confidential.cpp \ + primitives/confidential.h \ primitives/txwitness.cpp \ primitives/txwitness.h \ primitives/transaction.cpp \ @@ -403,11 +415,14 @@ libbitcoin_common_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_common_a_SOURCES = \ base58.cpp \ bech32.cpp \ + blech32.cpp \ + blind.cpp \ chainparams.cpp \ coins.cpp \ compressor.cpp \ core_read.cpp \ core_write.cpp \ + issuance.cpp \ key.cpp \ key_io.cpp \ keystore.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 0f6895768f8e8..f7668d804be7a 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -96,7 +96,8 @@ BITCOIN_TESTS =\ test/util_tests.cpp \ test/validation_block_tests.cpp \ test/versionbits_tests.cpp \ - test/pegin_spent_tests.cpp + test/pegin_spent_tests.cpp \ + test/blind_tests.cpp # ELEMENTS IN THE END if ENABLE_PROPERTY_TESTS diff --git a/src/asset.cpp b/src/asset.cpp new file mode 100644 index 0000000000000..9831c7ea5c040 --- /dev/null +++ b/src/asset.cpp @@ -0,0 +1,149 @@ + +#include + +CAmountMap& operator+=(CAmountMap& a, const CAmountMap& b) +{ + for(std::map::const_iterator it = b.begin(); it != b.end(); ++it) + a[it->first] += it->second; + return a; +} + +CAmountMap& operator-=(CAmountMap& a, const CAmountMap& b) +{ + for(std::map::const_iterator it = b.begin(); it != b.end(); ++it) + a[it->first] -= it->second; + return a; +} + +CAmountMap operator+(const CAmountMap& a, const CAmountMap& b) +{ + CAmountMap c; + for(std::map::const_iterator it = a.begin(); it != a.end(); ++it) + c[it->first] += it->second; + for(std::map::const_iterator it = b.begin(); it != b.end(); ++it) + c[it->first] += it->second; + return c; +} + +CAmountMap operator-(const CAmountMap& a, const CAmountMap& b) +{ + CAmountMap c; + for(std::map::const_iterator it = a.begin(); it != a.end(); ++it) + c[it->first] += it->second; + for(std::map::const_iterator it = b.begin(); it != b.end(); ++it) + c[it->first] -= it->second; + return c; +} + +bool operator<(const CAmountMap& a, const CAmountMap& b) +{ + bool smallerElement = false; + for(std::map::const_iterator it = b.begin(); it != b.end(); ++it) { + CAmount aValue = a.count(it->first) ? a.find(it->first)->second : 0; + if (aValue > it->second) + return false; + if (aValue < it->second) + smallerElement = true; + } + for(std::map::const_iterator it = a.begin(); it != a.end(); ++it) { + CAmount bValue = b.count(it->first) ? b.find(it->first)->second : 0; + if (it->second > bValue) + return false; + if (it->second < bValue) + smallerElement = true; + } + return smallerElement; +} + +bool operator<=(const CAmountMap& a, const CAmountMap& b) +{ + for(std::map::const_iterator it = b.begin(); it != b.end(); ++it) { + CAmount aValue = a.count(it->first) ? a.find(it->first)->second : 0; + if (aValue > it->second) + return false; + } + for(std::map::const_iterator it = a.begin(); it != a.end(); ++it) { + CAmount bValue = b.count(it->first) ? b.find(it->first)->second : 0; + if (it->second > bValue) + return false; + } + return true; +} + +bool operator>(const CAmountMap& a, const CAmountMap& b) +{ + bool largerElement = false; + for(std::map::const_iterator it = b.begin(); it != b.end(); ++it) { + CAmount aValue = a.count(it->first) ? a.find(it->first)->second : 0; + if (aValue < it->second) + return false; + if (aValue > it->second) + largerElement = true; + } + for(std::map::const_iterator it = a.begin(); it != a.end(); ++it) { + CAmount bValue = b.count(it->first) ? b.find(it->first)->second : 0; + if (it->second < bValue) + return false; + if (it->second > bValue) + largerElement = true; + } + return largerElement; +} + +bool operator>=(const CAmountMap& a, const CAmountMap& b) +{ + for(std::map::const_iterator it = b.begin(); it != b.end(); ++it) { + if ((a.count(it->first) ? a.find(it->first)->second : 0) < it->second) + return false; + } + for(std::map::const_iterator it = a.begin(); it != a.end(); ++it) { + if (it->second < (b.count(it->first) ? b.find(it->first)->second : 0)) + return false; + } + return true; +} + +bool operator==(const CAmountMap& a, const CAmountMap& b) +{ + for(std::map::const_iterator it = a.begin(); it != a.end(); ++it) { + if ((b.count(it->first) ? b.find(it->first)->second : 0) != it->second) + return false; + } + for(std::map::const_iterator it = b.begin(); it != b.end(); ++it) { + if ((a.count(it->first) ? a.find(it->first)->second : 0) != it->second) + return false; + } + return true; +} + +bool operator!=(const CAmountMap& a, const CAmountMap& b) +{ + return !(a == b); +} + +bool hasNegativeValue(const CAmountMap& amount) +{ + for(std::map::const_iterator it = amount.begin(); it != amount.end(); ++it) { + if (it->second < 0) + return true; + } + return false; +} + +bool hasNonPostiveValue(const CAmountMap& amount) +{ + for(std::map::const_iterator it = amount.begin(); it != amount.end(); ++it) { + if (it->second <= 0) + return true; + } + return false; +} + +CAmount valueFor(const CAmountMap& mapValue, const CAsset& asset) { + CAmountMap::const_iterator it = mapValue.find(asset); + if (it != mapValue.end()) { + return it->second; + } else { + return CAmount(0); + } +} diff --git a/src/asset.h b/src/asset.h new file mode 100644 index 0000000000000..381df84c4f11a --- /dev/null +++ b/src/asset.h @@ -0,0 +1,108 @@ + +#ifndef BITCOIN_ASSET_H +#define BITCOIN_ASSET_H + +#include + +#include +#include + +/** + * Native Asset Issuance + * + * An asset identifier tag, a 256 bits serialized hash (sha256) defined + * by the issuance transaction from which the output’s coins are derived. + * Each output contains coins from a single asset/currency. + * For the host currency, the similarly-calculated hash of the chain’s genesis + * block is used instead. +**/ +struct CAsset { + uint256 id; + + CAsset() { } + explicit CAsset(const uint256& idIn) : id(idIn) { } + explicit CAsset(const std::vector& vchIDIn) : id(vchIDIn) { } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(id); + } + + bool IsNull() const { return id.IsNull(); } + void SetNull() { id.SetNull(); } + + unsigned char* begin() { return id.begin(); } + unsigned char* end() { return id.end(); } + const unsigned char* begin() const { return id.begin(); } + const unsigned char* end() const { return id.end(); } + + std::string GetHex() const { return id.GetHex(); } + void SetHex(const std::string& str) { id.SetHex(str); } + + friend bool operator==(const CAsset& a, const CAsset& b) + { + return a.id == b.id; + } + + friend bool operator!=(const CAsset& a, const CAsset& b) + { + return !(a == b); + } + + friend bool operator<(const CAsset& a, const CAsset& b) + { + return a.id < b.id; + } +}; + +/** Used for consensus fee and general wallet accounting*/ +typedef std::map CAmountMap; + +CAmountMap& operator+=(CAmountMap& a, const CAmountMap& b); +CAmountMap& operator-=(CAmountMap& a, const CAmountMap& b); +CAmountMap operator+(const CAmountMap& a, const CAmountMap& b); +CAmountMap operator-(const CAmountMap& a, const CAmountMap& b); + +// WARNING: Comparisons are only looking for *complete* ordering. +// For strict inequality checks, if any entry would fail the non-strict +// inequality, the comparison will fail. Therefore it is possible +// that all inequality comparison checks may fail. +// Therefore if >/< fails against a CAmountMap(), this means there +// are all zeroes or one or more negative values. +// +// Examples: 1A + 2B <= 1A + 2B + 1C +// and 1A + 2B < 1A + 2B + 1C +// but +// !(1A + 2B == 1A + 2B + 1C) +//------------------------------------- +// 1A + 2B == 1A + 2B +// and 1A + 2B <= 1A + 2B +// but +// !(1A + 2B < 1A + 2B) +//------------------------------------- +// !(1A + 2B == 2B - 1C) +// !(1A + 2B >= 2B - 1C) +// ... +// !(1A + 2B < 2B - 1C) +// and 1A + 2B != 2B - 1C +bool operator<(const CAmountMap& a, const CAmountMap& b); +bool operator<=(const CAmountMap& a, const CAmountMap& b); +bool operator>(const CAmountMap& a, const CAmountMap& b); +bool operator>=(const CAmountMap& a, const CAmountMap& b); +bool operator==(const CAmountMap& a, const CAmountMap& b); +bool operator!=(const CAmountMap& a, const CAmountMap& b); + +inline bool MoneyRange(const CAmountMap& mapValue) { + for(CAmountMap::const_iterator it = mapValue.begin(); it != mapValue.end(); it++) { + if (it->second < 0 || it->second > MAX_MONEY) { + return false; + } + } + return true; +} + +CAmount valueFor(const CAmountMap& mapValue, const CAsset& asset); + +#endif // BITCOIN_AMOUNT_H diff --git a/src/assetsdir.cpp b/src/assetsdir.cpp new file mode 100644 index 0000000000000..0b897828a3542 --- /dev/null +++ b/src/assetsdir.cpp @@ -0,0 +1,99 @@ +// Copyright (c) 2017-2017 The Elements Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include + +#include +#include + +#include +#include + +void CAssetsDir::Set(const CAsset& asset, const AssetMetadata& metadata) +{ + // No asset or label repetition + if (GetLabel(asset) != "") + throw std::runtime_error(strprintf("duplicated asset '%s'", asset.GetHex())); + if (GetAsset(metadata.GetLabel()) != CAsset()) + throw std::runtime_error(strprintf("duplicated label '%s'", metadata.GetLabel())); + + mapAssetMetadata[asset] = metadata; + mapAssets[metadata.GetLabel()] = asset; +} + +void CAssetsDir::SetHex(const std::string& assetHex, const std::string& label) +{ + if (!IsHex(assetHex) || assetHex.size() != 64) + throw std::runtime_error("The asset must be hex string of length 64"); + + const std::vector protectedLabels = {"", "*", "bitcoin", "Bitcoin", "btc"}; + for (std::string proLabel : protectedLabels) { + if (label == proLabel) { + throw std::runtime_error(strprintf("'%s' label is protected", proLabel)); + } + } + Set(CAsset(uint256S(assetHex)), AssetMetadata(label)); +} + +void CAssetsDir::InitFromStrings(const std::vector& assetsToInit, const std::string& pegged_asset_name) +{ + for (std::string strToSplit : assetsToInit) { + std::vector vAssets; + boost::split(vAssets, strToSplit, boost::is_any_of(":")); + if (vAssets.size() != 2) { + throw std::runtime_error("-assetdir parameters malformed, expecting asset:label"); + } + SetHex(vAssets[0], vAssets[1]); + } + // Set "bitcoin" to the pegged asset for tests + Set(Params().GetConsensus().pegged_asset, AssetMetadata(pegged_asset_name)); +} + +CAsset CAssetsDir::GetAsset(const std::string& label) const +{ + auto it = mapAssets.find(label); + if (it != mapAssets.end()) + return it->second; + return CAsset(); +} + +AssetMetadata CAssetsDir::GetMetadata(const CAsset& asset) const +{ + auto it = mapAssetMetadata.find(asset); + if (it != mapAssetMetadata.end()) + return it->second; + return AssetMetadata(""); +} + +std::string CAssetsDir::GetLabel(const CAsset& asset) const +{ + return GetMetadata(asset).GetLabel(); +} + +std::vector CAssetsDir::GetKnownAssets() const +{ + std::vector knownAssets; + for (auto it = mapAssets.begin(); it != mapAssets.end(); it++) { + knownAssets.push_back(it->second); + } + return knownAssets; +} + +CAsset GetAssetFromString(const std::string& strasset) { + CAsset asset = gAssetsDir.GetAsset(strasset); + if (asset.IsNull() && strasset.size() == 64 && IsHex(strasset)) { + asset = CAsset(uint256S(strasset)); + } + return asset; +} + +// GLOBAL: +CAssetsDir _gAssetsDir; +const CAssetsDir& gAssetsDir = _gAssetsDir; + +void InitGlobalAssetDir(const std::vector& assetsToInit, const std::string& pegged_asset_name) +{ + _gAssetsDir.InitFromStrings(assetsToInit, pegged_asset_name); +} diff --git a/src/assetsdir.h b/src/assetsdir.h new file mode 100644 index 0000000000000..0c6e6814ca78b --- /dev/null +++ b/src/assetsdir.h @@ -0,0 +1,60 @@ + +#ifndef BITCOIN_ASSETSDIR_H +#define BITCOIN_ASSETSDIR_H + +#include + +#include + +class AssetMetadata +{ + std::string label; +public: + AssetMetadata() : label("") {}; + AssetMetadata(std::string _label) : label(_label) {}; + + const std::string& GetLabel() const + { + return label; + } +}; + +class CAssetsDir +{ + std::map mapAssetMetadata; + std::map mapAssets; + + void Set(const CAsset& asset, const AssetMetadata& metadata); + void SetHex(const std::string& assetHex, const std::string& label); +public: + void InitFromStrings(const std::vector& assetsToInit, const std::string& pegged_asset_name); + + /** + * @param label A label string + * @return asset id corresponding to the asset label + */ + CAsset GetAsset(const std::string& label) const; + + AssetMetadata GetMetadata(const CAsset& asset) const; + + /** @return the label associated to the asset id */ + std::string GetLabel(const CAsset& asset) const; + + std::vector GetKnownAssets() const; +}; + +/** + * Returns asset id corresponding to the given asset expression, which is either an asset label or a hex value. + * @param strasset A label string or a hex value corresponding to an asset + * @return The asset ID for the given expression + */ +CAsset GetAssetFromString(const std::string& strasset); + +// GLOBAL: +class CAssetsDir; + +extern const CAssetsDir& gAssetsDir; + +void InitGlobalAssetDir(const std::vector& assetsToInit, const std::string& pegged_asset_name); + +#endif // BITCOIN_ASSETSDIR_H diff --git a/src/bench/block_assemble.cpp b/src/bench/block_assemble.cpp index 46d2bb4642fa4..5b9acdbc0418a 100644 --- a/src/bench/block_assemble.cpp +++ b/src/bench/block_assemble.cpp @@ -95,7 +95,7 @@ static void AssembleBlock(benchmark::State& state) tx.vin.push_back(MineBlock(SCRIPT_PUB)); tx.witness.vtxinwit.resize(1); tx.witness.vtxinwit.back().scriptWitness = witness; - tx.vout.emplace_back(1337, SCRIPT_PUB); + tx.vout.emplace_back(CTxOut(CAsset(), 1337, SCRIPT_PUB)); if (NUM_BLOCKS - b >= COINBASE_MATURITY) txs.at(b) = MakeTransactionRef(tx); } diff --git a/src/bench/ccoins_caching.cpp b/src/bench/ccoins_caching.cpp index db303eeead065..a352cf61eca74 100644 --- a/src/bench/ccoins_caching.cpp +++ b/src/bench/ccoins_caching.cpp @@ -39,9 +39,9 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet) dummyTransactions[1].vout.resize(2); dummyTransactions[1].vout[0].nValue = 21 * CENT; - dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(key[2].GetPubKey().GetID()); + dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(PKHash(key[2].GetPubKey())); dummyTransactions[1].vout[1].nValue = 22 * CENT; - dummyTransactions[1].vout[1].scriptPubKey = GetScriptForDestination(key[3].GetPubKey().GetID()); + dummyTransactions[1].vout[1].scriptPubKey = GetScriptForDestination(PKHash(key[3].GetPubKey())); AddCoins(coinsRet, dummyTransactions[1], 0); return dummyTransactions; diff --git a/src/bench/checkqueue.cpp b/src/bench/checkqueue.cpp index 79689f6e0bb7f..5daaead5498e4 100644 --- a/src/bench/checkqueue.cpp +++ b/src/bench/checkqueue.cpp @@ -45,11 +45,12 @@ static void CCheckQueueSpeedPrevectorJob(benchmark::State& state) // Make insecure_rand here so that each iteration is identical. FastRandomContext insecure_rand(true); CCheckQueueControl control(&queue); - std::vector> vBatches(BATCHES); + std::vector> vBatches(BATCHES); for (auto& vChecks : vBatches) { vChecks.reserve(BATCH_SIZE); - for (size_t x = 0; x < BATCH_SIZE; ++x) - vChecks.emplace_back(insecure_rand); + for (size_t x = 0; x < BATCH_SIZE; ++x) { + vChecks.emplace_back(new PrevectorJob(insecure_rand)); + } control.Add(vChecks); } // control waits for completion by RAII, but diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index 0a6f5d85eaeec..884705a9aae5b 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include @@ -47,12 +49,23 @@ static void CoinSelection(benchmark::State& state) const CoinSelectionParams coin_selection_params(true, 34, 148, CFeeRate(0), 0); while (state.KeepRunning()) { std::set setCoinsRet; - CAmount nValueRet; + CAmountMap mapValueRet; bool bnb_used; - bool success = wallet.SelectCoinsMinConf(1003 * COIN, filter_standard, groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used); + CAmountMap mapValue; + mapValue[::policyAsset] = 1003 * COIN; + bool success = wallet.SelectCoinsMinConf(mapValue, filter_standard, groups, setCoinsRet, mapValueRet, coin_selection_params, bnb_used); assert(success); - assert(nValueRet == 1003 * COIN); + assert(mapValueRet[::policyAsset] == 1003 * COIN); assert(setCoinsRet.size() == 2); + +/* std::set > setCoinsRet; + CAmountMap nValueRet; + CAmountMap mapValue; + mapValue[CAsset()] = 1003 * COIN; + bool success = wallet.SelectCoinsMinConf(mapValue, 1, 6, 0, vCoins, setCoinsRet, nValueRet); + assert(success); + assert(nValueRet[CAsset()] == 1003 * COIN); + assert(setCoinsRet.size() == 2);*/ } } diff --git a/src/bench/verify_script.cpp b/src/bench/verify_script.cpp index 07810507fab56..cf8ef6b0d5adf 100644 --- a/src/bench/verify_script.cpp +++ b/src/bench/verify_script.cpp @@ -97,10 +97,12 @@ static void VerifyScriptBench(benchmark::State& state) #if defined(HAVE_CONSENSUS_LIB) CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); stream << txSpend; + CDataStream streamVal(SER_NETWORK, PROTOCOL_VERSION); + streamVal << txCredit.vout[0].nValue; int csuccess = bitcoinconsensus_verify_script_with_amount( txCredit.vout[0].scriptPubKey.data(), txCredit.vout[0].scriptPubKey.size(), - txCredit.vout[0].nValue, + (const unsigned char*)&streamVal[0], streamVal.size(), (const unsigned char*)stream.data(), stream.size(), 0, flags, nullptr); assert(csuccess == 1); #endif diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 0783303030119..8aebee768e13f 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -6,6 +6,7 @@ #include #endif +#include #include #include #include @@ -46,7 +47,7 @@ static void SetupBitcoinTxArgs() gArgs.AddArg("in=TXID:VOUT(:SEQUENCE_NUMBER)", "Add input to TX", false, OptionsCategory::COMMANDS); gArgs.AddArg("locktime=N", "Set TX lock time to N", false, OptionsCategory::COMMANDS); gArgs.AddArg("nversion=N", "Set TX version to N", false, OptionsCategory::COMMANDS); - gArgs.AddArg("outaddr=VALUE:ADDRESS", "Add address-based output to TX", false, OptionsCategory::COMMANDS); + gArgs.AddArg("outaddr=VALUE:ADDRESS(:ASSET)", "Add address-based output to TX", false, OptionsCategory::COMMANDS); gArgs.AddArg("outdata=[VALUE:]DATA", "Add data-based output to TX", false, OptionsCategory::COMMANDS); gArgs.AddArg("outmultisig=VALUE:REQUIRED:PUBKEYS:PUBKEY1:PUBKEY2:....[:FLAGS]", "Add Pay To n-of-m Multi-sig output to TX. n = REQUIRED, m = PUBKEYS. " "Optionally add the \"W\" flag to produce a pay-to-witness-script-hash output. " @@ -67,6 +68,8 @@ static void SetupBitcoinTxArgs() gArgs.AddArg("load=NAME:FILENAME", "Load JSON file FILENAME into register NAME", false, OptionsCategory::REGISTER_COMMANDS); gArgs.AddArg("set=NAME:JSON-STRING", "Set register NAME to given JSON-STRING", false, OptionsCategory::REGISTER_COMMANDS); + gArgs.AddArg("-serialization=TYPE", "Sets the serialization of transactions. ELEMENTS or BITCOIN are the two valid options.", false, OptionsCategory::REGISTER_COMMANDS); + // Hidden gArgs.AddArg("-h", "", false, OptionsCategory::HIDDEN); gArgs.AddArg("-help", "", false, OptionsCategory::HIDDEN); @@ -270,7 +273,7 @@ static void MutateTxAddOutAddr(CMutableTransaction& tx, const std::string& strIn std::vector vStrInputParts; boost::split(vStrInputParts, strInput, boost::is_any_of(":")); - if (vStrInputParts.size() != 2) + if (vStrInputParts.size() < 2) throw std::runtime_error("TX output missing or too many separators"); // Extract and validate VALUE @@ -284,8 +287,22 @@ static void MutateTxAddOutAddr(CMutableTransaction& tx, const std::string& strIn } CScript scriptPubKey = GetScriptForDestination(destination); + // extract and validate ASSET + CAsset asset; + if (!g_con_elementsmode && vStrInputParts.size() == 3) { + throw std::runtime_error("TX output asset type invalid for BITCOIN serialization"); + } + if (vStrInputParts.size() == 3) { + asset = CAsset(uint256S(vStrInputParts[2])); + if (asset.IsNull()) { + throw std::runtime_error("invalid TX output asset type"); + } + } else { + asset = Params().GetConsensus().pegged_asset; + } + // construct TxOut, append to transaction output list - CTxOut txout(value, scriptPubKey); + CTxOut txout(asset, CConfidentialValue(value), scriptPubKey); tx.vout.push_back(txout); } @@ -325,11 +342,11 @@ static void MutateTxAddOutPubKey(CMutableTransaction& tx, const std::string& str } if (bScriptHash) { // Get the ID for the script, and then construct a P2SH destination for it. - scriptPubKey = GetScriptForDestination(CScriptID(scriptPubKey)); + scriptPubKey = GetScriptForDestination(ScriptHash(scriptPubKey)); } // construct TxOut, append to transaction output list - CTxOut txout(value, scriptPubKey); + CTxOut txout(Params().GetConsensus().pegged_asset, CConfidentialValue(value), scriptPubKey); tx.vout.push_back(txout); } @@ -399,11 +416,11 @@ static void MutateTxAddOutMultiSig(CMutableTransaction& tx, const std::string& s "redeemScript exceeds size limit: %d > %d", scriptPubKey.size(), MAX_SCRIPT_ELEMENT_SIZE)); } // Get the ID for the script, and then construct a P2SH destination for it. - scriptPubKey = GetScriptForDestination(CScriptID(scriptPubKey)); + scriptPubKey = GetScriptForDestination(ScriptHash(scriptPubKey)); } // construct TxOut, append to transaction output list - CTxOut txout(value, scriptPubKey); + CTxOut txout(Params().GetConsensus().pegged_asset, CConfidentialValue(value), scriptPubKey); tx.vout.push_back(txout); } @@ -411,26 +428,46 @@ static void MutateTxAddOutData(CMutableTransaction& tx, const std::string& strIn { CAmount value = 0; - // separate [VALUE:]DATA in string - size_t pos = strInput.find(':'); + // separate [VALUE:]DATA[:ASSET] in string + std::vector vStrInputParts; + boost::split(vStrInputParts, strInput, boost::is_any_of(":")); - if (pos==0) + // Check that there are enough parameters + if (vStrInputParts[0].empty()) throw std::runtime_error("TX output value not specified"); - if (pos != std::string::npos) { - // Extract and validate VALUE - value = ExtractAndValidateValue(strInput.substr(0, pos)); + if (vStrInputParts.size()>3) + throw std::runtime_error("too many separators"); + + std::vector data; + CAsset asset(Params().GetConsensus().pegged_asset); + + if (vStrInputParts.size()==1) { + std::string strData = vStrInputParts[0]; + if (!IsHex(strData)) + throw std::runtime_error("invalid TX output data"); + data = ParseHex(strData); + + } else { + value = ExtractAndValidateValue(vStrInputParts[0]); + std::string strData = vStrInputParts[1]; + if (!IsHex(strData)) + throw std::runtime_error("invalid TX output data"); + data = ParseHex(strData); + + if (vStrInputParts.size()==3) { + std::string strAsset = vStrInputParts[2]; + if (!IsHex(strAsset)) + throw std::runtime_error("invalid TX output asset type"); + + asset.SetHex(strAsset); + if (asset.IsNull()) { + throw std::runtime_error("invalid TX output asset type"); + } + } } - // extract and validate DATA - std::string strData = strInput.substr(pos + 1, std::string::npos); - - if (!IsHex(strData)) - throw std::runtime_error("invalid TX output data"); - - std::vector data = ParseHex(strData); - - CTxOut txout(value, CScript() << OP_RETURN << data); + CTxOut txout(asset, CConfidentialValue(value), CScript() << OP_RETURN << data); tx.vout.push_back(txout); } @@ -471,11 +508,11 @@ static void MutateTxAddOutScript(CMutableTransaction& tx, const std::string& str throw std::runtime_error(strprintf( "redeemScript exceeds size limit: %d > %d", scriptPubKey.size(), MAX_SCRIPT_ELEMENT_SIZE)); } - scriptPubKey = GetScriptForDestination(CScriptID(scriptPubKey)); + scriptPubKey = GetScriptForDestination(ScriptHash(scriptPubKey)); } // construct TxOut, append to transaction output list - CTxOut txout(value, scriptPubKey); + CTxOut txout(Params().GetConsensus().pegged_asset, CConfidentialValue(value), scriptPubKey); tx.vout.push_back(txout); } @@ -612,7 +649,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) newcoin.out.scriptPubKey = scriptPubKey; newcoin.out.nValue = 0; if (prevOut.exists("amount")) { - newcoin.out.nValue = AmountFromValue(prevOut["amount"]); + newcoin.out.nValue = CConfidentialValue(AmountFromValue(prevOut["amount"])); } newcoin.nHeight = 1; view.AddCoin(out, std::move(newcoin), true); @@ -642,7 +679,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) continue; } const CScript& prevPubKey = coin.out.scriptPubKey; - const CAmount& amount = coin.out.nValue; + const CConfidentialValue& amount = coin.out.nValue; SignatureData sigdata = DataFromTransaction(mergedTx, i, coin.out); // Only sign SIGHASH_SINGLE if there's a corresponding output: @@ -812,6 +849,16 @@ static int CommandLineRawTx(int argc, char* argv[]) value = arg.substr(eqpos + 1); } + std::string serialization = gArgs.GetArg("-serialization", "ELEMENTS"); + if (serialization == "ELEMENTS") { + g_con_elementsmode = true; + } else if (serialization == "BITCOIN") { + g_con_elementsmode = false; + } else { + PrintExceptionContinue(nullptr, "Invalid serialization argument"); + throw; + } + MutateTx(tx, key, value); } diff --git a/src/blech32.cpp b/src/blech32.cpp new file mode 100644 index 0000000000000..8acd4f0a1f26a --- /dev/null +++ b/src/blech32.cpp @@ -0,0 +1,199 @@ +// Copyright (c) 2017 Pieter Wuille +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +/* + * IMPORTANT NOTE: Comments below may largely pertain for bech32, not blech32. + * Some of these magic constants have changes. + * See liquid_addr.py for compact difference from bech32 + * TODO: Update comments + */ + +namespace +{ + +typedef std::vector data; + +/** The Blech32 character set for encoding. */ +const char* CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; + +/** The Blech32 character set for decoding. */ +const int8_t CHARSET_REV[128] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1 +}; + +/** Concatenate two byte arrays. */ +data Cat(data x, const data& y) +{ + x.insert(x.end(), y.begin(), y.end()); + return x; +} + +/** This function will compute what 6 5-bit values to XOR into the last 6 input values, in order to + * make the checksum 0. These 6 values are packed together in a single 30-bit integer. The higher + * bits correspond to earlier values. */ +uint32_t PolyMod(const data& v) +{ + // The input is interpreted as a list of coefficients of a polynomial over F = GF(32), with an + // implicit 1 in front. If the input is [v0,v1,v2,v3,v4], that polynomial is v(x) = + // 1*x^5 + v0*x^4 + v1*x^3 + v2*x^2 + v3*x + v4. The implicit 1 guarantees that + // [v0,v1,v2,...] has a distinct checksum from [0,v0,v1,v2,...]. + + // The output is a 30-bit integer whose 5-bit groups are the coefficients of the remainder of + // v(x) mod g(x), where g(x) is the Blech32 generator, + // x^6 + {29}x^5 + {22}x^4 + {20}x^3 + {21}x^2 + {29}x + {18}. g(x) is chosen in such a way + // that the resulting code is a BCH code, guaranteeing detection of up to 3 errors within a + // window of 1023 characters. Among the various possible BCH codes, one was selected to in + // fact guarantee detection of up to 4 errors within a window of 89 characters. + + // Note that the coefficients are elements of GF(32), here represented as decimal numbers + // between {}. In this finite field, addition is just XOR of the corresponding numbers. For + // example, {27} + {13} = {27 ^ 13} = {22}. Multiplication is more complicated, and requires + // treating the bits of values themselves as coefficients of a polynomial over a smaller field, + // GF(2), and multiplying those polynomials mod a^5 + a^3 + 1. For example, {5} * {26} = + // (a^2 + 1) * (a^4 + a^3 + a) = (a^4 + a^3 + a) * a^2 + (a^4 + a^3 + a) = a^6 + a^5 + a^4 + a + // = a^3 + 1 (mod a^5 + a^3 + 1) = {9}. + + // During the course of the loop below, `c` contains the bitpacked coefficients of the + // polynomial constructed from just the values of v that were processed so far, mod g(x). In + // the above example, `c` initially corresponds to 1 mod (x), and after processing 2 inputs of + // v, it corresponds to x^2 + v0*x + v1 mod g(x). As 1 mod g(x) = 1, that is the starting value + // for `c`. + uint64_t c = 1; + for (const auto v_i : v) { + // We want to update `c` to correspond to a polynomial with one extra term. If the initial + // value of `c` consists of the coefficients of c(x) = f(x) mod g(x), we modify it to + // correspond to c'(x) = (f(x) * x + v_i) mod g(x), where v_i is the next input to + // process. Simplifying: + // c'(x) = (f(x) * x + v_i) mod g(x) + // ((f(x) mod g(x)) * x + v_i) mod g(x) + // (c(x) * x + v_i) mod g(x) + // If c(x) = c0*x^5 + c1*x^4 + c2*x^3 + c3*x^2 + c4*x + c5, we want to compute + // c'(x) = (c0*x^5 + c1*x^4 + c2*x^3 + c3*x^2 + c4*x + c5) * x + v_i mod g(x) + // = c0*x^6 + c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i mod g(x) + // = c0*(x^6 mod g(x)) + c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i + // If we call (x^6 mod g(x)) = k(x), this can be written as + // c'(x) = (c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i) + c0*k(x) + + // First, determine the value of c0: + uint8_t c0 = c >> 55; // ELEMENTS: 25->55 + + // Then compute c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i: + c = ((c & 0x7fffffffffffff) << 5) ^ v_i; // ELEMENTS 0x1ffffff->0x7fffffffffffff + + // Finally, for each set bit n in c0, conditionally add {2^n}k(x): + if (c0 & 1) c ^= 0x7d52fba40bd886; // ELEMENTS + if (c0 & 2) c ^= 0x5e8dbf1a03950c; // ELEMENTS + if (c0 & 4) c ^= 0x1c3a3c74072a18; // ELEMENTS + if (c0 & 8) c ^= 0x385d72fa0e5139; // ELEMENTS + if (c0 & 16) c ^= 0x7093e5a608865b; // ELEMENTS + } + return c; +} + +/** Convert to lower case. */ +inline unsigned char LowerCase(unsigned char c) +{ + return (c >= 'A' && c <= 'Z') ? (c - 'A') + 'a' : c; +} + +/** Expand a HRP for use in checksum computation. */ +data ExpandHRP(const std::string& hrp) +{ + data ret; + ret.reserve(hrp.size() + 90); + ret.resize(hrp.size() * 2 + 1); + for (size_t i = 0; i < hrp.size(); ++i) { + unsigned char c = hrp[i]; + ret[i] = c >> 5; + ret[i + hrp.size() + 1] = c & 0x1f; + } + ret[hrp.size()] = 0; + return ret; +} + +/** Verify a checksum. */ +bool VerifyChecksum(const std::string& hrp, const data& values) +{ + // PolyMod computes what value to xor into the final values to make the checksum 0. However, + // if we required that the checksum was 0, it would be the case that appending a 0 to a valid + // list of values would result in a new valid list. For that reason, Blech32 requires the + // resulting checksum to be 1 instead. + return PolyMod(Cat(ExpandHRP(hrp), values)) == 1; +} + +/** Create a checksum. */ +data CreateChecksum(const std::string& hrp, const data& values) +{ + data enc = Cat(ExpandHRP(hrp), values); + enc.resize(enc.size() + 12); // ELEMENTS: Append 6->12 zeroes + uint32_t mod = PolyMod(enc) ^ 1; // Determine what to XOR into those 6 zeroes. + data ret(12); // ELEMENTS: 6->12 + for (size_t i = 0; i < 12; ++i) { // ELEMENTS: 6->12 + // Convert the 5-bit groups in mod to checksum values. + ret[i] = (mod >> (5 * (11 - i))) & 31; // ELEMENTS: 5->11 + } + return ret; +} + +} // namespace + +namespace blech32 +{ + +/** Encode a Blech32 string. */ +std::string Encode(const std::string& hrp, const data& values) { + data checksum = CreateChecksum(hrp, values); + data combined = Cat(values, checksum); + std::string ret = hrp + '1'; + ret.reserve(ret.size() + combined.size()); + for (const auto c : combined) { + ret += CHARSET[c]; + } + return ret; +} + +/** Decode a Blech32 string. */ +std::pair Decode(const std::string& str) { + bool lower = false, upper = false; + for (size_t i = 0; i < str.size(); ++i) { + unsigned char c = str[i]; + if (c >= 'a' && c <= 'z') lower = true; + else if (c >= 'A' && c <= 'Z') upper = true; + else if (c < 33 || c > 126) return {}; + } + if (lower && upper) return {}; + size_t pos = str.rfind('1'); + if (str.size() > 1000 || pos == str.npos || pos == 0 || pos + 13 > str.size()) { // ELEMENTS: 90->1000, 7->13 + return {}; + } + data values(str.size() - 1 - pos); + for (size_t i = 0; i < str.size() - 1 - pos; ++i) { + unsigned char c = str[i + pos + 1]; + int8_t rev = CHARSET_REV[c]; + + if (rev == -1) { + return {}; + } + values[i] = rev; + } + std::string hrp; + for (size_t i = 0; i < pos; ++i) { + hrp += LowerCase(str[i]); + } + if (!VerifyChecksum(hrp, values)) { + return {}; + } + return {hrp, data(values.begin(), values.end() - 12)}; +} + +} // namespace blech32 diff --git a/src/blech32.h b/src/blech32.h new file mode 100644 index 0000000000000..cfe907581e7a6 --- /dev/null +++ b/src/blech32.h @@ -0,0 +1,30 @@ +// Copyright (c) 2017 Pieter Wuille +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +// Bech32 is a string encoding format used in newer address types. +// The output consists of a human-readable part (alphanumeric), a +// separator character (1), and a base32 data section, the last +// 6 characters of which are a checksum. +// +// For more information, see BIP 173. + +#ifndef BITCOIN_BLECH32_H +#define BITCOIN_BLECH32_H + +#include +#include +#include + +namespace blech32 +{ + +/** Encode a Bech32 string. Returns the empty string in case of failure. */ +std::string Encode(const std::string& hrp, const std::vector& values); + +/** Decode a Bech32 string. Returns (hrp, data). Empty hrp means failure. */ +std::pair> Decode(const std::string& str); + +} // namespace blech32 + +#endif // BITCOIN_BLECH32_H diff --git a/src/blind.cpp b/src/blind.cpp new file mode 100644 index 0000000000000..c168b23e4b971 --- /dev/null +++ b/src/blind.cpp @@ -0,0 +1,579 @@ +// Copyright (c) 2017-2019 The Elements Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include +#include +#include +#include + +static secp256k1_context* secp256k1_blind_context = NULL; + +class Blind_ECC_Init { +public: + Blind_ECC_Init() { + assert(secp256k1_blind_context == NULL); + + secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + assert(ctx != NULL); + + secp256k1_blind_context = ctx; + } + + ~Blind_ECC_Init() { + secp256k1_context *ctx = secp256k1_blind_context; + secp256k1_blind_context = NULL; + + if (ctx) { + secp256k1_context_destroy(ctx); + } + } +}; + +static Blind_ECC_Init ecc_init_on_load; + +bool UnblindConfidentialPair(const CKey& blinding_key, const CConfidentialValue& conf_value, const CConfidentialAsset& conf_asset, const CConfidentialNonce& nonce_commitment, const CScript& committedScript, const std::vector& vchRangeproof, CAmount& amount_out, uint256& blinding_factor_out, CAsset& asset_out, uint256& asset_blinding_factor_out) +{ + if (!blinding_key.IsValid() || vchRangeproof.size() == 0) { + return false; + } + CPubKey ephemeral_key(nonce_commitment.vchCommitment); + if (nonce_commitment.vchCommitment.size() > 0 && !ephemeral_key.IsFullyValid()) { + return false; + } + + // ECDH or not depending on if nonce commitment is non-empty + uint256 nonce; + bool blank_nonce = false; + if (nonce_commitment.vchCommitment.size() > 0) { + nonce = blinding_key.ECDH(ephemeral_key); + CSHA256().Write(nonce.begin(), 32).Finalize(nonce.begin()); + } else { + // Use blinding key directly, and don't commit to a scriptpubkey + // This is used for issuance inputs. + blank_nonce = true; + nonce = uint256(std::vector(blinding_key.begin(), blinding_key.end())); + } + + // API-prescribed sidechannel maximum size, though we only use 64 bytes + unsigned char msg[4096] = {0}; + // 32 bytes of asset type, 32 bytes of asset blinding factor in sidechannel + size_t msg_size = 64; + + // If value is unblinded, we don't support unblinding just the asset + if (!conf_value.IsCommitment()) { + return false; + } + + // Valid asset commitment? + secp256k1_generator observed_gen; + if (conf_asset.IsCommitment()) { + if (secp256k1_generator_parse(secp256k1_blind_context, &observed_gen, &conf_asset.vchCommitment[0]) != 1) + return false; + } else if (conf_asset.IsExplicit()) { + if (secp256k1_generator_generate(secp256k1_blind_context, &observed_gen, conf_asset.GetAsset().begin()) != 1) + return false; + } + + // Valid value commitment? + secp256k1_pedersen_commitment value_commit; + if (secp256k1_pedersen_commitment_parse(secp256k1_blind_context, &value_commit, conf_value.vchCommitment.data()) != 1) { + return false; + } + + // Rewind rangeproof + uint64_t min_value, max_value, amount; + if (!secp256k1_rangeproof_rewind(secp256k1_blind_context, blinding_factor_out.begin(), &amount, msg, &msg_size, nonce.begin(), &min_value, &max_value, &value_commit, &vchRangeproof[0], vchRangeproof.size(), (committedScript.size() && !blank_nonce)? &committedScript.front(): NULL, blank_nonce ? 0 : committedScript.size(), &observed_gen)) { + return false; + } + + // Value sidechannel must be a transaction-valid amount (should be belt-and-suspenders check) + if (amount > (uint64_t)MAX_MONEY || !MoneyRange((CAmount)amount)) { + return false; + } + + // Convenience pointers to starting point of each recovered 32 byte message + unsigned char *asset_type = msg; + unsigned char *asset_blinder = msg+32; + + // Asset sidechannel of asset type + asset blinder + secp256k1_generator recalculated_gen; + if (msg_size != 64 || secp256k1_generator_generate_blinded(secp256k1_blind_context, &recalculated_gen, asset_type, asset_blinder) != 1) { + return false; + } + + // Serialize both generators then compare + unsigned char observed_generator[33]; + unsigned char derived_generator[33]; + secp256k1_generator_serialize(secp256k1_blind_context, observed_generator, &observed_gen); + secp256k1_generator_serialize(secp256k1_blind_context, derived_generator, &recalculated_gen); + if (memcmp(observed_generator, derived_generator, sizeof(observed_generator))) { + return false; + } + + amount_out = (CAmount)amount; + asset_out = CAsset(std::vector(asset_type, asset_type+32)); + asset_blinding_factor_out = uint256(std::vector(asset_blinder, asset_blinder+32)); + return true; +} + +// Create surjection proof +bool SurjectOutput(CTxOutWitness& txoutwit, const std::vector& surjection_targets, const std::vector& target_asset_generators, const std::vector& target_asset_blinders, const std::vector asset_blindptrs, const secp256k1_generator& output_asset_gen, const CAsset& asset) +{ + int ret; + // 1 to 3 targets + size_t nInputsToSelect = std::min((size_t)3, surjection_targets.size()); + unsigned char randseed[32]; + GetStrongRandBytes(randseed, 32); + size_t input_index; + secp256k1_surjectionproof proof; + secp256k1_fixed_asset_tag tag; + memcpy(&tag, asset.begin(), 32); + // Find correlation between asset tag and listed input tags + if (secp256k1_surjectionproof_initialize(secp256k1_blind_context, &proof, &input_index, &surjection_targets[0], surjection_targets.size(), nInputsToSelect, &tag, 100, randseed) == 0) { + return false; + } + // Using the input chosen, build proof + ret = secp256k1_surjectionproof_generate(secp256k1_blind_context, &proof, target_asset_generators.data(), target_asset_generators.size(), &output_asset_gen, input_index, target_asset_blinders[input_index].begin(), asset_blindptrs[asset_blindptrs.size()-1]); + assert(ret == 1); + // Double-check answer + ret = secp256k1_surjectionproof_verify(secp256k1_blind_context, &proof, target_asset_generators.data(), target_asset_generators.size(), &output_asset_gen); + assert(ret != 0); + + // Serialize into output witness structure + size_t output_len = secp256k1_surjectionproof_serialized_size(secp256k1_blind_context, &proof); + txoutwit.vchSurjectionproof.resize(output_len); + secp256k1_surjectionproof_serialize(secp256k1_blind_context, &txoutwit.vchSurjectionproof[0], &output_len, &proof); + assert(output_len == txoutwit.vchSurjectionproof.size()); + return true; +} + +// Creates ECDH nonce commitment using ephemeral key and output_pubkey +uint256 GenerateOutputRangeproofNonce(CTxOut& out, const CPubKey output_pubkey) +{ + // Generate ephemeral key for ECDH nonce generation + CKey ephemeral_key; + ephemeral_key.MakeNewKey(true); + CPubKey ephemeral_pubkey = ephemeral_key.GetPubKey(); + assert(ephemeral_pubkey.size() == CConfidentialNonce::nCommittedSize); + out.nNonce.vchCommitment.resize(ephemeral_pubkey.size()); + memcpy(&out.nNonce.vchCommitment[0], &ephemeral_pubkey[0], ephemeral_pubkey.size()); + // Generate nonce + uint256 nonce = ephemeral_key.ECDH(output_pubkey); + CSHA256().Write(nonce.begin(), 32).Finalize(nonce.begin()); + return nonce; +} + +bool GenerateRangeproof(std::vector& rangeproof, const std::vector& value_blindptrs, const uint256& nonce, const CAmount amount, const CScript& scriptPubKey, const secp256k1_pedersen_commitment& value_commit, const secp256k1_generator& gen, const CAsset& asset, std::vector& asset_blindptrs) +{ + // Prep range proof + size_t nRangeProofLen = 5134; + rangeproof.resize(nRangeProofLen); + + // Compose sidechannel message to convey asset info (ID and asset blinds) + unsigned char asset_message[64]; + memcpy(asset_message, asset.begin(), 32); + memcpy(asset_message+32, asset_blindptrs[asset_blindptrs.size()-1], 32); + + // Sign rangeproof + // If min_value is 0, scriptPubKey must be unspendable + int res = secp256k1_rangeproof_sign(secp256k1_blind_context, rangeproof.data(), &nRangeProofLen, scriptPubKey.IsUnspendable() ? 0 : 1, &value_commit, value_blindptrs.back(), nonce.begin(), std::min(std::max((int)gArgs.GetArg("-ct_exponent", 0), -1),18), std::min(std::max((int)gArgs.GetArg("-ct_bits", 36), 1), 51), amount, asset_message, sizeof(asset_message), scriptPubKey.size() ? &scriptPubKey.front() : NULL, scriptPubKey.size(), &gen); + rangeproof.resize(nRangeProofLen); + return (res == 1); +} + +void BlindAsset(CConfidentialAsset& conf_asset, secp256k1_generator& asset_gen, const CAsset& asset, const unsigned char* asset_blindptr) +{ + conf_asset.vchCommitment.resize(CConfidentialAsset::nCommittedSize); + int ret = secp256k1_generator_generate_blinded(secp256k1_blind_context, &asset_gen, asset.begin(), asset_blindptr); + assert(ret == 1); + ret = secp256k1_generator_serialize(secp256k1_blind_context, conf_asset.vchCommitment.data(), &asset_gen); + assert(ret != 0); +} + +void CreateValueCommitment(CConfidentialValue& conf_value, secp256k1_pedersen_commitment& value_commit, const unsigned char* value_blindptr, const secp256k1_generator& asset_gen, const CAmount amount) +{ + int ret; + conf_value.vchCommitment.resize(CConfidentialValue::nCommittedSize); + ret = secp256k1_pedersen_commit(secp256k1_blind_context, &value_commit, value_blindptr, amount, &asset_gen); + assert(ret != 0); + secp256k1_pedersen_commitment_serialize(secp256k1_blind_context, conf_value.vchCommitment.data(), &value_commit); + assert(conf_value.IsValid()); +} + +size_t GetNumIssuances(const CTransaction& tx) +{ + unsigned int num_issuances = 0; + for (unsigned int i = 0; i < tx.vin.size(); i++) { + if (!tx.vin[i].assetIssuance.IsNull()) { + if (!tx.vin[i].assetIssuance.nAmount.IsNull()) { + num_issuances++; + } + if (!tx.vin[i].assetIssuance.nInflationKeys.IsNull()) { + num_issuances++; + } + } + } + return num_issuances; +} + +int BlindTransaction(std::vector& input_value_blinding_factors, const std::vector& input_asset_blinding_factors, const std::vector& input_assets, const std::vector& input_amounts, std::vector& out_val_blind_factors, std::vector& out_asset_blind_factors, const std::vector& output_pubkeys, const std::vector& issuance_blinding_privkey, const std::vector& token_blinding_privkey, CMutableTransaction& tx, std::vector >* auxiliary_generators) +{ + // Sanity check input data and output_pubkey size, clear other output data + assert(tx.vout.size() >= output_pubkeys.size()); + assert(tx.vin.size()+GetNumIssuances(tx) >= issuance_blinding_privkey.size()); + assert(tx.vin.size()+GetNumIssuances(tx) >= token_blinding_privkey.size()); + out_val_blind_factors.clear(); + out_val_blind_factors.resize(tx.vout.size()); + out_asset_blind_factors.clear(); + out_asset_blind_factors.resize(tx.vout.size()); + assert(tx.vin.size() == input_value_blinding_factors.size()); + assert(tx.vin.size() == input_asset_blinding_factors.size()); + assert(tx.vin.size() == input_assets.size()); + assert(tx.vin.size() == input_amounts.size()); + if (auxiliary_generators) { + assert(auxiliary_generators->size() >= tx.vin.size()); + } + + std::vector value_blindptrs; + std::vector asset_blindptrs; + std::vector blinded_amounts; + value_blindptrs.reserve(tx.vout.size() + tx.vin.size()); + asset_blindptrs.reserve(tx.vout.size() + tx.vin.size()); + + int ret; + int num_blind_attempts = 0, num_issuance_blind_attempts = 0, num_blinded = 0; + + //Surjection proof prep + + // Needed to surj init, only matches to output asset matters, rest can be garbage + std::vector surjection_targets; + + // Needed to construct the proof itself. Generators must match final transaction to be valid + std::vector target_asset_generators; + surjection_targets.resize(tx.vin.size()*3); + target_asset_generators.resize(tx.vin.size()*3); + + // input_asset_blinding_factors is only for inputs, not for issuances(0 by def) + // but we need to create surjection proofs against this list so we copy and insert 0's + // where issuances occur. + std::vector target_asset_blinders; + + size_t totalTargets = 0; + for (size_t i = 0; i < tx.vin.size(); i++) { + // For each input we either need the asset/blinds or the generator + if (input_assets[i].IsNull()) { + // If non-empty generator exists, parse + if (auxiliary_generators) { + // Parse generator here + ret = secp256k1_generator_parse(secp256k1_blind_context, &target_asset_generators[totalTargets], &(*auxiliary_generators)[i][0]); + if (ret != 1) { + return -1; + } + } else { + return -1; + } + } else { + ret = secp256k1_generator_generate_blinded(secp256k1_blind_context, &target_asset_generators[totalTargets], input_assets[i].begin(), input_asset_blinding_factors[i].begin()); + assert(ret == 1); + } + memcpy(&surjection_targets[totalTargets], input_assets[i].begin(), 32); + target_asset_blinders.push_back(input_asset_blinding_factors[i]); + totalTargets++; + + // Create target generators for issuances + CAssetIssuance& issuance = tx.vin[i].assetIssuance; + uint256 entropy; + CAsset asset; + CAsset token; + if (!issuance.IsNull()) { + if (issuance.nAmount.IsCommitment() || issuance.nInflationKeys.IsCommitment()) { + return -1; + } + // New Issuance + if (issuance.assetBlindingNonce.IsNull()) { + bool blind_issuance = (token_blinding_privkey.size() > i && token_blinding_privkey[i].IsValid()) ? true : false; + GenerateAssetEntropy(entropy, tx.vin[i].prevout, issuance.assetEntropy); + CalculateAsset(asset, entropy); + CalculateReissuanceToken(token, entropy, blind_issuance); + } else { + CalculateAsset(asset, issuance.assetEntropy); + } + + if (!issuance.nAmount.IsNull()) { + memcpy(&surjection_targets[totalTargets], asset.begin(), 32); + ret = secp256k1_generator_generate(secp256k1_blind_context, &target_asset_generators[totalTargets], asset.begin()); + assert(ret != 0); + // Issuance asset cannot be blinded by definition + target_asset_blinders.push_back(uint256()); + totalTargets++; + } + if (!issuance.nInflationKeys.IsNull()) { + assert(!token.IsNull()); + memcpy(&surjection_targets[totalTargets], token.begin(), 32); + ret = secp256k1_generator_generate(secp256k1_blind_context, &target_asset_generators[totalTargets], token.begin()); + assert(ret != 0); + // Issuance asset cannot be blinded by definition + target_asset_blinders.push_back(uint256()); + totalTargets++; + } + } + } + + if (auxiliary_generators) { + // Process any additional targets from auxiliary_generators + // we know nothing about it other than the generator itself + for (size_t i = tx.vin.size(); i < auxiliary_generators->size(); i++) { + ret = secp256k1_generator_parse(secp256k1_blind_context, &target_asset_generators[totalTargets], &(*auxiliary_generators)[i][0]); + if (ret != 1) { + return -1; + } + memset(&surjection_targets[totalTargets], 0, 32); + target_asset_blinders.push_back(uint256()); + totalTargets++; + } + } + + // Resize the target surjection lists to how many actually exist + assert(totalTargets == target_asset_blinders.size()); + surjection_targets.resize(totalTargets); + target_asset_generators.resize(totalTargets); + + //Total blinded inputs that you own (that you are balancing against) + int num_known_input_blinds = 0; + //Number of outputs and issuances to blind + int num_to_blind = 0; + + // Make sure witness lengths are correct + tx.witness.vtxoutwit.resize(tx.vout.size()); + tx.witness.vtxinwit.resize(tx.vin.size()); + + size_t txoutwitsize = tx.witness.vtxoutwit.size(); + for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) { + if (!input_value_blinding_factors[nIn].IsNull() || !input_asset_blinding_factors[nIn].IsNull()) { + if (input_amounts[nIn] < 0) { + return -1; + } + value_blindptrs.push_back(input_value_blinding_factors[nIn].begin()); + asset_blindptrs.push_back(input_asset_blinding_factors[nIn].begin()); + blinded_amounts.push_back(input_amounts[nIn]); + num_known_input_blinds++; + } + + // Count number of issuance pseudo-inputs to blind + CAssetIssuance& issuance = tx.vin[nIn].assetIssuance; + if (!issuance.IsNull()) { + // Marked for blinding + if (issuance_blinding_privkey.size() > nIn && issuance_blinding_privkey[nIn].IsValid()) { + if(issuance.nAmount.IsExplicit() && tx.witness.vtxinwit[nIn].vchIssuanceAmountRangeproof.empty()) { + num_to_blind++; + } else { + return -1; + } + } + if (token_blinding_privkey.size() > nIn && token_blinding_privkey[nIn].IsValid()) { + if(issuance.nInflationKeys.IsExplicit() && tx.witness.vtxinwit[nIn].vchInflationKeysRangeproof.empty()) { + num_to_blind++; + } else { + return -1; + } + } + } + } + + for (size_t nOut = 0; nOut < output_pubkeys.size(); nOut++) { + if (output_pubkeys[nOut].IsValid()) { + // Keys must be valid and outputs completely unblinded or else call fails + if (!output_pubkeys[nOut].IsFullyValid() || + (!tx.vout[nOut].nValue.IsExplicit() || !tx.vout[nOut].nAsset.IsExplicit()) || + (txoutwitsize > nOut && !tx.witness.vtxoutwit[nOut].IsNull()) + || tx.vout[nOut].IsFee()) { + return -1; + } + num_to_blind++; + } + } + + + //Running total of newly blinded outputs + static const unsigned char diff_zero[32] = {0}; + assert(num_to_blind <= 10000); // More than 10k outputs? Stop spamming. + unsigned char blind[10000][32]; + unsigned char asset_blind[10000][32]; + secp256k1_pedersen_commitment value_commit; + secp256k1_generator asset_gen; + CAsset asset; + + // First blind issuance pseudo-inputs + for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) { + for (size_t nPseudo = 0; nPseudo < 2; nPseudo++) { + if ((nPseudo == 0 && issuance_blinding_privkey.size() > nIn && issuance_blinding_privkey[nIn].IsValid()) || + (nPseudo == 1 && token_blinding_privkey.size() > nIn && token_blinding_privkey[nIn].IsValid())) { + num_blind_attempts++; + num_issuance_blind_attempts++; + CAssetIssuance& issuance = tx.vin[nIn].assetIssuance; + // First iteration does issuance asset, second inflation keys + CConfidentialValue& conf_value = nPseudo ? issuance.nInflationKeys : issuance.nAmount; + if (conf_value.IsNull()) { + continue; + } + CAmount amount = conf_value.GetAmount(); + blinded_amounts.push_back(amount); + + // Derive the asset of the issuance asset/token + if (issuance.assetBlindingNonce.IsNull()) { + uint256 entropy; + GenerateAssetEntropy(entropy, tx.vin[nIn].prevout, issuance.assetEntropy); + if (nPseudo == 0) { + CalculateAsset(asset, entropy); + } else { + bool blind_issuance = (token_blinding_privkey.size() > nIn && token_blinding_privkey[nIn].IsValid()) ? true : false; + CalculateReissuanceToken(asset, entropy, blind_issuance); + } + } else { + if (nPseudo == 0) { + CalculateAsset(asset, issuance.assetEntropy); + } else { + // Re-issuance only has one pseudo-input maximum + continue; + } + } + + // Fill out the value blinders and blank asset blinder + GetStrongRandBytes(&blind[num_blind_attempts-1][0], 32); + // Issuances are not asset-blinded + memset(&asset_blind[num_blind_attempts-1][0], 0, 32); + value_blindptrs.push_back(&blind[num_blind_attempts-1][0]); + asset_blindptrs.push_back(&asset_blind[num_blind_attempts-1][0]); + + if (num_blind_attempts == num_to_blind) { + // All outputs we own are unblinded, we don't support this type of blinding + // though it is possible. No privacy gained here, incompatible with secp api + return num_blinded; + } + + if (tx.witness.vtxinwit.size() <= nIn) { + tx.witness.vtxinwit.resize(tx.vin.size()); + } + CTxInWitness& txinwit = tx.witness.vtxinwit[nIn]; + + // Create unblinded generator. We throw away all but `asset_gen` + CConfidentialAsset conf_asset; + BlindAsset(conf_asset, asset_gen, asset, asset_blindptrs.back()); + + // Create value commitment + CreateValueCommitment(conf_value, value_commit, value_blindptrs.back(), asset_gen, amount); + + // nonce should just be blinding key + uint256 nonce = nPseudo ? uint256(std::vector(token_blinding_privkey[nIn].begin(), token_blinding_privkey[nIn].end())) : uint256(std::vector(issuance_blinding_privkey[nIn].begin(), issuance_blinding_privkey[nIn].end())); + + // Generate rangeproof, no script committed for issuances + bool rangeresult = GenerateRangeproof((nPseudo ? txinwit.vchInflationKeysRangeproof : txinwit.vchIssuanceAmountRangeproof), value_blindptrs, nonce, amount, CScript(), value_commit, asset_gen, asset, asset_blindptrs); + assert(rangeresult); + + // Successfully blinded this issuance + num_blinded++; + } + } + } + + // This section of code *only* deals with unblinded outputs + // that we want to blind + for (size_t nOut = 0; nOut < output_pubkeys.size(); nOut++) { + if (output_pubkeys[nOut].IsFullyValid()) { + CTxOut& out = tx.vout[nOut]; + num_blind_attempts++; + CConfidentialAsset& conf_asset = out.nAsset; + CConfidentialValue& conf_value = out.nValue; + CAmount amount = conf_value.GetAmount(); + asset = out.nAsset.GetAsset(); + blinded_amounts.push_back(conf_value.GetAmount()); + + GetStrongRandBytes(&blind[num_blind_attempts-1][0], 32); + GetStrongRandBytes(&asset_blind[num_blind_attempts-1][0], 32); + value_blindptrs.push_back(&blind[num_blind_attempts-1][0]); + asset_blindptrs.push_back(&asset_blind[num_blind_attempts-1][0]); + + // Last blinding factor r' is set as -(output's (vr + r') - input's (vr + r')). + // Before modifying the transaction or return arguments we must + // ensure the final blinding factor to not be its corresponding -vr (aka unblinded), + // or 0, in the case of 0-value output, insisting on additional output to blind. + if (num_blind_attempts == num_to_blind) { + + // Can't successfully blind in this case, since -vr = r + // This check is assuming blinds are generated randomly + // Adversary would need to create all input blinds + // therefore would already know all your summed output amount anyways. + if (num_blind_attempts == 1 && num_known_input_blinds == 0) { + return num_blinded; + } + + // Generate value we intend to insert + ret = secp256k1_pedersen_blind_generator_blind_sum(secp256k1_blind_context, &blinded_amounts[0], &asset_blindptrs[0], &value_blindptrs[0], num_blind_attempts + num_known_input_blinds, num_issuance_blind_attempts + num_known_input_blinds); + assert(ret); + + // Resulting blinding factor can sometimes be 0 + // where inputs are the negations of each other + // and the unblinded value of the output is 0. + // e.g. 1 unblinded input to 2 blinded outputs, + // then spent to 1 unblinded output. (vr + r') + // becomes just (r'), if this is 0, we can just + // abort and not blind and the math adds up. + // Count as success(to signal caller that nothing wrong) and return early + if (memcmp(diff_zero, &blind[num_blind_attempts-1][0], 32) == 0) { + return ++num_blinded; + } + } + + CTxOutWitness& txoutwit = tx.witness.vtxoutwit[nOut]; + + out_val_blind_factors[nOut] = uint256(std::vector(value_blindptrs[value_blindptrs.size()-1], value_blindptrs[value_blindptrs.size()-1]+32)); + out_asset_blind_factors[nOut] = uint256(std::vector(asset_blindptrs[asset_blindptrs.size()-1], asset_blindptrs[asset_blindptrs.size()-1]+32)); + + //Blind the asset ID + BlindAsset(conf_asset, asset_gen, asset, asset_blindptrs.back()); + + // Create value commitment + CreateValueCommitment(conf_value, value_commit, value_blindptrs.back(), asset_gen, amount); + + // Generate nonce for rewind by owner + uint256 nonce = GenerateOutputRangeproofNonce(out, output_pubkeys[nOut]); + + // Generate rangeproof + bool rangeresult = GenerateRangeproof(txoutwit.vchRangeproof, value_blindptrs, nonce, amount, out.scriptPubKey, value_commit, asset_gen, asset, asset_blindptrs); + assert(rangeresult); + + // Create surjection proof for this output + if (!SurjectOutput(txoutwit, surjection_targets, target_asset_generators, target_asset_blinders, asset_blindptrs, asset_gen, asset)) { + continue; + } + + // Successfully blinded this output + num_blinded++; + } + } + + return num_blinded; +} + +void RawFillBlinds(CMutableTransaction& tx, std::vector& output_value_blinds, std::vector& output_asset_blinds, std::vector& output_pubkeys) { + for (size_t nOut = 0; nOut < tx.vout.size(); nOut++) { + // Any place-holder blinding pubkeys are extracted + if (tx.vout[nOut].nValue.IsExplicit()) { + CPubKey pubkey(tx.vout[nOut].nNonce.vchCommitment); + if (pubkey.IsFullyValid()) { + output_pubkeys.push_back(pubkey); + } else { + output_pubkeys.push_back(CPubKey()); + } + } + // No way to unblind anything, just fill out + output_value_blinds.push_back(uint256()); + output_asset_blinds.push_back(uint256()); + } + // We cannot unwind issuance inputs because there is no nonce placeholder for pubkeys +} diff --git a/src/blind.h b/src/blind.h new file mode 100644 index 0000000000000..4296b70ce7bdb --- /dev/null +++ b/src/blind.h @@ -0,0 +1,63 @@ +// Copyright (c) 2017-2019 The Elements Core developers +// // Distributed under the MIT software license, see the accompanying +// // file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_BLIND_H +#define BITCOIN_BLIND_H + +#include +#include +#include +#include + +#include +#include +#include + +//! ELEMENTS: 36-bit rangeproof size +static const size_t DEFAULT_RANGEPROOF_SIZE = 2893; + +/* + * Unblind a pair of confidential asset and value. + * Note that unblinded data will only be outputted if *BOTH* asset and value could be unblinded. + * + * blinding_key is used to create the nonce to rewind the rangeproof in conjunction with the nNonce commitment. In the case of a 0-length nNonce, the blinding key is directly used as the nonce. + * Currently there is only a sidechannel message in the rangeproof so a valid rangeproof must + * be included in the pair to recover value and asset data. + */ +bool UnblindConfidentialPair(const CKey& blinding_key, const CConfidentialValue& conf_value, const CConfidentialAsset& conf_asset, const CConfidentialNonce& nonce_commitment, const CScript& committedScript, const std::vector& vchRangeproof, CAmount& amount_out, uint256& blinding_factor_out, CAsset& asset_out, uint256& asset_blinding_factor_out); + +bool GenerateRangeproof(std::vector& rangeproof, const std::vector& value_blindptrs, const uint256& nonce, const CAmount amount, const CScript& scriptPubKey, const secp256k1_pedersen_commitment& value_commit, const secp256k1_generator& gen, const CAsset& asset, std::vector& asset_blindptrs); + +bool SurjectOutput(CTxOutWitness& txoutwit, const std::vector& surjection_targets, const std::vector& target_asset_generators, const std::vector& target_asset_blinders, const std::vector asset_blindptrs, const secp256k1_generator& output_asset_gen, const CAsset& asset); + +uint256 GenerateOutputRangeproofNonce(CTxOut& out, const CPubKey output_pubkey); + +void BlindAsset(CConfidentialAsset& conf_asset, secp256k1_generator& asset_gen, const CAsset& asset, const unsigned char* asset_blindptr); + +void CreateValueCommitment(CConfidentialValue& conf_value, secp256k1_pedersen_commitment& value_commit, const unsigned char* value_blindptr, const secp256k1_generator& asset_gen, const CAmount amount); + +/* Returns the number of outputs that were successfully blinded. + * In many cases a `0` can be fixed by adding an additional output. + * @param[in] input_blinding_factors - A vector of input blinding factors that will be used to create the balanced output blinding factors + * @param[in] input_asset_blinding_factors - A vector of input asset blinding factors that will be used to create the balanced output blinding factors + * @param[in] input_assets - the asset of each corresponding input + * @param[in] input_amounts - the unblinded amounts of each input. Required for owned blinded inputs + * @param[in/out] output_blinding_factors - A vector of blinding factors. New blinding factors will replace these values. + * @param[in/out] output_asset_blinding_factors - A vector of asset blinding factors. New blinding factors will replace these values. + * @param[in] output_pubkeys - If valid, corresponding output must be unblinded, and will result in fully blinded output, modifying the output blinding arguments as well. + * @param[in] vBlindIssuanceAsset - List of keys to use as nonces for issuance asset blinding. + * @param[in] vBlindIssuanceToken - List of keys to use as nonces for issuance token blinding. + * @param[in/out] tx - The transaction to be modified. + * @param[in] auxiliary_generators - a list of generators to create surjection proofs when inputs are not owned by caller. Passing in non-empty elements results in ignoring of other input arguments for that index + */ +int BlindTransaction(std::vector& input_value_blinding_factors, const std::vector& input_asset_blinding_factors, const std::vector& input_assets, const std::vector& input_amounts, std::vector& out_val_blind_factors, std::vector& out_asset_blind_factors, const std::vector& output_pubkeys, const std::vector& issuance_blinding_privkey, const std::vector& token_blinding_privkey, CMutableTransaction& tx, std::vector >* auxiliary_generators = nullptr); + +/* + * Extract pubkeys from nonce commitment placeholders, fill out vector of blank output blinding data + */ +void RawFillBlinds(CMutableTransaction& tx, std::vector& output_value_blinds, std::vector& output_asset_blinds, std::vector& output_pubkeys); + +size_t GetNumIssuances(const CTransaction& tx); + +#endif //BITCOIN_BLIND_H diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 3a9533ff1a555..6cd7b36320e0c 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -47,10 +48,8 @@ static CBlock CreateGenesisBlock(const Consensus::Params& params, const CScript& CMutableTransaction txNew; txNew.nVersion = 1; txNew.vin.resize(1); - txNew.vout.resize(1); txNew.vin[0].scriptSig = genesisScriptSig; - txNew.vout[0].nValue = genesisReward; - txNew.vout[0].scriptPubKey = genesisOutputScript; + txNew.vout.push_back(CTxOut(CAsset(), genesisReward, genesisOutputScript)); CBlock genesis; genesis.nTime = nTime; @@ -85,23 +84,6 @@ static CBlock CreateGenesisBlock(uint32_t nTime, uint32_t nNonce, uint32_t nBits return CreateGenesisBlock(params, genesisScriptSig, genesisOutputScript, nTime, nNonce, nBits, nVersion, genesisReward); } -/** Add an issuance transaction to the genesis block. Typically used to pre-issue - * the policyAsset of a blockchain. The genesis block is not actually validated, - * so this transaction simply has to match issuance structure. */ -static void AppendInitialIssuance(CBlock& genesis_block, const COutPoint& prevout, const int64_t asset_values, const CScript& issuance_destination) { - - // Note: Genesis block isn't actually validated, outputs are entered into utxo db only - CMutableTransaction txNew; - txNew.nVersion = 1; - txNew.vin.resize(1); - txNew.vin[0].prevout = prevout; - - txNew.vout.push_back(CTxOut(asset_values, issuance_destination)); - - genesis_block.vtx.push_back(MakeTransactionRef(std::move(txNew))); - genesis_block.hashMerkleRoot = BlockMerkleRoot(genesis_block); -} - /** * Main network */ @@ -144,11 +126,14 @@ class CMainParams : public CChainParams { consensus.genesis_subsidy = 50*COIN; consensus.connect_genesis_outputs = false; + consensus.subsidy_asset = CAsset(); anyonecanspend_aremine = false; enforce_pak = false; multi_data_permitted = false; consensus.has_parent_chain = false; g_signed_blocks = false; + g_con_elementsmode = false; + g_con_blockheightinheader = false; /** * The message start string is designed to be unlikely to occur in normal data. @@ -187,6 +172,7 @@ class CMainParams : public CChainParams { base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x88, 0xAD, 0xE4}; bech32_hrp = "bc"; + blech32_hrp = blech32_hrp; vFixedSeeds = std::vector(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main)); @@ -266,10 +252,14 @@ class CTestNetParams : public CChainParams { consensus.genesis_subsidy = 50*COIN; consensus.connect_genesis_outputs = false; + consensus.subsidy_asset = CAsset(); anyonecanspend_aremine = false; enforce_pak = false; multi_data_permitted = false; consensus.has_parent_chain = false; + g_signed_blocks = false; + g_con_elementsmode = false; + g_con_blockheightinheader = false; pchMessageStart[0] = 0x0b; pchMessageStart[1] = 0x11; @@ -298,6 +288,7 @@ class CTestNetParams : public CChainParams { base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94}; bech32_hrp = "tb"; + blech32_hrp = blech32_hrp; vFixedSeeds = std::vector(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test)); @@ -362,10 +353,14 @@ class CRegTestParams : public CChainParams { consensus.genesis_subsidy = 50*COIN; consensus.connect_genesis_outputs = false; + consensus.subsidy_asset = CAsset(); anyonecanspend_aremine = false; enforce_pak = false; multi_data_permitted = false; consensus.has_parent_chain = false; + g_signed_blocks = false; + g_con_elementsmode = false; + g_con_blockheightinheader = false; pchMessageStart[0] = 0xfa; pchMessageStart[1] = 0xbf; @@ -407,6 +402,7 @@ class CRegTestParams : public CChainParams { base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94}; bech32_hrp = "bcrt"; + blech32_hrp = blech32_hrp; /* enable fallback fee on regtest */ m_fallback_fee_enabled = true; @@ -486,6 +482,7 @@ class CCustomParams : public CRegTestParams { m_fallback_fee_enabled = args.GetBoolArg("-fallback_fee_enabled", m_fallback_fee_enabled); bech32_hrp = args.GetArg("-bech32_hrp", bech32_hrp); + blech32_hrp = args.GetArg("-blech32_hrp", "el"); base58Prefixes[PUBKEY_ADDRESS] = std::vector(1, args.GetArg("-pubkeyprefix", 111)); base58Prefixes[SCRIPT_ADDRESS] = std::vector(1, args.GetArg("-scriptprefix", 196)); base58Prefixes[SECRET_KEY] = std::vector(1, args.GetArg("-secretprefix", 239)); @@ -527,7 +524,7 @@ class CCustomParams : public CRegTestParams { // Note: These globals are needed to avoid circular dependencies. // Default to true for custom chains. g_con_blockheightinheader = args.GetBoolArg("-con_blockheightinheader", true); - g_con_elementswitness = args.GetBoolArg("-con_elementswitness", true); + g_con_elementsmode = args.GetBoolArg("-con_elementsmode", true); // No subsidy for custom chains by default consensus.genesis_subsidy = args.GetArg("-con_blocksubsidy", 0); @@ -565,6 +562,30 @@ class CCustomParams : public CRegTestParams { base58Prefixes[PARENT_PUBKEY_ADDRESS] = std::vector(1, args.GetArg("-parentpubkeyprefix", 111)); base58Prefixes[PARENT_SCRIPT_ADDRESS] = std::vector(1, args.GetArg("-parentscriptprefix", 196)); parent_bech32_hrp = args.GetArg("-parent_bech32_hrp", "bcrt"); + parent_blech32_hrp = args.GetArg("-parent_bech32_hrp", "bcrt"); + + base58Prefixes[BLINDED_ADDRESS] = std::vector(1, args.GetArg("-blindedprefix", 4)); + + // Calculate pegged Bitcoin asset + std::vector commit = CommitToArguments(consensus, strNetworkID); + uint256 entropy; + GenerateAssetEntropy(entropy, COutPoint(uint256(commit), 0), parentGenesisBlockHash); + + // Elements serialization uses derivation, bitcoin serialization uses 0x00 + if (g_con_elementsmode) { + CalculateAsset(consensus.pegged_asset, entropy); + } else { + assert(consensus.pegged_asset == CAsset()); + } + + consensus.parent_pegged_asset.SetHex(args.GetArg("-con_parent_pegged_asset", "0x00")); + initial_reissuance_tokens = args.GetArg("-initialreissuancetokens", 0); + + // Subsidy asset, like policyAsset, defaults to the pegged_asset + consensus.subsidy_asset = consensus.pegged_asset; + if (gArgs.IsArgSet("-subsidyasset")) { + consensus.subsidy_asset = CAsset(uint256S(gArgs.GetArg("-subsidyasset", "0x00"))); + } // END ELEMENTS fields @@ -583,8 +604,8 @@ class CCustomParams : public CRegTestParams { // Intended compatibility with Liquid v1 and elements-0.14.1 std::vector commit = CommitToArguments(consensus, strNetworkID); genesis = CreateGenesisBlock(consensus, CScript(commit), CScript(OP_RETURN), 1296688602, 2, 0x207fffff, 1, 0); - if (initialFreeCoins != 0) { - AppendInitialIssuance(genesis, COutPoint(uint256(commit), 0), initialFreeCoins, CScript() << OP_TRUE); + if (initialFreeCoins != 0 || initial_reissuance_tokens != 0) { + AppendInitialIssuance(genesis, COutPoint(uint256(commit), 0), parentGenesisBlockHash, (initialFreeCoins > 0) ? 1 : 0, initialFreeCoins, (initial_reissuance_tokens > 0) ? 1 : 0, initial_reissuance_tokens, CScript() << OP_TRUE); } } else { throw std::runtime_error(strprintf("Invalid -genesis_style (%s)", consensus.genesis_style)); @@ -601,6 +622,145 @@ class CCustomParams : public CRegTestParams { } }; +/** + * Liquid v1 + */ +class CLiquidV1Params : public CChainParams { +public: + CLiquidV1Params() + { + + strNetworkID = "liquidv1"; + consensus.nSubsidyHalvingInterval = 150; + consensus.BIP16Exception = uint256(); + consensus.BIP34Height = 0; + consensus.BIP34Hash = uint256(); + consensus.BIP65Height = 0; + consensus.BIP66Height = 0; + consensus.powLimit = uint256S("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks; + consensus.nPowTargetSpacing = 60; // Minute block assumption + consensus.fPowAllowMinDifficultyBlocks = true; + consensus.fPowNoRetargeting = true; + consensus.nRuleChangeActivationThreshold = 108; + consensus.nMinerConfirmationWindow = 144; + + consensus.nMinimumChainWork = uint256(); + consensus.defaultAssumeValid = uint256(); + + nPruneAfterHeight = 1000; + fDefaultConsistencyChecks = false; + fRequireStandard = true; + fMineBlocksOnDemand = false; + m_fallback_fee_enabled = false; // TODO Will this break stuff? + + bech32_hrp = "ex"; // ex(plicit) + blech32_hrp = "lq"; // l(i)q(uid) + parent_bech32_hrp = "bc"; + parent_blech32_hrp = "bc"; // Doesn't exist but... + + base58Prefixes[PUBKEY_ADDRESS] = std::vector(1, 57); + base58Prefixes[SCRIPT_ADDRESS] = std::vector(1, 39); + base58Prefixes[SECRET_KEY] = std::vector(1, 128); + base58Prefixes[BLINDED_ADDRESS]= std::vector(1,12); + + base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x88, 0xB2, 0x1E}; + base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x88, 0xAD, 0xE4}; + + base58Prefixes[PARENT_PUBKEY_ADDRESS] = std::vector(1,0); + base58Prefixes[PARENT_SCRIPT_ADDRESS] = std::vector(1,5); + + pchMessageStart[0] = 0xfa; + pchMessageStart[1] = 0xbf; + pchMessageStart[2] = 0xb5; + pchMessageStart[3] = 0xda; + + nDefaultPort = 7042; + + vSeeds.clear(); + vSeeds.emplace_back("seed.liquidnetwork.io"); + vFixedSeeds = std::vector(pnSeed6_liquidv1, pnSeed6_liquidv1 + ARRAYLEN(pnSeed6_liquidv1)); + + // + // ELEMENTS fields + + consensus.genesis_style = "elements"; // unused here but let's set it anyways + + // Block signing encumberance script, default of 51 aka OP_TRUE + std::vector sign_bytes = ParseHex("5b21026a2a106ec32c8a1e8052e5d02a7b0a150423dbd9b116fc48d46630ff6e6a05b92102791646a8b49c2740352b4495c118d876347bf47d0551c01c4332fdc2df526f1a2102888bda53a424466b0451627df22090143bbf7c060e9eacb1e38426f6b07f2ae12102aee8967150dee220f613de3b239320355a498808084a93eaf39a34dcd62024852102d46e9259d0a0bb2bcbc461a3e68f34adca27b8d08fbe985853992b4b104e27412102e9944e35e5750ab621e098145b8e6cf373c273b7c04747d1aa020be0af40ccd62102f9a9d4b10a6d6c56d8c955c547330c589bb45e774551d46d415e51cd9ad5116321033b421566c124dfde4db9defe4084b7aa4e7f36744758d92806b8f72c2e943309210353dcc6b4cf6ad28aceb7f7b2db92a4bf07ac42d357adf756f3eca790664314b621037f55980af0455e4fb55aad9b85a55068bb6dc4740ea87276dc693f4598db45fa210384001daa88dabd23db878dbb1ce5b4c2a5fa72c3113e3514bf602325d0c37b8e21039056d089f2fe72dbc0a14780b4635b0dc8a1b40b7a59106325dd1bc45cc70493210397ab8ea7b0bf85bc7fc56bb27bf85e75502e94e76a6781c409f3f2ec3d1122192103b00e3b5b77884bf3cae204c4b4eac003601da75f96982ffcb3dcb29c5ee419b92103c1f3c0874cfe34b8131af34699589aacec4093399739ae352e8a46f80a6f68375fae"); + consensus.signblockscript = CScript(sign_bytes.begin(), sign_bytes.end()); + consensus.max_block_signature_size = 12*74; // 11 signatures plus wiggle room + g_signed_blocks = true; + + g_con_blockheightinheader = true; + g_con_elementsmode = true; + + consensus.genesis_subsidy = 0; + + // All non-zero coinbase outputs must go to this scriptPubKey + std::vector man_bytes = ParseHex("76a914fc26751a5025129a2fd006c6fbfa598ddd67f7e188ac"); + consensus.mandatory_coinbase_destination = CScript(man_bytes.begin(), man_bytes.end()); // Blank script allows any coinbase destination + + // Custom chains connect coinbase outputs to db by default + consensus.connect_genesis_outputs = true; + + initialFreeCoins = 0; + + anyonecanspend_aremine = false; + + consensus.has_parent_chain = true; + + enforce_pak = true; + + multi_data_permitted = true; + + parentGenesisBlockHash = uint256S("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"); + const bool parent_genesis_is_null = parentGenesisBlockHash == uint256(); + assert(consensus.has_parent_chain != parent_genesis_is_null); + consensus.parentChainPowLimit = uint256S("0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff"); + consensus.parent_chain_signblockscript = CScript(); // It has PoW + consensus.pegin_min_depth = 100; + + const CScript default_script(CScript() << OP_TRUE); + consensus.fedpegScript = StrHexToScriptWithDefault("745c87635b21020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b678172612102675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af992102896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d4821029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c2102a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc40102102f8a00b269f8c5e59c67d36db3cdc11b11b21f64b4bffb2815e9100d9aa8daf072103079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b2103111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2210318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa08401742103230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de121035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a62103bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c2103cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d175462103d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d4248282103ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a5f6702c00fb275522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb5368ae", default_script); + + + // Calculate pegged Bitcoin asset + std::vector commit = CommitToArguments(consensus, strNetworkID); + uint256 entropy; + GenerateAssetEntropy(entropy, COutPoint(uint256(commit), 0), parentGenesisBlockHash); + + // Elements serialization uses derivation, bitcoin serialization uses 0x00 + if (g_con_elementsmode) { + CalculateAsset(consensus.pegged_asset, entropy); + } else { + assert(consensus.pegged_asset == CAsset()); + } + + consensus.parent_pegged_asset.SetHex("0x00"); // No parent pegged asset + initial_reissuance_tokens = 0; + + consensus.subsidy_asset = consensus.pegged_asset; + + // CSV always active + consensus.vDeployments[Consensus::DEPLOYMENT_CSV].bit = 0; + consensus.vDeployments[Consensus::DEPLOYMENT_CSV].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE; + consensus.vDeployments[Consensus::DEPLOYMENT_CSV].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; + // Segwit, likewise + consensus.vDeployments[Consensus::DEPLOYMENT_SEGWIT].bit = 1; + consensus.vDeployments[Consensus::DEPLOYMENT_SEGWIT].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE; + consensus.vDeployments[Consensus::DEPLOYMENT_SEGWIT].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; + + + // Finally, create genesis block + genesis = CreateGenesisBlock(consensus, CScript(commit), CScript(OP_RETURN), 1296688602, 2, 0x207fffff, 1, 0); + consensus.hashGenesisBlock = genesis.GetHash(); + assert(consensus.hashGenesisBlock.GetHex() == "1466275836220db2944ca059a3a10ef6fd2ea684b0688d2c379296888a206003"); + } + +}; + + static std::unique_ptr globalChainParams; const CChainParams &Params() { @@ -617,6 +777,8 @@ std::unique_ptr CreateChainParams(const std::string& chain) return std::unique_ptr(new CTestNetParams()); else if (chain == CBaseChainParams::REGTEST) return std::unique_ptr(new CRegTestParams(gArgs)); + else if (chain == CBaseChainParams::LIQUID1) + return std::unique_ptr(new CLiquidV1Params()); return std::unique_ptr(new CCustomParams(chain, gArgs)); } diff --git a/src/chainparams.h b/src/chainparams.h index 8a9e760f8b058..8bd849bda00d1 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -54,6 +54,7 @@ class CChainParams EXT_PUBLIC_KEY, EXT_SECRET_KEY, // ELEMENTS + BLINDED_ADDRESS, PARENT_PUBKEY_ADDRESS, PARENT_SCRIPT_ADDRESS, @@ -80,6 +81,7 @@ class CChainParams const std::vector& DNSSeeds() const { return vSeeds; } const std::vector& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; } const std::string& Bech32HRP() const { return bech32_hrp; } + const std::string& Blech32HRP() const { return blech32_hrp; } const std::vector& FixedSeeds() const { return vFixedSeeds; } const CCheckpointData& Checkpoints() const { return checkpointData; } const ChainTxData& TxData() const { return chainTxData; } @@ -87,6 +89,7 @@ class CChainParams const uint256 ParentGenesisBlockHash() const { return parentGenesisBlockHash; } bool anyonecanspend_aremine; const std::string& ParentBech32HRP() const { return parent_bech32_hrp; } + const std::string& ParentBlech32HRP() const { return parent_blech32_hrp; } bool GetEnforcePak() const { return enforce_pak; } bool GetMultiDataPermitted() const { return multi_data_permitted; } @@ -100,9 +103,11 @@ class CChainParams std::vector vSeeds; std::vector base58Prefixes[MAX_BASE58_TYPES]; std::string bech32_hrp; + std::string blech32_hrp; std::string strNetworkID; CBlock genesis; CAmount initialFreeCoins; + CAmount initial_reissuance_tokens; std::vector vFixedSeeds; bool fDefaultConsistencyChecks; bool fRequireStandard; @@ -113,6 +118,7 @@ class CChainParams // ELEMENTS extra fields: uint256 parentGenesisBlockHash; std::string parent_bech32_hrp; + std::string parent_blech32_hrp; bool enforce_pak; bool multi_data_permitted; }; diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index 9f856be51f794..5b442b292805e 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -14,6 +14,7 @@ const std::string CBaseChainParams::MAIN = "main"; const std::string CBaseChainParams::TESTNET = "test"; const std::string CBaseChainParams::REGTEST = "regtest"; +const std::string CBaseChainParams::LIQUID1 = "liquidv1"; void SetupChainParamsBaseOptions() { @@ -26,7 +27,7 @@ void SetupChainParamsBaseOptions() gArgs.AddArg("-con_mandatorycoinbase", "All non-zero valued coinbase outputs must go to this scriptPubKey, if set.", false, OptionsCategory::ELEMENTS); gArgs.AddArg("-con_blocksubsidy", "Defines the amount of block subsidy to start with, at genesis block.", false, OptionsCategory::ELEMENTS); gArgs.AddArg("-con_connect_coinbase", "Connect outputs in genesis block to utxo database.", false, OptionsCategory::ELEMENTS); - gArgs.AddArg("-con_elementswitness", "Use Elements-like instead of Core-like witness encoding. This is required for CA/CT. (default: true)", false, OptionsCategory::ELEMENTS); + gArgs.AddArg("-con_elementsmode", "Use Elements-like instead of Core-like witness encoding. This is required for CA/CT. (default: true)", false, OptionsCategory::ELEMENTS); gArgs.AddArg("-con_blockheightinheader", "Whether the chain includes the block height directly in the header, for easier validation of block height in low-resource environments. (default: true)", false, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-con_genesis_style=