libsodium.js
libsodium.js copied to clipboard
Serialize state_out and state_in
Hello,
I'm wondering if there is a way to serialize the state_in and the state_out from crypto_secretstream_xchacha20poly1305_init_push and crypto_secretstream_xchacha20poly1305_init_pull in order to store them in a persistent memory.
My first use case is a client / server exchanges where the server is stateless and cannot keep in RAM the states.
My second use case is a client / server exchanges where the server is a set of multiple machines behind a load balancer where the client has no guarantee to hit the same machine in a row.
Thanks!
Yes, that should totally be doable. The state is just the address of a 52 byte array, that can be safely moved to different hosts.
@jedisct1 thanks, where that change should happen? in the parent libsodium library?
No, just in the JavaScript code. I think it can be done even without changing libsodium-wrappers.
First, I simply printed the the state_in and and state_out value. They seems to be numbers, which is not exactly what the @types/libsodium-wrappers is expecting.
What's weird too is that even if I rebuild the project and run multiple times i always get the same numbers.
state_in = 102240
state_out = 102144
So I tried to read through the wrapper code to understand what's going on.
I took a look at where the crypto_secretstream_xchacha20poly1305_init_pull is used:
var h = new u(52).address;
if (0 == (0 | a._crypto_secretstream_xchacha20poly1305_init_pull(h, n, c))) {
var p = h;
return g(_),
p
}
b(_, "invalid usage")
In this code:
- h is a new address for the state_in
- n is the other party header
- c is a shared key
and then where crypto_secretstream_xchacha20poly1305_init_push is used:
var s = new u(52).address,
c = new u(0 | a._crypto_secretstream_xchacha20poly1305_headerbytes()),
o = c.address;
if (t.push(o), 0 == (0 | a._crypto_secretstream_xchacha20poly1305_init_push(s, o, _))) {
var h = {
state: s,
header: y(c, r)
};
return g(t),
h
}
b(t, "invalid usage")
In this code:
- s is a new address for the state_out
- o is a new address for the secret stream header
- _ is a shared key
Now taking a look at the function u() that seems to allocate some memory.
function u(e) {
this.length = e,
this.address = v(e)
}
function d(e) {
var r = v(e.length);
return a.HEAPU8.set(e, r),
r
}
function v(e) {
var r = a._malloc(e);
if (0 === r) throw {
message: "_malloc() failed",
length: e
};
return r
}
Sounds like it's a binding to a malloc (probably to the C code?).
What I get from this point is that the state and the header are constructed the same way with the u() function, and from the output of the function we get the address in a Uint8Array which is easily convertible to any other format.
The call to y(c, r) seems to do the trick, so let's look at the code:
function y(e, r) {
var a = r || t;
if (!i(a)) throw new Error(a + " output format is not available");
if (e instanceof u) {
if ("uint8array" === a) return e.to_Uint8Array();
if ("text" === a) return s(e.to_Uint8Array());
if ("hex" === a) return c(e.to_Uint8Array());
if ("base64" === a) return p(e.to_Uint8Array(), o.URLSAFE_NO_PADDING);
throw new Error('What is output format "' + a + '"?')
}
if ("object" == typeof e) {
for (var _ = Object.keys(e), n = {},
h = 0; h < _.length; h++) n[_[h]] = y(e[_[h]], a);
return n
}
if ("string" == typeof e) return e;
throw new TypeError("Cannot format output")
}
So the .to_Uint8Array() method from the prototype is converting this address to the needed Uint8Array, which look like this in the code:
return u.prototype.to_Uint8Array = function() {
var e = new Uint8Array(this.length);
return e.set(a.HEAPU8.subarray(this.address, this.address + this.length)),
e
},
In the end the issue relies on the fact that for the state we return only the address var s = new u(52).address while for the header we convert the entire buffer to a to_Uint8Array().
Two options:
- either the function outputs the buffer content and not just the address
- a method is available to read the memory from the address that is returned, similar to :
a.HEAPU8.subarray(this.address, this.address + this.length)
What do you think?