Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
15cc7eb
drop the paperclip workflow, which targets a branch this repo does no…
kwsantiago Sep 16, 2026
fe9c85e
tests: satisfy flake8 in the psbt sighash helper
kwsantiago Sep 16, 2026
ba2ef55
tests: match the fork's funding weight in the coin move amounts
kwsantiago Sep 17, 2026
72ca6cc
tests: bit 68 widens node_announcement, so the gossip store is larger
kwsantiago Sep 16, 2026
92c6eed
hsmd: free the mlocked secrets so the test has a cleanup function
kwsantiago Sep 16, 2026
6ed8066
pyln-testing: mine the reorg shift blocks empty rather than depriorit…
kwsantiago Sep 17, 2026
6b9acc2
pyln-testing: raise maxtipage so canned blocks leave initial block do…
kwsantiago Sep 17, 2026
e5e691c
tests: coindesk needs an API key now, so drop it from the rate assert…
kwsantiago Sep 17, 2026
32052b5
tests: skip the metadata test, its hand-made invoice expired in May
kwsantiago Sep 17, 2026
b0cec4b
ci: drop maxfail so a group runs to completion, as upstream does
kwsantiago Sep 17, 2026
beb81c2
bitcoin: ignore gossip for channels funded before activation
kwsantiago Sep 17, 2026
3568c11
tests: pin the unified hash types and the activation constants
kwsantiago Sep 17, 2026
b4317cf
gossipd: do not pseudorand(0) when the tip is exactly num_blocks past…
kwsantiago Sep 17, 2026
4f39853
hsmd: fail closed after freeing the seed; gate testnet4 gossip too
kwsantiago Sep 17, 2026
95e032c
doc: say what the activation heights are compiled in for
kwsantiago Sep 17, 2026
39c0dfb
tests: wait for the settled state instead of racing the peer's hangup
kwsantiago Sep 17, 2026
fc3752e
tests: wait for the failed attempts to settle in sendpay grouping
kwsantiago Sep 17, 2026
97e043f
tests: poll for the overgrind warning instead of sleeping a fixed second
kwsantiago Sep 18, 2026
ba1b367
bitcoin: stop rehashing every prevout transaction for every signature
kwsantiago Sep 18, 2026
63429d6
hsmd: free each transaction clone instead of leaving it on tmpctx
kwsantiago Sep 18, 2026
f471a18
doc: name the other chain by its proof of work, and Core Lightning by…
kwsantiago Sep 19, 2026
079e505
hsmd: report overgrind from the unified signing path too
kwsantiago Sep 19, 2026
77dda72
tests: the onchain success path can be replaced, so mine whichever tx…
kwsantiago Sep 19, 2026
a4b14cd
tests: the unified Schnorr byte raised the default lease weight by two
kwsantiago Sep 20, 2026
b25051f
tests: the two unified tests that touch the db file assume sqlite
kwsantiago Sep 20, 2026
1ea32ac
hsmd: build the signing transaction once per pass, not once per input
kwsantiago Sep 20, 2026
35c2e1e
tests: the raw open_channel2 must name a feerate and channel type thi…
kwsantiago Sep 20, 2026
aa1a57d
pytest: a PSBT carrying only a non_witness_utxo must not be fatal
kwsantiago Sep 20, 2026
1fe5dcd
wallet: fill in the witness_utxo even when the caller supplied a non_…
kwsantiago Sep 20, 2026
1260ff7
bitcoin: keep checking a prevout whose txid we did not recompute
kwsantiago Sep 20, 2026
b056d84
bitcoin: bound the input index before reading the PSBT
kwsantiago Sep 20, 2026
884683c
pytest: activate Blake2b past the canned blocks instead of skipping t…
kwsantiago Sep 20, 2026
69559b2
pytest: don't reap a bitcoind that was never started
kwsantiago Sep 20, 2026
9863a4b
pytest: the splice feerate migration test modifies the db directly, s…
kwsantiago Sep 20, 2026
11e9407
ci: drop the elements integration job, which this branch does not target
kwsantiago Sep 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ env:
SLOW_MACHINE: 1
# This is for the pytest-trackflaky:
# CI_SERVER_URL: "http://35.239.136.52:3170"
PYTEST_OPTS_BASE: "-vvv --junit-xml=report.xml --timeout=1800 --durations=10 --maxfail=1"
PYTEST_OPTS_BASE: "-vvv --junit-xml=report.xml --timeout=1800 --durations=10"
TEST_LOG_IGNORE_ERRORS: "1"
SCCACHE_GHA_ENABLED: "true"

Expand Down Expand Up @@ -575,13 +575,6 @@ jobs:
COMPILER: gcc
TEST_DB_PROVIDER: postgres
TEST_NETWORK: regtest
# And don't forget about elements (like cdecker did when
# reworking the CI...)
- NAME: liquid
CFG: compile-gcc-O3
COMPILER: gcc
TEST_NETWORK: liquid-regtest
TEST_DB_PROVIDER: sqlite3
# And dual funding!
- NAME: dual-fund
CFG: compile-gcc
Expand Down
75 changes: 0 additions & 75 deletions .github/workflows/paperclip-build.yml

This file was deleted.

18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
# Core Lightning with BLAKE2b proof of work

This is an unofficial fork of [Core Lightning](https://github.com/ElementsProject/lightning) that follows the BLAKE2b proof-of-work hardfork of Bitcoin. It is not affiliated with the Core Lightning project. Upstream is still on the pre-fork rules, so use it instead if that is what you want.
This is an unofficial fork of [Core Lightning](https://github.com/ElementsProject/lightning) that follows the BLAKE2b proof-of-work hardfork of Bitcoin. It is not affiliated with the Core Lightning project. Core Lightning is still on the SHA256d rules, so use it instead if that is what you want.

> **Not audited. Use at your own risk, and no warranty of any kind, see the [BSD-MIT license](LICENSE).** It holds keys and funds, it changes how transactions are signed, and the feature numbers it uses on the wire are provisional. Read *Before opening channels* below. Everything under the divider is upstream's documentation and describes Core Lightning rather than this fork.
> **Not audited. Use at your own risk, and no warranty of any kind, see the [BSD-MIT license](LICENSE).** It holds keys and funds, it changes how transactions are signed, and the feature numbers it uses on the wire are provisional. Read *Before opening channels* below. Everything under the divider is Core Lightning's documentation and describes Core Lightning rather than this fork.

## What differs from upstream
## What differs from Core Lightning

- **BLAKE2b block headers.** Parses the 164 byte v2 header and takes its BLAKE2b hash as the block id. A header announces itself through the top bit of its version word, so no activation height is compiled in and nothing has to be configured per network. Without this a node cannot parse the activation block and stops there.
- **Unified signatures.** Wallet transactions and new channels are signed with the fork's opt-in `SIGHASH_UNIFIED` digest, so a channel funded past activation from post-activation coins is signed in a way the pre-fork rules reject, and cannot be replayed on the SHA256d chain. Built on [connorslab's](https://github.com/connorslab/lightning) unified-sigs work.
- **A required peer feature bit.** The node advertises `option_blake2b` as compulsory, so it will not connect to a Lightning node still on the pre-fork rules.
- **Unified signatures.** Wallet transactions and new channels are signed with the fork's opt-in `SIGHASH_UNIFIED` digest, so a channel funded past activation from post-activation coins is signed in a way the SHA256d rules reject, and cannot be replayed on the SHA256d chain. Built on [connorslab's](https://github.com/connorslab/lightning) unified-sigs work.
- **A required peer feature bit.** The node advertises `option_blake2b` as compulsory, so it will not connect to a Lightning node still on the SHA256d rules.
- **Downgrades are refused.** A build without unified signing computes a different signature hash and could not close the channels this one opens, so `lightning-downgrade` stops before touching the database.

## Before opening channels

The required feature bit means you **cannot cooperatively close a channel opened before activation** with a counterparty still on the pre-fork rules.
The required feature bit means you **cannot cooperatively close a channel opened before activation** with a counterparty still on the SHA256d rules.

The feature numbers are provisional. Bits 68 and 70 are not registered BOLT allocations and are expected to move; an alternative proposal signals odd in `init` with numbers at or above 32768, and the two are mutually exclusive. Channels opened under the current numbering may have to be closed and reopened once the numbers are settled.

Fund channels only from coins received past activation. A channel funded from a pre-fork UTXO has a funding transaction valid under both rule sets, which reopens the exposure unified signing exists to close.
Fund channels only from coins received past activation. A channel funded from a pre-activation UTXO has a funding transaction valid under both rule sets, which reopens the exposure unified signing exists to close.

## Activation

Expand All @@ -26,11 +26,11 @@ Fund channels only from coins received past activation. A channel funded from a
| mainnet | 961,640 |
| testnet4 | 150,308 |

These are the heights Bitcoin Knots activates at, listed for reference only. This fork keys off the header itself rather than a height, so it needs no updating if they change.
These are the activation heights. Block parsing does not use them: it keys off the header itself, so it needs no update if the heights move. They are compiled in for gossip alone. Announcements for channels funded before activation are ignored, since that funding output exists under the SHA256d rules too and its spend may happen where this node cannot see it. The same height bounds where the seeker probes for short channel ids.

## Building

Unchanged from upstream, see [Getting Started](#getting-started) below. Clone this repository rather than upstream's:
Unchanged from Core Lightning, see [Getting Started](#getting-started) below. Clone this repository rather than Core Lightning's:

```bash
git clone https://github.com/privkeyio/lightning.git
Expand Down
3 changes: 2 additions & 1 deletion bitcoin/chainparams.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const struct chainparams networks[] = {
.max_payment = AMOUNT_MSAT_INIT(0xFFFFFFFFULL),
.max_supply = AMOUNT_SAT_INIT(2100000000000000),
/* "Lightning Charge Powers Developers & Blockstream Store" */
.when_lightning_became_cool = 504500,
.when_lightning_became_cool = 961640,
.p2pkh_version = 0,
.p2sh_version = 5,
.testnet = false,
Expand Down Expand Up @@ -147,6 +147,7 @@ const struct chainparams networks[] = {
0x53, 0xbc, 0xfb, 0xba, 0xf2, 0x84, 0xda,
0x00, 0x00, 0x00, 0x00}}}},
.rpc_port = 48332,
.when_lightning_became_cool = 150308,
.ln_port = 49735,
.cli = "bitcoin-cli",
.cli_args = "-testnet4",
Expand Down
1 change: 1 addition & 0 deletions bitcoin/signature.c
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ void sign_tx_input(const struct bitcoin_tx *tx,
struct sha256_double hash;
bool use_segwit = witness_script != NULL;
const u8 *script = use_segwit ? witness_script : subscript;
assert(in < tx->psbt->num_inputs);
if (tx->psbt->inputs[in].sighash & SIGHASH_UNIFIED)
sighash_type |= SIGHASH_UNIFIED;

Expand Down
38 changes: 38 additions & 0 deletions bitcoin/test/run-chainparams-blake2b.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include "config.h"
#include <assert.h>
#include <bitcoin/chainparams.h>
#include <common/setup.h>
#include <stdio.h>

/* AUTOGENERATED MOCKS START */
/* AUTOGENERATED MOCKS END */

/* The proof of work changed at this block; the chain did not. */
#define BLAKE2B_ACTIVATION 961640

int main(int argc, char *argv[])
{
const struct chainparams *main;
struct bitcoin_blkid genesis;

common_setup(argv[0]);
main = chainparams_for_network("bitcoin");
assert(main);

/* Gossip for channels funded before activation is ignored: that output
* exists for nodes which did not upgrade too, and its spend may happen
* where this node cannot see it. */
assert(main->when_lightning_became_cool == BLAKE2B_ACTIVATION);

/* chain_hash stays Bitcoin's genesis. This is Bitcoin; only the proof
* of work changed, so the network is not given an identity of its own. */
assert(bitcoin_blkid_from_hex("000000000019d6689c085ae165831e93"
"4ff763ae46a2a6c172b3f1b60a8ce26f",
64, &genesis));
assert(bitcoin_blkid_eq(&main->genesis_blockhash, &genesis));

printf("when_lightning_became_cool = %u, chain_hash unchanged\n",
main->when_lightning_became_cool);
common_shutdown();
return 0;
}
26 changes: 20 additions & 6 deletions bitcoin/unified_sighash.c
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,27 @@ bool bitcoin_tx_unified_sighash(const struct bitcoin_tx *tx, size_t input,
if ((hash_type & 0x80) && i != input)
continue;
if (p->utxo) {
struct bitcoin_txid prev;
wally_txid(p->utxo, &prev);
if (memcmp(&prev, tx->wtx->inputs[i].txhash, sizeof(prev))
|| tx->wtx->inputs[i].index >= p->utxo->num_outputs)
const struct wally_tx_output *full;

if (tx->wtx->inputs[i].index >= p->utxo->num_outputs)
goto done;
const struct wally_tx_output *full =
&p->utxo->outputs[tx->wtx->inputs[i].index];
/* Only the txid costs anything here: deriving it means
* hashing the whole previous transaction, and this loop
* runs for every input of every signature, so doing it
* where `witness_utxo` already answers is quadratic in
* whole transactions. Check the input being signed,
* which is every input across a complete signing pass,
* and otherwise only where there is nothing else to
* read the output from. The comparison below is cheap,
* so it stays for every input. */
if (i == input || !out) {
struct bitcoin_txid prev;
wally_txid(p->utxo, &prev);
if (memcmp(&prev, tx->wtx->inputs[i].txhash,
sizeof(prev)))
goto done;
}
full = &p->utxo->outputs[tx->wtx->inputs[i].index];
if (out && (out->satoshi != full->satoshi
|| out->script_len != full->script_len
|| memcmp(out->script, full->script, out->script_len)))
Expand Down
8 changes: 6 additions & 2 deletions contrib/pyln-testing/pyln/testing/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,12 @@ def bitcoind(request, directory, teardown_checks):
try:
bitcoind.stop()
except Exception:
bitcoind.proc.kill()
bitcoind.proc.wait()
if bitcoind.proc is not None:
bitcoind.proc.kill()
# A test that took the fixture unstarted, or skipped before starting it,
# has nothing to reap here.
if bitcoind.proc is not None:
bitcoind.proc.wait()

bitcoind.cleanup_files()

Expand Down
20 changes: 10 additions & 10 deletions contrib/pyln-testing/pyln/testing/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,10 @@ def __init__(self, bitcoin_dir="/tmp/bitcoind-test", rpcport=None):
'-debug=rpc',
'-debug=validation',
'-rpcthreads=20',
# Canned blocks carry the timestamp they were recorded with, so
# without this the node stays in initial block download forever
# and lightningd waits on it.
'-maxtipage=315360000',
]
# For up to and including 0.16.1, this needs to be in main section.
BITCOIND_CONFIG['rpcport'] = rpcport
Expand Down Expand Up @@ -612,27 +616,23 @@ def simple_reorg(self, height, shift=0):
"""
assert self.canned_blocks is None
hashes = []
fee_delta = 1000000
orig_len = self.rpc.getblockcount()
old_hash = self.rpc.getblockhash(height)
final_len = height + shift if height + shift > orig_len else 1 + orig_len
# TODO: raise error for insane args?

self.rpc.invalidateblock(old_hash)
self.wait_for_log(r'InvalidChainFound: invalid block=.* height={}'.format(height))
memp = self.rpc.getrawmempool()

if shift == 0:
hashes += self.generate_block(1 + final_len - height)
else:
for txid in memp:
# lower priority (to effective feerate=0) so they are not mined
self.rpc.prioritisetransaction(txid, None, -fee_delta)
hashes += self.generate_block(shift)

for txid in memp:
# restore priority so they are mined
self.rpc.prioritisetransaction(txid, None, fee_delta)
# Mine the shift blocks empty so the mempool txs land after them.
# generateblock takes the exact tx list, where prioritisetransaction
# only asks: some backends mine a deprioritised tx regardless.
for _ in range(shift):
hashes.append(self.rpc.generateblock(self.rpc.getnewaddress(),
[])['hash'])
hashes += self.generate_block(1 + final_len - (height + shift))
self.wait_for_log(r'UpdateTip: new best=.* height={}'.format(final_len))
return hashes
Expand Down
5 changes: 4 additions & 1 deletion gossipd/seeker.c
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,10 @@ static void probe_random_scids(struct seeker *seeker, size_t num_blocks)
avail_blocks = seeker->daemon->current_blockheight
- chainparams->when_lightning_became_cool;

if (avail_blocks < num_blocks) {
/* <= not <: pseudorand(0) asserts, which would take gossipd and so
* lightningd down, once the tip is exactly num_blocks past the
* activation height. */
if (avail_blocks <= num_blocks) {
seeker->scid_probe_start = 0;
seeker->scid_probe_end = seeker->daemon->current_blockheight;
} else {
Expand Down
Loading
Loading