Back to Blog

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

2022년 3월 18일
4 min read

0. 리뷰

1. 개요

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

2. 크로스 프로그램 호출

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

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

2.1 코드 리뷰

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

process_instruction 함수에서, 22번째 줄부터 30번째 줄까지는 클라이언트가 전달한 세 개의 계정을 추출합니다. lamport는 from_account에서 to_account로 전송됩니다. 33번째 줄부터 44번째 줄까지, invoke() 함수가 호출됩니다. 이 함수는 두 개의 인자를 받습니다. 첫 번째는 호출할 대상 명령이고, 두 번째는 계정들의 집합입니다. 이 예제에서 대상 명령은 lamport 전송에 사용되는 transfer()입니다. 계정에는 호출되는 명령에 필요한 모든 계정이 포함됩니다. 이 경우에는 from_account와 to_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
약 4억 1,800만 달러 손실: Bitget, NEAR Intents 해킹 사건 | BlockSec Weekly
Security Insights

약 4억 1,800만 달러 손실: Bitget, NEAR Intents 해킹 사건 | BlockSec Weekly

이번 격주 보안 리포트는 2026년 9월 21일부터 10월 4일까지 발생한 8건의 사고를 다루며, 이더리움, BNB 체인, 솔라나, 비트코인, 트론, XRP 레저, 지캐시, 아발란체, 니어 등 네트워크에서 약 4억 1,840만 달러의 손실이 발생했습니다. Bitget의 3억 8,750만 달러 규모 제3자 보안 제품 취약점과 NEAR Intents의 387만 달러 환불 검증 결함 및 상태 롤백 누락 문제를 상세히 분석합니다.

Bitget의 3억 8,750만 달러 오프체인 침해 사고: 키와 컨트랙트를 넘어서
Security Insights

Bitget의 3억 8,750만 달러 오프체인 침해 사고: 키와 컨트랙트를 넘어서

2026.9.24, 공격자가 제3자 보안 제품 취약점을 악용해 내부 자격증명을 탈취하고 출금 명령을 위조했습니다. 이더리움·EVM·XRP Ledger·Zcash·TRON상 Bitget 지갑에서 약 3.875억 달러가 인출됐으나 개인키·콜드월렛은 무사했습니다. 이 글은 사건 경위·자금 흐름과 자산 전환·복구 대응을 분석하고, 기관용 심층 방어 체계를 제안하며 승인된 블록체인 침투 테스트의 검증 역할을 설명합니다.

약 1,130만 달러 손실: Multicall Router, Nostra | BlockSec Weekly
Security Insights

약 1,130만 달러 손실: Multicall Router, Nostra | BlockSec Weekly

2026/09/14~09/20, 이더리움·Starknet에서 약 1,130만 달러 손실 발생. 멀티콜 라우터가 자신 주소를 대상으로 받아들여 Safe 지갑 권한이 도용되어 약 2,900 aEthrsETH가 공격자 Uniswap v4 풀로 유출. Starknet Nostra는 오라클 최소 소스 1개 요구로 조작된 가격이 반영돼 NSTR이 약 $49.52로 평가, 약 350만 달러 대출 발생.