SealManifest¶
Namespace Nfty.Core.Publish in assembly Nfty.Core.
The seal.json at the top of a sealed export — plaintext, deliberately.
Remarks¶
Why any of it is readable without the key. A recipient holding a file they cannot open needs to be told what it is, or the only thing the format communicates is "something went wrong". inspect prints this header, so they learn the collection's name, its size, and the note the author wrote them, and then know which passphrase to go and find.
That is a deliberate metadata leak and it is the whole cost of the choice. Anyone who obtains the file learns that the collection exists, what it is called and how large it is. The art, the traits, the rarity and the DNA are all inside the encryption; the label on the tin is not.
SealManifest.PolicyMac is what stops the label being rewritten. It is an HMAC over SealManifest.Canonical() keyed by a second key derived from the same passphrase, so someone who intercepts the file cannot edit allowExport to true, or restate the note, and pass it on. Someone who HAS the passphrase can recompute it — that is not a gap in the implementation, it is what it means to hold the key, and it is why the seal is described everywhere in this product as a lock on a door rather than a wall.
Constructors¶
SealManifest(string, string, int, string?, SealPolicy, string, SealKdf, SealCipher, int)¶
public SealManifest(string SealId, string Collection, int Count, string? Note, SealPolicy Policy, string PolicyMac, SealKdf Kdf, SealCipher Cipher, int SchemaVersion = 2);
Parameters
SealId(string) - A random 128-bit id, hex. It is in the AAD of every frame, so frames cannot be spliced between two files sealed with the same passphrase.Collection(string) - The collection's name, for a recipient with no key.Count(int) - How many assets are inside.Note(string?) - What the author wants the recipient to read first. Null when none was given.Policy(SealPolicy) - What nfty will let the recipient do.PolicyMac(string) - Hex HMAC-SHA256 overSealManifest.Canonical().Kdf(SealKdf) - How the key was derived.Cipher(SealCipher) - How the payload was encrypted.SchemaVersion(int) - The schema this header was written against.
Remarks
Why any of it is readable without the key. A recipient holding a file they cannot open needs to be told what it is, or the only thing the format communicates is "something went wrong". inspect prints this header, so they learn the collection's name, its size, and the note the author wrote them, and then know which passphrase to go and find.
That is a deliberate metadata leak and it is the whole cost of the choice. Anyone who obtains the file learns that the collection exists, what it is called and how large it is. The art, the traits, the rarity and the DNA are all inside the encryption; the label on the tin is not.
SealManifest.PolicyMac is what stops the label being rewritten. It is an HMAC over SealManifest.Canonical() keyed by a second key derived from the same passphrase, so someone who intercepts the file cannot edit allowExport to true, or restate the note, and pass it on. Someone who HAS the passphrase can recompute it — that is not a gap in the implementation, it is what it means to hold the key, and it is why the seal is described everywhere in this product as a lock on a door rather than a wall.
Properties¶
Cipher¶
How the payload was encrypted.
Collection¶
The collection's name, for a recipient with no key.
Count¶
How many assets are inside.
Kdf¶
How the key was derived.
Note¶
What the author wants the recipient to read first. Null when none was given.
Policy¶
What nfty will let the recipient do.
PolicyMac¶
Hex HMAC-SHA256 over SealManifest.Canonical().
SchemaVersion¶
The schema this header was written against.
SealId¶
A random 128-bit id, hex. It is in the AAD of every frame, so frames cannot be spliced between two files sealed with the same passphrase.
Methods¶
Canonical()¶
The exact bytes the MAC covers, as a string.
Returns. One line naming every header field that carries meaning, in a fixed order.
Remarks
Spelled out by hand rather than by re-serializing this record, because the MAC has to mean the same thing in every future build. System.Text.Json emits a record's members in declaration order, so MACing its own JSON would make reordering two properties — a refactor with no observable effect anywhere else — silently invalidate every seal ever written. This method is the format; the record is just how it is carried.