We use ldk-node through orange-sdk for seed-based recovery of live Lightning channel state. We re-read the pinned revision 0cea341 (2026-07-20) before proposing changes, and re-checked main at c309ccd (2026-09-04), where the same code is at src/io/vss_store.rs:568-579 (put) and :614 (delete). We would first like to confirm the intended VSS threat/concurrency model.
At this revision, ordinary writes set vss_version = -1, send global_version: None, and put KeyValue.version = -1 (src/io/vss_store.rs:558-574). Schema initialization does the same (:881-901), and deletes use version: -1 (:590-608).
vss-client-ng 0.6.0 defines -1 as a non-conditional write which resets the key version to 1, while an absent global_version omits the store-wide equality check (src/types.rs:48-96).
Static measurement: all three mutation constructors we found—ordinary put, schema put, and delete—are unconditional. The read path also discards both the returned KeyValue.version and decrypted PlaintextBlob.version (src/io/vss_store.rs:508-543); list paths discard key versions and global_version (:478-505,681-724).
The in-process next_version counter only orders callbacks (:177-190,727-753) and resets on restart, so it does not prevent a restored device or second process from blindly overwriting newer state.
For new stores, schema V1 authenticates the obfuscated key as AAD (:59-73,539-564,881-909), but a previous ciphertext for the same key still decrypts. A coherent old manager/monitor set can therefore pass local startup reconciliation; LDK’s data-loss-protect catches it later on peer reconnect, but a stale commitment broadcast before then remains unsafe.
Is this intentional—i.e. is VssStore currently designed for a trusted, single-writer VSS service, with concurrent instances and server rollback out of scope? Is the encrypted PlaintextBlob.version intended to be checked by integrations such as ldk-node?
Our concrete need is safe new-device restore when the old instance may still run. KVStore exposes no versions to orange-sdk, so addressing this outside VssStore requires carrying a wrapper/fork and still cannot make ldk-node’s own writes conditional.
If blind mode is not intentional, would you accept a PR that:
- initializes a per-key version cache from
ListKeyVersions, uses version 0 for new keys, and sends current versions for conditional puts/deletes;
- serializes/cache-updates successful mutations and surfaces
VssError::ConflictError to the caller;
- optionally uses
global_version where store-wide ordering is required;
- authenticates the target stored version with the key, then compares the decrypted version with the outer returned version; and
- documents that CAS/version binding handles honest-server races, while malicious full rollback still needs an external trusted high-water mark?
We can prepare the change and rollback/concurrent-writer tests once we know whether this matches the intended design.
Context: graduated-wallet (Emergent) uses ldk-node via orange-sdk; this came out of our client-side custody audit. Happy to answer questions or provide traces.
We use ldk-node through orange-sdk for seed-based recovery of live Lightning channel state. We re-read the pinned revision
0cea341(2026-07-20) before proposing changes, and re-checkedmainatc309ccd(2026-09-04), where the same code is atsrc/io/vss_store.rs:568-579(put) and:614(delete). We would first like to confirm the intended VSS threat/concurrency model.At this revision, ordinary writes set
vss_version = -1, sendglobal_version: None, and putKeyValue.version = -1(src/io/vss_store.rs:558-574). Schema initialization does the same (:881-901), and deletes useversion: -1(:590-608).vss-client-ng 0.6.0defines-1as a non-conditional write which resets the key version to 1, while an absentglobal_versionomits the store-wide equality check (src/types.rs:48-96).Static measurement: all three mutation constructors we found—ordinary put, schema put, and delete—are unconditional. The read path also discards both the returned
KeyValue.versionand decryptedPlaintextBlob.version(src/io/vss_store.rs:508-543); list paths discard key versions andglobal_version(:478-505,681-724).The in-process
next_versioncounter only orders callbacks (:177-190,727-753) and resets on restart, so it does not prevent a restored device or second process from blindly overwriting newer state.For new stores, schema V1 authenticates the obfuscated key as AAD (
:59-73,539-564,881-909), but a previous ciphertext for the same key still decrypts. A coherent old manager/monitor set can therefore pass local startup reconciliation; LDK’s data-loss-protect catches it later on peer reconnect, but a stale commitment broadcast before then remains unsafe.Is this intentional—i.e. is
VssStorecurrently designed for a trusted, single-writer VSS service, with concurrent instances and server rollback out of scope? Is the encryptedPlaintextBlob.versionintended to be checked by integrations such as ldk-node?Our concrete need is safe new-device restore when the old instance may still run.
KVStoreexposes no versions to orange-sdk, so addressing this outsideVssStorerequires carrying a wrapper/fork and still cannot make ldk-node’s own writes conditional.If blind mode is not intentional, would you accept a PR that:
ListKeyVersions, uses version 0 for new keys, and sends current versions for conditional puts/deletes;VssError::ConflictErrorto the caller;global_versionwhere store-wide ordering is required;We can prepare the change and rollback/concurrent-writer tests once we know whether this matches the intended design.
Context: graduated-wallet (Emergent) uses ldk-node via orange-sdk; this came out of our client-side custody audit. Happy to answer questions or provide traces.