Data Types

← Main | 한국어 →


Overview

The Sigilaris datatype module provides type-safe, opaque types for common blockchain primitives with built-in codec support. These types ensure correctness at compile time while maintaining zero-cost abstractions at runtime.

Why Specialized Data Types?

In blockchain applications:

Key Features

Quick Start (30 seconds)

import org.sigilaris.core.datatype.*
import scodec.bits.ByteVector

// 256-bit unsigned integer
val hash = UInt256.fromHex("cafe").toOption.get
// hash: UInt256 = ByteVector(32 bytes, 0x000000000000000000000000000000000000000000000000000000000000cafe)

// Non-negative arbitrary-precision integer
val amount = BigNat.unsafeFromLong(1000L)
// amount: BigNat = 1000

// Length-prefixed UTF-8 string
val label = Utf8("account-1")
// label: Utf8 = "account-1"

That's it! These types work seamlessly with byte and JSON codecs.

Data Types

BigNat - Non-Negative Arbitrary-Precision Integer

Natural number (≥ 0) with arbitrary precision:

import org.sigilaris.core.datatype.*

val n1 = BigNat.unsafeFromLong(42L)
// n1: BigNat = 42
val n2 = BigNat.unsafeFromLong(10L)
// n2: BigNat = 10

// Safe arithmetic operations
val sum = BigNat.add(n1, n2)  // 52
// sum: BigNat = 52
val product = BigNat.multiply(n1, n2)  // 420
// product: BigNat = 420

// Subtraction returns Either
val diff = BigNat.tryToSubtract(n1, n2)  // Right(32)
// diff: Either[String, BigNat] = Right(value = 32)
val invalid = BigNat.tryToSubtract(n2, n1)  // Left("...")
// invalid: Either[String, BigNat] = Left(value = "Should be positive or zero")

Key Features:

UInt256 - 256-bit Unsigned Integer

Fixed-size 256-bit unsigned integer with big-endian representation:

import org.sigilaris.core.datatype.*
import scodec.bits.ByteVector

// From hex string
val u1 = UInt256.fromHex("ff").toOption.get
// u1: UInt256 = ByteVector(32 bytes, 0x00000000000000000000000000000000000000000000000000000000000000ff)
val u2 = UInt256.fromHex("0x123abc").toOption.get
// u2: UInt256 = ByteVector(32 bytes, 0x0000000000000000000000000000000000000000000000000000000000123abc)

// From BigInt
val u3 = UInt256.fromBigIntUnsigned(BigInt(42)).toOption.get
// u3: UInt256 = ByteVector(32 bytes, 0x000000000000000000000000000000000000000000000000000000000000002a)

// Conversions
val bigInt: BigInt = u1.toBigIntUnsigned
// bigInt: BigInt = 255
val hex: String = u1.toHexLower  // lowercase hex, no 0x prefix
// hex: String = "00000000000000000000000000000000000000000000000000000000000000ff"

Key Features:

Utf8 - Length-Prefixed UTF-8 String

UTF-8 string with length-prefixed byte encoding:

import org.sigilaris.core.datatype.*

val text = Utf8("Hello, 世界!")
// text: Utf8 = "Hello, 世界!"

// String conversion
val str: String = text.asString
// str: String = "Hello, 世界!"

// Works as Map keys
val map = Map(Utf8("key1") -> 42, Utf8("key2") -> 100)
// map: Map[Utf8, Int] = Map("key1" -> 42, "key2" -> 100)

Key Features:

Codec Integration

All types have built-in byte and JSON codecs:

import org.sigilaris.core.datatype.*
import org.sigilaris.core.codec.byte.{ByteEncoder, ByteDecoder}
import org.sigilaris.core.codec.json.{JsonEncoder, JsonDecoder}
import scodec.bits.ByteVector

val num = BigNat.unsafeFromLong(42L)
// num: BigNat = 42

// Byte encoding
val bytes = ByteEncoder[BigNat].encode(num)
// bytes: ByteVector = Chunk(
//   bytes = View(
//     at = scodec.bits.ByteVector$AtArray@40e16e9f,
//     offset = 0L,
//     size = 1L
//   )
// )
val decoded = ByteDecoder[BigNat].decode(bytes)
// decoded: Either[DecodeFailure, DecodeResult[BigNat]] = Right(
//   value = DecodeResult(
//     value = 42,
//     remainder = Chunk(
//       bytes = View(
//         at = scodec.bits.ByteVector$AtEmpty$@1e36eb43,
//         offset = 0L,
//         size = 0L
//       )
//     )
//   )
// )

// JSON encoding
val json = JsonEncoder[BigNat].encode(num)
// json: JsonValue = JString(value = "42")
val fromJson = JsonDecoder[BigNat].decode(json)
// fromJson: Either[DecodeFailure, BigNat] = Right(value = 42)

Type Safety Examples

Preventing Invalid Values

import org.sigilaris.core.datatype.*
import scodec.bits.ByteVector

// BigNat: compile-time guarantee of non-negativity
val valid = BigNat.fromBigInt(BigInt(100))  // Right(BigNat(100))
// valid: Either[String, BigNat] = Right(value = 100)
val invalid = BigNat.fromBigInt(BigInt(-1))  // Left("Constraint failed...")
// invalid: Either[String, BigNat] = Left(value = "Should be positive or zero")

// UInt256: typed failures for validation errors
val overflow = UInt256.fromBigIntUnsigned(BigInt(2).pow(256))
// overflow: Either[UInt256Failure, UInt256] = Left(
//   value = UInt256Overflow(detail = "BigInt does not fit into 256 bits")
// )
// Left(UInt256Overflow("..."))

val tooLong = UInt256.fromBytesBE(ByteVector.fill(33)(0xff.toByte))
// tooLong: Either[UInt256Failure, UInt256] = Left(
//   value = UInt256TooLong(actualBytes = 33L, maxBytes = 32)
// )
// Left(UInt256TooLong(33, 32))

Safe Arithmetic

import org.sigilaris.core.datatype.*

val a = BigNat.unsafeFromLong(10L)
// a: BigNat = 10
val b = BigNat.unsafeFromLong(3L)
// b: BigNat = 3

// Operations that always succeed
BigNat.add(a, b)       // 13
// res6: BigNat = 13
BigNat.multiply(a, b)  // 30
// res7: BigNat = 30
BigNat.divide(a, b)    // 3
// res8: BigNat = 3

// Operation that can fail
BigNat.tryToSubtract(a, b)  // Right(7)
// res9: Either[String, BigNat] = Right(value = 7)
BigNat.tryToSubtract(b, a)  // Left("Constraint failed...")
// res10: Either[String, BigNat] = Left(value = "Should be positive or zero")

Design Philosophy

Opaque Types

Validated Construction

Codec-First Design

Next Steps

  1. BigNat: Arbitrary-precision natural numbers
  2. UInt256: Fixed-size unsigned integers for hashes/addresses
  3. Utf8: Length-prefixed strings for metadata

← Main | 한국어 →