From 87a7317df35aee9dc681fb8c444693fe4203bffd Mon Sep 17 00:00:00 2001 From: jwinterm Date: Sat, 1 Aug 2026 08:14:28 -0400 Subject: [PATCH] docs(verify): document the grin tx_ref output-commitment contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A grin kernel excess is also a 66-hex Pedersen point, so validate_hex(66) cannot distinguish it from an output commitment; a kernel excess never matches a rewind scan and yields a silent not_found. A send slate also carries the sender's change output. tx_ref MUST be the recipient's received (non-change) output commitment — document the footgun so the payer/frontend sets it correctly. --- src/api/verify.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/api/verify.rs b/src/api/verify.rs index beb0623..e7fdcef 100644 --- a/src/api/verify.rs +++ b/src/api/verify.rs @@ -333,6 +333,15 @@ async fn verify_cryptonote( /// `expected_sender` is unenforceable. `dest_address` is meaningless (no addresses) /// and is ignored; recipient binding IS the rewind match. Any wallet/node outage is /// a `NodeError` (non-2xx, retryable), never a false negative. +/// +/// PAYER CONTRACT (footgun): `tx_ref` MUST be the recipient's received output +/// commitment (0x08/0x09-prefixed, 33 bytes). A grin KERNEL excess is ALSO a 66-hex +/// Pedersen point, so `validate_hex(66)` cannot tell them apart — but a kernel excess +/// never appears in a rewind scan, so submitting the `kernel_excess_hex` a wallet +/// conveniently surfaces yields a silent `not_found`. Likewise a send slate carries +/// both the recipient output AND the sender's CHANGE output; only the recipient's +/// commitment is recognized by the recipient's rewind_hash. The payer/frontend must +/// select the non-change received output, not the kernel excess or the change. async fn verify_grin( state: &AppState, coin: &str,