Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 0 additions & 2 deletions .github/workflows/groundwire-verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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+='}'

Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ A landing page at `http://<ship-url>/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

Expand Down Expand Up @@ -181,11 +182,13 @@ pass:<hex>
sig:<hex>
ecash-pubkey:<hex>
ecash-amount:100
ecash-tokens:[{"amount":64,"id":"...","secret":"...","C":"..."},...]
ecash-ciphertext:<hex>
ecash-ephemeral-pubkey:<hex>
ecash-mac:<hex>
-----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

Expand Down
58 changes: 47 additions & 11 deletions desk/app/vitriol.hoon
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1601,25 +1603,59 @@
:_ 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
0
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 @)
%pending
''
==
=. 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 --
Expand Down
Loading