Lightning Fork on BLAKE2b (beta testers needed)

Hello all, if you are not in it already, I would suggest joining the LN Discord: Lightning on Blake2b

We plan on doing weekly dev discussions, so would be good to have you guys there.

@paul I left some comments on your PR but overall looks good and I’d like to get this in the next release.

I have a new release here that can be tested: builds on Connor’s Lab unified-sigs CLN work, verified against all 166 digest vectors, ~600 CLN tests and 1036 mainnet blocks, with fixes for a PSBT bug that killed the node and channel_type being wrongly refused. (I’m a new user on this board so I can’t link it in here, but you can find it on GH repo privkeyio/lightning/releases/tag/v26.06.7-blake2b.4 and privkeyio/cln-startos/releases/tag/v26.06.7-blake2b.4)

Open question: it signals compulsory bit 68 per Connor’s Lab while Chris Guida’s plan says odd in init with numbers >= 32768. Those are mutually exclusive, so we should pick one before anyone funds this. Chris said we can always force close, but that we should nail down the exact protocol.

3 Likes

On the feature bit, I think Chris is right, and the part of his plan I had missed is the part that matters most: the asymmetry. I had been arguing for odd everywhere, which is wrong. Peering wants to be permissive, because during a migration you have to be able to talk to nodes that have not moved yet. The places where money moves want to be strict, because the failure there costs money rather than a reconnect. One choice for both cannot be right in both.

Where I ended up differs from his plan in one place. He puts the strict, even bit in invoices and offers. I put it inside channel_type, where a channel type is a set of even bits anyway and where both sides have to agree on the digest they sign, and I set nothing at all in invoices and offers. A payer on the other chain does need refusing, but the invoice prefix and chain_hash already refuse it, and a bit that duplicates a check does not add safety, it adds a second thing to get wrong. If Chris disagrees I would want to hear why, since he may be thinking of a case I am not.

I saw connectd: stop requiring peers to advertise blake2b bit 68 at the tip of blake2b-unified and first read it as settling this. Reading it properly, it settles one half. It stops you requiring the bit from a peer. You still send it compulsorily: in common/features.c, OPT_BLAKE2B is FEATURE_REPRESENT for INIT_FEATURE, so it goes out as even, and BOLT 9 obliges anyone who does not know bit 68 to close the connection. The comment above it says so on purpose, “legacy peers must reject it”, so I take this as a design choice rather than something overlooked.

The half that is left is the half that breaks interop. I built blake2b-unified with my series on top and pointed it at a Lightning Fork node on the same regtest chain:

  blake2b-unified @ 24d027310, plus my chain-identity series
  init features 90800898880a8a59a1

  Lightning Fork -> that node
    invalid remote features: feature vector contains unknown required
    features: [68]
  peered: no, in either direction

That is chain_hash agreement taken out of the picture and the bit still splitting two nodes that are following the same blocks.

Then I changed one word, FEATURE_REPRESENT to FEATURE_REPRESENT_AS_OPTIONAL for INIT_FEATURE, exactly what OPT_UNIFIED_SIGS on the next line already does, rebuilt from the same base commit with the same patches otherwise, and ran it against the same node:

  the same, plus one word in common/features.c
  init features a0800898880a8a59a1

  peered: yes, both directions

One nibble on the wire: 90 becomes a0, which is even bit 68 becoming odd bit 69, and the two nodes find each other. Nothing else differs between those two builds.

I am not asking you to drop the bit or to stop announcing it. Keeping it even in node_announcement costs nothing that I can measure, since lnd does not check required features there at all. It is specifically the init copy that refuses a node on your own chain.

The thing I found while testing that, which matters more

I did not stop at peering, because peering is a low bar: two nodes can finish init and still be unable to do anything. So I tried to open a channel between them, and could not, in either direction:

  CLN -> Lightning Fork
    lnd:  channel type negotiation failed: requested channel type not supported

  Lightning Fork -> CLN
    CLN:  Did not support channel_type [12,22]

[12,22] is option_static_remotekey plus option_anchors_zero_fee_htlc_tx, which is what essentially every channel opened anywhere today is. This is with chain_hash agreed and bit 68 already made optional, so it is a third barrier underneath the other two, and it is the one that survives fixing them.

It is option_unified_sigs. desired_channel_type() in lightningd/channel.c adds bit 70 to the proposed type, and channel_type_accept() in common/channel_type.c refuses an incoming type that lacks it. lightningd/options.c strips both only for elements, so on mainnet, testnet and regtest there is no flag that turns it off. The net effect is that an lnd which does not implement the channel type cannot open a channel with a blake2b-unified node, on any network, in either direction. At the time I ran this, that was every lnd there is.

I want to be careful here, because I do not think this is a bug and I am not reporting it as one. Your proposal document is explicit that a digest test does not establish that a channel transaction is replay-protected, and requiring the unified signer on new channels is a coherent answer to that. We reached the same conclusion about the risk. Lightning Fork carries this, in server_blake2b.go:

Channel %s was funded before the BLAKE2b fork: its funding output exists on both chains and its commitment transactions are valid on both, so a close on one chain can be replayed on the other. Close it and reopen with coins received after block 961640

So we agree on the vector. We differ on where the fix sits. Ours keeps the funding output off the other chain, so a commitment spending it has nothing to replay against, and scans for the channels where that did not happen. Yours binds the signature itself, which is the stronger form, because it does not depend on anyone funding correctly.

Yours is better, so rather than ask you to weaken it, I have implemented it.

Lightning Fork now negotiates option_unified_sigs as a channel type. The rule I followed is the one in your channel_type_sighash(): take the hash type BOLT 3 gives a signature and add SIGHASH_UNIFIED. So the commitment and the cooperative close sign 0x21, and second-level HTLC transactions on an anchor channel sign 0xa3, which is what full_channel.c does. Against your build at 24d027310, with nothing changed on your side:

  channel_type            [12,22,70]  "unified_sigs/even"
  payment, fork -> CLN    ok
  payment, CLN -> fork    ok
  cooperative close       confirmed, witness hash type byte 0x21
  force close             confirmed, witness hash type byte 0x21

That is the same image that, before this, refused to peer with us at all. I check the byte on chain rather than just that the close confirmed, because a channel that quietly fell back to plain SIGHASH_ALL would still pay and still close, and would not be bound to this chain at all, which is the entire point.

Two things I would still raise, and neither of them is a request to change your security model.

The first is one line, and it no longer affects me, which is why I think it is worth saying. desired_channel_type() adds the bit on

feature_offered(our_features->bits[INIT_FEATURE], OPT_UNIFIED_SIGS)

which is what we offer, while the anchors test three lines above it uses

feature_negotiated(our_features, their_features, OPT_ANCHORS_ZERO_FEE_HTLC_TX)

which is what both sides offer. A peer that never signalled the bit is still sent a channel type containing it, so the open fails where it could have degraded. Every other lnd on this chain, and anything else that implements the channel type later, hits that. Mine no longer does, because it signals the bit.

The second matters more. I matched your encoding by reading your source, and that is a bad way for two implementations to agree on a signature digest. Reading code tells me what you do today, not what is intended, and the places where I had to make a judgement call are exactly the places where we could diverge without either of us noticing until a channel cannot be closed. Two of mine:

  • I refuse the bit on taproot channels. There the commitment signature is a MuSig2 partial signature over a BIP341 digest, so opting in is a wire change (a 65-byte signature in commitment_signed) rather than a different hash type, and I would rather refuse than have two sides sign different digests. I do not know what you intend there.
  • Three of my verification paths built their sighash midstate from a fetcher that did not name the spent output. BIP143 never reads it, so this was invisible until the unified digest, which commits to the spent output’s value and scriptPubKey, started needing it. If your implementation has the same shape anywhere, it will show up as a signature that verifies locally and not on the peer.

Which brings me to a question I think has a good answer already. The .4 release notes say an independent implementation written from doc/unified-sighash.md alone reproduces all 166 Knots digest vectors. That is exactly the document I want, and I cannot find it: it is not in blake2b-unified, and git log --all -- "**/unified-sighash.md" finds nothing in the repository’s history. doc/BLAKE2B-UNIFIED-PROPOSAL.md is thirteen lines and mentions neither channel_type nor a hash type.

Is it on connorslab’s unified-sigs branch, which the notes credit as the basis? If so I would rather implement from it than from your source, and I would like to check my hash types against it rather than against full_channel.c. If it is not published anywhere, I am happy to write up what I have as a first draft and have you correct it. I would rather we disagree on a document now than find the disagreement in a channel later.

For what it is worth, the two independent checks agree so far. Your notes say channels funded past activation are signed with hash type 0x21 and that a full lifecycle against an independently built peer showed 0x21 from both peers. That is what I get, on the commitment and on the cooperative close, and 0xa3 on second-level HTLC transactions on an anchor channel.

One thing I should say plainly about the bit 68 half, since I spent a good deal of this message on it: it is no longer blocking me either. lnd refuses an unknown even bit, and the reason my node refused yours was that lnwire did not name bit 68 at all. It names it now, and sets the odd form (69) rather than the even one, on the reasoning above. So I am not asking you to change it. I am saying that any other lnd on this chain will refuse you until it does the same, and that the cost of the even bit is paid by them rather than by you.

The reason a compulsory bit in init is the wrong tool is the same reason your review of my PR was right about cln-application: an even bit there refuses more than it means to, including clients that speak the wire protocol only to reach a node’s RPC and have no business knowing what chain they are on. That was the same bug arriving from two directions, and the general form is “do not refuse at init for things chain_hash already covers”.

And on the numbers, for whenever the bit is actually set: the highest pair BOLT 9 has assigned is 66/67, option_onion_messages_only_channels, so 68 and 70 are the next ones the spec hands out rather than spare ones. That is an argument for Chris’s range whatever the even/odd split ends up being.

I am not clean here either. My chain identity document reserved 2100/2101, which is further from the frontier than 68 but no better, and a later revision moved that to Chris’s 32769/32768.

I have now given up on my own numbering rather than ask you to give up yours. The document describes 68/69 and 70/71, because those are what is on the wire and a second implementation that picked different numbers would simply not interoperate. What it keeps is the argument: 68 and 70 are the next pairs BOLT 9 hands out, and a custom-range pair sits clear of that.

Your own release notes say the same thing, which I had not registered when I was arguing about it: the numbers are unregistered, they are expected to move, and channels opened under them may have to be closed and reopened. So I do not think we actually disagree here, and the only live question is when to move and whether it is worth doing before more channels exist. The cost rises with every one.

What I did take from Chris is the asymmetry, which matters more than the range and which I had missed: odd in init and node_announcement, even inside channel_type, and neither in invoices or offers, where the prefix and chain_hash already refuse a payer from the other chain.

While testing the connectd change I got a useful confirmation of the argument. With silent peers kept, a stock Bitcoin lnd connects to Core Lightning and still gets nowhere: it sends a gossip_timestamp_filter carrying Bitcoin’s chain_hash and Core Lightning answers “bad chain” and closes. open_channel carries chain_hash too. So the isolation is in chain_hash, and the init rule was only ever making the failure happen earlier.

The larger thing I want to put in front of you, because it is bigger than the bit and I do not think it is a scheduling question.

Chris’s plan keeps chain_hash as the shared genesis hash through the migration and switches to a distinct value together afterwards, with the first BLAKE2b block as the candidate. My PR uses exactly that block. So we agree on the destination.

The part that I think has been overtaken is the timing, and not because I prefer my answer. Lightning Fork has been running the distinct chain_hash on mainnet since release and has channels on it, which have been growing. So right now a Lightning Fork node and a node on one of your builds cannot peer at all: mine advertises the BLAKE2b hash, yours advertises SHA256 chain’s, neither list contains the other’s, and both sides drop at init. One chain, two Lightning networks, no route between them.

I built .4 in the lab and put it on the same regtest chain as a Lightning Fork node to check rather than assume. They do not peer, in either direction:

  .4 -> Lightning Fork
    connectd: No common chain with this peer
    '...1aab09d928da45793d6f6f937b969e3b86f0403862d0de56289a16437bd59425'

  Lightning Fork -> .4
    invalid remote features: feature vector contains unknown required
    features: [68]

  .4 invoice     : lnbcrt10...
  Fork invoice   : lnblakert10u...

The hex at the end of the first one is the BLAKE2b chain_hash from my init networks list, which yours has no match for. The second is bit 68, which is where this reply started.

Both of those sit on top of the channel type, which is why I went after that one rather than asking you to change either: with chain_hash agreed and bit 68 out of the way, a node that cannot negotiate the channel type still cannot open a channel. That is the whole of it in one place, and it is the reason I do not think this is a 2027 question. It is true today and it gets more expensive the longer both sides grow separately. The scenarios are scripts/scenario-cln4-split.sh and scripts/scenario-bit68.sh in the lab repository if you want to run them yourself.

For reference, the current network using Lightning Fork is insignificant:

  Nodes in graph  : 6
  Channels        : 10
  Total capacity  : 104,000,000 sats (1.04 BTC)
  Last policy update: all 10 within a day

Three of those six nodes are mine. One other peer is in 60% of the channels. At current prices the whole thing is a couple of hundred dollars of capacity.

Ten channels can be easily closed and reopened. If the right answer turns out to be the shared hash, we will do that.

What the number does change is the urgency, in the opposite direction to the one I expected. Neither side has much at stake yet. That is exactly why this is the moment to settle it, while converging is cheap for everybody, rather than after either of us has grown into the split. The cost of getting it wrong is small today and compounds from here.

So the question is which way to converge, and I no longer think sunk cost should decide it. On the merits: converging on the distinct hash costs a one-time restamp, which my PR implements and which I have tested end to end, including a third node relearning the channel afterwards under the new identity. Nothing gets closed. Converging on the shared hash costs us ten channels and costs the chain a Lightning network that shares an identity with Bitcoin for another year. I think the first is better, but that is an argument about the design rather than about who has more to lose.

On the reason for keeping the shared hash: cooperative closes of pre-fork Bitcoin SHA256 channels are a real thing to protect, but I do not think they need the whole BLAKE2b Lightning network to stay on Bitcoin SHA256’s identity to get it. Both of our releases already tell people to fund from post-activation coins, and a user who still has a pre-fork channel with a SHA256 counterparty needs a SHA256 node to close it cooperatively whatever chain_hash we use here.

I considered proposing that nodes advertise both hashes in init during a transition, and discarded it. My check is a membership test over the list so I would accept such a peer with no change, but it only fixes peering: open_channel and gossip still have to agree on one hash, so the two nodes would be connected and still unable to do anything. There is no halfway house that I can find.

One practical note while I was looking: the PR was aimed at v26.06.7-blake2b, and .4 is a different commit on blake2b-unified, so if the next release comes off that branch the PR was pointed at the wrong place. I have rebased onto blake2b-unified and retargeted it. It applies with no conflicts, builds, and run-bolt11 and run-chainparams-blake2b pass there.

It also adds no test failures. tests/test_db.py and tests/test_invoices.py give 21 failures on blake2b-unified without my patches and the same 21 with them, identical by name. I built the branch unpatched specifically to be able to say that, since otherwise I would just be asserting it. Those 21 are yours rather than mine and your .4 notes suggest you already know about them; I mention it only so the number does not look like something I introduced.

BTW, nice work on the PSBT crash and the channel_type refusal. The fundchannel one would have bitten anyone running an LSP flow.

1 Like
2 Likes

I had my coding agent address your feedback on the PR, since I had to head out to work. Hopefully it didn’t do anything too annoying. I wanted the response in before your dev meeting today. I’ll look over everything more carefully when I get home this evening.

3 Likes

If there is a way I can assist with testing my node ID

023e300917fde06d24038083489cc9f31cce54ae8ff169b02e408a2f58533ebead@w4otb2jwynxfeojbii57a7osybfisepzdfwxe4xueqfuew276h7l2hyd.onion:9735

49,998,455

Sats funded on node

I would like to join your Lightning Beta testing.
May I send sats from the MUUN wallet as Lightning or doesn’t it work anymore? How do I start?

I have insatlled your Lightning fork.

What’s next?

|Action|Severity|Reason||
|—|—|—|—|
|Initialize Wallet |Required|LND needs a wallet to operate ||
|Select Node |Required|Lightning Fork needs to know which Bitcoin node to use. It must be on the Bitcoin BLAKE2b chain. ||
|Configure Channel Backups |Recommended|A StartOS backup only holds the channels you had when you took it. An off-server copy stays current. ||

Lightning Fork and the CLN port on the BLAKE2b chain are now aligned on the protocol, and I have published beta 12 with these updates.

Note #1: if you update first before your peers, your channels with those peers will show them as “Offline” (even if you know that they are actually online). The statuses will update as they upgrade their nodes.

Note #2: channels which were opened on the old version will not be routable through by CLN nodes, so they will need to be closed and re-opened. There are a couple of options you could follow for this:

  1. If neither you nor your channel partners have updated yet, you can simply close the channels and then upgrade. Then wait to re-open the channels until after your former channel partners have also upgraded.

  2. You can upgrade first and then close the channels. The channel closure will stay in a pending state until your channel partner upgrades, though. If they never upgrade, you can force-close the channel (and of course in that case you would not want to re-open a new channel with them).

I’m in the process of closing/re-opening/balancing the channels between my nodes, but I need to head out for the day. I’ll finish the process this evening, but in the mean time there probably won’t be any good routes through my nodes unless other folks update their channels while I am out today.

One more note, in case it confuses anyone: before you upgrade your node, other nodes which have already upgraded will appear as Offline, even if you know that they are online. Once you have upgraded too, they should appear Online if they are online.

1 Like

Hi, Paul: Sorry bout my silence but my foot ulcer gave me real stictches, nerve pain, etc. sore, pains and anxiety. Now I am a bit better so I am sending you this screenshot of my updated to today “Lightning Fork”. What do I have to do? I have not backed it to Dashlane or Google Drive or any other cloud service yet but that’s about it. I am willing to risk some sats to get this going on XBT. Let me know and have a good Sunday. Best regards.

Are you back yet? How do I start? Lightning Fork all up and running. Did you get my screenshot?

Got it!:slight_smile: PaulsCode

Name Version Package Hash Published
[image]Mempool Pruned 3.3.1:38 3.3.1:39 0001d4db298163ea51541ea05a8221c05529bce5 Oct 4, 2026
Release notes ( View listing )

Lightning Fork can be the explorer’s Lightning node, the mining pages work on a pruned node, and on the BLAKE2b chain fiat values are BTCB2’s own.

Enable Lightning offers Lightning Fork (BLAKE2b) beside LND and Core Lightning. The explorer reads Lightning Fork’s network graph the way it reads LND’s, using its read-only credentials. Lightning Fork runs on the BLAKE2b chain, so it cannot be chosen while the explorer follows the node on the SHA256 chain.

The Lightning pages also left out every channel on a pruned node. To date and size a channel, the explorer looked up the transaction that opened it, which only a node keeping a full transaction index can answer, and a pruned node cannot keep one. It now takes a channel’s capacity from the Lightning node and its date from the block header, which a pruned node keeps for every block.

On a pruned node the mining pages stopped filling in. Indexing reached the first block below your node’s prune height, asked the node for figures it can no longer compute for that block, failed, and started again every ten seconds, so hashrate, difficulty and pool statistics never ran either. Those figures now come from the block itself, fetched from peers as other old blocks already are, and match the node’s own exactly with one exception: per-transaction fees would need every input’s previous transaction, so an older block shows its average fee rate across its fee range instead of a spread. The catch-up runs once and takes a while. Each new block also no longer waits up to a minute while the explorer asks peers for stale blocks your node has pruned, which they do not serve.

On the BLAKE2b chain every fiat figure was the price of bitcoin on the SHA256 chain, about two hundred times too high. Prices now come from BTCB2’s dollar market on neoxa.exchange, the same source as Lightning Fork and Sparrow BLAKE2b, with other currencies converted at Coingecko’s rates. Past values use Neoxa’s hourly prices since BTCB2 listed on 1 September 2026; transactions between the fork and the listing use its first traded price, and anything before the fork keeps bitcoin’s price, which was the same coin then. On first start, prices recorded after the fork are replaced. On the SHA256 chain nothing about prices changes.

Nothing you have set changes and no reindex is needed.|

Hi Paul — happy to help bootstrap. Running Lightning Fork v0.21.3-beta-blake2b.17 (StartOS, installed from your GitHub releases), pointed at my own Bitcoin Knots 29.4.2 BLAKE2b node on the same box. Chain Identity check confirms block 961640, synced to tip.

My connection info:

033f46e249373fd2111b51c158cc3e896b94321a3a8fc0a7459b407af9a04a4eae@4dpgvyg574hlju3obk4z3dt4b5dxjxawn3466psz2acip3jieoe2vrad.onion:9737

Reaching me needs Tor on your end (I’m onion-only for now). If you’re still opening channels to testers, I’d love an inbound one — and I’m good for opening an equal channel back to you (1,000,000 sats, per your example) once you give me the green light. The sats behind it are my own Blake coins, already on the right side of the fork with replay protection sorted.

Also running Paperclip Wallet against Connor’s CLN port, so I’m glad to see the two implementations talking now. Keep it up.

It seems to be timing out when trying to connect. I’ll try again later.

Should be good now. Sorry about that.

Hello Paul. If it helps, I opened a channel with your start9 full node for 899,670 sats.

03f1024354e0ae38648a1a03e2de2fb6c236d5600ed97031c6e6fae7cab1da4f75@c53s2gcanvpzktssguxdfts72sswpsmw2khcm5m7gif3xtditoejhpad.onion:9737

Ive been following your site and repo since June. I heard about you through mathew kratter’s youtube channel. I continue to use pickhash, and the pruned sha256 stack on start OS (will tip you soon) and am now running the lightning-fork. Thank you! Im looking forward to participating in my own meager non-technical way.

2 Likes

Excellent, thanks! Every channel helps build out the network. Lots of little channels run by lots of plebs is better than a few big channels by only the large players in the space.

My XBT server has a glitch that has been taking it down at night. It is on now and you should be able to reach it for at least the next ten hours, probably overnight. I am working the glitch and should have it completely resolved by tomorrow. I appreciate your patience with me, and am quite energized to help you with this project.

did you manage to scrape some sats together to fund a LN node/channel?