본문으로 건너뛰기

WalletKit 암호화

채널 암호화

ChannelCrypto

페어링·세션 채널의 SHA-256, ChaCha20-Poly1305, HKDF-SHA256 연산을 제공합니다.

public object ChannelCrypto {
public fun sha256(data: ByteArray): ByteArray
public fun seal(
key: ByteArray,
plaintext: ByteArray,
associatedData: ByteArray? = null,
): ByteArray
public fun open(
key: ByteArray,
ciphertext: ByteArray,
associatedData: ByteArray? = null,
): ByteArray
public fun hkdfSha256(
ikm: ByteArray,
salt: ByteArray,
info: ByteArray,
sizeBits: Int = 256,
): ByteArray
}
함수반환오류
sha25632바이트 해시제공자 오류 가능
sealIV가 포함된 인증 암호문키 길이·제공자 오류
open검증된 평문잘못된 키, AAD 또는 변조 시 복호화 오류
hkdfSha256sizeBits / 8 바이트잘못된 출력 크기·제공자 오류
import io.lambda256.scopeconnect.walletkit.crypto.ChannelCrypto

val key = ByteArray(32) { it.toByte() }
val plaintext = "scope-session".encodeToByteArray()
val sealed = ChannelCrypto.seal(key, plaintext)
val opened = ChannelCrypto.open(key, sealed)
check(opened.contentEquals(plaintext))

X25519

양쪽이 임시 키 쌍을 만들고 동일한 공유 비밀을 계산합니다.

public object X25519 {
public class KeyPair {
public val publicKeyRaw: ByteArray
}

public fun generateKeyPair(): KeyPair
public fun sharedSecret(
self: KeyPair,
peerPublicKeyRaw: ByteArray,
): ByteArray
}

peerPublicKeyRaw는 32바이트 RAW X25519 공개키여야 합니다. 반환된 공유 비밀을 직접 암호 키로 쓰지 말고 ChannelCrypto.hkdfSha256()로 파생합니다. 세션 키 합의가 이 조합(X25519 ECDH → HKDF-SHA256)으로 이루어집니다.

import io.lambda256.scopeconnect.walletkit.crypto.X25519

val alice = X25519.generateKeyPair()
val bob = X25519.generateKeyPair()
val a = X25519.sharedSecret(alice, bob.publicKeyRaw)
val b = X25519.sharedSecret(bob, alice.publicKeyRaw)
check(a.contentEquals(b))

Secp256k1SignerEthSignature

32바이트 서명 해시(digest)에 서명해 Ethereum 형식의 복구 가능한 서명을 만듭니다.

public class EthSignature(
public val r: ByteArray,
public val s: ByteArray,
public val v: Int,
) {
public fun toBytes(): ByteArray
}

public object Secp256k1Signer {
public fun signRecoverable(
digest: ByteArray,
privateKey: ByteArray,
): EthSignature
public fun addressOf(privateKey: ByteArray): String
public fun recoverAddress(
digest: ByteArray,
signature: EthSignature,
): String
}

반환: rs는 각각 32바이트이며 v는 27 또는 28입니다. 주소 함수는 0x 접두사 없는 소문자 40자리 hex를 반환합니다.

오류: 서명 해시 또는 개인키 길이가 32바이트가 아니거나 복구 ID를 결정할 수 없으면 IllegalArgumentException을 던집니다.

import io.lambda256.scopeconnect.walletkit.crypto.ChannelCrypto
import io.lambda256.scopeconnect.walletkit.crypto.Secp256k1Signer

val privateKey = ByteArray(32).also { it[31] = 1 }
val digest = ChannelCrypto.sha256("approve".encodeToByteArray())
val signature = Secp256k1Signer.signRecoverable(digest, privateKey)
check(
Secp256k1Signer.recoverAddress(digest, signature) ==
Secp256k1Signer.addressOf(privateKey),
)

개인키는 샘플 상수로 저장하지 않습니다. 위 키는 로컬 연산 검증용이며 실제 지갑에서는 자체 보안 구조와 사용자 인증 정책을 적용합니다. 이 함수를 호출하면 개인키 바이트가 지갑 앱 프로세스 메모리에서 처리됩니다.

Kotlin object는 Swift에서 .shared로 접근합니다 — Secp256k1Signer.shared.signRecoverable(digest:privateKey:). Swift 쪽 호출 예시는 서명하기의 Swift 탭에 있습니다.

해시와 16진수 변환

public fun keccak256(data: ByteArray): ByteArray
public fun ByteArray.toHex(): String
public fun String.hexToBytes(): ByteArray

keccak256은 EVM에서 쓰는 legacy Keccak-256이며 NIST SHA3-256과 다릅니다. hexToBytes()는 선택적 0x 접두사를 허용하며 길이가 홀수이거나 문자가 잘못되면 IllegalArgumentException을 던집니다.

import io.lambda256.scopeconnect.walletkit.crypto.hexToBytes
import io.lambda256.scopeconnect.walletkit.crypto.toHex

val bytes = "0x0102ff".hexToBytes()
check(bytes.toHex() == "0102ff")

walletKitCryptoProvider

public expect val walletKitCryptoProvider: CryptographyProvider

WalletKit이 플랫폼별로 구성한 공개 암호 제공자입니다. iOS는 CryptoKit, JVM은 JDK 제공자를 쓰고, Android는 JDK 제공자가 X25519·Ed25519를 지원하지 않아 BouncyCastle을 포함합니다. 애플리케이션이 같은 제공자를 직접 사용해야 할 때 선택합니다.

EIP-712 인코딩

Eip712

구조화 데이터의 기본 타입 값과 도메인 분리자를 인코딩하고, 최종 서명 해시(digest)를 계산합니다.

public object Eip712 {
public fun typeHash(encodeType: String): ByteArray
public fun uint256(value: Long): ByteArray
public fun uint256(decimal: String): ByteArray
public fun address(addressHex: String): ByteArray
public fun bytes32(hex: String): ByteArray
public fun string(value: String): ByteArray
public fun domainSeparator(
name: String,
version: String,
chainId: Long,
verifyingContract: String,
): ByteArray
public fun digest(
domainSeparator: ByteArray,
hashStruct: ByteArray,
): ByteArray
}
함수제약반환
uint256(Long)0 이상32바이트 big-endian
uint256(String)비어 있지 않은 10진수, 256비트 이하32바이트 big-endian
address20바이트 hex12바이트 왼쪽 패딩을 더한 값
bytes32정확히 32바이트 hex32바이트
domainSeparator일반 도메인 필드32바이트 hash
digest두 입력 모두 32바이트32바이트 서명 해시

제약을 어기면 IllegalArgumentException을 던집니다.

import io.lambda256.scopeconnect.walletkit.crypto.keccak256
import io.lambda256.scopeconnect.walletkit.eip.Eip712

val domain = Eip712.domainSeparator(
name = "Example",
version = "1",
chainId = 11155111,
verifyingContract = "0x0000000000000000000000000000000000000001",
)
val messageHash = keccak256(
Eip712.typeHash("Message(string contents)") +
Eip712.string("hello"),
)
val digest = Eip712.digest(domain, messageHash)
check(digest.size == 32)