-
Notifications
You must be signed in to change notification settings - Fork 1
FEE wrap material on blob_encryption_params (FIL-480) #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
e054d52
3f445a1
9c91922
3db7e44
b3b1e3a
b864480
893e930
f83f9c0
d666f5f
bf5de99
f08520e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| /Users/bajtos/src/fil-forge/ingot/.claude | ||
|
bajtos marked this conversation as resolved.
Outdated
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,15 +11,16 @@ import ( | |
| ) | ||
|
|
||
| // In-memory implementations of the architecture's relational stores | ||
| // (registry.BlobRefStore / IntentStore / LocationStore / MultipartStore / | ||
| // GCStore), mirroring the Postgres tables so the in-process suite and | ||
| // standalone mode exercise the same write/read/delete code paths. | ||
| // (registry.BlobRefStore / IntentStore / LocationStore / EncryptionParamsStore / | ||
| // MultipartStore / GCStore), mirroring the Postgres tables so the in-process | ||
| // suite and standalone mode exercise the same write/read/delete code paths. | ||
|
|
||
| // Compile-time assertions: *MemStore satisfies every store interface. | ||
| var ( | ||
| _ registry.BlobRefStore = (*MemStore)(nil) | ||
| _ registry.IntentStore = (*MemStore)(nil) | ||
| _ registry.LocationStore = (*MemStore)(nil) | ||
| _ registry.EncryptionParamsStore = (*MemStore)(nil) | ||
| _ registry.InclusionStore = (*MemStore)(nil) | ||
| _ registry.MultipartStore = (*MemStore)(nil) | ||
| _ registry.GCStore = (*MemStore)(nil) | ||
|
|
@@ -126,9 +127,7 @@ func (m *MemStore) DeleteIntent(_ context.Context, digest []byte) error { | |
| func (m *MemStore) PutLocation(_ context.Context, loc registry.BlobLocation) error { | ||
| m.mu.Lock() | ||
| defer m.mu.Unlock() | ||
| cp := loc | ||
| cp.Digest = cloneBytes(loc.Digest) | ||
| m.locations[locKey{loc.Space, string(loc.Digest)}] = cp | ||
| m.locations[locKey{loc.Space, string(loc.Digest)}] = cloneLocation(loc) | ||
| return nil | ||
| } | ||
|
|
||
|
|
@@ -139,8 +138,7 @@ func (m *MemStore) GetLocation(_ context.Context, space did.DID, digest []byte) | |
| if !ok { | ||
| return nil, registry.ErrNotFound | ||
| } | ||
| cp := loc | ||
| cp.Digest = cloneBytes(loc.Digest) | ||
| cp := cloneLocation(loc) | ||
| return &cp, nil | ||
| } | ||
|
|
||
|
|
@@ -151,6 +149,37 @@ func (m *MemStore) DeleteLocation(_ context.Context, space did.DID, digest []byt | |
| return nil | ||
| } | ||
|
|
||
| // EncryptionParamsStore ====================================================== | ||
|
|
||
| func (m *MemStore) PutEncryptionParams(_ context.Context, params registry.BlobEncryptionParams) error { | ||
| // Match the Postgres store, whose columns are all NOT NULL. | ||
| if err := params.Validate(); err != nil { | ||
| return err | ||
| } | ||
| m.mu.Lock() | ||
| defer m.mu.Unlock() | ||
| m.encParams[locKey{params.Space, string(params.Digest)}] = cloneEncryptionParams(params) | ||
| return nil | ||
| } | ||
|
|
||
| func (m *MemStore) GetEncryptionParams(_ context.Context, space did.DID, digest []byte) (*registry.BlobEncryptionParams, error) { | ||
| m.mu.Lock() | ||
| defer m.mu.Unlock() | ||
| params, ok := m.encParams[locKey{space, string(digest)}] | ||
| if !ok { | ||
| return nil, registry.ErrNotFound | ||
| } | ||
| cp := cloneEncryptionParams(params) | ||
| return &cp, nil | ||
| } | ||
|
|
||
| func (m *MemStore) DeleteEncryptionParams(_ context.Context, space did.DID, digest []byte) error { | ||
| m.mu.Lock() | ||
| defer m.mu.Unlock() | ||
| delete(m.encParams, locKey{space, string(digest)}) | ||
| return nil | ||
| } | ||
|
|
||
| // ParkStore ================================================================== | ||
|
|
||
| func (m *MemStore) PutPark(_ context.Context, p registry.BlobPark) error { | ||
|
|
@@ -406,6 +435,22 @@ func cloneSession(s registry.MultipartSession) registry.MultipartSession { | |
| return s | ||
| } | ||
|
|
||
| // cloneLocation deep-copies a BlobLocation's digest so the stored copy and any | ||
| // returned copy never alias the caller's slice. | ||
| func cloneLocation(loc registry.BlobLocation) registry.BlobLocation { | ||
| loc.Digest = cloneBytes(loc.Digest) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just use
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Written by Claude. Done, and removed the local |
||
| return loc | ||
| } | ||
|
|
||
| // cloneEncryptionParams deep-copies a BlobEncryptionParams' byte-slice fields | ||
| // so the stored copy and any returned copy never alias the caller's slices. | ||
| func cloneEncryptionParams(p registry.BlobEncryptionParams) registry.BlobEncryptionParams { | ||
| p.Digest = cloneBytes(p.Digest) | ||
| p.BaseNonce = cloneBytes(p.BaseNonce) | ||
| p.AAD = cloneBytes(p.AAD) | ||
| return p | ||
| } | ||
|
|
||
| func clonePart(p registry.MultipartPart) registry.MultipartPart { | ||
| p.ETagMD5 = cloneBytes(p.ETagMD5) | ||
| if p.BlobDigests != nil { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,49 @@ | ||||||
| -- +goose Up | ||||||
| -- FEE (FilOne encryption envelope) per-blob encryption parameters. When Ingot | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically Filecoin Encryption Envelope not FilOne.
Suggested change
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Written by Claude. Fixed, here and in the Go doc comment. |
||||||
| -- encrypts an object's body, each body blob is stored as an independent | ||||||
| -- COSE/STREAM ciphertext envelope; a range GET must be able to decrypt any byte | ||||||
| -- span of that envelope WITHOUT first fetching and parsing its header. A row | ||||||
| -- here caches exactly the inputs the read path's decryptor needs, so a read | ||||||
| -- unwraps the CEK (held in OpenBao, under the region KEK) and goes straight to | ||||||
| -- a body-range fetch — no envelope-header round-trip. | ||||||
| -- | ||||||
| -- The existence of a row is what marks a blob as encrypted, so every column is | ||||||
| -- NOT NULL: there is no such thing as a half-populated parameter set the | ||||||
| -- decrypt path could not use. An unencrypted blob simply has no row. | ||||||
| -- | ||||||
| -- Deliberately a separate table from ingot.blob_locations, and deliberately | ||||||
| -- WITHOUT a foreign key to it. blob_locations is a reconstructible cache of the | ||||||
| -- indexing-service contract — every row can be re-derived from the indexer or | ||||||
| -- the accept receipt, and the table goes away when the topology moves to a real | ||||||
| -- indexer. A row here is instead the marker that a blob is encrypted, so the | ||||||
| -- two have independent lifecycles, and an FK would additionally force | ||||||
| -- location-before-parameters write ordering. | ||||||
| -- | ||||||
| -- aad holds the whole COSE Enc_structure rather than just the protected header, | ||||||
| -- because the structure's context string differs between a COSE_Encrypt and a | ||||||
| -- COSE_Encrypt0 and a bare row cannot record which form was used. The protected | ||||||
| -- header stays recoverable from it as element 1. | ||||||
| -- | ||||||
| -- No key material is stored here: the region-KEK-wrapped CEK and its key | ||||||
| -- version live in OpenBao. Per-blob crypto-shred is deleting the key there; | ||||||
| -- deleting this row only drops the cached decrypt parameters. Because there is no cascade, a caller removing a | ||||||
| -- blob must delete here as well as from blob_locations. | ||||||
| CREATE TABLE ingot.blob_encryption_params ( | ||||||
| space text NOT NULL, | ||||||
| digest bytea NOT NULL, -- ciphertext blob multihash, as in blob_locations | ||||||
| tenant_recipient_kid text NOT NULL -- opaque id of the Hilt wrap key (insurance-recovery unwrap) | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to store this? It's not something we will ever use and is stored in the header anyway, right? Or am I misunderstanding?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this bytes or a DID? EDIT: seems to be a DID with a fragment.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Written by Claude. You are right, dropped. The table caches only what a range GET needs to skip the header fetch, and that read unwraps via the region KEK. The insurance-recovery unwrap is rare and out-of-band, and it reads the envelope header anyway, where the COSE recipients live. Column, struct field, and tests are gone.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Written by Claude. Moot now, the column is gone. It was a DID with a fragment. |
||||||
| CHECK (tenant_recipient_kid <> ''), | ||||||
| header_len bigint NOT NULL -- encoded envelope length; the ciphertext starts at this offset | ||||||
| CHECK (header_len > 0), | ||||||
| base_nonce bytea NOT NULL -- COSE iv: the STREAM nonce seed for this blob's ciphertext | ||||||
| CHECK (octet_length(base_nonce) > 0), | ||||||
| chunk_size bigint NOT NULL -- FEE chunk size from the COSE protected header | ||||||
| CHECK (chunk_size > 0), | ||||||
| aad bytea NOT NULL -- COSE Enc_structure, bound into every chunk's GCM tag | ||||||
| CHECK (octet_length(aad) > 0), | ||||||
| created_at timestamptz NOT NULL DEFAULT now(), | ||||||
| PRIMARY KEY (space, digest) | ||||||
| ); | ||||||
|
|
||||||
| -- +goose Down | ||||||
| DROP TABLE ingot.blob_encryption_params; | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Written by Claude.
Removed. It is a symlink my worktree setup creates, committed by accident. I left it out of
.gitignoreon purpose so the repo can still check in a shared.claude/config later.