From jussi.kivilinna at iki.fi Sun Aug 2 11:55:10 2026 From: jussi.kivilinna at iki.fi (Jussi Kivilinna) Date: Sun, 2 Aug 2026 12:55:10 +0300 Subject: [PATCH 05/10] sntrup761: reduce freeze helpers without division In-Reply-To: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> References: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> Message-ID: <20260802095515.2729589-5-jussi.kivilinna@iki.fi> * cipher/sntrup761.c (int32_divmod_uint14, int32_mod_uint14): Remove. (F3_freeze, Fq_freeze): Bias input to non-negative range and compute quotient with multiply-shift. -- 'F3_freeze' and 'Fq_freeze' reduced through 'int32_mod_uint14', which handles run-time modulus and executes two hardware divisions per call. Modulus is compile-time constant in both, so biasing input by multiple of modulus allows open coding reduction as multiply-shift. Benchmark on AMD Ryzen 9 9950X3D, SNTRUP761 usec/operation, quick random disabled ('base' being state before this patch series): | base before after speedup total keygen | 37971.9 37971.9 9480.2 4.01x 4.01x encap | 7355.8 7355.8 4468.2 1.65x 1.65x decap | 12207.1 12207.1 3325.4 3.67x 3.67x Signed-off-by: Jussi Kivilinna --- cipher/sntrup761.c | 53 ++++++++++++++++++---------------------------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/cipher/sntrup761.c b/cipher/sntrup761.c index e9c7d707..11366f8f 100644 --- a/cipher/sntrup761.c +++ b/cipher/sntrup761.c @@ -208,36 +208,6 @@ uint32_mod_uint14 (uint32_t x, uint16_t m) return r; } -/* from supercop-20201130/crypto_kem/sntrup761/ref/int32.c */ - -static void -int32_divmod_uint14 (int32_t * q, uint16_t * r, int32_t x, uint16_t m) -{ - uint32_t uq, uq2; - uint16_t ur, ur2; - uint32_t mask; - - uint32_divmod_uint14 (&uq, &ur, 0x80000000 + (uint32_t) x, m); - uint32_divmod_uint14 (&uq2, &ur2, 0x80000000, m); - ur -= ur2; - uq -= uq2; - mask = ct_ulong_gen_mask(ur >> 15); - ur += mask & m; - uq += mask; - *r = ur; - *q = uq; -} - - -static uint16_t -int32_mod_uint14 (int32_t x, uint16_t m) -{ - int32_t q; - uint16_t r; - int32_divmod_uint14 (&q, &r, x, m); - return r; -} - /* from supercop-20201130/crypto_kem/sntrup761/ref/paramsmenu.h */ #define p 761 #define q 4591 @@ -402,7 +372,16 @@ typedef int8_t small; static small F3_freeze (int16_t x) { - return int32_mod_uint14 (x + 1, 3) - 1; + /* Bias by multiple of three so that reduction is done on non-negative + value. Multiply-shift quotient is exact for values below 2^17. */ + static const u32 max_s16 = 0x7fff; + static const u32 max_s16_round_up_3 = (max_s16 + (3 - 1)) / 3 * 3; + static const u32 mod3_shift = 17; + static const u32 mod3_mul = (1U << mod3_shift) / 3 + 1; + u32 biased = x + 1 + max_s16_round_up_3; + u32 quot = (biased * mod3_mul) >> mod3_shift; + + return (small)(biased - quot * 3) - 1; } /* ----- arithmetic mod q */ @@ -416,7 +395,17 @@ typedef int16_t Fq; static Fq Fq_freeze (int32_t x) { - return int32_mod_uint14 (x + q12, q) - q12; + /* Bias by multiple of q so that reduction is done on non-negative value. + Callers stay within +-2*q12*q12, where multiply-shift quotient is + exact. */ + static const u32 max_fq = 2 * q12 * q12; + static const u32 max_fq_round_up_q = (max_fq + (q - 1)) / q * q; + static const u32 modq_shift = 36; + static const u32 modq_mul = (u32)(((u64)1 << modq_shift) / q + 1); + u32 biased = x + q12 + max_fq_round_up_q; + u32 quot = (u32)(((u64)biased * modq_mul) >> modq_shift); + + return (Fq)(biased - quot * q) - q12; } static Fq -- 2.53.0 From jussi.kivilinna at iki.fi Sun Aug 2 11:55:08 2026 From: jussi.kivilinna at iki.fi (Jussi Kivilinna) Date: Sun, 2 Aug 2026 12:55:08 +0300 Subject: [PATCH 03/10] bench-slope: add RSA and DSA benchmarking In-Reply-To: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> References: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> Message-ID: <20260802095515.2729589-3-jussi.kivilinna@iki.fi> * tests/bench-slope.c (bench_pk_algo, bench_pk_operation, bench_pk_oper) (bench_pk_hd, sample_private_rsa_key_2048, sample_public_rsa_key_2048) (sample_private_rsa_key_3072, sample_public_rsa_key_3072) (sample_private_rsa_key_4096, sample_public_rsa_key_4096) (sample_private_dsa_key_2048, sample_public_dsa_key_2048) (sample_private_dsa_key_3072, sample_public_dsa_key_3072, pk_algos) (pk_algo_name, pk_map_name, bench_pk_init, bench_pk_free) (bench_pk_sign_do_bench, bench_pk_verify_do_bench, pk_sign_ops) (pk_verify_ops, pk_operations, cipher_pk_one, _pk_bench, pk_bench): New. (print_help): Add mention of 'pk'. (main): Add "pk" tests. -- Only ECC and post-quantum algorithms had slope benchmarking, so RSA and DSA signing and verification were covered by 'benchmark' alone. Keys are samples instead of being generated, because prime search time varies too much for slope measurement to give stable result. Signing repetitions are scaled down per key size, as RSA-4096 signing alone would otherwise take longer than rest of the section. $ tests/bench-slope pk Public-key: RSA-2048 | nanosecs/iter cycles/iter sign | 1785756 - verify | 20737 - = RSA-3072 | nanosecs/iter cycles/iter sign | 4916490 - verify | 45286 - = RSA-4096 | nanosecs/iter cycles/iter sign | 10355002 - verify | 70476 - = DSA-2048 | nanosecs/iter cycles/iter sign | 544555 - verify | 518044 - = DSA-3072 | nanosecs/iter cycles/iter sign | 1165046 - verify | 1133774 - = Signed-off-by: Jussi Kivilinna --- tests/bench-slope.c | 654 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 653 insertions(+), 1 deletion(-) diff --git a/tests/bench-slope.c b/tests/bench-slope.c index 29760227..4b296f0b 100644 --- a/tests/bench-slope.c +++ b/tests/bench-slope.c @@ -2335,6 +2335,648 @@ kdf_bench (char **argv, int argc) } +/************************************************************** PK benchmarks. */ + +enum bench_pk_algo +{ +#if USE_RSA + PK_ALGO_RSA2048 = 0, + PK_ALGO_RSA3072, + PK_ALGO_RSA4096, +#endif +#if USE_DSA + PK_ALGO_DSA2048, + PK_ALGO_DSA3072, +#endif + __MAX_PK_ALGO +}; + +enum bench_pk_operation +{ + PK_OPER_SIGN = 0, + PK_OPER_VERIFY, + __MAX_PK_OPER +}; + +struct bench_pk_oper +{ + enum bench_pk_operation oper; + const char *name; + struct bench_ops *ops; + + enum bench_pk_algo algo; +}; + +struct bench_pk_hd +{ + gcry_sexp_t pub_key; + gcry_sexp_t sec_key; + gcry_sexp_t data; + gcry_sexp_t sig; +}; + +static const char sample_private_rsa_key_2048[] = +"(private-key \n" +" (rsa \n" +" (n #00E11B35517B742D97078D46EC5FADC86E846436DBD852B1D83B80C0C629A1" +"AA0F5DBEEFD74B5C8FF420BA5EB586C4EEED96C8A3517E4C4C01B4525B7312D68A34" +"75B2FC78C87B33936260131F5A0AE3CFE2924E85D722C9FA0838F88C30BC8B5B0797" +"735450DE5C3F89B877B0960D285FA76FE64D93C2CA679AB6E59C014D13DBF513A301" +"B993E40C1AAF47171450690A8EA19E72C739C64D1CFCDA4FE0F165FA6F7AC8189C3F" +"CC2AB8E13A009B6B7869842CEEA54C91B249DC7906C8D3FE8FBFEBA88F6E5284A215" +"B2FCF28B31DC296EF26F7532D99EDEBC2D25D96AB3393702E7BAB7877285AA92324A" +"6D4A32E5BAD25B0B67541F526E1AF17331ECBBD971A3#)\n" +" (e #010001#)\n" +" (d #16091CCEDA2651E42BEE7AB17878418ECB38282A9CC8AD2889DC36E922468E" +"DB5F6C1CE2BD6CD53377AE4D2DEC4D374728BD4E1447282BA7C4A81F3A6ABBDE1191" +"C7FD70FE33E60B98D3E7381BDC2BD7A263AEE14F94A0D6239B84E452B36BD9A6C465" +"34C6C36738C03FF2BC4BF0D097F4830B8295C9537F5E0B3361A587C23EBB8564BA85" +"81AF7B48B4F65FC29AB0E9140101ED6AE837166910D3BE56B9F5D2816F8130D532C7" +"627C7AA8BF7F290B538B42C7F7BC5AB23F4001C6F5DB0FD8102491C80F1E275C3BD6" +"A3AC87AEBF9E467820A9B4CB7B09021F8C468789102A1D01B359ED6DFE4E056EA2FC" +"3B09F1E46890356D660BB33B479AB6981A445D1F89#)\n" +" (p #00EFA1B3F6348A80FB8041C97965985DDDF83B2FDB0AE8866C11F984E55C2E" +"57A9DD03101B6DC5BC0C77AEED227B4F77FB62A4D9932ABFCFDC39C43FDEB06C7525" +"0705B2F6D7F48E1C79891F74BBAD3A671F75FFBDA1478D565CF8A4AC3840429874EA" +"87DABF688696BA7AE85341101BE3A8958D76B1021C439F27F49F82EB3ADD#)\n" +" (q #00F07B823CFBB5F8DFA439EC6CA63BB8E2AA30679DD2D67F42F40AF4777C0F" +"154161E35CD5B789DD98DE7BF435F52EDB5A156F8840C45B00A341A2786A07C2FB37" +"942B384E45FD7CBCCC84A017998ACEC4C90A832EBBF58CB66FDB3D7B8BB447EB8782" +"551AF520281E6DF838013DA9AA1A7278801813ED9B24ADFA33AA9855567F#)\n" +" (u #5493CEDD291EBB5EFAB1911CA9BAE7C42D9F3FE303C25FBA6674047FCBEDB7" +"7A14A3C8F7176B4BF46AE3C44D85017A4F71C0CAADE6B97507A913FA001D38262772" +"12C78674C1FCC1273865DDA92A844269C086A769D5571D7E988E915339AC758E257F" +"96AC7A073A8EF8D288DE0A9F7F1E5761304CBDFBEB8BF1783F0967484E#)\n" +" )\n" +" )\n" +; + +static const char sample_public_rsa_key_2048[] = +"(public-key \n" +" (rsa \n" +" (n #00E11B35517B742D97078D46EC5FADC86E846436DBD852B1D83B80C0C629A1" +"AA0F5DBEEFD74B5C8FF420BA5EB586C4EEED96C8A3517E4C4C01B4525B7312D68A34" +"75B2FC78C87B33936260131F5A0AE3CFE2924E85D722C9FA0838F88C30BC8B5B0797" +"735450DE5C3F89B877B0960D285FA76FE64D93C2CA679AB6E59C014D13DBF513A301" +"B993E40C1AAF47171450690A8EA19E72C739C64D1CFCDA4FE0F165FA6F7AC8189C3F" +"CC2AB8E13A009B6B7869842CEEA54C91B249DC7906C8D3FE8FBFEBA88F6E5284A215" +"B2FCF28B31DC296EF26F7532D99EDEBC2D25D96AB3393702E7BAB7877285AA92324A" +"6D4A32E5BAD25B0B67541F526E1AF17331ECBBD971A3#)\n" +" (e #010001#)\n" +" )\n" +" )\n" +; + +static const char sample_private_rsa_key_3072[] = +"(private-key \n" +" (rsa \n" +" (n #00AC28969FBE9079BFFC90F55455E629D54E9125657C7543DB104F55001C9E" +"92797333910372D00931C4886BE3DA195ED540E799D5C0AA1EFBBAFB6D687097A3C7" +"FBC4430EFDDED910F66C67F4A11AE7A6925413394A4856426EA1B924849A88A459D2" +"0C963F0FA13DEE3DA3BF41445881A49A03E2417EEF15207C0114E57F0639AAFF32ED" +"92D2C51796F3601337EAB8E82036344F508972E560B57CBEB0413AB48885C9F20B6D" +"AE6D946E2562B3E9B42D4D82B6E3E4CE714E9C7EF8DC35C851013F637C4D658E902D" +"A85A3894C6CC2425D9C7979FD0482343B46B9543C4ACCA1A0FB34132E30F8654E583" +"98D38749FBC63158EA79B73C6C87DDB2BD56A920DF7A4BDE2EF16E28D36A3D76B560" +"7944A6804D4BBDFA92B52B7DADB80A6279D347D2D3124A4F0BAABD420882E00032E1" +"9476AE9E195C2A0FD09A73ACE50757FF250DDDF8F511B222F9510C1F17DFAAD5C8C3" +"F6198F511E69DEBE1257DF6692E9CB232A944F53521469224A3483CFC59AB7D59A5D" +"B6FFF620CF6359914B1905286D03#)\n" +" (e #010001#)\n" +" (d #2322235F155649AA6F022C36DA52DEDDABAB7E5CC031F4379C13FC8E49C8E8" +"AE855E942D067CC32B976699D2059BE0D917664C642D6DEA65C80A7090FC4D4DFCCD" +"7A078F632ADBD494DD99B7783B53E40FFFBD6E9724BD09D0A70B7012E9B0920DCC8A" +"8A0CF3851DECE5426A11094020B0F5476EA09C257183D01AAE67896D3D4E92C71369" +"BFBEBE2A2D9FC13C4B9811B3252CB6B5025FE2C4C234E37B77CC61B46CCD422AA7E7" +"0D70D9ABA28181E3A5CD282C67C4B586B51AC5E4C697E939F2780654FF2ED151B72F" +"800EE7EFAC08B44E309BF93A3304927591F34A912618EBA58369C42AC2D5A58476F7" +"A80C000A19278B4938C208294B8E1CEAA17941AF549CEC640C51BEDEA0D51A8C66E3" +"6EAF1DC1F79EFFEFEFB47119A3E434E32EACBB9C75EAF669DD91B54C739BFCCA9504" +"707EBAEA445673255160F3574EBAC5D257E52BF80C43F78D02DDC34CE291F30A9074" +"D5A0E1624F7FDC72D8EB7A4499754BC8CD302B20D7C14920726899E30D2D571BFDF6" +"B39A80CDBFF64DFB2F0E3CC891#)\n" +" (p #00CD37BD7DAE22FA808DC3CDCBE6A68AD444D692707C5BF564A97563193B30" +"211636A46D1C7523DF06DBDCA83F7D19290F83B132C43BE02E882655ABE94261EC2B" +"99BE4962875B3D8D78CE4A2F2F2B9FA006327E1E924E2AD9BBDC033F1521BA7E7FB8" +"12944CD7DC23E3F26635C9B998974915947EB634607A7C74FE6BE21A07F37836F0FF" +"F6DC44494B23E0098103EF12730D46B5D70A330690534BCF0359F252991A3D205F00" +"A266D02DB68F3C7530F63F7D8052E9DDC8A16CEFA17BA5A23813#)\n" +" (q #00D6C2982A666A794F2CE1B0C603343994369B702AA01BB5DE28DC8CC009C9" +"07F574875CEA31B8EC1E84AE38762FC33D531FB02EB01BA5D52B6AAD1287B18BA6B0" +"B3B1AF16B8449638E11CF53F5C764A3BE9C78C115936A2C20D44F88745CEFB9AAF10" +"C41557B3D4D7651E6A90E41A13DD9332142898795AD96AB0F3D783797688EAB1ACC8" +"4247CEE427F4C8B015F094B0C470F5ED5C64850B2B34F2EDE4F467D99C484BB61CC2" +"40B34F01B80C424E0CDA72D44B6ED87A56B91E54F989707C7551#)\n" +" (u #584ED527F71C163D8F552DCE5866CF8DF0FCC269BB0DBB992DFB4FA48C13B7" +"8C984269C08928C211A243194D713FEB2B38134E91AB5F89180BC8A9845E9483561E" +"B28006167DC0EAEDC11C57D9021347F71E4E7ED68E748865B9A55FAAE73DC0860F75" +"6A98CD87C34D2882E1F0985341F6AD4134E749598DB1C696B95306A758C7B9BA8B7F" +"14601117F7B67371DC8CC324F5B5C62AC333434A6752106F09E3699E94DEE5AB00F5" +"D478FE6D002D6473EFBC9A02E4507135ED37EF3F26401D687A#)\n" +" )\n" +" )\n" +; + +static const char sample_public_rsa_key_3072[] = +"(public-key \n" +" (rsa \n" +" (n #00AC28969FBE9079BFFC90F55455E629D54E9125657C7543DB104F55001C9E" +"92797333910372D00931C4886BE3DA195ED540E799D5C0AA1EFBBAFB6D687097A3C7" +"FBC4430EFDDED910F66C67F4A11AE7A6925413394A4856426EA1B924849A88A459D2" +"0C963F0FA13DEE3DA3BF41445881A49A03E2417EEF15207C0114E57F0639AAFF32ED" +"92D2C51796F3601337EAB8E82036344F508972E560B57CBEB0413AB48885C9F20B6D" +"AE6D946E2562B3E9B42D4D82B6E3E4CE714E9C7EF8DC35C851013F637C4D658E902D" +"A85A3894C6CC2425D9C7979FD0482343B46B9543C4ACCA1A0FB34132E30F8654E583" +"98D38749FBC63158EA79B73C6C87DDB2BD56A920DF7A4BDE2EF16E28D36A3D76B560" +"7944A6804D4BBDFA92B52B7DADB80A6279D347D2D3124A4F0BAABD420882E00032E1" +"9476AE9E195C2A0FD09A73ACE50757FF250DDDF8F511B222F9510C1F17DFAAD5C8C3" +"F6198F511E69DEBE1257DF6692E9CB232A944F53521469224A3483CFC59AB7D59A5D" +"B6FFF620CF6359914B1905286D03#)\n" +" (e #010001#)\n" +" )\n" +" )\n" +; + +static const char sample_private_rsa_key_4096[] = +"(private-key \n" +" (rsa \n" +" (n #00C13612FBA6DA535403677448ACA42C7601A556D08678DB1C1AB209D0341E" +"B5D6F9FCA26EA57199471A7C07E5FF1CEEB91202241756E7B6EA55EFAEC44B17A897" +"3337302AE96FE76142C110B1EBB7B40987BA9243D475B2739F75A444ED875384C84E" +"0753178E88CFA2D674474D444098111E0F8931BCB30CA03BF40D6D4D56AFE256E777" +"84DB66E258C1C6853E714DC1329D3441F5134F1F294C2A006C340A9D2AF824CFF457" +"27C5830A0AF32B1EF5C2C3281B22B78DB79079CEB38B3F3E160C7D406B4FD1BC2D9D" +"B8B52C67031D6A30464C1098450080779A87F961AA6E9952A78F8739C2AAD7ACA210" +"AD8CA3D713BD612C770F10C50ACC6908EAD0A03FB988D9671A6F66A232FA1F5FE395" +"8DEE13A69689C5FE9E2724E1AAC21E64FB9F7D95F58452074A33ECA1367B8CE90DFB" +"04F4C4120BAEC00E25DAD6C4EC28C3B54C0F17D19DFB4AD6120412E3B2BC4965BC8D" +"B402E69A0F53D2EE885C7C36CFA9683E42CDE2F261241F7D0528A61FA64E3D1C0180" +"E9369F6329F1F4B04E868E8A28932346013DC3F3444D227553E8F7F90B99D4B807F5" +"CF2675FA492D4C10764407FE2E0A71410401CFFCB6580270033206E0FDB55733E6AE" +"AB32494726C136833D7A1C3AA5AA870825ACDBD707C86A1032E6064CB525B30945EE" +"FF2E65FA6E560BB1C031CFE2C8828ACD226990BFC103FDDD8CD5C7F231E69B64BD97" +"A50EE74877E1#)\n" +" (e #010001#)\n" +" (d #0AFCB70DF8CF8FE48655B2E9A7BE50499CFA038553D84F3C25127C77600CB7" +"B2296B77408BDCF96699E2CABF86B1E500C0E83E982B2E28708C01821D3C8349BD8B" +"6BE1D681898662B95D3EEF4C8F18F30A750ECDECFE37C4812165FD861FBCC7F4C211" +"D83A4DE0897A379ACB8136813BD9E157E87E8668B783972A8A3C43252101B7326E26" +"AFCEAFCEC9FA2EE7E84BE526D52F671422372222583D47E289FFBE84A131800D38E0" +"3F4849366A874863EE6193081EAC747D27549DEF5CDAF45364536D148D289EA708C1" +"E65F9CDE0850051317A3EBF22494A9A32FD9FBCBFED7C38E5972F5A869CC5E4F505B" +"D0D47258778657F524A0AE012EB2F1B9B4B27587E889B1D107036C59DCE206409B5E" +"92F157DE97EAF1C41076E6BB012744EE578E19B4F060714DC3131A867D52AF346270" +"E58BE5536FDC48CD59C2062898181617648605600B749DB99BBA2D1E6787FC6F0F2C" +"D6B1FA428C00ABD3B32C372949CB3C19E8D567F1DEEB3ACECFE05984040358E5ADDD" +"921168445DBCFB480CDEE907A418F98003658ACCF041258DCCEA7102CF04CD5A1CD9" +"EB62A7E825B386A0A9045E4E91E3013DAA4858295B092B74C261F4447DC5F1C3514F" +"4FC47E6BA13C15B4C817E3AF087AFC638FA4635FCF7F0CE42BC76BF0A7E11DC029A2" +"E2D70DFBAFFAA49AAD9B7199233A7B9F74D989182705A5C9F1FE35DF6ABDFB482FAA" +"A273E29849#)\n" +" (p #00C57614F493C9B2FA39F8388DD775189F625FBAA9F310FE9B29050259B719" +"D9601A070BE2CDDFFE29FB7C1EC8228D565330D2CC8DC8D8AB3B3BB52CFDC75700A5" +"E9EBEF69ACDA308E5620F1626B20C84B3B28FC0ABE5BBCE3080196C66736D2BFDDA9" +"17F179126D4C569C7EB48F9353DE62AE49A42A7C03F696D4666558C7A1E23EAA147D" +"7C0621DA5612DCB324355811CE65341168A0FD4159A3B080BE2F96CCFD7B7DEE71B5" +"6C04A68455736E35009D3BBF3231E027F7D5F67CDB57D81E215623B647160716920C" +"90F9685C53197146A4A8ACDBF563C9F944D503AE1916AB481DCCA4E843926D5A5335" +"871E8DC40181CD1453988539E7407EE5F2337A4EC535#)\n" +" (q #00FA7D71E8A70AA5CF33B125A29F1B8E15EB60B83F6E44879EBF33C7029ADE" +"1BA565BF4862AECA2E4EA39CF8F9621AE6CF5094697CA43AFCE73F633B89DEBD026E" +"218740089FEDE00E16004048216DBDF69F0D66BEAD543B4983ED512585E85D55A6C2" +"7399157BB6B83AAFFA084C2599011A0F2E2228E85AC5457EC84DCB0E20E7B065AEC0" +"CAD67DA1C9E3FC79120F9096A5F57B81D97B81961C85D158C6BF34C1922D8D8C4B6B" +"4A138933A882C657AE94EB7B804BB973291D58DB40B541F02366B42C61AECBB22901" +"0C17AA19E33682617C03B6B20D66335C79933CC25CA5594068BCC94F09BAC7955D79" +"C84EF0F4821127D959286A0F30D06B3B7B760950197D#)\n" +" (u #3C322D5C68FDF8CF92A68E1EE596FE7ACD1E8287EAC36545036133D29A19FB" +"341186FB83CA59FDEF7F9F25E97C3EB7843C2C7188019C910F1E68735D6FA29C8E04" +"4F4E1D39008C0C52FFA76E271FDA096ADC6A67957A833ECC19E0D02C7336C8DA548A" +"B66EFFE98D3429A8935EF62C56764939DE157BC264F3F37253AE3A86F253BD522A21" +"4C4CD4D48EE639520C58C427F2E1A413BDF696F9335194E81D5FD9C46397AB566754" +"2ADE3818A44818F0C037D5A9DA21CBF60C610AF44A32FF78D4DFC751878C77B8FE92" +"AE40D67E0BC3C52CAE2DCC6AB8E35D93B235CCA908822A0A272A9D068E6FE0447BD0" +"3D485565B133FF42E4BB780DD3EA819261B07F64A6#)\n" +" )\n" +" )\n" +; + +static const char sample_public_rsa_key_4096[] = +"(public-key \n" +" (rsa \n" +" (n #00C13612FBA6DA535403677448ACA42C7601A556D08678DB1C1AB209D0341E" +"B5D6F9FCA26EA57199471A7C07E5FF1CEEB91202241756E7B6EA55EFAEC44B17A897" +"3337302AE96FE76142C110B1EBB7B40987BA9243D475B2739F75A444ED875384C84E" +"0753178E88CFA2D674474D444098111E0F8931BCB30CA03BF40D6D4D56AFE256E777" +"84DB66E258C1C6853E714DC1329D3441F5134F1F294C2A006C340A9D2AF824CFF457" +"27C5830A0AF32B1EF5C2C3281B22B78DB79079CEB38B3F3E160C7D406B4FD1BC2D9D" +"B8B52C67031D6A30464C1098450080779A87F961AA6E9952A78F8739C2AAD7ACA210" +"AD8CA3D713BD612C770F10C50ACC6908EAD0A03FB988D9671A6F66A232FA1F5FE395" +"8DEE13A69689C5FE9E2724E1AAC21E64FB9F7D95F58452074A33ECA1367B8CE90DFB" +"04F4C4120BAEC00E25DAD6C4EC28C3B54C0F17D19DFB4AD6120412E3B2BC4965BC8D" +"B402E69A0F53D2EE885C7C36CFA9683E42CDE2F261241F7D0528A61FA64E3D1C0180" +"E9369F6329F1F4B04E868E8A28932346013DC3F3444D227553E8F7F90B99D4B807F5" +"CF2675FA492D4C10764407FE2E0A71410401CFFCB6580270033206E0FDB55733E6AE" +"AB32494726C136833D7A1C3AA5AA870825ACDBD707C86A1032E6064CB525B30945EE" +"FF2E65FA6E560BB1C031CFE2C8828ACD226990BFC103FDDD8CD5C7F231E69B64BD97" +"A50EE74877E1#)\n" +" (e #010001#)\n" +" )\n" +" )\n" +; + +static const char sample_private_dsa_key_2048[] = +"(private-key\n" +" (dsa\n" +" (p #00B54636673962B64F7DC23C71ACEF6E7331796F607560B194DFCC0CA370E858A365" + "A413152FB6EB8C664BD171AC316FE5B381CD084D07377571599880A068EF1382D85C" + "308B4E9DEAC12D66DE5C4A826EBEB5ED94A62E7301E18927E890589A2F230272A150" + "C118BC3DC2965AE0D05BE4F65C6137B2BA7EDABB192C3070D202C10AA3F534574970" + "71454DB8A73DDB6511A5BA98EF1450FD90DE5BAAFC9FD3AC22EBEA612DD075BB7405" + "D56866D125E33982C046808F7CEBA8E5C0B9F19A6FE451461660A1CBA9EF68891179" + "0256A573D3B8F35A5C7A0C6C31F2DB90E25A26845252AD9E485EF2D339E7B5890CD4" + "2F9C9F315ED409171EC35CA04CC06B275577B3#)\n" +" (q #00DA67989167FDAC4AE3DF9247A716859A30C0CF9C5A6DBA01EABA3481#)\n" +" (g #48E35DA584A089D05142AA63603FDB00D131B07A0781E2D5A8F9614D2B33D3E40A78" + "98A9E10CDBB612CF093F95A3E10D09566726F2C12823836B2D9CD974BB695665F3B3" + "5D219A9724B87F380BD5207EDA0AE38C79E8F18122C3F76E4CEB0ABED3250914987F" + "B30D4B9E19C04C28A5D4F45560AF586F6A1B41751EAD90AE7F044F4E2A4A50C1F508" + "4FC202463F478F678B9A19392F0D2961C5391C546EF365368BB46410C9C1CEE96E9F" + "0C953570C2ED06328B11C90E86E57CAA7FA5ABAA278E22A4C8C08E16EE59F484EC44" + "2CF55535BAA2C6BEA8833A555372BEFE1E665D3C7DAEF58061D5136331EF4EB61BC3" + "6EE4425A553AF8885FEA15A88135BE133520#)\n" +" (y #66E0D1A69D663466F8FEF2B7C0878DAC93C36A2FB2C05E0306A53B926021D4B92A1C" + "2FA6860061E88E78CBBBA49B0E12700F07DBF86F72CEB2927EDAC0C7E3969C3A47BB" + "4E0AE93D8BB3313E93CC7A72DFEEE442EFBC81B3B2AEC9D8DCBE21220FB760201D79" + "328C41C773866587A44B6954767D022A88072900E964089D9B17133603056C985C4F" + "8A0B648F297F8D2C3CB43E4371DC6002B5B12CCC085BDB2CFC5074A0587566187EE3" + "E11A2A459BD94726248BB8D6CC62938E11E284C2C183576FBB51749EB238C4360923" + "79C08CE1C8CD77EB57404CE9B4744395ACF721487450BADE3220576F2F816248B0A7" + "14A264330AECCB24DE2A1107847B23490897#)\n" +" (x #477BD14676E22563C5ABA68025CEBA2A48D485F5B2D4AD4C0EBBD6D0#)\n" +"))\n"; + +static const char sample_public_dsa_key_2048[] = +"(public-key\n" +" (dsa\n" +" (p #00B54636673962B64F7DC23C71ACEF6E7331796F607560B194DFCC0CA370E858A365" + "A413152FB6EB8C664BD171AC316FE5B381CD084D07377571599880A068EF1382D85C" + "308B4E9DEAC12D66DE5C4A826EBEB5ED94A62E7301E18927E890589A2F230272A150" + "C118BC3DC2965AE0D05BE4F65C6137B2BA7EDABB192C3070D202C10AA3F534574970" + "71454DB8A73DDB6511A5BA98EF1450FD90DE5BAAFC9FD3AC22EBEA612DD075BB7405" + "D56866D125E33982C046808F7CEBA8E5C0B9F19A6FE451461660A1CBA9EF68891179" + "0256A573D3B8F35A5C7A0C6C31F2DB90E25A26845252AD9E485EF2D339E7B5890CD4" + "2F9C9F315ED409171EC35CA04CC06B275577B3#)\n" +" (q #00DA67989167FDAC4AE3DF9247A716859A30C0CF9C5A6DBA01EABA3481#)\n" +" (g #48E35DA584A089D05142AA63603FDB00D131B07A0781E2D5A8F9614D2B33D3E40A78" + "98A9E10CDBB612CF093F95A3E10D09566726F2C12823836B2D9CD974BB695665F3B3" + "5D219A9724B87F380BD5207EDA0AE38C79E8F18122C3F76E4CEB0ABED3250914987F" + "B30D4B9E19C04C28A5D4F45560AF586F6A1B41751EAD90AE7F044F4E2A4A50C1F508" + "4FC202463F478F678B9A19392F0D2961C5391C546EF365368BB46410C9C1CEE96E9F" + "0C953570C2ED06328B11C90E86E57CAA7FA5ABAA278E22A4C8C08E16EE59F484EC44" + "2CF55535BAA2C6BEA8833A555372BEFE1E665D3C7DAEF58061D5136331EF4EB61BC3" + "6EE4425A553AF8885FEA15A88135BE133520#)\n" +" (y #66E0D1A69D663466F8FEF2B7C0878DAC93C36A2FB2C05E0306A53B926021D4B92A1C" + "2FA6860061E88E78CBBBA49B0E12700F07DBF86F72CEB2927EDAC0C7E3969C3A47BB" + "4E0AE93D8BB3313E93CC7A72DFEEE442EFBC81B3B2AEC9D8DCBE21220FB760201D79" + "328C41C773866587A44B6954767D022A88072900E964089D9B17133603056C985C4F" + "8A0B648F297F8D2C3CB43E4371DC6002B5B12CCC085BDB2CFC5074A0587566187EE3" + "E11A2A459BD94726248BB8D6CC62938E11E284C2C183576FBB51749EB238C4360923" + "79C08CE1C8CD77EB57404CE9B4744395ACF721487450BADE3220576F2F816248B0A7" + "14A264330AECCB24DE2A1107847B23490897#)\n" +"))\n"; + +static const char sample_private_dsa_key_3072[] = +"(private-key\n" +" (dsa\n" +" (p #00BA73E148AEA5E8B64878AF5BE712B8302B9671C5F3EEB7722A9D0D9868D048C938" + "877C91C335C7819292E69C7D34264F1578E32EC2DA8408DF75D0EB76E0D3030B84B5" + "62D8EF93AB53BAB6B8A5DE464F5CA87AEA43BDCF0FB0B7815AA3114CFC84FD916A83" + "B3D5FD78390189332232E9D037D215313FD002FF46C048B66703F87FAE092AAA0988" + "AC745336EBE672A01DEDBD52395783579B67CF3AE1D6F1602CCCB12154FA0E00AE46" + "0D9B289CF709194625BCB919B11038DEFC50ADBBA20C3F320078E4E9529B4F6848E2" + "AB5E6278DB961FE226F2EEBD201E071C48C5BEF98B4D9BEE42C1C7102D893EBF8902" + "D7A91266340AFD6CE1D09E52282FFF5B97EAFA3886A3FCF84FF76D1E06538D0D8E60" + "B3332145785E07D29A5965382DE3470D1D888447FA9C00A2373378FC3FA7B9F7D17E" + "95A6A5AE1397BE46D976EF2C96E89913AC4A09351CA661BF6F67E30407DA846946C7" + "62D9BAA6B77825097D3E7B886456BB32E3E74516BF3FD93D71B257AA8F723E01CE33" + "8015353D3778B02B892AF7#)\n" +" (q #00BFF3F3CC18FA018A5B8155A8695E1E4939660D5E4759322C39D50F3B93E5F68B#)\n" +" (g #6CCFD8219F5FCE8EF2BEF3262929787140847E38674B1EF8DB20255E212CB6330EC4" + "DFE8A26AB7ECC5760DEB9BBF59A2B2821D510F1868172222867558B8D204E889C474" + "7CA30FBF9D8CF41AE5D5BD845174641101593849FF333E6C93A6550931B2B9D56B98" + "9CAB01729D9D736FA6D24A74D2DDE1E9E648D141473E443DD6BBF0B3CAB64F9FE4FC" + "134B2EB57437789F75C744DF1FA67FA8A64603E5441BC7ECE29E00BDF262BDC81E8C" + "7330A18A412DE38E7546D342B89A0AF675A89E6BEF00540EB107A2FE74EA402B0D89" + "F5C02918DEEEAF8B8737AC866B09B50810AB8D8668834A1B9E1E53866E2B0A926FAB" + "120A0CDE5B3715FFFE6ACD1AB73588DCC1EC4CE9392FE57F8D1D35811200CB07A0E6" + "374E2C4B0AEB7E3D077B8545C0E438DCC0F1AE81E186930E99EBC5B91B77E92803E0" + "21602887851A4FFDB3A7896AC655A0901218C121C5CBB0931E7D5EAC243F37711B5F" + "D5A62B1B38A83F03D8F6703D8B98DF367FC8A76990335F62173A5391836F0F2413EC" + "4997AF9EB55C6660B01A#)\n" +" (y #2320B22434C5DB832B4EC267CC52E78DD5CCFA911E8F0804E7E7F32B186B2D4167AE" + "4AA6869822E76400492D6A193B0535322C72B0B7AA4A87E33044FDC84BE24C64A053" + "A37655EE9EABDCDC1FDF63F3F1C677CEB41595DF7DEFE9178D85A3D621B4E4775492" + "8C0A58D2458D06F9562E4DE2FE6129A64063A99E88E54485B97484A28188C4D33F15" + "DDC903B6CEA0135E3E3D27B4EA39319696305CE93D7BA7BE00367DBE3AAF43491E71" + "CBF254744A5567F5D70090D6139E0C990239627B3A1C5B20B6F9F6374B8D8D8A8997" + "437265BE1E3B4810D4B09254400DE287A0DFFBAEF339E48D422B1D41A37E642BC026" + "73314701C8FA9792845C129351A87A945A03E6C895860E51D6FB8B7340A94D1A8A7B" + "FA85AC83B4B14E73AB86CB96C236C8BFB0978B61B2367A7FE4F7891070F56C78D5DD" + "F5576BFE5BE4F333A4E2664E79528B3294907AADD63F4F2E7AA8147B928D8CD69765" + "3DB98C4297CB678046ED55C0DBE60BF7142C594603E4D705DC3D17270F9F086EC561" + "2703D518D8D49FF0EBE6#)\n" +" (x #00A9FFFC88E67D6F7B810E291C050BAFEA7FC4A75E8D2F16CFED3416FD77607232#)\n" +"))\n"; + +static const char sample_public_dsa_key_3072[] = +"(public-key\n" +" (dsa\n" +" (p #00BA73E148AEA5E8B64878AF5BE712B8302B9671C5F3EEB7722A9D0D9868D048C938" + "877C91C335C7819292E69C7D34264F1578E32EC2DA8408DF75D0EB76E0D3030B84B5" + "62D8EF93AB53BAB6B8A5DE464F5CA87AEA43BDCF0FB0B7815AA3114CFC84FD916A83" + "B3D5FD78390189332232E9D037D215313FD002FF46C048B66703F87FAE092AAA0988" + "AC745336EBE672A01DEDBD52395783579B67CF3AE1D6F1602CCCB12154FA0E00AE46" + "0D9B289CF709194625BCB919B11038DEFC50ADBBA20C3F320078E4E9529B4F6848E2" + "AB5E6278DB961FE226F2EEBD201E071C48C5BEF98B4D9BEE42C1C7102D893EBF8902" + "D7A91266340AFD6CE1D09E52282FFF5B97EAFA3886A3FCF84FF76D1E06538D0D8E60" + "B3332145785E07D29A5965382DE3470D1D888447FA9C00A2373378FC3FA7B9F7D17E" + "95A6A5AE1397BE46D976EF2C96E89913AC4A09351CA661BF6F67E30407DA846946C7" + "62D9BAA6B77825097D3E7B886456BB32E3E74516BF3FD93D71B257AA8F723E01CE33" + "8015353D3778B02B892AF7#)\n" +" (q #00BFF3F3CC18FA018A5B8155A8695E1E4939660D5E4759322C39D50F3B93E5F68B#)\n" +" (g #6CCFD8219F5FCE8EF2BEF3262929787140847E38674B1EF8DB20255E212CB6330EC4" + "DFE8A26AB7ECC5760DEB9BBF59A2B2821D510F1868172222867558B8D204E889C474" + "7CA30FBF9D8CF41AE5D5BD845174641101593849FF333E6C93A6550931B2B9D56B98" + "9CAB01729D9D736FA6D24A74D2DDE1E9E648D141473E443DD6BBF0B3CAB64F9FE4FC" + "134B2EB57437789F75C744DF1FA67FA8A64603E5441BC7ECE29E00BDF262BDC81E8C" + "7330A18A412DE38E7546D342B89A0AF675A89E6BEF00540EB107A2FE74EA402B0D89" + "F5C02918DEEEAF8B8737AC866B09B50810AB8D8668834A1B9E1E53866E2B0A926FAB" + "120A0CDE5B3715FFFE6ACD1AB73588DCC1EC4CE9392FE57F8D1D35811200CB07A0E6" + "374E2C4B0AEB7E3D077B8545C0E438DCC0F1AE81E186930E99EBC5B91B77E92803E0" + "21602887851A4FFDB3A7896AC655A0901218C121C5CBB0931E7D5EAC243F37711B5F" + "D5A62B1B38A83F03D8F6703D8B98DF367FC8A76990335F62173A5391836F0F2413EC" + "4997AF9EB55C6660B01A#)\n" +" (y #2320B22434C5DB832B4EC267CC52E78DD5CCFA911E8F0804E7E7F32B186B2D4167AE" + "4AA6869822E76400492D6A193B0535322C72B0B7AA4A87E33044FDC84BE24C64A053" + "A37655EE9EABDCDC1FDF63F3F1C677CEB41595DF7DEFE9178D85A3D621B4E4775492" + "8C0A58D2458D06F9562E4DE2FE6129A64063A99E88E54485B97484A28188C4D33F15" + "DDC903B6CEA0135E3E3D27B4EA39319696305CE93D7BA7BE00367DBE3AAF43491E71" + "CBF254744A5567F5D70090D6139E0C990239627B3A1C5B20B6F9F6374B8D8D8A8997" + "437265BE1E3B4810D4B09254400DE287A0DFFBAEF339E48D422B1D41A37E642BC026" + "73314701C8FA9792845C129351A87A945A03E6C895860E51D6FB8B7340A94D1A8A7B" + "FA85AC83B4B14E73AB86CB96C236C8BFB0978B61B2367A7FE4F7891070F56C78D5DD" + "F5576BFE5BE4F333A4E2664E79528B3294907AADD63F4F2E7AA8147B928D8CD69765" + "3DB98C4297CB678046ED55C0DBE60BF7142C594603E4D705DC3D17270F9F086EC561" + "2703D518D8D49FF0EBE6#)\n" +"))\n"; + +/* Keys are kept as samples instead of being generated, as prime search + time varies too much for slope measurement. */ +static const struct +{ + const char *name; + const char *sec_key; + const char *pub_key; + unsigned int value_bits; /* Size of value to be signed. */ + int sign_cost; /* Scales down signing repetitions. */ +} pk_algos[] = { +#if USE_RSA + { "RSA-2048", sample_private_rsa_key_2048, sample_public_rsa_key_2048, + 2040, 4 }, + { "RSA-3072", sample_private_rsa_key_3072, sample_public_rsa_key_3072, + 3064, 8 }, + { "RSA-4096", sample_private_rsa_key_4096, sample_public_rsa_key_4096, + 4088, 16 }, +#endif +#if USE_DSA + { "DSA-2048", sample_private_dsa_key_2048, sample_public_dsa_key_2048, + 224, 1 }, + { "DSA-3072", sample_private_dsa_key_3072, sample_public_dsa_key_3072, + 256, 2 }, +#endif + { NULL, NULL, NULL, 0, 1 } +}; + + +static const char * +pk_algo_name (int algo) +{ + if (algo < 0 || algo >= __MAX_PK_ALGO) + return NULL; + + return pk_algos[algo].name; +} + + +static int +pk_map_name (const char *name) +{ + int i; + + for (i = 0; i < __MAX_PK_ALGO; i++) + if (!strcmp (pk_algos[i].name, name)) + return i; + + return -1; +} + + +static int +bench_pk_init (struct bench_obj *obj) +{ + struct bench_pk_oper *oper = obj->priv; + struct bench_pk_hd *hd; + gcry_mpi_t x; + gpg_error_t err; + int cost; + + cost = oper->oper == PK_OPER_SIGN ? pk_algos[oper->algo].sign_cost : 1; + + obj->min_bufsize = 1; + obj->max_bufsize = 4; + obj->step_size = 1; + obj->num_measure_repetitions = + num_measurement_repetitions / obj->max_bufsize / cost; + if (obj->num_measure_repetitions == 0) + obj->num_measure_repetitions = 1; + + hd = calloc (1, sizeof(*hd)); + if (!hd) + return -1; + + err = gcry_sexp_sscan (&hd->sec_key, NULL, pk_algos[oper->algo].sec_key, + strlen (pk_algos[oper->algo].sec_key)); + if (!err) + err = gcry_sexp_sscan (&hd->pub_key, NULL, pk_algos[oper->algo].pub_key, + strlen (pk_algos[oper->algo].pub_key)); + if (err) + { + fprintf (stderr, PGM ": gcry_sexp_sscan failed: %s\n", + gpg_strerror (err)); + exit (1); + } + + x = gcry_mpi_new (pk_algos[oper->algo].value_bits); + gcry_mpi_randomize (x, pk_algos[oper->algo].value_bits, GCRY_WEAK_RANDOM); + err = gcry_sexp_build (&hd->data, NULL, "(data (flags raw) (value %m))", x); + gcry_mpi_release (x); + if (err) + { + fprintf (stderr, PGM ": gcry_sexp_build failed: %s\n", + gpg_strerror (err)); + exit (1); + } + + err = gcry_pk_sign (&hd->sig, hd->data, hd->sec_key); + if (err) + { + fprintf (stderr, PGM ": gcry_pk_sign failed: %s\n", + gpg_strerror (err)); + exit (1); + } + + obj->hd = hd; + return 0; +} + + +static void +bench_pk_free (struct bench_obj *obj) +{ + struct bench_pk_hd *hd = obj->hd; + + gcry_sexp_release (hd->sig); + gcry_sexp_release (hd->data); + gcry_sexp_release (hd->pub_key); + gcry_sexp_release (hd->sec_key); + free (hd); + obj->hd = NULL; +} + + +static void +bench_pk_sign_do_bench (struct bench_obj *obj, void *buf, size_t num_iter) +{ + struct bench_pk_hd *hd = obj->hd; + gcry_sexp_t sig; + gpg_error_t err; + size_t i; + + (void)buf; + + for (i = 0; i < num_iter; i++) + { + err = gcry_pk_sign (&sig, hd->data, hd->sec_key); + if (err) + { + fprintf (stderr, PGM ": gcry_pk_sign failed: %s\n", + gpg_strerror (err)); + exit (1); + } + gcry_sexp_release (sig); + } +} + + +static void +bench_pk_verify_do_bench (struct bench_obj *obj, void *buf, size_t num_iter) +{ + struct bench_pk_hd *hd = obj->hd; + gpg_error_t err; + size_t i; + + (void)buf; + + for (i = 0; i < num_iter; i++) + { + err = gcry_pk_verify (hd->sig, hd->data, hd->pub_key); + if (err) + { + fprintf (stderr, PGM ": gcry_pk_verify failed: %s\n", + gpg_strerror (err)); + exit (1); + } + } +} + + +static struct bench_ops pk_sign_ops = { + &bench_pk_init, + &bench_pk_free, + &bench_pk_sign_do_bench +}; + +static struct bench_ops pk_verify_ops = { + &bench_pk_init, + &bench_pk_free, + &bench_pk_verify_do_bench +}; + +static struct bench_pk_oper pk_operations[] = { + { PK_OPER_SIGN, "sign", &pk_sign_ops }, + { PK_OPER_VERIFY, "verify", &pk_verify_ops }, + { 0, NULL, NULL } +}; + + +static void +cipher_pk_one (enum bench_pk_algo algo, struct bench_pk_oper *poper) +{ + struct bench_pk_oper oper = *poper; + struct bench_obj obj = { 0 }; + double result; + + oper.algo = algo; + + bench_print_mode (14, oper.name); + + obj.ops = oper.ops; + obj.priv = &oper; + + result = do_slope_benchmark (&obj); + bench_print_result_nsec_per_iteration (result); +} + + +static void +_pk_bench (int algo) +{ + int i; + + bench_print_header_nsec_per_iteration (14, pk_algo_name (algo)); + + for (i = 0; pk_operations[i].name; i++) + cipher_pk_one (algo, &pk_operations[i]); + + bench_print_footer (14); +} + + +void +pk_bench (char **argv, int argc) +{ + int i, algo; + + bench_print_section ("pk", "Public-key"); + + if (argv && argc) + { + for (i = 0; i < argc; i++) + { + algo = pk_map_name (argv[i]); + if (algo >= 0) + _pk_bench (algo); + } + } + else + { + for (i = 0; i < __MAX_PK_ALGO; i++) + _pk_bench (i); + } +} + + /************************************************************ ECC benchmarks. */ #if USE_ECC @@ -3854,7 +4496,8 @@ void print_help (void) { static const char *help_lines[] = { - "usage: bench-slope [options] [hash|mac|cipher|kdf|ecc|pq|mpi [algonames]]", + "usage: bench-slope [options] [hash|mac|cipher|kdf|pk|ecc|pq|mpi", + " [algonames]]", "", " options:", " --cpu-mhz Set CPU speed for calculating cycles", @@ -4067,6 +4710,7 @@ main (int argc, char **argv) mac_bench (NULL, 0); cipher_bench (NULL, 0); kdf_bench (NULL, 0); + pk_bench (NULL, 0); ecc_bench (NULL, 0); pq_bench (NULL, 0); mpi_bench (NULL, 0); @@ -4103,6 +4747,14 @@ main (int argc, char **argv) warm_up_cpu (); kdf_bench ((argc == 0) ? NULL : argv, argc); } + else if (!strcmp (*argv, "pk")) + { + argc--; + argv++; + + warm_up_cpu (); + pk_bench ((argc == 0) ? NULL : argv, argc); + } else if (!strcmp (*argv, "ecc")) { argc--; -- 2.53.0 From jussi.kivilinna at iki.fi Sun Aug 2 11:55:15 2026 From: jussi.kivilinna at iki.fi (Jussi Kivilinna) Date: Sun, 2 Aug 2026 12:55:15 +0300 Subject: [PATCH 10/10] dilithium: use strong random for signature nonce In-Reply-To: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> References: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> Message-ID: <20260802095515.2729589-10-jussi.kivilinna@iki.fi> * cipher/pubkey-dilithium.c (randombytes): Add 'level' parameter. (mldsa_generate): Use GCRY_VERY_STRONG_RANDOM for key seed. (mldsa_sign): Use GCRY_STRONG_RANDOM for per signature value. -- 'randombytes' used GCRY_VERY_STRONG_RANDOM for both key seed and hedged signing nonce. Nonce is per signature ephemeral value, same as ECDSA, DSA and GOST nonces, all of which use GCRY_STRONG_RANDOM. Level 2 request forces jitterentropy gathering costing about 2 ms, which dominated signing time. Key seed keeps level 2. Benchmark on AMD Ryzen 9 9950X3D, signing usec/operation, quick random disabled: before after speedup ML-DSA-44 2108.52 140.37 15.02x ML-DSA-65 2144.04 223.50 9.59x ML-DSA-87 2234.45 272.85 8.19x Signed-off-by: Jussi Kivilinna --- cipher/pubkey-dilithium.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/cipher/pubkey-dilithium.c b/cipher/pubkey-dilithium.c index 9d13aad3..92c64d91 100644 --- a/cipher/pubkey-dilithium.c +++ b/cipher/pubkey-dilithium.c @@ -121,9 +121,10 @@ mldsa_compute_keygrip (gcry_md_hd_t md, gcry_sexp_t keyparam) static void -randombytes (unsigned char *out, size_t outlen) +randombytes (unsigned char *out, size_t outlen, + enum gcry_random_level level) { - _gcry_randomize (out, outlen, GCRY_VERY_STRONG_RANDOM); + _gcry_randomize (out, outlen, level); } static gcry_err_code_t @@ -151,7 +152,8 @@ mldsa_generate (const gcry_sexp_t genparms, gcry_sexp_t *r_skey) rc = sexp_extract_param (genparms, NULL, "/S", &seed_mpi, NULL); if (rc == GPG_ERR_NOT_FOUND) { - randombytes (seed, SEEDBYTES); + /* Long term key material. */ + randombytes (seed, SEEDBYTES, GCRY_VERY_STRONG_RANDOM); rc = 0; } else if (rc) @@ -255,7 +257,10 @@ mldsa_sign (gcry_sexp_t *r_sig, gcry_sexp_t s_data, gcry_sexp_t keyparms) memcpy (rnd, ctx.rnd, RNDBYTES); } else - randombytes (rnd, RNDBYTES); + { + /* Per signature value, same level as ECDSA nonce. */ + randombytes (rnd, RNDBYTES, GCRY_STRONG_RANDOM); + } if (ctx.flags & PUBKEY_FLAG_NO_PREFIX) rc = dilithium_sign (info->algo, sig, info->sig_len, data, data_len, NULL, -1, sk, rnd); -- 2.53.0 From jussi.kivilinna at iki.fi Sun Aug 2 11:55:11 2026 From: jussi.kivilinna at iki.fi (Jussi Kivilinna) Date: Sun, 2 Aug 2026 12:55:11 +0300 Subject: [PATCH 06/10] sntrup761: read random values with single call In-Reply-To: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> References: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> Message-ID: <20260802095515.2729589-6-jussi.kivilinna@iki.fi> * cipher/sntrup761.c: Include "bithelp.h". (urandom32): Remove. (Short_random, Small_random): Fill 32-bit array with one call to random function instead of reading four bytes at a time. -- 'urandom32' requested four bytes per call and both callers looped over all p coefficients, so generating one polynomial made 761 separate calls to the random function. With libgcrypt CSPRNG each of those takes pool lock, pool mix and hash operation, which dominated encapsulation time. Benchmark on AMD Ryzen 9 9950X3D, SNTRUP761 usec/operation, quick random disabled ('base' being state before this patch series): | base before after speedup total keygen | 37971.9 9480.2 2900.4 3.27x 13.09x encap | 7355.8 4468.2 1184.7 3.77x 6.21x decap | 12207.1 3325.4 3322.4 1.00x 3.67x Signed-off-by: Jussi Kivilinna --- cipher/sntrup761.c | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/cipher/sntrup761.c b/cipher/sntrup761.c index 11366f8f..94229d79 100644 --- a/cipher/sntrup761.c +++ b/cipher/sntrup761.c @@ -34,6 +34,7 @@ #endif #include "sntrup761.h" +#include "bithelp.h" #include "const-time.h" /* from supercop-20201130/crypto_sort/int32/portable4/int32_minmax.inc */ @@ -698,38 +699,27 @@ Hash_prefix (unsigned char *out, int b, const unsigned char *in, int inlen) /* ----- higher-level randomness */ -static uint32_t -urandom32 (void *random_ctx, sntrup761_random_func * random) -{ - unsigned char c[4]; - uint32_t out[4]; - - random (random_ctx, 4, c); - out[0] = (uint32_t) c[0]; - out[1] = ((uint32_t) c[1]) << 8; - out[2] = ((uint32_t) c[2]) << 16; - out[3] = ((uint32_t) c[3]) << 24; - return out[0] + out[1] + out[2] + out[3]; -} - static void Short_random (small * out, void *random_ctx, sntrup761_random_func * random) { uint32_t L[p]; int i; + random (random_ctx, sizeof (L), (uint8_t *)L); for (i = 0; i < p; ++i) - L[i] = urandom32 (random_ctx, random); + L[i] = le_bswap32 (L[i]); Short_fromlist (out, L); } static void Small_random (small * out, void *random_ctx, sntrup761_random_func * random) { + uint32_t L[p]; int i; + random (random_ctx, sizeof (L), (uint8_t *)L); for (i = 0; i < p; ++i) - out[i] = (((urandom32 (random_ctx, random) & 0x3fffffff) * 3) >> 30) - 1; + out[i] = (((le_bswap32 (L[i]) & 0x3fffffff) * 3) >> 30) - 1; } /* ----- Streamlined NTRU Prime Core */ -- 2.53.0 From jussi.kivilinna at iki.fi Sun Aug 2 11:55:06 2026 From: jussi.kivilinna at iki.fi (Jussi Kivilinna) Date: Sun, 2 Aug 2026 12:55:06 +0300 Subject: [PATCH 01/10] bench-slope: add option to disable quick random generation Message-ID: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> * tests/bench-slope.c (print_help): Add '--no-quick-rng'. (main): Add '--no-quick-rng' option and initialize RNG before benchmarking. -- Quick random skips entropy gathering, which hides cost of GCRY_VERY_STRONG_RANDOM requests. Such request takes about 2 ms and dominates ECC key generation, so measuring what applications actually get needs a way to turn quick random off. Default stays as before, since otherwise entropy gathering swamps the algorithms. Random pool is initialized up front because fast random poll done on handle creation is no-operation until pool exists, which would make first measurements differ from rest. Signed-off-by: Jussi Kivilinna --- tests/bench-slope.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/bench-slope.c b/tests/bench-slope.c index 4b14541b..831fe545 100644 --- a/tests/bench-slope.c +++ b/tests/bench-slope.c @@ -3265,6 +3265,7 @@ print_help (void) " --repetitions Use N repetitions (default " STR2(NUM_MEASUREMENT_REPETITIONS) ")", " --unaligned Use unaligned input buffers.", + " --no-quick-rng Use default random number generation", " --csv Use CSV output format", NULL }; @@ -3297,6 +3298,8 @@ int main (int argc, char **argv) { int last_argc = -1; + int no_quick_rng = 0; + char tmp[4]; if (argc) { @@ -3351,6 +3354,12 @@ main (int argc, char **argv) argc--; argv++; } + else if (!strcmp (*argv, "--no-quick-rng")) + { + no_quick_rng = 1; + argc--; + argv++; + } else if (!strcmp (*argv, "--unaligned")) { unaligned_mode = 1; @@ -3427,7 +3436,12 @@ main (int argc, char **argv) xgcry_control ((GCRYCTL_DISABLE_SECMEM, 0)); xgcry_control ((GCRYCTL_INITIALIZATION_FINISHED, 0)); - xgcry_control ((GCRYCTL_ENABLE_QUICK_RANDOM, 0)); + + if (!no_quick_rng) + xgcry_control ((GCRYCTL_ENABLE_QUICK_RANDOM, 0)); + + /* Fill random pool so that first measurement is not different. */ + gcry_randomize (tmp, sizeof(tmp), GCRY_STRONG_RANDOM); if (gcry_fips_mode_active ()) in_fips_mode = 1; -- 2.53.0 From jussi.kivilinna at iki.fi Sun Aug 2 11:55:07 2026 From: jussi.kivilinna at iki.fi (Jussi Kivilinna) Date: Sun, 2 Aug 2026 12:55:07 +0300 Subject: [PATCH 02/10] bench-slope: add post-quantum algorithm benchmarking In-Reply-To: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> References: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> Message-ID: <20260802095515.2729589-2-jussi.kivilinna@iki.fi> * tests/bench-slope.c (bench_pq_algo, bench_pq_operation, bench_pq_oper) (bench_pq_kem_hd, bench_pq_sig_hd, pq_algos, PQ_SIG_SEED_LEN) (PQ_SIG_MSG_LEN, pq_algo_name, pq_algo_is_kem, pq_map_name) (pq_setup_obj, bench_pq_kem_keypair, bench_pq_kem_init) (bench_pq_kem_free, bench_pq_kem_encapsulate) (bench_pq_kem_keygen_do_bench, bench_pq_kem_encap_do_bench) (bench_pq_kem_decap_do_bench, bench_pq_sig_init, bench_pq_sig_free) (bench_pq_sig_keygen, bench_pq_sig_keygen_do_bench) (bench_pq_sig_sign_do_bench, bench_pq_sig_verify_do_bench) (pq_kem_keygen_ops, pq_kem_encap_ops, pq_kem_decap_ops) (pq_sig_keygen_ops, pq_sig_sign_ops, pq_sig_verify_ops) (pq_kem_operations, pq_sig_operations, cipher_pq_one, _pq_bench) (pq_bench, include_slow, bench_print_result_skipped): New. (print_help): Add mention of 'pq' and '--include-slow'. (main): Add "pq" tests and '--include-slow' option. -- Patch adds ML-KEM (keygen/encap/decap), ML-DSA (keygen/sign/verify), SNTRUP761 and Classic McEliece benchmarking for bench-slope. SNTRUP761 and Classic McEliece need tens to hundreds of milliseconds per operation, which would make default run take minutes. Therefore 'pq_algos' carries per algorithm factors for scaling down measurement repetitions, separately for key generation and for the other operations. Classic McEliece key generation takes about 160 ms even so, and runs only when algorithm is named on command-line or when '--include-slow' is given. KEM key pair is also generated once at setup instead of on every measurement round. $ tests/bench-slope pq Post-quantum: ML-KEM-512 | nanosecs/iter cycles/iter keygen | 22673 - encap | 23719 - decap | 24042 - = ML-KEM-768 | nanosecs/iter cycles/iter keygen | 37913 - encap | 37786 - decap | 40124 - = ML-KEM-1024 | nanosecs/iter cycles/iter keygen | 58541 - encap | 56363 - decap | 61300 - = sntrup761 | nanosecs/iter cycles/iter keygen | 37959347 - encap | 7353990 - decap | 12236447 - = cm6688128f | nanosecs/iter cycles/iter keygen | <> - encap | 41016 - decap | 276094 - = ML-DSA-44 | nanosecs/iter cycles/iter keygen | 58239 - sign | 200785 - verify | 67640 - = ML-DSA-65 | nanosecs/iter cycles/iter keygen | 97839 - sign | 304236 - verify | 74675 - = ML-DSA-87 | nanosecs/iter cycles/iter keygen | 165386 - sign | 356474 - verify | 174372 - = Signed-off-by: Jussi Kivilinna --- tests/bench-slope.c | 622 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 621 insertions(+), 1 deletion(-) diff --git a/tests/bench-slope.c b/tests/bench-slope.c index 831fe545..29760227 100644 --- a/tests/bench-slope.c +++ b/tests/bench-slope.c @@ -48,6 +48,7 @@ static int verbose; static int csv_mode; +static int include_slow; static int unaligned_mode; static int num_measurement_repetitions; @@ -877,6 +878,16 @@ bench_print_result_nsec_per_iteration (double nsecs_per_iteration) } } + +static void +bench_print_result_skipped (void) +{ + if (auto_ghz) + printf ("%14s %13s %9s\n", "<>", "-", "-"); + else + printf ("%14s %13s\n", "<>", "-"); +} + static void bench_print_section (const char *section_name, const char *print_name) { @@ -2951,6 +2962,595 @@ ecc_bench (char **argv, int argc) #endif } +/************************************************************ PQC benchmarks. */ + +enum bench_pq_algo +{ +#if USE_KYBER + PQ_ALGO_MLKEM512 = 0, + PQ_ALGO_MLKEM768, + PQ_ALGO_MLKEM1024, +#endif + PQ_ALGO_SNTRUP761, + PQ_ALGO_CM6688128F, +#if USE_DILITHIUM + PQ_ALGO_MLDSA44, + PQ_ALGO_MLDSA65, + PQ_ALGO_MLDSA87, +#endif + __MAX_PQ_ALGO +}; + +enum bench_pq_operation +{ + PQ_OPER_KEYGEN = 0, + PQ_OPER_ENCAP, + PQ_OPER_DECAP, + PQ_OPER_SIGN, + PQ_OPER_VERIFY, + __MAX_PQ_OPER +}; + +struct bench_pq_oper +{ + enum bench_pq_operation oper; + const char *name; + struct bench_ops *ops; + + enum bench_pq_algo algo; +}; + +struct bench_pq_kem_hd +{ + int algo; + size_t pubkey_len; + size_t seckey_len; + size_t ciph_len; + size_t shared_len; + unsigned char *pubkey; + unsigned char *seckey; + unsigned char *ciph; + unsigned char *shared; +}; + +struct bench_pq_sig_hd +{ + gcry_sexp_t key_spec; + gcry_sexp_t data; + gcry_sexp_t pub_key; + gcry_sexp_t sec_key; + gcry_sexp_t sig; +}; + +/* KEM algorithms use 'kem_algo', signature algorithms use 'sig_name'. */ +static const struct +{ + const char *name; + int kem_algo; + const char *sig_name; + size_t pubkey_len; + size_t seckey_len; + size_t ciph_len; + size_t shared_len; + int keygen_cost; /* Scales down key generation repetitions. */ + int oper_cost; /* Scales down other operation repetitions. */ + int keygen_on_demand; /* Run key generation only when named. */ +} pq_algos[] = { +#if USE_KYBER + { "ML-KEM-512", GCRY_KEM_MLKEM512, NULL, + GCRY_KEM_MLKEM512_PUBKEY_LEN, GCRY_KEM_MLKEM512_SECKEY_LEN, + GCRY_KEM_MLKEM512_ENCAPS_LEN, GCRY_KEM_MLKEM512_SHARED_LEN, 1, 1, 0 }, + { "ML-KEM-768", GCRY_KEM_MLKEM768, NULL, + GCRY_KEM_MLKEM768_PUBKEY_LEN, GCRY_KEM_MLKEM768_SECKEY_LEN, + GCRY_KEM_MLKEM768_ENCAPS_LEN, GCRY_KEM_MLKEM768_SHARED_LEN, 1, 1, 0 }, + { "ML-KEM-1024", GCRY_KEM_MLKEM1024, NULL, + GCRY_KEM_MLKEM1024_PUBKEY_LEN, GCRY_KEM_MLKEM1024_SECKEY_LEN, + GCRY_KEM_MLKEM1024_ENCAPS_LEN, GCRY_KEM_MLKEM1024_SHARED_LEN, 1, 1, 0 }, +#endif + { "sntrup761", GCRY_KEM_SNTRUP761, NULL, + GCRY_KEM_SNTRUP761_PUBKEY_LEN, GCRY_KEM_SNTRUP761_SECKEY_LEN, + GCRY_KEM_SNTRUP761_ENCAPS_LEN, GCRY_KEM_SNTRUP761_SHARED_LEN, 16, 4, 0 }, + { "cm6688128f", GCRY_KEM_CM6688128F, NULL, + GCRY_KEM_CM6688128F_PUBKEY_LEN, GCRY_KEM_CM6688128F_SECKEY_LEN, + GCRY_KEM_CM6688128F_ENCAPS_LEN, GCRY_KEM_CM6688128F_SHARED_LEN, 16, 1, 1 }, +#if USE_DILITHIUM + { "ML-DSA-44", -1, "dilithium2", 0, 0, 0, 0, 1, 1, 0 }, + { "ML-DSA-65", -1, "dilithium3", 0, 0, 0, 0, 1, 1, 0 }, + { "ML-DSA-87", -1, "dilithium5", 0, 0, 0, 0, 1, 1, 0 }, +#endif + { NULL, -1, NULL, 0, 0, 0, 0, 1, 1, 0 } +}; + +#define PQ_SIG_SEED_LEN 32 +#define PQ_SIG_MSG_LEN 32 + + +static const char * +pq_algo_name (int algo) +{ + if (algo < 0 || algo >= __MAX_PQ_ALGO) + return NULL; + + return pq_algos[algo].name; +} + +static int +pq_algo_is_kem (int algo) +{ + return pq_algos[algo].kem_algo >= 0; +} + +static int +pq_map_name (const char *name) +{ + int i; + + for (i = 0; i < __MAX_PQ_ALGO; i++) + { + if (strcmp (pq_algo_name (i), name) == 0) + return i; + } + + return -1; +} + +static void +pq_setup_obj (struct bench_obj *obj) +{ + struct bench_pq_oper *oper = obj->priv; + int cost; + + if (oper->oper == PQ_OPER_KEYGEN) + cost = pq_algos[oper->algo].keygen_cost; + else + cost = pq_algos[oper->algo].oper_cost; + + obj->min_bufsize = 1; + obj->max_bufsize = 4; + obj->step_size = 1; + obj->num_measure_repetitions = + num_measurement_repetitions / obj->max_bufsize / cost; + + while (obj->num_measure_repetitions == 0) + { + if (obj->max_bufsize == 2) + { + obj->num_measure_repetitions = 1; + } + else + { + obj->max_bufsize--; + obj->num_measure_repetitions = + num_measurement_repetitions / obj->max_bufsize / cost; + } + } +} + + +static void +bench_pq_kem_keypair (struct bench_pq_kem_hd *hd) +{ + gpg_error_t err; + + err = gcry_kem_keypair (hd->algo, hd->pubkey, hd->pubkey_len, + hd->seckey, hd->seckey_len); + if (err) + { + fprintf (stderr, PGM ": gcry_kem_keypair failed: %s\n", + gpg_strerror (err)); + exit (1); + } +} + +static int +bench_pq_kem_init (struct bench_obj *obj) +{ + struct bench_pq_oper *oper = obj->priv; + struct bench_pq_kem_hd *hd; + + pq_setup_obj (obj); + + hd = calloc (1, sizeof(*hd)); + if (!hd) + return -1; + + hd->algo = pq_algos[oper->algo].kem_algo; + hd->pubkey_len = pq_algos[oper->algo].pubkey_len; + hd->seckey_len = pq_algos[oper->algo].seckey_len; + hd->ciph_len = pq_algos[oper->algo].ciph_len; + hd->shared_len = pq_algos[oper->algo].shared_len; + + hd->pubkey = calloc (1, hd->pubkey_len); + hd->seckey = calloc (1, hd->seckey_len); + hd->ciph = calloc (1, hd->ciph_len); + hd->shared = calloc (1, hd->shared_len); + if (!hd->pubkey || !hd->seckey || !hd->ciph || !hd->shared) + { + free (hd->shared); + free (hd->ciph); + free (hd->seckey); + free (hd->pubkey); + free (hd); + return -1; + } + + obj->hd = hd; + bench_pq_kem_keypair (hd); + return 0; +} + +static void +bench_pq_kem_free (struct bench_obj *obj) +{ + struct bench_pq_kem_hd *hd = obj->hd; + + free (hd->shared); + free (hd->ciph); + free (hd->seckey); + free (hd->pubkey); + free (hd); + obj->hd = NULL; +} + +static void +bench_pq_kem_encapsulate (struct bench_pq_kem_hd *hd) +{ + gpg_error_t err; + + err = gcry_kem_encap (hd->algo, hd->pubkey, hd->pubkey_len, + hd->ciph, hd->ciph_len, hd->shared, hd->shared_len, + NULL, 0); + if (err) + { + fprintf (stderr, PGM ": gcry_kem_encap failed: %s\n", + gpg_strerror (err)); + exit (1); + } +} + +static void +bench_pq_kem_keygen_do_bench (struct bench_obj *obj, void *buf, + size_t num_iter) +{ + struct bench_pq_kem_hd *hd = obj->hd; + size_t i; + + (void)buf; + + for (i = 0; i < num_iter; i++) + bench_pq_kem_keypair (hd); +} + +static void +bench_pq_kem_encap_do_bench (struct bench_obj *obj, void *buf, size_t num_iter) +{ + struct bench_pq_kem_hd *hd = obj->hd; + size_t i; + + (void)buf; + + for (i = 0; i < num_iter; i++) + bench_pq_kem_encapsulate (hd); +} + +static void +bench_pq_kem_decap_do_bench (struct bench_obj *obj, void *buf, size_t num_iter) +{ + struct bench_pq_kem_hd *hd = obj->hd; + gpg_error_t err; + size_t i; + + (void)buf; + + bench_pq_kem_encapsulate (hd); + + for (i = 0; i < num_iter; i++) + { + err = gcry_kem_decap (hd->algo, hd->seckey, hd->seckey_len, + hd->ciph, hd->ciph_len, hd->shared, hd->shared_len, + NULL, 0); + if (err) + { + fprintf (stderr, PGM ": gcry_kem_decap failed: %s\n", + gpg_strerror (err)); + exit (1); + } + } +} + + +static int +bench_pq_sig_init (struct bench_obj *obj) +{ + struct bench_pq_oper *oper = obj->priv; + struct bench_pq_sig_hd *hd; + unsigned char seed[PQ_SIG_SEED_LEN]; + unsigned char msg[PQ_SIG_MSG_LEN]; + gpg_error_t err; + + pq_setup_obj (obj); + + hd = calloc (1, sizeof(*hd)); + if (!hd) + return -1; + + gcry_randomize (seed, sizeof(seed), GCRY_WEAK_RANDOM); + gcry_randomize (msg, sizeof(msg), GCRY_WEAK_RANDOM); + + err = gcry_sexp_build (&hd->key_spec, NULL, "(genkey(%s(S%b)))", + pq_algos[oper->algo].sig_name, + (int)sizeof(seed), seed, NULL); + if (!err) + err = gcry_sexp_build (&hd->data, NULL, + "(data(raw)(flags no-prefix)(value%b))", + (int)sizeof(msg), msg, NULL); + if (err) + { + fprintf (stderr, PGM ": gcry_sexp_build failed: %s\n", + gpg_strerror (err)); + exit (1); + } + + obj->hd = hd; + return 0; +} + +static void +bench_pq_sig_free (struct bench_obj *obj) +{ + struct bench_pq_sig_hd *hd = obj->hd; + + gcry_sexp_release (hd->sig); + gcry_sexp_release (hd->pub_key); + gcry_sexp_release (hd->sec_key); + gcry_sexp_release (hd->data); + gcry_sexp_release (hd->key_spec); + free (hd); + obj->hd = NULL; +} + +static void +bench_pq_sig_keygen (struct bench_pq_sig_hd *hd) +{ + gcry_sexp_t key_pair; + gpg_error_t err; + + err = gcry_pk_genkey (&key_pair, hd->key_spec); + if (err) + { + fprintf (stderr, PGM ": gcry_pk_genkey failed: %s\n", + gpg_strerror (err)); + exit (1); + } + + hd->pub_key = gcry_sexp_find_token (key_pair, "public-key", 0); + if (!hd->pub_key) + { + fprintf (stderr, PGM ": public part missing in key\n"); + exit (1); + } + hd->sec_key = gcry_sexp_find_token (key_pair, "private-key", 0); + if (!hd->sec_key) + { + fprintf (stderr, PGM ": private part missing in key\n"); + exit (1); + } + + gcry_sexp_release (key_pair); +} + +static void +bench_pq_sig_keygen_do_bench (struct bench_obj *obj, void *buf, + size_t num_iter) +{ + struct bench_pq_sig_hd *hd = obj->hd; + size_t i; + + (void)buf; + + for (i = 0; i < num_iter; i++) + { + bench_pq_sig_keygen (hd); + gcry_sexp_release (hd->pub_key); + gcry_sexp_release (hd->sec_key); + } + + hd->pub_key = NULL; + hd->sec_key = NULL; +} + +static void +bench_pq_sig_sign_do_bench (struct bench_obj *obj, void *buf, size_t num_iter) +{ + struct bench_pq_sig_hd *hd = obj->hd; + gpg_error_t err; + size_t i; + + (void)buf; + + bench_pq_sig_keygen (hd); + + for (i = 0; i < num_iter; i++) + { + err = gcry_pk_sign (&hd->sig, hd->data, hd->sec_key); + if (err) + { + fprintf (stderr, PGM ": gcry_pk_sign failed: %s\n", + gpg_strerror (err)); + exit (1); + } + gcry_sexp_release (hd->sig); + } + + gcry_sexp_release (hd->pub_key); + gcry_sexp_release (hd->sec_key); + hd->sig = NULL; + hd->pub_key = NULL; + hd->sec_key = NULL; +} + +static void +bench_pq_sig_verify_do_bench (struct bench_obj *obj, void *buf, + size_t num_iter) +{ + struct bench_pq_sig_hd *hd = obj->hd; + gpg_error_t err; + size_t i; + + (void)buf; + + bench_pq_sig_keygen (hd); + err = gcry_pk_sign (&hd->sig, hd->data, hd->sec_key); + if (err) + { + fprintf (stderr, PGM ": gcry_pk_sign failed: %s\n", + gpg_strerror (err)); + exit (1); + } + + for (i = 0; i < num_iter; i++) + { + err = gcry_pk_verify (hd->sig, hd->data, hd->pub_key); + if (err) + { + fprintf (stderr, PGM ": gcry_pk_verify failed: %s\n", + gpg_strerror (err)); + exit (1); + } + } + + gcry_sexp_release (hd->sig); + gcry_sexp_release (hd->pub_key); + gcry_sexp_release (hd->sec_key); + hd->sig = NULL; + hd->pub_key = NULL; + hd->sec_key = NULL; +} + + +static struct bench_ops pq_kem_keygen_ops = { + &bench_pq_kem_init, + &bench_pq_kem_free, + &bench_pq_kem_keygen_do_bench +}; + +static struct bench_ops pq_kem_encap_ops = { + &bench_pq_kem_init, + &bench_pq_kem_free, + &bench_pq_kem_encap_do_bench +}; + +static struct bench_ops pq_kem_decap_ops = { + &bench_pq_kem_init, + &bench_pq_kem_free, + &bench_pq_kem_decap_do_bench +}; + +static struct bench_ops pq_sig_keygen_ops = { + &bench_pq_sig_init, + &bench_pq_sig_free, + &bench_pq_sig_keygen_do_bench +}; + +static struct bench_ops pq_sig_sign_ops = { + &bench_pq_sig_init, + &bench_pq_sig_free, + &bench_pq_sig_sign_do_bench +}; + +static struct bench_ops pq_sig_verify_ops = { + &bench_pq_sig_init, + &bench_pq_sig_free, + &bench_pq_sig_verify_do_bench +}; + + +static struct bench_pq_oper pq_kem_operations[] = { + { PQ_OPER_KEYGEN, "keygen", &pq_kem_keygen_ops }, + { PQ_OPER_ENCAP, "encap", &pq_kem_encap_ops }, + { PQ_OPER_DECAP, "decap", &pq_kem_decap_ops }, + { 0, NULL, NULL } +}; + +static struct bench_pq_oper pq_sig_operations[] = { + { PQ_OPER_KEYGEN, "keygen", &pq_sig_keygen_ops }, + { PQ_OPER_SIGN, "sign", &pq_sig_sign_ops }, + { PQ_OPER_VERIFY, "verify", &pq_sig_verify_ops }, + { 0, NULL, NULL } +}; + + +static void +cipher_pq_one (enum bench_pq_algo algo, struct bench_pq_oper *poper) +{ + struct bench_pq_oper oper = *poper; + struct bench_obj obj = { 0 }; + double result; + + oper.algo = algo; + + bench_print_mode (14, oper.name); + + obj.ops = oper.ops; + obj.priv = &oper; + + result = do_slope_benchmark (&obj); + bench_print_result_nsec_per_iteration (result); +} + + +static void +_pq_bench (int algo, int named) +{ + struct bench_pq_oper *operations; + int i; + + bench_print_header_nsec_per_iteration (14, pq_algo_name (algo)); + + operations = pq_algo_is_kem (algo) ? pq_kem_operations : pq_sig_operations; + + for (i = 0; operations[i].name; i++) + { + if (!named && !include_slow && operations[i].oper == PQ_OPER_KEYGEN + && pq_algos[algo].keygen_on_demand) + { + if (!csv_mode) + { + bench_print_mode (14, operations[i].name); + bench_print_result_skipped (); + } + continue; + } + cipher_pq_one (algo, &operations[i]); + } + + bench_print_footer (14); +} + + +void +pq_bench (char **argv, int argc) +{ + int i, algo; + + bench_print_section ("pq", "Post-quantum"); + + if (argv && argc) + { + for (i = 0; i < argc; i++) + { + algo = pq_map_name (argv[i]); + if (algo >= 0) + _pq_bench (algo, 1); + } + } + else + { + for (i = 0; i < __MAX_PQ_ALGO; i++) + _pq_bench (i, 0); + } +} + /************************************************************ MPI benchmarks. */ #define MPI_START_SIZE 64 @@ -3254,7 +3854,7 @@ void print_help (void) { static const char *help_lines[] = { - "usage: bench-slope [options] [hash|mac|cipher|kdf|ecc|mpi [algonames]]", + "usage: bench-slope [options] [hash|mac|cipher|kdf|ecc|pq|mpi [algonames]]", "", " options:", " --cpu-mhz Set CPU speed for calculating cycles", @@ -3266,7 +3866,12 @@ print_help (void) STR2(NUM_MEASUREMENT_REPETITIONS) ")", " --unaligned Use unaligned input buffers.", " --no-quick-rng Use default random number generation", + " --include-slow Include slow benchmarks in default run", " --csv Use CSV output format", + "", + " notes:", + " Slow post-quantum key generation is benchmarked only when algorithm", + " is given by name or with '--include-slow'.", NULL }; const char **line; @@ -3360,6 +3965,12 @@ main (int argc, char **argv) argc--; argv++; } + else if (!strcmp (*argv, "--include-slow")) + { + include_slow = 1; + argc--; + argv++; + } else if (!strcmp (*argv, "--unaligned")) { unaligned_mode = 1; @@ -3457,6 +4068,7 @@ main (int argc, char **argv) cipher_bench (NULL, 0); kdf_bench (NULL, 0); ecc_bench (NULL, 0); + pq_bench (NULL, 0); mpi_bench (NULL, 0); } else if (!strcmp (*argv, "hash")) @@ -3499,6 +4111,14 @@ main (int argc, char **argv) warm_up_cpu (); ecc_bench ((argc == 0) ? NULL : argv, argc); } + else if (!strcmp (*argv, "pq")) + { + argc--; + argv++; + + warm_up_cpu (); + pq_bench ((argc == 0) ? NULL : argv, argc); + } else if (!strcmp (*argv, "mpi")) { argc--; -- 2.53.0 From jussi.kivilinna at iki.fi Sun Aug 2 11:55:13 2026 From: jussi.kivilinna at iki.fi (Jussi Kivilinna) Date: Sun, 2 Aug 2026 12:55:13 +0300 Subject: [PATCH 08/10] md, cipher: allow internal users to skip fast random poll In-Reply-To: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> References: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> Message-ID: <20260802095515.2729589-8-jussi.kivilinna@iki.fi> * cipher/md.c (md_open): Add 'fast_rnd_poll' parameter. (_gcry_md_open_internal): New. (_gcry_md_open): Forward to '_gcry_md_open_internal'. (_gcry_md_hash_buffer, _gcry_md_hash_buffers_extract): Do not poll on the one-shot fallback path. * cipher/cipher.c (_gcry_cipher_open_internal): Add 'fast_rnd_poll' parameter. (_gcry_cipher_open): Request poll. * src/cipher.h (_gcry_cipher_open_internal): Move declaration... * src/gcrypt-int.h (_gcry_cipher_open_internal): ...to here. (_gcry_md_open_internal): New. * cipher/kyber.c (shake128_init): Open handle without poll. * cipher/dilithium.c (shake128_init, shake256_init): Likewise. * cipher/des.c (selftest): Likewise. * cipher/dsa-common.c (_gcry_dsa_gen_rfc6979_k): Likewise. * cipher/ecc-sm2.c (kdf_x9_63, _gcry_ecc_sm2_encrypt) (_gcry_ecc_sm2_decrypt): Likewise. * cipher/ecc.c (test_keys_fips, selftest_hash_sign): Likewise. * cipher/hash-common.c (_gcry_hash_selftest_check_one): Likewise. * cipher/pubkey.c (_gcry_pk_get_keygrip): Likewise. * cipher/rijndael.c (selftest_fips_128_38a): Likewise. * cipher/rsa-common.c (mgf1, _gcry_rsa_pss_encode) (_gcry_rsa_pss_verify): Likewise. * cipher/rsa.c (test_keys_fips, selftest_hash_sign_2048): Likewise. * random/random-drbg.c (drbg_hash_init, drbg_hmac_init, drbg_sym_init): Likewise. * src/fips.c (hmac256_check): Likewise. * cipher/mceliece6688128f.c (crypto_xof_shake256): Use '_gcry_md_hash_buffers_extract'. * cipher/mceliece6688128f.sh: Likewise. * cipher/kdf.c (openpgp_s2k, _gcry_kdf_pkdf2, prng_aes_ctr_init) (onestep_kdf_open, x963_kdf_open): Use internal interface and keep poll. * cipher/mac-cmac.c (cmac_open): Likewise. * cipher/mac-gmac.c (gmac_open): Likewise. * cipher/mac-hmac.c (hmac_open, check_one): Likewise. * cipher/mac-poly1305.c (poly1305mac_open): Likewise. -- 'md_open' and 'cipher_open' call '_gcry_fast_random_poll', which takes pool lock, reads RDRAND and mixes CSPRNG pool. ML-KEM and ML-DSA route SHAKE through the md interface and open one handle per polynomial, so ML-DSA-87 key generation paid for 72 polls and ML-KEM-768 for nine on every operation. Poll exists for applications that never call 'gcry_control(GCRYCTL_FAST_POLL)' and adds nothing for handles that the library opens for its own hashing. Benchmark on AMD Ryzen 9 9950X3D, usec/operation, quick random enabled to exclude entropy gathering: before after speedup ML-KEM-512 keygen 22.70 18.57 1.22x encap 23.70 19.45 1.22x decap 24.13 20.03 1.20x ML-KEM-768 keygen 38.02 28.63 1.33x encap 37.79 28.34 1.33x decap 40.53 30.75 1.32x ML-KEM-1024 keygen 58.70 41.91 1.40x encap 56.40 39.95 1.41x decap 61.22 44.73 1.37x ML-DSA-44 keygen 59.22 33.96 1.74x sign 191.90 143.90 1.33x verify 56.76 35.79 1.59x ML-DSA-65 keygen 98.62 54.58 1.81x sign 304.21 225.64 1.35x verify 95.11 57.64 1.65x ML-DSA-87 keygen 164.97 90.05 1.83x sign 377.63 275.93 1.37x verify 157.23 95.03 1.65x Signed-off-by: Jussi Kivilinna --- cipher/cipher.c | 8 +++++--- cipher/des.c | 2 +- cipher/dilithium.c | 4 ++-- cipher/dsa-common.c | 3 ++- cipher/ecc-sm2.c | 6 +++--- cipher/ecc.c | 4 ++-- cipher/hash-common.c | 2 +- cipher/kdf.c | 15 +++++++++------ cipher/kyber.c | 2 +- cipher/mac-cmac.c | 2 +- cipher/mac-gmac.c | 2 +- cipher/mac-hmac.c | 4 ++-- cipher/mac-poly1305.c | 2 +- cipher/mceliece6688128f.c | 14 ++++++++------ cipher/mceliece6688128f.sh | 14 ++++++++------ cipher/md.c | 32 +++++++++++++++++++++----------- cipher/pubkey.c | 2 +- cipher/rijndael.c | 6 ++++-- cipher/rsa-common.c | 6 +++--- cipher/rsa.c | 4 ++-- random/random-drbg.c | 14 ++++++++------ src/cipher.h | 5 ----- src/fips.c | 2 +- src/gcrypt-int.h | 6 ++++++ 24 files changed, 93 insertions(+), 68 deletions(-) diff --git a/cipher/cipher.c b/cipher/cipher.c index fc130907..72227714 100644 --- a/cipher/cipher.c +++ b/cipher/cipher.c @@ -497,7 +497,7 @@ _gcry_cipher_open (gcry_cipher_hd_t *handle, if (mode >= GCRY_CIPHER_MODE_INTERNAL) rc = GPG_ERR_INV_CIPHER_MODE; else - rc = _gcry_cipher_open_internal (&h, algo, mode, flags); + rc = _gcry_cipher_open_internal (&h, algo, mode, flags, 1); *handle = rc ? NULL : h; @@ -567,7 +567,8 @@ _gcry_cipher_mode_fips_compliance (int mode) gcry_err_code_t _gcry_cipher_open_internal (gcry_cipher_hd_t *handle, - int algo, int mode, unsigned int flags) + int algo, int mode, unsigned int flags, + int fast_rnd_poll) { int secure = !!(flags & GCRY_CIPHER_SECURE); gcry_cipher_spec_t *spec; @@ -576,7 +577,8 @@ _gcry_cipher_open_internal (gcry_cipher_hd_t *handle, /* If the application missed to call the random poll function, we do it here to ensure that it is used once in a while. */ - _gcry_fast_random_poll (); + if (fast_rnd_poll) + _gcry_fast_random_poll (); spec = spec_from_algo (algo); if (!spec) diff --git a/cipher/des.c b/cipher/des.c index c28fcf5a..dcdf6a2c 100644 --- a/cipher/des.c +++ b/cipher/des.c @@ -1217,7 +1217,7 @@ selftest (void) unsigned char *p; gcry_md_hd_t h; - if (_gcry_md_open (&h, GCRY_MD_SHA1, 0)) + if (_gcry_md_open_internal (&h, GCRY_MD_SHA1, 0, 0)) return "SHA1 not available"; for (i = 0; i < 64; ++i) diff --git a/cipher/dilithium.c b/cipher/dilithium.c index 212c4afe..c5e12fb1 100644 --- a/cipher/dilithium.c +++ b/cipher/dilithium.c @@ -276,7 +276,7 @@ shake128_init (keccak_state *state) { gcry_err_code_t ec; - ec = _gcry_md_open (&state->h, GCRY_MD_SHAKE128, 0); + ec = _gcry_md_open_internal (&state->h, GCRY_MD_SHAKE128, 0, 0); if (ec) log_fatal ("internal md_open failed: %d\n", ec); } @@ -310,7 +310,7 @@ shake256_init (keccak_state *state) { gcry_err_code_t ec; - ec = _gcry_md_open (&state->h, GCRY_MD_SHAKE256, 0); + ec = _gcry_md_open_internal (&state->h, GCRY_MD_SHAKE256, 0, 0); if (ec) log_fatal ("internal md_open failed: %d\n", ec); } diff --git a/cipher/dsa-common.c b/cipher/dsa-common.c index 82b9c9dc..4924bff6 100644 --- a/cipher/dsa-common.c +++ b/cipher/dsa-common.c @@ -245,7 +245,8 @@ _gcry_dsa_gen_rfc6979_k (gcry_mpi_t *r_k, goto leave; /* Create a handle to compute the HMACs. */ - rc = _gcry_md_open (&hd, halgo, (GCRY_MD_FLAG_SECURE | GCRY_MD_FLAG_HMAC)); + rc = _gcry_md_open_internal (&hd, halgo, + (GCRY_MD_FLAG_SECURE | GCRY_MD_FLAG_HMAC), 0); if (rc) goto leave; diff --git a/cipher/ecc-sm2.c b/cipher/ecc-sm2.c index 8c3241e4..6625c590 100644 --- a/cipher/ecc-sm2.c +++ b/cipher/ecc-sm2.c @@ -49,7 +49,7 @@ kdf_x9_63 (int algo, const void *in, size_t inlen, void *out, size_t outlen) size_t rlen = outlen; size_t len; - rc = _gcry_md_open (&hd, algo, 0); + rc = _gcry_md_open_internal (&hd, algo, 0, 0); if (rc) return rc; @@ -174,7 +174,7 @@ _gcry_ecc_sm2_encrypt (gcry_sexp_t *r_ciph, gcry_mpi_t input, mpi_ec_t ec) /* hash(x2 || IN || y2) */ mdlen = _gcry_md_get_algo_dlen (algo); - rc = _gcry_md_open (&md, algo, 0); + rc = _gcry_md_open_internal (&md, algo, 0, 0); if (rc) goto leave; _gcry_md_write (md, raw, MPI_NBYTES(x2)); @@ -317,7 +317,7 @@ _gcry_ecc_sm2_decrypt (gcry_sexp_t *r_plain, gcry_sexp_t data_list, mpi_ec_t ec) /* Hash(x2 || IN || y2) == C3 */ mdlen = _gcry_md_get_algo_dlen (algo); - rc = _gcry_md_open (&md, algo, 0); + rc = _gcry_md_open_internal (&md, algo, 0, 0); if (rc) goto leave_main; _gcry_md_write (md, raw, MPI_NBYTES(x2)); diff --git a/cipher/ecc.c b/cipher/ecc.c index 95dc0535..18ef5837 100644 --- a/cipher/ecc.c +++ b/cipher/ecc.c @@ -427,7 +427,7 @@ test_keys_fips (gcry_sexp_t skey) _gcry_randomize (plaintext, sizeof plaintext, GCRY_WEAK_RANDOM); /* Open MD context and feed the random data in */ - rc = _gcry_md_open (&hd, GCRY_MD_SHA256, 0); + rc = _gcry_md_open_internal (&hd, GCRY_MD_SHA256, 0, 0); if (rc) { log_error ("ECDSA operation: failed to initialize MD context: %s\n", gpg_strerror (rc)); @@ -2069,7 +2069,7 @@ selftest_hash_sign (gcry_sexp_t pkey, gcry_sexp_t skey, const char *tmpl, gcry_mpi_t calculated_s = NULL; int cmp; - err = _gcry_md_open (&hd, md_algo, 0); + err = _gcry_md_open_internal (&hd, md_algo, 0, 0); if (err) { errtxt = "gcry_md_open failed"; diff --git a/cipher/hash-common.c b/cipher/hash-common.c index 03cd11f0..1b0ee64e 100644 --- a/cipher/hash-common.c +++ b/cipher/hash-common.c @@ -56,7 +56,7 @@ _gcry_hash_selftest_check_one (int algo, if (_gcry_md_get_algo_dlen (algo) != expectlen) expect_xof = 1; - err = _gcry_md_open (&hd, algo, 0); + err = _gcry_md_open_internal (&hd, algo, 0, 0); if (err) return "gcry_md_open failed"; diff --git a/cipher/kdf.c b/cipher/kdf.c index 66eff6c6..644888fc 100644 --- a/cipher/kdf.c +++ b/cipher/kdf.c @@ -54,7 +54,8 @@ openpgp_s2k (const void *passphrase, size_t passphraselen, secmode = _gcry_is_secure (passphrase) || _gcry_is_secure (keybuffer); - ec = _gcry_md_open (&md, hashalgo, secmode? GCRY_MD_FLAG_SECURE : 0); + ec = _gcry_md_open_internal (&md, hashalgo, + secmode? GCRY_MD_FLAG_SECURE : 0, 1); if (ec) return ec; @@ -173,8 +174,9 @@ _gcry_kdf_pkdf2 (const void *passphrase, size_t passphraselen, tbuf = sbuf + saltlen + 4; ubuf = tbuf + hlen; - ec = _gcry_md_open (&md, hashalgo, (GCRY_MD_FLAG_HMAC - | (secmode?GCRY_MD_FLAG_SECURE:0))); + ec = _gcry_md_open_internal (&md, hashalgo, + (GCRY_MD_FLAG_HMAC + | (secmode?GCRY_MD_FLAG_SECURE:0)), 1); if (ec) { xfree (sbuf); @@ -991,7 +993,8 @@ prng_aes_ctr_init (gcry_cipher_hd_t *hd_p, balloon_ctx_t b, blklen = _gcry_cipher_get_algo_blklen (cipher_algo); b->md_spec->hash_buffers (key, b->blklen, iov, iov_count); - ec = _gcry_cipher_open (&hd, cipher_algo, GCRY_CIPHER_MODE_CTR, 0); + ec = _gcry_cipher_open_internal (&hd, cipher_algo, GCRY_CIPHER_MODE_CTR, + 0, 1); if (ec) return ec; @@ -1465,7 +1468,7 @@ onestep_kdf_open (gcry_kdf_hd_t *hd, int hashalgo, xfree (o); return GPG_ERR_DIGEST_ALGO; } - ec = _gcry_md_open (&o->md, hashalgo, 0); + ec = _gcry_md_open_internal (&o->md, hashalgo, 0, 1); if (ec) { xfree (o); @@ -1925,7 +1928,7 @@ x963_kdf_open (gcry_kdf_hd_t *hd, int hashalgo, xfree (o); return GPG_ERR_DIGEST_ALGO; } - ec = _gcry_md_open (&o->md, hashalgo, 0); + ec = _gcry_md_open_internal (&o->md, hashalgo, 0, 1); if (ec) { xfree (o); diff --git a/cipher/kyber.c b/cipher/kyber.c index dcb7e671..82d906f1 100644 --- a/cipher/kyber.c +++ b/cipher/kyber.c @@ -215,7 +215,7 @@ shake128_init (keccak_state *state) { gcry_err_code_t ec; - ec = _gcry_md_open (&state->h, GCRY_MD_SHAKE128, 0); + ec = _gcry_md_open_internal (&state->h, GCRY_MD_SHAKE128, 0, 0); if (ec) log_fatal ("internal md_open failed: %d\n", ec); } diff --git a/cipher/mac-cmac.c b/cipher/mac-cmac.c index 2274bd8e..04757750 100644 --- a/cipher/mac-cmac.c +++ b/cipher/mac-cmac.c @@ -79,7 +79,7 @@ cmac_open (gcry_mac_hd_t h) flags = (secure ? GCRY_CIPHER_SECURE : 0); err = _gcry_cipher_open_internal (&hd, cipher_algo, GCRY_CIPHER_MODE_CMAC, - flags); + flags, 1); if (err) return err; diff --git a/cipher/mac-gmac.c b/cipher/mac-gmac.c index b5610c44..a6c725c7 100644 --- a/cipher/mac-gmac.c +++ b/cipher/mac-gmac.c @@ -66,7 +66,7 @@ gmac_open (gcry_mac_hd_t h) flags = (secure ? GCRY_CIPHER_SECURE : 0); err = _gcry_cipher_open_internal (&hd, cipher_algo, GCRY_CIPHER_MODE_GCM, - flags); + flags, 1); if (err) return err; diff --git a/cipher/mac-hmac.c b/cipher/mac-hmac.c index 94fe2c9f..31ee4a46 100644 --- a/cipher/mac-hmac.c +++ b/cipher/mac-hmac.c @@ -115,7 +115,7 @@ hmac_open (gcry_mac_hd_t h) flags = GCRY_MD_FLAG_HMAC; flags |= (secure ? GCRY_MD_FLAG_SECURE : 0); - err = _gcry_md_open (&hd, md_algo, flags); + err = _gcry_md_open_internal (&hd, md_algo, flags, 1); if (err) return err; @@ -255,7 +255,7 @@ check_one (int algo, if (_gcry_md_get_algo_dlen (algo) != expectlen) return "invalid tests data"; } - if (_gcry_md_open (&hd, algo, GCRY_MD_FLAG_HMAC)) + if (_gcry_md_open_internal (&hd, algo, GCRY_MD_FLAG_HMAC, 1)) return "gcry_md_open failed"; if (_gcry_md_setkey (hd, key, keylen)) { diff --git a/cipher/mac-poly1305.c b/cipher/mac-poly1305.c index dfaef446..53782bec 100644 --- a/cipher/mac-poly1305.c +++ b/cipher/mac-poly1305.c @@ -92,7 +92,7 @@ poly1305mac_open (gcry_mac_hd_t h) } err = _gcry_cipher_open_internal (&mac_ctx->hd, cipher_algo, - GCRY_CIPHER_MODE_ECB, flags); + GCRY_CIPHER_MODE_ECB, flags, 1); if (err) goto err_free; diff --git a/cipher/mceliece6688128f.c b/cipher/mceliece6688128f.c index 3130c0cc..99bb4b4d 100644 --- a/cipher/mceliece6688128f.c +++ b/cipher/mceliece6688128f.c @@ -144,15 +144,17 @@ randombytes (uint8_t *out, size_t outlen) static void crypto_xof_shake256(unsigned char *h,long long hlen, const unsigned char *m,long long mlen) { - gcry_md_hd_t mdh; + gcry_buffer_t iov = + { + .data = (void *)m, + .off = 0, + .len = mlen + }; gcry_err_code_t ec; - ec = _gcry_md_open (&mdh, GCRY_MD_SHAKE256, 0); + ec = _gcry_md_hash_buffers_extract (GCRY_MD_SHAKE256, 0, h, hlen, &iov, 1); if (ec) - log_fatal ("internal md_open failed: %d\n", ec); - _gcry_md_write (mdh, m, mlen); - _gcry_md_extract (mdh, GCRY_MD_SHAKE256, h, hlen); - _gcry_md_close (mdh); + log_fatal ("internal shake256 failed: %d\n", ec); } /* from libmceliece-20230612/include-build/crypto_declassify.h */ #ifndef crypto_declassify_h diff --git a/cipher/mceliece6688128f.sh b/cipher/mceliece6688128f.sh index 84245432..34917b98 100755 --- a/cipher/mceliece6688128f.sh +++ b/cipher/mceliece6688128f.sh @@ -138,15 +138,17 @@ randombytes (uint8_t *out, size_t outlen) static void crypto_xof_shake256(unsigned char *h,long long hlen, const unsigned char *m,long long mlen) { - gcry_md_hd_t mdh; + gcry_buffer_t iov = + { + .data = (void *)m, + .off = 0, + .len = mlen + }; gcry_err_code_t ec; - ec = _gcry_md_open (&mdh, GCRY_MD_SHAKE256, 0); + ec = _gcry_md_hash_buffers_extract (GCRY_MD_SHAKE256, 0, h, hlen, &iov, 1); if (ec) - log_fatal ("internal md_open failed: %d\n", ec); - _gcry_md_write (mdh, m, mlen); - _gcry_md_extract (mdh, GCRY_MD_SHAKE256, h, hlen); - _gcry_md_close (mdh); + log_fatal ("internal shake256 failed: %d\n", ec); } EOF N=16 diff --git a/cipher/md.c b/cipher/md.c index 183f36cc..839f84f1 100644 --- a/cipher/md.c +++ b/cipher/md.c @@ -481,7 +481,7 @@ check_digest_algo (int algo) * may be 0. */ static gcry_err_code_t -md_open (gcry_md_hd_t *h, int algo, unsigned int flags) +md_open (gcry_md_hd_t *h, int algo, unsigned int flags, int fast_rnd_poll) { gcry_err_code_t err = 0; int secure = !!(flags & GCRY_MD_FLAG_SECURE); @@ -539,7 +539,8 @@ md_open (gcry_md_hd_t *h, int algo, unsigned int flags) if (! err) { /* Hmmm, should we really do that? - yes [-wk] */ - _gcry_fast_random_poll (); + if (fast_rnd_poll) + _gcry_fast_random_poll (); if (algo) { @@ -555,13 +556,10 @@ md_open (gcry_md_hd_t *h, int algo, unsigned int flags) return err; } -/* Create a message digest object for algorithm ALGO. FLAGS may be - given as an bitwise OR of the gcry_md_flags values. ALGO may be - given as 0 if the algorithms to be used are later set using - gcry_md_enable. H is guaranteed to be a valid handle or NULL on - error. */ + gcry_err_code_t -_gcry_md_open (gcry_md_hd_t *h, int algo, unsigned int flags) +_gcry_md_open_internal (gcry_md_hd_t *h, int algo, unsigned int flags, + int fast_rnd_poll) { gcry_err_code_t rc; gcry_md_hd_t hd; @@ -571,7 +569,7 @@ _gcry_md_open (gcry_md_hd_t *h, int algo, unsigned int flags) | GCRY_MD_FLAG_BUGEMU1))) rc = GPG_ERR_INV_ARG; else - rc = md_open (&hd, algo, flags); + rc = md_open (&hd, algo, flags, fast_rnd_poll); if (!rc && fips_mode ()) { @@ -613,6 +611,18 @@ _gcry_md_open (gcry_md_hd_t *h, int algo, unsigned int flags) } +/* Create a message digest object for algorithm ALGO. FLAGS may be + given as an bitwise OR of the gcry_md_flags values. ALGO may be + given as 0 if the algorithms to be used are later set using + gcry_md_enable. H is guaranteed to be a valid handle or NULL on + error. */ +gcry_err_code_t +_gcry_md_open (gcry_md_hd_t *h, int algo, unsigned int flags) +{ + return _gcry_md_open_internal(h, algo, flags, 1); +} + + static gcry_err_code_t md_enable (gcry_md_hd_t hd, int algorithm) @@ -1406,7 +1416,7 @@ _gcry_md_hash_buffer (int algo, void *digest, gcry_md_hd_t h; gpg_err_code_t err; - err = md_open (&h, algo, 0); + err = md_open (&h, algo, 0, 0); if (err) log_bug ("gcry_md_open failed for algo %d: %s", algo, gpg_strerror (gcry_error(err))); @@ -1487,7 +1497,7 @@ _gcry_md_hash_buffers_extract (int algo, unsigned int flags, void *digest, gcry_md_hd_t h; gpg_err_code_t rc; - rc = md_open (&h, algo, (hmac? GCRY_MD_FLAG_HMAC:0)); + rc = md_open (&h, algo, (hmac? GCRY_MD_FLAG_HMAC:0), 0); if (rc) return rc; diff --git a/cipher/pubkey.c b/cipher/pubkey.c index 7120c24f..3f05b862 100644 --- a/cipher/pubkey.c +++ b/cipher/pubkey.c @@ -1034,7 +1034,7 @@ _gcry_pk_get_keygrip (gcry_sexp_t key, unsigned char *array) if (!elems) goto fail; /* No grip parameter. */ - if (_gcry_md_open (&md, GCRY_MD_SHA1, 0)) + if (_gcry_md_open_internal (&md, GCRY_MD_SHA1, 0, 0)) goto fail; if (spec->comp_keygrip) diff --git a/cipher/rijndael.c b/cipher/rijndael.c index cf7f4c23..647334c9 100644 --- a/cipher/rijndael.c +++ b/cipher/rijndael.c @@ -2049,10 +2049,12 @@ selftest_fips_128_38a (int requested_mode) if (tvi == DIM (tv)) Fail ("no test data for this mode"); - err = _gcry_cipher_open (&hdenc, GCRY_CIPHER_AES, tv[tvi].mode, 0); + err = _gcry_cipher_open_internal (&hdenc, GCRY_CIPHER_AES, tv[tvi].mode, 0, + 0); if (err) Fail ("open"); - err = _gcry_cipher_open (&hddec, GCRY_CIPHER_AES, tv[tvi].mode, 0); + err = _gcry_cipher_open_internal (&hddec, GCRY_CIPHER_AES, tv[tvi].mode, 0, + 0); if (err) Fail ("open"); err = _gcry_cipher_setkey (hdenc, tv[tvi].key, sizeof tv[tvi].key); diff --git a/cipher/rsa-common.c b/cipher/rsa-common.c index c1d2dcd5..6427b853 100644 --- a/cipher/rsa-common.c +++ b/cipher/rsa-common.c @@ -435,7 +435,7 @@ mgf1 (unsigned char *output, size_t outlen, unsigned char *seed, size_t seedlen, gcry_md_hd_t hd; gcry_err_code_t err; - err = _gcry_md_open (&hd, algo, 0); + err = _gcry_md_open_internal (&hd, algo, 0, 0); if (err) return err; @@ -834,7 +834,7 @@ _gcry_rsa_pss_encode (gcry_mpi_t *r_result, unsigned int nbits, int algo, /* This code is implemented as described by rfc-3447 9.1.1. */ - rc = _gcry_md_open (&hd, algo, 0); + rc = _gcry_md_open_internal (&hd, algo, 0, 0); if (rc) return rc; @@ -1005,7 +1005,7 @@ _gcry_rsa_pss_verify (gcry_mpi_t value, int hashed_already, /* This code is implemented as described by rfc-3447 9.1.2. */ - rc = _gcry_md_open (&hd, algo, 0); + rc = _gcry_md_open_internal (&hd, algo, 0, 0); if (rc) return rc; diff --git a/cipher/rsa.c b/cipher/rsa.c index e3b1891c..9bc0a899 100644 --- a/cipher/rsa.c +++ b/cipher/rsa.c @@ -192,7 +192,7 @@ test_keys_fips (gcry_sexp_t skey) _gcry_randomize (plaintext, sizeof plaintext, GCRY_WEAK_RANDOM); /* Open MD context and feed the random data in */ - ec = _gcry_md_open (&hd, GCRY_MD_SHA256, 0); + ec = _gcry_md_open_internal (&hd, GCRY_MD_SHA256, 0, 0); if (ec) goto leave; _gcry_md_write (hd, plaintext, sizeof(plaintext)); @@ -1924,7 +1924,7 @@ selftest_hash_sign_2048 (gcry_sexp_t pkey, gcry_sexp_t skey) gcry_mpi_t ref_mpi = NULL; gcry_mpi_t sig_mpi = NULL; - err = _gcry_md_open (&hd, md_algo, 0); + err = _gcry_md_open_internal (&hd, md_algo, 0, 0); if (err) { errtxt = "gcry_md_open failed"; diff --git a/random/random-drbg.c b/random/random-drbg.c index 323c0dd9..bb7d7b95 100644 --- a/random/random-drbg.c +++ b/random/random-drbg.c @@ -2529,7 +2529,7 @@ drbg_hash_init (drbg_state_t drbg) gcry_md_hd_t hd; gpg_error_t err; - err = _gcry_md_open (&hd, drbg->core->backend_cipher, 0); + err = _gcry_md_open_internal (&hd, drbg->core->backend_cipher, 0, 0); if (err) return err; @@ -2544,7 +2544,8 @@ drbg_hmac_init (drbg_state_t drbg) gcry_md_hd_t hd; gpg_error_t err; - err = _gcry_md_open (&hd, drbg->core->backend_cipher, GCRY_MD_FLAG_HMAC); + err = _gcry_md_open_internal (&hd, drbg->core->backend_cipher, + GCRY_MD_FLAG_HMAC, 0); if (err) return err; @@ -2598,8 +2599,8 @@ drbg_sym_init (drbg_state_t drbg) gcry_cipher_hd_t hd; gpg_error_t err; - err = _gcry_cipher_open (&hd, drbg->core->backend_cipher, - GCRY_CIPHER_MODE_ECB, 0); + err = _gcry_cipher_open_internal (&hd, drbg->core->backend_cipher, + GCRY_CIPHER_MODE_ECB, 0, 0); if (err) { drbg_sym_fini (drbg); @@ -2607,8 +2608,9 @@ drbg_sym_init (drbg_state_t drbg) } drbg->priv_data = hd; - err = _gcry_cipher_open (&drbg->ctr_handle, drbg->core->backend_cipher, - GCRY_CIPHER_MODE_CTR, 0); + err = _gcry_cipher_open_internal (&drbg->ctr_handle, + drbg->core->backend_cipher, + GCRY_CIPHER_MODE_CTR, 0, 0); if (err) { drbg_sym_fini (drbg); diff --git a/src/cipher.h b/src/cipher.h index 14cb6fc6..46311c8b 100644 --- a/src/cipher.h +++ b/src/cipher.h @@ -108,11 +108,6 @@ enum gcry_cipher_internal_modes }; -/*-- cipher.c --*/ -gcry_err_code_t _gcry_cipher_open_internal (gcry_cipher_hd_t *handle, - int algo, int mode, - unsigned int flags); - /*-- cipher-cmac.c --*/ gcry_err_code_t _gcry_cipher_cmac_authenticate /* */ (gcry_cipher_hd_t c, const unsigned char *abuf, size_t abuflen); diff --git a/src/fips.c b/src/fips.c index d1aff8a5..c5ccf4f4 100644 --- a/src/fips.c +++ b/src/fips.c @@ -930,7 +930,7 @@ hmac256_check (const char *filename, const char *key) return err; } - err = _gcry_md_open (&hd, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC); + err = _gcry_md_open_internal (&hd, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC, 0); if (err) { fclose (fp); diff --git a/src/gcrypt-int.h b/src/gcrypt-int.h index a0861f8a..2f120e22 100644 --- a/src/gcrypt-int.h +++ b/src/gcrypt-int.h @@ -49,6 +49,10 @@ typedef struct mpi_ec_ctx_s *mpi_ec_t; gpg_err_code_t _gcry_cipher_open (gcry_cipher_hd_t *handle, int algo, int mode, unsigned int flags); +gcry_err_code_t _gcry_cipher_open_internal (gcry_cipher_hd_t *handle, + int algo, int mode, + unsigned int flags, + int fast_rnd_poll); void _gcry_cipher_close (gcry_cipher_hd_t h); gpg_err_code_t _gcry_cipher_ctl (gcry_cipher_hd_t h, int cmd, void *buffer, size_t buflen); @@ -145,6 +149,8 @@ gpg_err_code_t _gcry_pk_get_single_data (gcry_ctx_t *r_ctx, size_t *r_len); gpg_err_code_t _gcry_md_open (gcry_md_hd_t *h, int algo, unsigned int flags); +gcry_err_code_t _gcry_md_open_internal (gcry_md_hd_t *h, int algo, + unsigned int flags, int fast_rnd_poll); void _gcry_md_close (gcry_md_hd_t hd); gpg_err_code_t _gcry_md_enable (gcry_md_hd_t hd, int algo); gpg_err_code_t _gcry_md_copy (gcry_md_hd_t *bhd, gcry_md_hd_t ahd); -- 2.53.0 From jussi.kivilinna at iki.fi Sun Aug 2 11:55:14 2026 From: jussi.kivilinna at iki.fi (Jussi Kivilinna) Date: Sun, 2 Aug 2026 12:55:14 +0300 Subject: [PATCH 09/10] kyber: use strong random for encapsulation coins In-Reply-To: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> References: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> Message-ID: <20260802095515.2729589-9-jussi.kivilinna@iki.fi> * cipher/kyber.c (randombytes): Remove. (crypto_kem_keypair_2, crypto_kem_keypair_3, crypto_kem_keypair_4) (crypto_kem_enc_2, crypto_kem_enc_3, crypto_kem_enc_4): Remove declarations. (crypto_kem_keypair, crypto_kem_enc): Remove variant defines. (kyber_keypair): Generate coins with GCRY_VERY_STRONG_RANDOM and always use derandomized entry point. (kyber_encap): Likewise, but with GCRY_STRONG_RANDOM. * cipher/kyber-kdep.c (crypto_kem_keypair, crypto_kem_enc): Remove. -- 'randombytes' used GCRY_VERY_STRONG_RANDOM for both key generation and encapsulation. Encapsulation coins are per message ephemeral value, same as ECDH ephemeral secret in 'ecc-ecdh.c' and as coins for sntrup761 and Classic McEliece, all of which use GCRY_STRONG_RANDOM. Level 2 request forces jitterentropy gathering costing about 2 ms, which is two orders of magnitude more than the encapsulation itself. Key generation keeps level 2 as with RSA, DSA and ECC long term keys. Entry points already had derandomized variants for caller supplied coins, so glue now uses those and generates coins itself. Benchmark on AMD Ryzen 9 9950X3D, encapsulation usec/operation, quick random disabled: before after speedup ML-KEM-512 1940.51 19.27 100.70x ML-KEM-768 1953.88 28.43 68.73x ML-KEM-1024 1966.06 39.58 49.67x Signed-off-by: Jussi Kivilinna --- cipher/kyber-kdep.c | 49 ------------------------------ cipher/kyber.c | 74 +++++++++++++++++---------------------------- 2 files changed, 28 insertions(+), 95 deletions(-) diff --git a/cipher/kyber-kdep.c b/cipher/kyber-kdep.c index 774504ce..6d2fe9ed 100644 --- a/cipher/kyber-kdep.c +++ b/cipher/kyber-kdep.c @@ -415,28 +415,6 @@ int crypto_kem_keypair_derand(uint8_t *pk, return 0; } -/************************************************* -* Name: crypto_kem_keypair -* -* Description: Generates public and private key -* for CCA-secure Kyber key encapsulation mechanism -* -* Arguments: - uint8_t *pk: pointer to output public key -* (an already allocated array of KYBER_PUBLICKEYBYTES bytes) -* - uint8_t *sk: pointer to output private key -* (an already allocated array of KYBER_SECRETKEYBYTES bytes) -* -* Returns 0 (success) -**************************************************/ -int crypto_kem_keypair(uint8_t *pk, - uint8_t *sk) -{ - uint8_t coins[2*KYBER_SYMBYTES]; - randombytes(coins, 2*KYBER_SYMBYTES); - crypto_kem_keypair_derand(pk, sk, coins); - return 0; -} - /************************************************* * Name: crypto_kem_enc_derand * @@ -476,31 +454,6 @@ int crypto_kem_enc_derand(uint8_t *ct, return 0; } -/************************************************* -* Name: crypto_kem_enc -* -* Description: Generates cipher text and shared -* secret for given public key -* -* Arguments: - uint8_t *ct: pointer to output cipher text -* (an already allocated array of KYBER_CIPHERTEXTBYTES bytes) -* - uint8_t *ss: pointer to output shared secret -* (an already allocated array of KYBER_SSBYTES bytes) -* - const uint8_t *pk: pointer to input public key -* (an already allocated array of KYBER_PUBLICKEYBYTES bytes) -* -* Returns 0 (success) -**************************************************/ -int crypto_kem_enc(uint8_t *ct, - uint8_t *ss, - const uint8_t *pk) -{ - uint8_t coins[KYBER_SYMBYTES]; - randombytes(coins, KYBER_SYMBYTES); - crypto_kem_enc_derand(ct, ss, pk, coins); - return 0; -} - /************************************************* * Name: crypto_kem_dec * @@ -803,8 +756,6 @@ void polyvec_add(polyvec *r, const polyvec *a, const polyvec *b) #undef poly_getnoise_eta1 #undef crypto_kem_keypair_derand #undef crypto_kem_enc_derand -#undef crypto_kem_keypair -#undef crypto_kem_enc #undef crypto_kem_dec #undef polyvec #undef polyvec_compress diff --git a/cipher/kyber.c b/cipher/kyber.c index 82d906f1..a8291c26 100644 --- a/cipher/kyber.c +++ b/cipher/kyber.c @@ -108,10 +108,6 @@ static int crypto_kem_keypair_derand_3(uint8_t *pk, uint8_t *sk, static int crypto_kem_keypair_derand_4(uint8_t *pk, uint8_t *sk, const uint8_t *coins); -static int crypto_kem_keypair_2(uint8_t *pk, uint8_t *sk); -static int crypto_kem_keypair_3(uint8_t *pk, uint8_t *sk); -static int crypto_kem_keypair_4(uint8_t *pk, uint8_t *sk); - static int crypto_kem_enc_derand_2(uint8_t *ct, uint8_t *ss, const uint8_t *pk, const uint8_t *coins); static int crypto_kem_enc_derand_3(uint8_t *ct, uint8_t *ss, const uint8_t *pk, @@ -119,10 +115,6 @@ static int crypto_kem_enc_derand_3(uint8_t *ct, uint8_t *ss, const uint8_t *pk, static int crypto_kem_enc_derand_4(uint8_t *ct, uint8_t *ss, const uint8_t *pk, const uint8_t *coins); -static int crypto_kem_enc_2(uint8_t *ct, uint8_t *ss, const uint8_t *pk); -static int crypto_kem_enc_3(uint8_t *ct, uint8_t *ss, const uint8_t *pk); -static int crypto_kem_enc_4(uint8_t *ct, uint8_t *ss, const uint8_t *pk); - static int crypto_kem_dec_2(uint8_t *ss, const uint8_t *ct, const uint8_t *sk); static int crypto_kem_dec_3(uint8_t *ss, const uint8_t *ct, const uint8_t *sk); static int crypto_kem_dec_4(uint8_t *ss, const uint8_t *ct, const uint8_t *sk); @@ -130,56 +122,60 @@ static int crypto_kem_dec_4(uint8_t *ss, const uint8_t *ct, const uint8_t *sk); void kyber_keypair (int algo, uint8_t *pk, uint8_t *sk, const uint8_t *coins) { + uint8_t rnd[GCRY_KEM_MLKEM_RANDOM_LEN * 2]; + + if (!coins) + { + /* Long term key material. */ + _gcry_randomize (rnd, sizeof (rnd), GCRY_VERY_STRONG_RANDOM); + coins = rnd; + } + switch (algo) { case GCRY_KEM_MLKEM512: - if (coins) - crypto_kem_keypair_derand_2 (pk, sk, coins); - else - crypto_kem_keypair_2 (pk, sk); + crypto_kem_keypair_derand_2 (pk, sk, coins); break; case GCRY_KEM_MLKEM768: default: - if (coins) - crypto_kem_keypair_derand_3 (pk, sk, coins); - else - crypto_kem_keypair_3 (pk, sk); + crypto_kem_keypair_derand_3 (pk, sk, coins); break; case GCRY_KEM_MLKEM1024: - if (coins) - crypto_kem_keypair_derand_4 (pk, sk, coins); - else - crypto_kem_keypair_4 (pk, sk); + crypto_kem_keypair_derand_4 (pk, sk, coins); break; } + + wipememory (rnd, sizeof (rnd)); } void kyber_encap (int algo, uint8_t *ct, uint8_t *ss, const uint8_t *pk, const uint8_t *coins) { + uint8_t rnd[GCRY_KEM_MLKEM_RANDOM_LEN]; + + if (!coins) + { + /* Per message value, same level as ECDH ephemeral secret. */ + _gcry_randomize (rnd, sizeof (rnd), GCRY_STRONG_RANDOM); + coins = rnd; + } + switch (algo) { case GCRY_KEM_MLKEM512: - if (coins) - crypto_kem_enc_derand_2 (ct, ss, pk, coins); - else - crypto_kem_enc_2 (ct, ss, pk); + crypto_kem_enc_derand_2 (ct, ss, pk, coins); break; case GCRY_KEM_MLKEM768: default: - if (coins) - crypto_kem_enc_derand_3 (ct, ss, pk, coins); - else - crypto_kem_enc_3 (ct, ss, pk); + crypto_kem_enc_derand_3 (ct, ss, pk, coins); break; case GCRY_KEM_MLKEM1024: - if (coins) - crypto_kem_enc_derand_4 (ct, ss, pk, coins); - else - crypto_kem_enc_4 (ct, ss, pk); + crypto_kem_enc_derand_4 (ct, ss, pk, coins); break; } + + wipememory (rnd, sizeof (rnd)); } void @@ -200,12 +196,6 @@ kyber_decap (int algo, uint8_t *ss, const uint8_t *ct, const uint8_t *sk) } } -static void -randombytes (uint8_t *out, size_t outlen) -{ - _gcry_randomize (out, outlen, GCRY_VERY_STRONG_RANDOM); -} - typedef struct { gcry_md_hd_t h; } keccak_state; @@ -290,8 +280,6 @@ sha3_512 (uint8_t h[64], const uint8_t *in, size_t inlen) #else #include "kyber.h" -void randombytes (uint8_t *out, size_t outlen); - typedef struct { uint64_t s[25]; unsigned int pos; @@ -472,8 +460,6 @@ static void kyber_shake128_absorb (keccak_state *state, # define poly_getnoise_eta1 poly_getnoise_eta1_2 # define crypto_kem_keypair_derand VARIANT2(crypto_kem_keypair_derand) # define crypto_kem_enc_derand VARIANT2(crypto_kem_enc_derand) -# define crypto_kem_keypair VARIANT2(crypto_kem_keypair) -# define crypto_kem_enc VARIANT2(crypto_kem_enc) # define crypto_kem_dec VARIANT2(crypto_kem_dec) # define polyvec VARIANT2(polyvec) # define polyvec_compress VARIANT2(polyvec_compress) @@ -505,8 +491,6 @@ static void kyber_shake128_absorb (keccak_state *state, # define poly_getnoise_eta1 poly_getnoise_eta1_3_4 # define crypto_kem_keypair_derand VARIANT3(crypto_kem_keypair_derand) # define crypto_kem_enc_derand VARIANT3(crypto_kem_enc_derand) -# define crypto_kem_keypair VARIANT3(crypto_kem_keypair) -# define crypto_kem_enc VARIANT3(crypto_kem_enc) # define crypto_kem_dec VARIANT3(crypto_kem_dec) # define polyvec VARIANT3(polyvec) # define polyvec_compress VARIANT3(polyvec_compress) @@ -538,8 +522,6 @@ static void kyber_shake128_absorb (keccak_state *state, # define poly_getnoise_eta1 poly_getnoise_eta1_3_4 # define crypto_kem_keypair_derand VARIANT4(crypto_kem_keypair_derand) # define crypto_kem_enc_derand VARIANT4(crypto_kem_enc_derand) -# define crypto_kem_keypair VARIANT4(crypto_kem_keypair) -# define crypto_kem_enc VARIANT4(crypto_kem_enc) # define crypto_kem_dec VARIANT4(crypto_kem_dec) # define polyvec VARIANT4(polyvec) # define polyvec_compress VARIANT4(polyvec_compress) -- 2.53.0 From jussi.kivilinna at iki.fi Sun Aug 2 11:55:12 2026 From: jussi.kivilinna at iki.fi (Jussi Kivilinna) Date: Sun, 2 Aug 2026 12:55:12 +0300 Subject: [PATCH 07/10] sntrup761: defer reduction in polynomial multiplication In-Reply-To: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> References: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> Message-ID: <20260802095515.2729589-7-jussi.kivilinna@iki.fi> * cipher/sntrup761.c (Rq_mult_small, R3_mult): Accumulate inner product in int32_t and reduce once per output coefficient. * tests/bench-slope.c (pq_algos): Use full measurement repetitions for sntrup761. -- Both multiplications called 'Fq_freeze'/'F3_freeze' on every multiply- accumulate of the O(p^2) inner loop. Terms are bounded by q12 for Rq and by one for R3, and there are at most p of them, so the accumulator stays far from int32_t range and reduction is needed only for the result. Benchmark on AMD Ryzen 9 9950X3D, SNTRUP761 usec/operation, quick random disabled ('base' being state before this patch series): | base before after speedup total keygen | 37971.9 2900.4 1971.5 1.47x 19.26x encap | 7355.8 1184.7 243.0 4.88x 30.27x decap | 12207.1 3322.4 592.5 5.61x 20.60x Remaining keygen time is in R3_recip and Rq_recip3, where reduction is part of an element-wise running update and cannot be deferred. Signed-off-by: Jussi Kivilinna --- cipher/sntrup761.c | 24 ++++++++++++++---------- tests/bench-slope.c | 2 +- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/cipher/sntrup761.c b/cipher/sntrup761.c index 94229d79..bcb6e651 100644 --- a/cipher/sntrup761.c +++ b/cipher/sntrup761.c @@ -451,22 +451,24 @@ static void R3_mult (small * h, const small * f, const small * g) { small fg[p + p - 1]; - small result; + int32_t result; int i, j; + /* Terms are in {-1,0,1} and there are at most p of them, so the + accumulator cannot overflow and needs reduction only once. */ for (i = 0; i < p; ++i) { result = 0; for (j = 0; j <= i; ++j) - result = F3_freeze (result + f[j] * g[i - j]); - fg[i] = result; + result += f[j] * g[i - j]; + fg[i] = F3_freeze (result); } for (i = p; i < p + p - 1; ++i) { result = 0; for (j = i - p + 1; j < p; ++j) - result = F3_freeze (result + f[j] * g[i - j]); - fg[i] = result; + result += f[j] * g[i - j]; + fg[i] = F3_freeze (result); } for (i = p + p - 2; i >= p; --i) @@ -547,22 +549,24 @@ static void Rq_mult_small (Fq * h, const Fq * f, const small * g) { Fq fg[p + p - 1]; - Fq result; + int32_t result; int i, j; + /* Products are bounded by q12 and there are at most p terms, so the + accumulator cannot overflow and needs reduction only once. */ for (i = 0; i < p; ++i) { result = 0; for (j = 0; j <= i; ++j) - result = Fq_freeze (result + f[j] * (int32_t) g[i - j]); - fg[i] = result; + result += f[j] * (int32_t) g[i - j]; + fg[i] = Fq_freeze (result); } for (i = p; i < p + p - 1; ++i) { result = 0; for (j = i - p + 1; j < p; ++j) - result = Fq_freeze (result + f[j] * (int32_t) g[i - j]); - fg[i] = result; + result += f[j] * (int32_t) g[i - j]; + fg[i] = Fq_freeze (result); } for (i = p + p - 2; i >= p; --i) diff --git a/tests/bench-slope.c b/tests/bench-slope.c index 4b296f0b..911b2b15 100644 --- a/tests/bench-slope.c +++ b/tests/bench-slope.c @@ -3691,7 +3691,7 @@ static const struct #endif { "sntrup761", GCRY_KEM_SNTRUP761, NULL, GCRY_KEM_SNTRUP761_PUBKEY_LEN, GCRY_KEM_SNTRUP761_SECKEY_LEN, - GCRY_KEM_SNTRUP761_ENCAPS_LEN, GCRY_KEM_SNTRUP761_SHARED_LEN, 16, 4, 0 }, + GCRY_KEM_SNTRUP761_ENCAPS_LEN, GCRY_KEM_SNTRUP761_SHARED_LEN, 1, 1, 0 }, { "cm6688128f", GCRY_KEM_CM6688128F, NULL, GCRY_KEM_CM6688128F_PUBKEY_LEN, GCRY_KEM_CM6688128F_SECKEY_LEN, GCRY_KEM_CM6688128F_ENCAPS_LEN, GCRY_KEM_CM6688128F_SHARED_LEN, 16, 1, 1 }, -- 2.53.0 From jussi.kivilinna at iki.fi Sun Aug 2 11:55:09 2026 From: jussi.kivilinna at iki.fi (Jussi Kivilinna) Date: Sun, 2 Aug 2026 12:55:09 +0300 Subject: [PATCH 04/10] tests: add sntrup761 internal arithmetic regression test In-Reply-To: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> References: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> Message-ID: <20260802095515.2729589-4-jussi.kivilinna@iki.fi> * tests/Makefile.am (tests_bin): Add 't-sntrup761'. (t_sntrup761_CPPFLAGS): New. * tests/t-sntrup761.c: New. -- Polynomial multiplication and modular reduction helpers of SNTRUP761 are file-static, so test includes implementation directly, same way as bench-slope reaches cipher internals. Outputs are compared against reference reduction written with plain C operators. Input patterns maximize intermediate accumulators instead of sampling randomly, as that is what deferred reduction depends on. Signed-off-by: Jussi Kivilinna --- tests/Makefile.am | 3 +- tests/t-sntrup761.c | 407 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 409 insertions(+), 1 deletion(-) create mode 100644 tests/t-sntrup761.c diff --git a/tests/Makefile.am b/tests/Makefile.am index 8fcde4fc..8c468b4d 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -26,7 +26,7 @@ tests_bin = \ t-mpi-bit t-mpi-point t-lock \ prime basic keygen pubkey hmac hashtest t-kdf keygrip \ aeswrap random t-kem t-thread-local t-fips-service-ind \ - t-cipher-internal + t-cipher-internal t-sntrup761 if USE_RSA tests_bin += pkcs1v2 t-rsa-pss t-rsa-15 t-rsa-testparm @@ -106,6 +106,7 @@ t_thread_local_LDADD = $(standard_ldadd) $(GPG_ERROR_MT_LIBS) @LDADD_FOR_TESTS_K t_thread_local_CFLAGS = $(GPG_ERROR_MT_CFLAGS) testdrv_LDADD = $(LDADD_FOR_TESTS_KLUDGE) t_cipher_internal_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/cipher +t_sntrup761_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/cipher # Build a version of the test driver for the build platform. testdrv-build: testdrv.c diff --git a/tests/t-sntrup761.c b/tests/t-sntrup761.c new file mode 100644 index 00000000..7d8b2c80 --- /dev/null +++ b/tests/t-sntrup761.c @@ -0,0 +1,407 @@ +/* t-sntrup761.c - SNTRUP761 internal arithmetic regression tests + * Copyright (C) 2026 Jussi Kivilinna + * + * This file is part of Libgcrypt. + * + * Libgcrypt is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * Libgcrypt is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see . + */ + +/* + * Reference functions derived from public domain source, written + * by (in alphabetical order): + * - Daniel J. Bernstein + * - Chitchanok Chuengsatiansup + * - Tanja Lange + * - Christine van Vredendaal + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +/* Stub out external dependencies so that including implementation does + not require linking them. */ +#define _gcry_md_hash_buffer t_sntrup761_md_hash_buffer +#define _gcry_ct_not_memequal t_sntrup761_ct_not_memequal +#define _gcry_ct_memmov_cond t_sntrup761_ct_memmov_cond + +static void +t_sntrup761_md_hash_buffer (int algo, void *digest, const void *buf, + size_t len) +{ + (void)algo; + (void)digest; + (void)buf; + (void)len; +} + +static unsigned int +t_sntrup761_ct_not_memequal (const void *b1, const void *b2, size_t len) +{ + (void)b1; + (void)b2; + (void)len; + return 0; +} + +static void +t_sntrup761_ct_memmov_cond (void *dst, const void *src, size_t len, + unsigned long op_enable) +{ + (void)dst; + (void)src; + (void)len; + (void)op_enable; +} + +/* Include after implementation, as 't-common.h' pulls in public gcrypt.h + which 'sntrup761.h' refuses to see. */ +#include "../cipher/sntrup761.c" + +#define PGM "t-sntrup761" +#include "t-common.h" + +static uint32_t rng_counter; + +static uint32_t +rng (void) +{ + unsigned char ctr[4], dig[20]; + + ctr[0] = rng_counter; + ctr[1] = rng_counter >> 8; + ctr[2] = rng_counter >> 16; + ctr[3] = rng_counter >> 24; + rng_counter++; + + gcry_md_hash_buffer (GCRY_MD_SHA1, dig, ctr, sizeof (ctr)); + + return ((uint32_t)dig[0] | ((uint32_t)dig[1] << 8) + | ((uint32_t)dig[2] << 16) | ((uint32_t)dig[3] << 24)); +} + +/* Canonical representative of X modulo M, in [0,M). */ +static long +ref_mod (long long x, long m) +{ + long long r = x % m; + + if (r < 0) + r += m; + return (long)r; +} + +static int +ref_F3_freeze (long long x) +{ + return ref_mod (x + 1, 3) - 1; +} + +static int +ref_Fq_freeze (long long x) +{ + return ref_mod (x + q12, q) - q12; +} + +/* Multiplication in Z[x]/(q, x^p - x - 1). */ +static void +ref_Rq_mult_small (Fq *h, const Fq *f, const small *g) +{ + static long long fg[p + p - 1]; + int i, j; + + for (i = 0; i < p + p - 1; i++) + fg[i] = 0; + for (i = 0; i < p; i++) + for (j = 0; j < p; j++) + fg[i + j] += (long long)f[i] * g[j]; + for (i = p + p - 2; i >= p; i--) + { + fg[i - p] += fg[i]; + fg[i - p + 1] += fg[i]; + } + for (i = 0; i < p; i++) + h[i] = ref_Fq_freeze (fg[i]); +} + +static void +ref_R3_mult (small *h, const small *f, const small *g) +{ + static long long fg[p + p - 1]; + int i, j; + + for (i = 0; i < p + p - 1; i++) + fg[i] = 0; + for (i = 0; i < p; i++) + for (j = 0; j < p; j++) + fg[i + j] += (long long)f[i] * g[j]; + for (i = p + p - 2; i >= p; i--) + { + fg[i - p] += fg[i]; + fg[i - p + 1] += fg[i]; + } + for (i = 0; i < p; i++) + h[i] = ref_F3_freeze (fg[i]); +} + +static void +test_freeze_helpers (void) +{ + /* Coprime to q, so that sweep hits different residue every step. */ + static const long sweep_step = 9973; + static const long fq_extremes[] = + { + -2 * (long)q12 * q12, 2 * (long)q12 * q12, + -2 * (long)q12 * q12 + 1, 2 * (long)q12 * q12 - 1, + -(long)q12 * q12, (long)q12 * q12, + -(long)p * q12, (long)p * q12 + }; + long x; + unsigned int i; + + if (verbose) + fprintf (stderr, PGM ": checking F3_freeze over all int16_t\n"); + for (x = -32768; x <= 32767; x++) + { + int got = F3_freeze ((int16_t)x); + int want = ref_F3_freeze (x); + + if (got != want) + fail ("F3_freeze(%ld): got %d, want %d", x, got, want); + } + + if (verbose) + fprintf (stderr, PGM ": checking Fq_freeze over reachable range\n"); + for (x = -(2 * (long)q12 + 8); x <= 2 * (long)q12 + 8; x++) + { + int got = Fq_freeze ((int32_t)x); + int want = ref_Fq_freeze (x); + + if (got != want) + fail ("Fq_freeze(%ld): got %d, want %d", x, got, want); + } + + /* Widest values callers produce, from Rq_recip3. */ + for (x = -2 * (long)q12 * q12; x <= 2 * (long)q12 * q12; x += sweep_step) + { + int got = Fq_freeze ((int32_t)x); + int want = ref_Fq_freeze (x); + + if (got != want) + fail ("Fq_freeze(%ld): got %d, want %d", x, got, want); + } + + for (i = 0; i < DIM (fq_extremes); i++) + { + int got = Fq_freeze ((int32_t)fq_extremes[i]); + int want = ref_Fq_freeze (fq_extremes[i]); + + if (got != want) + fail ("Fq_freeze(%ld): got %d, want %d", fq_extremes[i], got, want); + } +} + +/* Input patterns maximize intermediate accumulators. */ +static void +make_inputs (int pattern, Fq *f, small *g) +{ + int i; + + switch (pattern) + { + case 0: + for (i = 0; i < p; i++) + { + f[i] = q12; + g[i] = 1; + } + break; + case 1: + for (i = 0; i < p; i++) + { + f[i] = -q12; + g[i] = -1; + } + break; + case 2: + for (i = 0; i < p; i++) + { + f[i] = q12; + g[i] = -1; + } + break; + case 3: + for (i = 0; i < p; i++) + { + f[i] = q12; + g[i] = (i & 1) ? -1 : 1; + } + break; + case 4: + for (i = 0; i < p; i++) + { + f[i] = (i & 1) ? -q12 : q12; + g[i] = 1; + } + break; + case 5: + for (i = 0; i < p; i++) + { + f[i] = (rng () & 1) ? q12 : -q12; + g[i] = 1; + } + break; + case 6: + for (i = 0; i < p; i++) + { + f[i] = 0; + g[i] = 0; + } + break; + case 7: + for (i = 0; i < p; i++) + { + f[i] = 0; + g[i] = 0; + } + f[p - 1] = q12; + g[p - 1] = -1; + break; + case 8: + for (i = 0; i < p; i++) + { + f[i] = q12; + g[i] = 0; + } + g[0] = 1; + g[p - 1] = 1; + break; + case 9: + for (i = 0; i < p; i++) + { + f[i] = (int)(rng () % (2 * q12 + 1)) - q12; + g[i] = i < w ? ((rng () & 1) ? 1 : -1) : 0; + } + break; + default: + for (i = 0; i < p; i++) + { + f[i] = (int)(rng () % (2 * q12 + 1)) - q12; + g[i] = (int)(rng () % 3) - 1; + } + break; + } +} + +static void +test_mult (void) +{ + static Fq f[p], h[p], h_ref[p]; + static small g[p], fs[p], h3[p], h3_ref[p]; + int pattern, i; + + for (pattern = 0; pattern < 32; pattern++) + { + rng_counter = pattern << 24; + + make_inputs (pattern, f, g); + for (i = 0; i < p; i++) + fs[i] = g[i]; + + Rq_mult_small (h, f, g); + ref_Rq_mult_small (h_ref, f, g); + if (memcmp (h, h_ref, sizeof (h))) + { + for (i = 0; i < p; i++) + if (h[i] != h_ref[i]) + { + fail ("Rq_mult_small pattern %d coeff %d: got %d, want %d", + pattern, i, (int)h[i], (int)h_ref[i]); + break; + } + } + + R3_mult (h3, fs, g); + ref_R3_mult (h3_ref, fs, g); + if (memcmp (h3, h3_ref, sizeof (h3))) + { + for (i = 0; i < p; i++) + if (h3[i] != h3_ref[i]) + { + fail ("R3_mult pattern %d coeff %d: got %d, want %d", + pattern, i, (int)h3[i], (int)h3_ref[i]); + break; + } + } + } +} + +int +main (int argc, char **argv) +{ + int last_argc = -1; + + if (argc) + { + argc--; + argv++; + } + + while (argc && last_argc != argc) + { + last_argc = argc; + if (!strcmp (*argv, "--")) + { + argc--; + argv++; + break; + } + else if (!strcmp (*argv, "--help")) + { + fputs ("usage: " PGM " [--verbose]\n", stdout); + exit (0); + } + else if (!strcmp (*argv, "--verbose")) + { + verbose++; + argc--; + argv++; + } + else if (!strncmp (*argv, "--", 2)) + { + fprintf (stderr, PGM ": unknown option '%s'\n", *argv); + exit (1); + } + } + + xgcry_control ((GCRYCTL_SET_VERBOSITY, (int) verbose)); + + if (!gcry_check_version (GCRYPT_VERSION)) + die ("version mismatch\n"); + + xgcry_control ((GCRYCTL_DISABLE_SECMEM, 0)); + xgcry_control ((GCRYCTL_INITIALIZATION_FINISHED, 0)); + xgcry_control ((GCRYCTL_ENABLE_QUICK_RANDOM, 0)); + + test_freeze_helpers (); + test_mult (); + + if (verbose) + fprintf (stderr, PGM ": %d error(s)\n", error_count); + return !!error_count; +} -- 2.53.0 From wk at gnupg.org Mon Aug 3 14:47:53 2026 From: wk at gnupg.org (Werner Koch) Date: Mon, 03 Aug 2026 14:47:53 +0200 Subject: [PATCH 08/10] md, cipher: allow internal users to skip fast random poll In-Reply-To: <20260802095515.2729589-8-jussi.kivilinna@iki.fi> (Jussi Kivilinna's message of "Sun, 2 Aug 2026 12:55:13 +0300") References: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> <20260802095515.2729589-8-jussi.kivilinna@iki.fi> Message-ID: <87fr0v5q12.fsf@jacob.g10code.de> On Sun, 2 Aug 2026 12:55, Jussi Kivilinna said: > 'md_open' and 'cipher_open' call '_gcry_fast_random_poll', which takes > pool lock, reads RDRAND and mixes CSPRNG pool. ML-KEM and ML-DSA > route SHAKE through the md interface and open one handle per polynomial, Sounds okay for me. The random poll things used to be effective when random bytes were scarce which is for most platforms not anymore the case. Shalom-Salam, Werner -- The pioneers of a warless world are the youth that refuse military service. - A. Einstein -------------- next part -------------- A non-text attachment was scrubbed... Name: openpgp-digital-signature.asc Type: application/pgp-signature Size: 284 bytes Desc: not available URL: From wk at gnupg.org Mon Aug 3 14:50:49 2026 From: wk at gnupg.org (Werner Koch) Date: Mon, 03 Aug 2026 14:50:49 +0200 Subject: [PATCH 09/10] kyber: use strong random for encapsulation coins In-Reply-To: <20260802095515.2729589-9-jussi.kivilinna@iki.fi> (Jussi Kivilinna's message of "Sun, 2 Aug 2026 12:55:14 +0300") References: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> <20260802095515.2729589-9-jussi.kivilinna@iki.fi> Message-ID: <87bjbj5pw6.fsf@jacob.g10code.de> On Sun, 2 Aug 2026 12:55, Jussi Kivilinna said: > 'randombytes' used GCRY_VERY_STRONG_RANDOM for both key generation and > encapsulation. Encapsulation coins are per message ephemeral value, same > as ECDH ephemeral secret in 'ecc-ecdh.c' and as coins for sntrup761 and That is a good idea. BTW, we also need to do some internal changes to the Kyber implementation so that we can get the seed value back using the gcry_pk_genkey inteface. Although not yet NSA^WNIST approved, using the seed as private key is what IETF and other specifications prefer. Salam-Shalom, Werner -- The pioneers of a warless world are the youth that refuse military service. - A. Einstein -------------- next part -------------- A non-text attachment was scrubbed... Name: openpgp-digital-signature.asc Type: application/pgp-signature Size: 284 bytes Desc: not available URL: From gniibe at fsij.org Thu Aug 6 05:00:16 2026 From: gniibe at fsij.org (NIIBE Yutaka) Date: Thu, 6 Aug 2026 12:00:16 +0900 Subject: [PATCH] cipher:ecc: Add curve ietf25 with exact RFC8410 semantics. Message-ID: <2cc9475876bd579357d72405a57f8a23594a5545.1785984705.git.gniibe@fsij.org> * cipher/ecc-curves.c ("ietf25"): Add. (domain_parms): Add "ietf25". (find_domain_parms_idx): Allow curve with no other name. * tests/curves.c (N_CURVES): Increment. * tests/t-cv25519.c (test_cv_hl25): New. (test_cv): Add test_cv_hl25. -- GnuPG-bug-id: 8400 Signed-off-by: NIIBE Yutaka --- cipher/ecc-curves.c | 16 ++++++- tests/curves.c | 2 +- tests/t-cv25519.c | 112 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 2 deletions(-) -------------- next part -------------- A non-text attachment was scrubbed... Name: 0001-cipher-ecc-Add-curve-ietf25-with-exact-RFC8410-seman.patch Type: text/x-patch Size: 5684 bytes Desc: not available URL: From gniibe at fsij.org Thu Aug 6 07:35:28 2026 From: gniibe at fsij.org (NIIBE Yutaka) Date: Thu, 6 Aug 2026 14:35:28 +0900 Subject: [PATCH] cipher:kdf:argon2: Validate parallelism. Message-ID: <1d881a41f819cbedbe4dbce8078b8f751cc0e220.1785994518.git.gniibe@fsij.org> * cipher/kdf.c (ARGON2_PARALLELISM_MAX): New. (argon2_init): Validate with ARGON2_PARALLELISM_MAX. -- GnuPG-bug-id: 8373 Signed-off-by: NIIBE Yutaka --- cipher/kdf.c | 6 ++++++ 1 file changed, 6 insertions(+) -------------- next part -------------- A non-text attachment was scrubbed... Name: 0001-cipher-kdf-argon2-Validate-parallelism.patch Type: text/x-patch Size: 723 bytes Desc: not available URL: From gniibe at fsij.org Thu Aug 6 08:16:54 2026 From: gniibe at fsij.org (NIIBE Yutaka) Date: Thu, 6 Aug 2026 15:16:54 +0900 Subject: [PATCH] cipher:kdf:baloon: Validate parameters. Message-ID: <74f0c825951a6004435bba12bcbfeb2fd0e9d662.1785997008.git.gniibe@fsij.org> * cipher/kdf.c (BALLOON_TIMECOST_MAX): New. (BALLOON_PARALLELISM_MAX): New. (balloon_open): Validate parameters with those constants. Calculate the multiplication in 64-bit. -- GnuPG-bug-id: 8383 Signed-off-by: NIIBE Yutaka --- cipher/kdf.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) -------------- next part -------------- A non-text attachment was scrubbed... Name: 0001-cipher-kdf-baloon-Validate-parameters.patch Type: text/x-patch Size: 1110 bytes Desc: not available URL: From wk at gnupg.org Thu Aug 6 16:12:20 2026 From: wk at gnupg.org (Werner Koch) Date: Thu, 06 Aug 2026 16:12:20 +0200 Subject: [PATCH] cipher:ecc: Add curve ietf25 with exact RFC8410 semantics. In-Reply-To: <2cc9475876bd579357d72405a57f8a23594a5545.1785984705.git.gniibe@fsij.org> (NIIBE Yutaka via Gcrypt-devel's message of "Thu, 6 Aug 2026 12:00:16 +0900") References: <2cc9475876bd579357d72405a57f8a23594a5545.1785984705.git.gniibe@fsij.org> Message-ID: <87ldaj1gor.fsf@jacob.g10code.de> Hi! Thanks for the patch. My wk/rfc9980 branch does now work properly when using this patch. I did a minor modification: - { "ietf25" }, /* rfc9580 */ + { "ietf25", "1.3.6.1.4.1.11591.15.25" },/* rfc9580 */ which should make > (find_domain_parms_idx): Allow curve with no other name. superflous, but it doesn't harm to have it anyway. I added the new OID, so that we have a uniform system. RFC9580 does not use OIDs for the new algos but I think it is better to have it. from doc/DETAILS which reflects the parts of the GNU arc: 1.3.6.1.4.1.11591.15 EllipticCurves 1.3.6.1.4.1.11591.15.1 Ed25519 1.3.6.1.4.1.11591.15.25 ietf25 The curve ietf25 is actually X25519 as specified by RFC-7748. We used the name X25519 already as an alias for Curve25519 in Libgcrypt but with slighly different semantics. Iam going to update gnupg.org/oids.html also. The GNU site is currently not reachable but we should tell them the new entry also. Shalom-Salam, Werner -- The pioneers of a warless world are the youth that refuse military service. - A. Einstein -------------- next part -------------- A non-text attachment was scrubbed... Name: openpgp-digital-signature.asc Type: application/pgp-signature Size: 284 bytes Desc: not available URL: From jussi.kivilinna at iki.fi Thu Aug 6 19:29:03 2026 From: jussi.kivilinna at iki.fi (Jussi Kivilinna) Date: Thu, 6 Aug 2026 20:29:03 +0300 Subject: [PATCH 08/10] md, cipher: allow internal users to skip fast random poll In-Reply-To: <87fr0v5q12.fsf@jacob.g10code.de> References: <20260802095515.2729589-1-jussi.kivilinna@iki.fi> <20260802095515.2729589-8-jussi.kivilinna@iki.fi> <87fr0v5q12.fsf@jacob.g10code.de> Message-ID: On 03/08/2026 15:47, Werner Koch via Gcrypt-devel wrote: > On Sun, 2 Aug 2026 12:55, Jussi Kivilinna said: > >> 'md_open' and 'cipher_open' call '_gcry_fast_random_poll', which takes >> pool lock, reads RDRAND and mixes CSPRNG pool. ML-KEM and ML-DSA >> route SHAKE through the md interface and open one handle per polynomial, > > Sounds okay for me. The random poll things used to be effective when > random bytes were scarce which is for most platforms not anymore the > case. > Another approach that I was thinking of would have be to limit md_open/cipher_open random pool to first md_open/cipher_open call per application. But maybe that would be detrimental for platforms with limited random sources. -Jussi From gniibe at fsij.org Fri Aug 21 07:39:26 2026 From: gniibe at fsij.org (NIIBE Yutaka) Date: Fri, 21 Aug 2026 14:39:26 +0900 Subject: [PATCH] cipher:rsa:pkcs1: Reject shorter PS on decryption. Message-ID: <5212e50204f007ffdc07d05c81180984d2989feb.1787290759.git.gniibe@fsij.org> * cipher/rsa-common.c (_gcry_rsa_pkcs1_decode_for_enc): Validate the length of PS. -- GnuPG-bug-id: 8393 Reported-by: JEAN Jeremy Signed-off-by: NIIBE Yutaka --- cipher/rsa-common.c | 6 ++++++ 1 file changed, 6 insertions(+) -------------- next part -------------- A non-text attachment was scrubbed... Name: 0001-cipher-rsa-pkcs1-Reject-shorter-PS-on-decryption.patch Type: text/x-patch Size: 666 bytes Desc: not available URL: From gniibe at fsij.org Tue Aug 25 09:05:56 2026 From: gniibe at fsij.org (NIIBE Yutaka) Date: Tue, 25 Aug 2026 16:05:56 +0900 Subject: [PATCH] cipher:rsa:pkcs1: Fix the condition of frame length. Message-ID: <2ef95711d898d65609c232f4f61e38efd066a7bd.1787641542.git.gniibe@fsij.org> * cipher/rsa-common.c (_gcry_rsa_pkcs1_encode_for_enc): Fix the validation of NFRAME and VALUELEN. -- GnuPG-bug-id: 8394 Reported-by: JEAN Jeremy Signed-off-by: NIIBE Yutaka --- cipher/rsa-common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) -------------- next part -------------- A non-text attachment was scrubbed... Name: 0001-cipher-rsa-pkcs1-Fix-the-condition-of-frame-length.patch Type: text/x-patch Size: 493 bytes Desc: not available URL: From gniibe at fsij.org Wed Aug 26 09:21:40 2026 From: gniibe at fsij.org (NIIBE Yutaka) Date: Wed, 26 Aug 2026 16:21:40 +0900 Subject: [PATCH] cipher:rsa:oaep: Validate all-zero PS. Message-ID: <3b36166f1fa98d2bfd1d174377ec736e7e602d5b.1787728888.git.gniibe@fsij.org> * cipher/rsa-common.c (_gcry_rsa_oaep_decode): Reject non-zero PS. -- GnuPG-bug-id: 8390 Reported-by: JEAN Jeremy Signed-off-by: NIIBE Yutaka --- cipher/rsa-common.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) -------------- next part -------------- A non-text attachment was scrubbed... Name: 0001-cipher-rsa-oaep-Validate-all-zero-PS.patch Type: text/x-patch Size: 792 bytes Desc: not available URL: From gniibe at fsij.org Thu Aug 27 07:41:38 2026 From: gniibe at fsij.org (NIIBE Yutaka) Date: Thu, 27 Aug 2026 14:41:38 +0900 Subject: [PATCH] cipher:rsa:pss: Validate the length of hashed input. Message-ID: <612eb8c157c057feeaf17d30644fd65b05c5ea25.1787809245.git.gniibe@fsij.org> * cipher/rsa-common.c (_gcry_rsa_pss_verify): Reject invalid input. -- GnuPG-bug-id: 8391 Reported-by: JEAN Jeremy Signed-off-by: NIIBE Yutaka --- cipher/rsa-common.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) -------------- next part -------------- A non-text attachment was scrubbed... Name: 0001-cipher-rsa-pss-Validate-the-length-of-hashed-input.patch Type: text/x-patch Size: 574 bytes Desc: not available URL: From gniibe at fsij.org Fri Aug 28 08:34:42 2026 From: gniibe at fsij.org (NIIBE Yutaka) Date: Fri, 28 Aug 2026 15:34:42 +0900 Subject: [PATCH] cipher:rsa:pss: Fix SALT-LENGTH handling. Message-ID: <3ed69d3fb85bac0901dc5b4899d7983cf1f7bbe7.1787898845.git.gniibe@fsij.org> * cipher/pubkey-util.c (_gcry_pk_util_data_to_mpi): For PUBKEY_OP_SIGN, just like for PUBKEY_OP_VERIFY, reject larger SALT-LENGTH as the comment says. Fix releasing LIST on error. -- Fixes-commit: 0bd8137e68c201b6c2290710e348aaf57efa2b2e GnuPG-bug-id: 8377 Reported-by: JEAN Jeremy Signed-off-by: NIIBE Yutaka --- cipher/pubkey-util.c | 6 ++++++ 1 file changed, 6 insertions(+) -------------- next part -------------- A non-text attachment was scrubbed... Name: 0001-cipher-rsa-pss-Fix-SALT-LENGTH-handling.patch Type: text/x-patch Size: 952 bytes Desc: not available URL: From ametzler at bebt.de Fri Aug 28 18:55:21 2026 From: ametzler at bebt.de (Andreas Metzler) Date: Fri, 28 Aug 2026 18:55:21 +0200 Subject: 1.12.3 library versioning Message-ID: Hello, 1.12.3's NEWS says: - Due to the minor API updates and but with no newer branch released the SO name has been updated. On linux we went from libgcrypt.so.20.7.2 to libgcrypt.so.20.8.8 which seemed strange. Looking at configure.ac we find: 8x------------------------------------------------------- # LT Version numbers, remember to change them just *before* a release. # (Code changed: REVISION++) # (Interfaces added/removed/changed: CURRENT++, REVISION=0) # (Interfaces added: AGE++) # (Interfaces removed: AGE=0) # # (Interfaces removed: CURRENT++, AGE=0, REVISION=0) # (Interfaces added: CURRENT++, AGE++, REVISION=0) # (No interfaces changed: REVISION++) -LIBGCRYPT_LT_CURRENT=27 -LIBGCRYPT_LT_AGE=7 -LIBGCRYPT_LT_REVISION=2 +LIBGCRYPT_LT_CURRENT=28 +LIBGCRYPT_LT_AGE=8 +LIBGCRYPT_LT_REVISION=8 8x------------------------------------------------------- Using LT versioning we should have gone from LC=27 LA=7 LR=2 to one of * LC=27 LA=7 LR=3 (code changed, interface unchanged) * LC=28 LA=8 LR=0 (Interfaces added, none removed) * LC=28 LA=0 LR=0 (Interfaces removed, none removed) instead of LC=28 LA=8 LR=8 (Interfaces added, none removed, followed by 8 releases without ABI changes.) I guess LR=8 was a typo but I wanted to doublecheck whether I am missing something. I do not propose to switching to LR=0, not jumping backward in versioning is better than fixing a very minor cosmetic thingy. thanks, cu Andreas -- "You people are noisy," Nia said. I made the gesture of agreement.