How do you validate a public key in a pseudonymous network?
Now that more node operators are adopting Bitcoin Knots, it’s time to shore up our defenses—let’s implement strict public‑key validation in Knots’ policy to block invalid‑key spam once and for all. 💪
nostr:nprofile1qqs0m40g76hqmwqhhc9hrk3qfxxpsp5k3k9xgk24nsjf7v305u6xffcpzamhxue69uhhyetvv9ujuurjd9kkzmpwdejhgtcpzamhxue69uhhyetvv9ujumn0wd68ytnfdenx7tclykz52 nostr:nprofile1qqs8fl79rnpsz5x00xmvkvtd8g2u7ve2k2dr3lkfadyy4v24r4k3s4spremhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet59uq32amnwvaz7tmjv4kxz7fwv3sk6atn9e5k7tc9vz57s nostr:nprofile1qqsggcc8dz9qnmq399n7kp2yu79fazxy3ag8ztpea4y3lu4klgqe46qppamhxue69uhku6n4d4czumt99uq3gamnwvaz7tmjv4kxz7tpvfkx2tn0wfnj7ru4snc
Discussion
You validate a public key purely by checking its math, not the user’s identity
What math?
Does this address pass your validation?
11wizSAYSbuyXbt9d8JV8ytm5q69NaCfJ
How about this one?
1wizSAYSbuyXbt9d8JV8ytm5acqq2TorC
Or are you referring to something else? Is there an occurrence of public keys which arent valid secp256k1 points?
Yes—this is precisely how a real spam attack works. Attackers craft fake public keys that aren’t valid secp256k1 points to create outputs that look normal but can never be spent. Since these counterfeit keys don’t need real signatures, the outputs are smaller and cheaper to include in blocks, making it a low-cost way to bloat the UTXO set and push storage burdens onto full node operators. That’s why validating public keys at the curve level is essential—it closes this loophole.
But many addresses are a hash of the public key, so we cannot validate the public keys when transactions are paying money to them. For these addresses it is impossible to filter based on public key until it is revealed, at time of spending, which requires them to be real and not spam!
Though I haven't thought about this wrt P2TR, and was somewhat surprised to see point validity isn't enforced:
https://github.com/bitcoin/bitcoin/pull/24106
I think id rather the spammers use invalid secp points! A nice broom to have up the sleeve? Can easily patch into a node to sweep out all the invalid ones from the utxoset.
It is super easy to generate these "fake" secp256k1 points which are valid:
02b33fb33fb33fb33fb33fb33fb33fb33fb33fb33fb33fb33fb33fb33fb33fb33f
```rust
use rand::{RngCore, thread_rng};
use secp256kfun::{
Point, hex,
marker::{NonZero, Public},
};
fn main() {
let mut rng = thread_rng();
let hex_str = "b33fb33fb33fb33fb33fb33fb33fb33fb33fb33fb33fb33fb33fb33fb33f";
let hex_bytes = hex::decode(hex_str).expect("invalid hex string");
loop {
let mut random_bytes = [0u8; 64];
rng.fill_bytes(&mut random_bytes);
let my_point_bytes = {
let mut bytes = [0u8; 33];
bytes[0] = 0x02; // start with 02
let hex_len = hex_bytes.len().min(32);
bytes[1..1 + hex_len].copy_from_slice(&hex_bytes[0..hex_len]);
// fill the rest with random bytes
if hex_len < 32 {
bytes[1 + hex_len..33].copy_from_slice(&random_bytes[0..32 - hex_len]);
}
bytes
};
let point = Point::<_, Public, NonZero>::from_bytes(my_point_bytes);
match point {
Some(valid_point) => {
println!("Random valid point: {:?}", valid_point);
}
None => {
eprintln!("Invalid point from bytes: {}", hex::encode(&my_point_bytes));
}
}
}
}
```
A spam‑style transaction begins when a wallet crafts an output script like OP_DATA_33
Checksum catches typos at the surface; a quick curve check one layer deeper—during relay or consensus—blocks those fake‑key outputs before they hard‑code themselves into the UTXO set, and forces spammers to pay at least some extra computation and density penalty.
Yeah not sure i am convinced of the reasons in that issue to **not** filter these, but i am thinking there may be value to the fact that this filter could be created at any time in the future, clear out a large chunk of the UTXO set after they have wasted their sats. Depends if how bad you consider the spam, is it about utxoset bloat or want to filter their existence in blocks
I feel the same way; I'm still trying to grasp this situation. In a surprising turn of events, the entire OP_RETURN debacle has become a blessing. As we can see from this discussion and the node charts, more and more people are becoming educated about spam issues, with the number of knots gaining momentum. 🫡💪🪢
Has been great discussing this - got me thinking deeply and I found a bug in one of our cryptography libraries while exploring valid secp256k1 points! I haven't looked into knots really, semi dismissed it a while ago when i first saw this nuts counting system was supported(?)
By the way, your product looks amazing! I can't wait to try it!
Making the pubkey curve‑valid doesn’t bypass the fix, because once every key has to sit on secp256k1, spammers must brute‑force each payload byte, turning their zero‑cost UTXO bloat into an exponentially expensive grind
This should demonstrate that:
https://codesandbox.io/p/devbox/6wrmny
(Might be completely wrong, I am also doing my best to wrap my head around this complex issue)
We need the pros to comment here nostr:nprofile1qyxhwumn8ghj7cnjvghxjme0qyt8wumn8ghj7etyv4hzumn0wd68ytnvv9hxgtcqyr7at68k4cxms9a7pdca5gzf3svqd95d3fj9j4vuyj0nyta8x3j2whad7ya nostr:nprofile1qythwumn8ghj7ct5d3shxtnwdaehgu3wd3skuep0qyt8wumn8ghj7ct4w35zumn0wd68yvfwvdhk6tcqyp60l3gucvq4pnmekm9nzmf6zh8nx24jngu0aj0tfp9tz4gad5v9v94ulq2 nostr:nprofile1qyt8wumn8ghj7ct5d3shxtnwdaehgu3wd3skueqpz4mhxue69uhk2er9dchxummnw3ezumrpdejqqgxv4fvwxlyeepdute65q298rz75vjz7t57txdzkj8k2hq782h2ges3dvnqp

You can verify it yourself here:
https://onecompiler.com/python/43gy82we6
The first address fails the Base58Check checksum, so it’s not valid. The second one passes and decodes to a proper version 0 (P2PKH) hash.
Ah im an idiot i accidentally added an additional character at the front of the top one, it passes if you include it (11wizSAYSbuyXbt9d8JV8ytm5q69NaCfJ).
As do all these
"1WizSAYSbuyXbt9d8JV8ytm5q663tk94u",
"1WizSAYSbuyXbt9d8JV8ytm5q69PymsLb",
"1WizSAYSbuyXbt9d8JV8ytm5q69PymsLb",
And these (your npub padded by 1s)
"1Npub1w78wrepa4wz5odvqLq1112PjcmD",
"1HfeL834gv3282grjeg5cw2e6311CxBVx",
"1Eqszny2ms2jovtt111czzzzzzzywryfu"
Huge respect for posting code 🔥
Mine can be found here