Skip to content

Commit 019e3d9

Browse files
committed
Make it possible to save and restore the state of a ChaChaStream
Add a getstate() function that saves the state of a ChaChaStream as a ChaChaState NamedTuple, and add constructors to restore the ChaChaStream from that saved state.
1 parent 9333863 commit 019e3d9

File tree

4 files changed

+81
-4
lines changed

4 files changed

+81
-4
lines changed

src/ChaChaCiphers.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ include("generation.jl")
88

99
export ChaCha
1010
export ChaChaStream, ChaCha20Stream, ChaCha12Stream
11+
export getstate
1112

1213
end

src/core.jl

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using ChaChaCiphers.ChaCha: CHACHA_BLOCK_SIZE
22
using Random: AbstractRNG
3+
using StaticArrays
34

45
### UnsafeView
56

@@ -26,11 +27,34 @@ function Base.getindex(a::UnsafeView{T}, i::UnitRange) where T
2627
UnsafeView(pointer(a) + view_start, len)
2728
end
2829

30+
### ChaChaState
31+
32+
"""
33+
ChaChaState
34+
35+
A `NamedTuple` storing the current state of a ChaCha keystream. `ChaChaState`
36+
can be used to save and restore the state of a keystream.
37+
"""
38+
const ChaChaState = @NamedTuple begin
39+
key :: SVector{8,UInt32}
40+
nonce :: UInt64
41+
counter :: UInt64
42+
position :: Int
43+
doublerounds :: Int
44+
end
45+
46+
2947
### AbstractChaChaStream
3048

3149
# Number of bytes to store in an AbstractChaChaStream
3250
const STREAM_BUFFER_SIZE = CHACHA_BLOCK_SIZE
3351

52+
if STREAM_BUFFER_SIZE % CHACHA_BLOCK_SIZE != 0
53+
error("STREAM_BUFFER_SIZE must be a multiple of the CHACHA_BLOCK_SIZE")
54+
end
55+
56+
const STREAM_BUFFER_BLOCKS = STREAM_BUFFER_SIZE ÷ CHACHA_BLOCK_SIZE
57+
3458
"""
3559
AbstractChaChaStream
3660
@@ -44,4 +68,21 @@ abstract type AbstractChaChaStream <: AbstractRNG end
4468
T(SVector{8,UInt32}(rand(RandomDevice(), UInt32, 8)))
4569
(::Type{T})(key; kws...) where {T <: AbstractChaChaStream} =
4670
T(key, UInt64(0); kws...)
71+
(::Type{T})(state::ChaChaState) where {T <: AbstractChaChaStream} =
72+
T(state.key, state.nonce, state.counter, state.position; doublerounds=state.doublerounds)
4773

74+
"""
75+
getstate(stream::AbstractChaChaStream)
76+
77+
Return a `NamedTuple` containing enough state of the input
78+
`AbstractChaChaStream` to be able to reproduce it.
79+
"""
80+
function getstate(stream::AbstractChaChaStream)
81+
(;
82+
:key => key(stream),
83+
:nonce => nonce(stream),
84+
:counter => counter(stream) - STREAM_BUFFER_BLOCKS,
85+
:position => position(stream),
86+
:doublerounds => doublerounds(stream),
87+
)
88+
end

src/keystream.jl

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,28 @@ mutable struct ChaChaStream <: AbstractChaChaStream
1717
position :: Int
1818
doublerounds :: Int
1919

20-
function ChaChaStream(key, nonce, counter = UInt64(0); doublerounds = 10)
21-
if doublerounds (6, 10)
22-
error("`doublerounds` must be equal to 6 or 10")
20+
function ChaChaStream(
21+
key,
22+
nonce,
23+
counter = UInt64(0),
24+
position = 1;
25+
doublerounds = 10
26+
)
27+
if doublerounds < 0 || !iseven(doublerounds)
28+
error("`doublerounds` must be an even positive number")
2329
end
2430

2531
key = SVector{8,UInt32}(key)
2632
buffer = MVector{STREAM_BUFFER_SIZE,UInt8}(undef)
2733
stream = new(key, nonce, counter, buffer, 1, doublerounds)
2834
_refresh_buffer!(stream)
35+
stream.position = position
36+
37+
stream
2938
end
3039
end
3140

3241
# Constructors
33-
3442
ChaCha20Stream(args...) = ChaChaStream(args...; doublerounds=10)
3543
ChaCha12Stream(args...) = ChaChaStream(args...; doublerounds=6)
3644

@@ -39,6 +47,12 @@ Base.show(io::IO, rng::ChaChaStream) =
3947

4048
buffer_size(stream::ChaChaStream) = STREAM_BUFFER_SIZE - stream.position
4149

50+
key(stream::ChaChaStream) = stream.key
51+
nonce(stream::ChaChaStream) = stream.nonce
52+
counter(stream::ChaChaStream) = stream.counter
53+
position(stream::ChaChaStream) = stream.position
54+
doublerounds(stream::ChaChaStream) = stream.doublerounds
55+
4256
@generated function _refresh_buffer!(stream::ChaChaStream)
4357
local blocks_in_buffer, rem = divrem(STREAM_BUFFER_SIZE, CHACHA_BLOCK_SIZE)
4458
local words_per_block = div(CHACHA_BLOCK_SIZE, sizeof(UInt32))

test/test_keystream.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,26 @@ using Test
4646
@test isapprox(mean(samples), 0., atol=1e-2)
4747
@test isapprox(std(samples), 1., atol=1e-2)
4848
end
49+
50+
@testset "Save and restore a keystream" begin
51+
# Create a stream, run some operations on it,
52+
# and save its state. Ensure that we can
53+
# reproduce the stream from its saved state.
54+
stream = ChaCha12Stream()
55+
rand(stream, UInt8, 3_000)
56+
stream_repro = getstate(stream) |> ChaChaStream
57+
58+
rand_orig = randn(stream, 5_000)
59+
rand_repro = randn(stream_repro, 5_000)
60+
@test rand_orig == rand_repro
61+
62+
stream = ChaCha20Stream()
63+
randn(stream, Float64, 3_000)
64+
stream_repro = getstate(stream) |> ChaChaStream
65+
66+
rand_orig = rand(stream, 1:10, 1024)
67+
rand_repro = rand(stream_repro, 1:10, 1024)
68+
@test rand_orig == rand_repro
69+
end
4970
end
5071

0 commit comments

Comments
 (0)