diff --git a/qip-0016.md b/qip-0016.md new file mode 100644 index 0000000..0525271 --- /dev/null +++ b/qip-0016.md @@ -0,0 +1,381 @@ +# QIP-16 — SOAP: Subsidy Mining via **Traditional AuxPoW** (no LTC↔DOGE merge mining) + +**Status:** Draft +**Authors:** +**Last updated:** 2025-09-15 +**Scope:** Quai mainnet/testnets +**Audience:** core node implementers, pool/template operators + +--- + +## 0. Abstract + +SOAP (“**S**ubsidized **O**pen-market **A**cquisition **P**rogram”) imports external Proof-of-Work from **Bitcoin-family donors** (BTC, LTC, DOGE, RVN) to fund a **protocol coinbase payout (QADDR)** and to **commit** a Quai WorkObject reference inside donor blocks. Quai will switch from using Progpow to **KAWPOW** which is a slight modification from Progpow and will produce blocks with **KAWPOW** only. **Auxiliary shares** from other algos (SHA256d, Scrypt, …) may be *included* in Quai blocks for a QUAI payout, governed by a **separate DAA** targeting **3 included shares per block** with a hard cap of **9**. + +Donor participation is verified on Quai using a **traditional AuxPoW-style proof**: donor **header (80B)** + **coinbase tx** + **merkle branch**. We do **not** rely on LTC↔DOGE merged mining; DOGE and LTC are mined independently. + +--- + +## 1. Goals + +- Do not allow any non-KAWPOW algorithm to author Quai blocks. +- Do not require miners to run donor chain state. +- Do not depend on pool code changes beyond standard Stratum v1 job fields. + +--- + +## 2. Terminology + +- **WorkObjectHeader (WOHeader):** The unit Quai miners build and commit to in donor blocks. +- **QADDR:** Protocol-specified donor payout scriptPubKey; 100% of donor subsidy+fees must pay QADDR. +- **SOAP push:** A fixed push of `"SOAP" 0x01 || 32B WO_HASH` placed in the **coinbase input scriptSig**. +- **AuxPow (in this spec):** A donor proof object included in a Quai block: `{header, coinbase, merkle branch, index, chain_id, height}`. + +--- + +## 3. On-chain objects (Quai) + +### 3.1 WorkObjectHeader (wire) + +```go +type WorkObjectHeader struct { + headerHash common.Hash + parentHash common.Hash + number *big.Int + difficulty *big.Int + primeTerminusNumber *big.Int + txHash common.Hash + primaryCoinbase common.Address + location common.Location + mixHash common.Hash + time uint64 + nonce BlockNonce + data []byte + lock uint8 + auxpow *AuxPow // optional; donor proof (this spec) +} + +``` + +### 3.2 AuxPow (donor proof) + +> “Traditional AuxPoW structure” = SPV-style: donor header + coinbase + merkle path. No LTC→DOGE merge minining. + +```go +type PowID uint16 // 0=ProgPow, 1=KawPow, 2=DOGE, 3=BTC/LTC(extensible) + +type AuxPow struct { + powID PowID // PoW algorithm identifier + header []byte // 120B donor header for KAWPOW + signature []byte // Signature proving the work + merkleBranch [][]byte // siblings for coinbase index=0 up to root (little endian 32-byte hashes) + coinbase *wire.MsgTx // Full coinbase transaction (contains value in TxOut[0]) + prevHash []byte // Previous block hash reference + signatureTime uint64 // Timestamp when signature was created +} + +``` + +## coinbase will have a scriptsig commitment +```go +QAddrScript []byte // exact scriptPubKey bytes required for output[0] +// The commitment format we expect in coinbase scriptSig: +// PUSHDATA(height) PUSHDATA("SOAP" 0x01 || 32B WO_HASH) PUSHDATA(extranonce1) PUSHDATA(extranonce2) +``` + +--- + +## 4. Donor coinbase **consensus requirements** + +For a donor block to count: + +1. **Payout rule (MUST):** The coinbase **first spendable output** MUST pay **100% subsidy + fees** to **QADDR** (`QAddrScript` exact bytes). + - (Optional network param: allow additional zero-value OP_RETURN outputs.) +2. **Commitment rule (MUST):** The coinbase **input scriptSig** MUST include a fixed push: + ``` + PUSHDATA("SOAP" 0x01 || 32B WO_HASH) + ``` + placed after the BIP34 **height** push, before or after pool extranonces. + ScriptSig total size MUST be **2..100 bytes** (Bitcoin-family consensus). +3. **Header inclusion (MUST):** The AuxPow proof MUST show `txid(coinbase)` included in `Header.merkleRoot` via `Branch, Index`. +4. **(Optional) Donor PoW check:** Nodes MAY verify donor header PoW under that chain’s rules (policy toggle). +5. **Freshness (RECOMMENDED):** The donor `Height` SHOULD be within **Δ blocks** of a committee-advertised tip (policy; not consensus). This is also acieved by the signature time and prevhash committed in the AuxPow. + +> **Rationale:** scriptSig is universally available, keeps miners stock, and avoids OP_RETURN size/policy variability across fleets. + +--- + +## 5. Quai consensus changes + +1. **Block PoW algorithm:** **KAWPOW** ONLY for block authoring. +2. **AuxPow binding (MUST):** If `auxpow != nil`, `woHeader.auxCommit = H("QUAI:AUXv1" || serialize(auxpow))` and `auxCommit` MUST be included in the Quai block header preimage. +3. **AuxPow effect (policy → can be elevated later):** + - **Soft mode (RECOMMENDED initial):** Valid AuxPow adds reward/weight bonus (see §7). No liveness coupling. + - **Epoch mode (optional future):** Require ≥1 valid AuxPow per **E** Quai blocks to avoid reward decay. +4. **Shares (MUST):** Non-KAWPOW algos **cannot** produce Quai blocks; they produce **shares** only (§6). +5. **Transition Period** where both progpow and kawpow blocks are accepted, but progpow rewards will slowly be phased out. + +--- + +## 6. WorkShares (using existing WorkObject) + +### 6.1 Classification & validity + +A **WorkObjectHeader** `woHeader` is classified at validation time as: + +- **Block:** if the KAWPOW seal on `woHeader` meets the current block target. +- **WorkShare:** if **not Block**, and `wo.auxpow` passes **VerifyAuxPow(wo)** and the donor header PoW satisfies the per‑chain **share target** `T_share[wo.auxpow.Chain]`. +- **Invalid:** otherwise. + +**Tip binding / freshness:** Because `woHeader.parentHash` binds the object to the current Quai tip, separate tip-commit fields are unnecessary. Nodes SHOULD reject WorkShares older than **TTL = 2** Quai blocks (policy). Additional checks based on the signature time is also imposed. + +### 6.2 Scoring & normalization + +Two options are supported; implementers SHOULD start with **Equal Weight** for simplicity. + +- **Equal Weight (default):** Every valid WorkShare counts as `w_norm = 1`. +- **Difficulty‑proportional (optional):** Let `D_share[chain]` be the difficulty implied by `T_share[chain]`, and `D_sol` be the difficulty implied by the donor solution’s PoW (stateless from `Header`). Score + `w_norm = min(D_sol / D_share[chain], W_max)`. + +### 6.3 Inclusion & ordering + +- Producers MUST include up to **S_max = 9** highest‑`w_norm` WorkShares. +- Tie‑break by arrival time. +- MAY enforce a per‑chain soft cap (e.g., ≤ 3) to avoid dominance. + +### 6.4 Rewards (QUAI-only) + +- **Each included WorkShare:** pays its submitter under Equal‑Weight, or proportionally to `w_norm` under Difficulty‑proportional. +- **Payout address:** the QUAI address is taken from `wo.woHeader.primaryCoinbase` (no extra fields required). + +### 6.5 Adjustment of rewards for the additional chains + +- If more than expected shares are found for additional chains, the reward is scaled down relative to the average expectation, indirectly creating an incentive for the miners to adjust hash rate + + + +--- + +## 7. Reward & verification pseudocode + +The only layout rule now is: +**SOAP must appear immediately after the BIP34 height push and entirely in the fixed (non-extranonce) part of the coinbase scriptSig.** + +--- + +# 7. Verification + +This defines **consensus** checks when AuxPoW requires a **quorum-signed AuxTemplate** and strict **correspondence** between that template, the donor **header**, and the **coinbase**. SOAP placement is minimal: **height push → SOAP push → (extranonce pushes, if any).** + +## 7.1 Data shapes + +```go +type AuxTemplate struct { + // Enforced correspondence + ChainID ChainID // must match ap.Chain + PrevHash [32]byte // must equal donor_header.hashPrevBlock + PayoutScript []byte // must equal coinbase.outputs[0].scriptPubKey + ScriptSigMaxLen uint16 // ≤100; template may be tighter + + // Advisory (policy/UX; NOT consensus unless elevated) + Extranonce2Size uint8 + NBits uint32 + NTimeMask NTimeMask + + // Quorum signatures over CanonicalEncode(AuxTemplate) WITHOUT Sigs + Sigs []SignerEnvelope // (SignerID, Signature) +} +``` + +> **Signatures:** sign `SHA256d("QUAI:SOAP:AUXTEMPLATEv1" || CanonicalEncode(tmpl_without_Sigs))`. +> No embedded template hash field; nodes recompute the digest for signature checks. + +## 7.2 Top-level verification + +```go +func VerifyWorkObjectAuxPow_WithAuxTemplate(wo *WorkObject, tmpl *AuxTemplate, params Params) error { + ap := wo.auxpow + if ap == nil { return ErrNoAuxPow } + + // (1) SPV: coinbase ∈ donor header’s Merkle root + if !VerifyMerkle(ap.Coinbase, ap.Branch, ap.Index, HeaderMerkleRoot(ap.Header)) { + return ErrBadAuxMerkle + } + + // (2) SOAP: scriptSig must be [BIP34 height][SOAP("SOAP\x01||WO_HASH")][... extranonce pushes ...] + cb := ParseTx(ap.Coinbase) + if !HasSoapImmediatelyAfterHeight(cb.ScriptSig, WOHash(wo)) { + return ErrMissingOrMisplacedSOAP + } + + // (3) Quorum signatures over canonical AuxTemplate (no embedded hash) + digest := AuxTemplateDigest(*tmpl) + if !VerifyQuorumSigs(tmpl.Sigs, digest, params.ActiveSignerSet, params.QuorumThreshold) { + return ErrBadQuorum + } + + // (4) Correspondence: template ↔ donor header & coinbase + if tmpl.ChainID != ap.Chain { return ErrChainMismatch } + + donorPrev := DonorHeaderPrevHash(ap.Header) // hashPrevBlock from 80B header + if !bytes.Equal(tmpl.PrevHash[:], donorPrev[:]) { + return ErrPrevhashMismatch + } + + if len(cb.Outputs) == 0 || !EqualScript(cb.Outputs[0].Script, tmpl.PayoutScript) { + return ErrWrongPayoutScript + } + + // (5) Coinbase scriptSig bound (≤100 consensus; template may be tighter) + if ScriptSigLen(cb) > min(uint16(100), tmpl.ScriptSigMaxLen) { + return ErrScriptSigTooLong + } + + // (Optional policy — NOT consensus unless elevated) + // if params.EnforceShareTarget && !VerifyDonorPoW(ap.Chain, ap.Header, params.Tshare[ap.Chain]) { + // return ErrBelowShareTarget + // } + + return nil +} +``` + +## 7.3 Helpers + +```go +func AuxTemplateDigest(t AuxTemplate) [32]byte { + enc := CanonicalEncode_WithoutSigs(t) + return SHA256d( append([]byte("QUAI:SOAP:AUXTEMPLATEv1"), enc...) ) +} + +func VerifyMerkle(coinbase []byte, branch [][]byte, index uint32, root [32]byte) bool { + h := DblSHA256(coinbase) + for i, sib := range branch { + if (index>>uint(i))&1 == 0 { + h = DblSHA256( append(h[:], sib...) ) + } else { + h = DblSHA256( append(sib, h[:]...) ) + } + } + return bytes.Equal(h[:], root[:]) +} + +// Minimal placement rule: SOAP push must be the SECOND push (right after BIP34 height) +func HasSoapImmediatelyAfterHeight(scriptSig []byte, expectWO [32]byte) bool { + pushes := ParseScriptSigPushes(scriptSig) // returns the sequence of PUSHDATA payloads + if len(pushes) < 2 { return false } + if !IsBIP34HeightPush(pushes[0]) { return false } + return IsSoapPush(pushes[1], expectWO) +} + +// A SOAP push is exactly "SOAP\x01" || 32-byte WO hash (37 bytes total) +func IsSoapPush(b []byte, expectWO [32]byte) bool { + if len(b) != 37 { return false } + if !bytes.Equal(b[0:5], []byte{'S','O','A','P',0x01}) { return false } + return bytes.Equal(b[5:], expectWO[:]) +} +``` + +## 7.4 Notes + +* **Consensus requires:** SPV inclusion, SOAP presence/placement, **quorum-signed AuxTemplate**, and correspondence (`ChainID`, `PrevHash == hashPrevBlock`, `outputs[0] == PayoutScript`, `ScriptSigLen ≤ bound`). +* **No freshness rule** at consensus; a stale template won’t match current `hashPrevBlock` anyway. + + +## 8. Donor **coinbase construction** (template/pool) + +**ScriptSig (no LTC↔DOGE AuxPoW header in this spec):** +``` +PUSHDATA(BIP34_height) +PUSHDATA("SOAP" 0x01 || 32B WO_HASH) // fixed by server; miners do NOT change +PUSHDATA(extranonce1) // server +PUSHDATA(extranonce2) // miner, size typically 4–8 bytes +``` +- **Keep total scriptSig ≤ 100 bytes.** +- Leave **witness-reserved 32B** (BTC/LTC segwit) untouched (it lives in the coinbase *witness*, not scriptSig). + +**Outputs:** +1) `value = subsidy+fees` → **QADDR** (exact `QAddrScript`). +2) *(Optional)* zero-value **OP_RETURN** for an audit breadcrumb; not required by consensus. + +**Stratum v1 job fields (unchanged):** +- `coinb1` ends after SOAP push and before miner’s `extranonce2`. +- `coinb2` starts at `extranonce2` placeholder and contains the rest of scriptSig + outputs. +- `extranonce1`, `extranonce2_size` (keep 4–8 for ASIC compatibility), `merkle_branch[]`, `prevhash`, `version`, `nbits`, `ntime`. + +**Miner behavior:** builds coinbase (`coinb1||ex1||ex2||coinb2`), computes txid, folds `merkle_branch` to root, hashes header nonce/time. + +--- +## 9. Parameters + +## 10. Security & liveness + +- **No liveness coupling:** AuxPow is optional (soft mode). Blocks can be mined with zero shares included. +- **ScriptSig bounds:** enforce 2..100-byte consensus window; reject oversize coinbases. +- **DoS control:** bound AuxPow size (~1–2 KB typical), share size (≤~300B), per-block share cap (`S_max`). +- **Replay safety:** WO hash includes a **domain tag**; optionally add an 8-byte Quai tip prefix inside the SOAP push if desired. + +--- + +## 11. Compatibility notes + +- **BTC/LTC:** SegWit witness commitment uses the coinbase **witness reserved value**, not scriptSig; unaffected. +- **DOGE (no LTC merge):** Treat like LTC: its own coinbase scriptSig/outputs; same SOAP push fits. +- **RVN (KAWPOW):** No SegWit; scriptSig ≤100 still applies. KAWPOW-specific fields are irrelevant for donor proof. + +--- + +## 12. Deployment plan + +1. **Phase A:** Ship sharepool, DAA, QUAI payouts; accept AuxPow in soft mode (bonus only). +2. **Phase B:** Provide public donor **template relays** (BTC/LTC/DOGE/RVN) that fix QADDR + SOAP push; miners can point rigs with zero firmware changes. +3. **Phase C (optional):** Make AuxPow participation economically stronger (higher bonus) or add epoch requirement with decay. + +--- + +## 13. Test guidance (devnets/testnets) + +- Stand up a donor regtest/testnet node; generate coinbase-only templates with SOAP push; validate AuxPow in Quai. +- Ensure scriptSig length stays under 100 **with** height + SOAP + ex1 + ex2. +- Verify that modifying only `extranonce2` on the miner recomputes Merkle root correctly via the provided `merkle_branch`. + +--- + +## 14. Reference encodings + +**SOAP scriptSig push (hex):** +``` +25 53 4f 41 50 01 <32-byte WO_HASH> +^^ len=37 "SOAP" 01 +``` + +**OP_RETURN audit (optional)** +``` +6a 25 53 4f 41 50 01 <32-byte WO_HASH> +OP_RETURN len=37 "SOAP" 01 +``` +*(Do not rely on OP_RETURN for consensus in this draft; SOAP-in-scriptSig is normative.)* + +--- + +## 15. Open questions / future work + +- Whether to elevate donor header PoW verification from policy → consensus. +- Optional inclusion of an 8-byte **Quai tip prefix** in SOAP push for tighter replay binding. +- Extending AuxPow to support compact proofs (NiPoPoW/FlyClient) if donor policy limits coinbase sizes. + +--- + +## 16. Appendix — Minimal verifier sketch + +```go +func VerifyAuxPowStrict(ap *AuxPow, expectWO Hash, expectQ Script) error { + if !VerifyMerkle(ap.Coinbase, ap.Branch, ap.Index, HeaderMerkleRoot(ap.Header)) { + return ErrBadAuxMerkle + } + cb := ParseTx(ap.Coinbase) + if !PaysExactly(cb, expectQ) { return ErrWrongPayout } + if !HasSoapScriptSig(cb, expectWO) { return ErrMissingSOAP } + return nil +} +```