Cryptographically-strong random UUIDs v4 with Elm 0.19?

Hi,

Given that Elm 0.19 Random seed is still 32 bits and Zinggi/elm-uuid has not been updated, how do you folks generate UUIDs with a very low chance of collision even with thousands of clients?

If you use ports, I’m interested by the kind of API you use at the port and Elm level.

I’m thinking about providing a buffer of cryptographically-strong random UUIDs in flags then have a port Cmd to refill it, but I’m not sure what to do if I don’t have enough during a single update, also I struggle to find a nice Elm API to use in SPA pages that also prevents duplicating or forgetting the port Cmd to request a refill of the buffer on use (maybe the best is to check the buffer size after each update and batch a port refill Cmd if it is not full?).

1 Like

Hey there!

I haven’t had the time to update my packages yet.
But if it’s useful to you, please open an issue to motivate me a bit more :wink:
Or if you want to help out, I’m also open to pull requests.

You could of course also just use a port for it, but I think a generator is nicer (and gives you more control).

If you go for the port solution, a buffer sounds kinda weird to me.
It seems much easier to just compute a new UUID on demand.
I’d use something like port receiveUUID : (String -> msg) -> Sub msg
and a port generateNewUUID : () -> Cmd msg for that.

2 Likes

Thank you @zinggi, I don’t think that I can open an issue because your Zinggi/elm-uuid repo is a fork. Also I’m not sure how you could do it as effect managers are not possible anymore :thinking: Still for now I use danyx23/elm-uuid, so I would love to have a 128 bits seed option instead of converting to ports.

Concerning your port proposal, I have not tried it so I may overlook something but it seems to me that the pain point is that because ports are not requests/reponses, I have to store somehow why I need the UUID, either in the model or serialized in the port command (I need random UUIDs for different reasons in my app).

In contrast, with a Generator, I can simply create a Cmd msg that will trigger the wanted message once generation is done.

On a side note, something that seems lacking with elm/random is the possibility to return a Task, preventing to use Task.mapX. For example I often need simultaneously a random UUID and a Time.Posix to timestamp the creation of a new object and it makes harder to use Random.generate in this case (Random.step does not have this issue but requires to track the seed which is not always handy).

I don’t think that I can open an issue because your Zinggi/elm-uuid repo is a fork

Try now, I think it should work now.

I’m not sure how you could do it as effect managers are not possible anymore

It never was an effect manager, the update should be quite simple.

I have to store somehow why I need the UUID, either in the model or serialized in the port command

I wouldn’t store such information in the model. To provide more context to a port, I would just add the needed context to the command and then send the context back.
E.g. port generate : Reason -> Cmd msg and port newUUID : ({ reason : Reason, uuid : String} -> msg) -> Sub msg.
I think this is much nicer to manage, as every incoming message can be understood without looking at the current state.

On a side note, something that seems lacking with elm/random is the possibility to return a Task , preventing to use Task.mapX

I agree, having random as a Task would be nice.
You could work around this by passing in a Seed to the Task, e.g. like this:

taskWithRandomAndTime : Seed -> Task (Output, Seed)
taskWithRandomAndTime seed =
    Time.now
    |> Task.map (\now ->
        let
            (randVal, newSeed) = Random.step seed someGenerator
        in
        (process now randVal, newSeed)
    )
``
1 Like

Thank you! I will think about it a little more before pressurizing you though.

You’re right. I overlooked the fact that elm-random-pcg was using Time.now in 0.18 as a seed in generate. I guess this is why this function had the following warning:

Additionally, due to constraints on third-party packages, it’s possible that multiple commands sent at the same moment will return the same values.

This does not seem to be the case anymore now that it is integrated as an effect manager in elm/random (and the warning has been removed).

I also overlooked the fact that elm-random-pcg-extended does not provide the generate function, which makes sense without using an effect manager.

Is there a way to update Zinggi/elm-uuid without replacing elm/random?

This is what I meant by “serialized in the port”, because Reason will most likely be a custom type. But this is not that bad indeed.

Indeed, but the whole point of Random.generate is to avoid managing the seed manually.

Anyway thank you very much for your answer, this helps me clearing things up.

I’m not quite sure what you mean by that?
Zinggi/elm-uuid doesn’t depend on elm/random.
It does depend on Zinggi/elm-random-pcg-extended and Zinggi/elm-random-general which would have to be updated as well.

Sure, but unfortunately we can’t have that without an effect manager.

I think at some point it would be nice to have a package under elm-explorations that would provide access to Crypto.getRandomValues() and an effect manager that manages the seed of a cryptographically secure PRNG, but that’s a topic for another thread :wink:

That’s what I meant, sorry if it was not clear.

Everything is clear now, thank you very much.

1 Like

In the port based solution, if you need UUIDs from multiple places and may need multiple UUIDs in rapid succession, then you probably want to do something like:

  1. Figure out a way to give each localized need for a UUID a unique string-based identifier.
  2. Set up the command port to take a string identifier and an integer index and the subscription port to return both of these allow with the UUID string.
  3. For each of these localized needs, maintain a record of the form
{ localIdentifier : String
, toMsg : Dict Int (String -> MyMsg)
, lastIndex : Int
}

where MyMsg is your local message type. The dictionary provides a way to interpret results as they come in. The index identifies which request it goes with.

When you want to generate a new UUID, you take this structure and a function to convert a UUID string into a message, and it updates the structure along with returning a command built by calling the command port. In your subscription code, you subscribe to the subscription port if the dictionary is non-empty and direct the messages from the subscription at code which modifies this state logic and returns a Maybe MyMsg which when it contains a value will be the message you were waiting for.

This pattern works for other sorts of ports as well when trying to simulate the standard behavior of effects module-based commands of being able to direct the response back to the code that invoked the command. This pattern can also be wrapped up in code that hides most of it from the client requiring simply the plumbing to embed another piece of data within ones model. Depending on how these are being used, the hardest part could be making sure that local identifiers truly are unique which they need to be since they are how one tells that the subscription results are meant for oneself. (Maybe we need one more effects module to safely solve that problem: Give me a unique local ID. All it needs to do is maintain a global counter and hand out values.)

Mark

P.S. Ignore the functions stored in the model. They are a necessary evil in the general case. Effects managers store functions and we’re building this without the benefit of effects managers.

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.