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
약 1,026만 달러 규모: Term Finance 및 MAYAChain 공격 | BlockSec
Security Insights

약 1,026만 달러 규모: Term Finance 및 MAYAChain 공격 | BlockSec

2026년 8월 17-23일, 이더리움·MAYAChain에서 보안 사고 2건으로 총 약 1026만달러 손실이 발생했다. Term Finance(약850만달러)는 거버넌스 설계 결함으로, 참여가 거의 없어 공격자가 0.5 ETH로 과반 투표권을 확보, 지연 후 볼트 6개에서 탈취했다. MAYAChain(약176만달러)은 조작된 입금으로 출금 실패처럼 보이게 해 복구 로직이 저유동성 풀 잔액을 부풀렸고, 유동성 추가·인출로 탈취했다.

하모니 크로스샤드 ONE 민팅 + 약 4,700만 달러 키 손실 | BlockSec
Security Insights

하모니 크로스샤드 ONE 민팅 + 약 4,700만 달러 키 손실 | BlockSec

2026년 8월10-16일, 보안사건 5건, 손실 약4700만달러. 핵심은 Harmony L1의 크로스샤드 영수증 재생 결함: 목적지 샤드가 서명된 소스 헤더 대신 미인증 MerkleProof의 ShardID·BlockNum으로 소비마커를 산출, 이미 지급된 영수증 재생으로 약3.01조 ONE이 위조됐으나 시총 초과로 손실 제외. 실손실은 개인키 유출(고래~2500만, Kite~1400만, Coinsbuy~790만)과 Fox 로직결함(~11.7만).

~$160만 손실: Moke 토큰, LpdFi 익스플로잇 | BlockSec 위클리
Security Insights

~$160만 손실: Moke 토큰, LpdFi 익스플로잇 | BlockSec 위클리

2026년 8월 3~9일, BNB 체인에서 2건의 보안 사고가 발생해 총 약 160만 달러의 손실이 발생했으며, 모두 가격 조작이 원인이었습니다. LpdFi(약 69.7만 달러)는 PancakeSwap 유동성 풀을 주문 평가와 이자 정산에 동시 사용해 공격자가 원금을 부풀리고 과도한 이자를 탈취했습니다. Moke Token(약 90.6만 달러)은 조작 가능한 현물 가격과 중복 LP 배당 회계를 결합해 MOKE를 부풀려 BNB 배당을 반복 수령했습니다.