TON 스마트 계약의 3가지 인증 유형
Source: Dev.to
소개
스마트 계약에서는 특정 행위를 특정 행위자에게만 제한해야 할 때가 많습니다.
대표적인 예가 지갑 계약입니다: 메시지 발신자를 인증해 유효한 사용자만 자금을 보낼 수 있도록 해야 합니다.
아래는 TON 스마트 계약에서 메시지를 인증하는 세 가지 일반적인 방법입니다. 이 메커니즘들은 별도 언급이 없는 한 외부 메시지에 사용됩니다.
서명 기반 인증 (외부 메시지)
계약은 데이터에 저장된 공개키를 사용해 메시지의 서명을 검증합니다. 이 방법은 결정론적이며, 계약 저장소가 공개되어 있기 때문에 개인키를 계약 내부에 보관할 수 없다는 점에서 작동합니다.
// wallet3-code.fc (excerpt)
() recv_external(slice in_msg) impure {
var signature = in_msg~load_bits(512);
;; some code
var (stored_seqno, stored_subwallet, public_key) =
(ds~load_uint(32), ds~load_uint(32), ds~load_uint(256));
;;
throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key));
accept_message();
;; other code
}
Source: wallet‑v3 contract on GitHub
왜 작동하는가
- 결정론성 – 암호학적 검증은 무작위 수 생성과 달리 결정론적입니다.
- 공개 저장소 – 공개키를 계약 상태에 안전하게 저장할 수 있습니다.
서명 기반 인증은 외부 메시지에 가장 널리 사용되는 방법입니다.
주소 비교 (내부 메시지)
내부 메시지의 경우 계약은 발신자의 주소를 상태에 저장된 주소와 단순히 비교합니다.
// nft-collection-editable.fc (excerpt)
() recv_internal(cell in_msg_full, slice in_msg_body) impure {
if (in_msg_body.slice_empty?()) {
;; ignore empty messages
return ();
}
slice cs = in_msg_full.begin_parse();
int flags = cs~load_uint(4);
if (flags & 1) {
;; ignore all bounced messages
return ();
}
slice sender_address = cs~load_msg_addr();
int op = in_msg_body~load_uint(32);
int query_id = in_msg_body~load_uint(64);
var (owner_address, next_item_index, content, nft_item_code, royalty_params) = load_data();
;; some code
throw_unless(401, equal_slices(sender_address, owner_address));
;; some code
}
Source: NFT Collection contract on GitHub
계약은 소유자 주소를 저장하고, 발신자가 일치하지 않을 경우 오류를 발생시킵니다. 이 방식은 역할별로 여러 인증 주소를 저장하도록 확장할 수 있습니다.
다중 발신자 인증 (Jetton Wallet 예시)
계약이 임의의 다수 발신자를 인증해야 할 때—예를 들어 Jetton 계약—보다 유연한 접근이 필요합니다. Jetton Wallet은 발신자 지갑이 동일한 Jetton Master에 속하거나, 발신자 주소에서 파생된 결정론적 지갑 주소인지 확인합니다.
// jetton-wallet.fc (excerpt)
() receive_jettons(slice in_msg_body, slice sender_address, int my_ton_balance, int msg_value)
impure inline_ref {
(int status, int balance, slice owner_address, slice jetton_master_address) = load_data();
int query_id = in_msg_body~load_query_id();
int jetton_amount = in_msg_body~load_coins();
slice from_address = in_msg_body~load_msg_addr();
slice response_address = in_msg_body~load_msg_addr();
throw_unless(error::not_valid_wallet,
equal_slices_bits(jetton_master_address, sender_address)
|
equal_slices_bits(
calculate_user_jetton_wallet_address(
from_address,
jetton_master_address,
my_code()
),
sender_address
)
);
;; other code
}
Source: Jetton Wallet contract on GitHub
핵심 포인트
calculate_user_jetton_wallet_address는 발신자 주소, Jetton Master 주소, 그리고 계약 코드(my_code())를 이용해 기대되는 지갑 주소를 파생합니다.my_code()는c7레지스터를 통해 현재 계약 코드를 반환합니다(자세한 내용은 TVM 레지스터 문서를 참고).- 계약은 외부 계약 주소를 계산하기 위해 다른 계약의 코드를 저장할 수도 있습니다.
- 초기화 파라미터가 필요할 경우, 두 계약이 공통으로 알고 있는 파라미터(예: 관리자 주소)가 있다면 메시지보다 자체 저장소에서 읽는 것이 안전합니다.
결론
올바른 인증 전략을 선택하는 것은 TON 스마트 계약 아키텍처를 설계할 때 핵심 요소입니다. 외부 메시지에는 서명 검증을, 간단한 내부 검증에는 주소 비교를, 다수의 발신자를 허용해야 할 경우에는 Jetton Wallet처럼 결정론적 주소 계산을 활용하세요.
행복한 코딩 되세요! 🚀