Skip to content

Base interfaces & data structures#1

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

Base interfaces & data structures#1
grod220 wants to merge 9 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

  • DurableSignerAccount: stores nonce Hash and an authority
  • DurableSignerPda: the PDA the program signs as during CPI
  • Submit instruction data: a serialized transaction payload

The intended flow is:

  1. Derive DurableSignerAccount from the authority and DurableSignerPda from the DurableSignerAccount.
  2. Initialize the account with the authority as a required signer.
  3. Build a wrapped transaction-v1 message whose lifetime specifier is state.nonce.
  4. Have every required wrapped signer authorize the canonical message bytes offline.
  5. A hot wallet submits Submit with that wrapped transaction as instruction data.
  6. Program then:
    a. Verifies the stored authority signed the wrapped message
    b. Checks message.lifetime_specifier == state.nonce
    c. Executes each wrapped instruction by CPI, promoting DurableSignerPda to signer wherever referenced
    d. Advances the nonce as sha256(tag ‖ state_pda ‖ old_nonce ‖ slot_hashes[0] ‖ sha256(message_bytes)).

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
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 thread interface/src/state.rs Outdated
@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
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.

making the payload (at least optionally) a transaction i think will ease some migration concerns. we'll need to think about how to handle some of the current features that are specified in transaction payloads, but require runtime support, which will not be available for these. similarly, how the message header will be handled

Comment thread interface/src/instruction.rs Outdated
Comment thread interface/src/instruction.rs Outdated
Comment thread interface/src/instruction.rs Outdated
Comment thread interface/src/instruction.rs Outdated
@grod220
Copy link
Copy Markdown
Member Author

grod220 commented Apr 30, 2026

@joncinque @t-nelson re-review on the updated API when you have a chance 🙏

Comment thread interface/src/state.rs
pub nonce: Hash,
/// Address allowed to consume this nonce and advance its value. `Submit` verifies that this
/// address signed the wrapped transaction message.
pub authority: Address,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

since we're enforcing a 1:1 mapping of authority to pda, this field is superfluous since it must be passed in the tx, right?

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.

API update here: #1 (comment)

Think parallelism is a legitimate usecase requested from foundation.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

p sure it's the nonce hash that prevents "parallelism", not the authority


we need to tell these people that we're shipping an mvp replacement here, not taking feature requests

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

An authority makes sense if we want to cover the existing use-case, where you have a nonce authority that must sign to advance the nonce, and then a bunch of other signers who are signers on the transaction.

A single authority can manage multiple nonce accounts for different offline signers. As a custodian, I might use the same authority for many different clients. When they want to move funds, they sign with one of my many accounts

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

sure but we already have the authorities and the submit ix accounts list, as presently defined, only supports a single vault account. we can encode its authority in the ordering, like we do with the fee payer on outer tx, then assert it by virtue of the associated pda being passed. this could be trivially extended to multiple vault accounts should we choose with a vault count in the ix data

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

So I'm sure we're on the same page, there's two kinds of accounts: the PDA being promoted, and the account storing the hash and authority. For clarity, let's call the PDA the "vault" since it actually owns assets, and the other account the "durable signer".

In that sense, transactions must have at least one durable signer, and need to support having multiple vaults. Otherwise this program won't support existing use-cases, where multiple keypairs perform offline signing.

Comment thread interface/Cargo.toml Outdated
homepage = {workspace = true}
license = {workspace = true}
edition = {workspace = true}
description = "Interface for the SPL Nonce program"
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'm thinking maybe we should just drop the "nonce" terminology altogether. any "programmatic signer/authority" can be used to decouple asset authority from signatures that must cover the recent blockhash

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.

Interesting. Would spl-program-signer or spl-pda-authority or spl-authority-proxy be more accurate?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

spl-ed25519-signer?

@joncinque join the bikeshed session!

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've been summoned! I really like including ed25519. On the other hand, this is still meant to cover single-use durable signatures, so how about spl-ed25519-durable-signer?

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.

Updated terms!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

there really isn't anything "durable" about this thing tho. that qualifier was added to the original feature as a call out to its extending the ttl of an otherwise short-lived nonce value. here we're simply never signing a nonce with a lifetime at all. this is so much more powerful and shouldn't be burdened by historical artifacts. the technology has advanced to the point as to make the old ways irrelevant. let them die


wherever we land, we should also rename the repo before it gets much content

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'm not sure I understand -- "durable" means "long-lasting", and this program provides a long-lasting way to sign transactions. What are other powers that you want to highlight with the name?

Someone could make a different version of this program that requires signing the same blockhash as the outer transaction, which could be called ed25519-blockhash-signer. In this case, would ed25519-hash-signer be better? Or if you feel really strongly about spl-ed25519-signer, I can get behind it

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 on the right path for me, I think implementation can get started very soon!

Comment thread interface/src/state.rs
pub nonce: Hash,
/// Address allowed to consume this nonce and advance its value. `Submit` verifies that this
/// address signed the wrapped transaction message.
pub authority: Address,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

An authority makes sense if we want to cover the existing use-case, where you have a nonce authority that must sign to advance the nonce, and then a bunch of other signers who are signers on the transaction.

A single authority can manage multiple nonce accounts for different offline signers. As a custodian, I might use the same authority for many different clients. When they want to move funds, they sign with one of my many accounts

Comment thread interface/src/instruction.rs Outdated
Comment on lines +69 to +70
/// Runs only as an inner instruction of a wrapped transaction submitted through `Submit`
/// because nothing outside this program can sign for `NonceAuthorityPda`.
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 is clever! And makes sense, it's a great show of how this would work

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.

Yeah, I found myself thinking through auth for this message and found the duplication annoying. Think this method addresses that!

/// 2. Reads the authority stored in the nonce state account.
/// 3. Checks the passed nonce state account's authority signed the wrapped message.
/// 4. Checks the wrapped message's lifetime / recent blockhash field equals `state.nonce`.
/// 5. Verifies the outer transaction's only top-level instruction is `Submit`.
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 wasn't sure about this restriction, but we can always relax it in the future if there are legitimate use-cases hampered by it

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 think the only real concern is that it makes v1 transactions a prerequisite for consumers that require compute budget instructions

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The program could potentially just skip compute budget instructions, no?

Comment thread interface/src/instruction.rs Outdated
Comment thread interface/src/instruction.rs Outdated
Comment on lines +61 to +62
/// - Remaining: all accounts referenced by the wrapped message, in order, with `is_signer`
/// and `is_writable` flags matching the wrapped message.
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'll see how this shakes out in the implementation, but it isn't possible for non-pda signers to appear in the original message, unless we expect them to sign the inner and outer transactions.

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.

True. I suppose we could do something where a missing inner signature can be satisfied by outer transaction signer. That sort of would be a way to designate an intended hot wallet broadcaster. Hmm, may be best to come back to this during the implementation PR. Going to drop the is_signer language here for now.

@grod220 grod220 requested review from joncinque and t-nelson May 5, 2026 23:05
@joncinque
Copy link
Copy Markdown

This looks good to go from my side to start implementation, but please get @t-nelson 's approval

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