← Blocktrails Core

Blocktrails Profiles (v0.1)

Application-layer profiles defining state formats and validation rules.

Abstract

This document defines the Blocktrails profile system and the Monochrome profile framework for building client-validated state contracts on Bitcoin. It includes MRC20, an issuer-controlled fungible token profile.

1. Introduction

Blocktrails defines a minimal primitive for anchoring and ordering off-chain state using Bitcoin P2TR output-key commitments. It provides ordering and integrity but does not define application semantics.

Profiles extend Blocktrails with meaning. A profile specifies:

Profiles do not modify Blocktrails Core verification.

1.1 Available Profiles

ProfileDescriptionTweak Source
MonochromeState machine framework with JCS serializationsha256(JCS(state))
MRC20Issuer-controlled fungible tokenssha256(JCS(state))
Git-markGit commits anchored to Bitcoinsha256(commit_hash)

2. Profile Model

2.1 Identification

A profile is identified by a stable string:

profile_id := <namespace>.<name>.<version>

Examples:

mono.monochrome.v0.1
mono.mrc20.v0.1

2.2 Requirements

A profile MUST:

A profile MUST NOT:

3. Canonical Serialization

Profiles use JCS (JSON Canonicalization Scheme, RFC 8785) for deterministic hashing.

3.1 Rules

3.2 State Hash and Scalar

Profiles define serialize(state) := jcs(state). The Blocktrails Core scalar() function is then:

scalar(state) := int(sha256(jcs(state)), big-endian) mod n

Serialization MUST be canonical and byte-stable: the same logical state MUST always produce identical bytes. JCS provides this guarantee.

The resulting tweak MUST be in the range [1, n-1]. If scalar(state) = 0, the state MUST be rejected (probability ~2-256).

Note on collisions: A hash collision is catastrophic by construction — it breaks commitment binding, not merely on-chain distinguishability. Two different states producing the same scalar() value would share an output key, creating semantic ambiguity: verifiers cannot determine which state was intended.

4. Common Encodings

4.1 Public Keys

<pubkey> := 32-byte x-only secp256k1 public key, hex-encoded (64 characters, lowercase)

Profiles use x-only pubkeys as account identifiers (e.g., token holders in MRC20). Issuer authority is separate — it derives from the Blocktrails spend signer (d_base holder), not from account pubkeys.

Nostr npub keys can be converted by bech32-decoding to the 32-byte x-only key; no parity adjustment is needed.

4.2 Hashes

<hex64> := 32-byte SHA-256 hash, hex-encoded (64 characters, lowercase)

4.3 Integers

Integers are JSON numbers with no fractional component. Values MUST NOT exceed 253-1 (JSON safe integer limit). Values above this limit are invalid.

Note: While fields are documented as <uint64> for clarity, the JSON-safe subset applies.

5. URN Vocabulary

Operations MUST use URN identifiers:

URNOperation
urn:mono:op:setSet key-value
urn:mono:op:mintCreate units
urn:mono:op:transferMove units
urn:mono:op:burnDestroy units
urn:mono:op:appendAppend to log

Short-form operation names (e.g., "op": "mint") are invalid. Implementations MUST reject states using non-URN operation identifiers.

5.1 URN Extensibility

The urn:mono: namespace is currently unregistered and defined by convention.

Normative constraint: Third parties MUST NOT define new operations under urn:mono:op: without governance by the Blocktrails/Monochrome specification maintainers. Namespace squatting creates ambiguity for verifiers.

Profile authors MAY define additional operations under their own namespace (e.g., urn:myorg:op:custom).

6. Monochrome Profile Framework (v0.1)

6.1 Purpose

Monochrome is a minimal profile framework for linear client-side state contracts. One active state at a time, no branching, no concealment, no concurrency.

6.2 Profile ID

mono.monochrome.v0.1

6.3 Contract Identity

contract_id := sha256("blocktrails.contract" || genesis_outpoint || P_base_xonly || profile_id)

The "blocktrails.contract" prefix provides domain separation to prevent cross-protocol identifier collisions.

Rationale: The parent spec explicitly omits domain separation for state commitments (simplicity; context is already bound to outpoint + base key). Contract identifiers are different: they are cross-protocol references used outside the commitment context, so domain separation is warranted here.

6.4 Base State Schema

{
  "profile": <string>,
  "seq": <uint64>,
  "prev": <hex64>,
  "ops": [ <operation> ]
}
FieldTypeDescription
profilestringProfile identifier
seqintegerSequence number, starts at 0
prevhex64sha256(serialize(previous_state)), 64 zeros for genesis
opsarrayOperations applied in this transition

The ops array contains only the operations applied in this state transition, not a cumulative log.

6.5 Base Validation

A transition is valid if:

  1. Blocktrails Core verification succeeds
  2. seq increments by exactly 1
  3. prev equals sha256(serialize(previous_state))
  4. All operations use valid URN identifiers
  5. All operations are well-formed per profile schema

6.6 Authorization

The signer of the Blocktrails spend is the sole authority for state transitions. This is a single-writer model.

7. MRC20: Fungible Token Profile (v0.1)

7.1 Purpose

MRC20 is a minimal fungible token profile built on Monochrome.

7.2 Authority Model

MRC20 is an issuer-controlled ledger.

The holder of the Blocktrails base key (d_base) has full authority over all operations, including:

This is analogous to a centralized ledger with cryptographic auditability. The issuer cannot forge history, but can authorize any valid state transition.

Implications:

7.2.1 Trust Through Observed Auditability

MRC20 operates on an earned trust model. The issuer cannot:

Trust is established through:

  1. Complete history visibility — Any party can replay and verify all transitions
  2. Immutable audit trail — Misbehavior is permanently recorded
  3. Reputation at stake — Observed honesty over time builds confidence

This differs from trustless systems (e.g., Bitcoin L1) where rules are enforced by consensus. In MRC20, rules are enforced by social accountability — the issuer can misbehave, but cannot hide misbehavior.

Practical implication: Users should verify issuer history before accepting tokens. Long-running contracts with clean histories are more trustworthy than new ones.

7.3 Profile ID

mono.mrc20.v0.1

7.4 State Schema

{
  "profile": "mono.mrc20.v0.1",
  "seq": <uint64>,
  "prev": <hex64>,
  "ops": [ <operation> ],
  "balances": { "<pubkey>": <uint64> },
  "supply": <uint64>,
  "name": <string>,
  "ticker": <string>,
  "decimals": <uint8>
}
FieldTypeRequiredMutable
profilestringYesNo
seqintegerYesYes
prevhex64YesYes
opsarrayYesYes
balancesobjectYesYes
supplyintegerYesYes
namestringYesNo
tickerstringYesNo
decimalsintegerYesNo

Integer limits: Per Section 4.3, all integer values are constrained to 253-1. For MRC20, this means maximum representable balance or supply per contract is 9,007,199,254,740,991 base units. With 8 decimals, this is ~90 million tokens.

7.5 State Computation Model

ops is authoritative. balances and supply are computed state.

Verifiers MUST:

  1. Start from genesis state (balances: {}, supply: 0)
  2. Apply each operation in sequence
  3. Verify computed balances and supply match the state exactly

Transition verification is local: it only requires the previous state S and the new state S'. Full-chain replay from genesis is also valid and produces equivalent results.

7.6 Operations

MINT

{ "op": "urn:mono:op:mint", "to": "<pubkey>", "amt": <uint64> }

TRANSFER

{ "op": "urn:mono:op:transfer", "from": "<pubkey>", "to": "<pubkey>", "amt": <uint64> }

BURN

{ "op": "urn:mono:op:burn", "from": "<pubkey>", "amt": <uint64> }

7.7 Validation Rules

A transition from S to S' is valid if:

  1. S'.profile == "mono.mrc20.v0.1"
  2. S'.seq == S.seq + 1
  3. S'.prev == sha256(serialize(S))
  4. Immutable fields unchanged: name, ticker, decimals
  5. All operations use URN identifiers
  6. For each operation, computed deltas are valid
  7. S'.balances equals computed balances after applying all ops
  8. S'.supply equals computed supply after applying all ops
  9. Blocktrails spend signed by issuer (d_base holder)
OpPreconditionEffect
MINTbalances[to] += amt; supply += amt
TRANSFERbalances[from] >= amtbalances[from] -= amt; balances[to] += amt
BURNbalances[from] >= amtbalances[from] -= amt; supply -= amt

7.8 Genesis State

{
  "profile": "mono.mrc20.v0.1",
  "seq": 0,
  "prev": "0000000000000000000000000000000000000000000000000000000000000000",
  "ops": [],
  "balances": {},
  "supply": 0,
  "name": "Example Token",
  "ticker": "EXT",
  "decimals": 8
}

8. Security Considerations

8.1 Inherited from Blocktrails Core

8.2 Profile-Specific

ThreatMitigation
Invalid state acceptedClient MUST validate all transitions
Balance manipulationRecompute from ops, verify match
Issuer misbehaviorPublicly auditable; cannot rewrite history
State withholdingRedundant state distribution

8.3 Trust Boundaries

┌─────────────────────────────────────┐ │ Trusted (Blocktrails Core) │ │ - Commitment integrity │ │ - Spend ordering │ │ - Double-spend prevention │ └─────────────────────────────────────┘ │ ┌─────────────────────────────────────┐ │ Trusted (MRC20 Issuer) │ │ - Honest operation authorization │ │ - State availability │ └─────────────────────────────────────┘ │ ┌─────────────────────────────────────┐ │ Untrusted (Verifiable) │ │ - Transition validity │ │ - Balance correctness │ │ - History integrity │ └─────────────────────────────────────┘

9. Non-Goals

This specification does not provide:

10. Relationship to RGB

Monochrome occupies similar design space to RGB with different trade-offs:

AspectMonochrome/MRC20RGB
State modelLinearDAG
VisibilityExplicitConcealed
ComplexityMinimalExpressive
AuthorityIssuer-controlledSelf-sovereign
ValidationJSON + JCSStrict types

11. References

12. Summary

LayerPurpose
Blocktrails CoreOrdering and anchoring
MonochromeProfile framework
MRC20Issuer-controlled fungible tokens