Skip to content

Base interfaces & data structures#1

Open
grod220 wants to merge 2 commits intomainfrom
base-ixs
Open

Base interfaces & data structures#1
grod220 wants to merge 2 commits intomainfrom
base-ixs

Conversation

@grod220
Copy link
Copy Markdown
Member

@grod220 grod220 commented Apr 12, 2026

This PR adds base interfaces for the program: instructions, state, message schema, and PDA types.

Overall objective

Write a program that can serve as a functional replacement for the durable nonce usecase:

  • Approve something offline
  • Submit it later from a hot environment
  • Prevent replay
  • Support cold-signing and threshold-multisig workflows

The difference is that durable nonces do this via special runtime functionality (that folks are interested in removing), while this program does it through a promoting a PDA to signer on a pre-signed tx message.

Inspiration

Trent's durable nonce replacement proposal: solana-foundation/solana-improvement-documents#456

& Jon's idea to take a pre-signed-tx and promote the PDA to a signer on that tx.

High-level design

  • NonceState: stores nonce Hash and an authority
  • NonceStatePda: the PDA that stores state
  • NonceAuthorityPda: the PDA the program signs as during CPI
  • Submit instruction data: a canonical serialized solana_transaction::Transaction

The intended flow is:

  1. Derive NonceStatePda + NonceAuthorityPda from a chosen nonce_id.
  2. Initialize the account with a starting authority.
  3. Build a wrapped Transaction whose message.recent_blockhash = state.nonce and whose account_keys[0] = state.authority. Have the authority sign the canonical message bytes offline.
  4. A hot wallet submits Submit with that wrapped Transaction as instruction data.
  5. Program then:
    a. Verifies the authority signed
    b. Checks message.recent_blockhash == state.nonce
    c. Executes each wrapped instruction by CPI, promoting NonceAuthorityPda to signer wherever referenced
    d. Advances the nonce as sha256(tag ‖ state_pda ‖ old_nonce ‖ slot_hashes[0] ‖ sha256(wincode(tx.message))).

Divergences from original proposal

This follows the spirit of Trent's original proposal, but there are a few divergences:

  • Does not use Vault naming as I felt it had too much overlap with the idea of custody and the defi vault concept.
  • Nonce is a 32-byte Hash advanced via a sha256 derivation and not a counter.
  • Signer seeds are not passed in the payload. The program derives one canonical authority PDA from the authority policy.

What's next

  • Add Codama support
  • Generate clients
  • Program implementation
  • Tests
  • CLI helpers
  • Benchmarking

@grod220 grod220 requested review from joncinque and t-nelson April 12, 2026 19:38
Copy link
Copy Markdown

@t-nelson t-nelson left a comment

Choose a reason for hiding this comment

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

good start!

Comment thread interface/src/message.rs Outdated
Comment thread interface/src/message.rs Outdated
Comment thread interface/src/message.rs Outdated
Comment thread interface/src/message.rs Outdated
Comment thread interface/src/message.rs Outdated
Comment thread interface/src/pda.rs Outdated
Comment thread interface/src/pda.rs Outdated
Comment thread interface/src/pda.rs Outdated
Comment thread interface/src/state.rs Outdated
Comment thread interface/src/state.rs Outdated
#[derive(Clone, Debug, PartialEq, Eq, SchemaRead, SchemaWrite)]
pub struct AuthorityPolicy {
/// Number of member approvals required to authorize execution.
pub threshold: u8,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

multisig could also be a standalone cpi-passthrough program. imagine this glorious future 🤓

sign(sign(sign(timelock(multisig(transfer())))))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This design looks pretty, but composition might be tricky from the client-side. For example, if you need multiple signers, then you need to generate a different message for each nesting level, right?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

in theory we could make a version of this that allows the same payload to be signed by multiple authorities. only one would need to "own" the "nonce account"

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.

New design accepts a signed legacy transaction. This should mitigate nested custom message issues.

Copy link
Copy Markdown

@joncinque joncinque left a comment

Choose a reason for hiding this comment

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

This is definitely on the right track!

We were talking about this offline, and I mentioned that a good use-case to target is minting SPL tokens, where the mint authority is a multisig, which I think is essentially Trent's sign(sign(sign(... example.

As another consideration, if people want to use tools like Ledgers as their offline signer, I don't think the current format will be accepted. We might need these to look like a Solana transaction instead, so that existing tools Just Work ™️

Comment thread interface/src/state.rs Outdated
Comment on lines +12 to +15
/// Counter that prevents reuse of signed messages. A signed message must
/// reference this exact value. Each successful signed action increments
/// this value, invalidating any previously signed messages.
pub nonce: U32,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

We discussed this a bit offline, but wanted to flag here. By using an incremented integer nonce, it's possible to presign a bunch of messages to be executed.

This has some upsides and downsides, but most importantly, it is different from the current nonce behavior, where each nonce is practically random. Not saying it's good or bad, just pointing it out

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 could go either way. But considering this is going for the durable-nonce-replacement angle, updated this to a hash that gets recomputed on successes.

Comment thread interface/src/state.rs Outdated
#[derive(Clone, Debug, PartialEq, Eq, SchemaRead, SchemaWrite)]
pub struct AuthorityPolicy {
/// Number of member approvals required to authorize execution.
pub threshold: u8,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This design looks pretty, but composition might be tricky from the client-side. For example, if you need multiple signers, then you need to generate a different message for each nesting level, right?

@t-nelson
Copy link
Copy Markdown

what's status here? foundation wants an example to float to current durable nonce consumers

@grod220
Copy link
Copy Markdown
Member Author

grod220 commented Apr 21, 2026

@t-nelson sorry for the delay on iterating on this. Talked offline to Jon a bit about a simpler design today. Expect an update soon.

@grod220
Copy link
Copy Markdown
Member Author

grod220 commented Apr 22, 2026

inspired by jon

This next iteration is an alternative design that simplifies the program a good deal. New flow:

  1. Consumer uses a standard signed solana_transaction::Transaction as the Submit payload.
  2. Nonce program validates: message.account_keys[0] == authority stored in PDA state && message.recent_blockhash == nonce in PDA state.
  3. Nonce program executes message.instructions by CPI, promoting NonceAuthorityPda to is_signer=true

It's no longer necessary to define a custom inner message format. This makes it friendlier to work with existing infra that expects signatures on the legacy transaction format.

Also removed embedded deadline/multisig support. Delegates that responsibility to external CPIs.

@grod220 grod220 requested review from joncinque and t-nelson April 22, 2026 01:27
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.

3 participants