Skip to content

SealManifest

Namespace Nfty.Core.Publish in assembly Nfty.Core.

public record SealManifest : ISchemaVersioned

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 over SealManifest.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

public SealCipher Cipher { get; init; }

How the payload was encrypted.

Collection

public string Collection { get; init; }

The collection's name, for a recipient with no key.

Count

public int Count { get; init; }

How many assets are inside.

Kdf

public SealKdf Kdf { get; init; }

How the key was derived.

Note

public string? Note { get; init; }

What the author wants the recipient to read first. Null when none was given.

Policy

public SealPolicy Policy { get; init; }

What nfty will let the recipient do.

PolicyMac

public string PolicyMac { get; init; }

Hex HMAC-SHA256 over SealManifest.Canonical().

SchemaVersion

public int SchemaVersion { get; init; }

The schema this header was written against.

SealId

public string SealId { get; init; }

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()

public string 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.