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
~$135만 달러 손실: BarnBridge, DeFiTuna | BlockSec 주간 보고
Security Insights

~$135만 달러 손실: BarnBridge, DeFiTuna | BlockSec 주간 보고

이 주간 보고서는 2026년 7월 13일부터 19일까지 발생한 2건의 보안 사고를 다루며, 이더리움과 솔라나에서 총 약 135만 달러의 손실이 발생했습니다. 솔라나 대출 프로토콜 DeFiTuna은 포지션 가치가 0일 때 부채와 무관하게 건전한 것으로 처리되는 결함으로 약 57만 달러를 잃었습니다. BarnBridge는 더 이상 사용되지 않지만 활성 상태인 이더리움 거버넌스 시스템을 악용한 공격자가 악의적인 제안을 통과시켜 약 77만 6천 달러의 USDC를 탈취당했습니다.

~$3600만 달러 손실: Summer.fi, Bonzo Lend 등 | BlockSec 위클리
Security Insights

~$3600만 달러 손실: Summer.fi, Bonzo Lend 등 | BlockSec 위클리

이 주간 보안 보고서는 2026년 7월 6일부터 12일까지 이더리움, 솔라나, 헤데라에서 발생한 주목할 만한 3건의 사고를 다루며, 총 손실액은 약 3,629만 달러입니다. Summer.fi 사고($604만)는 완전히 종료되지 않은 볼트 구성요소가 총 자산에 포함되어 주가 조작에 악용된 사례이며, Bonzo Lend($905만)는 Supra 오라클의 BLS 서명 검증 우회로 해킹되었습니다. 두 사고 모두 배경, 취약점 세부 정보, 단계별 공격 분석 및 보안 권고사항을 포함합니다.

~$800K 손실: Hinkal 이중 지불 사건 | BlockSec 위클리
Security Insights

~$800K 손실: Hinkal 이중 지불 사건 | BlockSec 위클리

이 주간 보안 보고서는 2026년 6월 29일~7월 5일 이더리움에서 발생한 약 80만 달러 손실의 주요 사고 1건을 다룹니다. Hinkal 실드 풀 프로토콜이 이중 지출 공격으로 탈취되었으며, 레거시 노트 형식의 결함을 악용해 단일 예치금에서 다수의 널리파이어를 도출한 것으로 추정됩니다. 회로 수준 취약점, 공격 흐름, 널리파이어 기반 프라이버시 프로토콜의 시사점을 분석합니다.