diff --git a/.github/workflows/groundwire-verify.yml b/.github/workflows/groundwire-verify.yml index 980b98e..b87e102 100644 --- a/.github/workflows/groundwire-verify.yml +++ b/.github/workflows/groundwire-verify.yml @@ -86,7 +86,6 @@ jobs: ECASH_EPH_PUBKEY=$(echo "$SIG_BLOCK" | grep '^ecash-ephemeral-pubkey:' | cut -d: -f2) ECASH_TOKENS=$(echo "$SIG_BLOCK" | grep '^ecash-tokens:' | cut -d: -f2-) ECASH_AMOUNT=$(echo "$SIG_BLOCK" | grep '^ecash-amount:' | cut -d: -f2) - MINT_URL=$(echo "$SIG_BLOCK" | grep '^mint:' | cut -d: -f2-) # --- Send to CI's Groundwire ship for full verification --- # The ship checks: @@ -105,7 +104,6 @@ jobs: fi [ -n "$ECASH_AMOUNT" ] && { VERIFY_ARGS+=(--argjson ecash_amount "$ECASH_AMOUNT"); VERIFY_EXPR+=', ecash_amount: $ecash_amount'; } - [ -n "$MINT_URL" ] && { VERIFY_ARGS+=(--arg mint "$MINT_URL"); VERIFY_EXPR+=', mint: $mint'; } VERIFY_EXPR+='}' diff --git a/README.md b/README.md index ab4f554..1bad658 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,7 @@ A landing page at `http:///vitriol` describes the app and lists all en | POST | `/vitriol/admin/set-price` | Set sats-per-PR price | | POST | `/vitriol/admin/ban` | Ban a ship (form) | | POST | `/vitriol/admin/unban` | Unban a ship (form) | +| POST | `/vitriol/admin/withdraw` | Withdraw tokens to Lightning (mint + invoice) | ## Contributor setup @@ -181,11 +182,13 @@ pass: sig: ecash-pubkey: ecash-amount:100 -ecash-tokens:[{"amount":64,"id":"...","secret":"...","C":"..."},...] +ecash-ciphertext: +ecash-ephemeral-pubkey: +ecash-mac: -----END GROUNDWIRE SIGNATURE----- ``` -The `ecash-*` fields are only present when a maintainer price is configured and the committer has tokens. +The `ecash-*` fields are only present when a maintainer price is configured and the committer has tokens. Tokens are encrypted with the maintainer's Curve25519 pubkey — the ciphertext contains both the mint URL and the token proofs. ## Maintainer setup diff --git a/desk/app/vitriol.hoon b/desk/app/vitriol.hoon index 7e281e7..e6a4c23 100644 --- a/desk/app/vitriol.hoon +++ b/desk/app/vitriol.hoon @@ -176,9 +176,12 @@ :: :: Authenticated encryption via Curve25519 ECDH. :: -:: Encrypt: ephemeral keypair → DH shared secret → derive enc_key -:: and mac_key → XOR plaintext with counter-mode keystream → -:: HMAC-SHA256 the ciphertext → return [eph-pub ciphertext mac]. +:: Encrypt: ephemeral keypair (from eny) → DH shared secret → +:: derive enc_key = SHA-256(shared || 'encrypt') and +:: mac_key = SHA-256(shared || 'authenticate') → +:: XOR plaintext with counter-mode keystream from enc_key → +:: MAC = SHA-256(mac_key || ciphertext) → +:: return [eph-pub ciphertext mac]. :: :: Decrypt: DH shared secret → derive same keys → verify MAC → :: XOR to recover plaintext. Returns ~ on MAC failure. @@ -1569,9 +1572,8 @@ :: TTL expired — swap these tokens at the mint for fresh ones =/ flight-mint=@t mint.u.flight ?: =('' flight-mint) - :: no mint URL — just return tokens to wallet as-is - =/ existing=(list cashu-proof) (~(gut by wallet) flight-mint ~) - =. wallet (~(put by wallet) flight-mint (weld existing proofs.u.flight)) + :: no mint URL — can't swap, discard (shouldn't happen) + ~& >>> %in-flight-no-mint-url =. in-flight (~(del by in-flight) fid) `this :: get keyset id from first token @@ -1601,7 +1603,7 @@ :_ this :~ [%pass /iris/verify-keys/[fid] %arvo %i %request [%'GET' keys-url ~ ~] *outbound-config:iris] == - :: have keys — build swap directly + :: have keys — build swap directly, skip key fetch =. pending-verifies %+ ~(put by pending-verifies) fid :* our.bowl @@ -1609,7 +1611,7 @@ flight-mint proofs.u.flight (roll proofs.u.flight |=([p=cashu-proof a=@ud] (add a amount.p))) - %fetch-keys + %swap keyset-id *(list @t) *(list @) @@ -1617,9 +1619,43 @@ '' == =. in-flight (~(del by in-flight) fid) - =/ keys-url=@t (crip ;:(weld mint-clean "/v1/keys/" (trip keyset-id))) - :_ this - :~ [%pass /iris/verify-keys/[fid] %arvo %i %request [%'GET' keys-url ~ ~] *outbound-config:iris] + :: build swap request inline using cached keys + =/ amounts=(list @ud) (turn proofs.u.flight |=(p=cashu-proof amount.p)) + =/ idx=@ud 0 + =/ secrets=(list @t) ~ + =/ bfactors=(list @) ~ + =/ outputs=(list [amount=@ud id=@t b-hex=@t]) ~ + |- ^- (quip card _this) + ?: (gte idx (lent amounts)) + =/ inputs-json=json + :- %a + %+ turn proofs.u.flight + |= p=cashu-proof + %- pairs:enjs:format + :~ ['amount' (numb:enjs:format amount.p)] + ['id' s+id.p] + ['secret' s+secret.p] + ['C' s+c.p] + == + =/ swap-req=json (build-swap-request:ca inputs-json (flop outputs)) + =/ swap-body=@t (en:json:html swap-req) + =/ swap-octs=octs [(met 3 swap-body) swap-body] + =/ swap-url=@t (crip (weld mint-clean "/v1/swap")) + =/ pv-entry (~(got by pending-verifies) fid) + =. pending-verifies + %+ ~(put by pending-verifies) fid + pv-entry(step %swap, secrets (flop secrets), blinding-factors (flop bfactors)) + :_ this + :~ [%pass /iris/verify-swap/[fid] %arvo %i %request [%'POST' swap-url ~[['content-type' 'application/json']] `swap-octs] *outbound-config:iris] + == + =/ amt=@ud (snag idx amounts) + =/ eny-seed=@ (sham [eny.bowl fid idx now.bowl]) + =/ [b-hex=@t secret=@t blinding-factor=@] (make-output:ca amt keyset-id eny-seed) + %= $ + idx +(idx) + secrets [secret secrets] + bfactors [blinding-factor bfactors] + outputs [[amt keyset-id b-hex] outputs] == :: :: -- Verify flow: fetch keyset keys for swap --