chain_signatures/
lib.rs

1#![allow(unexpected_cfgs)]
2use anchor_lang::prelude::*;
3
4declare_id!("4uvZW8K4g4jBg7dzPNbb9XDxJLFBK7V6iC76uofmYvEU");
5
6#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug, PartialEq)]
7#[repr(u8)]
8pub enum SerializationFormat {
9    Borsh = 0,
10    AbiJson = 1,
11}
12
13#[program]
14pub mod chain_signatures_project {
15    use super::*;
16
17    pub fn initialize(ctx: Context<Initialize>, signature_deposit: u64) -> Result<()> {
18        let program_state = &mut ctx.accounts.program_state;
19        program_state.admin = ctx.accounts.admin.key();
20        program_state.signature_deposit = signature_deposit;
21
22        Ok(())
23    }
24
25    pub fn update_deposit(ctx: Context<AdminOnly>, new_deposit: u64) -> Result<()> {
26        let program_state = &mut ctx.accounts.program_state;
27        program_state.signature_deposit = new_deposit;
28
29        emit!(DepositUpdatedEvent {
30            old_deposit: program_state.signature_deposit,
31            new_deposit,
32        });
33
34        Ok(())
35    }
36
37    pub fn withdraw_funds(ctx: Context<WithdrawFunds>, amount: u64) -> Result<()> {
38        let program_state = &ctx.accounts.program_state;
39        let recipient = &ctx.accounts.recipient;
40
41        let program_state_info = program_state.to_account_info();
42        require!(
43            program_state_info.lamports() >= amount,
44            ChainSignaturesError::InsufficientFunds
45        );
46
47        require!(
48            recipient.key() != Pubkey::default(),
49            ChainSignaturesError::InvalidRecipient
50        );
51
52        // Transfer funds from program_state to recipient
53        **program_state_info.try_borrow_mut_lamports()? -= amount;
54        **recipient.try_borrow_mut_lamports()? += amount;
55
56        emit!(FundsWithdrawnEvent {
57            amount,
58            recipient: recipient.key(),
59        });
60
61        Ok(())
62    }
63
64    pub fn sign(
65        ctx: Context<Sign>,
66        payload: [u8; 32],
67        key_version: u32,
68        path: String,
69        algo: String,
70        dest: String,
71        params: String,
72    ) -> Result<()> {
73        let program_state = &ctx.accounts.program_state;
74        let requester = &ctx.accounts.requester;
75        let system_program = &ctx.accounts.system_program;
76
77        let payer = match &ctx.accounts.fee_payer {
78            Some(fee_payer) => fee_payer.to_account_info(),
79            None => requester.to_account_info(),
80        };
81
82        require!(
83            payer.lamports() >= program_state.signature_deposit,
84            ChainSignaturesError::InsufficientDeposit
85        );
86
87        let transfer_instruction = anchor_lang::system_program::Transfer {
88            from: payer,
89            to: program_state.to_account_info(),
90        };
91
92        anchor_lang::system_program::transfer(
93            CpiContext::new(system_program.to_account_info(), transfer_instruction),
94            program_state.signature_deposit,
95        )?;
96
97        // TODO: Change to use emit_cpi!, currently emit! to test the old implementation
98        emit_cpi!(SignatureRequestedEvent {
99            sender: *requester.key,
100            payload,
101            key_version,
102            deposit: program_state.signature_deposit,
103            chain_id: 0,
104            path,
105            algo,
106            dest,
107            params,
108            fee_payer: match &ctx.accounts.fee_payer {
109                Some(payer) => Some(*payer.key),
110                None => None,
111            },
112        });
113
114        Ok(())
115    }
116
117    pub fn sign_respond(
118        ctx: Context<SignRespond>,
119        serialized_transaction: Vec<u8>,
120        slip44_chain_id: u32,
121        key_version: u32,
122        path: String,
123        algo: String,
124        dest: String,
125        params: String,
126        explorer_deserialization_format: SerializationFormat,
127        explorer_deserialization_schema: Vec<u8>,
128        callback_serialization_format: SerializationFormat,
129        callback_serialization_schema: Vec<u8>,
130    ) -> Result<()> {
131        let program_state = &ctx.accounts.program_state;
132        let requester = &ctx.accounts.requester;
133        let system_program = &ctx.accounts.system_program;
134
135        let payer = match &ctx.accounts.fee_payer {
136            Some(fee_payer) => fee_payer.to_account_info(),
137            None => requester.to_account_info(),
138        };
139
140        require!(
141            payer.lamports() >= program_state.signature_deposit,
142            ChainSignaturesError::InsufficientDeposit
143        );
144
145        require!(
146            !serialized_transaction.is_empty(),
147            ChainSignaturesError::InvalidTransaction
148        );
149
150        let transfer_instruction = anchor_lang::system_program::Transfer {
151            from: payer,
152            to: program_state.to_account_info(),
153        };
154
155        anchor_lang::system_program::transfer(
156            CpiContext::new(system_program.to_account_info(), transfer_instruction),
157            program_state.signature_deposit,
158        )?;
159
160        emit_cpi!(SignRespondRequestedEvent {
161            sender: *requester.key,
162            transaction_data: serialized_transaction,
163            slip44_chain_id,
164            key_version,
165            deposit: program_state.signature_deposit,
166            path,
167            algo,
168            dest,
169            params,
170            explorer_deserialization_format: explorer_deserialization_format as u8,
171            explorer_deserialization_schema,
172            callback_serialization_format: callback_serialization_format as u8,
173            callback_serialization_schema
174        });
175
176        Ok(())
177    }
178
179    pub fn respond(
180        ctx: Context<Respond>,
181        request_ids: Vec<[u8; 32]>,
182        signatures: Vec<Signature>,
183    ) -> Result<()> {
184        require!(
185            request_ids.len() == signatures.len(),
186            ChainSignaturesError::InvalidInputLength
187        );
188
189        for i in 0..request_ids.len() {
190            emit!(SignatureRespondedEvent {
191                request_id: request_ids[i],
192                responder: *ctx.accounts.responder.key,
193                signature: signatures[i].clone(),
194            });
195        }
196
197        Ok(())
198    }
199
200    pub fn read_respond(
201        ctx: Context<ReadRespond>,
202        request_id: [u8; 32],
203        serialized_output: Vec<u8>,
204        signature: Signature,
205    ) -> Result<()> {
206        // The signature should be an ECDSA signature over keccak256(request_id || serialized_output)
207
208        // only possible error responses // (this tx could never happen):
209        // - nonce too low
210        // - balance too low
211        // - literal on chain error
212
213        emit!(ReadRespondedEvent {
214            request_id,
215            responder: *ctx.accounts.responder.key,
216            serialized_output,
217            signature,
218        });
219
220        Ok(())
221    }
222}
223
224#[account]
225pub struct ProgramState {
226    pub admin: Pubkey,
227    pub signature_deposit: u64,
228}
229
230#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
231pub struct AffinePoint {
232    pub x: [u8; 32],
233    pub y: [u8; 32],
234}
235
236#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
237pub struct Signature {
238    pub big_r: AffinePoint,
239    pub s: [u8; 32],
240    pub recovery_id: u8,
241}
242
243#[derive(Accounts)]
244pub struct Initialize<'info> {
245    #[account(
246        init,
247        payer = admin,
248        space = 8 + 32 + 8,
249        seeds = [b"program-state"],
250        bump
251    )]
252    pub program_state: Account<'info, ProgramState>,
253    #[account(mut)]
254    pub admin: Signer<'info>,
255    pub system_program: Program<'info, System>,
256}
257
258#[derive(Accounts)]
259pub struct AdminOnly<'info> {
260    #[account(
261        mut,
262        seeds = [b"program-state"],
263        bump,
264        has_one = admin @ ChainSignaturesError::Unauthorized
265    )]
266    pub program_state: Account<'info, ProgramState>,
267    #[account(mut)]
268    pub admin: Signer<'info>,
269    pub system_program: Program<'info, System>,
270}
271
272#[derive(Accounts)]
273pub struct WithdrawFunds<'info> {
274    #[account(
275        mut,
276        seeds = [b"program-state"],
277        bump,
278        has_one = admin @ ChainSignaturesError::Unauthorized
279    )]
280    pub program_state: Account<'info, ProgramState>,
281
282    #[account(mut)]
283    pub admin: Signer<'info>,
284
285    /// CHECK: The safety check is performed in the withdraw_funds
286    /// function by checking it is not the zero address.
287    #[account(mut)]
288    pub recipient: AccountInfo<'info>,
289
290    pub system_program: Program<'info, System>,
291}
292
293#[event_cpi]
294#[derive(Accounts)]
295pub struct Sign<'info> {
296    #[account(mut, seeds = [b"program-state"], bump)]
297    pub program_state: Account<'info, ProgramState>,
298    #[account(mut)]
299    pub requester: Signer<'info>,
300    #[account(mut)]
301    pub fee_payer: Option<Signer<'info>>,
302    pub system_program: Program<'info, System>,
303}
304
305#[event_cpi]
306#[derive(Accounts)]
307pub struct SignRespond<'info> {
308    #[account(mut, seeds = [b"program-state"], bump)]
309    pub program_state: Account<'info, ProgramState>,
310    #[account(mut)]
311    pub requester: Signer<'info>,
312    #[account(mut)]
313    pub fee_payer: Option<Signer<'info>>,
314    pub system_program: Program<'info, System>,
315    pub instructions: Option<AccountInfo<'info>>,
316}
317
318#[derive(Accounts)]
319pub struct Respond<'info> {
320    pub responder: Signer<'info>,
321}
322
323#[derive(Accounts)]
324pub struct ReadRespond<'info> {
325    pub responder: Signer<'info>,
326}
327
328#[event]
329pub struct SignatureRequestedEvent {
330    pub sender: Pubkey,
331    pub payload: [u8; 32],
332    pub key_version: u32,
333    pub deposit: u64,
334    pub chain_id: u64,
335    pub path: String,
336    pub algo: String,
337    pub dest: String,
338    pub params: String,
339    pub fee_payer: Option<Pubkey>,
340}
341
342#[event]
343pub struct SignRespondRequestedEvent {
344    pub sender: Pubkey,
345    pub transaction_data: Vec<u8>,
346    pub slip44_chain_id: u32,
347    pub key_version: u32,
348    pub deposit: u64,
349    pub path: String,
350    pub algo: String,
351    pub dest: String,
352    pub params: String,
353    pub explorer_deserialization_format: u8,
354    pub explorer_deserialization_schema: Vec<u8>,
355    pub callback_serialization_format: u8,
356    pub callback_serialization_schema: Vec<u8>,
357}
358
359#[event]
360pub struct SignatureErrorEvent {
361    pub request_id: [u8; 32],
362    pub responder: Pubkey,
363    pub error: String,
364}
365
366#[event]
367pub struct SignatureRespondedEvent {
368    pub request_id: [u8; 32],
369    pub responder: Pubkey,
370    pub signature: Signature,
371}
372
373#[event]
374pub struct ReadRespondedEvent {
375    pub request_id: [u8; 32],
376    pub responder: Pubkey,
377    pub serialized_output: Vec<u8>,
378    pub signature: Signature,
379}
380
381#[event]
382pub struct DepositUpdatedEvent {
383    pub old_deposit: u64,
384    pub new_deposit: u64,
385}
386
387#[event]
388pub struct FundsWithdrawnEvent {
389    pub amount: u64,
390    pub recipient: Pubkey,
391}
392
393#[error_code]
394pub enum ChainSignaturesError {
395    #[msg("Insufficient deposit amount")]
396    InsufficientDeposit,
397    #[msg("Arrays must have the same length")]
398    InvalidInputLength,
399    #[msg("Unauthorized access")]
400    Unauthorized,
401    #[msg("Insufficient funds for withdrawal")]
402    InsufficientFunds,
403    #[msg("Invalid recipient address")]
404    InvalidRecipient,
405    #[msg("Invalid transaction data")]
406    InvalidTransaction,
407    #[msg("Missing instruction sysvar")]
408    MissingInstructionSysvar,
409}