Privacy-preserving price threshold commitments for Bitcoin hashlocks using Chainlink CRE and Nostr.
Creates Hash160 commitments to price thresholds that reveal their secret only when breached.
Example: "If BTC drops below $94k, reveal secret X" - the threshold stays hidden until the price crosses it.
The CRE is a reactive WASM module - it responds to HTTP triggers from the Gateway/Regulator.
| Trigger | Input | Action | Frequency |
|---|---|---|---|
| CREATE | {domain, thold_price} |
Create new threshold commitment | On-demand |
| CHECK | {domain, thold_hash} |
Check if price breached threshold | Every 90s (liquidation poll) |
| Cron | (scheduled) | Generate batch quotes at all collateral levels | Every 1.5min |
All prices are float64 because HMAC computation uses %.8f formatting. Timestamps are int64.
type PriceQuote struct {
// Server identity
SrvNetwork string `json:"srv_network"` // "main" | "test"
SrvPubkey string `json:"srv_pubkey"` // Oracle public key (hex)
// Quote price (at commitment creation)
QuoteOrigin string `json:"quote_origin"` // "link" | "nostr" | "cre"
QuotePrice float64 `json:"quote_price"` // BTC/USD price
QuoteStamp int64 `json:"quote_stamp"` // Unix timestamp
// Latest price (most recent observation)
LatestOrigin string `json:"latest_origin"`
LatestPrice float64 `json:"latest_price"`
LatestStamp int64 `json:"latest_stamp"`
// Event price (at breach, if any)
EventOrigin *string `json:"event_origin"`
EventPrice *float64 `json:"event_price"`
EventStamp *int64 `json:"event_stamp"`
EventType string `json:"event_type"` // "active" | "breach"
// Threshold commitment
TholdHash string `json:"thold_hash"` // Hash160 (20 bytes hex)
TholdKey *string `json:"thold_key"` // Revealed on breach
TholdPrice float64 `json:"thold_price"`
// State & signatures
IsExpired bool `json:"is_expired"`
ReqID string `json:"req_id"` // Request ID hash
ReqSig string `json:"req_sig"` // Schnorr signature
}┌─────────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Client App │──────▶│ Gateway │──────▶│ CRE (WASM) │──────▶│ Nostr Relay │
└─────────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
│ │
SQLite cache Chainlink DON
(base price) (price consensus)
| Component | Description |
|---|---|
| Gateway | HTTP proxy, caches base price, routes to CRE |
| CRE (WASM) | Chainlink Compute Runtime - creates/evaluates quotes |
| Nostr Relay | Stores threshold commitment events (NIP-33) |
| Chainlink DON | Byzantine fault tolerant price consensus |
Creates a new threshold commitment at a specific price.
Client → GET /api/quote?th=105000 → Gateway → CRE → Nostr
Batch check quotes for breach, reveals secrets if price crossed.
Client → POST /api/evaluate {thold_hashes: [...]} → Gateway → CRE → Nostr
Get the current batch price (cached from CRE cron job).
Client → GET /api/price → Gateway (SQLite cache)
CRE Gateway
│ │
├─ fetchPrice() → $105,000 │
├─ generateQuotesParallel() │
│ (366 quotes at 1% collateral steps) │
├─ publish all to Nostr │
│ │
├─ POST /webhook/ducat ───────────────────►│
│ { │
│ event_type: "batch_generated", │
│ data: { │
│ base_price: 105000, │
│ base_stamp: 1734567890 │
│ } │
│ } │
│ ├─ cache in memory + SQLite
Client Gateway CRE
│ │ │
├─ GET /api/price ────────────────────────►│ │
│◄─ {base_price: 105000, ...} ─────────────┤ │
│ │ │
│ (calculates collateral %) │ │
│ │ │
├─ GET /api/quote?th=141750 ──────────────►│ │
│ ├─ triggerWorkflow() ─────────►│
│ │◄─ PriceContractResponse ─────┤
│◄─ PriceContractResponse ─────────────────┤ │
- Go 1.24+
- Docker (for Nostr relay)
- CRE CLI (Chainlink Runtime Environment)
git clone https://github.com/DUCAT-UNIT/cre-hmac
cd cre-hmac
go mod downloadgit clone https://github.com/DUCAT-UNIT/strfry-http
cd ../strfry-http
docker-compose up -d# Secp256k1 private key (32 bytes hex)
export DUCAT_PRIVATE_KEY="8ce73a2db5cbaf4b0ab3cabece9408e3b898c64474c0dbe27826c65d1180370e"
# HMAC secret (min 32 bytes)
export DUCAT_CLIENT_SECRET="your_secret_here"cd tools && go run derive_key.goAdd the output public key to strfry whitelist.
./run_all_tests.shcd tools
go build -o gateway-server gateway_server.gocd hmac
GOOS=wasip1 GOARCH=wasm go build -o main.wasmcre-hmac/
├── hmac/ # CRE WASM workflow
│ ├── main.go # Entry point, routing
│ ├── handlers.go # createQuote, evaluateQuotes, generateQuotesParallel
│ ├── crypto.go # Crypto delegation
│ ├── types.go # Data structures
│ ├── constants.go # Constants
│ └── workflow.yaml # CRE workflow definition
│
├── crypto/ # Testable crypto library
│ ├── crypto.go # Pure crypto functions
│ └── crypto_test.go # Unit tests
│
├── internal/
│ └── ethsign/ # Ethereum signing utilities
│ ├── ethsign.go # JWT generation, Ethereum message signing
│ └── ethsign_test.go # Signing and recovery tests
│
├── shared/ # Shared validation and types
│ ├── types.go # NostrEvent, etc.
│ └── validation.go # Input validation helpers
│
├── tools/ # Gateway and utilities
│ ├── gateway_server.go # HTTP gateway with SQLite cache
│ └── derive_key.go # Key derivation tool
│
├── wasmtest/ # WASM handler tests
│ └── handlers_test.go # 461 tests
│
├── integration/ # Integration tests
│ └── integration_test.go
│
├── GATEWAY_API.md # Gateway API documentation
├── DOCKER.md # Docker deployment guide
└── README.md # This file
{
"client_id": "your-client-id",
"data_stream_url": "https://api.testnet-dataengine.chain.link",
"feed_id": "0x00037da06502da...",
"relay_url": "http://localhost:8080",
"network": "mutinynet",
"cron_schedule": "0 */90 * * * *",
"rate_min": 1.35,
"rate_max": 5.00,
"step_size": 0.01,
"gateway_callback_url": "http://gateway:8080/webhook/ducat"
}| Field | Description |
|---|---|
cron_schedule |
Cron expression for batch generation (every 1.5 min) |
rate_min |
Minimum collateral rate (1.35 = 135%) |
rate_max |
Maximum collateral rate (5.00 = 500%) |
step_size |
Step between rates (0.01 = 1%) |
gateway_callback_url |
URL for CRE to notify gateway of new batches |
secretsNames:
private_key:
- DUCAT_PRIVATE_KEY
client_secret:
- DUCAT_CLIENT_SECRETSee GATEWAY_API.md for full documentation.
| Endpoint | Method | Description |
|---|---|---|
/api/price |
GET | Get cached base price |
/api/quote |
GET | Create threshold quote |
/api/evaluate |
POST | Batch evaluate quotes |
/health |
GET | Health check |
/metrics |
GET | Prometheus metrics |
All quote operations return PriceContractResponse (matches core-ts PriceContract schema):
{
"chain_network": "mutinynet",
"oracle_pubkey": "6b5008a293291c14effeb0e8b7c56a80ecb5ca7b801768e17ec93092be6c0621",
"base_price": 105000,
"base_stamp": 1734567890,
"commit_hash": "a1b2c3d4...",
"contract_id": "e5f6g7h8...",
"oracle_sig": "deadbeef...",
"thold_hash": "240c124e4a188281668b4899b6456c101c568de8",
"thold_key": null,
"thold_price": 141750
}When breached, thold_key contains the revealed secret.
Price contracts are stored as NIP-33 replaceable events (kind 30078).
| Tag | Description |
|---|---|
d |
commit_hash - NIP-33 replaceable event identifier |
# Query by commit_hash (d tag)
GET /api/query?#d=<commit_hash>The event content is the PriceContract JSON (no extra fields):
{
"chain_network": "mutinynet",
"oracle_pubkey": "6b5008...",
"base_price": 105000,
"base_stamp": 1734567890,
"commit_hash": "a1b2c3d4...",
"contract_id": "e5f6g7h8...",
"oracle_sig": "deadbeef...",
"thold_hash": "240c124e...",
"thold_key": null,
"thold_price": 141750
}- Quote generation: 366 quotes in ~600ms (parallel)
- Per contract: ~840μs crypto operations
- Contracts/second: ~1,200 (local crypto)
CRE production estimates:
- Price fetch: ~100ms
- Parallel relay publish: ~500ms
- Total batch: ~600ms
- HMAC-SHA256: Deterministic, domain-separated secret generation
- Hash160: Bitcoin-compatible commitments (RIPEMD160(SHA256))
- Schnorr signatures: BIP-340 for Nostr events
- DON consensus: Byzantine fault tolerance for price data
- Input validation: Domain, price, hash format checks
See DOCKER.md for deployment guide.
# Build
docker build -t ducat-gateway:latest .
# Run with SQLite persistence
docker run -d \
-p 8080:8080 \
-v ducat-data:/data \
-e GATEWAY_DB_PATH=/data/gateway.db \
--env-file .env \
ducat-gateway:latest# Run all tests
./run_all_tests.sh
# Or individually
go test ./crypto -v
go test ./shared -v
go test ./wasmtest -v
go test ./integration -v
# Build gateway
cd tools && go build -o gateway-server gateway_server.go