forked from Keelrleek/SoulStream
a room-key proposal signs the hash the verifier checks, and the ping's pill shortens a raw id
This commit is contained in:
1 parent
6f7452c803
commit
8093adc89e
3 files changed
+40
-2
No files matched your search
@@ -311,6 +311,32 @@ pub fn validate_asset(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// `sign_local` has to be the exact inverse of `verify_sig_in`, and that
|
||||||
|
/// includes WHAT is signed: every family here signs blake3(body), and the
|
||||||
|
/// verifier hashes before it checks. Signing the raw body produces a
|
||||||
|
/// signature that validates nowhere — the board rejected a room-key
|
||||||
|
/// proposal for exactly this on 2026-09-03, and only a second instance
|
||||||
|
/// on the live network surfaced it.
|
||||||
|
#[test]
|
||||||
|
fn sign_local_is_the_inverse_of_verify_and_the_payload_is_hashed() {
|
||||||
|
const D: &str = "soulstream.playlist/v1";
|
||||||
|
let secret = [3u8; 32];
|
||||||
|
let body = b"a proposal body";
|
||||||
|
let payload = blake3::hash(body).as_bytes().to_vec();
|
||||||
|
let sig = sign_local(D, &secret, &payload);
|
||||||
|
assert!(verify_sig_in(D, &public_of(&secret), &payload, &sig));
|
||||||
|
assert!(
|
||||||
|
!verify_sig_in(D, &public_of(&secret), body, &sig),
|
||||||
|
"the raw body must not validate — callers hash first"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!verify_sig_in("soulstream.friend/v1", &public_of(&secret), &payload, &sig),
|
||||||
|
"a signature must not cross domains"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use ed25519_dalek::{Signer, SigningKey};
|
use ed25519_dalek::{Signer, SigningKey};
|
||||||
|
|
||||||
|
|||||||
@@ -523,7 +523,12 @@ pub async fn propose(app: &tauri::AppHandle, space_owner_b58: &str, space_b58: &
|
|||||||
};
|
};
|
||||||
pr::apply_proposal(&manifest, &hash, &p).map_err(|e| e.to_string())?;
|
pr::apply_proposal(&manifest, &hash, &p).map_err(|e| e.to_string())?;
|
||||||
p.sig = match room {
|
p.sig = match room {
|
||||||
Some(secret) => persona_state::sign_local(pr::SIGN_DOMAIN, &secret, &pr::proposal_sig_body(&p)),
|
// sign_payload() first: everything here signs blake3(body), and the
|
||||||
|
// verifier hashes before it checks. Handing over the raw body makes a
|
||||||
|
// signature that validates nowhere.
|
||||||
|
Some(secret) => {
|
||||||
|
persona_state::sign_local(pr::SIGN_DOMAIN, &secret, &pr::sign_payload(&pr::proposal_sig_body(&p)))
|
||||||
|
}
|
||||||
None => sign(app, pr::sign_payload(&pr::proposal_sig_body(&p))).await?,
|
None => sign(app, pr::sign_payload(&pr::proposal_sig_body(&p))).await?,
|
||||||
};
|
};
|
||||||
if !pr::proposal_valid(&p) {
|
if !pr::proposal_valid(&p) {
|
||||||
|
|||||||
@@ -7,6 +7,13 @@
|
|||||||
import Icon from "./Icon.svelte";
|
import Icon from "./Icon.svelte";
|
||||||
import { proposalToast } from "../lib/state.svelte.js";
|
import { proposalToast } from "../lib/state.svelte.js";
|
||||||
|
|
||||||
|
// Without a linked Soulseek name the backend can only give us the raw
|
||||||
|
// SoulStream ID, and a 44-character key runs the pill off the screen.
|
||||||
|
const who = $derived.by(() => {
|
||||||
|
const f = proposalToast.from ?? "";
|
||||||
|
return f.length > 24 ? `${f.slice(0, 4)}…${f.slice(-4)}` : f;
|
||||||
|
});
|
||||||
|
|
||||||
const HOLD_MS = 4200;
|
const HOLD_MS = 4200;
|
||||||
const LEAVE_MS = 280;
|
const LEAVE_MS = 280;
|
||||||
|
|
||||||
@@ -46,7 +53,7 @@
|
|||||||
>
|
>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="text"
|
<span class="text"
|
||||||
><b>{proposalToast.from}</b> proposed a change to {proposalToast.playlist}</span
|
><b>{who}</b> proposed a change to {proposalToast.playlist}</span
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user