Skip to content

Solution: LP-0003 Private Allowlist / Airdrop Distributor - #126

Open
edenbd1 wants to merge 3 commits into
logos-co:masterfrom
edenbd1:lp-0003-submission
Open

Solution: LP-0003 Private Allowlist / Airdrop Distributor#126
edenbd1 wants to merge 3 commits into
logos-co:masterfrom
edenbd1:lp-0003-submission

Conversation

@edenbd1

@edenbd1 edenbd1 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

A distributor commits an eligibility set on chain. An eligible recipient claims
their allocation without revealing which entry is theirs — not to observers, and
not to the distributor.

Updated: redeployed on LEZ v0.2.4 (commit 47eba25). A guest's ImageID
depends on the pinned revision, so both programs are new binaries at new
content-addressed deploy transactions, and every account derived from the
verifier's program id moves with them. The two distributions and all 23 claims
were re-created on chain rather than carried over, and the video was
re-recorded against this deployment. The hashes below are the current ones.

What is verified on chain

A claim travels on LEZ's privacy-preserving path, carrying a real STARK receipt
the sequencer checks against the node-pinned PRIVACY_PRESERVING_CIRCUIT_ID.
What lands is a single marker account whose address is
SHA256(prefix ‖ distribution_id ‖ nullifier), where the nullifier is derived
from the recipient's secret. An observer who knows every candidate address still
cannot compute the nullifier, so no marker maps back to a recipient.

Evidence, on the current public testnet

Hash
claim program (deploy) 59c2160b40c5d0f4cce01fd89e7755dbafd9c7d088a071d6f6fd3a10cbbea7c5
claim verifier (deploy) 7b16e471b35ce8c718e066d80a8198f8831ebc7b6704583ddd28bb287092e34c
one claim (privacy tx) 441ccd15e7b5eac388a0849481e95db409f1b6f23a202b6ee1a3ce37ae112c86
curl -s -X POST https://testnet.lez.logos.co -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"getTransaction","params":["<hash>"]}'

Each hash links to its page on the block explorer. Both deploys and all 23 claims render there; a claim shows as Privacy-Preserving Transaction with its proof size, and nothing naming a distribution or a recipient.

The two program hashes are SHA256(borsh(bytecode)) of the binaries committed in
the repository, so they can be recomputed without trusting this table.

Two distributions and 23 privacy-preserving claims (12 + 11) are live, listed
with their nullifiers in artifacts/e2e/claims.tsv.
Each of the 23 marker accounts is derived from the verifier's ImageID and the
claim's nullifier, and each is owned by the verifier — which is the part no
transaction lookup can fake, and what ./scripts/verify-onchain-claim.sh ends on
after five checks.

Beyond the unit tests

docs/onchain-audit.md records three adversarial demonstrations run against the
live deployed programs rather than a local harness: a claim that does not
genuinely prove membership, one that redirects its allocation, and one that
replays a spent nullifier. All three fail at proof generation, so none reaches a
block. scripts/adversarial-onchain.sh re-runs all three against whatever is
deployed, and fails the run if any of them produces a transaction instead of a
rejection.

On the explorer

The explorer is a separate index and reaches a transaction about an hour and
three quarters after the sequencer does — measured, not assumed, so a hash
submitted recently is not there yet while getTransaction already has it.
scripts/check-explorer.py re-measures that by rendering the pages headless with
an impossible hash as the control, because the explorer is a WASM app that
returns an identical shell for every URL and cannot be probed with curl.

Once indexed, a claim renders its transaction type, its proof size and the marker
address — and nothing naming a distribution or a recipient. That is the privacy
property, rendered by a third party.

Against the criteria

30 tests, CI green, including a workflow that runs the whole lifecycle against a
real standalone LEZ sequencer with RISC0_DEV_MODE=0. CU cost is measured per
instruction by replaying the sequencer's own execution of the deployed binary:
318,242 user cycles for a claim, 1.56% of the public budget. The Basecamp module
ships as a .lgx with darwin-arm64 and linux-amd64 variants;
scripts/package-lgx.py --verify recomputes every manifest hash from the
archive's own contents, and the video shows that check passing.

The video shows a fresh claim proved and submitted during the recording, with
RISC0_DEV_MODE=0 visible before any proving starts, and the five checks passing
against it afterwards. It is a single take; the stretch where the proof runs and
nothing on screen changes is played at eight times speed, and nothing is cut.

Happy to address anything in review.

@github-actions

Copy link
Copy Markdown

✅ Validation passed

A reviewer will assess against the prize criteria.
ℹ️ Solution submission for LP-0003.
ℹ️ Checking repo: https://github.com/edenbd1/lp-0003-private-airdrop-distributor


Automated check. See solution template and TERMS.

The deployment moves to LEZ v0.2.4. A guest's ImageID depends on the pinned
revision, so both programs are new binaries at new content-addressed deploy
transactions, and the two distributions and all 23 claims are re-created on
chain rather than carried over. All 23 are verified live and privacy-preserving
over RPC, and all 23 marker accounts re-derived from the verifier ImageID and
confirmed owned by it.

The anchor check is tightened while the deployment was re-keying anyway: `claim`
now requires the distribution account's owner to equal the verifier itself,
rather than merely to be non-default. The published IDL is unchanged.

Corrections, each found by re-checking a claim this document makes:

- `lee/state_machine/src/program.rs` does not exist; the file is
  `program/mod.rs`, and the split predates the release this targets, so the
  citation never resolved. `execution_state.rs:149` sat four lines above the
  `env::verify` it names.
- The explorer does index these transactions; it lags by about an hour and three
  quarters. It does not drop them. And what it shows for a claim — type, proof
  size, marker address, nothing naming a distribution or an address — is the
  privacy property rendered by a third party, so this now says that instead of
  explaining an absence.
- The claim cost is 318,242 user cycles, re-measured against the current binary.
  Proving wall-clock is no longer quoted as a fixed number: it moves by half
  again with machine contention.
- 30 tests across the workspace, not 29; 25 adversarial rejections in the
  executor suite, not 28.

The demo video is re-recorded against this deployment, since the previous one
showed the superseded ImageIDs on screen.
The verify snippet passes a claim tx, a nullifier and a distribution id. Only the
first is a transaction; getTransaction returns null on the other two. Worth
stating here rather than only in the repository docs, since a reviewer checking
the hashes would otherwise hit two nulls and reasonably wonder.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant