NakedPnL

The public registry of verified investment performance. Every return sourced from SEC filings, exchange APIs, or platform data.

Registry

  • Registry
  • Market Context
  • How It Works
  • Community

Verification

  • Get Verified
  • Connect Exchange

Legal

  • Terms of Service
  • Privacy Policy
  • Refund & Cancellation
  • Support
  • GDPR Rights
  • Cookie Policy
  • Disclaimers
  • Methodology
  • Compliance
Follow

NakedPnL is a publisher of verified performance data. Nothing on this site constitutes investment advice, a recommendation, or a solicitation to buy, sell, or hold any security, commodity, or digital asset. Past performance does not indicate future results. Trading carries a high risk of total capital loss.

© 2026 NakedPnLAll performance data is verified by the NakedPnL teamcontact@nakedpnl.com
NakedPnL
RegistryPricingHow It WorksCommunitySupport
NakedPnL/Guides/How to Verify a NakedPnL Trader Track Record in 10 Minutes
Verification guide

How to Verify a NakedPnL Trader Track Record in 10 Minutes

A step-by-step tutorial for independently verifying any NakedPnL track record using SHA-256 hash chains, Bitcoin OpenTimestamps, and exchange API responses.

By NakedPnL Research·May 7, 2026·16 min read
TL;DR
  • Every NakedPnL track record is a SHA-256 hash chain you can recompute locally with no trust in NakedPnL.
  • The full verification has four phases: chain fetch, hash recomputation, OpenTimestamps anchor check, and optional raw-API replay.
  • Phase 1 to 3 take under ten minutes with the Python or Node.js snippets in this guide.
  • If any chainHash, contentHash, or Bitcoin attestation fails to match, the track record is broken and the trader's profile will display a chain-break alert.
  • Verification is a due-diligence input, not investment advice. Use it the same way you would use an auditor's report.
On this page
  1. Phase 1 — Fetch the public chain JSON
  2. Phase 2 — Verify the genesis row
  3. Phase 3 — Recompute every chainHash
  4. Phase 4 — Verify the OpenTimestamps Bitcoin anchor
  5. Phase 5 (optional) — Replay the raw exchange API
  6. Common verification failures and what they mean
  7. Putting it all together — a single script
  8. Frequently asked questions

NakedPnL is a publisher of verified investment performance. The output of that pipeline is a public registry where every row is a daily NAV snapshot bound to the previous row by a SHA-256 hash chain, and every daily batch of chain heads is anchored to Bitcoin via OpenTimestamps. None of that matters unless you, the reader, can re-verify the chain on your own machine. This guide walks through the four phases of full verification end to end.

If you have a Python 3.11+ runtime or Node.js 20+ on your laptop, the entire process takes under ten minutes for a track record with one year of daily snapshots. We will use the public chain JSON endpoint, the Web Crypto API or hashlib, and the OpenTimestamps verifier endpoint. No login is required. No NakedPnL API key is required.

What you will prove
By the end of this tutorial you will have independently confirmed that (a) the chain is internally consistent, (b) the genesis row exists and is correctly tagged, (c) the most recent chain head was committed to the public Bitcoin ledger before any later edit could have occurred, and (d) optionally, the raw exchange API responses match the on-record content hashes.

Phase 1 — Fetch the public chain JSON

Every public NakedPnL profile exposes a JSON endpoint at /verify/chain/[handle].json. The endpoint returns the full ordered chain of NAV snapshots for that handle, including sequence numbers, content hashes, chain hashes, the previous chain hash, and the canonicalized raw-response digest. The endpoint is rate-limited per IP but does not require authentication, because the data is already public registry content.

Pick any verified trader. We will use the placeholder handle `cpx-trader` throughout. Replace it with the real handle you want to inspect. The first command pulls the chain into a local file so the rest of the verification is offline.

curl -s https://nakedpnl.com/verify/chain/cpx-trader.json \
  -o cpx-trader.chain.json
jq '.entries | length' cpx-trader.chain.json
# 365   <-- one year of daily snapshots
Fetch the chain into a local file (curl).

Each entry in the JSON document has the following shape. The fields you must care about are sequence, contentHash, chainHash, and previousHash. The rawResponseDigest is what you would recompute in Phase 4 if you decide to do an end-to-end replay against the venue API.

{
  "sequence": 0,
  "snapshotDate": "2025-05-07",
  "navUsd": "12450.27",
  "venue": "BINANCE",
  "contentHash":  "9f4c...e2a1",
  "chainHash":    "1aa9...b7d0",
  "previousHash": "genesis",
  "rawResponseDigest": "8b22...4f01"
}
Single entry from the chain JSON.

Phase 2 — Verify the genesis row

The first row of every chain is special. Its previousHash is the literal ASCII string `genesis`. Any chain that does not start with that exact value is broken at the root and the rest of the verification is moot. The chainHash of the genesis row is therefore SHA-256 of the bytes 'genesis' concatenated with the contentHash of that first row. This is the only row in the chain whose previous hash is not itself a SHA-256 digest, and it exists so the recursion has a defined base case.

import json, hashlib

with open("cpx-trader.chain.json") as f:
    chain = json.load(f)["entries"]

genesis = chain[0]
assert genesis["sequence"] == 0
assert genesis["previousHash"] == "genesis"

expected = hashlib.sha256(
    ("genesis" + genesis["contentHash"]).encode("utf-8")
).hexdigest()
assert expected == genesis["chainHash"], "Genesis chain hash mismatch"
print("Genesis row OK:", genesis["snapshotDate"])
Verify the genesis row in Python 3.11+.

Phase 3 — Recompute every chainHash

Now walk the chain forward. For every row at index i greater than zero, the expected chainHash is SHA-256 of (chain[i-1].chainHash || chain[i].contentHash). If a single row in the middle has been edited, every downstream row will fail this check. This is the property that makes the chain tamper-evident: an attacker who silently rewrites row 142 must also rewrite rows 143 through 365, but cannot rewrite the OpenTimestamps Bitcoin anchor that fixes row 365 in time.

def sha256(s: str) -> str:
    return hashlib.sha256(s.encode("utf-8")).hexdigest()

prev = "genesis"
for row in chain:
    expected = sha256(prev + row["contentHash"])
    if expected != row["chainHash"]:
        raise SystemExit(
            f"Chain break at sequence {row['sequence']} "
            f"({row['snapshotDate']})"
        )
    prev = row["chainHash"]

print(f"Chain OK: {len(chain)} rows, head={prev[:12]}...")
Walk the chain and recompute every chainHash.

The same algorithm in JavaScript using the Web Crypto API runs in any modern browser, in Node.js 20+, or in Deno. The only nuance is that crypto.subtle.digest returns an ArrayBuffer that you must hex-encode yourself.

const sha256 = async (s) => {
  const data = new TextEncoder().encode(s);
  const buf = await crypto.subtle.digest("SHA-256", data);
  return Array.from(new Uint8Array(buf))
    .map((b) => b.toString(16).padStart(2, "0"))
    .join("");
};

const chain = (await (await fetch(
  "https://nakedpnl.com/verify/chain/cpx-trader.json"
)).json()).entries;

let prev = "genesis";
for (const row of chain) {
  const expected = await sha256(prev + row.contentHash);
  if (expected !== row.chainHash) {
    throw new Error("Break at " + row.sequence);
  }
  prev = row.chainHash;
}
console.log("Chain OK:", chain.length, "rows");
Same chain walk in Node.js 20+ / browser Web Crypto.

Phase 4 — Verify the OpenTimestamps Bitcoin anchor

Recomputing the chain only proves internal consistency. It does not prove that NakedPnL did not silently rebuild the entire chain yesterday. To get external time-evidence, NakedPnL builds a Merkle tree every day at 00:05 UTC over the chain heads of every public entity. The Merkle root is submitted to OpenTimestamps calendar servers, which batch-anchor it into the public Bitcoin ledger.

After Bitcoin includes the calendar's commitment in a block (typically within a few hours), NakedPnL upgrades the .ots receipt with the Merkle path from the daily root all the way down to a Bitcoin block header. Once the proof is in the UPGRADED state with a btcBlockHeight set, the chain head as it existed on that date is anchored to a public block whose hash you can independently verify against any Bitcoin node or block explorer.

The public verifier endpoint /api/verify/[date] returns the full attestation bundle for a given UTC date. The response includes the dailyMerkleRoot, the OpenTimestamps proof in serialized form, the chain heads it covers, and the Bitcoin block height once the proof is upgraded.

curl -s https://nakedpnl.com/api/verify/2026-05-06 | jq '.'

# {
#   "date": "2026-05-06",
#   "merkleRoot": "5fe1...c9b2",
#   "status": "UPGRADED",
#   "btcBlockHeight": 893421,
#   "btcBlockHash": "0000000000000000000272...",
#   "otsProof": "<base64 .ots receipt>",
#   "entityHeads": [
#     { "handle": "cpx-trader", "chainHash": "1aa9...b7d0" },
#     { "handle": "atlas",      "chainHash": "7be4...d901" }
#   ]
# }
Fetch the OTS attestation bundle for the latest snapshot date.

There are two checks to perform here. First, confirm that the chain head you finished Phase 3 with is present in the entityHeads array for the most recent UPGRADED date. If the chain head you computed locally is not in the list, either you are looking at an off-by-one date or the snapshot has not yet been anchored. Second, run the .ots receipt through the standard OpenTimestamps client to confirm the Bitcoin attestation independently of NakedPnL.

# Decode the base64 .ots receipt from the API response
echo "<base64 here>" | base64 -d > 2026-05-06.ots

# Install the official client (https://opentimestamps.org)
pip install opentimestamps-client

# Save the merkle root as a binary file
echo -n "5fe1...c9b2" | xxd -r -p > 2026-05-06.root

# Verify
ots verify 2026-05-06.ots
# Success! Bitcoin block 893421 attests existence as of 2026-05-06
Independent OTS verification with the official client.
Pending versus Upgraded proofs
Anchors created in the last few hours will have status PENDING. The calendar server has accepted the commitment but Bitcoin has not yet confirmed it. Wait until the proof transitions to UPGRADED before treating the date as Bitcoin-anchored. NakedPnL retries upgrades hourly for seven days; after that the proof is marked FAILED and the date is re-anchored.

Phase 5 (optional) — Replay the raw exchange API

The strongest possible verification level requires the trader's cooperation. The contentHash of every row is computed over the canonicalized raw exchange API response, not over the trader-friendly NAV number. If the trader gives you read-only API credentials for the same venue, you can re-fetch the historical balance at the same snapshot timestamp and recompute the contentHash yourself. If it matches, you have proven that NakedPnL did not invent or alter the underlying data.

This phase is optional because it requires venue credentials. Most third-party verifiers (allocators, journalists, regulators) stop after Phase 4 because the OpenTimestamps anchor already binds the contentHash to a Bitcoin block. The raw-replay phase exists for traders who want to demonstrate the strongest possible audit trail to a single counterparty.

PhaseWhat it provesTrust in NakedPnL?
1 — FetchYou have the same JSON the public registry serves.Low
2 — GenesisThe chain is correctly rooted.Zero
3 — RecomputeNo row has been silently edited.Zero
4 — OpenTimestampsThe chain head existed by a specific Bitcoin block.Zero
5 — Raw replayThe published data matches the venue's actual history.Zero
What each verification phase actually proves.

Common verification failures and what they mean

If Phase 3 fails on a single row, the chain has been broken at that sequence number. This is rare in practice because NakedPnL writes are atomic and append-only, but it can happen if a snapshot was retroactively edited. The trader's profile will display a chain-break alert and the registry rank will be suspended pending investigation.

If Phase 4 fails because the merkleRoot does not match the local chain head, you may be looking at the wrong UTC date. The OpenTimestamps anchor at /api/verify/[date] uses calendar dates, so for a snapshot at 23:55 UTC on 2026-05-06 the anchor will be at /api/verify/2026-05-06 and the OTS submission cron at 00:05 UTC on 2026-05-07.

If Phase 5 fails because the recomputed contentHash differs by even one byte, double-check the canonicalization step. Different JSON libraries serialize floats and key order differently. The reference canonicalization rules are documented at /docs/verification along with a Python and JavaScript reference implementation.

Verification is not investment advice
A clean verification proves the published track record is internally consistent and tamper-evident. It does not predict future returns, characterize risk, or substitute for a regulated adviser. Use NakedPnL the same way you would use an auditor's report: as one input into your due-diligence process, not as a recommendation.

Putting it all together — a single script

The four phases compose into a single script you can run on any modern Python install. The complete reference implementation is below. It exits non-zero on any verification failure, which makes it suitable for inclusion in a CI pipeline if you maintain a watchlist of handles.

import json, sys, hashlib, urllib.request, base64

def sha256(s):
    return hashlib.sha256(s.encode("utf-8")).hexdigest()

def fetch(url):
    with urllib.request.urlopen(url) as r:
        return json.loads(r.read())

def verify_chain(handle):
    chain = fetch(f"https://nakedpnl.com/verify/chain/{handle}.json")["entries"]
    prev = "genesis"
    for row in chain:
        expected = sha256(prev + row["contentHash"])
        if expected != row["chainHash"]:
            sys.exit(f"BREAK at sequence {row['sequence']}")
        prev = row["chainHash"]
    return chain[-1]

def verify_ots(date, head_hash):
    bundle = fetch(f"https://nakedpnl.com/api/verify/{date}")
    if bundle["status"] != "UPGRADED":
        print(f"Anchor for {date} is {bundle['status']} (not yet on Bitcoin)")
        return
    heads = {e["chainHash"] for e in bundle["entityHeads"]}
    if head_hash not in heads:
        sys.exit(f"Head {head_hash[:12]} missing from anchor {date}")
    print(f"Anchored at Bitcoin block {bundle['btcBlockHeight']}")

if __name__ == "__main__":
    handle, date = sys.argv[1], sys.argv[2]
    head = verify_chain(handle)
    print(f"Chain OK: {handle} sequence={head['sequence']}")
    verify_ots(date, head["chainHash"])
Full chain + OTS verifier in 60 lines of Python.

Run it once a week against the handles you care about. If the script ever exits non-zero, that is signal worth investigating before any allocation decision.

Frequently asked questions

Do I need a NakedPnL account to verify someone else's track record?
No. The /verify/chain/[handle].json endpoint and /api/verify/[date] endpoint are public and unauthenticated. They are rate-limited per IP, but no login is required because the data is already public registry content.
What happens if the OpenTimestamps proof is still PENDING when I check?
Pending means the OpenTimestamps calendar has accepted the Merkle root but Bitcoin has not yet confirmed it in a block. NakedPnL retries the upgrade hourly for seven days. Wait a few hours and re-fetch the bundle; status will transition to UPGRADED with a btcBlockHeight once Bitcoin confirms the calendar's commitment.
Can a trader edit a row in their own chain to hide a bad day?
Editing a row changes its contentHash, which breaks the chainHash of every subsequent row. Anyone running Phase 3 will detect the break, and any anchor date already covered by a Bitcoin attestation cannot be retroactively rewritten without finding a SHA-256 collision and forking Bitcoin. The chain is append-only by construction, not by promise.
Why is the OpenTimestamps step needed if the chain hashes already match?
Internal hash consistency only proves the chain has not been edited since some unspecified time. It does not prove when each row was written. OpenTimestamps adds the time dimension by binding each daily Merkle root to a public Bitcoin block, so anyone can prove that a given chain head existed before that block was mined.
What does it mean if a trader's profile shows a chain-break alert?
A chain-break alert means the daily snapshot cron found that one or more rows fail to recompute correctly. The trader's registry rank is suspended pending investigation. The most common cause is a corrupted database row from an emergency restore, not deliberate fraud, but the burden of proof is on the trader to explain the break before the rank is restored.
Can I verify a track record without internet access to nakedpnl.com?
Once you have downloaded the chain JSON and the OpenTimestamps .ots receipt, all four phases of verification run offline. You only need internet access to a Bitcoin node or block explorer to confirm the block hash returned by the .ots verifier, which is the entire point of using a public ledger as the trust anchor.
Does the same verification process work for IBKR and prediction-market accounts?
Yes. The hash chain and OpenTimestamps anchor are venue-agnostic. The contentHash is computed over whatever raw response the venue adapter returns: a Binance balance snapshot, an IBKR Flex Web Service XML report, a Polymarket subgraph query result, or a Kalshi positions response. The verification algorithm does not care which venue produced the data.

References

  • OpenTimestamps protocol specification
  • RFC 6234 — US Secure Hash Algorithms (SHA-256)
  • Web Crypto API — SubtleCrypto.digest (MDN)
  • Bitcoin block explorer — independent confirmation of OTS anchors
  • NakedPnL verification algorithm reference
NakedPnL is a publisher of verified investment performance data. We are not an investment adviser, broker, dealer, or asset manager, and nothing on this page constitutes investment advice or a recommendation. See the compliance page for our full regulatory posture.