diff --git a/bip-0324.mediawiki b/bip-0324.mediawiki index 8a8861a5..47032ddc 100644 --- a/bip-0324.mediawiki +++ b/bip-0324.mediawiki @@ -247,19 +247,19 @@ To find encodings of a given X coordinate ''x'', we first need the inverse of '' * ''XSwiftECInv(x, u, case)'': ** If ''case & 2 = 0'': *** If ''lift_x(-x - u)'' succeeds, return ''None''. -*** Let ''v = x'' if ''case & 1 = 0''; let ''v = -x - u (mod p)'' otherwise. +*** Let ''v = x''. *** Let ''s = -(u3 + 7)/(u2 + uv + v2) (mod p)''. ** If ''case & 2 = 2'': *** Let ''s = x - u (mod p)''. *** If ''s = 0'', return ''None''. *** Let ''r'' be the square root of ''-s(4(u3 + 7) + 3u2s) (mod p).'''''How to compute a square root mod ''p''?''' Due to the structure of ''p'', a candidate for the square root of ''a'' mod ''p'' can be computed as ''x = a(p+1)/4 mod p''. If ''a'' is not a square mod ''p'', this formula returns the square root of ''-a mod p'' instead, so it is necessary to verify that ''x2 mod p = a''. If that is the case ''-x mod p'' is a solution too, but we define "the" square root to be equal to that expression (the square root will therefore always be a square itself, as ''(p+1)/4'' is even). This algorithm is a specialization of the [https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm Tonelli-Shanks algorithm]. Return ''None'' if it does not exist. -*** If ''case & 1 = 1'': -**** If ''r = 0'', return ''None''. -**** let ''r = -r (mod p)''. +** If ''case & 1 = 1'' and ''r = 0'', return ''None''. *** Let ''v = (-u + r/s)/2''. ** Let ''w'' be the square root of ''s (mod p)''. Return ''None'' if it does not exist. -** If ''case & 4 = 4'', let ''w = -w (mod p)''. -** Return ''w(u(c - 1)/2 - v)''. +** If ''case & 5 = 0'', return ''-w(u(1 - c)/2 + v)''. +** If ''case & 5 = 1'', return ''w(u(1 + c)/2 + v)''. +** If ''case & 5 = 4'', return ''w(u(1 - c)/2 + v)''. +** If ''case & 5 = 5'', return ''-w(u(1 + c)/2 + v)''. The overall ''XElligatorSwift'' algorithm, matching the name used in the paper, then uses this inverse to randomly'''''Can the ElligatorSwift encoding be used to construct public key encodings that satisfy a certain structure (and not pseudorandom)?''' The algorithm chooses the first 32 bytes (i.e., the value ''u'') and then computes a corresponding ''t'' such that the mapping to the curve point holds. In general, picking ''u'' from a uniformly random distribution provides pseudorandomness. But we can also fix any of the 32 bytes in ''u'', and the algorithm will still find a corresponding ''t''. The fact that it is possible to fix the first 32 bytes, combined with the garbage bytes in the handshake, provides a limited but very simple method of parroting other protocols such as [https://tls13.xargs.org/ TLS 1.3], which can be deployed by one of the peers without explicit support from the other peer. More general methods of parroting, e.g., introduced by defining new protocol or a protocol upgrade, are not precluded. sample encodings of ''x'': @@ -586,8 +586,8 @@ Peers supporting the v2 transport protocol signal support by advertising the =p +0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff2664bbd5,50873db31badcc71890e4f67753a65757f97aaa7dd5f1e82b753ace32219064b,u%p=0;valid_x(x3);valid_x(x2);valid_x(x1);t>=p +0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff7028de7d,1eea9cc59cfcf2fa151ac6c274eea4110feb4f7b68c5965732e9992e976ef68e,u%p=0;valid_x(x2);t>=p +0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffcbcfb7e7,12303941aedc208880735b1f1795c8e55be520ea93e103357b5d2adb7ed59b8e,u%p=0;valid_x(x1);t>=p +0000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffff3113ad9,7eed6b70e7b0767c7d7feac04e57aa2a12fef5e0f48f878fcbb88b3b6b5e0783,u%p=0;valid_x(x3);t>=p +0a2d2ba93507f1df233770c2a797962cc61f6d15da14ecd47d8d27ae1cd5f8530000000000000000000000000000000000000000000000000000000000000000,532167c11200b08c0e84a354e74dcc40f8b25f4fe686e30869526366278a0688,t%p=0;(u'^3+t'^2+7)%p=0;valid_x(x3);valid_x(x2);valid_x(x1) +0a2d2ba93507f1df233770c2a797962cc61f6d15da14ecd47d8d27ae1cd5f853fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,532167c11200b08c0e84a354e74dcc40f8b25f4fe686e30869526366278a0688,t%p=0;(u'^3+t'^2+7)%p=0;valid_x(x3);valid_x(x2);valid_x(x1);t>=p +0ffde9ca81d751e9cdaffc1a50779245320b28996dbaf32f822f20117c22fbd6c74d99efceaa550f1ad1c0f43f46e7ff1ee3bd0162b7bf55f2965da9c3450646,74e880b3ffd18fe3cddf7902522551ddf97fa4a35a3cfda8197f947081a57b8f,valid_x(x3) +0ffde9ca81d751e9cdaffc1a50779245320b28996dbaf32f822f20117c22fbd6ffffffffffffffffffffffffffffffffffffffffffffffffffffffff156ca896,377b643fce2271f64e5c8101566107c1be4980745091783804f654781ac9217c,valid_x(x2);t>=p +123658444f32be8f02ea2034afa7ef4bbe8adc918ceb49b12773b625f490b368ffffffffffffffffffffffffffffffffffffffffffffffffffffffff8dc5fe11,ed16d65cf3a9538fcb2c139f1ecbc143ee14827120cbc2659e667256800b8142,(u'^3-t'^2+7)%p=0;valid_x(x3);valid_x(x2);valid_x(x1);t>=p +146f92464d15d36e35382bd3ca5b0f976c95cb08acdcf2d5b3570617990839d7ffffffffffffffffffffffffffffffffffffffffffffffffffffffff3145e93b,0d5cd840427f941f65193079ab8e2e83024ef2ee7ca558d88879ffd879fb6657,(u'^3+t'^2+7)%p=0;valid_x(x3);t>=p +15fdf5cf09c90759add2272d574d2bb5fe1429f9f3c14c65e3194bf61b82aa73ffffffffffffffffffffffffffffffffffffffffffffffffffffffff04cfd906,16d0e43946aec93f62d57eb8cde68951af136cf4b307938dd1447411e07bffe1,(u'^3+t'^2+7)%p=0;valid_x(x2);t>=p +1f67edf779a8a649d6def60035f2fa22d022dd359079a1a144073d84f19b92d50000000000000000000000000000000000000000000000000000000000000000,025661f9aba9d15c3118456bbe980e3e1b8ba2e047c737a4eb48a040bb566f6c,t%p=0;valid_x(x2) +1f67edf779a8a649d6def60035f2fa22d022dd359079a1a144073d84f19b92d5fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,025661f9aba9d15c3118456bbe980e3e1b8ba2e047c737a4eb48a040bb566f6c,t%p=0;valid_x(x2);t>=p +1fe1e5ef3fceb5c135ab7741333ce5a6e80d68167653f6b2b24bcbcfaaaff507fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,98bec3b2a351fa96cfd191c1778351931b9e9ba9ad1149f6d9eadca80981b801,t%p=0;(u'^3-t'^2+7)%p=0;valid_x(x3);valid_x(x2);valid_x(x1);t>=p +4056a34a210eec7892e8820675c860099f857b26aad85470ee6d3cf1304a9dcf375e70374271f20b13c9986ed7d3c17799698cfc435dbed3a9f34b38c823c2b4,868aac2003b29dbcad1a3e803855e078a89d16543ac64392d122417298cec76e,(u'^3-t'^2+7)%p=0;valid_x(x3) +4197ec3723c654cfdd32ab075506648b2ff5070362d01a4fff14b336b78f963fffffffffffffffffffffffffffffffffffffffffffffffffffffffffb3ab1e95,ba5a6314502a8952b8f456e085928105f665377a8ce27726a5b0eb7ec1ac0286,(u'^3+t'^2+7)%p=0;valid_x(x1);t>=p +47eb3e208fedcdf8234c9421e9cd9a7ae873bfbdbc393723d1ba1e1e6a8e6b24ffffffffffffffffffffffffffffffffffffffffffffffffffffffff7cd12cb1,d192d52007e541c9807006ed0468df77fd214af0a795fe119359666fdcf08f7c,(u'^3+t'^2+7)%p=0;valid_x(x3);valid_x(x2);valid_x(x1);t>=p +5eb9696a2336fe2c3c666b02c755db4c0cfd62825c7b589a7b7bb442e141c1d693413f0052d49e64abec6d5831d66c43612830a17df1fe4383db896468100221,ef6e1da6d6c7627e80f7a7234cb08a022c1ee1cf29e4d0f9642ae924cef9eb38,(u'^3+t'^2+7)%p=0;valid_x(x1) +7bf96b7b6da15d3476a2b195934b690a3a3de3e8ab8474856863b0de3af90b0e0000000000000000000000000000000000000000000000000000000000000000,50851dfc9f418c314a437295b24feeea27af3d0cd2308348fda6e21c463e46ff,t%p=0;valid_x(x1) +7bf96b7b6da15d3476a2b195934b690a3a3de3e8ab8474856863b0de3af90b0efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,50851dfc9f418c314a437295b24feeea27af3d0cd2308348fda6e21c463e46ff,t%p=0;valid_x(x1);t>=p +851b1ca94549371c4f1f7187321d39bf51c6b7fb61f7cbf027c9da62021b7a65fc54c96837fb22b362eda63ec52ec83d81bedd160c11b22d965d9f4a6d64d251,3e731051e12d33237eb324f2aa5b16bb868eb49a1aa1fadc19b6e8761b5a5f7b,(u'^3+t'^2+7)%p=0;valid_x(x2) +943c2f775108b737fe65a9531e19f2fc2a197f5603e3a2881d1d83e4008f91250000000000000000000000000000000000000000000000000000000000000000,311c61f0ab2f32b7b1f0223fa72f0a78752b8146e46107f8876dd9c4f92b2942,t%p=0;valid_x(x3);valid_x(x2);valid_x(x1) +943c2f775108b737fe65a9531e19f2fc2a197f5603e3a2881d1d83e4008f9125fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,311c61f0ab2f32b7b1f0223fa72f0a78752b8146e46107f8876dd9c4f92b2942,t%p=0;valid_x(x3);valid_x(x2);valid_x(x1);t>=p +a0f18492183e61e8063e573606591421b06bc3513631578a73a39c1c3306239f2f32904f0d2a33ecca8a5451705bb537d3bf44e071226025cdbfd249fe0f7ad6,97a09cf1a2eae7c494df3c6f8a9445bfb8c09d60832f9b0b9d5eabe25fbd14b9,valid_x(x1) +a1ed0a0bd79d8a23cfe4ec5fef5ba5cccfd844e4ff5cb4b0f2e71627341f1c5b17c499249e0ac08d5d11ea1c2c8ca7001616559a7994eadec9ca10fb4b8516dc,65a89640744192cdac64b2d21ddf989cdac7500725b645bef8e2200ae39691f2,valid_x(x2) +ba94594a432721aa3580b84c161d0d134bc354b690404d7cd4ec57c16d3fbe98ffffffffffffffffffffffffffffffffffffffffffffffffffffffffea507dd7,5e0d76564aae92cb347e01a62afd389a9aa401c76c8dd227543dc9cd0efe685a,valid_x(x1);t>=p +bcaf7219f2f6fbf55fe5e062dce0e48c18f68103f10b8198e974c184750e1be3932016cbf69c4471bd1f656c6a107f1973de4af7086db897277060e25677f19a,2d97f96cac882dfe73dc44db6ce0f1d31d6241358dd5d74eb3d3b50003d24c2b,valid_x(x3);valid_x(x2);valid_x(x1) +bcaf7219f2f6fbf55fe5e062dce0e48c18f68103f10b8198e974c184750e1be3ffffffffffffffffffffffffffffffffffffffffffffffffffffffff6507d09a,e7008afe6e8cbd5055df120bd748757c686dadb41cce75e4addcc5e02ec02b44,valid_x(x3);valid_x(x2);valid_x(x1);t>=p +c5981bae27fd84401c72a155e5707fbb811b2b620645d1028ea270cbe0ee225d4b62aa4dca6506c1acdbecc0552569b4b21436a5692e25d90d3bc2eb7ce24078,948b40e7181713bc018ec1702d3d054d15746c59a7020730dd13ecf985a010d7,(u'^3+t'^2+7)%p=0;valid_x(x3) +c894ce48bfec433014b931a6ad4226d7dbd8eaa7b6e3faa8d0ef94052bcf8cff336eeb3919e2b4efb746c7f71bbca7e9383230fbbc48ffafe77e8bcc69542471,f1c91acdc2525330f9b53158434a4d43a1c547cff29f15506f5da4eb4fe8fa5a,(u'^3-t'^2+7)%p=0;valid_x(x3);valid_x(x2);valid_x(x1) +cbb0deab125754f1fdb2038b0434ed9cb3fb53ab735391129994a535d925f6730000000000000000000000000000000000000000000000000000000000000000,872d81ed8831d9998b67cb7105243edbf86c10edfebb786c110b02d07b2e67cd,t%p=0;(u'^3-t'^2+7)%p=0;valid_x(x3);valid_x(x2);valid_x(x1) +d917b786dac35670c330c9c5ae5971dfb495c8ae523ed97ee2420117b171f41effffffffffffffffffffffffffffffffffffffffffffffffffffffff2001f6f6,e45b71e110b831f2bdad8651994526e58393fde4328b1ec04d59897142584691,valid_x(x3);t>=p +e28bd8f5929b467eb70e04332374ffb7e7180218ad16eaa46b7161aa679eb4260000000000000000000000000000000000000000000000000000000000000000,66b8c980a75c72e598d383a35a62879f844242ad1e73ff12edaa59f4e58632b5,t%p=0;valid_x(x3) +e28bd8f5929b467eb70e04332374ffb7e7180218ad16eaa46b7161aa679eb426fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,66b8c980a75c72e598d383a35a62879f844242ad1e73ff12edaa59f4e58632b5,t%p=0;valid_x(x3);t>=p +e7ee5814c1706bf8a89396a9b032bc014c2cac9c121127dbf6c99278f8bb53d1dfd04dbcda8e352466b6fcd5f2dea3e17d5e133115886eda20db8a12b54de71b,e842c6e3529b234270a5e97744edc34a04d7ba94e44b6d2523c9cf0195730a50,(u'^3+t'^2+7)%p=0;valid_x(x3);valid_x(x2);valid_x(x1) +f292e46825f9225ad23dc057c1d91c4f57fcb1386f29ef10481cb1d22518593fffffffffffffffffffffffffffffffffffffffffffffffffffffffff7011c989,3cea2c53b8b0170166ac7da67194694adacc84d56389225e330134dab85a4d55,(u'^3-t'^2+7)%p=0;valid_x(x3);t>=p +fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f0000000000000000000000000000000000000000000000000000000000000000,edd1fd3e327ce90cc7a3542614289aee9682003e9cf7dcc9cf2ca9743be5aa0c,u%p=0;t%p=0;valid_x(x2);u>=p +fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f01d3475bf7655b0fb2d852921035b2ef607f49069b97454e6795251062741771,b5da00b73cd6560520e7c364086e7cd23a34bf60d0e707be9fc34d4cd5fdfa2c,u%p=0;valid_x(x1);u>=p +fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f4218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d587e76a7ee,aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9fffffd6b,u%p=0;(u'^3-t'^2+7)%p=0;valid_x(x3);u>=p +fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f82277c4a71f9d22e66ece523f8fa08741a7c0912c66a69ce68514bfd3515b49f,f482f2e241753ad0fb89150d8491dc1e34ff0b8acfbb442cfe999e2e5e6fd1d2,u%p=0;valid_x(x3);valid_x(x2);valid_x(x1);u>=p +fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f8421cc930e77c9f514b6915c3dbe2a94c6d8f690b5b739864ba6789fb8a55dd0,9f59c40275f5085a006f05dae77eb98c6fd0db1ab4a72ac47eae90a4fc9e57e0,u%p=0;valid_x(x2);u>=p +fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fd19c182d2759cd99824228d94799f8c6557c38a1c0d6779b9d4b729c6f1ccc42,70720db7e238d04121f5b1afd8cc5ad9d18944c6bdc94881f502b7a3af3aecff,u%p=0;valid_x(x3);u>=p +fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,edd1fd3e327ce90cc7a3542614289aee9682003e9cf7dcc9cf2ca9743be5aa0c,u%p=0;t%p=0;valid_x(x2);u>=p;t>=p +fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fffffffffffffffffffffffffffffffffffffffffffffffffffffffff2664bbd5,50873db31badcc71890e4f67753a65757f97aaa7dd5f1e82b753ace32219064b,u%p=0;valid_x(x3);valid_x(x2);valid_x(x1);u>=p;t>=p +fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fffffffffffffffffffffffffffffffffffffffffffffffffffffffff7028de7d,1eea9cc59cfcf2fa151ac6c274eea4110feb4f7b68c5965732e9992e976ef68e,u%p=0;valid_x(x2);u>=p;t>=p +fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fffffffffffffffffffffffffffffffffffffffffffffffffffffffffcbcfb7e7,12303941aedc208880735b1f1795c8e55be520ea93e103357b5d2adb7ed59b8e,u%p=0;valid_x(x1);u>=p;t>=p +fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3113ad9,7eed6b70e7b0767c7d7feac04e57aa2a12fef5e0f48f878fcbb88b3b6b5e0783,u%p=0;valid_x(x3);u>=p;t>=p +ffffffffffffffffffffffffffffffffffffffffffffffffffffffff13cea4a70000000000000000000000000000000000000000000000000000000000000000,649984435b62b4a25d40c6133e8d9ab8c53d4b059ee8a154a3be0fcf4e892edb,t%p=0;valid_x(x1);u>=p +ffffffffffffffffffffffffffffffffffffffffffffffffffffffff13cea4a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,649984435b62b4a25d40c6133e8d9ab8c53d4b059ee8a154a3be0fcf4e892edb,t%p=0;valid_x(x1);u>=p;t>=p +ffffffffffffffffffffffffffffffffffffffffffffffffffffffff15028c590063f64d5a7f1c14915cd61eac886ab295bebd91992504cf77edb028bdd6267f,3fde5713f8282eead7d39d4201f44a7c85a5ac8a0681f35e54085c6b69543374,(u'^3+t'^2+7)%p=0;valid_x(x2);u>=p +ffffffffffffffffffffffffffffffffffffffffffffffffffffffff2715de860000000000000000000000000000000000000000000000000000000000000000,3524f77fa3a6eb4389c3cb5d27f1f91462086429cd6c0cb0df43ea8f1e7b3fb4,t%p=0;valid_x(x3);valid_x(x2);valid_x(x1);u>=p +ffffffffffffffffffffffffffffffffffffffffffffffffffffffff2715de86fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,3524f77fa3a6eb4389c3cb5d27f1f91462086429cd6c0cb0df43ea8f1e7b3fb4,t%p=0;valid_x(x3);valid_x(x2);valid_x(x1);u>=p;t>=p +ffffffffffffffffffffffffffffffffffffffffffffffffffffffff2c2c5709e7156c417717f2feab147141ec3da19fb759575cc6e37b2ea5ac9309f26f0f66,d2469ab3e04acbb21c65a1809f39caafe7a77c13d10f9dd38f391c01dc499c52,(u'^3-t'^2+7)%p=0;valid_x(x3);valid_x(x2);valid_x(x1);u>=p +ffffffffffffffffffffffffffffffffffffffffffffffffffffffff3a08cc1efffffffffffffffffffffffffffffffffffffffffffffffffffffffff760e9f0,38e2a5ce6a93e795e16d2c398bc99f0369202ce21e8f09d56777b40fc512bccc,valid_x(x3);u>=p;t>=p +ffffffffffffffffffffffffffffffffffffffffffffffffffffffff3e91257d932016cbf69c4471bd1f656c6a107f1973de4af7086db897277060e25677f19a,864b3dc902c376709c10a93ad4bbe29fce0012f3dc8672c6286bba28d7d6d6fc,valid_x(x3);u>=p +ffffffffffffffffffffffffffffffffffffffffffffffffffffffff795d6c1c322cadf599dbb86481522b3cc55f15a67932db2afa0111d9ed6981bcd124bf44,766dfe4a700d9bee288b903ad58870e3d4fe2f0ef780bcac5c823f320d9a9bef,(u'^3+t'^2+7)%p=0;valid_x(x1);u>=p +ffffffffffffffffffffffffffffffffffffffffffffffffffffffff8e426f0392389078c12b1a89e9542f0593bc96b6bfde8224f8654ef5d5cda935a3582194,faec7bc1987b63233fbc5f956edbf37d54404e7461c58ab8631bc68e451a0478,valid_x(x1);u>=p +ffffffffffffffffffffffffffffffffffffffffffffffffffffffff91192139ffffffffffffffffffffffffffffffffffffffffffffffffffffffff45f0f1eb,ec29a50bae138dbf7d8e24825006bb5fc1a2cc1243ba335bc6116fb9e498ec1f,valid_x(x2);u>=p;t>=p +ffffffffffffffffffffffffffffffffffffffffffffffffffffffff98eb9ab76e84499c483b3bf06214abfe065dddf43b8601de596d63b9e45a166a580541fe,1e0ff2dee9b09b136292a9e910f0d6ac3e552a644bba39e64e9dd3e3bbd3d4d4,(u'^3-t'^2+7)%p=0;valid_x(x3);u>=p +ffffffffffffffffffffffffffffffffffffffffffffffffffffffff9b77b7f2c74d99efceaa550f1ad1c0f43f46e7ff1ee3bd0162b7bf55f2965da9c3450646,8b7dd5c3edba9ee97b70eff438f22dca9849c8254a2f3345a0a572ffeaae0928,valid_x(x2);u>=p +ffffffffffffffffffffffffffffffffffffffffffffffffffffffff9b77b7f2ffffffffffffffffffffffffffffffffffffffffffffffffffffffff156ca896,0881950c8f51d6b9a6387465d5f12609ef1bb25412a08a74cb2dfb200c74bfbf,valid_x(x3);valid_x(x2);valid_x(x1);u>=p;t>=p +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffa2f5cd838816c16c4fe8a1661d606fdb13cf9af04b979a2e159a09409ebc8645d58fde02,2f083207b9fd9b550063c31cd62b8746bd543bdc5bbf10e3a35563e927f440c8,(u'^3+t'^2+7)%p=0;valid_x(x3);valid_x(x2);valid_x(x1);u>=p +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffb13f75c00000000000000000000000000000000000000000000000000000000000000000,4f51e0be078e0cddab2742156adba7e7a148e73157072fd618cd60942b146bd0,t%p=0;valid_x(x3);u>=p +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffb13f75c0fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,4f51e0be078e0cddab2742156adba7e7a148e73157072fd618cd60942b146bd0,t%p=0;valid_x(x3);u>=p;t>=p +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7bc1f8d0000000000000000000000000000000000000000000000000000000000000000,16c2ccb54352ff4bd794f6efd613c72197ab7082da5b563bdf9cb3edaafe74c2,t%p=0;valid_x(x2);u>=p +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7bc1f8dfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,16c2ccb54352ff4bd794f6efd613c72197ab7082da5b563bdf9cb3edaafe74c2,t%p=0;valid_x(x2);u>=p;t>=p +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffef64d162750546ce42b0431361e52d4f5242d8f24f33e6b1f99b591647cbc808f462af51,d41244d11ca4f65240687759f95ca9efbab767ededb38fd18c36e18cd3b6f6a9,(u'^3+t'^2+7)%p=0;valid_x(x3);u>=p +fffffffffffffffffffffffffffffffffffffffffffffffffffffffff0e5be52372dd6e894b2a326fc3605a6e8f3c69c710bf27d630dfe2004988b78eb6eab36,64bf84dd5e03670fdb24c0f5d3c2c365736f51db6c92d95010716ad2d36134c8,valid_x(x3);valid_x(x2);valid_x(x1);u>=p +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffefbb982fffffffffffffffffffffffffffffffffffffffffffffffffffffffff6d6db1f,1c92ccdfcf4ac550c28db57cff0c8515cb26936c786584a70114008d6c33a34b,valid_x(x1);u>=p;t>=p diff --git a/bip-0324/gen_test_vectors.py b/bip-0324/gen_test_vectors.py new file mode 100644 index 00000000..05b30a83 --- /dev/null +++ b/bip-0324/gen_test_vectors.py @@ -0,0 +1,418 @@ +"""Generate the BIP-0324 test vectors.""" + +import csv +import hashlib +import os +import sys +from reference import ( + FE, + GE, + MINUS_3_SQRT, + hkdf_sha256, + SECP256K1_G, + ellswift_decode, + ellswift_ecdh_xonly, + xswiftec_inv, + xswiftec, + v2_ecdh, + initialize_v2_transport, + v2_enc_packet +) + +FILENAME_PACKET_TEST = os.path.join(sys.path[0], 'packet_encoding_test_vectors.csv') +FILENAME_XSWIFTEC_INV_TEST = os.path.join(sys.path[0], 'xswiftec_inv_test_vectors.csv') +FILENAME_ELLSWIFT_DECODE_TEST = os.path.join(sys.path[0], 'ellswift_decode_test_vectors.csv') + +def xswiftec_flagged(u, t, simplified=False): + """A variant of xswiftec which also returns 'flags', describing conditions encountered.""" + flags = [] + if u == 0: + flags.append("u%p=0") + u = FE(1) + if t == 0: + flags.append("t%p=0") + t = FE(1) + if u**3 + t**2 + 7 == 0: + flags.append("(u'^3+t'^2+7)%p=0") + t = 2 * t + X = (u**3 + 7 - t**2) / (2 * t) + Y = (X + t) / (MINUS_3_SQRT * u) + if X == 0: + if not simplified: + flags.append("(u'^3-t'^2+7)%p=0") + x3 = u + 4 * Y**2 + if GE.is_valid_x(x3): + flags.append("valid_x(x3)") + x2 = (-X / Y - u) / 2 + if GE.is_valid_x(x2): + flags.append("valid_x(x2)") + x1 = (X / Y - u) / 2 + if GE.is_valid_x(x1): + flags.append("valid_x(x1)") + for x in (x3, x2, x1): + if GE.is_valid_x(x): + break + return x, flags + + +def ellswift_create_deterministic(seed, features): + """This is a variant of ellswift_create which doesn't use randomness. + + features is an integer selecting some properties of the result: + - (f & 3) == 0: only x1 is valid on decoding (see xswiftec{_flagged}) + - (f & 3) == 1: only x2 is valid on decoding + - (f & 3) == 2: only x3 is valid on decoding + - (f & 3) == 3: x1,x2,x3 are all valid on decoding + - (f & 4) == 4: u >= p + - (f & 8) == 8: u mod n == 0 + + Returns privkey, ellswift + """ + + cnt = 0 + while True: + sec = hkdf_sha256(32, seed, (cnt).to_bytes(4, 'little'), b"sec") + xval = (int.from_bytes(sec, 'big') * SECP256K1_G).x + cnt += 1 + if features & 8: + u = 0 + if features & 4: + u += FE.SIZE + else: + udat = hkdf_sha256(64, seed, (cnt).to_bytes(4, 'little'), b"u") + if features & 4: + u = FE.SIZE + 1 + int.from_bytes(udat, 'big') % (2**256 - FE.SIZE - 1) + else: + u = 1 + int.from_bytes(udat, 'big') % (FE.SIZE - 1) + case = hkdf_sha256(1, seed, (cnt).to_bytes(4, 'little'), b"case")[0] & 7 + coru = FE(u) + ((features & 8) == 8) + t = xswiftec_inv(xval, coru, case) + if t is None: + continue + assert xswiftec(FE(u), t) == xval + x2, flags = xswiftec_flagged(FE(u), t) + assert x2 == xval + have_x1 = "valid_x(x1)" in flags + have_x2 = "valid_x(x2)" in flags + have_x3 = "valid_x(x3)" in flags + if (features & 4) == 0 and not (have_x1 and not have_x2 and not have_x3): + continue + if (features & 4) == 1 and not (not have_x1 and have_x2 and not have_x3): + continue + if (features & 4) == 2 and not (not have_x1 and not have_x2 and have_x3): + continue + if (features & 4) == 3 and not (have_x1 and have_x2 and have_x3): + continue + return sec, u.to_bytes(32, 'big') + t.to_bytes() + +def ellswift_decode_flagged(ellswift, simplified=False): + """Decode a 64-byte ElligatorSwift encoded coordinate, returning byte array + flag string.""" + uv = int.from_bytes(ellswift[:32], 'big') + tv = int.from_bytes(ellswift[32:], 'big') + x, flags = xswiftec_flagged(FE(uv), FE(tv)) + if not simplified: + if uv >= FE.SIZE: + flags.append("u>=p") + if tv >= FE.SIZE: + flags.append("t>=p") + return int(x).to_bytes(32, 'big'), ";".join(flags) + +def random_fe_int(_, seed, i, p): + """Function to use in tuple_expand, generating a random integer in 0..p-1.""" + rng_out = hkdf_sha256(64, seed, i.to_bytes(4, 'little'), b"v%i_fe" % p) + return int.from_bytes(rng_out, 'big') % FE.SIZE + +def random_fe_int_high(_, seed, i, p): + """Function to use in tuple_expand, generating a random integer in p..2^256-1.""" + rng_out = hkdf_sha256(64, seed, i.to_bytes(4, 'little'), b"v%i_fe_high" % p) + return FE.SIZE + int.from_bytes(rng_out, 'big') % (2**256 - FE.SIZE) + +def fn_of(p_in, fn): + """Function to use in tuple_expand, to pick one variable in function of another.""" + def inner(vs, _seed, _i, p): + assert p != p_in + if isinstance(vs[p_in], int): + return fn(vs[p_in]) + return None + return inner + +def tuple_expand(out, tuplespec, prio, seed=None, cnt=1): + """Given a tuple specification, expand it cnt times, and add results to out. + + Expansion is defined recursively: + - If any of the spec elements is a list, each element of the list results + in an expansion (by replacing the list with its element). + - If any of the spec elements is a function, that function is invoked with + (spec, seed, expansion count, index in spec) as arguments. If the function + needs to wait for other indices to be expanded, it can return None. + + The output consists of (prio, expansion count, SHA256(result), result, seed) + tuples.""" + + def recurse(vs, seed, i, change_pos=None, change=None): + if change_pos is not None: + vs = list(vs) + vs[change_pos] = change + for p, v in enumerate(vs): + if v is None: + return + if isinstance(v, list): + for ve in v: + recurse(vs, seed, i, p, ve) + return + if callable(v): + res = v(vs, seed, i, p) + if res is not None: + recurse(vs, seed, i, p, res) + return + h = hashlib.sha256() + for v in vs: + h.update(int(v).to_bytes(32, 'big')) + out.append((prio, i, h.digest(), vs, seed)) + for i in range(cnt): + recurse(tuplespec, seed, i) + +def gen_ellswift_decode_cases(seed, simplified=False): + """Generate a set of interesting (ellswift, x, flags) ellswift decoding cases.""" + inputs = [] + + # Aggregate for use in tuple_expand, expanding to int in 0..p-1, and one in p..2^256-1. + RANDOM_VAL = [random_fe_int, random_fe_int_high] + # Aggregate for use in tuple_expand, expanding to integers which %p equal 0. + ZERO_VAL = [0, FE.SIZE] + # Helpers for constructing u and t values such that u^3+t^2+7=0 or u^3-t^2+7=0. + T_FOR_SUM_ZERO = fn_of(0, lambda u: (-FE(u)**3 - 7).sqrts()) + T_FOR_DIFF_ZERO = fn_of(0, lambda u: (FE(u)**3 + 7).sqrts()) + U_FOR_SUM_ZERO = fn_of(1, lambda t: (-FE(t)**2 - 7).cbrts()) + U_FOR_DIFF_ZERO = fn_of(1, lambda t: (FE(t)**2 - 7).cbrts()) + + tuple_expand(inputs, [RANDOM_VAL, RANDOM_VAL], 0, seed + b"random", 64) + tuple_expand(inputs, [RANDOM_VAL, T_FOR_SUM_ZERO], 1, seed + b"t=sqrt(-u^3-7)", 64) + tuple_expand(inputs, [U_FOR_SUM_ZERO, RANDOM_VAL], 1, seed + b"u=cbrt(-t^2-7)", 64) + tuple_expand(inputs, [RANDOM_VAL, T_FOR_DIFF_ZERO], 1, seed + b"t=sqrt(u^3+7)", 64) + tuple_expand(inputs, [U_FOR_DIFF_ZERO, RANDOM_VAL], 1, seed + b"u=cbrt(t^2-7)", 64) + tuple_expand(inputs, [ZERO_VAL, RANDOM_VAL], 2, seed + b"u=0", 64) + tuple_expand(inputs, [RANDOM_VAL, ZERO_VAL], 2, seed + b"t=0", 64) + tuple_expand(inputs, [ZERO_VAL, FE(8).sqrts()], 3, seed + b"u=0;t=sqrt(8)") + tuple_expand(inputs, [FE(-8).cbrts(), ZERO_VAL], 3, seed + b"t=0;u=cbrt(-8)") + tuple_expand(inputs, [FE(-6).cbrts(), ZERO_VAL], 3, seed + b"t=0;u=cbrt(-6)") + tuple_expand(inputs, [ZERO_VAL, ZERO_VAL], 3, seed + b"u=0;t=0") + # Unused. + tuple_expand(inputs, [ZERO_VAL, FE(-8).sqrts()], 4, seed + b"u=0;t=sqrt(-8)") + + seen = set() + cases = [] + for _prio, _cnt, _hash, vs, _seed in sorted(inputs): + inp = int(vs[0]).to_bytes(32, 'big') + int(vs[1]).to_bytes(32, 'big') + outp, flags = ellswift_decode_flagged(inp, simplified) + if flags not in seen: + cases.append((inp, outp, flags)) + seen.add(flags) + + return cases + +def gen_all_ellswift_decode_vectors(fil): + """Generate all xelligatorswift decoding test vectors.""" + + cases = gen_ellswift_decode_cases(b"") + writer = csv.DictWriter(fil, ["ellswift", "x", "comment"]) + writer.writeheader() + for val, x, flags in sorted(cases): + writer.writerow({"ellswift": val.hex(), "x": x.hex(), "comment": flags}) + +def xswiftec_inv_flagged(x, u, case): + """A variant of xswiftec_inv which also returns flags, describing conditions encountered.""" + + flags = [] + + if case & 2 == 0: + if GE.is_valid_x(-x - u): + flags.append("bad[valid_x(-x-u)]") + return None, flags + v = x if case & 1 == 0 else -x - u + if v == 0: + flags.append("info[v=0]") + s = -(u**3 + 7) / (u**2 + u*v + v**2) + assert s != 0 # would imply X=0 on curve + else: + s = x - u + if s == 0: + flags.append("bad[s=0]") + return None, flags + q = (-s * (4 * (u**3 + 7) + 3 * s * u**2)) + if q == 0: + flags.append("info[q=0]") + r = q.sqrt() + if r is None: + flags.append("bad[non_square(q)]") + return None, flags + if case & 1: + if r == 0: + flags.append("bad[r=0]") + return None, flags + r = -r + v = (-u + r / s) / 2 + if v == 0: + flags.append("info[v=0]") + w = s.sqrt() + assert w != 0 + if w is None: + flags.append("bad[non_square(s)]") + return None, flags + if case & 4: + w = -w + Y = w / 2 + assert Y != 0 + X = 2 * Y * (v + u / 2) + if X == 0: + flags.append("info[X=0]") + flags.append("ok") + return w * (u * (MINUS_3_SQRT - 1) / 2 - v), flags + +def xswiftec_inv_combo_flagged(x, u): + """Compute the aggregate results and flags from xswiftec_inv_flagged for case=0..7.""" + ts = [] + allflags = [] + for case in range(8): + t, flags = xswiftec_inv_flagged(x, u, case) + if t is not None: + assert x == xswiftec(u, t) + ts.append(t) + allflags.append(f"case{case}:{'&'.join(flags)}") + return ts, ";".join(allflags) + +def gen_all_xswiftec_inv_vectors(fil): + """Generate all xswiftec_inv test vectors.""" + + # Two constants used below. Compute them only once. + C1 = (FE(MINUS_3_SQRT) - 1) / 2 + C2 = (-FE(MINUS_3_SQRT) - 1) / 2 + # Helper functions that pick x and u with special properties. + TRIGGER_Q_ZERO = fn_of(1, lambda u: (FE(u)**3 + 28) / (FE(-3) * FE(u)**2)) + TRIGGER_DIVZERO_A = fn_of(1, lambda u: FE(u) * C1) + TRIGGER_DIVZERO_B = fn_of(1, lambda u: FE(u) * C2) + TRIGGER_V_ZERO = fn_of(1, lambda u: FE(-7) / FE(u)**2) + TRIGGER_X_ZERO = fn_of(0, lambda x: FE(-2) * FE(x)) + + inputs = [] + tuple_expand(inputs, [random_fe_int, random_fe_int], 0, b"uniform", 256) + tuple_expand(inputs, [TRIGGER_Q_ZERO, random_fe_int], 1, b"x=-(u^3+28)/(3*u^2)", 64) + tuple_expand(inputs, [TRIGGER_V_ZERO, random_fe_int], 1, b"x=-7/u^2", 512) + tuple_expand(inputs, [random_fe_int, fn_of(0, lambda x: x)], 2, b"u=x", 64) + tuple_expand(inputs, [random_fe_int, fn_of(0, lambda x: -FE(x))], 2, b"u=-x", 64) + # Unused. + tuple_expand(inputs, [TRIGGER_DIVZERO_A, random_fe_int], 3, b"x=u*(sqrt(-3)-1)/2", 64) + tuple_expand(inputs, [TRIGGER_DIVZERO_B, random_fe_int], 3, b"x=u*(-sqrt(-3)-1)/2", 64) + tuple_expand(inputs, [random_fe_int, TRIGGER_X_ZERO], 3, b"u=-2x", 64) + + seen = set() + cases = [] + for _prio, _cnt, _hash, vs, _seed in sorted(inputs): + x, u = FE(vs[0]), FE(vs[1]) + if u == 0: + continue + if not GE.is_valid_x(x): + continue + ts, flags = xswiftec_inv_combo_flagged(x, u) + if flags not in seen: + cases.append((int(u), int(x), ts, flags)) + seen.add(flags) + + writer = csv.DictWriter(fil, ["u", "x"] + [f"case{c}_t" for c in range(8)] + ["comment"]) + writer.writeheader() + for u, x, ts, flags in sorted(cases): + row = {"u": FE(u), "x": FE(x), "comment": flags} + for c in range(8): + if ts[c] is not None: + row[f"case{c}_t"] = FE(ts[c]) + writer.writerow(row) + +def gen_packet_encoding_vector(case): + """Given a dict case with specs, construct a packet_encoding test vector as a CSV line.""" + ikm = str(case).encode('utf-8') + in_initiating = case["init"] + in_ignore = int(case["ignore"]) + in_priv_ours, in_ellswift_ours = ellswift_create_deterministic(ikm, case["features"]) + mid_x_ours = (int.from_bytes(in_priv_ours, 'big') * SECP256K1_G).x.to_bytes() + assert mid_x_ours == ellswift_decode(in_ellswift_ours) + in_ellswift_theirs = case["theirs"] + in_contents = hkdf_sha256(case["contentlen"], ikm, b"contents", b"") + contents = in_contents * case["multiply"] + in_aad = hkdf_sha256(case["aadlen"], ikm, b"aad", b"") + mid_shared_secret = v2_ecdh(in_priv_ours, in_ellswift_theirs, in_ellswift_ours, in_initiating) + + peer = initialize_v2_transport(mid_shared_secret, in_initiating) + for _ in range(case["idx"]): + v2_enc_packet(peer, b"") + ciphertext = v2_enc_packet(peer, contents, in_aad, case["ignore"]) + long_msg = len(ciphertext) > 128 + + return { + "in_idx": case['idx'], + "in_priv_ours": in_priv_ours.hex(), + "in_ellswift_ours": in_ellswift_ours.hex(), + "in_ellswift_theirs": in_ellswift_theirs.hex(), + "in_initiating": int(in_initiating), + "in_contents": in_contents.hex(), + "in_multiply": case['multiply'], + "in_aad": in_aad.hex(), + "in_ignore": in_ignore, + "mid_x_ours": mid_x_ours.hex(), + "mid_x_theirs": ellswift_decode(in_ellswift_theirs).hex(), + "mid_x_shared": ellswift_ecdh_xonly(in_ellswift_theirs, in_priv_ours).hex(), + "mid_shared_secret": mid_shared_secret.hex(), + "mid_initiator_l": peer['initiator_L'].hex(), + "mid_initiator_p": peer['initiator_P'].hex(), + "mid_responder_l": peer['responder_L'].hex(), + "mid_responder_p": peer['responder_P'].hex(), + "mid_send_garbage_terminator": peer["send_garbage_terminator"].hex(), + "mid_recv_garbage_terminator": peer["recv_garbage_terminator"].hex(), + "out_session_id": peer["session_id"].hex(), + "out_ciphertext": "" if long_msg else ciphertext.hex(), + "out_ciphertext_endswith": ciphertext[-128:].hex() if long_msg else "" + } + +def gen_all_packet_encoding_vectors(fil): + """Return a list of CSV lines, one for each packet encoding vector.""" + + ellswift = gen_ellswift_decode_cases(b"simplified_", simplified=True) + ellswift.sort(key=lambda x: hashlib.sha256(b"simplified:" + x[0]).digest()) + + fields = [ + "in_idx", "in_priv_ours", "in_ellswift_ours", "in_ellswift_theirs", "in_initiating", + "in_contents", "in_multiply", "in_aad", "in_ignore", "mid_x_ours", "mid_x_theirs", + "mid_x_shared", "mid_shared_secret", "mid_initiator_l", "mid_initiator_p", + "mid_responder_l", "mid_responder_p", "mid_send_garbage_terminator", + "mid_recv_garbage_terminator", "out_session_id", "out_ciphertext", "out_ciphertext_endswith" + ] + + writer = csv.DictWriter(fil, fields) + writer.writeheader() + for case in [ + {"init": True, "contentlen": 1, "multiply": 1, "aadlen": 0, "ignore": False, "idx": 1, + "theirs": ellswift[0][0], "features": 0}, + {"init": False, "contentlen": 17, "multiply": 1, "aadlen": 0, "ignore": False, "idx": 999, + "theirs": ellswift[1][0], "features": 1}, + {"init": True, "contentlen": 63, "multiply": 1, "aadlen": 4095, "ignore": False, "idx": 0, + "theirs": ellswift[2][0], "features": 2}, + {"init": False, "contentlen": 128, "multiply": 1, "aadlen": 0, "ignore": True, "idx": 223, + "theirs": ellswift[3][0], "features": 3}, + {"init": True, "contentlen": 193, "multiply": 1, "aadlen": 0, "ignore": False, "idx": 448, + "theirs": ellswift[4][0], "features": 4}, + {"init": False, "contentlen": 41, "multiply": 97561, "aadlen": 0, "ignore": False, + "idx": 673, "theirs": ellswift[5][0], "features": 5}, + {"init": True, "contentlen": 241, "multiply": 69615, "aadlen": 0, "ignore": True, + "idx": 1024, "theirs": ellswift[6][0], "features": 6}, + ]: + writer.writerow(gen_packet_encoding_vector(case)) + +if __name__ == "__main__": + print(f"Generating {FILENAME_PACKET_TEST}...") + with open(FILENAME_PACKET_TEST, "w", encoding="utf-8") as fil_packet: + gen_all_packet_encoding_vectors(fil_packet) + print(f"Generating {FILENAME_XSWIFTEC_INV_TEST}...") + with open(FILENAME_XSWIFTEC_INV_TEST, "w", encoding="utf-8") as fil_xswiftec_inv: + gen_all_xswiftec_inv_vectors(fil_xswiftec_inv) + print(f"Generating {FILENAME_ELLSWIFT_DECODE_TEST}...") + with open(FILENAME_ELLSWIFT_DECODE_TEST, "w", encoding="utf-8") as fil_ellswift_decode: + gen_all_ellswift_decode_vectors(fil_ellswift_decode) diff --git a/bip-0324/packet_encoding_test_vectors.csv b/bip-0324/packet_encoding_test_vectors.csv index 519b77a9..4f70b92d 100644 --- a/bip-0324/packet_encoding_test_vectors.csv +++ b/bip-0324/packet_encoding_test_vectors.csv @@ -1,8 +1,8 @@ -in_idx,in_priv_ours,in_ellswift_ours,in_ellswift_theirs,in_initiating,in_content,in_multiply,in_aad,in_ignore,mid_x_ours,mid_x_shared,mid_shared_secret,mid_initiator_l,mid_initiator_p,mid_responder_l,mid_responder_p,mid_send_garbage_terminator,mid_recv_garbage_terminator,mid_session_id,out_ciphertext,out_ciphertext_endswith -1,e79f04ad4c684525ff3e3cf7c19f4cdb11193d387d7f3fe82a948a0c3165f5fe,4eff53ea1945e57b5d170565e760d771e5d496101c1005c302fd687af80d8b858c5a681c474fc4c0546ee464a1b95899ecb9d4f50c2c0854fe029fc054ae6777,824a1da0530fa695b95b375fee2a56ec96ce375ddd2fcf8367cc8ec1c0b751a3304bbef5dc96543acaac50c24be8cb39906ef8521727de0c6e96c2060a026bd7,1,1f,1,,0,b63341693587b4944b865485cffcc34707b3e0760a6ba8d1d402fc1c996c4e91,961fa6c9491bde887a6e67898c9335579d1931b435af16d969f3c9f12e58c698,05ac09c882b2cddd57fd4a1a14f93ae78d78a2ab2adb8cb6d67176cdb3101e15,c845747c92a2f0d83058ccceee69322cfd84cf3b086c30790f2a8789b303defc,3d0c55fa080de10236da830c7b092f63f6897c5243259adcedc4288a847ee9bc,f32949925481a88ad5246c408aeb8b9c0dc5dbc21fd6e5484331e1c0cd0ef6d6,5b94f3fce2b276e9d7ef5646657e61dbbc8ea27a9bf531ce23c553de1fdfd27d,38d7b65a6d58a08b2506e799caacd4f6,d6156094782d98fa27f37758dcd0677f,4418cf03c0d7cd5849022a7976014f234b4d4f5c18a6d3a6540c11e01a7180c1,d88ea86cdd6b61592e038741b474eeef0de0802412, -999,53a915e6d8f6c5bbb93e537081085e9e642dc525649acf05be74e3a825e20921,715d7b8357dd4d15a07b3f8c6764ebd9927140f75047fb50478c33ea8889d710511cdf39aa0fd6b1e94ea660d87f9d97bffc548646f9e2aaebc1468fe563fb97,cea7abc3112397b894bd3e89a615248af473f897bbdd30997c20e53e7ce499a295e55c4c5655cd3d21191e76c8af0aa1bbea10fd1b540a42d0fbed388dc59877,0,e4,1,,0,533bbaefc711f303be5e8271fdecfd31d03abd42128ff73479e2a427059e4a74,ed679745127d32714ff84bcce7651a14b9f275bf4b18bc8e880ff51cd7ac012b,08cbe6a86ff92a9757b2aeea92b8b2f4d78426786a2caec99c3cc73d7dda47ab,953a372f0d3933afef342fec533fa4fed644cc1cbc9252d31883e10fbca9b81a,e8c1759d865d59f37e5cf0404296fb0530f0fc601ad783853791e5459ca7f135,52925c95091e7dac8e580e9a0548e18915e01919a416c5aac22096b8bab17c31,ba778684047d705839590c466daef7c0ac53a3f8790b9744f154712e5644c647,0442c66254045c5018d828d42afaa69f,930c6d7cbf8c7c2006ed287c2ba6d941,9619dd4731d6160617da82da407c80732455a67b59ad7be07f86252aadb79440,0d5892533b0453c777d5ff3f217c94297e7927bb45, -0,dcf45714d450a32cdd05997b75acd34e8786969dfcff34295c287a13ba3225f0,feaa1ef8bb05296a8d5153855357868835fc5bc59d96dcbccb108e0e2dd671676c5683ad566ee4290ede5236ba6259ab47668e25853d64c0678641d27d5d0eed,c8067851ccc5df7331e9f137e66ebce1220306c4644266ff3e38436a1036ea36582dac8ce2c30de59a890f81611f866f659c1334bdd82cce1ea20ab0128b7f56,1,d3ed40688778a439928b38f1f67e04afb94843625ce1932590fe0606fcabffad25df7d2476214dc1d06aa368a4dc1e6d940a974307836e291c54f6948623ce,1,3c208efa88a545dbd29d71bbf41268fe3d123eac7e20e92be7f227b93166ee3cfd2f26639b27f20513ec5fe25c4e762096b3e49c29110a5b0d170eb9292e1ecdd3d3bab3660f9970811330d6a0f7112709666bb9d3443c9f35a84e7bde9bf2b5e29b903c64e86aca86bb5799fcf6a7416f48d98668749852c49e4cb2f3325c44d63995cea48af98bb42bcf4fbe03681bb552cf5d51b424bd53f1ffb6d6cb70af606debbe07124aeb14b28f2b26e63f3421c6ac512d5cdb7f97540f2ae64487d84dc57fb8c97f5785b60bcf12a22225988007c4dcc8895301a6e36c59f3a33ab5fa099945b1dd4d6e0a3822ffea087b01f9fb27b38c7711211c523b0e153422a5e06c8200ed45560ed4eb1317b7b730b5d31096bd3ac5cfc1e876a178851d297bec38b7a753013d7590533b5f6d654add3b38ca046d932d9739a5f0fbe6cda89cc97597a046d08b66fde7533fcb0528562960832f623dadbbe35d2d54ff15e1a9a260477169e2d8a95ec66e6ed8ccc2366f5ded2fddc2b351d6475c4b3831b6dd5c330917ea1b0b229ce719b5bd6d839861e5374f51a176b500e7546a2b4b1325be9615305d591605783279a73f28b9a281b17a45bb79029a30bf3c0b248945f4523979bbb57d69df4881d8030a4146080dbf13255b712919fb4fa016c66dd323d7a5c56b3ceeb57c4120fd554719a3851b9f3e810c4ad35b141ebff6b7c7038fdfa784681f20dcf99773721d4ec07e58a312d638fa7a90c1afbb282f30ea74d7002288441c93263eebb593f2b36cf795a8a0ee7cc119d28caa13332fdb6f2f5a6011e26e1f33941fa275d2d75af014c7739b9a999f018c7c96b854332225cb9104c016f94ff255191ab93df7c1ac29e566d672e8ccb1a4b1f53f201d583da6c410df98fc502f3b366d7624cd812cb9dedce92329b6e591366bb83c7b84c1fb30301f0199ee9ec727a33d3a9a0382af1fcf1f42e945a5acf1ea8db30a03856b8d4e0361c08028bcfc05c5204fd508e67b9924600526a1f297efd091e3a0bdc6de5a46229792069c08af9b7961ec890a3ecd6af6b08ae08f3892084a8224dd5ac50b99a7034ba48b6f599ab3e3f9fe30734229558694b8e3091161f8bd425cc2e63635c1ff82e6b6b6c89d3ea4a0dc4193b80e6014f87571bc8499107576a910f0aa50d471fb35fa07dfd0488a404ef9907ee767b1f3786415561c445e0487feed348da527bd5e8adad8de928159fb32cfac4430f51dcf710507baedf326597d8039cbecd07e9cde781d0e307d589d52b5ac262a69b366541efc78e9398dc69345f00004268c9c6ae079f09aa4e4afb830ae35715623451ee2520ef201cb5885030aec9a24d9968f8c6f25d234c69cb2f569ae2a91029b6401110fed798b2289de1e7e8621dcbe466a2fef44f09176e252dae6707ecc35695a156f6b97ab4cbaabd325fd8eb941248490824f2520f49aae6eaab95a1cf92c061c57c90910873f1866b8763b78f90b2b737020527ad16c33938ab438f0eb7845a034aec20253e71ab717c4243dbcc084f4b09a87ceeb8ef124755ac8802a66b02cf9396a700a661e16b418cf838ea1262935bb7bdeec1648f0cacb1c3236ec1856d93f172c5a9a938246606db326336425c27e2adfa428f347b9f7aa4cf1648dd4e7e66f5e8a858d499c95dc2092a0a9c8b80a05c93bd359ba5b1acc714be5a1c26be118abe8abafce45d0b9bb668a8204fccf166bb85ce17f70330c0c8ca8dc7394beee22a52624558b69b81e223c779447ee09d822dc44226432a0c86f9b25e7d85a98e187d879a4ddeeb2463d12bce3eb137e227d41e2a3df55dad1219cf18129dd23ad50930286b732037cbe7d474d06ae9f7d93f7285d2db44a8e72011460e3d34381bfe6d3221f49915312d00a5a2c7ef98ab829c995081abf9dc5551f472f34f9b39607b8e55bdb047e68582d27d2582afb9a07549c7a134a34f7d0e5a1a046cb2c3959feb98fe7a698291a2defa5fbb32473b9c9ed02d02284559325e12cda1bf0bc24ea9849fc4c87eaed71f2537242b8635fdc969872b1017d218943647fca47f9430af3b78ae2c39882118612c288d474b0298de3c3ec1efd23c65554f6d6813b4ccbf604250d295f795b679fe0ecceef5b4739eb6b418376d2fd47f95685a259cd2e2e7c00c18e93329a80a467a2a0aeb22e361732c943beb0c3dc7ca57699b690d26e6314d0b28082f8f26894c249d1587b8895dd1c5e8863bbe31cdebbce06b1d6aa17f64a125f4cbad47f2e02761259973ea164b22a580d74413d900023f03d2b2587bca60501f639050262b61a67bec83246d6ec7726f5a8a62d99c582a159db6a539ae88732f8d101d324550c9fa2dd9ba60c449d329cba823ec4b3d80d613d8df5256b3ce6ffec42ab23e5e27c001aec29d080fb519a6cce5b974f65375870c22577a2f6e76760870de047ff65dca90bfd6a303b4605b6f90631d57a8f66969ec7b96749c1f1a4ee70e8915396e114100fde651700416239d82ce7e32be5f20e5886510a37b3cd3eefe78c40ccb0425f03bcaab1aa9eeffa2f4681b7938a813f49f10a34c280de09345ffe00af340a451ebdae630ddade51a6510fdda44c3fcacfaf3f2f748d8bfe48ff71871eaec9b1790df27d4ae913d1ad16b0b33a04266540a49cb5acb8d2e16df7a35933bba7c5af1ac1f522419e76c58cddee219cf5d3229d5a27af4411ae890b4329ae5699820766b0d73841baddc11c24802684c7f7c738bf4843595dd508850b69dbd38671a385ea39188a04e7b489daabf860f4747a7172cb72a578b9903ce161e7b8a7f31ea7e8630f97571bae93f51186b94cf3c697839a6dcb2f53de9893e53a7bc2f94c9a33b2dd112cdae81f60019b7c20aa7c4921f72492329afb8841a4c80a42d1a12fcc09cf7094cadf6a48ffd6f200bd64ec17906cf4ceda6f10313bdc9ce23eba2d8d8bdba728fac90321b4893a0a61e443f611ce16486aa477ed59ccd27b58bf01a5d6d396ffa249c0728eaf87f1c41dd56c7cbc1491b369aec1a576e286fceb37746891aa37da87fa77db443bbc2df63c589f928532c6e8f4e6e3186441dca4c7118d1bfdd0a1e24087837d7dabff8d64f7df1d4cd1628eb312d6c631f092ac0172642de2f2c3c1a71573de41d2165b8f3164db59070593d8c16dd6c161303a6523a33da328fd88929e434da4d102c0ef7303eafeb6a0c3583f74bc277ae1fac36efe68b98af3340a766b0ddb61e268f3dfdc6d858a90aab491e00e010ef195bbe12435a6f225395a1c3d0ba05a094cb2c6610353fb5bdda198c6c6a1564ddf21ab51b2e13e735969d109acf4ff05ac0071d018e98b42b18692ff2dff6bb1fdc6aa630d2a978b652fa6944fbf693a251db943e10cdabfd7e26276ef16844024432286400d36d15c6acf43cb6247c6316b0729ad2a465ed215a97c937e9f9a405ae9f520d91a198cd18077e7d6134f268741b73c6ad16d1e2164102e8bd276237599ff5811837faa0b1b4ac6b61092c14da64ee83904d62c57d336b80f4476f5a9fa6f0ce6ba6b34454906bf5af60b5e42170bc358bb90927c20f753b8d69d80e5aff23daee4727bdf16e01cb549783816b9a066ade19c26bf2e30aad9f3faf35c5e17c81062f53fdb9f57c2608f6c638f0f2e8c071581107bba371eb0cfec67bd823c3d892d21d14e65b366343839bddb2b7da0d7af7e7b3b927c4d71441ecbf14f5276d0da11cf33723a10fbf1e5a97b1f95072de9872932d7bfc200080043cec2dbf2f0840be85933d8f0cb6862316d1dc18292b90b8a583df01464fe1d6a4720ea361ff2280398df7008023f364989692c86bd2ceb94e466febe2fae6bb2635c1cfcdde45c4d3b6ff55106a432a64e6827f52cc0a25f4581a058c962e99ee9b3ca2c10e32eb93729690b3066d9b9343be9863faaa0599b99daddb2e53704002cc3dc09d5df8f3bb783b37e3ef85f01b272d84073576352428720ee08b9bc527020413346c0bb7f7040fd9b149197bdc4988eebc4e012e73d75bc4f4d2d2a3cc30dae96836c590e5a5976f0392102e1c592da1fd373be252fc3b0d34a6dec22725dd697ac55d4d50e07ccba88f70a44b75abb5dfececacb1b5483760e5d40b84255455fdb93faecbc24ae3b30d73311d6afd5fbad5b6e61e64530e7645cc43adc3053a40d6fc0e63d6c664df163f197d2d3efbb5a5abcf4a7ad69d07bb866876fc22d9c7a2fe529260153283cdbe1fc5768d625ad09a9bdb60442a744d22a827264d2722baae4549097f745bcf0de7f21c80951cc5ed8a48e4099663dbf6ac1f995e7ab3d6c8fe2c3e817e13b26741f2a7f33bad56bb002ebf0c69282dc763e1e9cb44bc0a0814170116d2dc16fdf1e08158fc571c5a5285242ab6376f9c213953e3a0a315eff722831cc27e426b0fe299fb325933ba9631be00ffd51f7bb3d13d962f5b6a0a01ad0c60455993699b9ec594cb749c82c870dc443c7466db518e4a5ebc7c530539ae48bea63d77c0e175d673edff001511bbdbddf30f2c231cba0565f043aa52c584fc05069728a95e040893d4e9c6c8d3b7bae906be38b9e1480f8e4665f157f4b347be01d1c355c94576b32a335864f6cd341857a696fe5f8af10a4ae87246cd119d33205bb969009655e36db394f750382fd58648aee80179bb3f1df7f291b4de27edf056c3968aca546e0e81d131b3fb52ae8b3fdedf297289420c4aa1aa74560aae150f623e120b08592c0304dbf7ff2e432e737227ac1e7e9e65485b30d058b30f81807a18fba48822baaf2e7ec6c88a604495a000e62340273db9079c2a7123a8a197b63d9616e7e9ebef572ba3efec09fb038a57adceffeffcbbb6c4e0e9bd73c1d1e82d26d39bda2420a45fb2463218b2988b5fbd316b3f4e81e7b0728d700637333fe5448b395e384169c4bfc57ff6062c962ac4f13e66cd5b6166a7a049b6459291d2d2a632d749060d8070b9e2db3159db8047e8024bc12c92562c7b4ee953b426c551d1b9064551833c758683693a57e724bed8a5c05e55d8e5f27d22b4adc246116747f23cc8ec35b51148168cd27dfb8a466cb8f4b1980209735694e411184875da7254a2ff850a73377c1f9f0b87f808b76a9f0882231fcc4446ed01c9876edf92fc505040583d9ccd0fcaf3935f215dd45022a9464835a505022cdd54de634fe207b5e202f7bcad463e00312952d3d4b421cc77be0cac587426f0239b8d3d94c0c24141b10cc8ab33aa655388fa5a323016e7a1fe2470d4dad92cb19acf779ce1c48f64bf588c99a766010252b983edcd4725ff16c15683bd7be62323c39405358358fcff306182a3a147c025dc91801258f4d71e1888b0a19f4e52a8c5885cc99474e94448c0081d1ce2a7110e1a6c7e23184cd76a44c806b0f1ea3de321cfdf8569dd35221e9ab1ccbcf6dee81cc611d0731226228d71e98abf05a0db761bf6807d4c0400a39835f33b144f85ddcdee7945e63027d3cd25d44b7e9fdda2548284497af9b57dc72b3e026d49e86950b434fc9a9713f5be858500b54614d5d4c79186289d744fa0df3d655f9fb79ec60f49dc72eea538515b53f74c578f9d4ffc9e79063e19f3a38d9dde42f7c3910ac5ca16f60afefcccd4a49b1d93032b7e82189caebd2993991f7f4f1610d3b7cd4b8dbf895523b28d0a3e672232412145649b1a28570d9c114a851f10e6761f52ade202167d94fb69135f3d2932849a85267b998a23f6c6229697e0098097cb6a6dc4f84cff4c7da997d5631b694,0,d23f19ad26c7ab984c682c5b1475f5a7df0452a05dd29a164974d2027a6e488a,6f8420ebf786ee13dcf65855b11428db43061cb599e6fe9ed79e59d9f273dc44,33a1ef8464def100bf031dae882ae4b7837b481f6adbc63c44baa8ef61c102eb,8edefd89c5ddb77db6333981740b83fd4d9c246639e0dcfd1ea2a26d4a26f508,6b0aae6192e7a92c7a3b5de8ab1349d26637b17e500da162cf25df13c85d35df,87a8da66207605720c64d5267b5ce90becb01248a60808a921c5eff59013d59f,e9292dc44be3294db04cf689a3141bbe0d0dbaca2dd8d8fb606a45b8d7d8e089,57b8c1343aea943714ff848970b2a89b,ce7988ab7ce5c93b89328ff815ae803b,a306c86bea9e6befa7f72bd5cde761d5f4f86cfbc8749c1e844100c540a6cefc,a844ddeb465f302c4b408a6ab972d4ff4035109bc25fb8fe88d4892bccefc238cf93e5a61459f9c0cb4877cb03967d592c5d8dc6d4c4b5f489005a645b2f7a2ee368749f0b172b9ddbcaa76f20485a272b2347, -255,f360308206fb441c7713b1a24ba4096a2fa0d5401411198b7008f82ae85257fe,3d4aa6a71a8a734e014b4d9e6b62989f022c1e2238ae476b11465cb86fe8cf7243db6f9472e188590705cf9b35121e06e57cb106a6043ab147b7c73871da8222,f0ebc3c872339622fbf2ab1b8aa2117c4d752b64eac9203962c03a47497a89c0dd298348e5bb63d3433c48ed6da6bc2e2dbe78f7b91247dd29f1f51a858d464a,0,b766200432932708a77dd15337bf70ca58397907a1cd31ecd1b43fab27fa0de0e85d5c4dea99543e79a75b149d325044934efccf679a764f683b2fbcb7c6dc4baea9797554779aff937f5195c172892c38dc423bc809885e5a19d07733e7c5f5806ffb865074cf2c46fbb5be4f9b5833c6829b8217ccb301dad7cb49e4fbcad1,1,,1,29bf8fe2cbd12835d3e6652840b8b1a2acf99d7b44610e738aafd7fd207c9b25,8f088a98122e3ffae5fee255fbfdf653a1f830f85dfe595707a1f4a7f74dab76,07eff272cd331aeb14db342b82f6df98303b01471f4dbe71028b127577216d9b,74ee2a5f9221cb386e587ac9a0c724bc6f65ea04cf3b0686c10b3e39e66bca02,35d93b912c54f3d28f834ee96a787f76f84b543fc231a45242fa4953b89cfc9f,70c4abb811bb6c209c6f108be383f67c8a14cd2dd25815f6156e6c3edd7d1e3d,3a16c6f3ff6f2b735b89d2af77c94b9dd328f5330e1283627835d32c5c856446,e09f7df050a3f76bc8d5a21697fefb9b,b8951fd528743d2e8625de363d9ae0e7,b1d1e3ae7d9337f31a410986fe6e377726eb0f2334f24d66b38ec4d29529d1a5,98178b6e498f808c90b098c8ceb09c5630d50925323d051d379adb85be9e3b7d3e5a4c0eafa3fb69abcf60e631262278395c4ec017b1a2c8b4aa6a25e65afa99ea3e73038d152b392b2f754a655f3fd034fd633e7889072c3e1351bff97494a6737785a00016c294e0f62219eb6becfcd14e5f81841118275a460780178b050615ab02e56efc0c777844620ba3de42bb54674842, -512,073f53403256e2b699e32cd4cebadfab63d9076a7cd541d9656e27b1baff66bb,5c9c90c4e216f8a4c2642535d1f699364f9054ca01bca0239fa65bcaf0ec0278e5a36bc85eae96850feff889b66c9106c170fe655f973f533ee3557bd3cb6e64,e9221c9698cb69b63c5a4e224132389d346fd2f0f3262218301be51687b8de846c652ea8aa7425a5c08a7bd52078168d28ecf42196e33e155e215553ce6aee83,1,196391ebfe9af3a9186d82692a00e485d6653c8408050e2efbfd864642860022af68053ee4230f69ea565fee775778bc231307b5b534545878f864364bb2690acc9e8991fed5a777eb2850c3c54cca94a5bc2cb92729bcd4e27c57ebddbdf14974030115bc5a8a600b207a4884f568d6a9d3d9367977410577674601d1cc5f1f672f4e54d4944e1f7807416159eb5ece5b805c8a23e6735351a8cf8de654d87228ba21baf815190ef026b29612498121fe85480d9e0050d4781fa9f0e60cab5195,1,,0,0ce4860107efd6deb9e8fc71cfc2522b4a5a0dc171746fb995ce485570449ab5,d87966cc3cbdcc14ce06a1ef6c28125cec3136647a499d4b4465c925a5f85efd,88cf0eb49ee4b7b46cd39fb3f4126b285750d897fdcba72b08d9da351167e227,df6857d7fbb822069d3f284973cdc6677a539d81844247ca2ac174b818623550,4f1c0b4b834681298ef127067369747074e571c68fc69966f3ad20673f786b82,5b9aa4239daed582b04d316f0550fdf1efc5309544349b01d62ea4f045e3425f,0c1d30189bf24a5032a93add5335fcbf351b2071870fd1626b66e23be99e1df5,ece7e13c65def883efc1f0dac6d55974,efca4cefee7a95602ebab090875e4212,afde967955012c17bfa7bea218bff3aa1aef10a49139c66080e9e7c567706409,5400e5add3682ea8c5587dbaec8867c54b7dc97e07533db32b5f68ff9eeccf68db30c5013b276374e1b60dd417df516e375a995b45811817b326c6bfbaaec589d82415bc48bc8f1eee5b9ff430b486cc951752e943dccef384a66bc6d906c7f0e839772ffce47928bb73a8f828ac82dca6f8f4f8340f11f4ef849575fd16ed32e5d450249b89004164918fe739a83f2b5dae04f7a8ae32682779f65edaef9c80090f581c7d9000cef48bed0089b5eb8767cc963ee2bda2d00716a463ee5413036bd2a055c0f5e3f72791e0526ac5e35bd269b71117, -769,810159fc6708a85aa9d6c8e7c4f8c587056594910174c0309b2c323055efaa6a,2a875927b98fd441d8c469f6902c167eb440e52d3331a4b7406f21cad216e80917401ddb8795a9706fe42a7ddfca70fee7240b1e918b9f32414b8b26e611d571,758bcf0381c0a1c35a174503a43b08f79eb407aa61eb9e6579a62400d4a13f8d3cef884728c34cb79c7f088b4d4e735ce796361e71bd8b66d637f56326b49af3,0,0f492bc40bc635ca20a46c6fd97b7de536f8326e677e0dcf9ed91e4c7873ec903198010f7cf0ff1595,97561,,0,27cdbeb6c8ef5f97982a7af00018a16ba243176fe6bb5d5c30c8a466a0193a47,85bbb016a00056ffd3878b2a5cfac4352d581bfec5a0407f0733585513d4b463,17624a0ed455d13430bf27337e27a1d55402cb1c7ebb32a4a66b3ef2f9947f1d,aeb3befd6841f8ec867e154424b5c6d49ad3fc1f789e7d3048750302c5d89c09,80033fb9014f1a407e46d35cf36a7c4245e55e6a810abaaad88ec5d25f89443f,65b0f93ac2a34327a4aacf14c2c54d92210294b425c3b6bb4ced8c44dad80457,4893a9b0ec706fcc7a05c1fed011ced86afd7324c63537e49ec2974df481a83c,330b7aae0e4d13f5806bc1f696042155,d514d7861e65ced36c7ea6ad2a8f6938,2ad4f52fd2a9cd078c0664dc4ba33904095d4affb5227fbc779662ca6140376c,,c05bf91046901716cab14414ee73dbd7f17a6e4cf0bc3ba22e22d40198fbd1527726a99c006378ba3079e9da8dcc68ecd8a0a23a692eb775cf11d97e16847b3398e11ee1ddab092d6a994a7246802bb3140ecbd4d8495f36abb5aa3e0aa325f02b5be69e4ad546356c453003949ea9d2adfb169004fe37da65b247ffbf021e9109d6d090b224bd4f51b803955be2fd7fdd0e198f89d903deba9be904cd6d769bbd4f1b5b609be95a9b6db2c2a6fcff029899cea13196330a433277c96a069fa8b6590d921da84172adf1b23fb76eb6cec95f8bcd6b497777eb2b44dd15440f430d3f9ea8729ea5598df46b299cff81729d3fca241872e61b84945c70189189f5 -1024,f7c11ca3b55137fa6124c49b25dea0287815b887760e1b986497e976444eb5bb,5a2a0ce42feb17e70a96db1e55bc380e26a40a589dfc453d6c2e82bbc4c2b161716a05df9039ccffb6df45183480119deb69299e44d48e38de746ad084156edd,2c402d53332d914e4f5501c8db682bec6738ed53c7410655d9346db99bae37642beb6a1d48394502941965ea7140e1172ce591923235640f964c0d597caae304,1,bc5fdee9e87d05b9723871a35823f643cc53c400851686d69d87433028c19cc295ceea1513d8056021800c86d886f27f6018e4053121df4db5216bff17160f7b1bc4d3be67354f0551f55a353de5f15add353605e2a0761bb0c1b3bb8a37787e797e619ebf902a8ed4fcf1c741c897c8469cf54565b21c85e2b4fac5ef6e948f2c36df269f7189d1f293d2a95a08a1b9d00528521ff407ff992726712c8c9d6273a878d1700912ff2ef5b1b6899e9b88f6999bc628c8a390b37d9be99bd742e16a1eb83b0d1b8bfb6858e3fddb33ce2d9ef40afefd29833fd59dbc0aef25c35771b17d62595b5de9dc7426f1d976deea78,69615,,1,6d89eb9e87319fd088ee1e77fa5b36d2d2d9e66fae8d11ab7bd70407d4aaba29,0b6c87c76983e5c84428bd646d24c133d9321af5f7cb4624ca1524bcab828ffd,2a4d8d7ab2d2e92c429c8c4cb0d78f0f170a244410d588b92c7fd8baf7c27e59,001f5a1254d1734551f7895221f370aa5eb2ea382dfefd586941becac178d8a2,acf0ffecff0851e065af2a149f1c0a0db21c8a885613ebd1c445957b56c3d705,42b3699eec52f5f4648bdb40c25c956944f0a3e9986552917a3498078a8de907,a721aa5cf8d2b4ce9f46f20693438d94f75130585b6ab137894b273a436c90d4,a5e8c7130b7b8cb34e303bc3da911230,0169b4c927c5a809bd3b0981887e7e0b,462c77437e78cb8620e0572e5a401d151e6ae45aed57d7638d924f569eccd73e,,d3a1508bf5455510d63279ef808f8eb5979b2793e481990f489046adbf9eb4247384912949eb8a9800fee8a3b25c7f3771a426b60b5ce40a60df4e7399ca9069f5833addad5f62dfff9171a964211489855b5aaef2f227b63d75e3d07b4ea9fc068b35497f70a7b708f1af662f435282ededcc0e20e38003733cf52703343f40f390b2cc38d10a8ea82434f2126ec841f67f54660f6ece22ae874a3a2c880e78dc87fbfbbcafa4bff042e78668855584386a11349a64e2018e8ca3857439345d1368998e3bc4995ece49634a75786c6ec06914181d35153fa29af610dc8db16531e601d8bba061accbf2c49ac567769d5c1b0607d71ecb34642d75b51bdb6776 +in_idx,in_priv_ours,in_ellswift_ours,in_ellswift_theirs,in_initiating,in_contents,in_multiply,in_aad,in_ignore,mid_x_ours,mid_x_theirs,mid_x_shared,mid_shared_secret,mid_initiator_l,mid_initiator_p,mid_responder_l,mid_responder_p,mid_send_garbage_terminator,mid_recv_garbage_terminator,out_session_id,out_ciphertext,out_ciphertext_endswith +1,61062ea5071d800bbfd59e2e8b53d47d194b095ae5a4df04936b49772ef0d4d7,ec0adff257bbfe500c188c80b4fdd640f6b45a482bbc15fc7cef5931deff0aa186f6eb9bba7b85dc4dcc28b28722de1e3d9108b985e2967045668f66098e475b,a4a94dfce69b4a2a0a099313d10f9f7e7d649d60501c9e1d274c300e0d89aafaffffffffffffffffffffffffffffffffffffffffffffffffffffffff8faf88d5,1,8e,1,,0,19e965bc20fc40614e33f2f82d4eeff81b5e7516b12a5c6c0d6053527eba0923,0c71defa3fafd74cb835102acd81490963f6b72d889495e06561375bd65f6ffc,4eb2bf85bd00939468ea2abb25b63bc642e3d1eb8b967fb90caa2d89e716050e,c6992a117f5edbea70c3f511d32d26b9798be4b81a62eaee1a5acaa8459a3592,9a6478b5fbab1f4dd2f78994b774c03211c78312786e602da75a0d1767fb55cf,7d0c7820ba6a4d29ce40baf2caa6035e04f1e1cefd59f3e7e59e9e5af84f1f51,17bc726421e4054ac6a1d54915085aaa766f4d3cf67bbd168e6080eac289d15e,9f0fc1c0e85fd9a8eee07e6fc41dba2ff54c7729068a239ac97c37c524cca1c0,faef555dfcdb936425d84aba524758f3,02cb8ff24307a6e27de3b4e7ea3fa65b,ce72dffb015da62b0d0f5474cab8bc72605225b0cee3f62312ec680ec5f41ba5,7530d2a18720162ac09c25329a60d75adf36eda3c3, +999,1f9c581b35231838f0f17cf0c979835baccb7f3abbbb96ffcc318ab71e6e126f,a1855e10e94e00baa23041d916e259f7044e491da6171269694763f018c7e63693d29575dcb464ac816baa1be353ba12e3876cba7628bd0bd8e755e721eb0140,fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f0000000000000000000000000000000000000000000000000000000000000000,0,3eb1d4e98035cfd8eeb29bac969ed3824a,1,,0,45b6f1f684fd9f2b16e2651ddc47156c0695c8c5cd2c0c9df6d79a1056c61120,edd1fd3e327ce90cc7a3542614289aee9682003e9cf7dcc9cf2ca9743be5aa0c,c40eb6190caf399c9007254ad5e5fa20d64af2b41696599c59b2191d16992955,a0138f564f74d0ad70bc337dacc9d0bf1d2349364caf1188a1e6e8ddb3b7b184,b82a0a7ce7219777f914d2ab873c5c487c56bd7b68622594d67fe029a8fa7def,d760ba8f62dd3d29d7d5584e310caf2540285edc6b51c640f9497e99c3536fd2,9db0c6f9a903cbab5d7b3c58273a3421eec0001814ec53236bd405131a0d8e90,23d2b5e653e6a3a8db160a2ca03d11cb5a79983babba861fcb57c38413323c0c,efb64fd80acd3825ac9bc2a67216535a,b3cb553453bceb002897e751ff7588bf,9267c54560607de73f18c563b76a2442718879c52dd39852885d4a3c9912c9ea,1da1bcf589f9b61872f45b7fa5371dd3f8bdf5d515b0c5f9fe9f0044afb8dc0aa1cd39a8c4, +0,0286c41cd30913db0fdff7a64ebda5c8e3e7cef10f2aebc00a7650443cf4c60d,d1ee8a93a01130cbf299249a258f94feb5f469e7d0f2f28f69ee5e9aa8f9b54a60f2c3ff2d023634ec7f4127a96cc11662e402894cf1f694fb9a7eaa5f1d9244,ffffffffffffffffffffffffffffffffffffffffffffffffffffffff22d5e441524d571a52b3def126189d3f416890a99d4da6ede2b0cde1760ce2c3f98457ae,1,054290a6c6ba8d80478172e89d32bf690913ae9835de6dcf206ff1f4d652286fe0ddf74deba41d55de3edc77c42a32af79bbea2c00bae7492264c60866ae5a,1,84932a55aac22b51e7b128d31d9f0550da28e6a3f394224707d878603386b2f9d0c6bcd8046679bfed7b68c517e7431e75d9dd34605727d2ef1c2babbf680ecc8d68d2c4886e9953a4034abde6da4189cd47c6bb3192242cf714d502ca6103ee84e08bc2ca4fd370d5ad4e7d06c7fbf496c6c7cc7eb19c40c61fb33df2a9ba48497a96c98d7b10c1f91098a6b7b16b4bab9687f27585ade1491ae0dba6a79e1e2d85dd9d9d45c5135ca5fca3f0f99a60ea39edbc9efc7923111c937913f225d67788d5f7e8852b697e26b92ec7bfcaa334a1665511c2b4c0a42d06f7ab98a9719516c8fd17f73804555ee84ab3b7d1762f6096b778d3cb9c799cbd49a9e4a325197b4e6cc4a5c4651f8b41ff88a92ec428354531f970263b467c77ed11312e2617d0d53fe9a8707f51f9f57a77bfb49afe3d89d85ec05ee17b9186f360c94ab8bb2926b65ca99dae1d6ee1af96cad09de70b6767e949023e4b380e66669914a741ed0fa420a48dbc7bfae5ef2019af36d1022283dd90655f25eec7151d471265d22a6d3f91dc700ba749bb67c0fe4bc0888593fbaf59d3c6fff1bf756a125910a63b9682b597c20f560ecb99c11a92c8c8c3f7fbfaa103146083a0ccaecf7a5f5e735a784a8820155914a289d57d8141870ffcaf588882332e0bcd8779efa931aa108dab6c3cce76691e345df4a91a03b71074d66333fd3591bff071ea099360f787bbe43b7b3dff2a59c41c7642eb79870222ad1c6f2e5a191ed5acea51134679587c9cf71c7d8ee290be6bf465c4ee47897a125708704ad610d8d00252d01959209d7cd04d5ecbbb1419a7e84037a55fefa13dee464b48a35c96bcb9a53e7ed461c3a1607ee00c3c302fd47cd73fda7493e947c9834a92d63dcfbd65aa7c38c3e3a2748bb5d9a58e7495d243d6b741078c8f7ee9c8813e473a323375702702b0afae1550c8341eedf5247627343a95240cb02e3e17d5dca16f8d8d3b2228e19c06399f8ec5c5e9dbe4caef6a0ea3ffb1d3c7eac03ae030e791fa12e537c80d56b55b764cadf27a8701052df1282ba8b5e3eb62b5dc7973ac40160e00722fa958d95102fc25c549d8c0e84bed95b7acb61ba65700c4de4feebf78d13b9682c52e937d23026fb4c6193e6644e2d3c99f91f4f39a8b9fc6d013f89c3793ef703987954dc0412b550652c01d922f525704d32d70d6d4079bc3551b563fb29577b3aecdc9505011701dddfd94830431e7a4918927ee44fb3831ce8c4513839e2deea1287f3fa1ab9b61a256c09637dbc7b4f0f8fbb783840f9c24526da883b0df0c473cf231656bd7bc1aaba7f321fec0971c8c2c3444bff2f55e1df7fea66ec3e440a612db9aa87bb505163a59e06b96d46f50d8120b92814ac5ab146bc78dbbf91065af26107815678ce6e33812e6bf3285d4ef3b7b04b076f21e7820dcbfdb4ad5218cf4ff6a65812d8fcb98ecc1e95e2fa58e3efe4ce26cd0bd400d6036ab2ad4f6c713082b5e3f1e04eb9e3b6c8f63f57953894b9e220e0130308e1fd91f72d398c1e7962ca2c31be83f31d6157633581a0a6910496de8d55d3d07090b6aa087159e388b7e7dec60f5d8a60d93ca2ae91296bd484d916bfaaa17c8f45ea4b1a91b37c82821199a2b7596672c37156d8701e7352aa48671d3b1bbbd2bd5f0a2268894a25b0cb2514af39c8743f8cce8ab4b523053739fd8a522222a09acf51ac704489cf17e4b7125455cb8f125b4d31af1eba1f8cf7f81a5a100a141a7ee72e8083e065616649c241f233645c5fc865d17f0285f5c52d9f45312c979bfb3ce5f2a1b951deddf280ffb3f370410cffd1583bfa90077835aa201a0712d1dcd1293ee177738b14e6b5e2a496d05220c3253bb6578d6aff774be91946a614dd7e879fb3dcf7451e0b9adb6a8c44f53c2c464bcc0019e9fad89cac7791a0a3f2974f759a9856351d4d2d7c5612c17cfc50f8479945df57716767b120a590f4bf656f4645029a525694d8a238446c5f5c2c1c995c09c1405b8b1eb9e0352ffdf766cc964f8dcf9f8f043dfab6d102cf4b298021abd78f1d9025fa1f8e1d710b38d9d1652f2d88d1305874ec41609b6617b65c5adb19b6295dc5c5da5fdf69f28144ea12f17c3c6fcce6b9b5157b3dfc969d6725fa5b098a4d9b1d31547ed4c9187452d281d0a5d456008caf1aa251fac8f950ca561982dc2dc908d3691ee3b6ad3ae3d22d002577264ca8e49c523bd51c4846be0d198ad9407bf6f7b82c79893eb2c05fe9981f687a97a4f01fe45ff8c8b7ecc551135cd960a0d6001ad35020be07ffb53cb9e731522ca8ae9364628914b9b8e8cc2f37f03393263603cc2b45295767eb0aac29b0930390eb89587ab2779d2e3decb8042acece725ba42eda650863f418f8d0d50d104e44fbbe5aa7389a4a144a8cecf00f45fb14c39112f9bfb56c0acbd44fa3ff261f5ce4acaa5134c2c1d0cca447040820c81ab1bcdc16aa075b7c68b10d06bbb7ce08b5b805e0238f24402cf24a4b4e00701935a0c68add3de090903f9b85b153cb179a582f57113bfc21c2093803f0cfa4d9d4672c2b05a24f7e4c34a8e9101b70303a7378b9c50b6cddd46814ef7fd73ef6923feceab8fc5aa8b0d185f2e83c7a99dcb1077c0ab5c1f5d5f01ba2f0420443f75c4417db9ebf1665efbb33dca224989920a64b44dc26f682cc77b4632c8454d49135e52503da855bc0f6ff8edc1145451a9772c06891f41064036b66c3119a0fc6e80dffeb65dc456108b7ca0296f4175fff3ed2b0f842cd46bd7e86f4c62dfaf1ddbf836263c00b34803de164983d0811cebfac86e7720c726d3048934c36c23189b02386a722ca9f0fe00233ab50db928d3bccea355cc681144b8b7edcaae4884d5a8f04425c0890ae2c74326e138066d8c05f4c82b29df99b034ea727afde590a1f2177ace3af99cfb1729d6539ce7f7f7314b046aab74497e63dd399e1f7d5f16517c23bd830d1fdee810f3c3b77573dd69c4b97d80d71fb5a632e00acdfa4f8e829faf3580d6a72c40b28a82172f8dcd4627663ebf6069736f21735fd84a226f427cd06bb055f94e7c92f31c48075a2955d82a5b9d2d0198ce0d4e131a112570a8ee40fb80462a81436a58e7db4e34b6e2c422e82f934ecda9949893da5730fc5c23c7c920f363f85ab28cc6a4206713c3152669b47efa8238fa826735f17b4e78750276162024ec85458cd5808e06f40dd9fd43775a456a3ff6cae90550d76d8b2899e0762ad9a371482b3e38083b1274708301d6346c22fea9bb4b73db490ff3ab05b2f7f9e187adef139a7794454b7300b8cc64d3ad76c0e4bc54e08833a4419251550655380d675bc91855aeb82585220bb97f03e976579c08f321b5f8f70988d3061f41465517d53ac571dbf1b24b94443d2e9a8e8a79b392b3d6a4ecdd7f626925c365ef6221305105ce9b5f5b6ecc5bed3d702bd4b7f5008aa8eb8c7aa3ade8ecf6251516fbefeea4e1082aa0e1848eddb31ffe44b04792d296054402826e4bd054e671f223e5557e4c94f89ca01c25c44f1a2ff2c05a70b43408250705e1b858bf0670679fdcd379203e36be3500dd981b1a6422c3cf15224f7fefdef0a5f225c5a09d15767598ecd9e262460bb33a4b5d09a64591efabc57c923d3be406979032ae0bc0997b65336a06dd75b253332ad6a8b63ef043f780a1b3fb6d0b6cad98b1ef4a02535eb39e14a866cfc5fc3a9c5deb2261300d71280ebe66a0776a151469551c3c5fa308757f956655278ec6330ae9e3625468c5f87e02cd9a6489910d4143c1f4ee13aa21a6859d907b788e28572fecee273d44e4a900fa0aa668dd861a60fb6b6b12c2c5ef3c8df1bd7ef5d4b0d1cdb8c15fffbb365b9784bd94abd001c6966216b9b67554ad7cb7f958b70092514f7800fc40244003e0fd1133a9b850fb17f4fcafde07fc87b07fb510670654a5d2d6fc9876ac74728ea41593beef003d6858786a52d3a40af7529596767c17000bfaf8dc52e871359f4ad8bf6e7b2853e5229bdf39657e213580294a5317c5df172865e1e17fe37093b585e04613f5f078f761b2b1752eb32983afda24b523af8851df9a02b37e77f543f18888a782a994a50563334282bf9cdfccc183fdf4fcd75ad86ee0d94f91ee2300a5befbccd14e03a77fc031a8cfe4f01e4c5290f5ac1da0d58ea054bd4837cfd93e5e34fc0eb16e48044ba76131f228d16cde9b0bb978ca7cdcd10653c358bdb26fdb723a530232c32ae0a4cecc06082f46e1c1d596bfe60621ad1e354e01e07b040cc7347c016653f44d926d13ca74e6cbc9d4ab4c99f4491c95c76fff5076b3936eb9d0a286b97c035ca88a3c6309f5febfd4cdaac869e4f58ed409b1e9eb4192fb2f9c2f12176d460fd98286c9d6df84598f260119fd29c63f800c07d8df83d5cc95f8c2fea2812e7890e8a0718bb1e031ecbebc0436dcf3e3b9a58bcc06b4c17f711f80fe1dffc3326a6eb6e00283055c6dabe20d311bfd5019591b7954f8163c9afad9ef8390a38f3582e0a79cdf0353de8eeb6b5f9f27b16ffdef7dd62869b4840ee226ccdce95e02c4545eb981b60571cd83f03dc5eaf8c97a0829a4318a9b3dc06c0e003db700b2260ff1fa8fee66890e637b109abb03ec901b05ca599775f48af50154c0e67d82bf0f558d7d3e0778dc38bea1eb5f74dc8d7f90abdf5511a424be66bf8b6a3cacb477d2e7ef4db68d2eba4d5289122d851f9501ba7e9c4957d8eba3be3fc8e785c4265a1d65c46f2809b70846c693864b169c9dcb78be26ea14b8613f145b01887222979a9e67aee5f800caa6f5c4229bdeefc901232ace6143c9865e4d9c07f51aa200afaf7e48a7d1d8faf366023beab12906ffcb3eaf72c0eb68075e4daf3c080e0c31911befc16f0cc4a09908bb7c1e26abab38bd7b788e1a09c0edf1a35a38d2ff1d3ed47fcdaae2f0934224694f5b56705b9409b6d3d64f3833b686f7576ec64bbdd6ff174e56c2d1edac0011f904681a73face26573fbba4e34652f7ae84acfb2fa5a5b3046f98178cd0831df7477de70e06a4c00e305f31aafc026ef064dd68fd3e4252b1b91d617b26c6d09b6891a00df68f105b5962e7f9d82da101dd595d286da721443b72b2aba2377f6e7772e33b3a5e3753da9c2578c5d1daab80187f55518c72a64ee150a7cb5649823c08c9f62cd7d020b45ec2cba8310db1a7785a46ab24785b4d54ff1660b5ca78e05a9a55edba9c60bf044737bc468101c4e8bd1480d749be5024adefca1d998abe33eaeb6b11fbb39da5d905fdd3f611b2e51517ccee4b8af72c2d948573505590d61a6783ab7278fc43fe55b1fcc0e7216444d3c8039bb8145ef1ce01c50e95a3f3feab0aee883fdb94cc13ee4d21c542aa795e18932228981690f4d4c57ca4db6eb5c092e29d8a05139d509a8aeb48baa1eb97a76e597a32b280b5e9d6c36859064c98ff96ef5126130264fa8d2f49213870d9fb036cff95da51f270311d9976208554e48ffd486470d0ecdb4e619ccbd8226147204baf8e235f54d8b1cba8fa34a9a4d055de515cdf180d2bb6739a175183c472e30b5c914d09eeb1b7dafd6872b38b48c6afc146101200e6e6a44fe5684e220adc11f5c403ddb15df8051e6bdef09117a3a5349938513776286473a3cf1d2788bb875052a2e6459fa7926da33380149c7f98d7700528a60c954e6f5ecb65842fde69d614be69eaa2040a4819ae6e756accf936e14c1e894489744a79c1f2c1eb295d13e2d767c09964b61f9cfe497649f712,0,33a32d10066fa3963a9518a14d1bd1cb5ccaceaeaaeddb4d7aead90c08395bfd,568146140669e69646a6ffeb3793e8010e2732209b4c34ec13e209a070109183,a1017beaa8784f283dee185cd847ae3a327a981e62ae21e8c5face175fc97e9b,250b93570d411149105ab8cb0bc5079914906306368c23e9d77c2a33265b994c,4ec7daf7294a4a2c717442dd21cf2f052a3bfe9d535b55da0f66fecf87a27534,52ab4db9c4b06621f8ded3405691eb32465b1360d15a6b127ded4d15f9cde466,ba9906da802407ddedf6733e29f3996c62425e79d3cbfeebbd6ec4cdc7c976a8,ee661e18c97319ad071106bf35fe1085034832f70718d92f887932128b6100c7,d4e3f18ac2e2095edb5c3b94236118ad,4faa6c4233d9fd53d170ede4172142a8,23f154ac43cfc59c4243e9fc68aeec8f19ad3942d74108e833b36f0dd3dcd357,8da7de6ea7bf2a81a396a42880ba1f5756734c4821309ac9aeffa2a26ce86873b9dc4935a772de6ec5162c6d075b14536800fb174841153511bfb597e992e2fe8a450c4bce102cc550bb37fd564c4d60bf884e, +223,6c77432d1fda31e9f942f8af44607e10f3ad38a65f8a4bddae823e5eff90dc38,d2685070c1e6376e633e825296634fd461fa9e5bdf2109bcebd735e5a91f3e587c5cb782abb797fbf6bb5074fd1542a474f2a45b673763ec2db7fb99b737bbb9,56bd0c06f10352c3a1a9f4b4c92f6fa2b26df124b57878353c1fc691c51abea77c8817daeeb9fa546b77c8daf79d89b22b0e1b87574ece42371f00237aa9d83a,0,7e0e78eb6990b059e6cf0ded66ea93ef82e72aa2f18ac24f2fc6ebab561ae557420729da103f64cecfa20527e15f9fb669a49bbbf274ef0389b3e43c8c44e5f60bf2ac38e2b55e7ec4273dba15ba41d21f8f5b3ee1688b3c29951218caf847a97fb50d75a86515d445699497d968164bf740012679b8962de573be941c62b7ef,1,,1,193d019db571162e52567e0cfdf9dd6964394f32769ae2edc4933b03b502d771,2dd7b9cc85524f8670f695c3143ac26b45cebcabb2782a85e0fe15aee3956535,5e35f94adfd57976833bffec48ef6dde983d18a55501154191ea352ef06732ee,1918b741ef5f9d1d7670b050c152b4a4ead2c31be9aecb0681c0cd4324150853,97124c56236425d792b1ec85e34b846e8d88c9b9f1d4f23ac6cdcc4c177055a0,8c71b468c61119415e3c1dfdd184134211951e2f623199629a46bff9673611f2,b43b8791b51ed682f56d64351601be28e478264411dcf963b14ee60b9ae427fa,794dde4b38ef04250c534a7fa638f2e8cc8b6d2c6110ec290ab0171fdf277d51,cf2e25f23501399f30738d7eee652b90,225a477a28a54ea7671d2b217a9c29db,7ec02fea8c1484e3d0875f978c5f36d63545e2e4acf56311394422f4b66af612,,729847a3e9eba7a5bff454b5de3b393431ee360736b6c030d7a5bd01d1203d2e98f528543fd2bf886ccaa1ada5e215a730a36b3f4abfc4e252c89eb01d9512f94916dae8a76bf16e4da28986ffe159090fe5267ee3394300b7ccf4dfad389a26321b3a3423e4594a82ccfbad16d6561ecb8772b0cb040280ff999a29e3d9d4fd +448,a6ec25127ca1aa4cf16b20084ba1e6516baae4d32422288e9b36d8bddd2de35a,ffffffffffffffffffffffffffffffffffffffffffffffffffffffff053d7ecca53e33e185a8b9be4e7699a97c6ff4c795522e5918ab7cd6b6884f67e683f3dc,ffffffffffffffffffffffffffffffffffffffffffffffffffffffffa7730be30000000000000000000000000000000000000000000000000000000000000000,1,00cf68f8f7ac49ffaa02c4864fdf6dfe7bbf2c740b88d98c50ebafe32c92f3427f57601ffcb21a3435979287db8fee6c302926741f9d5e464c647eeb9b7acaeda46e00abd7506fc9a719847e9a7328215801e96198dac141a15c7c2f68e0690dd1176292a0dded04d1f548aad88f1aebdc0a8f87da4bb22df32dd7c160c225b843e83f6525d6d484f502f16d923124fc538794e21da2eb689d18d87406ecced5b9f92137239ed1d37bcfa7836641a83cf5e0a1cf63f51b06f158e499a459ede41c,1,,0,02b225089255f7b02b20276cfe9779144df8fb1957b477bff3239d802d1256e9,5232c4b6bde9d3d45d7b763ebd7495399bb825cc21de51011761cd81a51bdc84,379223d2f1ea7f8a22043c4ce4122623098309e15b1ce58286ebe3d3bf40f4e1,dd210aa6629f20bb328e5d89daa6eb2ac3d1c658a725536ff154f31b536c23b2,393472f85a5cc6b0f02c4bd466db7a2dc5b91fc9dcb15c0dd6dc21116ece8bca,c80b87b793db47320b2795db66d331bd3021cc24e360d59d0fa8974f54687e0c,ef16a43d77e2b270b0a145ee1618d35f3c943cc7877d6cfcff2287d41692be39,20d4b62e2d982c61bb0cc39a93283d98af36530ef12331d44b2477b0e521b490,fead69be77825a23daec377c362aa560,511d4980526c5e64aa7187462faeafdd,acb8f084ea763ddd1b92ac4ed23bf44de20b84ab677d4e4e6666a6090d40353d,,77b4656934a82de1a593d8481f020194ddafd8cac441f9d72aeb8721e6a14f49698ca6d9b2b6d59d07a01aa552fd4d5b68d0d1617574c77dea10bfadbaa31b83885b7ceac2fd45e3e4a331c51a74e7b1698d81b64c87c73c5b9258b4d83297f9debc2e9aa07f8572ff434dc792b83ecf07b3197de8dc9cf7be56acb59c66cff5 +673,0af952659ed76f80f585966b95ab6e6fd68654672827878684c8b547b1b94f5a,ffffffffffffffffffffffffffffffffffffffffffffffffffffffffc81017fd92fd31637c26c906b42092e11cc0d3afae8d9019d2578af22735ce7bc469c72d,9652d78baefc028cd37a6a92625b8b8f85fde1e4c944ad3f20e198bef8c02f19fffffffffffffffffffffffffffffffffffffffffffffffffffffffff2e91870,0,5c6272ee55da855bbbf7b1246d9885aa7aa601a715ab86fa46c50da533badf82b97597c968293ae04e,97561,,0,4b1767466fe2fb8deddf2dc52cc19c7e2032007e19bfb420b30a80152d0f22d6,64c383e0e78ac99476ddff2061683eeefa505e3666673a1371342c3e6c26981d,5bcfeac98d87e87e158bf839f1269705429f7af2a25b566a25811b5f9aef9560,3568f2aea2e14ef4ee4a3c2a8b8d31bc5e3187ba86db10739b4ff8ec92ff6655,c7df866a62b7d404eb530b2be245a7aece0fb4791402a1de8f33530cbf777cc1,8f732e4aae2ba9314e0982492fa47954de9c189d92fbc549763b27b1b47642ce,992085edfecb92c62a3a7f96ea416f853f34d0dfe065b966b6968b8b87a83081,c5ba5eaf9e1c807154ebab3ea472499e815a7be56dfaf0c201cf6e91ffeca8e6,5e2375ac629b8df1e4ff3617c6255a70,70bcbffcb62e4d29d2605d30bceef137,7332e92a3f9d2792c4d444fac5ed888c39a073043a65eefb626318fd649328f8,,657a4a19711ce593c3844cb391b224f60124aba7e04266233bc50cafb971e26c7716b76e98376448f7d214dd11e629ef9a974d60e3770a695810a61c4ba66d78b936ee7892b98f0b48ddae9fcd8b599dca1c9b43e9b95e0226cf8d4459b8a7c2c4e6db80f1d58c7b20dd7208fa5c1057fb78734223ee801dbd851db601fee61e +1024,f90e080c64b05824c5a24b2501d5aeaf08af3872ee860aa80bdcd430f7b63494,ffffffffffffffffffffffffffffffffffffffffffffffffffffffff115173765dc202cf029ad3f15479735d57697af12b0131dd21430d5772e4ef11474d58b9,12a50f3fafea7c1eeada4cf8d33777704b77361453afc83bda91eef349ae044d20126c6200547ea5a6911776c05dee2a7f1a9ba7dfbabbbd273c3ef29ef46e46,1,5f67d15d22ca9b2804eeab0a66f7f8e3a10fa5de5809a046084348cbc5304e843ef96f59a59c7d7fdfe5946489f3ea297d941bac326225df316a25fc90f0e65b0d31a9c497e960fdbf8c482516bc8a9c1c77b7f6d0e1143810c737f76f9224e6f2c9af5186b4f7259c7e8d165b6e4fe3d38a60bdbdd4d06ecdcaaf62086070dbb68686b802d53dfd7db14b18743832605f5461ad81e2af4b7e8ff0eff0867a25b93cec7becf15c43131895fed09a83bf1ee4a87d44dd0f02a837bf5a1232e201cb882734eb9643dc2dc4d4e8b5690840766212c7ac8f38ad8a9ec47c7a9b3e022ae3eb6a32522128b518bd0d0085dd81c5,69615,,1,8b8de966150bf872b4b695c9983df519c909811954d5d76e99ed0d5f1860247b,eef379db9bd4b1aa90fc347fad33f7d53083389e22e971036f59f4e29d325ac2,0a402d812314646ccc2565c315d1429ec1ed130ff92ff3f48d948f29c3762cf1,e25461fb0e4c162e18123ecde88342d54d449631e9b75a266fd9260c2bb2f41d,97771ce2ce17a25c3d65bf9f8e4acb830dce8d41392be3e4b8ed902a3106681a,2e7022b4eae9152942f68160a93e25d3e197a557385594aa587cb5e431bb470d,613f85a82d783ce450cfd7e91a027fcc4ad5610872f83e4dbe9e2202184c6d6e,cb5de4ed1083222e381401cf88e3167796bc9ab5b8aa1f27b718f39d1e6c0e87,b709dea25e0be287c50e3603482c2e98,1f677e9d7392ebe3633fd82c9efb0f16,889f339285564fd868401fac8380bb9887925122ec8f31c8ae51ce067def103b,,7c4b9e1e6c1ce69da7b01513cdc4588fd93b04dafefaf87f31561763d906c672bac3dfceb751ebd126728ac017d4d580e931b8e5c7d5dfe0123be4dc9b2d2238b655c8a7fadaf8082c31e310909b5b731efc12f0a56e849eae6bfeedcc86dd27ef9b91d159256aa8e8d2b71a311f73350863d70f18d0d7302cf551e4303c7733 diff --git a/bip-0324/reference.py b/bip-0324/reference.py index e07731b3..f02c44ac 100644 --- a/bip-0324/reference.py +++ b/bip-0324/reference.py @@ -1,3 +1,5 @@ +"""Reference implementation for the cryptographic aspects of BIP-324""" + import sys import random import hashlib @@ -70,7 +72,7 @@ class FE: self.den = (a.den * b.num) % FE.SIZE else: self.num = (a * b.den) % FE.SIZE - self.den = a.num + self.den = b.num else: b = b % FE.SIZE assert b != 0 @@ -85,8 +87,7 @@ class FE: """Compute the sum of two field elements (second may be int).""" if isinstance(a, FE): return FE(self.num * a.den + self.den * a.num, self.den * a.den) - else: - return FE(self.num + self.den * a, self.den) + return FE(self.num + self.den * a, self.den) def __radd__(self, a): """Compute the sum of an integer and a field element.""" @@ -96,8 +97,7 @@ class FE: """Compute the difference of two field elements (second may be int).""" if isinstance(a, FE): return FE(self.num * a.den - self.den * a.num, self.den * a.den) - else: - return FE(self.num - self.den * a, self.den) + return FE(self.num - self.den * a, self.den) def __rsub__(self, a): """Compute the difference between an integer and a field element.""" @@ -107,8 +107,7 @@ class FE: """Compute the product of two field elements (second may be int).""" if isinstance(a, FE): return FE(self.num * a.num, self.den * a.den) - else: - return FE(self.num * a, self.den) + return FE(self.num * a, self.den) def __rmul__(self, a): """Compute the product of an integer with a field element.""" @@ -140,15 +139,57 @@ class FE: def sqrt(self): """Compute the square root of a field element. - Due to the fact that our modulus is of the form (p % 4) == 3, the Tonelli-Shanks - algorithm (https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm) is simply - raising the argument to the power (p + 3) / 4.""" + Due to the fact that our modulus p is of the form p = 3 (mod 4), the + Tonelli-Shanks algorithm (https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm) + is simply raising the argument to the power (p + 1) / 4. + + To see why: p-1 = 0 (mod 2), so 2 divides the order of the multiplicative group, + and thus only half of the non-zero field elements are squares. An element a is + a (nonzero) square when Euler's criterion, a^((p-1)/2) = 1 (mod p), holds. We're + looking for x such that x^2 = a (mod p). Given a^((p-1)/2) = 1 (mod p), that is + equivalent to x^2 = a^(1 + (p-1)/2) (mod p). As (1 + (p-1)/2) is even, this is + equivalent to x = a^((1 + (p-1)/2)/2) (mod p), or x = a^((p+1)/4) (mod p).""" v = int(self) s = pow(v, (FE.SIZE + 1) // 4, FE.SIZE) if s**2 % FE.SIZE == v: return FE(s) return None + def sqrts(self): + """Compute all square roots of a field element, if any.""" + s = self.sqrt() + if s is None: + return [] + return [FE(s), -FE(s)] + + # The cube roots of 1 (mod p). + CBRT1 = [ + 1, + 0x851695d49a83f8ef919bb86153cbcb16630fb68aed0a766a3ec693d68e6afa40, + 0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee + ] + + + def cbrts(self): + """Compute all cube roots of a field element, if any. + + Due to the fact that our modulus p is of the form p = 7 (mod 9), one cube root + can always be computed by raising to the power (p + 2) / 9. The other roots + (if any) can be found by multiplying with the two non-trivial cube roots of 1. + + To see why: p-1 = 0 (mod 3), so 3 divides the order of the multiplicative group, + and thus only 1/3 of the non-zero field elements are cubes. An element a is a + (nonzero) cube when a^((p-1)/3) = 1 (mod p). We're looking for x such that + x^3 = a (mod p). Given a^((p-1)/3) = 1 (mod p), that is equivalent to + x^3 = a^(1 + (p-1)/3) (mod p). As (1 + (p-1)/3) is a multiple of 3, this is + equivalent to x = a^((1 + (p-1)/3)/3) (mod p), or x = a^((p+2)/9) (mod p).""" + v = int(self) + c = pow(v, (FE.SIZE + 2) // 9, FE.SIZE) + + if pow(c, 3, FE.SIZE) == v: + return [FE(c * f) for f in FE.CBRT1] + return [] + def is_square(self): """Determine if this field element has a square root.""" # Compute the Jacobi symbol of (self / p). Since our modulus is prime, this @@ -161,7 +202,7 @@ class FE: while n & 1 == 0: n >>= 1 r = k & 7 - t ^= (r == 3 or r == 5) + t ^= (r in (3, 5)) n, k = k, n t ^= (n & k & 3 == 3) n = n % k @@ -172,8 +213,7 @@ class FE: """Check whether two field elements are equal (second may be an int).""" if isinstance(a, FE): return (self.num * a.den - self.den * a.num) % FE.SIZE == 0 - else: - return (self.num - self.den * a) % FE.SIZE == 0 + return (self.num - self.den * a) % FE.SIZE == 0 def to_bytes(self): """Convert a field element to 32-byte big endian encoding.""" @@ -187,6 +227,16 @@ class FE: return None return FE(v) + def __str__(self): + """Convert this field element to a string.""" + return f"{int(self):064x}" + + def __repr__(self): + """Get a string representation of this field element.""" + return f"FE(0x{int(self):x})" + +assert all(pow(c, 3, FE.SIZE) == 1 for c in FE.CBRT1) + class GE: """Objects of this class represent points (group elements) on the secp256k1 curve. @@ -221,12 +271,11 @@ class GE: x3 = l**2 - self.x - a.x y3 = l * (self.x - x3) - self.y return GE(x3, y3) - elif self.y == a.y: + if self.y == a.y: # Adding point to itself return self.double() - else: - # Adding point to its negation - return None + # Adding point to its negation + return None def __radd__(self, a): """Add infinity to a point.""" @@ -260,13 +309,21 @@ class GE: """Determine whether the provided field element is a valid X coordinate.""" return (FE(x)**3 + 7).is_square() + def __str__(self): + """Convert this group element to a string.""" + return f"({self.x},{self.y})" + + def __repr__(self): + """Get a string representation for this group element.""" + return f"GE(0x{int(self.x)},0x{int(self.y)})" + SECP256K1_G = GE( 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8) ### ElligatorSwift -# Precomputed constant square root of -3 modulo p. +# Precomputed constant square root of -3 (mod p). MINUS_3_SQRT = FE(-3).sqrt() def xswiftec(u, t): @@ -292,7 +349,7 @@ def xswiftec_inv(x, u, case): if case & 2 == 0: if GE.is_valid_x(-x - u): return None - v = x if case & 1 == 0 else -x - u + v = x s = -(u**3 + 7) / (u**2 + u*v + v**2) else: s = x - u @@ -301,17 +358,16 @@ def xswiftec_inv(x, u, case): r = (-s * (4 * (u**3 + 7) + 3 * s * u**2)).sqrt() if r is None: return None - if case & 1: - if r == 0: - return None - r = -r + if case & 1 and r == 0: + return None v = (-u + r / s) / 2 w = s.sqrt() if w is None: return None - if case & 4: - w = -w - return w * (u * (MINUS_3_SQRT - 1) / 2 - v) + if case & 5 == 0: return -w * (u * (1 - MINUS_3_SQRT) / 2 + v) + if case & 5 == 1: return w * (u * (1 + MINUS_3_SQRT) / 2 + v) + if case & 5 == 4: return w * (u * (1 - MINUS_3_SQRT) / 2 + v) + if case & 5 == 5: return -w * (u * (1 + MINUS_3_SQRT) / 2 + v) def xelligatorswift(x): """Given a field element X on the curve, find (u, t) that encode them.""" @@ -328,12 +384,17 @@ def ellswift_create(): u, t = xelligatorswift((priv * SECP256K1_G).x) return priv.to_bytes(32, 'big'), u.to_bytes() + t.to_bytes() +def ellswift_decode(ellswift): + """Convert ellswift encoded X coordinate to 32-byte xonly format.""" + u = FE(int.from_bytes(ellswift[:32], 'big')) + t = FE(int.from_bytes(ellswift[32:], 'big')) + return xswiftec(u, t).to_bytes() + def ellswift_ecdh_xonly(pubkey_theirs, privkey): """Compute X coordinate of shared ECDH point between elswift pubkey and privkey.""" - u = FE(int.from_bytes(pubkey_theirs[:32], 'big')) - t = FE(int.from_bytes(pubkey_theirs[32:], 'big')) d = int.from_bytes(privkey, 'big') - return (d * GE.lift_x(xswiftec(u, t))).x.to_bytes() + pub = ellswift_decode(pubkey_theirs) + return (d * GE.lift_x(FE.from_bytes(pub))).x.to_bytes() ### Poly1305 @@ -402,7 +463,7 @@ def chacha20_block(key, nonce, cnt): for i in range(3): init[13 + i] = int.from_bytes(nonce[4 * i:4 * (i+1)], 'little') # Perform 20 rounds. - state = [v for v in init] + state = list(init) for _ in range(10): chacha20_doubleround(state) # Add initial values back into state. @@ -459,6 +520,7 @@ class FSChaCha20Poly1305: self.packet_counter = 0 def crypt(self, aad, text, is_decrypt): + """Encrypt or decrypt the specified (plain/cipher)text.""" nonce = ((self.packet_counter % REKEY_INTERVAL).to_bytes(4, 'little') + (self.packet_counter // REKEY_INTERVAL).to_bytes(8, 'little')) if is_decrypt: @@ -474,12 +536,14 @@ class FSChaCha20Poly1305: self.packet_counter += 1 return ret - def decrypt(self, aad, ciphertext): - return self.crypt(aad, ciphertext, True) - def encrypt(self, aad, plaintext): + """Encrypt the specified plaintext with provided AAD.""" return self.crypt(aad, plaintext, False) + def decrypt(self, aad, ciphertext): + """Decrypt the specified ciphertext with provided AAD.""" + return self.crypt(aad, ciphertext, True) + class FSChaCha20: """Rekeying wrapper stream cipher around ChaCha20.""" @@ -491,6 +555,7 @@ class FSChaCha20: self.keystream = b'' def get_keystream_bytes(self, nbytes): + """Generate nbytes keystream bytes.""" while len(self.keystream) < nbytes: nonce = ((0).to_bytes(4, 'little') + (self.chunk_counter // REKEY_INTERVAL).to_bytes(8, 'little')) @@ -501,6 +566,7 @@ class FSChaCha20: return ret def crypt(self, chunk): + """Encrypt or decypt chunk.""" ks = self.get_keystream_bytes(len(chunk)) ret = bytes([ks[i] ^ chunk[i] for i in range(len(chunk))]) if ((self.chunk_counter + 1) % REKEY_INTERVAL) == 0: @@ -509,6 +575,15 @@ class FSChaCha20: self.chunk_counter += 1 return ret + def encrypt(self, chunk): + """Encrypt chunk.""" + return self.crypt(chunk) + + def decrypt(self, chunk): + """Decrypt chunk.""" + return self.crypt(chunk) + + ### Shared secret computation def v2_ecdh(priv, ellswift_theirs, ellswift_ours, initiating): @@ -519,10 +594,9 @@ def v2_ecdh(priv, ellswift_theirs, ellswift_ours, initiating): # Initiating, place our public key encoding first. return TaggedHash("bip324_ellswift_xonly_ecdh", ellswift_ours + ellswift_theirs + ecdh_point_x32) - else: - # Responding, place their public key encoding first. - return TaggedHash("bip324_ellswift_xonly_ecdh", - ellswift_theirs + ellswift_ours + ecdh_point_x32) + # Responding, place their public key encoding first. + return TaggedHash("bip324_ellswift_xonly_ecdh", + ellswift_theirs + ellswift_ours + ecdh_point_x32) ### Key derivation @@ -571,5 +645,5 @@ def v2_enc_packet(peer, contents, aad=b'', ignore=False): header = (ignore << IGNORE_BIT_POS).to_bytes(HEADER_LEN, 'little') plaintext = header + contents aead_ciphertext = peer['send_P'].encrypt(aad, plaintext) - enc_plaintext_len = peer['send_L'].crypt(len(contents).to_bytes(LENGTH_FIELD_LEN, 'little')) + enc_plaintext_len = peer['send_L'].encrypt(len(contents).to_bytes(LENGTH_FIELD_LEN, 'little')) return enc_plaintext_len + aead_ciphertext diff --git a/bip-0324/run_test_vectors.py b/bip-0324/run_test_vectors.py index ada7371a..8e4b8f25 100644 --- a/bip-0324/run_test_vectors.py +++ b/bip-0324/run_test_vectors.py @@ -1,53 +1,69 @@ +"""Run the BIP-324 test vectors.""" + import csv import os import sys import reference -with open(os.path.join(sys.path[0], 'packet_encoding_test_vectors.csv'), newline='') as csvfile: - reader = csv.reader(csvfile) - reader.__next__() +FILENAME_PACKET_TEST = os.path.join(sys.path[0], 'packet_encoding_test_vectors.csv') +FILENAME_XSWIFTEC_INV_TEST = os.path.join(sys.path[0], 'xswiftec_inv_test_vectors.csv') +FILENAME_ELLSWIFT_DECODE_TEST = os.path.join(sys.path[0], 'ellswift_decode_test_vectors.csv') + +with open(FILENAME_PACKET_TEST, newline='', encoding='utf-8') as csvfile: + print(f"Running {FILENAME_PACKET_TEST} tests...") + reader = csv.DictReader(csvfile) for row in reader: - in_idx, in_priv_ours, in_ellswift_ours, in_ellswift_theirs, in_initiating, in_content, in_multiply, in_aad, in_ignore, mid_x_ours, mid_x_shared, mid_shared_secret, mid_initiator_l, mid_initiator_p, mid_responder_l, mid_responder_p, mid_send_garbage_terminator, mid_recv_garbage_terminator, mid_session_id, out_ciphertext, out_ciphertext_endswith = row + in_initiating = int(row['in_initiating']) + bytes_priv_ours = bytes.fromhex(row['in_priv_ours']) + int_priv_ours = int.from_bytes(bytes_priv_ours, 'big') + assert row['mid_x_ours'] == (int_priv_ours * reference.SECP256K1_G).x.to_bytes().hex() + bytes_ellswift_ours = bytes.fromhex(row['in_ellswift_ours']) + assert row['mid_x_ours'] == reference.ellswift_decode(bytes_ellswift_ours).hex() + bytes_ellswift_theirs = bytes.fromhex(row['in_ellswift_theirs']) + assert row['mid_x_theirs'] == reference.ellswift_decode(bytes_ellswift_theirs).hex() + x_shared = reference.ellswift_ecdh_xonly(bytes_ellswift_theirs, bytes_priv_ours) + assert row['mid_x_shared'] == x_shared.hex() + shared_secret = reference.v2_ecdh(bytes_priv_ours, bytes_ellswift_theirs, + bytes_ellswift_ours, in_initiating) + assert row['mid_shared_secret'] == shared_secret.hex() - assert mid_x_ours == (int.from_bytes(bytes.fromhex(in_priv_ours), 'big') * reference.SECP256K1_G).x.to_bytes().hex() - assert mid_x_shared == reference.ellswift_ecdh_xonly(bytes.fromhex(in_ellswift_theirs), bytes.fromhex(in_priv_ours)).hex() - assert mid_shared_secret == reference.v2_ecdh(bytes.fromhex(in_priv_ours), bytes.fromhex(in_ellswift_theirs), bytes.fromhex(in_ellswift_ours), int(in_initiating)).hex() - - peer = reference.initialize_v2_transport(bytes.fromhex(mid_shared_secret), int(in_initiating)) - assert mid_initiator_l == peer['initiator_L'].hex() - assert mid_initiator_p == peer['initiator_P'].hex() - assert mid_responder_l == peer['responder_L'].hex() - assert mid_responder_p == peer['responder_P'].hex() - assert mid_send_garbage_terminator == peer['send_garbage_terminator'].hex() - assert mid_recv_garbage_terminator == peer['recv_garbage_terminator'].hex() - assert mid_session_id == peer['session_id'].hex() - for _ in range(int(in_idx)): + peer = reference.initialize_v2_transport(shared_secret, in_initiating) + assert row['mid_initiator_l'] == peer['initiator_L'].hex() + assert row['mid_initiator_p'] == peer['initiator_P'].hex() + assert row['mid_responder_l'] == peer['responder_L'].hex() + assert row['mid_responder_p'] == peer['responder_P'].hex() + assert row['mid_send_garbage_terminator'] == peer['send_garbage_terminator'].hex() + assert row['mid_recv_garbage_terminator'] == peer['recv_garbage_terminator'].hex() + assert row['out_session_id'] == peer['session_id'].hex() + for _ in range(int(row['in_idx'])): reference.v2_enc_packet(peer, b"") - ciphertext = reference.v2_enc_packet(peer, bytes.fromhex(in_content) * int(in_multiply), bytes.fromhex(in_aad), int(in_ignore)) - if len(out_ciphertext): - assert out_ciphertext == ciphertext.hex() - if len(out_ciphertext_endswith): - assert ciphertext.hex().endswith(out_ciphertext_endswith) + ciphertext = reference.v2_enc_packet( + peer, + bytes.fromhex(row['in_contents']) * int(row['in_multiply']), + bytes.fromhex(row['in_aad']), int(row['in_ignore'])) + if len(row['out_ciphertext']): + assert row['out_ciphertext'] == ciphertext.hex() + if len(row['out_ciphertext_endswith']): + assert ciphertext.hex().endswith(row['out_ciphertext_endswith']) -with open(os.path.join(sys.path[0], 'xswiftec_test_vectors.csv'), newline='') as csvfile: - reader = csv.reader(csvfile) - reader.__next__() +with open(FILENAME_XSWIFTEC_INV_TEST, newline='', encoding='utf-8') as csvfile: + print(f"Running {FILENAME_XSWIFTEC_INV_TEST} tests...") + reader = csv.DictReader(csvfile) for row in reader: - u = reference.FE.from_bytes(bytes.fromhex(row[0])) - x = reference.FE.from_bytes(bytes.fromhex(row[1])) + u = reference.FE.from_bytes(bytes.fromhex(row['u'])) + x = reference.FE.from_bytes(bytes.fromhex(row['x'])) for case in range(8): ret = reference.xswiftec_inv(x, u, case) if ret is None: - assert row[2 + case] == "" + assert row[f"case{case}_t"] == "" else: - assert row[2 + case] == ret.to_bytes().hex() + assert row[f"case{case}_t"] == ret.to_bytes().hex() assert reference.xswiftec(u, ret) == x -with open(os.path.join(sys.path[0], 'xelligatorswift_test_vectors.csv'), newline='') as csvfile: - reader = csv.reader(csvfile) - reader.__next__() +with open(FILENAME_ELLSWIFT_DECODE_TEST, newline='', encoding='utf-8') as csvfile: + print(f"Running {FILENAME_ELLSWIFT_DECODE_TEST} tests...") + reader = csv.DictReader(csvfile) for row in reader: - ellswift = bytes.fromhex(row[0]) - x = bytes.fromhex(row[1]) - assert reference.ellswift_ecdh_xonly(ellswift, (1).to_bytes(32, 'big')) == x + ellswift = bytes.fromhex(row['ellswift']) + assert reference.ellswift_decode(ellswift).hex() == row['x'] diff --git a/bip-0324/secp256k1_test_vectors.py b/bip-0324/secp256k1_test_vectors.py new file mode 100644 index 00000000..57ae801c --- /dev/null +++ b/bip-0324/secp256k1_test_vectors.py @@ -0,0 +1,52 @@ +"""Convert the BIP-324 test vectors to secp256k1 code.""" + +import csv +import reference +import os +import sys + +FILENAME_XSWIFTEC_INV_TEST = os.path.join(sys.path[0], 'xswiftec_inv_test_vectors.csv') +FILENAME_ELLSWIFT_DECODE_TEST = os.path.join(sys.path[0], 'ellswift_decode_test_vectors.csv') + +def format_int(v): + """Format 0 as "0", but other integers as 0x%08x.""" + if v == 0: + return "0" + return f"0x{v:08x}" + +def format_fe(fe): + """Format a field element constant as SECP256K1_FE_CONST code.""" + vals = [(int(fe) >> (32 * (7 - i))) & 0xffffffff for i in range(8)] + strs = ", ".join(format_int(v) for v in vals) + return f"SECP256K1_FE_CONST({strs})" + +def output_xswiftec_inv_cases(): + """Generate lines corresponding to the xswiftec_inv test cases.""" + with open(FILENAME_XSWIFTEC_INV_TEST, newline='', encoding='utf-8') as csvfile: + reader = csv.DictReader(csvfile) + print("xswiftec_inv cases:") + for row in reader: + u = int.from_bytes(bytes.fromhex(row['u']), 'big') + x = int.from_bytes(bytes.fromhex(row['x']), 'big') + pat = sum(1<