인텐트 서명
인텐트는 서명이 커밋하는 메시지의 도메인 구분자 역할을 하는 간결한 struct이다. 서명이 커밋하는 데이터는 인텐트 메시지이다. Sui의 모든 서명은 메시지 자체가 아니라 인텐트 메시지에 커밋해야 한다.
인텐트 서명 표준은 사용자 서명과 authority 서명 모두에 대해 서명되는 데이터에 간결한 도메인 구분자를 제공한다. 이 표준에는 여러 이점이 있다:
-
인텐트 scope는 Rust struct tag name string 대신
u8표현으로 대체된다. -
인텐트 scope 외에도 intent version 및 app ID 같은 다른 중요한 도메인 구분자에도 커밋할 수 있다.
-
데이터 자체는 더 이상
Signabletrait을 구현할 필요가 없고,Serialize만 구현하면 된다. -
모든 서명은 사용자 서명(
TransactionData에만 커밋)과 authority 서명(TransactionEffects,ProofOfPossession,SenderSignedTransaction같은 모든 내부 intent scope에 커밋)을 포함해 같은 인텐트 메시지 구조를 채택할 수 있다.
Structs
IntentMessage struct는 인텐트와 직렬화된 데이터 값으로 구성된다.
pub struct IntentMessage<T> {
pub intent: Intent,
pub value: T,
}
인텐트 struct를 생성하려면 IntentScope(메시지 타입), IntentVersion(네트워크가 지원하는 버전), AppId(서명이 참조하는 애플리케이션)를 포함한다.
pub struct Intent {
scope: IntentScope,
version: IntentVersion,
app_id: AppId,
}
자세한 필드 정의는 소스 코드의 enum 정의를 참고한다:
pub enum IntentScope {
TransactionData = 0, // Used for a user signature on a transaction data.
TransactionEffects = 1, // Used for an authority signature on transaction effects.
CheckpointSummary = 2, // Used for an authority signature on a checkpoint summary.
PersonalMessage = 3, // Used for a user signature on a personal message.
SenderSignedTransaction = 4, // Used for an authority signature on a user signed transaction.
ProofOfPossession = 5, // Used as a signature representing an authority's proof of possession of its authority protocol key.
HeaderDigest = 6, // Used for narwhal authority signature on header digest.
BridgeEventUnused = 7, // for bridge purposes but it's currently not included in messages.
ConsensusBlock = 8, // Used for consensus authority signature on block's digest.
DiscoveryPeers = 9, // Used for reporting peer addresses in discovery.
}
pub enum IntentVersion {
V0 = 0,
}
pub enum AppId {
#[default]
Sui = 0,
Narwhal = 1,
Consensus = 2,
}
pub enum HashingIntentScope {
ChildObjectId = 0xf0,
RegularObjectId = 0xf1,
}
Intent의 직렬화는 각 필드가 바이트로 표현되는 3바이트 배열이다.
IntentMessage<T>의 직렬화는 인텐트의 3바이트와 BCS로 직렬화된 메시지를 연결한 것이다.
User signature
사용자 서명을 생성하려면 먼저 인텐트 메시지를 구성한 다음, transaction data의 인텐트 메시지(intent || message)를 BCS로 직렬화한 값의 32바이트 Blake2b 해시에 대해 서명을 생성한다.
다음 예시는 이를 보여준다:
- Rust
- TypeScript
let intent = Intent::default();
let intent_msg = IntentMessage::new(intent, data);
let signature = Signature::new_secure(&intent_msg, signer);
const intentMessage = messageWithIntent('TransactionData', transactionBytes);
const signature = await this.sign(intentMessage);
내부적으로 Rust의 new_secure 메서드와 TypeScript의 signData 메서드는 다음을 수행한다:
-
인텐트 메시지를 3바이트 인텐트와 transaction data의 BCS 직렬화 바이트를 연결한 값으로 직렬화한다.
-
Blake2b hash를 적용해 32바이트 digest를 얻는다.
-
digest를 서명자의 해당 scheme에 대한 signing API에 전달한다. 지원되는 signature scheme은 pure Ed25519, ECDSA Secp256k1, ECDSA Secp256r1이다. 각 scheme의 요구사항은 Sui 서명을 참고한다.
Authority signature
Authority 서명은 protocol key를 사용하여 생성된다. 이 서명이 커밋하는 데이터도 인텐트 메시지 intent || message이다. 사용 가능한 모든 intent scope는 소스 코드에서 확인한다.
Authority의 proof of possession 생성
Authority가 네트워크 참여를 요청할 때는 protocol public key와 proof of possession(PoP)이 필요하다. PoP는 rogue key attack을 방지하기 위해 필요하다.
Proof of possession은 authority protocol private key를 사용해 생성하는 BLS 서명이며, 다음 메시지에 커밋한다: intent || pubkey || address || epoch. 값은 다음과 같다:
-
intent: scope가Proof of Possession,version이V0,app_id가Sui인 인텐트를 나타내는[5, 0, 0]으로 직렬화된다. -
pubkey: authority BLS protocol key의 직렬화된 public key bytes이다. -
address: authority account key와 연결된 account address이다. -
epoch:[0, 0, 0, 0, 0, 0, 0, 0]으로 직렬화된다.
Rust에서 proof of possession을 생성하려면 fn generate_proof_of_possession의 구현을 참고한다. Test vector는 fn test_proof_of_possession을 참고한다.