fix(kez-chat/web): signClaim was producing envelopes without primary/key
AddClaim.svelte passed session.unlocked (an UnlockedIdentity, shape
{handle, server, primary, seed}) to signClaim, which expects an
Ed25519Identity ({seed, publicKey, identity}). Different fields:
session.unlocked.identity is undefined.
Result: payload.primary was undefined → JCS omits it → signature was
valid over a payload-without-primary, and signature.key was also
undefined. Verifiers correctly rejected these envelopes — and the
markdown header read "Primary: undefined".
Fix:
- AddClaim: derive a real Ed25519Identity via identityFromSeed(session.
unlocked.seed) before calling signClaim. The seed is the canonical
source of truth — publicKey + identity are derived deterministically.
- signWith: throw if signer.identity is missing or seed is malformed.
Belt-and-suspenders so a future caller passing the wrong shape gets
a loud error instead of producing silently unverifiable envelopes.
Note: any claims signed before this fix have invalid signatures and
must be re-created. Remove them on the Claims page and re-add.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
41f66ae366
commit
cd8dda681c
@ -116,6 +116,18 @@ function signWith(
|
|||||||
payload: unknown,
|
payload: unknown,
|
||||||
signer: Ed25519Identity,
|
signer: Ed25519Identity,
|
||||||
): SignatureBlock {
|
): SignatureBlock {
|
||||||
|
// Defensive: if a caller (Svelte file with loose checks, etc.) passes
|
||||||
|
// something missing .identity, the envelope would be silently
|
||||||
|
// unverifiable because both signature.key and payload.primary would
|
||||||
|
// be undefined. Fail loudly instead.
|
||||||
|
if (!signer || typeof signer.identity !== "string") {
|
||||||
|
throw new Error(
|
||||||
|
"signWith: signer.identity missing — pass an Ed25519Identity (use identityFromSeed)",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!(signer.seed instanceof Uint8Array) || signer.seed.length !== 32) {
|
||||||
|
throw new Error("signWith: signer.seed must be a 32-byte Uint8Array");
|
||||||
|
}
|
||||||
const jcs = canonicalBytes(payload);
|
const jcs = canonicalBytes(payload);
|
||||||
const sig = ed25519.sign(jcs, signer.seed);
|
const sig = ed25519.sign(jcs, signer.seed);
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
import { push } from "svelte-spa-router";
|
import { push } from "svelte-spa-router";
|
||||||
import {
|
import {
|
||||||
signClaim,
|
signClaim,
|
||||||
|
identityFromSeed,
|
||||||
toPrettyJson,
|
toPrettyJson,
|
||||||
toMarkdown,
|
toMarkdown,
|
||||||
toCompact,
|
toCompact,
|
||||||
@ -166,7 +167,12 @@
|
|||||||
alert("Identifier looks empty — please fill in the field.");
|
alert("Identifier looks empty — please fill in the field.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
envelope = signClaim(session.unlocked, subject);
|
// UnlockedIdentity has {handle, server, primary, seed} — not the
|
||||||
|
// Ed25519Identity {seed, publicKey, identity} shape signClaim wants.
|
||||||
|
// Reconstruct from the seed so signer.identity is defined and
|
||||||
|
// ends up in payload.primary + signature.key.
|
||||||
|
const signer = identityFromSeed(session.unlocked.seed);
|
||||||
|
envelope = signClaim(signer, subject);
|
||||||
step = "publish";
|
step = "publish";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user