Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[enhancement] Safe serialization of RNG providers #598

Closed
WasabiThumb opened this issue May 28, 2023 · 5 comments
Closed

[enhancement] Safe serialization of RNG providers #598

WasabiThumb opened this issue May 28, 2023 · 5 comments

Comments

@WasabiThumb
Copy link

WasabiThumb commented May 28, 2023

For a project I am working on, I am utilizing pure-rand version 6.0.2 for it's xoroshiro128plus functionality. However, I wanted to be able to serialize the full state to a JSON-compatible and ideally light format for transmission to be reconstructed by the client, for the purposes of a "shared random" that can make random events have predictable outcomes without waiting for a response from the server (I am aware of the pitfalls of this system).

I have resorted to accessing the fields s01, s00, s11, s10 on the object (permalink), which does allow exactly what I wanted. Ideally though, this would be top-level API, as relying on undocumented fields has prevented my project from receiving patches from the upstream. Clarification on whether it's safe to assume these fields won't change would be valuable in the short-term.

For reference, here is the relevant code I used for serialization. I am aware that a Uint8Array is a more efficient representation when working with websockets, but for my own sanity and the speed of my project development I've elected for the usage of JSON packets.

type XoroshiroData = { s01: number, s00: number, s11: number, s10: number }

(in class PureRandom, where this.random is a RandomGenerator)

    serialize(): number[] {
        try {
            const data: XoroshiroData = this.random as unknown as XoroshiroData;
            return [data.s00, data.s01, data.s10, data.s11];
        } catch (e) {
            console.warn("Failed to cast RandomGenerator", e);
        }
        const seed: number = this.random.next()[0];
        return [~seed, -1, 0, seed | 0];
    }

    serializeString(): string {
        const base: number[] = this.serialize();
        const dv: DataView = new DataView(new ArrayBuffer(base.length * 4));

        // disgusting loop, but DataView can ensure endian-ness
        let i=0;
        while ((i * 4) < dv.byteLength && i < base.length) {
            dv.setInt32(i * 4, base[i], true);
            i++;
        }

        const bytes: number[] = [];
        for (let i=0; i < dv.byteLength; i++) {
            bytes.push(dv.getUint8(i));
        }
        return encodeBase64(new Uint8Array(bytes));
    }

    static deserializeString(data: string): PureRandom {
        let bytes: Uint8Array;
        try {
            bytes = decodeBase64(data);
        } catch (e) {
            console.warn("PureRandom data string couldn't be decoded!", e);
            return new PureRandom();
        }
        const dv: DataView = new DataView(bytes.buffer);
        const arr: number[] = [];
        let i=0;
        while (i < (dv.byteLength - 3)) {
            arr.push(dv.getInt32(i, true));
            i += 4;
        }
        return this.deserialize(arr);
    }

    static deserialize(array: number[]): PureRandom {
        if (array.length < 4) {
            console.warn("PureRandom data must have a length of at least 4");
            let seed: number | null = array.length > 0 ? ~array[0] : null;
            return new PureRandom(seed);
        }

        const rng: RandomGenerator = xoroshiro128plus(~array[0]);
        try {
            const unsafe: XoroshiroData = rng as unknown as XoroshiroData;
            unsafe.s00 = array[0];
            unsafe.s01 = array[1];
            unsafe.s10 = array[2];
            unsafe.s11 = array[3];
        } catch (e) {
            console.warn("Failed to cast RandomGenerator", e);
        }

        return new PureRandom(rng);
    }

I would make a PR, but the "best" way to serialize an RNG provider is entirely subjective. My hand has been placed on the scales. ;)

@WasabiThumb WasabiThumb changed the title Safe serialization of RNG providers [enhancement] Safe serialization of RNG providers May 28, 2023
@WasabiThumb
Copy link
Author

Above implementation in action:
image

@dubzzz
Copy link
Owner

dubzzz commented Jun 25, 2023

Sorry for the delay. It might indeed be useful to have some kind of serialize-like API. Something like rng.readState() and xoroshiro128.fromState() might be good additions. Concerning the idea to serialize it as base64, I'd rather let it on user side or expose it as an extra API outside of the generators themselves like: stateAsString() and stateFromString().

@dubzzz
Copy link
Owner

dubzzz commented Mar 20, 2024

@WasabiThumb Version 6.1.0 comes with a way to extract the internal state of a rng as an array of numerical values. Based on it you can recreate the very same generator.

@dubzzz
Copy link
Owner

dubzzz commented Jun 27, 2024

Closing for now since 6.1.0 offers ways to implement such serialization.

@dubzzz dubzzz closed this as completed Jun 27, 2024
@WasabiThumb
Copy link
Author

Great stuff! Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants