Git-mark Profile (v0.2)
Git commits anchored to Bitcoin via Blocktrails output-key commitments.
Abstract
Git-mark binds Bitcoin UTXOs to Git commits using BIP-341 TapTweak derivation. Each commit hash is combined with the current pubkey to derive a new pubkey, creating a cryptographic chain linking repository history to Bitcoin.
1. Introduction
Git-mark creates a two-way binding between Git and Bitcoin:
- Git → Bitcoin: Commit hash tweaks the pubkey via BIP-341 TapTweak, recorded on-chain
- Bitcoin → Git: TXO URI in repository points to the UTXO
Git-mark uses Git commit hashes as the state data, combined with BIP-341 tagged hashes for pubkey-dependent derivation.
2. Cryptographic Model
2.1 Chained Key Derivation
Git-mark is Blocktrails Core with the state being the commit hash as text: it follows the chained derivation model with pubkey-dependent tweaks, on the full point at every step (never its even-y lift). Every TXO, the first one included, carries the commit it was tweaked by; the base key itself is never an output.
Note: Each tweak depends on the current pubkey, not just the commit hash. The same commit at different positions produces different tweaks.
2.2 Scalar Function (Core scalar, state = the commit as text)
Git-mark uses the Core scalar function with the state serialized as the commit hash's 40 lowercase hex characters, as UTF-8 text:
scalar(P, commit):
h = sha256(utf8(commit)) // the 40 hex characters as bytes, not the 20 raw bytes
t = tagged_hash("TapTweak", x_only(P) || h)
return int(t, big-endian) mod n
This is the rule the live trails follow (checked against the chain: every sampled mark of a 68-mark trail of 1 October 2026, through 35 odd-y intermediate points). An earlier draft of this page hashed the 20 raw commit bytes and had no commit at genesis; no trail was made that way.
Where:
tagged_hash(tag, msg) = sha256(sha256(tag) || sha256(tag) || msg)x_only(P)extracts the 32-byte x-coordinatecommitis the git commit hash as 40 lowercase hex characters;his the SHA-256 of those 40 bytes of textPis the current full point, whatever its parity; the tweak is added to it as it isnis the secp256k1 curve order
If scalar(P, commit) = 0, the commit MUST be rejected (probability ~2-256).
2.3 Private Key Derivation
The spending key for any state, where each tweak tᵢ = scalar(Pᵢ₋₁, commitᵢ):
d_n = d₀ + t₀ + t₁ + ... + tₙ (mod n)
where t₀ = scalar(P_base, commit₀), t₁ = scalar(P₀, commit₁), etc.
3. TXO URI Format
Git-mark uses TXO URIs to record the on-chain state:
txo:<network>:<txid>:<vout>?amount=<sats>&commit=<git_hash>[&pubkey=<hex>]
| Field | Type | Description |
|---|---|---|
| network | string | Bitcoin network (mainnet, tbtc4, signet) |
| txid | hex64 | Transaction ID (64 hex characters) |
| vout | integer | Output index |
| amount | integer | Value in satoshis |
| pubkey | hex64 | X-only pubkey (64 hex characters), optional: a verifier recomputes it from the base key and the commits |
| commit | hex40 | Git commit hash: every TXO carries one, the genesis TXO too |
3.1 Genesis TXO
The genesis TXO carries the first commit; its pubkey is the base key tweaked by it, x(P_base + scalar(P_base, commit₀) × G). The base key itself is never an output: it is published with the trail (pubkeyBase in blocktrails.json) as a full compressed point, 02/03 + x, so a verifier has nothing to guess.
txo:tbtc4:51d87101...420eeacf:0?amount=999700&commit=9adc596c...20f16d0b
3.2 Subsequent TXOs
Each subsequent TXO includes the commit that tweaked to this pubkey:
txo:tbtc4:45fbcbd7...81a9f7d:0?amount=999000&pubkey=b5bc388c...5266c&commit=cf97baba...83d17d
4. State Transitions
4.1 Transition Flow
4.2 Making a Transition
- Create git commit with state changes
- Compute new pubkey:
P_new = P_current + scalar(P_current, commit) × G - Spend current UTXO to new pubkey
- Append new TXO URI to
txo.json - Push to git remote
5. Directory Structure
Git-mark state is stored in the repository:
.well-known/
└── txo/
└── txo.json # Array of TXO URIs
blocktrails.json # the trail: the base key, the states, the TXO URIs (what a verifier reads)
5.1 txo.json Format
Simple JSON array of TXO URI strings:
[
"txo:tbtc4:<txid>:0?amount=1000000&commit=<genesis_commit>",
"txo:tbtc4:<txid>:0?amount=999000&commit=<hash>",
"txo:tbtc4:<txid>:0?amount=998000&commit=<hash>"
]
5.2 blocktrails.json
The trail as a verifier reads it, served beside the repository (the same shape the live trails use):
{
"@type": "Blocktrail",
"version": "0.0.3",
"profile": "gitmark",
"pubkeyBase": "02…", // the base key as a full compressed point (02/03 + x): nothing to guess
"chain": "tbtc4",
"states": ["<genesis_commit>", "<hash>", "<hash>"], // one per TXO, in order
"txo": ["txo:tbtc4:…", "txo:tbtc4:…", "txo:tbtc4:…"]
}
pubkeyBase is where a verifier obtains the base key; txo.json alone does not carry it.
6. Verification
6.1 Chain Validity
A git-mark chain is valid if:
- Genesis: First TXO pubkey =
P_base + scalar(P_base, genesis_commit) × G - Continuity: Each subsequent
pubkey = prev_pubkey + scalar(prev_pubkey, commit) × G - On-chain: Each UTXO exists (or existed) on Bitcoin
- Git validity: Commit hashes correspond to actual git commits
6.2 Verification Algorithm
function verify(pubkeyBase, txoUris):
txos = parse(txoUris)
// the base key is published with the trail as a full point: nothing to guess
P = pubkeyBase // 02/03 || x
// every link, not the head alone: the head commits only to the sum of the tweaks
for i = 0 to txos.length - 1:
commit = txos[i].commit
if commit == null: return { valid: false, error: "a TXO without a commit" }
t = scalar(P, commit) // depends on the current point, kept as it is
P = P + t × G
if xonly(P) != witness_program(txos[i]): // the output on-chain (or the pubkey the URI records)
return { valid: false, error: "pubkey mismatch at " + i }
return { valid: true }
6.3 Commit Selection and Ordering
Commits anchored to the TXO chain need not be consecutive in git history. Any commit may be selected for marking. However:
- Marked commits SHOULD be in ancestral order (each marked commit is a descendant of the previous)
- The TXO chain records the order commits were marked
- Verifiers SHOULD confirm referenced commits exist in the repository
- Verifiers MAY check that commits form a valid ancestral chain in git
The EC_ADD verification ensures pubkey chain integrity. Git ancestry verification is an additional check that confirms the logical ordering of state transitions.
7. Comparison with Monochrome
| Aspect | Git-mark | Monochrome |
|---|---|---|
| Tweak derivation | BIP-341 TapTweak with commit hash | BIP-341 TapTweak with sha256(JCS(state)) |
| State storage | Git tree objects | JSON state array |
| State format | Any git content | Structured JSON with ops |
| Linking | TXO URI in txo.json | State contains prev hash |
| Use case | Repository anchoring | Application state machines |
Both profiles use BIP-341 TapTweak for pubkey-dependent derivation. Git-mark leverages Git's existing content-addressed storage and commit graph. Monochrome defines its own state schema with explicit operations.
8. Security Considerations
8.1 Inherited from Blocktrails
- Hash preimage resistance → commit binding
- Discrete log hardness → pubkey security
- Bitcoin consensus → spend ordering
8.2 Git-mark Specific
| Threat | Mitigation |
|---|---|
| Commit rewriting | Git's content-addressing; verifiers check commit exists |
| History divergence | TXO chain anchors specific commit sequence |
| UTXO availability | Bitcoin full node or SPV verification |
| txo.json tampering | Verify pubkey derivation matches recorded commits |
8.3 Trust Model
- Bitcoin: UTXO existence and spend ordering
- Git: Commit immutability (content-addressed)
- Client: Must verify EC_ADD chain and commit existence
9. Implementation
Reference implementation: blocktrails/git-mark
npm install git-seal
git mark init
git mark genesis --txid <txid> --vout 0 --amount 1000000
git commit -m "state change"
git mark advance --txid <txid> --vout 0 --amount 999000
git mark verify
10. References
- Blocktrails Core Specification
- Blocktrails Profiles
- Legacy gitmark
- BIP-340: Schnorr Signatures
- BIP-341: Taproot