fix(marmot-interop): decode npub locally so member-list checks use hex

`wn users show <npub>` only resolves identities the local daemon has
cached, so A's npub (the Amethyst account, which B's daemon has never
interacted with directly) falls through to the "return input unchanged"
branch. That leaks the raw npub into the Test 02 member-list assertion,
where it's compared against hex pubkeys pulled from `wn groups members`
and fails with "member list missing npub1...".

Add a pure-awk bech32 decoder and use it as the final fallback in
`npub_to_hex`, so unknown npubs still convert to the 32-byte hex form
the daemon emits. Also harden the JSON path to peel a `.result` wrapper
in case a future `wn --json users show` ships one.
This commit is contained in:
Claude
2026-04-22 18:01:05 +00:00
parent e452cf8abd
commit 13741be695
+53 -6
View File
@@ -293,13 +293,56 @@ extract_pubkey() {
if [[ -n "$v" ]]; then printf '%s' "$v"; return; fi
}
# Best-effort npub -> hex conversion via `wn users show`. If the lookup fails
# (e.g. --json not supported or user not resolvable), returns the input
# unchanged — downstream comparisons tolerate either form.
# Pure-awk bech32 decoder for npub1... → 32-byte lowercase hex. Needed because
# `wn users show` only resolves identities the local daemon already knows
# about, so A's npub (the Amethyst account) can't be converted via wn — and a
# failed conversion leaks the raw npub into hex-equality checks like the
# member-list assertion in Test 02, which then fails with
# "member list missing npub1…".
bech32_decode_npub() {
local npub="$1"
[[ "$npub" == npub1* ]] || return 1
local body="${npub#npub1}"
# Need at least 6 chars of checksum + some data.
(( ${#body} > 6 )) || return 1
local hex
hex=$(awk -v data="$body" 'BEGIN {
charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
for (i = 0; i < 32; i++) map[substr(charset, i + 1, 1)] = i
data = substr(data, 1, length(data) - 6)
acc = 0; bits = 0; out = ""
for (i = 1; i <= length(data); i++) {
c = substr(data, i, 1)
if (!(c in map)) exit 1
acc = acc * 32 + map[c]
bits += 5
while (bits >= 8) {
bits -= 8
pow = 1
for (j = 0; j < bits; j++) pow *= 2
byte = int(acc / pow)
acc -= byte * pow
out = out sprintf("%02x", byte)
}
}
print out
}' 2>/dev/null) || return 1
# 32-byte x-only pubkey = 64 hex chars.
[[ ${#hex} -eq 64 ]] || return 1
printf '%s' "$hex"
}
# npub -> hex conversion. Tries `wn users show` first (handy when it works,
# since it also validates the npub against the daemon's view), then falls
# back to a local bech32 decode so unknown npubs still convert. Returns the
# input unchanged only if both paths fail, which should never happen for a
# well-formed npub.
npub_to_hex() {
local npub="$1" raw hex
raw=$(wn_b --json users show "$npub" 2>/dev/null || true)
hex=$(printf '%s' "$raw" | jq -r '.pubkey // .public_key // .hex // empty' 2>/dev/null || true)
hex=$(printf '%s' "$raw" \
| jq -r '(.result // .) | (.pubkey // .public_key // .hex // .[0]?.pubkey // .[0]?.public_key // empty)' \
2>/dev/null || true)
if [[ -z "$hex" || "$hex" == "null" ]]; then
# fallback to yaml-ish output
raw=$(wn_b users show "$npub" 2>/dev/null || true)
@@ -307,9 +350,13 @@ npub_to_hex() {
fi
if [[ -n "$hex" && "$hex" != "null" ]]; then
printf '%s' "$hex"
else
printf '%s' "$npub"
return
fi
if hex=$(bech32_decode_npub "$npub"); then
printf '%s' "$hex"
return
fi
printf '%s' "$npub"
}
# ------- state file ----------------------------------------------------------