DEV Community

Zeke
Zeke

Posted on

Trustless Bug Bounty Releases with a PoW-Gated DLC Oracle

Bug bounties have a trust problem. The developer patches the bug, the PR merges, and then they wait. Maybe forever. The organization controls the escrow. The payout depends on a committee deciding the fix was complete, the vulnerability was real, and the payout tier is correct. None of that is verifiable by anyone except the people holding the keys.

DLC contracts fix this — in theory. Lock funds into a contract where an oracle signs the release condition. The oracle can't lie because the signature is public and verifiable against its key. No committee, no discretion. The moment the condition is met, the adaptor signature unlocks the output.

The missing piece was a practical oracle that maps real-world GitHub events to DLC-compatible attestations. I built one. Here's how it works and how to wire it up.

What pow-attest does

attest.powforge.dev is a Schnorr attestation oracle. You register a bounty with a GitHub condition (github_pr_merged or github_issue_closed). The oracle hands back an announcement that includes the oracle's public key, a nonce, and the outcome hash for the RELEASED state. You use those to construct a DLC contract where the counterparty's funds are locked to a CET that only spends if the oracle signs RELEASED.

The oracle polls GitHub. When the condition is met, it signs RELEASED using BIP-340 Schnorr with its private key. Anyone can verify that signature offline — just check it against the oracle_pubkey from /api/v1/info.

Registration is PoW-gated at SHA-256 difficulty 18. That's not pay-to-play — it's spam prevention. Grinding 18 leading zero bits takes a few seconds on a laptop. It's free. It's also the proof that separates bots from developers.

The oracle pubkey is permanent

2bc78390c94d8bbb96ac3e6940462ba2812418d871e701c1a845fdb1dfd4a0e5
Enter fullscreen mode Exit fullscreen mode

Every attestation the oracle ever signs is verifiable against this key. If the oracle signs something false, the key is compromised forever — there's no "take it back." That's the security model. It's the same one Bitcoin uses for pubkeys.

The attestation tag is DLC/oracle/attestation/v0. Combined with the nonce from the announcement, that's all a DLC manager needs to reconstruct the adaptor point T = R + h·P where the CET output locks.

Install the JavaScript client

npm install @powforge/attest-client
Enter fullscreen mode Exit fullscreen mode

Zero dependencies. Node 18+ or browser. Uses globalThis.crypto and globalThis.fetch. Works in Cloudflare Workers.

Register a bounty in 15 lines

const { AttestClient } = require('@powforge/attest-client');

const client = new AttestClient({ baseUrl: 'https://attest.powforge.dev' });

// Solve the PoW challenge (takes a few seconds)
const challenge = await client.getChallenge();
const nonce = await client.solveChallenge(challenge);

// Register: funds auto-release when PR #999 in owner/repo merges
const bounty = await client.registerBounty({
  condition: { type: 'github_pr_merged', repo: 'owner/repo', ref: 999 },
  pow_challenge: challenge.challenge,
  pow_nonce: nonce
});

console.log(bounty.bounty_id);        // UUID — store this
console.log(bounty.oracle_pubkey);    // 2bc78390...
console.log(bounty.announcement);     // nonce, outcome_hash, event_descriptor
Enter fullscreen mode Exit fullscreen mode

The announcement object is what you hand to your DLC counterparty. They use it to construct the contract. The oracle's nonce_pubkey and oracle_pubkey together define the adaptor point. The outcome_hash defines what string the oracle will sign when releasing.

The outcome hash is deterministic

sha256("RELEASED" + bounty_id)
Enter fullscreen mode Exit fullscreen mode

Both parties can compute this independently before any money moves. That's the property that makes trustless DLCs possible — the oracle can't change what it will sign after the contract is established.

You can verify this against the test vectors page:

curl -s https://attest.powforge.dev/test-vectors | grep -A3 "RELEASED"
Enter fullscreen mode Exit fullscreen mode

The test vectors include a fixed bounty_id with the precomputed sha256, so integrators can check their own hashing against a known answer before connecting to real funds.

Check bounty status

const status = await client.getBountyStatus(bounty.bounty_id);

if (status.state === 'RELEASED') {
  // The oracle has signed. status.attestation contains the Schnorr sig.
  // Feed it to your DLC manager to unlock the CET output.
  console.log(status.attestation.sig);    // 128-hex BIP-340 signature
  console.log(status.attestation.outcome); // "RELEASED"
}
Enter fullscreen mode Exit fullscreen mode

The oracle polls GitHub every 60 seconds. Once the PR merges, the next poll triggers the RELEASED attestation. Typical latency: under 2 minutes from merge to signed attestation.

Verify the attestation without any library

The security claim is that you can verify the oracle's sig offline with nothing but a hash function and an elliptic curve library. Here's the raw verification:

const { schnorr } = require('@noble/curves/secp256k1');
const crypto = require('crypto');

// oracle_pubkey from /api/v1/info (XOnly, 32 bytes)
const pubkey = Buffer.from('2bc78390c94d8bbb96ac3e6940462ba2812418d871e701c1a845fdb1dfd4a0e5', 'hex');

// Reconstruct the message the oracle signed:
// sha256(attestation_tag || nonce_pubkey || outcome_hash_of_outcome_string)
// In practice: use the dlcdevkit verifyAttestation() helper.
const sig = Buffer.from(status.attestation.sig, 'hex');
const msg = /* reconstruct from announcement + outcome */ ...;

const valid = schnorr.verify(sig, msg, pubkey);
// If valid === true, the oracle actually attested this outcome.
// If valid === false, the sig is fraudulent — funds are still locked.
Enter fullscreen mode Exit fullscreen mode

The point is that verification requires no trust in the oracle operator, no API call, no third party. Just the pubkey that's been public since the oracle launched.

The dead man's switch use case

The bounty oracle is one half. The other half is pow-attest's original purpose: proving you're alive.

Register a dead man's switch, check in every N hours by signing a per-window message with your Schnorr key. If you miss the deadline, the oracle signs a DEAD attestation. DLC counterparties who took the DEAD leg of the contract can unlock their output.

This is the insurance and succession-planning use case for Bitcoin. Prove you're alive, regularly, with a cryptographic signature. If you stop, the proof-of-death is automatic and verifiable by anyone.

const sw = await client.registerSwitch({
  owner_pubkey: myXOnlyPubkey,   // 64-hex
  checkin_interval_hours: 24,
  pow_challenge: challenge.challenge,
  pow_nonce: nonce
});

// Every 24h: sign the current time bucket and check in
const msg = await client.getCheckinMessage(sw.switch_id);
const sig = schnorrSign(myPrivkey, msg);
await client.checkin(sw.switch_id, sig);
Enter fullscreen mode Exit fullscreen mode

The checkin message is sha256(switch_id + bucket_timestamp) where bucket_timestamp rounds to the nearest 10 minutes. This prevents replay attacks — a valid sig in one window doesn't satisfy a different window.

Why PoW-gating matters

The registration cost (18 bits of SHA-256) means spam registration is expensive. A million fake bounties would cost more electricity than running the oracle legitimately. That's the Softwar property: PoW converts computational energy into an economic barrier that can't be argued around, bribed around, or committee-decided around.

It also means the oracle has no financial relationship with the registrant. No API key, no account, no invoice. Prove you burned CPU, get a bounty registered. The oracle doesn't know who you are and doesn't need to.

Where this goes next

The oracle currently uses JSON format. I'm building toward DLC TLV compatibility — the full OracleAnnouncement and OracleAttestation wire format that dlcdevkit and other Rust DLC frameworks consume natively. Once that lands, @powforge/attest-client becomes a thin shim over a fully interoperable DLC oracle that any conformant framework can use without modifications.

Until then, the Rust shim is small. The GitHub DLC oracle trait needs three methods: get_public_key(), get_announcement(event_id), get_attestation(event_id). The JSON-to-struct conversion is ~20 lines.

If you're building on dlcdevkit and want to wire in a GitHub-condition oracle, the issue is open.


Source: https://github.com/zekebuilds-lab/attest-client (GitHub mirror)
Oracle: https://attest.powforge.dev
npm: @powforge/attest-client

Top comments (0)