forked from microsoft/SEAL
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Serialization.cs
430 lines (377 loc) · 20.3 KB
/
Serialization.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;
namespace Microsoft.Research.SEAL
{
/// <summary>
/// A type to describe the compression algorithm applied to serialized data.
/// Ciphertext and key data consist of a large number of 64-bit words storing
/// integers modulo prime numbers much smaller than the word size, resulting in
/// a large number of zero bytes in the output. Any compression algorithm should
/// be able to clean up these zero bytes and hence compress both ciphertext and
/// key data.
/// </summary>
public enum ComprModeType : byte
{
/// <summary>No compression is used.</summary>
None = 0,
/// <summary>Use ZLIB compression.</summary>
ZLIB = 1,
/// <summary>Use Zstandard compression.</summary>
ZSTD = 2
}
/// <summary>Class to provide functionality for serialization.</summary>
/// <remarks>
/// Class to provide functionality for serialization. Most users of the library
/// should never have to call these functions explicitly, as they are called
/// internally by functions such as Ciphertext.Save and Ciphertext.Load.
/// </remarks>
public abstract class Serialization
{
/// <summary>
/// The compression mode used by default.
/// </summary>
public static readonly ComprModeType ComprModeDefault = ((Func<ComprModeType>)(() => {
NativeMethods.Serialization_ComprModeDefault(out byte comprMode);
return (ComprModeType)comprMode;
}))();
/// <summary>The magic value indicating a Microsoft SEAL header.</summary>
public static readonly ushort SEALMagic = ((Func<ushort>)(() => {
NativeMethods.Serialization_SEALMagic(out ushort sealMagic);
return sealMagic;
}))();
/// <summary>The size in bytes of the SEALHeader.</summary>
public static readonly byte SEALHeaderSize = ((Func<byte>)(() => {
NativeMethods.Serialization_SEALHeaderSize(out byte sealHeaderSize);
return sealHeaderSize;
}))();
/// <summary>Struct to contain header information for serialization.</summary>
/// <remarks>
/// Struct to contain header information for serialization. The size of the header is 16 bytes and it consists
/// of the following fields:
///
/// 1. a magic number identifying this is a SEALHeader struct (2 bytes)
/// 2. size in bytes of the SEALHeader struct (1 byte)
/// 3. Microsoft SEAL's major version number (1 byte)
/// 4. Microsoft SEAL's minor version number (1 byte)
/// 5. a ComprModeType indicating whether data after the header is compressed (1 byte)
/// 6. reserved for future use and data alignment (2 bytes)
/// 7. the size in bytes of the entire serialized object, including the header (8 bytes)
/// </remarks>
[StructLayout(LayoutKind.Explicit, Size=16)]
public class SEALHeader : ISettable<SEALHeader>
{
/// <summary>A magic number identifying this is a SEALHeader struct (2 bytes)</summary>
[FieldOffset(0)]public ushort Magic = SEALMagic;
/// <summary>Size in bytes of the SEALHeader struct (1 byte)</summary>
[FieldOffset(2)]public byte HeaderSize = SEALHeaderSize;
/// <summary>Microsoft SEAL's major version number (1 byte)</summary>
[FieldOffset(3)]public byte VersionMajor = SEALVersion.Major;
/// <summary>Microsoft SEAL's minor version number (1 byte)</summary>
[FieldOffset(4)]public byte VersionMinor = SEALVersion.Minor;
/// <summary>A compr_mode_type indicating whether data after the header is compressed (1 byte)</summary>
[FieldOffset(5)]public ComprModeType ComprMode = ComprModeDefault;
/// <summary>Reserved for future use and data alignment (2 bytes)</summary>
[FieldOffset(6)]public ushort Reserved = 0;
/// <summary>The size in bytes of the entire serialized object, including the header (8 bytes)</summary>
[FieldOffset(8)]public ulong Size = 0;
/// <summary>
/// Copies a given SEALHeader to the current one.
/// </summary>
/// <param name="assign">The SEALHeader to copy from</param>
/// <exception cref="ArgumentNullException">if assign is null</exception>
public void Set(SEALHeader assign)
{
if (null == assign)
throw new ArgumentNullException(nameof(assign));
Magic = assign.Magic;
HeaderSize = assign.HeaderSize;
VersionMajor = assign.VersionMajor;
VersionMinor = assign.VersionMinor;
ComprMode = assign.ComprMode;
Reserved = assign.Reserved;
Size = assign.Size;
}
};
private static bool IsSupportedComprMode(byte comprMode)
{
NativeMethods.Serialization_IsSupportedComprMode(comprMode, out bool result);
return result;
}
/// <summary>Returns true if the given value corresponds to a supported compression mode.</summary>
/// <param name="comprMode">The compression mode to validate</param>
public static bool IsSupportedComprMode(ComprModeType comprMode) =>
IsSupportedComprMode((byte)comprMode);
/// <summary>Returns true if the SEALHeader has a version number compatible with this version of
/// Microsoft SEAL.</summary>
/// <param name="header">The SEALHeader</param>
public static bool IsCompatibleVersion(SEALHeader header)
{
byte[] headerArray = new byte[SEALHeaderSize];
using (MemoryStream stream = new MemoryStream(headerArray))
{
SaveHeader(header, stream);
NativeMethods.Serialization_IsCompatibleVersion(
headerArray, (ulong)headerArray.Length, out bool result);
return result;
}
}
/// <summary>Returns true if the given SEALHeader is valid for this version of Microsoft SEAL.</summary>
/// <param name="header">The SEALHeader</param>
public static bool IsValidHeader(SEALHeader header)
{
byte[] headerArray = new byte[SEALHeaderSize];
using (MemoryStream stream = new MemoryStream(headerArray))
{
SaveHeader(header, stream);
NativeMethods.Serialization_IsValidHeader(
headerArray, (ulong)headerArray.Length, out bool result);
return result;
}
}
/// <summary>Saves a SEALHeader to a given binary stream.</summary>
/// <remarks>
/// Saves a SEALHeader to a given stream. The output is in binary format and not human-readable.
/// </remarks>
/// <param name="header">The SEALHeader to save to the stream</param>
/// <param name="stream">The stream to save the SEALHeader to</param>
/// <exception cref="ArgumentNullException">if header or stream is null</exception>
/// <exception cref="ArgumentException">if the stream is closed or does not support writing</exception>
/// <exception cref="IOException">if I/O operations failed</exception>
public static void SaveHeader(SEALHeader header, Stream stream)
{
if (null == header)
throw new ArgumentNullException(nameof(header));
if (null == stream)
throw new ArgumentNullException(nameof(stream));
if (!stream.CanWrite)
throw new ArgumentException(nameof(stream));
using (BinaryWriter writer = new BinaryWriter(stream, Encoding.UTF8, true))
{
writer.Write(header.Magic);
writer.Write(header.HeaderSize);
writer.Write(header.VersionMajor);
writer.Write(header.VersionMinor);
writer.Write((byte)header.ComprMode);
writer.Write(header.Reserved);
writer.Write(header.Size);
}
}
/// <summary>Loads a SEALHeader from a given stream.</summary>
/// <param name="stream">The stream to load the SEALHeader from</param>
/// <param name="header">The SEALHeader to populate with the loaded data</param>
/// <param name="tryUpgradeIfInvalid">If the loaded SEALHeader is invalid, attempt to identify its format and
/// upgrade to the current SEALHeader version</param>
/// <exception cref="ArgumentNullException">if header or stream is null</exception>
/// <exception cref="ArgumentException">if the stream is closed or does not support reading</exception>
/// <exception cref="InvalidOperationException">if the loaded data is not a valid SEALHeader or if the loaded
/// compression mode is not supported</exception>
/// <exception cref="EndOfStreamException">if the stream ended unexpectedly</exception>
/// <exception cref="IOException">if I/O operations failed</exception>
public static void LoadHeader(Stream stream, SEALHeader header, bool tryUpgradeIfInvalid = true)
{
if (null == header)
throw new ArgumentNullException(nameof(header));
if (null == stream)
throw new ArgumentNullException(nameof(stream));
if (!stream.CanRead)
throw new ArgumentException(nameof(stream));
using (BinaryReader reader = new BinaryReader(stream, Encoding.UTF8, true))
{
header.Magic = reader.ReadUInt16();
header.HeaderSize = reader.ReadByte();
header.VersionMajor = reader.ReadByte();
header.VersionMinor = reader.ReadByte();
header.ComprMode = (ComprModeType)reader.ReadByte();
header.Reserved = reader.ReadUInt16();
header.Size = reader.ReadUInt64();
}
// If header is invalid this may be an older header and we can try to automatically upgrade it
if (tryUpgradeIfInvalid && !IsValidHeader(header))
{
// Try interpret the data as a Microsoft SEAL 3.4 header
LegacyHeaders.SEALHeader_3_4 header_3_4 = new LegacyHeaders.SEALHeader_3_4(header);
SEALHeader newHeader = new SEALHeader();
// Copy over the fields; of course the result may not be valid depending on whether the input was a
// valid version 3.4 header
newHeader.ComprMode = header_3_4.ComprMode;
newHeader.Size = header_3_4.Size;
// Now validate the new header and discard if still not valid; something else is probably wrong
if (IsValidHeader(newHeader))
{
header.Set(newHeader);
}
}
}
internal delegate void SaveDelegate(
byte[] outptr, ulong size, byte comprMode, out long outBytes);
internal delegate void LoadDelegate(
byte[] inptr, ulong size, out long inBytes);
/// <summary>Saves data to a given binary stream.</summary>
/// <remarks>
/// First this function allocates a buffer of size <paramref name="size" />. The buffer is used by the
/// <paramref name="SaveData"/> delegate that writes some number of bytes to the buffer and outputs (in
/// out-parameter) the number of bytes written (less than the size of the buffer). The contents of the buffer
/// are then written to <paramref name="stream"/> and the function returns the output value of
/// <paramref name="SaveData"/>. This function is intended only for internal use.
/// </remarks>
/// <param name="SaveData">The delegate that writes some number of bytes to a given buffer</param>
/// <param name="size">An upper bound on the number of bytes that <paramref name="SaveData" /> requires</param>
/// <param name="comprMode">The desired compression mode</param>
/// <param name="stream">The destination stream</param>
/// <exception cref="ArgumentNullException">if SaveData or stream is null</exception>
/// <exception cref="ArgumentException">if the stream is closed or does not support writing, or if size is
/// negative or too large</exception>
/// <exception cref="IOException">if I/O operations failed</exception>
/// <exception cref="InvalidOperationException">if the data to be saved is invalid, if compression mode is not
/// supported, or if compression failed</exception>
internal static long Save(SaveDelegate SaveData, long size,
ComprModeType comprMode, Stream stream)
{
if (null == stream)
throw new ArgumentNullException(nameof(stream));
if (null == SaveData)
throw new ArgumentNullException(nameof(SaveData));
if (!stream.CanWrite)
throw new ArgumentException(nameof(stream));
if (!IsSupportedComprMode(comprMode))
throw new InvalidOperationException("Unsupported compression mode");
try
{
int sizeInt = checked((int)size);
byte[] buffer = new byte[sizeInt];
SaveData(buffer, checked((ulong)sizeInt), (byte)comprMode, out long outBytes);
int intOutBytes = checked((int)outBytes);
using (BinaryWriter writer = new BinaryWriter(stream, Encoding.UTF8, true))
{
writer.Write(buffer, 0, intOutBytes);
}
// Clear the buffer for safety reasons
Array.Clear(buffer, 0, intOutBytes);
return outBytes;
}
catch (OverflowException ex)
{
throw new ArgumentException($"{nameof(size)} is out of bounds", ex);
}
}
/// <summary>Loads data from a given binary stream.</summary>
/// <remarks>
/// This function calls the <see cref="LoadHeader" /> function to first load a <see cref="SEALHeader" /> object
/// from <paramref name="stream"/>. The <see cref="SEALHeader.Size"/> is then read from the
/// <see cref="SEALHeader" /> and a buffer of corresponding size is allocated. Next, the buffer is filled with
/// data read from <paramref name="stream"/> and <paramref name="LoadData"/> is called with the buffer as input,
/// which outputs (in out-parameter) the number bytes read from the buffer. This should match exactly the size
/// of the buffer. Finally, the function returns the output value of <paramref name="LoadData"/>. This function
/// is intended only for internal use.
/// </remarks>
/// <param name="LoadData">The delegate that reads some number of bytes to a given buffer</param>
/// <param name="stream">The input stream</param>
/// <exception cref="ArgumentNullException">if LoadData or stream is null</exception>
/// <exception cref="ArgumentException">if the stream is closed or does not support reading</exception>
/// <exception cref="EndOfStreamException">if the stream ended unexpectedly</exception>
/// <exception cref="IOException">if I/O operations failed</exception>
/// <exception cref="InvalidOperationException">if the loaded data is invalid, if the loaded compression mode is
/// not supported, or if size of the object is more than 2 GB</exception>
internal static long Load(LoadDelegate LoadData, Stream stream)
{
if (null == stream)
throw new ArgumentNullException(nameof(stream));
if (null == LoadData)
throw new ArgumentNullException(nameof(LoadData));
if (!stream.CanRead)
throw new ArgumentException(nameof(stream));
try
{
SEALHeader header = new SEALHeader();
var pos = stream.Position;
LoadHeader(stream, header);
// Check the validity of the header
if (!IsCompatibleVersion(header))
throw new InvalidOperationException("Incompatible version");
if (!IsSupportedComprMode(header.ComprMode))
throw new InvalidOperationException("Unsupported compression mode");
if (!IsValidHeader(header))
throw new InvalidOperationException("Loaded SEALHeader is invalid");
if (header.Size > checked((ulong)int.MaxValue))
throw new InvalidOperationException("Object size is larger than 2 GB");
int sizeInt = checked((int)header.Size);
stream.Seek(pos, SeekOrigin.Begin);
byte[] buffer = null;
using (BinaryReader reader = new BinaryReader(stream, Encoding.UTF8, true))
{
buffer = reader.ReadBytes(sizeInt);
}
LoadData(buffer, header.Size, out long outBytes);
// Clear the buffer for safety reasons
Array.Clear(buffer, 0, sizeInt);
return outBytes;
}
catch (OverflowException ex)
{
throw new InvalidOperationException("Size indicated by loaded SEALHeader is out of bounds", ex);
}
}
}
/// <summary>Class to contain header information for legacy headers.</summary>
public abstract class LegacyHeaders
{
/// <summary>Class to enable compatibility with Microsoft SEAL 3.4 headers.</summary>
[StructLayout(LayoutKind.Explicit, Size=16)]
public class SEALHeader_3_4 : ISettable<SEALHeader_3_4>, ISettable<Serialization.SEALHeader>
{
/// <summary>SEALMagic</summary>
[FieldOffset(0)]public ushort Magic = Serialization.SEALMagic;
/// <summary>ZeroByte</summary>
[FieldOffset(2)]public byte ZeroByte = 0;
/// <summary>ComprModeType</summary>
[FieldOffset(3)]public ComprModeType ComprMode = Serialization.ComprModeDefault;
/// <summary>Size</summary>
[FieldOffset(4)]public uint Size = 0;
/// <summary>Reserved</summary>
[FieldOffset(8)]public ulong Reserved = 0;
/// <summary>Creates a new SEALHeader_3_4.</summary>
public SEALHeader_3_4()
{
}
/// <summary>
/// Constructs a new SEALHeader_3_4 by copying a given one.
/// </summary>
/// <param name="copy">The SEALHeader_3_4 to copy from</param>
/// <exception cref="ArgumentNullException">if copy is null</exception>
public SEALHeader_3_4(Serialization.SEALHeader copy)
{
if (null == copy)
throw new ArgumentNullException(nameof(copy));
Set(copy);
}
/// <summary>Copies a given SEALHeader_3_4 to the current one.</summary>
/// <param name="assign">The SEALHeader_3_4 to copy from</param>
/// <exception cref="ArgumentNullException">if assign is null</exception>
public void Set(SEALHeader_3_4 assign)
{
if (null == assign)
throw new ArgumentNullException(nameof(assign));
Magic = assign.Magic;
ZeroByte = assign.ZeroByte;
ComprMode = assign.ComprMode;
Size = assign.Size;
Reserved = assign.Reserved;
}
/// <summary>Copies a given SEALHeader to the current one as a byte array.</summary>
/// <param name="assign">The SEALHeader to copy from</param>
/// <exception cref="ArgumentNullException">if assign is null</exception>
public void Set(Serialization.SEALHeader assign)
{
if (null == assign)
throw new ArgumentNullException(nameof(assign));
GCHandle gch = GCHandle.Alloc(this, GCHandleType.Pinned);
Marshal.StructureToPtr(assign, gch.AddrOfPinnedObject(), false);
gch.Free();
}
};
}
}