Back to Blog

Move 개발의 보안 실천 (1): Hello World

Code Auditing
November 7, 2022
9 min read

Aptos 네트워크가 10월 18일 메인넷을 출시함에 따라, Move 프로그래밍 언어와 그 생태계는 지속적으로 영향력을 확대하고 있습니다. 커뮤니티 참여를 위해, 저희는 Move에서의 안전한 개발 방법론을 제공하는 일련의 글을 발표할 예정입니다. 예제, 설명, 그리고 무엇보다도 보안 방법론을 통해 여러분을 Move의 세계로 안내하겠습니다.

TL;DR

이 글에서는 다음 내용을 다룹니다:

  • Aptos 애플리케이션을 생성하고 개발하는 방법;
  • 모듈(즉, Move의 스마트 컨트랙트)이 어떻게 생겼는지;
  • 로컬 네트워크에서 모듈을 컴파일하고 배포하는 방법;
  • 모듈과 상호작용하고 브라우저에서 발생한 이벤트를 확인하는 방법.

0x1. Move와 그 생태계에 대하여

Move는 스마트 컨트랙트 개발을 위해 설계된 Diem(2020년 12월 이전의 이름은 Libra)의 유산입니다. 안전하고, 빠르며, 유연하다고 알려져 있으며, 차세대 언어를 목표로 하고 있습니다. 그러나 AptosSui와 같이 새롭게 등장한 프로젝트들은 원래의 Move를 그대로 채택하지 않고, 자신들의 필요에 맞게 일부 수정을 가했습니다.

별도로 명시되지 않는 한, 저희는 주로 Aptos 컨텍스트에서의 개발 방법론에 집중할 것입니다. 이 아이디어들은 다른 Move 기반 프로젝트에도 일반적으로 적용됩니다. 또한 Move 프로그래밍 언어에 대한 기본적인 이해를 위해 이미 MoveBook을 읽었다고 가정합니다.

0x2. 환경 준비

0x2.1 Aptos 툴체인

Aptos는 이미 CLI에 Move를 통합했습니다. 따라서 해당 툴체인을 설치하려면 CLI 설치 가이드를 따르는 것을 권장합니다. 설치가 완료되면 터미널에 aptos를 입력하면 다음과 같은 출력을 볼 수 있습니다:

$ aptos
aptos 1.0.1
Aptos Labs <[email protected]>
Command Line Interface (CLI) for developing and interacting with the Aptos blockchain
USAGE:
    aptos <SUBCOMMAND>
OPTIONS:
    -h, --help       Print help information
    -V, --version    Print version information
SUBCOMMANDS:
    account       Tool for interacting with accounts
    config        Tool for interacting with configuration of the Aptos CLI tool
    genesis       Tool for setting up an Aptos chain Genesis transaction
    governance    Tool for on-chain governance
    help          Print this message or the help of the given subcommand(s)
    info          Show build information about the CLI
    init          Tool to initialize current directory for the aptos tool
    key           Tool for generating, inspecting, and interacting with keys
    move          Tool for Move related operations
    node          Tool for operations related to nodes
    stake         Tool for manipulating stake and stake pools

0x2.2 로컬 테스트넷 및 계정

Aptos는 개발 및 배포를 위해 여러 네트워크(메인넷, 테스트넷, 데브넷, 로컬 테스트넷)를 제공합니다. 이 글에서는 로컬 테스트넷을 사용할 것입니다.

로컬 테스트넷을 실행하려면 다음 명령어를 입력하세요:

$ aptos node run-local-testnet --with-faucet --force-restart

다음과 같은 출력이 표시될 것입니다:

......
Aptos is running, press ctrl-c to exit
Faucet is running.  Faucet endpoint: 0.0.0.0:8081

이제 다음 명령어를 입력하여 계정을 생성할 수 있습니다(이전 터미널을 포그라운드로 유지하려면 다른 터미널 창에서 입력하세요):

$ aptos init

이 명령어는 몇 가지 속성 지정이 필요한 대화형 셸을 실행합니다. 여기서는 local 네트워크를 선택합니다. 그 후, 현재 디렉토리에 기본 계정 구성이 담긴 .aptos 폴더가 생성됩니다. 파일을 확인하고 account 주소(예: c0d8xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx14bd)를 복사하세요.

팁#1: 계정 구성 파일을 안전하게 보관하세요! 개인 키가 파일 안에 저장되어 있습니다!

0x3. 첫 번째 Hello World 프로그램

Hello World 프로그램은 코드 작성 학습의 첫 번째 단계로 항상 사용됩니다. 저희는 이 관례를 따라 Move 애플리케이션 개발 방법을 시연하고자 합니다.

0x3.1 패키지 준비

원하는 디렉토리를 선택하고 다음 명령어를 입력하세요:

$ aptos move init --framework-local-dir "../../aptos-core/aptos-move/framework/aptos-framework" --name hello

이 명령어는 지정된 위치(즉, 개발 워크스페이스)에 새로운 Move 패키지를 생성합니다. Aptos 프레임워크의 일부 라이브러리 함수가 필요하므로 Aptos 프레임워크의 로컬 디렉토리를 지정합니다(다른 위치의 경우 상대 경로를 변경해야 할 수 있습니다). 또는 --framework-git-rev를 사용하여 Aptos 프레임워크의 git 리비전이나 브랜치를 지정할 수도 있습니다.

이제 디렉토리에서 Move.toml 파일을 확인할 수 있습니다. 이 파일은 일부 의존성 구성을 나열할 뿐만 아니라 패키지 이름과 버전도 설명합니다. 또한 Move 소스 코드를 저장하는 데 사용되는 sources라는 폴더도 있습니다. sources 디렉토리에 .move 파일을 생성하면 워크스페이스를 사용할 준비가 됩니다.

[package]
name = 'hello'
version = '1.0.0'
[dependencies.AptosFramework]
local = '../../aptos-core/aptos-move/framework/aptos-framework'
[addresses]
BlockSec="c0d8xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx14bd"

Move.toml의 마지막 줄에서 (앞서 생성한) 계정 주소를 BlockSec과 연결합니다. 이는 이 패키지의 모듈을 배포(즉, 스마트 컨트랙트 배포)하는 데 사용됩니다(나중에 설명). 위의 주소는 단순한 자리 표시자이므로, 반드시 자신의 계정 주소로 교체해야 합니다.

0x3.2 모듈 준비

아래는 move 파일(예: hello.move)에 직접 넣을 수 있는 간단하지만 완전한 샘플 모듈입니다. 다음에서는 Move와 다른 프로그래밍 언어의 차이점을 설명하기 위해 코드를 살펴보겠습니다. 이미 잘 알고 있다면 이 섹션을 건너뛰어도 됩니다.

module BlockSec::hello{
    use aptos_framework::account;
    use aptos_framework::event;
    use std::signer;
    use std::string::{String, utf8};
    struct SecEventStore has key{
        event_handle: event::EventHandle<String>,
    }
    public entry fun say_hello_script(account: &signer) acquires SecEventStore{
        say_hello(account);
    }
    public fun say_hello(account: &signer) acquires SecEventStore{
        let address_ = signer::address_of(account);
        if(!exists<SecEventStore>(address_)){
            move_to(account, SecEventStore{event_handle: account::new_event_handle(account)});
        };
        event::emit_event<String>(&mut borrow_global_mut<SecEventStore>(address_).event_handle, utf8(b"Hello World!"));
    }
}

여기서 BlockSec 주소에 배포될 hello라는 모듈을 정의합니다. 이 모듈의 엔트리 함수는 say_hello_script로, say_function 함수를 호출합니다. 이 모듈이 say_hello 함수에서 "Hello World!" 메시지와 함께 이벤트를 발생시키는 데 사용된다는 것을 쉽게 알 수 있습니다. 그러나 명확히 설명이 필요한 세부 사항들이 여전히 존재합니다.

구체적으로, aptos_framework 네임스페이스의 event::emit_event 함수를 사용하기 위해서는 emit_event 함수의 선언이 다음과 같으므로 event::EventHandle 구조체가 필요합니다:

/// Emit an event with payload `msg` by using `handle_ref`'s key and counter.
public fun emit_event<T: drop + store>(handle_ref: &mut EventHandle<T>, msg: T)
struct EventHandle<phantom T: drop + store> has store {
    /// Total number of events emitted to this event stream.
    counter: u64,
    /// A globally unique ID for this event stream.
    guid: GUID,
}

EventHandlekey 능력(ability)을 가지지 않으므로, 다른 구조체에 저장해야 합니다. 따라서 EventHandle을 저장하기 위해 SecEventStore라는 구조체를 정의합니다:

struct SecEventStore has key{
    event_handle: event::EventHandle<String>,
}

팁#2: Move에서 구조체의 능력(abilities)을 반드시 고려해야 합니다. _****_key_****_ 능력만이 _****_move_to_****_될 수 있다는 것을 기억하세요. 모듈이 _****_store_****_ 능력만 가진 구조체를 반환하고 이를 전역 스토리지에 보존하려면, 래퍼로서 새 구조체를 정의하는 것을 고려하세요.

단순화를 위해 메시지를 나타내는 데 String을 사용합니다. 따라서 say_hello 함수의 최종 단계는 다음과 같습니다:

event::emit_event<String>(&mut borrow_global_mut<SecEventStore>(address_).event_handle, utf8(b"Hello World!"));

팁#3: Move는 기본적으로 _****_String_****_ 타입을 지원하지 않습니다. 표준 라이브러리는 바이트 벡터를 _****_String_****_으로 변환하는 기능을 제공합니다. 자세한 내용은 GitHub 저장소의 소스 코드를 확인하세요.

모든 새로운 사용자가 SecEventStore에 등록해야 하도록 하기 위해, exists 함수를 통해 사용자 계정에 구조체가 존재하는지 먼저 확인해야 합니다. 사용자가 리소스를 가지고 있지 않다면, 함수는 하나를 생성하여 사용자 계정으로 이동시킵니다.

if(!exists<SecEventStore>(address_)){
    move_to(account, SecEventStore{event_handle: account::new_event_handle(account)});
};

0x3.3 모듈 컴파일 및 배포

모듈이 준비되면 다음 명령어로 컴파일할 수 있습니다:

$ aptos move compile
Compiling, may take a little while to download git dependencies...
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING hello
{
  "Result": [
    "c0d8xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx14bd::hello"
  ]
}

필요하다면 테스트도 할 수 있습니다:

$ aptos move test
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING hello
Running Move unit tests
Test result: OK. Total tests: 0; passed: 0; failed: 0
{
  "Result": "Success"
}

이제 Aptos 네트워크에 모듈을 배포할 수 있습니다. 사용자는 자신이 제어하는 계정에만 모듈을 배포할 수 있으므로, 계정을 BlockSec과 연결한 것을 기억하세요. 또한 파우셋이 가스비 지불을 위해 이미 계정에 일부 APT를 지급했습니다. 남은 것은 Move 패키지의 모듈을 Aptos 네트워크에 배포하는 것입니다:

$ aptos move publish --package-dir ./ --profile default
Compiling, may take a little while to download git dependencies...
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING hello
package size 1271 bytes
Do you want to submit a transaction for a range of [672200 - 1008300] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
{
  "Result":
  ...
}

이것도 대화형 셸이며, Result의 내용은 생략되었습니다.

0x3.4 모듈과 상호작용

모듈이 이벤트를 발생시키도록 하려면 다음 명령어를 입력하세요:

$ aptos move run --function-id default::hello::say_hello_script
Do you want to submit a transaction for a range of [34500 - 51700] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
{
  "Result": {
    "transaction_hash": "0x9af16532de5e79803c823fe28e3251703927d93809274b76972d8e83c6fcd433",
    ...
    }
}

엔트리 함수로서 say_hello_script 함수를 직접 호출할 수 있습니다. 스크립트를 작성하여 스크립트에서 say_hello 함수를 호출할 수도 있습니다.

팁#4: 더 나은 상호작용 경험을 위해, 일부 엔트리 함수를 정의하는 것이 좋은 방법입니다. 사용자가 프로젝트와 상호작용할 수 있도록 일부 스크립트를 개발할 수도 있습니다.

전통적인 Web2 Hello World 프로그램과 달리, 발생한 이벤트는 셸에 표시되지 않습니다. Aptos 탐색기를 확인하고 local 네트워크를 선택하세요.

그런 다음 셸에서 transaction_hash 값을 복사하여 검색창에 붙여넣으면 트랜잭션 세부 정보를 볼 수 있습니다.

마지막으로 Events를 클릭하면, Aptos 네트워크가 여러분에게 인사를 건넬 것입니다.

0x4. 다음 단계는?

Hello World 프로그램은 Move 애플리케이션 개발을 위한 작은 첫걸음에 불과합니다. 앞으로 연재될 글에서는 Aptos에서 안전하고 보안성 높은 Move 애플리케이션을 개발하는 방법에 대해 더 많이 소개할 예정입니다. 많은 기대 부탁드립니다!

BlockSec 소개

BlockSec은 전 세계적으로 著名한 보안 전문가들이 2021년에 설립한 선구적인 블록체인 보안 회사입니다. 이 회사는 대중적 채택을 촉진하기 위해 신흥 Web3 세계의 보안성과 사용성을 향상시키는 데 전념하고 있습니다. 이를 위해 BlockSec은 스마트 컨트랙트 및 EVM 체인 보안 감사 서비스, 보안 개발 및 능동적 위협 차단을 위한 Phalcon 플랫폼, 자금 추적 및 조사를 위한 MetaSleuth 플랫폼, 그리고 Web3 빌더들이 크립토 세계를 효율적으로 탐색할 수 있도록 돕는 MetaSuites 확장 프로그램을 제공합니다.

현재까지 MetaMask, Uniswap Foundation, Compound, Forta, PancakeSwap 등 300개 이상의 저명한 고객사를 대상으로 서비스를 제공했으며, Matrix Partners, Vitalbridge Capital, Fenbushi Capital 등 저명한 투자사로부터 두 차례의 투자 라운드를 통해 수천만 달러를 유치했습니다.

공식 웹사이트: https://blocksec.com/

공식 트위터 계정: https://twitter.com/BlockSecTeam

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 회로의 제약 누락으로 공격자가 가짜 머클 트리로 온체인 검증을 통과했습니다.

Web3 컴패니언: 오픈소스 보안 에이전틱 지갑

Web3 컴패니언: 오픈소스 보안 에이전틱 지갑

BlockSec가 Web3 Companion을 오픈소스로 공개했습니다. 이 보안 중심의 에이전트 지갑은 자체 AI 에이전트를 신뢰하지 않는 방식으로 설계되었으며, 키 격리, 강력한 정책, Passkey를 활용해 온체인 자산을 보호합니다.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit