State of PRNG code in GMP

Emmanuel Thomé Emmanuel.Thome at inria.fr
Tue Jun 2 10:11:10 UTC 2020


Hi,

While you're at it touching the PRNG code, could you give consideration
to this feature request ? (adding gmp_randstate_ptr , and more generally
documenting {srcptr,ptr} types). I don't mind if you don't feel like
checking this in, but feedback would be appreciated.

https://gmplib.org/list-archives/gmp-devel/2017-August/004664.html
https://gmplib.org/list-archives/gmp-devel/2018-May/004964.html
https://gmplib.org/list-archives/gmp-devel/2018-June/005004.html
https://gmplib.org/list-archives/gmp-devel/2019-January/005182.html

Best,

E.

On Tue, Jun 02, 2020 at 11:22:19AM +0200, Torbjörn Granlund wrote:
> Thanks Pedro for a quick and thorough answer!  Much appreciated!
> 
> Pedro Gimeno <gmpdevel at formauri.es> writes:
> 
>   > Question 1: Why is _mp_lc wrapped in a union?
> 
>   Historical reasons. It was that way when I implemented MT.
> 
> I see.
> 
>   > Question 2: "_lc" = Linear Congruential? This is supposed to be a
>   > generic structure, right?
> 
>   Historical reasons again :)
> 
> Surely the field _lc could be be renamed to something less confusing
> without dire compatibility consequences.  :-)
> 
>   Yes, LC is Linear Congruential. It was the only available generator
>   when I started working on the MT one. The struct had the exact same
>   shape before I took it, and I fitted the new generator into that
>   struct trying to use the available fields in a backwards compatible
>   way. Kevin went quite paranoid about ABI compatibility, to the point
>   of not wanting to add another pointer member to the union for fear of
>   changing the size of the structure. I think his words were along the
>   lines of "I don't know if it would be compatible, it should be, but
>   better safe than sorry". So my patch included these changes:
> 
> I see, I don't know if it is a valid compatibility concern; perhaps the
> C standard does not pin down that pointers to two different struct types
> need to be the same size, but I wouldn't have worried about that.
> 
>   The pre-existing random structure was very limited, and building a
>   compatible generic generator layer on top of it while keeping
>   backwards compatibility needed some compromises, like having to
>   allocate a function pointers struct instead of using the random state
>   struct itself to store them; abusing the pointer field in the seed's
>   mpz_t for the state, and using the macros RNG_FNPTR() and RNG_STATE()
>   to access those in a more programmer-friendly way.
> 
> I understand.
> 
>   The plan was to get rid of the struct issues in the next ABI breakage,
>   but I guess no one took note of that with respect to the generators,
>   or at least no one did anything about it (we've broken the ABI since
>   GMP 4.0.1, right?)
> 
> What breakage happened for 4.0.1?
> 
>   LC (2exp) is the default and the only choice for gmp_randinit, which
>   is considered obsolete. When I implemented the MT patch, there was
>   also a disabled and probably non-functional Blum-Blum-Shub generator.
> 
>   I'm not too sure why I removed gmp_randinit_lc, but something about it
>   being broken rings a bell, so it's possible that Kevin told me to do
>   that.
> 
> It's strange, but I cannot recall the reasons either.
> 
>   >    gmp_randinit (my_randstate, GMP_RAND_ALG_DEFAULT)
>   >
>   > to also choose Mersenne Twister.  But GMP_RAND_ALG_DEFAULT =
>   > GMP_RAND_ALG_LC as per the enum definition.
> 
>   No, gmp_randinit is declared obsolete. You're supposed to use the
>   algorithm-specific gmp_randinit_xxxx for any supported algorithm xxxx.
> 
>   I don't know for sure why gmp_randinit was not adapted to the new
>   generators. Probably Kevin told me to do that but I don't remember the
>   rationale.
> 
> Perhaps it was to avoid more code than necessary to be pulled into a
> user binary?  With a generic init function, there will be a broad link
> deendency on all (init_[ALGORITHM] functions which may in turn pull in
> the actual generators.
> 
>   > Question 3: What algorithm does the call
>   >    gmp_randinit (my_randstate, GMP_RAND_ALG_DEFAULT)
>   > choose?  Is it Mersenne Twister?
> 
>   No, it should be LC if it hasn't changed since the time I wrote the patch.
> 
> I don't think it makes any sense to have gmp_randinit_default and
> gmp_randinit (..., GMP_RAND_ALG_DEFAULT) give different algorithms.
> 
>   > Perhaps we should start anew with a new set of random state functions
>   > and a new type (where all fields are actually used!).  The name
>   > gmp_prngstate_t might be useful.
> 
>   That would not be off the mark. Trying to build on the existing
>   facilities is what led to the current mess.
> 
> I'd like to have a coherent interface which also provide reentrant
> random functions to the mpn layer.  And in no case should mpn pull in
> mpz.
> 
> I'd like to keep Mersenne Twister, and add block cipher based PRNGs (AES
> and perhaps Salsa20r12).  We might also keep LC for historical or
> compatibility reasons.
> 
> Unfortunately, Mersenne Twister uses mpz, and I am not saying that that
> was a bad choice when you implemented it.  But in order to provide
> reentrant mpn PRNG functions, we either rewrite the relevant parts of MT
> to use mpn, or exclude it from a new mpn PRNG interface.
> 
> Here is some code I have tinkered with.
> 
> /* PRNG algorithm specific data for any constants, buffered random bits, or
>    other state.  The _mp_data field should typically point to a algorithm
>    specific struct.  The _mp_datasize field is used by generic code for
>    de-allocating and copying a gmp_prng_t in an algorithm agnostic manner.  */
> struct appdata {
>   void* _mp_data;
>   size_t _mp_datasize;
> };
> 
> struct prng {
>   void (*_prng_seed_fn) (struct appdata*, const mp_limb_t*, size_t);
>   void (*_prng_run_fn) (mp_limb_t*, size_t, struct appdata*);
>   struct appdata _mp_app;
> };
> typedef struct prng gmp_prng_t[1];
> 
> struct prng_aes {
>   uint32_t key[44];
>   uint32_t buf[4*8];
>   uint8_t n_in_buf;
> };
> 
> typedef enum {
>   GMP_PRNG_ALG_LC,  GMP_PRNG_ALG_MT,  GMP_PRNG_ALG_AES,
>   GMP_PRNG_ALG_LIM,  GMP_PRNG_ALG_DEFAULT = GMP_PRNG_ALG_AES
> } gmp_prng_alg;
> 
> void gmp_prng_lc_seed (struct appdata*, const mp_limb_t*, size_t);
> void gmp_prng_lc (mp_limb_t*, size_t, struct appdata*);
> void gmp_prng_mt_seed (struct appdata*, const mp_limb_t*, size_t);
> void gmp_prng_mt (mp_limb_t*, size_t, struct appdata*);
> void gmp_prng_aes_seed (struct appdata*, const mp_limb_t*, size_t);
> void gmp_prng_aes (mp_limb_t*, size_t, struct appdata*);
> 
> /* Generic PRNG init function.  Is this really a good idea?  A problem is that
>    this will pull in all PRNG code into a binary which uses just one
>    algorithms.  It even pulls in mpz functions in a mpn-only program if any
>    PRNG uses mpz. */
> void
> gmp_prng_init (gmp_prng_t rs, gmp_prng_alg alg, ...)
> {
>   if (alg == GMP_PRNG_ALG_LC)
>     {
>       rs->_prng_run_fn = gmp_prng_lc;
>       rs->_prng_seed_fn = gmp_prng_lc_seed;
>       /* ... */
>     }
>   else if (alg == GMP_PRNG_ALG_MT)
>     {
>       /* ... */
>     }
>   else if (alg == GMP_PRNG_ALG_AES)
>     {
>       struct prng_aes* pa;
> 
>       rs->_prng_run_fn = gmp_prng_aes;
>       rs->_prng_seed_fn = gmp_prng_aes_seed;
> 
>       pa = malloc (sizeof (struct prng_aes));
>       rs->_mp_app._mp_data = pa;
>       rs->_mp_app._mp_datasize = sizeof (struct prng_aes);
>       pa->n_in_buf = 0xff;	/* "not initialied yet" */
>     }
>   else
>     {
>       /* use whatever we define as default */
>       /* ... */
>     }
> }
> 
> void
> mpn_prng_uniform (mp_ptr rp, mp_size_t rn, gmp_prng_t rs)
> {
>   (rs->_prng_run_fn) (rp, rn, &(rs->_mp_app));
> }
> 
> 
> 
> -- 
> Torbjörn
> Please encrypt, key id 0xC8601622
> _______________________________________________
> gmp-devel mailing list
> gmp-devel at gmplib.org
> https://gmplib.org/mailman/listinfo/gmp-devel


More information about the gmp-devel mailing list