bolt3

Lightning transaction and script formats, per BOLT #3 (docs.ppad.tech/bolt3).
git clone git://git.ppad.tech/bolt3.git
Log | Files | Refs | README | LICENSE

commit 6e6b266de82e16f9fc27e3ad5c2bf7d66167f29e
parent e66edc606bc7553d01e20b847195527142024ecf
Author: Jared Tobin <jared@jtobin.io>
Date:   Sat, 10 Oct 2026 13:14:57 +0400

lib: follow the spec's insert_secret in the secret store

insert_secret dropped the entries in the new secret's bucket and every
bucket above it, keeping only the lower (redundant) buckets. Inserting
any odd index therefore emptied the store down to a single entry, so
derive_old_secret could not recover most older secrets, and storage
vectors #2, #4 and #6 of Appendix D were wrongly accepted.

Per BOLT #3, the new secret now replaces only its own bucket's entry
and all other entries are kept, in ascending bucket order. Secrets that
are not 32 bytes and indices >= 2^48 are rejected.

Diffstat:
Mlib/Lightning/Protocol/BOLT3/Keys.hs | 55++++++++++++++++++++++++++++++-------------------------
1 file changed, 30 insertions(+), 25 deletions(-)

diff --git a/lib/Lightning/Protocol/BOLT3/Keys.hs b/lib/Lightning/Protocol/BOLT3/Keys.hs @@ -432,39 +432,44 @@ where_to_put_secret idx = go 0 where -- | Insert a secret into the store, validating against existing secrets. -- --- Returns Nothing if the secret doesn't derive correctly from known --- secrets (indicating the secrets weren't generated from the same seed). +-- Implements insert_secret from BOLT #3: the secret is checked against +-- every known secret in a lower bucket, then replaces the entry in its +-- own bucket. Entries in all other buckets are kept, so that every +-- previously-inserted secret remains derivable via 'derive_old_secret'. +-- Entries are kept in ascending bucket order. +-- +-- Returns Nothing if the secret is not 32 bytes, if the index is not +-- below 2^48, or if a known secret doesn't derive correctly from the +-- new one (indicating the secrets weren't generated from the same +-- seed). -- -- >>> insert_secret secret 281474976710655 empty_store -- Just (SecretStore ...) insert_secret :: BS.ByteString -- ^ secret (32 bytes) - -> Word64 -- ^ index + -> Word64 -- ^ index (max 2^48 - 1) -> SecretStore -- ^ current store -> Maybe SecretStore -insert_secret secret idx (SecretStore known) = do - let !bucket = where_to_put_secret idx - -- Validate: for each bucket < this bucket, check we can derive - validated <- validateBuckets bucket known - if validated - then - -- Remove entries at bucket >= this bucket, then insert - let !known' = filter (\e -> se_bucket e < bucket) known - !entry = SecretEntry bucket idx secret - in pure $! SecretStore (known' ++ [entry]) - else Nothing +insert_secret secret idx (SecretStore known) + | BS.length secret /= 32 = Nothing + | idx > 0xFFFFFFFFFFFF = Nothing + | all derives known = Just $! SecretStore (put known) + | otherwise = Nothing where - validateBuckets :: Int -> [SecretEntry] -> Maybe Bool - validateBuckets b entries = go entries where - go [] = Just True - go (SecretEntry entryBucket knownIdx knownSecret : rest) - | entryBucket >= b = go rest -- skip entries at higher buckets - | otherwise = - -- Check if we can derive the known secret from the new one - let !derived = derive_secret secret b knownIdx - in if derived == knownSecret - then go rest - else Nothing + !bucket = where_to_put_secret idx + !entry = SecretEntry bucket idx secret + + -- known secrets in lower buckets must derive from the new one + derives (SecretEntry b knownIdx knownSecret) + | b < bucket = derive_secret secret bucket knownIdx == knownSecret + | otherwise = True + + -- replace this bucket's entry, preserving ascending bucket order + put [] = [entry] + put (e : es) = case compare (se_bucket e) bucket of + LT -> e : put es + EQ -> entry : es + GT -> entry : e : es {-# INLINE insert_secret #-} -- | Derive a previously-received secret from the store.