kcode-k1-transaction 0.2.1

Canonical K1 transaction parsing and construction
Documentation
# Public API

```rust
use kcode_k1_transaction_store::TxId;

pub const SUBSYSTEM_BYTES: usize = 20;
pub const PUBLIC_KEY_BYTES: usize = 32;
pub const SIGNATURE_BYTES: usize = 64;
pub const MIN_TRANSACTION_BYTES: usize = 136;
pub const GENESIS_PARENT: TxId = TxId::from_bytes([0xff; 12]);
pub const REGISTER_AT_TIP: TxId = TxId::from_bytes([
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
]);

#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct SubsystemId;

impl SubsystemId {
    pub fn from_bytes(bytes: [u8; SUBSYSTEM_BYTES]) -> Result<Self, String>;
    pub fn from_str(value: &str) -> Result<Self, String>;
    pub const fn as_bytes(&self) -> &[u8; SUBSYSTEM_BYTES];
    pub fn as_str(&self) -> &str;
}

pub struct Transaction<'a>;

impl<'a> Transaction<'a> {
    pub fn parse(bytes: &'a [u8]) -> Result<Self, String>;
    pub fn parent(&self) -> TxId;
    pub fn timestamp(&self) -> u64;
    pub fn creator(&self) -> &[u8; PUBLIC_KEY_BYTES];
    pub fn subsystem(&self) -> SubsystemId;
    pub fn payload(&self) -> &'a [u8];
    pub fn signature(&self) -> &'a [u8; SIGNATURE_BYTES];
    pub fn signing_bytes(&self) -> &'a [u8];
}

pub fn build_signed_transaction<F>(
    parent: TxId,
    timestamp: u64,
    creator: [u8; PUBLIC_KEY_BYTES],
    subsystem: SubsystemId,
    payload: &[u8],
    signer: F,
) -> Result<Vec<u8>, String>
where
    F: FnOnce(&[u8]) -> Result<[u8; SIGNATURE_BYTES], String>;
```

`SUBSYSTEM_BYTES`, `PUBLIC_KEY_BYTES`, and `SIGNATURE_BYTES` are the exact field widths. `MIN_TRANSACTION_BYTES` is the wire length with an empty payload. `GENESIS_PARENT` is the all-`0xff` transaction-parent sentinel and does not identify a real transaction. `REGISTER_AT_TIP` is the 12-byte value `[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe]` and is only a KTO subsystem-registration cursor requesting activation at the current canonical tip. It is not a transaction parent or real transaction identity.

`SubsystemId` represents a nonempty logical UTF-8 name whose encoded byte length is at most 20 and which contains no NUL byte. Its canonical wire representation is exactly 20 bytes: the logical bytes followed by zero bytes to width 20. A 20-byte logical name has no padding and retains its existing representation unchanged. `from_str` creates that canonical representation and rejects empty names, names over 20 bytes, and names containing NUL. `from_bytes` accepts either a valid 20-byte UTF-8 name containing no NUL or a nonempty valid UTF-8 prefix followed only by zero padding. It rejects all-zero input, invalid UTF-8 in the logical prefix, and any nonzero byte after the first zero. `as_str` returns the unpadded logical name, while `as_bytes` returns the exact canonical 20-byte representation.

A transaction has this wire layout:

| Byte range | Value |
| --- | --- |
| `0..12` | Parent transaction ID |
| `12..20` | Little-endian `u64` Unix timestamp in seconds |
| `20..52` | Ed25519 public key |
| `52..72` | Canonical padded subsystem ID |
| `72..len - 64` | Payload |
| `len - 64..len` | Ed25519 signature |

Transactions are at least 136 bytes, and an empty payload is valid. The signature covers every preceding byte; `signing_bytes` returns that exact prefix.

`Transaction::parse` borrows its input and validates only minimum length and canonical subsystem encoding. It does not validate the signature, creator or leader authority, timestamp policy, payload, parent existence, storage, or ingress policy.

`build_signed_transaction` accepts every payload whose final `136 + payload.len()` byte output is representable and allocatable. It requests one output `Vec` with that exact final capacity, writes the parent, little-endian timestamp, creator, canonical subsystem representation, and payload, then invokes `signer` exactly once with that exact prefix. A successful signature is appended unchanged. A signer error is returned unchanged and the partial output is discarded. The function performs no signature verification, clock or key lookup, authority decision, storage, or other I/O. Length overflow returns an error; allocation failure follows `Vec` behavior.

All operations are synchronous and have no shared mutable state, locks, queues, background work, persistence, internal retries, or timeouts. Independent calls do not coordinate unless caller-supplied signer code does so. Parsing, constructors, and accessors perform bounded constant work independent of transaction and payload length; successful calls allocate no memory, while errors may allocate only their returned `String`. The builder performs `O(payload.len())` work, copies the payload once, requests one `136 + payload.len()` byte allocation, and does not reallocate. The signer runs inline and may block indefinitely; its own computation, allocation, I/O, synchronization, and side effects are caller-owned. No wall-clock guarantee is made for builder or signer execution.