I have been building a thing I call depth-of-identity: instead of asking an account who are you (credentials, KYC, a captcha), you measure what it has done and how hard that history would be to fake. Chains of work, social reciprocity, money that actually moved. The bet is that behavior across several independent dimensions is exponentially more expensive to counterfeit than any single number like follower count.
I wrote a companion post about one narrow slice of this — gap-entropy, telling scheduled bots from humans purely by when they post. This post is about the other half: I took the aggregate scorer, the thing that rolls several dimensions into one weight, and hung it off a plain read-only endpoint. You can curl it. I want people to curl it and tell me where it is wrong.
The endpoint
GET https://captcha.powforge.dev/api/depth-check?pubkey=<64-hex or npub1...>
No auth, no body, no cost. It accepts either a raw 64-char hex pubkey or an npub1.... Give it garbage and it tells you:
{"error":"invalid or missing pubkey (64-hex or npub1...)"}
Give it a real pubkey and you get the full breakdown. Here is the actual shape, trimmed to the scores (every account has the four dimensions; each carries a pile of sub-metrics I left in the live response):
{
"pubkey": "...",
"depth": 307,
"threshold": 100,
"would_skip": true,
"gating_active": false,
"active_dimensions": 2,
"total_events": 837,
"account_age_days": 564,
"dimensions": {
"social": { "score": 142, "reciprocityRatio": ..., "bidirectionalPeers": ... },
"access": { "score": 0, "totalPowBits": ..., "maxDifficulty": ... },
"vouch": { "score": 0, "inboundVouches": ..., "cycleDropped": ... },
"economic": { "score": 114, "inboundZaps": ..., "genuineSenders": ... }
},
"note": "diagnostic only — CAPTCHA gating is not affected by this score"
}
Those are real numbers from scoring a long-lived, active pubkey (837 events, 564 days old). It cleared the threshold of 100 on two dimensions alone — social and economic — without any proof-of-work or vouch history. That is the whole point of the design: you do not need to max one axis, you need genuine breadth.
What the four dimensions mean
- social — not follower count. It looks at reciprocity: how many peers reference you back versus the ones you only shout at. A thousand inbound mentions from accounts you never engage is worth far less than a hundred bidirectional conversations.
- access — proof-of-work you have actually spent. Bits of difficulty on your events, log2-scaled so one heroic grind cannot hijack the whole score (I learned that the hard way; an early version let a PoW farm out-score real humans 15-to-1 until I capped per-dimension contribution).
- vouch — inbound vouches from other identities, with cycles dropped so a ring of sockpuppets vouching for each other collapses to near nothing.
- economic — zaps, deduplicated by the underlying Lightning invoice so you cannot inflate by re-broadcasting the same payment. It weighs genuine reciprocal senders over one-directional spray.
The depth weight is the additive roll-up. active_dimensions counts how many axes are non-zero, which matters more than the raw sum — a Sybil can farm one dimension, but farming three independent ones at once is where the cost explodes.
The honest limitation
Here is the part product pages leave out. The scorer only knows what its relay has seen. The deployed endpoint queries the powforge relay's view of the network. So if you throw your own pubkey at it and get a wall of zeros with would_skip: false, that does not mean you are a bot — it means that relay has never ingested your events. Depth is relative to a corpus, and a small corpus makes almost everyone look shallow.
That is not a bug I am hiding, it is the actual research question. A depth score is only as good as the events behind it, and honest deployment means telling you which events those are.
If you want a real number for an arbitrary pubkey, run the scorer yourself against public relays. It is the same @powforge/identity code the endpoint calls:
const { getIdentityDepth } = require('@powforge/identity');
const report = await getIdentityDepth(pubkey, {
relays: ['wss://relay.damus.io', 'wss://nos.lol'],
timeout: 12000,
});
console.log(report.weight, report.activeDimensions);
Pointed at those two relays, the pubkey above scored 307 across two active dimensions. Pointed at a relay that has never heard of it, the same pubkey scores 0. Same code, different corpus. Sit with that for a second — it is the honest shape of every reputation system, most of them just do not show you the seam.
Why it does not gate anything
Notice gating_active: false and the note. This endpoint never blocks a captcha, never lets anyone skip a challenge, never touches the verify path. It is groundwork. Before I wire "skip the captcha if depth > threshold" into anything real, I want to watch the skip policy run against live traffic and see how often it would have been wrong — how many bots would have sailed through, how many humans it would have wrongly gated. Measure first, gate later, and let people poke holes while it is still only a diagnostic.
That is the invitation. Query it, run the local version against relays that actually know your account, and if you find a pubkey where the score is obviously backwards, that is exactly the failure I want to hear about.
Top comments (0)