Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Message Approval

Pre-Alpha Disclaimer: This is an early pre-alpha release for exploring the SDK and starting development only. There is no real MPC signing — all signatures are generated by a single mock signer, not a distributed network. Do not submit any real transactions for signing or rely on any 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.

Overview

Message approval is the core mechanism for requesting signatures from the Ika network. When you call approve_message, it creates a MessageApproval PDA on-chain. The network detects this account and produces a signature.

MessageApproval Account

MessageApproval PDA:
  Seeds: ["message_approval", dwallet_pubkey, message_hash]
  Program: DWALLET_PROGRAM_ID
  Total: 287 bytes (2 + 285)

The message_hash must be the keccak256 hash of the message you want signed:

#![allow(unused)]
fn main() {
let message_hash = solana_sdk::keccak::hash(message).to_bytes();
}
import { keccak_256 } from "@noble/hashes/sha3.js";
const messageHash = keccak_256(message);

This is consistent across all examples, the mock, and the gRPC service. Using any other hash function will result in a PDA mismatch when the network tries to commit the signature on-chain.

OffsetFieldSizeDescription
0discriminator114
1version11
2dwallet32dWallet account pubkey
34message_hash32Hash of the message to sign
66user_pubkey32User public key
98signature_scheme1Ed25519(0), Secp256k1(1), Secp256r1(2)
99caller_program32Program that created this approval
131cpi_authority32CPI authority PDA that signed
139status1Pending(0) or Signed(1)
140signature_len2Length of the signature (LE u16)
142signature128Signature bytes (padded)

Approval Flow

Direct Approval (User Signer)

When the dWallet’s authority is a user wallet:

User signs approve_message instruction
  → dWallet program verifies user == dwallet.authority
  → Creates MessageApproval PDA (status = Pending)

CPI Approval (Program Signer)

When the dWallet’s authority is a CPI authority PDA:

Your program calls DWalletContext::approve_message
  → invoke_signed with CPI authority seeds
  → dWallet program verifies:
      - caller_program is executable
      - cpi_authority == PDA(["__ika_cpi_authority"], caller_program)
      - dwallet.authority == cpi_authority
  → Creates MessageApproval PDA (status = Pending)

approve_message Instruction

Discriminator: 8

Instruction Data (67 bytes):

OffsetFieldSize
0discriminator1
1bump1
2message_hash32
34user_pubkey32
66signature_scheme1

Accounts (CPI path):

#AccountWSDescription
0message_approvalyesnoMessageApproval PDA (must be empty)
1dwalletnonodWallet account
2caller_programnonoCalling program (executable)
3cpi_authoritynoyesCPI authority PDA (signed via invoke_signed)
4payeryesyesRent payer
5system_programnonoSystem program

Signature Lifecycle

  1. Pending: Your program calls approve_message → MessageApproval created, status = 0, signature_len = 0
  2. gRPC Sign: You send a Sign request via gRPC with ApprovalProof referencing the on-chain approval. The network returns the 64-byte signature directly and commits it on-chain via CommitSignature.
  3. Signed: status = 1, signature bytes written at offset 142, readable by anyone.
Your program calls approve_message (CPI)
  → MessageApproval PDA created (status = Pending)
  → You send gRPC Sign request with ApprovalProof
  → Network signs and returns signature via gRPC
  → Network calls CommitSignature on-chain
  → status = Signed, signature available at offset 142

The signature is available both from the gRPC response and on-chain in the MessageApproval account.

CommitSignature Instruction

Called by the NOA to write the signature into the MessageApproval account.

Discriminator: 43

Instruction Data:

OffsetFieldSize
0discriminator1
1signature_len2
3signature128

Accounts:

#AccountWSDescription
0message_approvalyesnoMessageApproval PDA
1neknonoNetworkEncryptionKey PDA
2noanoyesNOA signer

Reading the Signature

#![allow(unused)]
fn main() {
let data = client.get_account(&message_approval_pda)?.data;

let status = data[139];
if status == 1 {
    let sig_len = u16::from_le_bytes(data[140..142].try_into().unwrap()) as usize;
    let signature = &data[142..142 + sig_len];
    // Use the signature
}
}

Idempotency

The same (dwallet, message_hash) pair always derives the same MessageApproval PDA. Attempting to create a MessageApproval that already exists will fail (the account is non-empty). This prevents duplicate signing requests.