Back to Blog

Solana 생태계 보안 (2) — 프로그램 간 호출

March 18, 2022
4 min read

0. 리뷰

1. 개요

이전 블로그에서 프로그램을 배포하고 상호작용하는 방법을 소개했습니다. 클라이언트 측에서 프로그램의 명령을 호출하는 것 외에도, Solana는 크로스 프로그램 호출(cross-program invocation)이라는 메커니즘을 통해 프로그램들이 서로를 호출할 수 있도록 허용합니다. 이번 포스트에서는 크로스 프로그램 호출이 어떻게 사용되는지 설명합니다. 테스트 코드는 여기에 있습니다.

2. 크로스 프로그램 호출

크로스 프로그램 호출은 일반적으로 invoke 함수를 사용하여 구현됩니다. 이 섹션에서는 프로그램 내에서 lamport를 전송하는 예제를 통해 invoke의 사용법을 설명합니다.

Solana에는 새 계정을 생성하고 lamport(SOL)를 전송하는 역할을 하는 System Program이라는 네이티브 프로그램이 있습니다. 이 경우, 프로그램(예: 프로그램 A) 내에서 lamport를 전송하려면, 프로그램 A가 System Programtransfer() 함수를 호출합니다. 아래의 구체적인 예제를 살펴보겠습니다.

2.1 코드 리뷰

3번째 줄부터 10번째 줄까지는 필요한 라이브러리를 가져옵니다. invoke() 함수는 solana_program::program 라이브러리에 있다는 점에 주목하세요.

process_instruction 함수에서, 22번째 줄부터 30번째 줄까지는 클라이언트가 전달한 세 개의 계정을 추출합니다. lamport는 from_account에서 to_account로 전송됩니다. 33번째 줄부터 44번째 줄까지, invoke() 함수가 호출됩니다. 이 함수는 두 개의 인자를 받습니다. 첫 번째는 호출할 대상 명령이고, 두 번째는 계정들의 집합입니다. 이 예제에서 대상 명령은 lamport 전송에 사용되는 transfer()입니다. 계정에는 호출되는 명령에 필요한 모든 계정이 포함됩니다. 이 경우에는 from_accountto_account입니다.

배포된 프로그램은 다음 링크에서 확인할 수 있습니다.

https://explorer.solana.com/address/EPaLuYQ4c11BJAe9ucLbta3xGFb17Zy3cZh3UDPbXRG9?cluster=devnet

2.2 Lamport 전송

앞서 언급했듯이, 배포된 프로그램에 트랜잭션을 전송합니다. 배포된 프로그램은 System Program을 추가로 호출하여 대상 주소로 lamport를 전송합니다. 위의 코드는 클라이언트가 트랜잭션을 구성하는 방법을 보여줍니다. 93번째 줄부터 101번째 줄까지는 배포된 프로그램의 programId를 추출합니다. 103번째 줄부터 109번째 줄까지는 발신자, 수신자, 그리고 System Program의 주소를 생성합니다. 111번째 줄부터 117번째 줄까지는 트랜잭션을 구성합니다. 118번째 줄에서 트랜잭션이 Solana 클러스터로 전송됩니다. 이 트랜잭션의 유일한 서명자는 112번째 줄에서 설정된 발신자입니다. 발신자는 자신의 계정에서 돈을 가져가는 트랜잭션에 서명(즉, 승인)해야 하며, 수신자는 서명할 필요가 없습니다(113번째 줄).

트랜잭션은 다음 링크에서 확인할 수 있습니다.

https://explorer.solana.com/tx/4cxqnff8SakVcE9y5phmh6utcbBUGaLDPvqMRgSy9aPGdNVH6DCsQyJXCCzRGvW5CpygUi5pqhgBQxczXnWCoqPJ?cluster=devnet

3. Invoke 또는 Invoke_signed

Solana에서 프로그램은 런타임에 프로그램 파생 주소(Program Derived Addresses, PDA)라는 계정을 생성할 수 있습니다. 프로그램이 호출하는 대상 명령에 PDA로 서명된 계정이 포함된 경우, invoke() 대신 invoke_signed()를 사용해야 합니다. invoke_signed()의 사용법을 보여주기 위해 다른 예제를 사용하겠습니다. 먼저 컨트랙트 코드를 살펴보겠습니다.

3.1 코드 리뷰

이 예제에서는 프로그램(즉, 프로그램 B) 내부에서 런타임에 PDA를 생성합니다. PDA를 생성하려면 프로그램 B에서 System Program을 호출해야 합니다.

3번째 줄부터 10번째 줄까지는 필요한 라이브러리를 가져옵니다. 이번에는 invoke_signed()가 가져와진다는 점에 주목하세요(6번째 줄).

섹션 2의 예제와 유사하게, System Program과 PDA인 필요한 계정들을 추출합니다(22번째 줄 - 27번째 줄). 그런 다음 find_program_address() 함수를 사용하여 PDA의 주소와 이 PDA를 ed25519 곡선에서 벗어나게 하는 데 사용된 시드를 생성합니다(29번째 줄 - 30번째 줄). 이는 주소에 연결된 개인 키가 없음을 보장하기 위한 것입니다. 이 예제에서 클라이언트와 프로그램은 동일한 시드(즉, 'You pass butter')를 사용하여 PDA를 생성합니다. 따라서 공개 키는 동일해야 하며, 이는 31번째 줄부터 34번째 줄까지 확인됩니다. 그 후, 37번째 줄부터 47번째 줄까지 invoke_signer를 호출하여 allocate() 명령을 실행합니다. invoke() 함수와 달리, PDA를 생성하는 데 사용된 시드와 find_program_address() 함수에서 사용된 bump 시드를 인자로 추가로 받습니다.

계정을 생성/할당하려면 계정 자체가 서명을 제공해야 하며, 이 예제에서는 PDA가 서명자입니다. PDA에 서명하기 위해 invoke_signed가 사용됩니다. 구체적으로, Solana는 시드와 호출자(즉, 프로그램 B)의 programId를 사용하여 PDA를 재생성하고, 이를 주어진 계정(두 번째 인자)과 대조합니다. 일치하면 PDA에 서명됩니다.

배포된 프로그램은 아래에서 확인할 수 있습니다.

https://explorer.solana.com/address/4h3RXGsouTRvUWNG1Dqq2tuuASTXFebHC3wFzv3tSFCK?cluster=devnet

3.2 PDA 생성

두 번째 예제의 클라이언트 스크립트를 살펴보겠습니다.

104번째 줄에서, findProgramAddress() 함수를 통해 PDA와 bump 시드를 추출합니다. 112번째 줄부터 120번째 줄까지, 트랜잭션이 생성되고 전송됩니다. PDA의 속성(isSigner)이 'false'(113번째 줄)인 점에 주목하세요. 클라이언트가 서명할 수 없기 때문입니다. 런타임에 프로그램 B가 PDA에 서명하고 System Program 프로그램의 allocate 명령을 호출합니다.

다음 트랜잭션에서 할당된 PDA를 확인할 수 있습니다.

https://explorer.solana.com/tx/5fzdfzbD4281pY1HJm37f1fxSEMGmWtQmbmFPzEiP9v6hWF8GRRZmfcMDvPJugTrs3npnCsaWUGvw15URyBhx3LS?cluster=devnet

3.3 Invoke()를 사용할 수 있을까요?

invoke를 여기서 사용할 수 없음을 보여주기 위해, 다른 프로그램(즉, 프로그램 C)을 사용하여 allocation 명령을 호출합니다.

우리가 변경한 유일한 사항은 invoke_signed() 함수를 invoke() 함수로 변경한 것입니다. 계정이 생성되지 않는 것을 확인했습니다.

오류는 크로스 프로그램 호출이 성공적으로 실행되려면 서명자가 필요하다는 것을 보여줍니다. 즉, invoke_signed() 함수가 필요합니다.

4. 결론

이 글에서는 invoke() 함수를 통해 크로스 프로그램 호출을 구현하는 방법을 소개했습니다. 또한 다양한 예제를 사용하여 invoke()invoke_signed()의 차이점을 설명했습니다. 계속 지켜봐 주세요. 이 시리즈의 더 많은 글이 게시될 예정입니다.

이 시리즈의 다른 글 읽기:

Sign up for the latest updates
~$410만 손실: Taiko, SecondFi 익스플로잇 | BlockSec 위클리
Security Insights

~$410만 손실: Taiko, SecondFi 익스플로잇 | BlockSec 위클리

이 주간 블록체인 보안 리포트는 2026년 6월 22~28일 발생한 주요 사건 2건을 다루며, 이더리움과 카르다노에서 약 410만 달러의 피해가 확인됐습니다. Taiko 브릿지 공격은 노출된 SGX 서명 키와 디버그 엔클레이브를 거부하지 못한 증명 정책 결함을 이용해 악성 증명자를 등록하고 L2 상태 증명을 위조했습니다. SecondFi 지갑은 Ed25519 논스 도출 시 비밀 입력이 제거되는 결함으로 공개 트랜잭션 데이터만으로 개인 키 복구가 가능했습니다.

~$18M 손실: jaredFromSubway, Aztec 등 | BlockSec 위클리
Security Insights

~$18M 손실: jaredFromSubway, Aztec 등 | BlockSec 위클리

이 주간 블록체인 보안 보고서는 2026년 6월 15일~21일을 다루며, 이더리움과 BNB 체인에서 3건의 주요 사고가 발생해 약 $18.3M의 손실이 발생했습니다. jaredFromSubway 사건은 MEV 봇이 차익거래를 위해 신뢰할 수 없는 제3자 컨트랙트에 자산을 승인한 역방향 승인 공격으로, 가짜 래퍼 토큰과 스왑 풀을 이용해 약 $15M 손실이 발생했습니다. Aztec은 이스케이프 해치 ZK 회로의 제약 누락으로 공격자가 가짜 머클 트리로 온체인 검증을 통과했습니다.

~$598만 달러 손실: Aztec, Raydium 등 | BlockSec 위클리
Security Insights

~$598만 달러 손실: Aztec, Raydium 등 | BlockSec 위클리

이 주간 블록체인 보안 리포트는 2026년 6월 8일~14일을 다루며, 이더리움과 솔라나에서 발생한 4건의 주요 사고를 분석하고 총 손실액은 약 598만 달러입니다. Aztec Connect의 입력 검증 누락으로 롤업 증명 경로와 L1 정산 불일치가 발생했고, Raydium의 레거시 AMM v3 검증 누락으로 LP 토큰 상환 계산이 조작되어 4개 풀이 탈취됐습니다. 두 취약점 모두 수년간 노출된 상태였습니다. 입력 검증 부재, 정수 오버플로우, 거버넌스 탈취 등의 공격 유형을 다룹니다.