Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

Open specifications for blockchain-anchored verification on Bitcoin SV.

These protocols share a common cryptographic foundation: **HKDF-SHA256 key derivation from identity to Bitcoin address**. This means anyone can independently verify proofs and contracts — no server, no database, no trust required. Only math.
These protocols share a common cryptographic foundation: **salted HKDF-SHA256 key derivation from identity to Bitcoin address**. The algorithm is published and deterministic, so anyone holding the inputs can independently re-derive an address and verify proofs and contracts against the blockchain — no proprietary server logic, no trust in a database. Only math.

> **Important:** key derivation mixes a per-user 256-bit random salt (the user's *presentation key*) with the identity input. Identity alone is **not** enough to derive the key — see [BEVISET §4](./BEVISET.md#4-key-derivation). Without the salt, derived keys would be reproducible by anyone who knows the (low-entropy) identity inputs; the salt is what makes derivation forgery-resistant. The snippet below is illustrative; the normative algorithm (including the salt) lives in the spec.

## Protocols

Expand Down Expand Up @@ -31,14 +33,15 @@ Two-party contract signing with blockchain anchoring. One inscription per signer
Both protocols use the same key derivation algorithm (HKDF-SHA256, RFC 5869) with independent domain separators per service. The same identity produces different Bitcoin addresses for Beviset and Helt Enig — by design.

```
Identity (BankID PID or email+phone)
Identity (BankID PID or email+phone) + per-user 256-bit salt
├─ domain: "beviset-v1-keygen" → Beviset address
├─ domain: "heltenig-v1-keygen" → Helt Enig address
└─ domain: "yourapp-v1-keygen" → Your service's address
├─ domain: "wab-keygen-beviset-v1" → Beviset address (BankID)
├─ domain: "beviset-v1-keygen-emailphone" → Beviset address (email+phone)
├─ domain: "wab-keygen-heltenig-v1" → Helt Enig address (BankID)
└─ domain: "heltenig-v1-keygen-emailphone" → Helt Enig address (email+phone)
```

The algorithm is published. Anyone can derive the address from an identity claim and verify it against the blockchain.
The full list of domain separators is in [BEVISET §4.3](./BEVISET.md#43-domain-separators). The algorithm is published; anyone holding the identity claim **and** the user's salt can re-derive the address and verify it against the blockchain. Services also expose a public `/api/verify-address` endpoint that confirms an address belongs to an identity without revealing the salt.

## Quick reference

Expand All @@ -49,9 +52,12 @@ import hashlib, hmac
PID_PEPPER = "beviset-protocol-pid-pepper-v1-datamynt"
identity_hash = hmac.new(PID_PEPPER.encode(), pid.encode(), hashlib.sha256).hexdigest()

# Key derivation (HKDF-SHA256)
def derive_key(identity_input: str, domain: str) -> bytes:
prk = hmac.new(domain.encode(), identity_input.encode(), hashlib.sha256).digest()
# Key derivation (salted HKDF-SHA256). `salt` is a per-user 256-bit random
# hex string (the presentation key). Without it, derivation is forgeable —
# see BEVISET §4.5. The IKM is identity_input || salt.
def derive_key(identity_input: str, domain: str, salt: str) -> bytes:
ikm = identity_input + salt
prk = hmac.new(domain.encode(), ikm.encode(), hashlib.sha256).digest()
return hmac.new(prk, b"bitcoin-key-derivation\x01", hashlib.sha256).digest()
```

Expand Down