Skip to content

docs(spec): add NanoTDF v1alpha specification suite - #3909

Draft
dmihalcik-virtru wants to merge 2 commits into
ictdf/spec-v1alphafrom
nanotdf/spec-v1alpha
Draft

docs(spec): add NanoTDF v1alpha specification suite#3909
dmihalcik-virtru wants to merge 2 commits into
ictdf/spec-v1alphafrom
nanotdf/spec-v1alpha

Conversation

@dmihalcik-virtru

Copy link
Copy Markdown
Member

Stacked on #3907.

Proposed Changes

  • Reorganize the nanotdf version 1 specification into parallel BaseTDF-style components under spec/nanotdf/v1alpha.
  • Preserve the wire format, algorithms, validation, and security semantics. The refactoring changes document ownership only.
  • Cover the full normative surface: the three-section binary frame, the magic-and-version encoding, both configuration bitfields, the closed curve and cipher registries, HKDF derivation with its fixed salt, Resource Locators, all four policy types including Policy Key Access, both policy binding modes, the optional creator signature, and the KAS exchange.
  • Normalize the source specification where it is internally inconsistent, and record every correction in an errata table in NanoTDF-CORE §6.
  • Reproduce both worked examples in full in NanoTDF-EX, with byte-offset parsing tables.
  • Add a row for the NanoTDF suite to spec/README.md.

Module layout

Layer Documents
Foundation SEC, ALG
Policy POL
Operations KAO, BND, PAY, KAS
Storage LOC, PKG
Assembly CORE
Examples EX

NanoTDF-BND owns both the policy binding and the creator signature, and covers the signature half of BaseTDF-ASN; in NanoTDF the binding is a load-bearing wire field sized by a header bitfield rather than a property of an assertion. NanoTDF-PAY exists because payload carriage has its own length field, reserved IV, and implied MAC length, which BaseTDF folds into CORE and INT.

Four BaseTDF modules are deliberately absent: INT (no segmentation and no per-segment integrity — one payload is one AEAD operation), ASN (no assertions), MTD (version 1 carries no metadata and no extension socket), and SCH (no external schema artifact — the byte tables in NanoTDF-PKG are the grammar). The suite README states these boundaries explicitly. There are no extension, profile, or migration documents, because NanoTDF version 1 defines no extension mechanism.

Notes for reviewers

The source specification is internally inconsistent in several places. This suite states the corrected value and lists all eleven corrections in NanoTDF-CORE §6. The substantive ones are size ranges that do not follow from their own constituent fields:

Field Source Corrected
Policy 3–257 12–714
Header 43–584 53–1,043
Signature 97–133 97–199
Policy Key Access 36–136 36–324
Policy Key Access ephemeral key 33–133 33–67

The rest are editorial: the Ephemeral ECC Params Enum is called "7-bit" where its own table gives it 3 bits, the source does not say which curve sizes an ECDSA policy binding (it is the ephemeral curve, not the Signature ECC Mode), a cross-reference is left as the placeholder [section X.X.X.X](), §3.4.2.2 is skipped, protocol 0xff is described as experimental with no processing rules, and the §6.1.6 parsing breakdown omits the payload Length field.

One figure that looks wrong is correct and has been preserved: the Payload range of 14–16,777,218. The three-byte Length field caps IV + ciphertext + MAC at 16,777,215, and the source's 16,777,204-byte ciphertext maximum pairs with the minimum 8-byte MAC, so a naive max-plus-max sum overshoots. NanoTDF-CORE §6 carries a note explaining this so it is not "corrected" later.

The source's three SVG figures are redrawn as ASCII, since spec/ contains no image assets.

Checklist

  • I have added or updated unit tests (not applicable: documentation only)
  • I have added or updated integration tests (not applicable: documentation only)
  • I have added or updated documentation

Testing Instructions

Documentation-only change. Both worked examples were decoded and verified rather than transcribed: each base64 object decodes to its stated length (258 and 197 bytes), every field offset in the NanoTDF-EX tables lands on a real field boundary, the per-section byte counts sum to the total, and both consume to zero remaining bytes. The per-field hex in NanoTDF-EX was generated from the decoded binaries. Example 2 has 173 bytes of overhead, which substantiates the format's sub-200-byte claim.

Also verified: the corrected size ranges each recompute from their constituent fields and are consistent across PKG, POL, BND, and KAO; the bitfield diagrams in NanoTDF-PKG decode the vectors' 0x80 and 0x35 configuration bytes correctly; all relative Markdown links resolve; every NanoTDF-* cross-reference names a real module and an existing section; and no source figure leaks into an owning module. No Go changes, so make lint and make fmt do not apply; the repository has no Markdown linter.

@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 17ef9640-d4c1-42d5-8771-3f3a9837545c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 245.882444ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 116.574373ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 423.874612ms
Throughput 235.92 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 42.442743669s
Average Latency 423.505981ms
Throughput 117.81 requests/second

@github-actions

Copy link
Copy Markdown
Contributor

X-Test Failure Report

@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 220.38546ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 123.649882ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 421.382637ms
Throughput 237.31 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 45.858319212s
Average Latency 457.669892ms
Throughput 109.03 requests/second

@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 222.33086ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 126.153559ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 432.800074ms
Throughput 231.05 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 43.909356839s
Average Latency 438.451786ms
Throughput 113.87 requests/second

Add spec/nanotdf/v1alpha/, refactoring the nanotdf version 1 specification
into the module architecture shared by the BaseTDF, BinaryTDF, and IC-TDF
suites. The refactoring changes document ownership only, not the wire format,
algorithms, validation, or security semantics.

NanoTDF carries no wrapped key: the symmetric key is derived from an ECDH
exchange between an ephemeral key pair and the KAS key pair. That is what
keeps fixed overhead under 200 bytes and allows objects to be produced
offline.

Two modules have no BaseTDF counterpart. NanoTDF-BND owns both the policy
binding and the creator signature, because in NanoTDF the binding is a
load-bearing wire field sized by a header bitfield rather than a property of
an assertion. NanoTDF-PAY exists because payload carriage has its own length
field, reserved IV, and implied MAC length.

Four BaseTDF modules are deliberately absent: INT (no segmentation or
per-segment integrity), ASN (no assertions), MTD (version 1 carries no
metadata), and SCH (no external schema artifact -- the byte tables in
NanoTDF-PKG are the grammar).

Where the source specification is internally inconsistent, this suite states
the corrected value and records the correction in NanoTDF-CORE section 6.
The eleven corrections include the Policy range (3-257 to 12-714), the Header
range (43-584 to 53-1043), the Signature range (97-133 to 97-199), and Policy
Key Access (36-136 to 36-324). The Payload range of 14-16,777,218 looks wrong
but is correct, and carries a note explaining why so it is not "fixed" later.

Both worked examples are reproduced in full in NanoTDF-EX and verified: they
decode to 258 and 197 bytes, with every field offset and section length
reconciling. The second, at 173 bytes of overhead, substantiates the
sub-200-byte claim.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
NanoTDF v1 is a frozen wire format, so this records the flaws rather
than changing them.

The suite never said how the three-byte IV becomes a GCM nonce. Three
readings fit the text and give different ciphertext, so two conforming
implementations could produce objects the other cannot open. Both
worked vectors were decrypted under all three: only the 24-bit reading,
with J0 from GHASH per SP 800-38D Algorithm 4 step 2, reproduces the
recorded plaintext and tag. That reading is now stated normatively.

A 24-bit nonce space collides at even odds after 4,823 objects, which
matters only if a producer reuses an ephemeral key pair -- and the
encrypted policy, pinned to the reserved IV 0x000000, then collides
with certainty on the first repeat.

Short tags are now coupled to payload length: a 64-bit tag over a
maximum payload is worth about 43 bits, not 64, and absorbs only 2,048
forgery attempts. SP 800-38D Appendix C Table 2 is reproduced, since it
is a shall, not advice, and NanoTDF has no second integrity layer
unless a creator signature is present.

Also state the 2^40-byte volume bound, which one object cannot
approach, and the scale ceiling: 16,777,215 bytes is 327,680 times
smaller than a maximal S3 object.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
@github-actions

Copy link
Copy Markdown
Contributor

X-Test Failure Report

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Govulncheck found vulnerabilities ⚠️

The following modules have known vulnerabilities:

  • examples
  • otdfctl
  • sdk
  • service
  • lib/ocrypto
  • lib/fixtures
  • tests-bdd

See the workflow run for details.

@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 237.77703ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 138.407367ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 425.07164ms
Throughput 235.25 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 44.003671121s
Average Latency 439.204714ms
Throughput 113.63 requests/second

@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 201.819797ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 119.69611ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 435.669615ms
Throughput 229.53 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 43.342942591s
Average Latency 432.142777ms
Throughput 115.36 requests/second

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant