Skip to content

Add stateless signer program#7

Open
grod220 wants to merge 3 commits into
mainfrom
executor-pt-1
Open

Add stateless signer program#7
grod220 wants to merge 3 commits into
mainfrom
executor-pt-1

Conversation

@grod220

@grod220 grod220 commented Jun 11, 2026

Copy link
Copy Markdown
Member

Implements the first step of the executor-model decomposition proposed in #4. This new program has a single ix Submit which simply verifies authority ed25519 signatures over an opaque payload, then CPIs the executor program the payload names, promoting each authority's ProgrammaticSigner PDA to a signer.

The executor program (in the next PR) is responsible for nonce advancing, parsing the message bytes, and replaying the instructions.

This new signer program is simple and stateless, so the objective is to make this immutable.

Comment thread client/src/instruction.rs Outdated
solana_instruction::{AccountMeta, Instruction},
solana_sdk_ids::sysvar::slot_hashes,
spl_ed25519_programmatic_signer_interface::instruction::ProgrammaticSignerInstruction,
spl_ed25519_programmatic_signer_legacy_interface::instruction::ProgrammaticSignerInstruction,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping this old crates here until I can get Initialize code moved to the new program in the next PR

Comment thread Cargo.toml
Comment on lines +43 to +44
# TODO: pinned to mollusk's `4.1-beta` branch for a friendlier dep graph resolution w/ the new no-std crates
mollusk-svm = { git = "https://github.com/anza-xyz/mollusk", branch = "4.1-beta" }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found this to solve all of my no-std crate issues given the latest updates. Think acceptable for now and can go back to stable mollusk version when ready.

@grod220 grod220 force-pushed the executor-pt-1 branch 2 times, most recently from 534687c to b4dabef Compare June 11, 2026 10:43
Comment thread signer/interface/src/instruction.rs Outdated
@grod220 grod220 requested review from joncinque and t-nelson June 11, 2026 10:47

@joncinque joncinque left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the lateness, but looks really good to me! A few questions about usability, mostly around hardware wallets, but the guts are definitely solid

Comment thread signer/client/src/instruction.rs Outdated
Comment thread signer/interface/src/instruction.rs Outdated
Comment thread signer/interface/src/instruction.rs Outdated
Comment thread signer/program/src/submit.rs
Comment thread signer/program/src/submit.rs Outdated
Comment on lines +46 to +50
// The signed payload specifies which signer program it authorizes, so an envelope
// signed for a different signer program cannot be replayed against this one.
if &payload.signer_program_id != program_id {
return Err(Error::SignerProgramMismatch.into());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is needed, since execution / replay protection is handled by the executor program.

If someone uses a different signer program, they'll get different PDAs, which shouldn't cause any issues. If someone has PDAs with multiple different signer programs, then the executor should still protect.

Writing this out, it seems like protection against a certain possible class of executor program bugs, which isn't the worst thing in the world, so we can keep it

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually think that's a good point. If the cold-wallet is expecting a certain signer program, it's because they need signer privileges it can grant. With the relayer passing it into the wrong one, it arrives without those privileges. The executor program needs to check for .is_signer() regardless.

That said, in the new design, if we wanted to bring back this check, I suppose we could hash the signer program id and put it in the recent blockhash field (currently unused in the inner wrapped tx and used in the inner-inner one as the nonce).

Comment thread signer/program/src/submit.rs Outdated
Comment thread signer/program/tests/helpers/submit_builder.rs Outdated
Comment thread signer/program/tests/test_submit.rs
Comment thread signer/program/tests/test_submit.rs Outdated
Comment thread signer/program/tests/test_submit.rs
@2501babe 2501babe self-requested a review June 22, 2026 21:38
Comment thread client/src/instruction.rs
Comment thread signer/client/src/instruction.rs Outdated
Comment thread signer/interface/src/instruction.rs Outdated
Comment thread signer/interface/src/instruction.rs Outdated
Comment thread signer/program/src/submit.rs Outdated
Comment thread signer/program/tests/helpers/submit_builder.rs
Comment thread signer/program/tests/helpers/submit_builder.rs Outdated
Comment thread signer/program/tests/helpers/submit_builder.rs Outdated
Comment thread signer/program/src/submit.rs Outdated

let (programmatic_signer, bump) =
ProgrammaticSigner::derive_address_and_bump(program_id, authority);
authorized.push(AuthorizedSigner {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we reject duplicates (somewhere)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't see a security reason why to prevent it. Duplicates don't grant any extra authority and the executor handles the account list policy.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should check the history of the spl-token multisig functionality 😂

Comment thread Cargo.toml
@grod220

grod220 commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

A pretty significant refactor on this latest commit given the issues discussed in #7 (comment). Hardware wallet support is critical, so the signed payload changed:

BEFORE: SubmitPayload { signer_program_id, executor_program_id, data }        ✗ Ledger/Trezor reject
AFTER:  Message { header | account_keys | opaque blockhash | 1 executor ix }  ✓ any wallet

Also, now that the signed message contains the account list/flags, the signer program can enforce that the outer accounts mirror the signed keys exactly.

@grod220 grod220 requested review from joncinque and t-nelson July 6, 2026 17:13

@t-nelson t-nelson left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i can get behind this as mvp. just a couple nits

still p sure signer and executor functionality can be fully decoupled, but we can table that for now as most of the work should be in client side transaction construction, which we're handing to consumers a library either way

Comment on lines +165 to +175
let signer_seeds: Vec<[Seed; 3]> = authorized_signers
.iter()
.map(|signer| {
[
Seed::from(ProgrammaticSigner::SEED_PREFIX),
Seed::from(signer.authority.as_ref()),
Seed::from(&signer.bump_seed),
]
})
.collect();
let cpi_signers: Vec<Signer> = signer_seeds.iter().map(Signer::from).collect();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like these can be combined to save a collect?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It explodes. Signer does not own the seed array, it only stores a borrow pointing into it. So the first is the storage that keeps those seed arrays alive.

Comment thread signer/program/src/submit.rs Outdated
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.

4 participants