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
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
/*!
The raw data format used by fog-pack.
fog-pack is a binary data format that builds off of the MessagePack
specification, and limits it in important ways to create a canonical encoding
for all data. It also extends MessagePack's types to support cryptographic
hashes, Public keys, and encrypted data (including private keys, symmetric keys,
and arbitrary data).
# Differences from MessagePack
MessagePack provides an excellent self-describing, yet compact format. The
fog-pack format primarily places limitations on MessagePack to create a
canonical encoding for all data, and further limits it to make validation
easier. The main differences are:
- Data must be encoded using the shortest available encoding.
- Positive integers always use positive integer encodings.
- 32-bit and 64-bit floating point types are considered completely separate types.
- The string type only permits valid UTF-8 byte sequences.
- Objects replace the Map type. They only allow strings as keys, the field-value
pairs must be in lexicographical order, and the keys must be unique.
- The extension types are all reserved and may not be extended by an application.
- Hash, Identity, and Lockbox extension types are defined for cryptographic purposes.
- Timestamps are explicitly UTC and let leapseconds be handled with a nanosecond
interval of 0 to 1,999,999,999.
- Decoding fails if any of the above limitations aren't met.
Lexicographical order, here, means ordered by the string's encoded UTF-8 byte sequence, with
absence of a byte being the absolute minimum, followed by 0-255.
## Why This Differs from MessagePack
MessagePack is already a great binary encoding format, but it's easy to end up encoding data
differently each time - for example, 0 can be encoded 9 different ways! This is useful for
extremely optimized encoders, where one might want to serialize a 32-bit signed value into a 1 byte
tag plus the value itself, but it causes problems for when we want identical data to have identical
hashes. This is where a "canonical" encoding comes in: identical data will always have a single
defined encoding. Any format that refers to data by its hash should want this, as it prevents
duplication and prevents identical data from hashing differently depending on how the encoder was
written.
As a baseline, MessagePack can create a "canonical" encoding by simply choosing the shortest
available encoding for each of its base types.
However, fog-pack doesn't stop there. It also makes some assumptions about how the format will be
used, and limits MessagePack accordingly.
### Strings are UTF-8
First, MessagePack allows invalid UTF-8 in order to allow broken data to be easily serialized, and
leaves it up to the data consumer to make decisions about invalid UTF-8. fog-pack takes a different
approach, expecting that the consumer should always want valid UTF-8 strings, and requires the data
producer to decide how to handle invalid UTF-8 input - usually, by failing or by coercing it into
valid UTF-8.
### Map only allows unique strings as keys, ordered lexicographically
Second, MessagePack allows maps to contain arbitrary data as their keys, and allows them to be in
any order. This makes serialization trivial for many objects, and allows one to easily encode a
hash map with arbitrary keys. fog-pack limits the map type to only allow unique keys, requires
they be strings, and requires they be lexicographically ordered by key. The expectation is that
maps will be deserialized into structures with named fields, or into a key-value struct that
expects unique keys (which is almost all of them). Requiring an order makes deserialization easier,
as the exact order is known ahead of time, and ensures identical structures can't be encoded in
multiple ways. Further limiting the keys to strings makes schema creation easier, makes data order
more intuitive (no need to order by encoded byte sequence), and maps well to structures with named
fields.
### Limited extension types
Finally, MessagePack allows for arbitrary extension types for end-users to mark byte sequences with
special handling requirements. fog-pack uses this to define 3 additional types for working with
cryptographic primitives, and bans the remainder. Since fog-pack is meant to be a public
interchange format, allowing for custom obscured types would create conflicting interpretations of
identical data. As such, non-recognized extensions are prohibited.
# Types
fog-pack takes the existing MessagePack type families and defines required
encoding forms, as well as defining several additional extended types. The full
type list is:
| Type | Short Name | Description |
| -- | -- | -- |
| [`Null`] | Null | Null/nil value |
| [`Boolean`] | Bool | True or false |
| [`Integer`] | Int | Any integer from `-(2^63)` up to `(2^64)-1` |
| [`String`] | Str | Any valid UTF-8 string up to `(2^32)-1` bytes |
| [`F32`] | F32 | An IEEE 754 single precision value |
| [`F64`] | F64 | An IEEE 754 double precision value |
| [`Binary`] | Bin | Any sequence of bytes up to `(2^32)-1` long |
| [`Array`] | Array | A sequence of elements |
| [`Object`] | Obj | Field-value pairs of elements, where Field is a String |
| [`Hash`] | Hash | A cryptographic hash |
| [`Identity`] | Ident | A public key |
| [`Lockbox`] | Lock | An encrypted sequence of bytes |
| [`Timestamp`] | Time | A UTC timestamp with nanosecond precision |
[`Null`]: #null
[`Boolean`]: #boolean
[`Integer`]: #integer
[`String`]: #string
[`F32`]: #f32
[`F64`]: #f64
[`Binary`]: #binary
[`Array`]: #array
[`Object`]: #object
[`Hash`]: #hash
[`Identity`]: #identity
[`Lockbox`]: #lockbox
[`Timestamp`]: #timestamp
User defined types are explicitly prohibited, as are Hash/Identity/Lockbox
elements whose cryptographic scheme isn't documented here.
The Hash, Identity, Lockbox, and Timestamp utilize MessagePack's ext format
family, which is reproduced here for convenience.
## Ext Family
The Ext Family of formats isn't directly used as a type of its own, but is used
to encapsulate the Hash, Identity, Lockbox, and Timestamp types. The length of
the encoded element is determined and then encapsulated as a byte sequence by
the appropriate ext format. The reserved Ext "Types" are as follows:
| Type Number | Type |
| -- | -- |
| -1 | Timestamp |
| 1 | Hash |
| 2 | Identity |
| 3 | Lockbox |
Unknown types should cause a decoding error.
```text
fixext1 stores a single byte
+----------+----------+----------+
| 0xd4 | type | data |
+----------+----------+----------+
fixext2 stores 2 bytes
+----------+----------+==========+
| 0xd5 | type | data |
+----------+----------+==========+
fixext4 stores 4 bytes
+----------+----------+==========+
| 0xd5 | type | data |
+----------+----------+==========+
fixext8 stores 8 bytes
+----------+----------+==========+
| 0xd7 | type | data |
+----------+----------+==========+
fixext16 stores 16 bytes
+----------+----------+==========+
| 0xd8 | type | data |
+----------+----------+==========+
ext8 stores the length and up to (2^8)-1 bytes,
unless the length is 1, 2, 4, 8, or 16
+----------+----------+----------+==========+
| 0xc7 | XXXXXXXX | type | data |
+----------+----------+----------+==========+
ext16 stores the length and between 2^8 and (2^16)-1 bytes
+----------+----------+----------+----------+==========+
| 0xc8 | YYYYYYYY | YYYYYYYY | type | data |
+----------+----------+----------+----------+==========+
ext32 stores the length and between 2^16 and (2^32)-1 bytes
+----------+----------+----------+----------+----------+----------+==========+
| 0xc9 | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | type | data |
+----------+----------+----------+----------+----------+----------+==========+
where:
- N is the length of the encoded type byte sequence
- XXXXXXXX is a 8-bit unsigned integer representing N
- YYYYYYYY_YYYYYYYY is a 16-bit big-endian unsigned integer representing N
- ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ is a 32-bit big-endian unsigned integer
representing N
```
## Null
Stores Null in 1 byte.
```text
null:
+----------+
| 0xc0 |
+----------+
```
## Boolean
Booleans store true or false in 1 byte.
```text
false:
+----------+
| 0xc2 |
+----------+
true:
+----------+
| 0xc3 |
+----------+
```
## Integer
Integers are stored in 1, 2, 3, 5, or 9 bytes. If the integer is non-negative,
it is stored using the minimum-length of the following formats:
```text
positive fixnum stores a 7-bit positive integer,
(0XXXXXXX is a 8-bit unsigned integer)
+----------+
| 0XXXXXXX |
+----------+
uint8 stores a 8-bit unsigned integer >= 128
+----------+----------+
| 0xcc | ZZZZZZZZ |
+----------+----------+
uint16 stores a 16-bit big-endian unsigned integer >= 256
+----------+----------+----------+
| 0xcd | ZZZZZZZZ | ZZZZZZZZ |
+----------+----------+----------+
uint32 stores a 32-bit big-endian unsigned integer >= 65536
+----------+----------+----------+----------+----------+
| 0xce | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ |
+----------+----------+----------+----------+----------+
uint64 stores a 64-bit big-endian unsigned integer >= 2^32
+----------+----------+----------+----------+----------+----------+----------+----------+----------+
| 0xcf | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ |
+----------+----------+----------+----------+----------+----------+----------+----------+----------+
```
If the integer is negative, it is stored as a two's complement number using the
minimum length of the following formats:
```text
negative fixnum stores a 5-bit negative integer,
(111YYYYY is a 8-bit signed integer)
+----------+
| 111YYYYY |
+----------+
int8 stores a 8-bit signed integer < -32
+----------+----------+
| 0xd0 | ZZZZZZZZ |
+----------+----------+
int16 stores a 16-bit big-endian signed integer < -128
+----------+----------+----------+
| 0xd1 | ZZZZZZZZ | ZZZZZZZZ |
+----------+----------+----------+
int32 stores a 32-bit big-endian signed integer < -32768
+----------+----------+----------+----------+----------+
| 0xd2 | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ |
+----------+----------+----------+----------+----------+
int64 stores a 64-bit big-endian signed integer < 2^-31
+----------+----------+----------+----------+----------+----------+----------+----------+----------+
| 0xd3 | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ |
+----------+----------+----------+----------+----------+----------+----------+----------+----------+
```
## F32
An IEEE 754 single precision number encoded using 5 bytes. When ordered, the
IEEE 754 total order predicate is used.
```text
float32 stores a IEEE 754 single precision number,
written in big-endian byte order:
+----------+----------+----------+----------+----------+
| 0xca | XXXXXXXX | XXXXXXXX | XXXXXXXX | XXXXXXXX |
+----------+----------+----------+----------+----------+
```
### F64
An IEEE 754 double precision number encoded using 9 bytes. When ordered, the
IEEE 754 total order predicate is used.
```text
float64 stores a IEEE 754 double precision number,
written in big-endian byte order:
+----------+----------+----------+----------+----------+----------+----------+----------+----------+
| 0xcb | XXXXXXXX | XXXXXXXX | XXXXXXXX | XXXXXXXX | XXXXXXXX | XXXXXXXX | XXXXXXXX | XXXXXXXX |
+----------+----------+----------+----------+----------+----------+----------+----------+----------+
```
## String
String stores a valid UTF-8 byte sequence with 1, 2, 3, or 5 bytes of overhead
beyond the sequence itself. Invalid UTF-8 is considered an encoding error and
should be treated as such by the encoder/decoder.
```text
fixstr stores a sequence up to 31 bytes in length
+----------+==========+
| 101XXXXX | data |
+----------+==========+
str8 stores a sequence between 32 and (2^8)-1 bytes in length
+----------+----------+==========+
| 0xd9 | YYYYYYYY | data |
+----------+----------+==========+
str16 stores a sequence between 2^8 and (2^16)-1 bytes in length
+----------+----------+----------+==========+
| 0xda | ZZZZZZZZ | ZZZZZZZZ | data |
+----------+----------+----------+==========+
str32 stores a sequence between 2^16 and (2^32)-1 bytes in length
+----------+----------+----------+----------+----------+==========+
| 0xdb | AAAAAAAA | AAAAAAAA | AAAAAAAA | AAAAAAAA | data |
+----------+----------+----------+----------+----------+==========+
where:
- N is the length of the byte sequence
- XXXXX is a 5-bit unsigned integer representing N
- YYYYYYYY is a 8-bit unsigned integer representing N
- ZZZZZZZZ_ZZZZZZZZ is a 16-bit big-endian unsigned integer representing N
- AAAAAAAA_AAAAAAAA_AAAAAAAA_AAAAAAAA is a 32-bit big-endian unsigned integer
representing N
```
## Binary
Binary stores any byte sequence with 2, 3, or 5 bytes of overhead beyond the
sequence itself.
```text
bin8 stores a sequence up to (2^8)-1 bytes in length
+----------+----------+==========+
| 0xc4 | XXXXXXXX | data |
+----------+----------+==========+
bin16 stores a sequence between 2^8 and (2^16)-1 bytes in length
+----------+----------+----------+==========+
| 0xc5 | YYYYYYYY | YYYYYYYY | data |
+----------+----------+----------+==========+
bin32 stores a sequence between 2^16 and (2^32)-1 bytes in length
+----------+----------+----------+----------+----------+==========+
| 0xc6 | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | data |
+----------+----------+----------+----------+----------+==========+
where:
- N is the length of the byte sequence
- XXXXXXXX is a 8-bit unsigned integer representing N
- YYYYYYYY_YYYYYYYY is a 16-bit big-endian unsigned integer representing N
- ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ is a 32-bit big-endian unsigned integer
representing N
```
## Array
Array stores a sequence of FogPack elements with 1, 3, or 5 bytes of overhead.
```text
fixarray stores a sequence of up to 15 elements
+----------+~~~~~~~~~~~~+
| 1001XXXX | N Elements |
+----------+~~~~~~~~~~~~+
array16 stores a sequence between 16 and (2^16)-1 elements
+----------+----------+----------+~~~~~~~~~~~~+
| 0xdc | YYYYYYYY | YYYYYYYY | N Elements |
+----------+----------+----------+~~~~~~~~~~~~+
array32 stores a sequence between 2^16 and (2^32)-1 elements
+----------+----------+----------+----------+----------+~~~~~~~~~~~~+
| 0xdd | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | N Elements |
+----------+----------+----------+----------+----------+~~~~~~~~~~~~+
where:
- N is the number of elements
- XXXX is a 4-bit unsigned integer representing N
- YYYYYYYY_YYYYYYYY is a 16-bit big-endian unsigned integer representing N
- ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ is a 32-bit big-endian unsigned integer
representing N
```
## Object
Object stores a sequence of field-value pairs with 1, 3, or 5 bytes of overhead.
The keys *must* be of the String type and arranged in lexicographical order,
while the values can be elements of any type. Field-value pairs are encoded in
order, such that a key string will be the first element, followed by its value,
and so on.
This type replaces MessagePack's `map` format family as a strict subset of what
MessagePack could encode.
```text
fixobj stores a sequence of up to 15 field-value pairs
+----------+~~~~~~~~~~~~~~+
| 1000XXXX | N*2 elements |
+----------+~~~~~~~~~~~~~~+
obj16 stores a sequence between 16 and (2^16)-1 field-value pairs
+----------+----------+----------+~~~~~~~~~~~~~~+
| 0xde | YYYYYYYY | YYYYYYYY | N*2 Elements |
+----------+----------+----------+~~~~~~~~~~~~~~+
obj32 stores a sequence between 2^16 and (2^32)-1 field-value pairs
+----------+----------+----------+----------+----------+~~~~~~~~~~~~~~+
| 0xdf | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | N*2 Elements |
+----------+----------+----------+----------+----------+~~~~~~~~~~~~~~+
where:
- N is the number of field-value pairs
- XXXX is a 4-bit unsigned integer representing N
- YYYYYYYY_YYYYYYYY is a 16-bit big-endian unsigned integer representing N
- ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ is a 32-bit big-endian unsigned integer
representing N
```
## Hash
Hash stores a single cryptographic hash. Only one hashing algorithm is
supported, with the expectation that new algorithms will only be added when the
current one is being deprecated due to security concerns. The algorithm used is
indicated by a version byte, which should be one of the following:
| Version | Meaning |
| -- | -- |
| 0 | No hash |
| 1 | BLAKE2b with 32-byte digest |
The current recommended hash algorithm is BLAKE2b with a 32-byte digest. The
Version 0 hash is available primarily for schema to reference themselves, taking
the place of their hash.
The encoded element is wrapped within the appropriate [ext format](#ext-family),
as shown:
```text
fixext1 is used for Hash Version 0:
+----------+----------+----------+
| 0xd4 | 0x01 | 0x00 |
+----------+----------+----------+
ext8 is used for Hash Version 1:
+----------+----------+----------+----------+==========+
| 0xc7 | XXXXXXXX | type | version | hash |
+----------+----------+----------+----------+==========+
+----------+----------+----------+----------+==========+
| 0xc7 | XXXXXXXX | 0x01 | 0x01 | hash |
+----------+----------+----------+----------+==========+
where:
- XXXXXXXX is an unsigned integer indicating the hash length plus 1. For version
1, this is fixed at 33.
```
## Identity
Identity stores a single cryptographic public key, used for both signing and
encryption. Only one public key cryptography method is supported, with the
expectation that new algorithms will only be added when the current one is
deprecated due to security concerns. The method used is indicated by a version
byte, which should be one of the following:
| Version | Meaning |
| -- | -- |
| 0 | Reserved |
| 1 | Ed25519/Curve25519 |
The Ed25519 public key can be converted to a X25519 public key (the curves are
birationally equivalent). Any future version of the Identity element will be
intended for use as both a signing key and encryption key, even if that requires
publishing two separate keys within one element.
```text
ext8 is used for Identity Version 1:
+----------+----------+----------+----------+==============+
| 0xc7 | XXXXXXXX | type | version | identity |
+----------+----------+----------+----------+==============+
+----------+----------+----------+----------+========================+
| 0xc7 | XXXXXXXX | 0x02 | 0x01 | Ed25519 public key |
+----------+----------+----------+----------+========================+
where:
- XXXXXXXX is an unsigned integer indicating the hash length plus 1. For version
1, this is fixed at 33.
```
## Lockbox
Lockbox stores authenticated, encrypted arbitrary data, prepended with an
identifier indicating the public key or symmetric key used to encrypt it. The
public key is part of an Identity - if the private portion of an Identity is
known, the Lockbox may be decrypted. It can likewise be decrypted if the
symmetric key is known.
The primary uses of Lockbox are to securely store sensitive data for an extended
period, to pass the secret key of an Identity, and to pass a symmetric key used
in other Lockboxes.
Lockbox can be described as three nested formats: The ext format wrapper, the
Lockbox structure, and the internal encrypted data.
#### Lockbox Format Wrapper
```text
ext8 is used when Lockbox is up to 255 bytes:
+----------+----------+----------+==============+
| 0xc7 | XXXXXXXX | 0x03 | Lockbox |
+----------+----------+----------+==============+
ext16 is used when Lockbox is between 256 and (2^16)-1 bytes:
+----------+----------+----------+----------+==============+
| 0xc8 | YYYYYYYY | YYYYYYYY | 0x03 | Lockbox |
+----------+----------+----------+----------+==============+
ext32 is used when Lockbox is between (2^16) and (2^32)-1 bytes:
+----------+----------+----------+----------+----------+----------+==============+
| 0xc9 | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | ZZZZZZZZ | 0x03 | Lockbox |
+----------+----------+----------+----------+----------+----------+==============+
- N is the number of bytes in the Lockbox structure
- XXXXXXXX is a 8-bit unsigned integer representing N
- YYYYYYYY_YYYYYYYY is a 16-bit big-endian unsigned integer representing N
- ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ_ZZZZZZZZ is a 32-bit big-endian unsigned integer
representing N
```
### Lockbox Structure
The internal lockbox format differs depending on whether a symmetric key was
used for encryption or an Identity was used for encryption. Both use XChaCha20
for encryption with an AEAD construction with no additional data. If an Identity
was used for encryption, the symmetric key is derived by:
1. Calculate Curve25519 public encryption key from Ed25519 public key.
2. Calculate the shared secret between the Curve25519 key and an ephemeral
Curve25519 key pair. If decrypting, the ephemeral public key can be combined
with the key was used for encryption. If encrypting, the ephemeral private key
can be combined with the public encryption key.
For a public key / Identity, the format consists of a version byte, a byte set
to 1, the public Identity signing key, a public ephemeral key randomly generated
for the Lockbox, a nonce, the ciphertext, and a Poly1305 message authentication
tag.
For a symmetric key, the format consists of a version byte, a byte set to 2, an
identifier derived from the key, a nonce, the ciphertext, and a Poly1305 message
authentication tag. The identifier is generated using libsodium's key derivation
function using the secret key as an input, "fogpack" as the context, and a
subkey ID of 1.
Currently the version byte must be set to 1.
```text
+----------+----------+==========+==========+==========+==============+=====+
| Version | 0x01 | SignKey | EphKey | Nonce | Ciphertext | Tag |
+----------+----------+==========+==========+==========+==============+=====+
+----------+----------+==========+==========+==============+=====+
| Version | 0x02 | StreamId | Nonce | Ciphertext | Tag |
+----------+----------+==========+==========+==============+=====+
- SignKey is a 32-byte Ed25519 public key
- EphKey is a 32-byte Curve25519 public key
- StreamId is a 32-byte hash of the encryption key (see above documentation)
- Nonce is a 24-byte random nonce
- Ciphertext is the internal data, encrypted with XChaCha20
- Tag is the authentication tag produced using the XChaCha20-Poly1305 AEAD
construction.
```
Internal data:
```text
+----------+=============+
| 0x01 | Private Key |
+----------+=============+
+----------+=============+
| 0x02 | Secret Key |
+----------+=============+
+----------+=============+
| 0x03 | Data |
+----------+=============+
```
# Cryptography Considerations
Choices for cryptographic algorithms were based on the preferred algorithms used
by libsodium. This resulted in the choices of:
- BLAKE2b for hashes
- Ed25519 for signing
- Curve25519 for public-key encryption
- XChaCha20 with Poly1305 message authentication codes
## Hashing
With 64-bit processors being increasingly common in consumer electronics,
BLAKE2b seemed the preferrable choice within the BLAKE2 series of hashing
algorithms.
A 32-byte hash was selected primarily because it provides 128-bit security, a
commonly accepted sufficient level for all applications.
BLAKE3 was considered as well, but is too recent and not thoroughly vetted at
the time of this standard's creation.
*/