Approve Messages
Pre-Alpha Disclaimer: This is a pre-alpha release for development and testing only. Signing uses a single mock signer, not real distributed MPC. All 11 protocol operations are implemented (DKG, Sign, Presign, FutureSign, ReEncryptShare, etc.) across all 4 curves and 7 signature schemes, but without real MPC security guarantees. The dWallet keys, trust model, and signing protocol are not final; do not rely on any key material until mainnet. All interfaces, APIs, and data formats are subject to change without notice. The Solana program and all on-chain data will be wiped periodically and everything will be deleted when we transition to Ika Alpha 1. This software is provided “as is” without warranty of any kind; use is entirely at your own risk and dWallet Labs assumes no liability for any damages arising from its use.
create_proposal Instruction
The create_proposal instruction creates a Proposal PDA that references a dWallet and the message to sign.
Instruction Data
| Offset | Field | Size |
|---|---|---|
| 0 | proposal_id | 32 |
| 32 | message_hash | 32 |
| 64 | user_pubkey | 32 |
| 96 | signature_scheme | 1 |
| 97 | quorum | 4 |
| 101 | message_approval_bump | 1 |
| 102 | bump | 1 |
Total: 103 bytes.
Accounts
| # | Account | W | S | Description |
|---|---|---|---|---|
| 0 | proposal | yes | no | Proposal PDA (["proposal", proposal_id]) |
| 1 | dwallet | no | no | dWallet account |
| 2 | creator | no | yes | Proposal creator (signer) |
| 3 | payer | yes | yes | Rent payer |
| 4 | system_program | no | no | System program |
Implementation
#![allow(unused)]
fn main() {
fn create_proposal(
program_id: &Address,
accounts: &[AccountView],
data: &[u8],
) -> ProgramResult {
if data.len() < 103 {
return Err(ProgramError::InvalidInstructionData);
}
let [proposal_account, _dwallet, creator, payer, _system_program, ..] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
if !creator.is_signer() {
return Err(ProgramError::MissingRequiredSignature);
}
// Parse instruction data
let proposal_id: [u8; 32] = data[0..32].try_into().unwrap();
let message_hash: [u8; 32] = data[32..64].try_into().unwrap();
let user_pubkey: [u8; 32] = data[64..96].try_into().unwrap();
let signature_scheme = data[96];
let quorum = u32::from_le_bytes(data[97..101].try_into().unwrap());
let message_approval_bump = data[101];
let bump = data[102];
// Quorum must be at least 1
if quorum == 0 {
return Err(ProgramError::InvalidInstructionData);
}
// Create PDA with seeds ["proposal", proposal_id, bump]
let bump_byte = [bump];
let signer_seeds = [
pinocchio::cpi::Seed::from(b"proposal" as &[u8]),
pinocchio::cpi::Seed::from(proposal_id.as_ref()),
pinocchio::cpi::Seed::from(bump_byte.as_ref()),
];
let signer = Signer::from(&signer_seeds);
CreateAccount {
from: payer,
to: proposal_account,
lamports: minimum_balance(PROPOSAL_LEN),
space: PROPOSAL_LEN as u64,
owner: program_id,
}
.invoke_signed(&[signer])?;
// Write all proposal fields into the account data
let prop_data = unsafe { proposal_account.borrow_unchecked_mut() };
prop_data[0] = PROPOSAL_DISCRIMINATOR;
prop_data[1] = 1; // version
// ... copy proposal_id, dwallet, message_hash, etc. ...
Ok(())
}
}
The key points:
- The proposal stores the
message_hashandmessage_approval_bumpso the CPI call can construct the correct MessageApproval PDA later - The
user_pubkeyandsignature_schemeare passed through toapprove_messagewhen quorum is reached - Quorum of zero is rejected
MessageApproval PDA
When quorum is reached, the program creates a MessageApproval PDA via CPI. This PDA is derived by the dWallet program, not the voting program:
Seeds: ["message_approval", dwallet_pubkey, message_hash]
Program: DWALLET_PROGRAM_ID
Important: The
message_hashyou pass toapprove_messageis the uniqueness key for the MessageApproval PDA and must be computed askeccak256(preimage)regardless of which destination chain the dWallet will eventually sign for. The dwallet program treats it as opaque 32 bytes; using the same hash function (keccak256, with Solana’s cheap on-chain syscall) for every chain keeps the dwallet program chain-agnostic.The digest the dwallet network actually signs is a separate concern, controlled by the
hash_schemefield on the gRPCSignrequest. For Secp256k1 the network applieshash_scheme(message)and signs the resulting 32-byte digest viasign_prehash, so the produced signature is valid on whichever chain expects that exact hash function (Keccak256for EVM,DoubleSHA256for Bitcoin BIP143, etc.). For EVM the on-chain lookup hash and the signing digest happen to coincide; for Bitcoin they differ. The mock supportshash_scheme(it used to ignore it and always hash with SHA-256, which produced signatures that wouldn’t verify on real EVM/Bitcoin nodes).
The dWallet program verifies:
- The caller is a valid program (executable account)
- The CPI authority PDA matches the dWallet’s current authority
- The CPI authority is signed (via
invoke_signed)
Next Step
With proposals created, the next chapter implements vote casting and the quorum-triggered CPI.