feat(security): seal emergency payloads end-to-end before submission - #663
Merged
Mikey-222 merged 5 commits intoSep 30, 2026
Merged
Conversation
Contact numbers and medical notes are now encrypted on the requester's device with ECDH P-256 + HKDF-SHA256 + AES-256-GCM before anything is submitted. The ledger, the relay and the map markers only ever carry ciphertext. Crypto (src/lib/crypto.ts) - One random content key per request, sealed once and wrapped per recipient, so a 32-responder envelope still fits the contract budget. - Every AEAD tag is domain-separated: the payload is bound to bindingContext, each CEK wrap to bindingContext + recipient keyId. - bindingContext is a client-generated submission id carried in the envelope. It is public but tamper-evident, and a responder needs no out-of-band coordination to read it. The contract assigns the request id only after signing, so it cannot be part of a pre-signature AAD. - Per-request ephemeral keys, so a compromised responder key only exposes the envelopes addressed to it. Contract (BREAKING) - HelpRequest.nickname/contact replaced by encrypted_payload: Bytes. - create_request drops the phantom priority argument, takes the sealed payload, and returns Result<u64, Error>. - New validation: PayloadEmpty=12, PayloadTooLarge=13, EmergencyTypeInvalid=14; MAX_ENCRYPTED_PAYLOAD_BYTES=12288. - This changes the stored XDR layout. Testnet and mainnet contracts must be redeployed and drain live requests first; there is no in-place upgrade path. encodeEncryptedPayload rejects anything that is not a valid v1 envelope, so plaintext cannot reach the new field. Relay (server/routes/dispatch.ts) - Blind opaque store indexed by both submission id and on-chain request id, public-key registry, structural and size validation, rate limiting, Prometheus counters. Logs shape only, never the blob. - An accelerator, not a system of record: the authoritative copy is encrypted_payload on-chain, so a relay wipe costs latency only. - Left unmounted, matching every other factory in server/routes/. Location and emergency_type stay readable on purpose: dispatch needs them, and the existing ZK proof plus client-side coarsening is what protects location. Verification - cargo test -p helphone-contract: 29 passed, up from 24; the 4 pre-existing nonce:: failures are unchanged. - cargo build --target wasm32v1-none --release and cargo clippy clean. - test/e2e-encryption.test.js: 37 new tests covering the seal/open path, tamper and downgrade resistance, limits, plaintext-leak checks and the relay round trip through the real Express routers. - Full vitest: +40 passing, failures 33 -> 32 files and 245 -> 244 tests. tsc --noEmit is byte-identical to the pre-change baseline. Responder keys live in sessionStorage, so they are lost when the tab closes; moving them into the encrypted SecureStorage is follow-up work. Signed-off-by: Aliyu Habibu <aliyuhabibu111@gmail.com>
|
@aliyuHabibu Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Contributor
Author
|
@ALL Please merge, Wave has ended |
Contributor
Author
|
@ALL Please merge, wave has ended |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Contact numbers and medical notes are now encrypted on the requester's device with ECDH P-256 + HKDF-SHA256 + AES-256-GCM before anything is submitted. The ledger, the relay and the map markers only ever carry ciphertext.
Crypto (src/lib/crypto.ts)
Contract (BREAKING)
Relay (server/routes/dispatch.ts)
Location and emergency_type stay readable on purpose: dispatch needs them, and the existing ZK proof plus client-side coarsening is what protects location.
Verification
Responder keys live in sessionStorage, so they are lost when the tab closes; moving them into the encrypted SecureStorage is follow-up work.
Closes #504