OrangeCheck Protocol
The OrangeCheck Protocol (OCP) defines a portable, verifiable proof of Bitcoin address control with optional identity bindings. It is non-custodial, Bitcoin-native, and built on BIP-322 message signatures.
Any wallet can create a proof. Any verifier can confirm it—without trusting OrangeCheck or writing to the blockchain.
Why a protocol?
Existing Bitcoin signatures are often ad-hoc, wallet-specific, or fragile. OrangeCheck standardizes this with:
- Canonical message format - Deterministic, reproducible messages
- Attestation IDs - Unique identifiers derived from message hash
- Multi-protocol identities - Bind Nostr, GitHub, Twitter, DNS to one attestation
- Nostr publishing - Decentralized storage and discovery
- Clear verification flow - Reproducible verification by anyone
Think of it as a "minimal grammar" for Bitcoin reputation: small, strict, and portable.
Canonical message format
Wallets must sign the full UTF-8 text below with LF line endings and exactly one trailing LF.
Core lines (7 required)
orangecheck
identities: <COMMA_SEPARATED_PROTOCOL_IDENTIFIER_PAIRS>
address: <BITCOIN_ADDRESS>
purpose: portable reputation attestation (non-custodial)
nonce: <RANDOM_16B_HEX_LOWER>
issued_at: <ISO8601_UTC>
ack: I attest control of this address and bind it to my identities.
Field specifications
- Line 1: Fixed string
orangecheck(no version number) - Line 2:
identities:followed by comma-separatedprotocol:identifierpairs, sorted lexicographically. Empty string if no identities. - Line 3:
address:followed by the Bitcoin address - Line 4: Fixed purpose string
- Line 5:
nonce:followed by 32 hex characters (16 random bytes, lowercase) - Line 6:
issued_at:followed by ISO 8601 timestamp withZsuffix - Line 7: Fixed acknowledgment string
Example with identities
orangecheck
identities: github:alice,nostr:npub1alice...,twitter:@alice
address: bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh
purpose: portable reputation attestation (non-custodial)
nonce: a1b2c3d4e5f6789012345678901234ab
issued_at: 2024-01-15T12:00:00.000Z
ack: I attest control of this address and bind it to my identities.
Example without identities
orangecheck
identities:
address: bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh
purpose: portable reputation attestation (non-custodial)
nonce: a1b2c3d4e5f6789012345678901234ab
issued_at: 2024-01-15T12:00:00.000Z
ack: I attest control of this address and bind it to my identities.
Attestation ID
The attestation ID is the SHA-256 hash of the canonical message (UTF-8 encoded), represented as 64 lowercase hexadecimal characters.
This makes attestation IDs:
- Deterministic - Same message always produces same ID
- Unique - Collision-resistant (SHA-256)
- Verifiable - Anyone can recompute from the message
- Portable - Can be used as a universal identifier
Computation
const encoder = new TextEncoder();
const data = encoder.encode(canonicalMessage);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const attestationId = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
Identity bindings
Identities are bound to attestations using the identities: field. Each identity is a protocol:identifier pair.
Supported protocols
- nostr - Nostr public key (npub or hex)
- github - GitHub username
- twitter - Twitter handle (with or without @)
- dns - Domain name
Format rules
- Protocol names are lowercase
- Identifiers are case-sensitive
- Multiple identities are comma-separated
- Identities are sorted lexicographically by full
protocol:identifierstring - Empty identities field is valid (no bindings)
Examples
identities: github:satoshi
identities: github:alice,nostr:npub1alice...,twitter:@alice
identities: dns:example.com,github:bob
identities:
Identity verification
Identity verification is optional and happens off-protocol. Each protocol has its own verification method:
Nostr
User posts a Nostr note (kind 1) containing the attestation ID. Verifiers query Nostr relays for notes from the claimed npub containing the attestation ID and verify the event signature.
GitHub
User creates a public gist containing the attestation ID. Verifiers search the user's gists via GitHub API for the attestation ID.
User tweets the attestation ID. Verifiers check the user's timeline for a tweet containing the attestation ID.
DNS
User adds a .well-known/orangecheck.txt file to their domain containing the attestation ID. Verifiers fetch the file via HTTPS.
All verifications are optional. Attestations can exist without any identity verification.
Nostr publishing
Attestations can be published to Nostr relays as NIP-78 events (kind 30078 - Parameterized Replaceable Events).
Event structure
{
"kind": 30078,
"content": "<JSON_ENCODED_ATTESTATION_ENVELOPE>",
"tags": [
["d", "<ATTESTATION_ID>"],
["address", "<BITCOIN_ADDRESS>"],
["i", "nostr:npub1..."],
["i", "github:alice"],
["scheme", "bip322"]
],
"created_at": 1234567890,
"pubkey": "<NOSTR_PUBKEY>"
}
Publishing options
- Sign with bound npub - If a Nostr identity is bound, sign the event with that npub's private key
- Sign with ephemeral key - Generate a temporary keypair for signing
- Don't publish - Keep the attestation local
Discovery
Published attestations can be discovered by:
- Attestation ID - Query for
kind:30078with#dtag - Bitcoin address - Query for
kind:30078with#addresstag - Identity - Query for
kind:30078with#itag
Verification flow
- Parse the canonical message from the attestation
- Verify the BIP-322 signature against the declared Bitcoin address
- Compute the attestation ID from the message hash
- Check that the computed ID matches the claimed ID
- Query the blockchain for UTXOs at the address
- Calculate reputation metrics (sats bonded, days unspent, score)
- Verify identity bindings (optional, off-protocol)
The outcome is a self-contained proof: portable, reproducible, and independently checkable—no API calls, no reliance on OrangeCheck.
Reputation metrics
OrangeCheck computes reputation metrics from confirmed UTXOs:
- sats_bonded - Total confirmed satoshis at the address
- days_unspent - Weighted average age of UTXOs in days
- score - Computed using configurable scoring algorithm
Scoring algorithms
- v0 - Reference algorithm:
sqrt(sats) * log2(days + 1) - tier - Bronze/Silver/Gold/Platinum tiers
- time-weighted - Emphasizes UTXO age
- amount-weighted - Emphasizes satoshi amount
- threshold - Binary pass/fail based on minimums
- none - No score, just raw metrics
All scores are advisory interpretations of raw metrics. Verifiers can apply their own scoring logic.
Extensions (future)
The protocol reserves space for optional extensions after the 7 core lines. Extensions must be:
- Lowercase keys
- Lexicographically sorted
- Format:
key: value
Potential future extensions:
aud- Audience/origin hintexpires- Expiry timestampnetwork- Bitcoin network (signet, testnet)scope- Human-readable label
Extensions are not yet implemented but the format allows for backward-compatible additions.
Security considerations
- Signature verification - Always verify BIP-322 signatures before trusting attestations
- Replay attacks - Nonces prevent message reuse
- Identity spoofing - Always verify identity bindings independently
- UTXO confirmation - Only count confirmed UTXOs for metrics
- Nostr event signatures - Verify Schnorr signatures on Nostr events