feat(external-delegate-token-master): add pinocchio example - #715
feat(external-delegate-token-master): add pinocchio example#715MarkFeder wants to merge 2 commits into
Conversation
Greptile SummaryAdds a Pinocchio implementation of the external-delegate token-transfer example, including raw Keccak/secp256k1 syscall bindings, program state and instruction handlers, workspace integration, and LiteSVM coverage.
Confidence Score: 5/5The PR appears safe to merge with no concrete changed-code defect identified. The new implementation consistently validates the stored Solana authority, derives and signs with the user-scoped PDA, binds Ethereum signatures to the complete transfer context and nonce, and relies on the SPL Token program’s account and mint invariants at the CPI boundary. Important Files Changed
Reviews (1): Last reviewed commit: "external-delegate-token-master: drop an ..." | Re-trigger Greptile |
|
@amilz could you take a look at this one when you get a chance? No open review threads left on it, so it is ready for maintainer review. It is one of 23 open Pinocchio ports I have up — they are independent and self-contained, so they can be reviewed and merged in any order: https://github.com/solana-developers/program-examples/pulls/MarkFeder |
Adds a Pinocchio implementation of
external-delegate-token-master, alongside the existing Anchor one.What it does
Lets an Ethereum key authorise Solana token transfers. Tokens sit in an account owned by a PDA of this program, and a secp256k1 signature over an on-chain-rebuilt digest is what moves them — no Solana signature from the token holder required.
Four instructions:
Initialize,SetEthereumAddress,TransferTokens(Ethereum-signed), andAuthorityTransfer(the Solana escape hatch, so losing the Ethereum key does not strand the balance).The crypto, and why it is hand-rolled
solana-keccak-hasherandsolana-secp256k1-recoverboth linkstd, which collides withnostd_panic_handler!in a#![no_std]pinocchio program —error[E0152]: found duplicate lang item panic_impl. Socrypto.rsdeclares the two syscalls directly withsolana_define_syscall::define_syscall!, which is smaller and supplies a host-side stub socargo clippystill compiles for the host target.The subtle part, called out in a comment because it is the classic way to get this wrong:
sol_secp256k1_recoverreturns the bare 64-byteX || Y, whereas most Ethereum tooling (@noble/curvesincluded) returns the 65-byte0x04-prefixed form. Keccak-hashing the wrong one yields a different address. The Rust side does not slice; the TypeScript side does.Replay resistance
The digest commits to everything that decides where funds go — domain separator, program id, user account, both token accounts, amount — plus a nonce that is consumed before the transfer executes. So a signature cannot be replayed, redirected, or re-presented for a different amount. It is signed raw, with no EIP-191 prefix, so a wallet's default
personal_signoutput deliberately will not verify.There are tests for each of those: replay, amount swap, and wrong signing key.
One addition over the Anchor version
A fresh account's Ethereum address is all zeroes. The Anchor version verifies against it as-is, so a signature that recovers to the zero address would be accepted on an account whose owner never opted in. This port rejects transfers until an address has been set (
EthereumAddressUnset), with a test.Dependencies
Adds
solana-define-syscallto the workspace, and@noble/curves+@noble/hashesas dev dependencies for the test (the same libraries the Anchor test uses);pnpm-lock.yamlis regenerated and--frozen-lockfileverified.Tests
10 LiteSVM tests covering both transfer paths and five rejection cases, exercising real secp256k1 recovery through the syscall. Verified locally:
tsc --noEmit,pnpm test,prettier --check,cargo fmt --check,cargo clippy -D warnings.