트랜잭션 서명 및 전송
Sui의 트랜잭션 authentication은 사용자가 제어하는 cryptographic key, 온체인에서 account를 식별하는 address, 소유권을 증명하는 signature라는 세 가지 핵심 개념을 포함한다. Sui는 널리 쓰이는 wallet specification과 여러 signature scheme을 사용해 이 개념들을 구현한다.
key와 address
Sui는 사용자의 key 관리를 돕기 위해 cryptocurrency 업계에서 널리 받아들여진 wallet specification인 BIP-32와 그 변형인 SLIP-0010, BIP-44, BIP-39를 따른다. Sui는 signed transaction에 대해 pure Ed25519, ECDSA Secp256k1, ECDSA Secp256r1, multisig를 지원한다.
key derivation scheme
Ed25519(EdDSA) signing scheme을 지원하는 wallet을 관리할 때 Sui는 SLIP-0010을 따른다. SLIP-0010은 wallet이 hardened key path를 사용해 parent private key에서 child private key를 항상 derive하도록 강제한다.
Sui는 ECDSA Secp256k1과 ECDSA Secp256r1 signing scheme을 지원하는 wallet을 관리할 때 BIP-32를 따른다.
BIP-32는 key 집합을 논리적으로 연결하기 위한 hierarchical deterministic wallet 구조를 정의한다. 이렇게 key를 그룹화하면 많은 private key를 추적해야 하는 부담을 줄일 수 있다. 또한 custodian은 하나의 control source 아래에서 사용자 account마다 별도의 managed address를 발급할 수 있다. BIP-32를 사용하면 private key derivation과 public key derivation이 분리되어 watch-only wallet use case가 가능해진다. 이 경우 public key 체인과 그 address는 derive할 수 있지만, private key는 signing을 위해 offline으로 유지할 수 있다.
key derivation path
BIP-44는 derivation path의 5개 level과 그 정확한 의미를 추가로 정의한다:
이 구조에서 slash는 hierarchy의 level을 나타낸다.
purpose level은 서로 다른 signing scheme을 구분한다:
-
44: Ed25519 -
54: ECDSA Secp256k1 -
74: Secp256r1
예를 들어 BIP-49와 BIP-84는 Bitcoin의 script type을 식별하는 데 사용된다. Sui는 ECDSA Secp256k1을 나타내기 위해 54를 선택했다. 54 아래에는 기존 BIP가 없어서 Bitcoin standard와 혼동될 가능성을 피할 수 있기 때문이다.
coin_type 값은 다른 cryptocurrency와 함께 repository에서 관리된다. 두 signature scheme 모두 Sui의 등록된 coin_type인 784를 사용한다.
account level은 user account를 논리적으로 분리하고 특정 account category를 만드는 데 사용된다.
Account-based currency는 처음 3개 level만 정의하는 반면, UTXO-based currency는 change와 address level 정의를 추가한다. Sui의 객체-oriented data model은 UTXO 기반도 account 기반도 아니며 두 방식을 결합하므로, 최대한의 호환성을 위해 5개 level을 모두 사용한다.
| Scheme | Path | 설명 |
|---|---|---|
| Ed25519 | m/44'/784'/{account}'/{change}'/{address}' | derivation path의 각 level이 hardened이다. |
| ECDSA Secp256k1 | m/54'/784'/{account}'/{change}/{address} | 처음 3개 level이 hardened이다. |
| ECDSA Secp256r1 | m/74'/784'/{account}'/{change}/{address} | 처음 3개 level이 hardened이다. |
mnemonic 지원
Sui가 seed에서 master key를 derive하는 deterministic 방식을 정의한 뒤, BIP-39는 mnemonics를 사용해 seed를 사람이 읽고 기억하기 쉽게 만든다. Sui는 BIP-39 word list에서 checksum이 올바른 12, 15, 18, 21, 24개 단어를 허용하며, 이는 각각 128, 160, 192, 224, 256 bit entropy에 대응한다.
주소 형식
32-byte Sui address를 derive하기 위해 Sui는 signature scheme flag 1-byte와 public key byte를 연결한 값을 BLAKE2b(256-bit output) hashing function으로 hash한다. Sui address는 현재 pure Ed25519, Secp256k1, Secp256r1, multisig를 지원하며, 각각 대응하는 flag byte는 0x00, 0x01, 0x02, 0x03이다.
서명
사용자가 signed transaction을 제출하면 serialized signature와 serialized transaction data가 제출된다. serialized transaction data는 TransactionData struct를 Binary Canonical Serialization으로 serialize한 byte이고, serialized signature는 flag || sig || pk byte를 연결한 값으로 정의된다.
서명 구조
Sui signature는 세 component를 연결한 값이다:
-
flag: signature scheme에 대응하는 1-byte 표현이다. -
sig: signature의 compressed byte 표현이다. DER encoding이 아니다. -
pk: signature에 대응하는 public key의 byte 표현이다.
지원되는 signature scheme
다음 표는 각 signing scheme과 대응하는 flag, signature format, public key format을 나열한다:
| Scheme | Flag | Signature format | Public key format |
|---|---|---|---|
| Pure Ed25519 | 0x00 | Compressed, 64 bytes | Compressed, 32 bytes |
| ECDSA Secp256k1 | 0x01 | Non-recoverable, compressed, 64 bytes | Compressed, 33 bytes |
| ECDSA Secp256r1 | 0x02 | Non-recoverable, compressed, 64 bytes | Compressed, 33 bytes |
| multisig | 0x03 | BCS serialized all signatures, size varies | BCS serialized all participating public keys, size varies |
| zkLogin | 0x05 | BCS serialized zkLogin inputs, max epoch and ephemeral signature, size varies | iss length, iss byte, 32-byte로 padding된 address seed를 연결한 값, size varies |
| passkey | 0x06 | BCS serialized passkey inputs(authenticatorData, clientDataJson, userSignature), size varies | Compressed, 33 bytes |
Signature requirements
signature는 transaction data의 intent message hash에 commit해야 한다. 이 hash는 BCS serialized transaction data 앞에 3-byte intent를 붙여 구성할 수 있다. intent가 무엇인지와 intent message를 구성하는 방법을 자세히 알아보려면 Intent Signing을 참조한다.
signing API를 호출할 때는 먼저 트랜잭션 data의 intent message를 Blake2b로 hash하여 32 byte로 만들어야 한다. 이 external hashing은 signing API 내부에서 수행되는 hashing과 별개이다. 기존 standard와 hardware secure 모듈(HSM)과 호환되도록 signing algorithm은 내부적으로 추가 hashing을 수행한다. ECDSA Secp256k1과 Secp256r1의 경우 internal hash 함수으로 SHA-2 SHA256을 사용해야 한다. Pure Ed25519의 경우 SHA-512를 사용해야 한다.
ECDSA signature requirement
허용되는 ECDSA Secp256k1 및 Secp256r1 signature는 다음 조건을 따라야 한다:
-
ECDSA가 사용하는 internal hash는 transaction data의 SHA256 SHA-2 hash여야 한다. Sui가 SHA256을 사용하는 이유는 Apple, HSM에서 지원되고 Bitcoin에서도 널리 채택되었기 때문이다.
-
signature는 첫 32 byte가
[r, s], 두 번째 32 byte가r인s형식의 64 byte 길이여야 한다. -
r값은0x1부터0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364140까지의 범위(포함)에 있어야 한다. -
s값은 curve order의 lower half에 있어야 한다. signature가 너무 높으면 해당 curve order에 대해s를 사용해 BIP-0062에 따라 더 낮은order - s로 변환한다. Secp256k1의 curve order는0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141이다. Secp256r1의 curve order는 Standards for Efficient Cryptography에 정의된0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551이다. -
이상적으로 signature는 RFC6979에 따라 deterministic nonce로 생성되어야 한다.
Ed25519 signature requirement
허용되는 pure Ed25519 signature는 다음 조건을 따라야 한다:
특수 signature scheme
advanced signature scheme에 대한 자세한 내용은 다음을 참조한다:
-
zkLogin: zero-knowledge login signature의 자세한 내용은 zkLogin을 참조한다.
-
Passkey: passkey implementation의 자세한 내용은 SIP-8을 참조한다.
-
Multisig: multi-signature transaction의 자세한 내용은 Multisig를 참조한다.
-
Offline Signing: offline transaction signing의 구체적인 예시는 Offline Signing을 참조한다.
Authority 서명
Sui의 validator collection은 서로 다른 목적을 위해 3개의 distinct key pair를 보유한다.
Protocol key pair
protocol key pair는 user-signed 트랜잭션이 검증되면 해당 트랜잭션에 authority signature를 제공한다. user 트랜잭션에 signature를 제공하는 authority의 stake가 필요한 2/3 threshold를 넘으면 Sui는 트랜잭션을 실행한다. Sui는 주어진 authority 수에 대해 aggregated signature를 빠르게 검증할 수 있도록 BLS12381 scheme을 사용한다. 특히 Sui는 각 public key가 96 byte이고 signature가 48 byte인 minSig BLS mode를 사용한다. 후자가 중요한 이유는 일반적으로 validator가 각 epoch 시작 시 key를 한 번 등록한 뒤 트랜잭션에 계속 서명하기 때문에, Sui가 최소 signature size에 맞춰 최적화하기 때문이다.
BLS scheme과 마찬가지로 독립 signature를 aggregate해 단일 BLS signature payload로 만들 수 있다. Sui는 aggregate signature와 함께 어떤 validator가 서명했는지를 나타내는 bitmap도 제공한다. 이는 authority signature size를 (2f + 1) × BLS_sig size에서 하나의 BLS_sig payload로 줄인다. 이 방식은 validator set size와 독립적으로 트랜잭션 certificate를 압축하여 network cost를 크게 줄인다.
BLS12381 aggregated signature에서 발생할 수 있는 rogue key attack에 대응하기 위해 authority registration 중 secret key에 대한 proof of knowledge(KOSK)를 사용한다. authority가 validator set에 추가되도록 요청하면 proof of possession이 제출되고 검증된다. proof of possession을 만드는 방법은 Intent Signing을 참조한다. 대부분의 standard와 달리 Sui proof of knowledge scheme은 address에도 commit하므로, 악의적인 다른 validator의 validator BLS key를 재사용하는 공격에 대한 추가 보호를 제공한다.
Account key pair
authority가 staking reward payment를 받는 데 사용하는 account는 account key pair로 보호된다. Sui는 signing scheme으로 pure Ed25519를 사용한다.
Network key pair
private key는 consensus networking에 필요한 TLS handshake를 수행하는 데 사용된다. public key는 validator identity에 사용된다. scheme으로는 pure Ed25519가 사용된다.
더 많은 authority key tooling은 Validator Tool을 참조한다.
예시
Sui CLI tool과 Sui SDKs는 여러 signing scheme으로 transaction에 서명할 수 있는 유연한 interface를 제공한다.
- CLI
- TypeScript
$ sui keytool import "TEST_MNEMONIC" ed25519 "m/44'/784'/0'/0'/0'"
$ sui client new-address ed25519 "m/44'/784'/0'/0'/0'"
const keypair = Ed25519Keypair.deriveKeypair(TEST_MNEMONIC, `m/44'/784'/0'/0'/0'`);
const address = keypair.getPublicKey().toSuiAddress();
더 많은 test vector는 pure Ed25519 또는 ECDSA Secp256k1을 참조한다.
워크플로
다음 상위 수준 과정은 온체인 트랜잭션을 구성하고 서명하고 실행하는 전체 흐름을 설명한다:
-
여러 command를 연결한
Transaction을 만들어 transaction data를 구성한다. 자세한 내용은 Programmable Transaction Block 구성하기를 참조한다. -
SDK의 내장 gas estimation과 코인 selection이 가스 코인을 선택한다.
-
signature를 생성하도록 transaction에 서명한다.
-
Transaction과 그 signature를 제출해 온체인 실행을 요청한다.
Address Balances 기능을 사용하는 경우, 이 기능이 가스 코인을 선택하거나 병합할 필요를 완전히 제거한다.
특정 가스 코인을 사용하고 싶다면 먼저 gas 지불에 사용할 가스 코인 객체 ID를 찾고 이를 PTB에 명시적으로 사용한다. 가스 코인 객체가 없다면 splitCoin 트랜잭션을 사용해 가스 코인 객체를 만든다. split coin 트랜잭션은 PTB의 첫 번째 트랜잭션 호출이어야 한다.
같은 address의 여러 트랜잭션 처리
Sui SDK는 같은 address에서 여러 트랜잭션을 처리하는 데 도움이 되는 트랜잭션 executor를 제공한다.
SerialTransactionExecutor
트랜잭션을 하나씩 순차적으로 처리할 때는 SerialTransactionExecutor를 사용한다.
이 executor는 sender의 모든 코인을 가져와 하나의 코인으로 합친 뒤 모든 트랜잭션에 사용한다.
SerialTransactionExecutor를 사용하면 PTB 전반의 객체 입력 versioning을 처리하여 SequenceNumber error를 방지할 수 있다.
ParallelTransactionExecutor
같은 sender의 트랜잭션을 동시에 처리하고 싶다면 ParallelTransactionExecutor를 사용한다.
이 클래스는 병렬 트랜잭션이 해당 코인에 대해 equivocate하지 않도록 관리하는 가스 코인 pool을 만든다.
이 클래스는 트랜잭션 전반에서 사용된 객체를 추적하고, 객체 입력도 equivocate되지 않도록 그 처리 순서를 정렬한다.
Transaction signing 예시
다음 예시는 Rust, TypeScript, Sui CLI를 사용해 트랜잭션에 서명하고 실행하는 방법을 보여준다.
- TypeScript
- Rust
- Sui CLI
Sui TypeScript SDK를 사용하면 key pair를 인스턴스화하고 그 public key와 Sui address를 파생하는 여러 방법이 있다.
import { fromHex } from '@mysten/bcs';
import { type Keypair } from '@mysten/sui/cryptography';
import { SuiGrpcClient } from '@mysten/sui/grpc';
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
import { Secp256k1Keypair } from '@mysten/sui/keypairs/secp256k1';
import { Secp256r1Keypair } from '@mysten/sui/keypairs/secp256r1';
import { Transaction } from '@mysten/sui/transactions';
const kp_rand_0 = new Ed25519Keypair();
const kp_rand_1 = new Secp256k1Keypair();
const kp_rand_2 = new Secp256r1Keypair();
const kp_import_0 = Ed25519Keypair.fromSecretKey(
fromHex('0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82'),
);
const kp_import_1 = Secp256k1Keypair.fromSecretKey(
fromHex('0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82'),
);
const kp_import_2 = Secp256r1Keypair.fromSecretKey(
fromHex('0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82'),
);
// $MNEMONICS refers to 12/15/18/21/24 words from the wordlist, for example, "retire skin goose will hurry this field stadium drastic label husband venture cruel toe wire". Refer to [Keys and Addresses](/develop/transactions/transaction-auth/auth-overview) for more.
const kp_derive_0 = Ed25519Keypair.deriveKeypair('$MNEMONICS');
const kp_derive_1 = Secp256k1Keypair.deriveKeypair('$MNEMONICS');
const kp_derive_2 = Secp256r1Keypair.deriveKeypair('$MNEMONICS');
const kp_derive_with_path_0 = Ed25519Keypair.deriveKeypair('$MNEMONICS', "m/44'/784'/1'/0'/0'");
const kp_derive_with_path_1 = Secp256k1Keypair.deriveKeypair('$MNEMONICS', "m/54'/784'/1'/0/0");
const kp_derive_with_path_2 = Secp256r1Keypair.deriveKeypair('$MNEMONICS', "m/74'/784'/1'/0/0");
// replace `kp_rand_0` with the variable names above.
const pk = kp_rand_0.getPublicKey();
const sender = pk.toSuiAddress();
// create an example transaction.
const txb = new Transaction();
txb.setSender(sender);
txb.setGasPrice(5);
txb.setGasBudget(100);
const bytes = await txb.build();
const serializedSignature = (await keypair.signTransaction(bytes)).signature;
// verify the signature locally
expect(await keypair.getPublicKey().verifyTransaction(bytes, serializedSignature)).toEqual(true);
// define sui client for the desired network.
const client = new SuiGrpcClient({
baseUrl: 'https://fullnode.testnet.sui.io:443',
network: 'testnet',
});
// execute transaction.
let res = await client.executeTransaction({
transaction: bytes,
signatures: [serializedSignature],
});
console.log(res);
아래 전체 코드 예시는 crates/sui-sdk에 있다.
Sui Rust SDK를 사용하면 SuiKeyPair를 인스턴스화하고 그 public key와 Sui address를 파생하는 여러 방법이 있다.
// deterministically generate a key pair, testing only, do not use for mainnet, use the next section to randomly generate a key pair instead.
let skp_determ_0 =
SuiKeyPair::Ed25519(Ed25519KeyPair::generate(&mut StdRng::from_seed([0; 32])));
let _skp_determ_1 =
SuiKeyPair::Secp256k1(Secp256k1KeyPair::generate(&mut StdRng::from_seed([0; 32])));
let _skp_determ_2 =
SuiKeyPair::Secp256r1(Secp256r1KeyPair::generate(&mut StdRng::from_seed([0; 32])));
// randomly generate a key pair.
let _skp_rand_0 = SuiKeyPair::Ed25519(get_key_pair_from_rng(&mut rand::rngs::OsRng).1);
let _skp_rand_1 = SuiKeyPair::Secp256k1(get_key_pair_from_rng(&mut rand::rngs::OsRng).1);
let _skp_rand_2 = SuiKeyPair::Secp256r1(get_key_pair_from_rng(&mut rand::rngs::OsRng).1);
// import a key pair from a base64 encoded 32-byte `private key`.
let _skp_import_no_flag_0 = SuiKeyPair::Ed25519(Ed25519KeyPair::from_bytes(
&Base64::decode("1GPhHHkVlF6GrCty2IuBkM+tj/e0jn64ksJ1pc8KPoI=")
.map_err(|_| anyhow!("Invalid base64"))?,
)?);
let _skp_import_no_flag_1 = SuiKeyPair::Ed25519(Ed25519KeyPair::from_bytes(
&Base64::decode("1GPhHHkVlF6GrCty2IuBkM+tj/e0jn64ksJ1pc8KPoI=")
.map_err(|_| anyhow!("Invalid base64"))?,
)?);
let _skp_import_no_flag_2 = SuiKeyPair::Ed25519(Ed25519KeyPair::from_bytes(
&Base64::decode("1GPhHHkVlF6GrCty2IuBkM+tj/e0jn64ksJ1pc8KPoI=")
.map_err(|_| anyhow!("Invalid base64"))?,
)?);
// import a key pair from a base64 encoded 33-byte `flag || private key`. The signature scheme is determined by the flag.
let _skp_import_with_flag_0 =
SuiKeyPair::decode_base64("ANRj4Rx5FZRehqwrctiLgZDPrY/3tI5+uJLCdaXPCj6C")
.map_err(|_| anyhow!("Invalid base64"))?;
let _skp_import_with_flag_1 =
SuiKeyPair::decode_base64("AdRj4Rx5FZRehqwrctiLgZDPrY/3tI5+uJLCdaXPCj6C")
.map_err(|_| anyhow!("Invalid base64"))?;
let _skp_import_with_flag_2 =
SuiKeyPair::decode_base64("AtRj4Rx5FZRehqwrctiLgZDPrY/3tI5+uJLCdaXPCj6C")
.map_err(|_| anyhow!("Invalid base64"))?;
// replace `skp_determ_0` with the variable names above
let pk = skp_determ_0.public();
let sender = SuiAddress::from(&pk);
기본 가스 코인, 가스 예산, 가스 가격를 사용한 예시 programmable 트랜잭션으로 구성한 트랜잭션 data에 다음으로 서명한다. 자세한 내용은 Programmable Transaction Block 구성하기를 참조한다.
// construct an example programmable transaction.
let pt = {
let mut builder = ProgrammableTransactionBuilder::new();
builder.pay_sui(vec![sender], vec![1])?;
builder.finish()
};
let gas_budget = 5_000_000;
let gas_price = sui_client.read_api().get_reference_gas_price().await?;
// create the transaction data that will be sent to the network.
let tx_data = TransactionData::new_programmable(
sender,
vec![gas_coin.object_ref()],
pt,
gas_budget,
gas_price,
);
intent message의 Blake2b hash digest(intent || bcs bytes of tx_data)에 signature를 commit한다.
// derive the digest that the key pair should sign on, that is, the blake2b hash of `intent || tx_data`.
let intent_msg = IntentMessage::new(Intent::sui_transaction(), tx_data);
let raw_tx = bcs::to_bytes(&intent_msg).expect("bcs should not fail");
let mut hasher = sui_types::crypto::DefaultHash::default();
hasher.update(raw_tx.clone());
let digest = hasher.finalize().digest;
// use SuiKeyPair to sign the digest.
let sui_sig = skp_determ_0.sign(&digest);
// if you would like to verify the signature locally before submission, use this function. if it fails to verify locally, the transaction will fail to execute in Sui.
let res = sui_sig.verify_secure(
&intent_msg,
sender,
sui_types::crypto::SignatureScheme::ED25519,
);
assert!(res.is_ok());
마지막으로 signature와 함께 트랜잭션을 제출한다.
let transaction_response = sui_client
.quorum_driver_api()
.execute_transaction_block(
sui_types::transaction::Transaction::from_generic_sig_data(
intent_msg.value,
Intent::sui_transaction(),
vec![GenericSignature::Signature(sui_sig)],
),
SuiTransactionBlockResponseOptions::default(),
None,
)
.await?;
Sui CLI를 처음 사용할 때는 머신의 ~/.sui/keystore에 private key 목록(Base64로 인코딩된 flag || 32-byte-private-key)을 담은 로컬 파일을 만든다.
address를 지정하면 어떤 key든 사용해 트랜잭션에 서명할 수 있다.
address 목록을 보려면 sui keytool list를 사용한다.
key를 초기화하는 방법은 3가지가 있다:
# generate randomly.
sui client new-address ed25519
sui client new-address secp256k1
sui client new-address secp256r1
# import the 32-byte private key to keystore.
sui keytool import "0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82" ed25519
sui keytool import "0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82" secp256k1
sui keytool import "0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82" secp256r1
# import the mnemonics (recovery phrase) with derivation path to keystore.
# $MNEMONICS refers to 12/15/18/21/24 words from the wordlist, for example, "retire skin goose will hurry this field stadium drastic label husband venture cruel toe wire". Refer to [Keys and Addresses](/develop/transactions/transaction-auth/auth-overview) for more.
sui keytool import "$MNEMONICS" ed25519
sui keytool import "$MNEMONICS" secp256k1
sui keytool import "$MNEMONICS" secp256r1
CLI에서 transfer 트랜잭션을 생성한다.
$SUI_ADDRESS는 서명에 사용할 key pair에 대응하는 값으로 설정한다.
$GAS_COIN_ID는 gas에 사용할 sender 소유 객체 ID를 가리킨다.
$GAS_BUDGET은 트랜잭션 실행에 사용할 예산을 가리킨다.
그 다음 sender address에 대응하는 private key로 서명한다.
$MNEMONICS는 wordlist에 있는 12/15/18/21/24개 단어를 가리키며, 예를 들어 "retire skin goose will hurry this field stadium drastic label husband venture cruel toe wire" 같은 형태이다. 자세한 내용은 Keys and addresses를 참조한다.
Beginning with the Sui v1.24.1 release, the --gas-budget option is no longer required for CLI commands.
$ sui client gas
$ sui client transfer-sui --to $SUI_ADDRESS --sui-coin-object-id $GAS_COIN_ID --gas-budget $GAS_BUDGET --serialize-unsigned-transaction
$ sui keytool sign --address $SUI_ADDRESS --data $TX_BYTES
$ sui client execute-signed-tx --tx-bytes $TX_BYTES --signatures $SERIALIZED_SIGNATURE