Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
20bf992
wip
xalsina-sequent Nov 5, 2025
2ac007b
wip
xalsina-sequent Nov 6, 2025
1c1d262
wip
xalsina-sequent Nov 6, 2025
74f344a
fixed signature missing from plaintext and frontend start tally
xalsina-sequent Nov 7, 2025
6c0dcfd
fix formatting
xalsina-sequent Nov 7, 2025
f33eec8
wip
xalsina-sequent Nov 27, 2025
e8ed572
Merge branch 'main' of github.com:sequentech/step into feat/meta-838/…
xalsina-sequent Nov 27, 2025
320ab77
Merge branch 'main' of github.com:sequentech/step into feat/meta-838/…
xalsina-sequent Dec 10, 2025
b217955
fix format
xalsina-sequent Dec 10, 2025
7763b8a
wip
xalsina-sequent Dec 10, 2025
60abc5b
fix formatting
xalsina-sequent Dec 10, 2025
dd357bf
Merge branch 'main' into feat/meta-838/main
xalsina-sequent Dec 10, 2025
7d39128
Refactoring a bit
xalsina-sequent Dec 11, 2025
6acacd7
wip
xalsina-sequent Dec 11, 2025
3389bbb
format
xalsina-sequent Dec 11, 2025
2ecf261
format
xalsina-sequent Dec 11, 2025
51ffd06
Small fixes
xalsina-sequent Dec 11, 2025
5e6bf71
Merge branch 'main' into feat/meta-838/main
Findeton Dec 20, 2025
d1b9f1b
Merge branch 'main' into feat/meta-838/main
Findeton Dec 22, 2025
2d060b6
build sequent core
Findeton Dec 22, 2025
c759e8e
release notes link
Findeton Dec 22, 2025
379c84f
build sequent core
Findeton Dec 28, 2025
dbe2bea
fix tally ceremony
Findeton Dec 28, 2025
24c607c
fix default encryption
Findeton Dec 28, 2025
7771047
address comments
Findeton Jan 10, 2026
ade1a13
Merge branch 'main' into feat/meta-838/main
Findeton Jan 10, 2026
504912a
Merge branch 'main' into feat/meta-838/main
xalsina-sequent Jan 20, 2026
16b0853
wip
xalsina-sequent Jan 22, 2026
0d22fcc
Merge branch 'main' into feat/meta-838/main
xalsina-sequent Jan 23, 2026
e0412b3
fix formatting and wasm build
xalsina-sequent Jan 23, 2026
181b65b
fix tests
Findeton Jan 25, 2026
6609fb5
run wasm tests
Findeton Jan 25, 2026
9f7764b
Fix wasm tests
xalsina-sequent Jan 26, 2026
c925d98
fix formatting
xalsina-sequent Jan 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .devcontainer/scripts/build-sequent-core.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/usr/bin/bash
set -euo pipefail

# SPDX-FileCopyrightText: 2025 Sequent Tech <legal@sequentech.io>
Expand All @@ -9,7 +9,8 @@ set -euo pipefail
# flag, which is not supported when compiling C code for WebAssembly targets
# (used by the ring cryptographic library):
export NIX_HARDENING_ENABLE=""
export CFLAGS_wasm32_unknown_unknown="-O3 -ffunction-sections -fdata-sections -fno-exceptions";
# Append optimization flags to existing CFLAGS (preserving include paths from flake.nix)
export CFLAGS_wasm32_unknown_unknown="${CFLAGS_wasm32_unknown_unknown} -O3 -ffunction-sections -fdata-sections -fno-exceptions"

TARGET_DIR=/workspaces/step/packages/sequent-core
cd "$TARGET_DIR"
Expand All @@ -22,7 +23,7 @@ wasm-pack --version
which wasm-bindgen
wasm-bindgen --version

wasm-pack build --mode no-install --out-name index --release --target web --features=wasmtest
wasm-pack build --mode no-install --out-name index --release --target web --features=wasmtest,default_features
wasm-pack -v pack . 2>&1 | tee output.log

cd ..
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/build_wasm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,14 @@ jobs:
- name: Check that WASM build works
working-directory: packages/sequent-core
run: |
nix develop --command wasm-pack build --mode no-install --out-name index --release --target web --features=wasmtest
nix develop --command wasm-pack build --mode no-install --out-name index --release --target web --features=wasmtest,default_features

- name: Check that WASM pack works
working-directory: packages/sequent-core
run: |
nix develop --command wasm-pack -v pack .

- name: Check that WASM tests work
working-directory: packages/sequent-core
run: |
nix develop --command wasm-pack test --release --firefox --headless -- --features=wasmtest,default_features
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- service: immu-board
- service: immudb-rs
- service: sequent-core
extra: --features keycloak
extra: --features keycloak,default_features
- service: velvet
- service: windmill
- service: wrap-map-err
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,9 @@ Provide documents that voters can access in the Voting Portal.
Configure advanced system behaviors for this Election Event.

- **Contest Encryption Policy**:
- **Single Contests**: Encrypt contests individually.
- **Single Contest**: Encrypt contests individually.
- **Multiple Contests**: Encrypt multiple contests together to enable ballot-level audit.
- **Unencrypted Single Contest (Plaintext)**: Run elections without encryption. This option is suitable for non-confidential voting scenarios where encryption is not required. When this policy is selected, key ceremonies are not necessary, and the tally process is simplified.
- **Lockdown Status**: When enabled, no changes can be made to this Election Event. This action is irreversible.
- **Voting Portal Countdown Policy**:
- Define the session timeout duration in seconds.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ SPDX-License-Identifier: AGPL-3.0-only

The Key Ceremony establishes the collective private key used to decrypt votes and publishes a corresponding public key for voters to encrypt their ballots. During this ceremony, trustees collaboratively generate fragments of the private key, ensuring no single party holds the full secret. Each trustee downloads, securely backs up, and verifies their key fragment. Only after all trustees complete these steps can the election proceed to stages such as ballot publication, voting, and tallying. This distributed approach guarantees that votes encrypted under the shared public key remain confidential and tamper-resistant, and that the combined private key—reconstructed only when a threshold of trustees collaborates—preserves integrity and availability of decryption throughout the election lifecycle.

> **Note for Unencrypted Elections**: If your Election Event is configured with the **Unencrypted Single Contest (Plaintext)** encryption policy, keys are not required. The Key Ceremony is only necessary for encrypted elections. You can proceed directly to publishing and tallying without performing a Key Ceremony.


## Opening Key Distribution Ceremony

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ SPDX-License-Identifier: AGPL-3.0-only
## Introduction
The Tally section covers the essential procedures required to consolidate resultd after voting has concluded. Election Board members are guided through starting the tally ceremony, verifying key fragments, running the tally, and reviewing results. By following these instructions, the post-election phase is handled with integrity and precision, ensuring that results are verified and published correctly and that the process remains transparent and trustworthy.

## Closing Key Ceremony
During the Closing Key Ceremony for an Election Event, trustees use their cryptographic key fragments to decrypt the final tally:
## Closing Key Ceremony (Encrypted Elections Only)
During the Closing Key Ceremony for an **encrypted** Election Event, trustees use their cryptographic key fragments to decrypt the final tally:

- **Retrieve stored key fragments:** Each trustee securely retrieves their previously backed-up fragment of the private key.
- **Verify integrity and functionality:** Trustees perform cryptographic tests or audits to confirm each fragment is unaltered and functional.
Expand All @@ -22,24 +22,41 @@ During the Closing Key Ceremony for an Election Event, trustees use their crypto

This ceremony ensures that only a quorum of trustees can reconstruct the private key for decryption, preserving security throughout the election lifecycle.

> **Note**: This ceremony step is automatically skipped for Election Events configured with the **Unencrypted Single Contest (Plaintext)** encryption policy.

## Prerequisites for Tally
Before initiating the tally process:

1. **Opening Key Ceremony completed:** All key fragments have been generated and verified.
1. **Opening Key Ceremony completed (for encrypted elections):** All key fragments have been generated and verified.
2. **Voting phase concluded (optional):** Votes may or may not have been cast; starting a tally with no votes is technically possible but yields empty results.

> Note: According to the Election Event Keys/Tally Ceremonies Policy, if the Key Ceremony is set to 'Automatic,' the Tally Ceremony will also be automated, and no trustee action is required.
> **Note for Automated Ceremonies**: According to the Election Event Keys/Tally Ceremonies Policy, if the Key Ceremony is set to 'Automatic,' the Tally Ceremony will also be automated, and no trustee action is required.

> **Note for Unencrypted Elections**: If your Election Event is configured with the **Unencrypted Single Contest (Plaintext)** encryption policy, the Key Ceremony step is not required. The tally process will automatically skip the ceremony phase and proceed directly to tallying the votes.

## Starting the Tally Process

### For Encrypted Elections
1. Navigate to the relevant **Election Event** in the Administration Portal.
2. Go to the **Tally** tab.
3. Click **Start Tally Ceremony**.
4. Select one or more Elections to include in this tally.
5. Confirm to proceed; the system will notify trustees to verify their key fragments.
6. Trustees verify their fragments as prompted; once the threshold of fragments is verified, the tally begins automatically.
7. Wait for the system to finalize the tally. A success notification appears when complete.
5. Select the Key Ceremony to use for decryption.
6. Confirm to proceed; the system will notify trustees to verify their key fragments.
7. Trustees verify their fragments as prompted; once the threshold of fragments is verified, the tally begins automatically.
8. Wait for the system to finalize the tally. A success notification appears when complete.

### For Unencrypted Elections (Plaintext)
1. Navigate to the relevant **Election Event** in the Administration Portal.
2. Go to the **Tally** tab.
3. Click **Start Tally Ceremony**.
4. Select one or more Elections to include in this tally.
5. Confirm to proceed; the system will **automatically start the tally** without requiring trustee verification.
6. Wait for the system to finalize the tally. A success notification appears when complete.

## Trustee Key Verification for Tally (Encrypted Elections Only)
This section applies only to **encrypted** elections. For unencrypted elections, this step is automatically skipped.

## Trustee Key Verification for Tally
1. Log in to the Administration Portal and open the relevant Election Event.
2. Go to the **Tally** tab.
3. Click the Key Action invitation or the key icon next to the Tally ceremony.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
alter table "sequent_backend"."tally_session" alter column "keys_ceremony_id" set not null;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
alter table "sequent_backend"."tally_session" alter column "keys_ceremony_id" drop not null;
10 changes: 3 additions & 7 deletions packages/admin-portal/graphql.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -116895,13 +116895,9 @@
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "uuid",
"ofType": null
}
"kind": "SCALAR",
"name": "uuid",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
Expand Down
Binary file modified packages/admin-portal/rust/sequent-core-0.1.0.tgz
Binary file not shown.
2 changes: 1 addition & 1 deletion packages/admin-portal/src/gql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16352,7 +16352,7 @@ export type Sequent_Backend_Tally_Session = {
execution_status?: Maybe<Scalars['String']['output']>;
id: Scalars['uuid']['output'];
is_execution_completed: Scalars['Boolean']['output'];
keys_ceremony_id: Scalars['uuid']['output'];
keys_ceremony_id?: Maybe<Scalars['uuid']['output']>;
labels?: Maybe<Scalars['jsonb']['output']>;
last_updated_at?: Maybe<Scalars['timestamptz']['output']>;
permission_label?: Maybe<Array<Scalars['String']['output']>>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {useAliasRenderer} from "@/hooks/useAliasRenderer"
import {useKeysPermissions} from "./useKeysPermissions"
import {TrusteeItems} from "@/components/TrusteeItems"
import {StyledChip} from "@/components/StyledChip"
import {EElectionEventContestEncryptionPolicy} from "@sequentech/ui-core"

const NotificationLink = styled("span")`
text-decoration: underline;
Expand Down Expand Up @@ -129,6 +130,11 @@ export const EditElectionEventKeys: React.FC<EditElectionEventKeysProps> = (prop
const isTrustee = authContext.hasRole(IPermissions.TRUSTEE_CEREMONY)
const {globalSettings} = useContext(SettingsContext)

// Check if the election policy is Plaintext
const isUnencrypted =
electionEvent?.presentation?.contest_encryption_policy ===
EElectionEventContestEncryptionPolicy.PLAINTEXT

const {data: keysCeremonies} = useQuery<ListKeysCeremonyQuery>(LIST_KEYS_CEREMONY, {
variables: {
tenantId: tenantId,
Expand Down Expand Up @@ -176,7 +182,8 @@ export const EditElectionEventKeys: React.FC<EditElectionEventKeysProps> = (prop
const CreateButton = () => (
<Button
onClick={() => setShowCeremony(true)}
disabled={!keysCeremonies}
// Disable if data is missing OR if it is a plaintext election
disabled={!keysCeremonies || isUnencrypted}
className="keys-add-button"
>
<ResourceListStyles.CreateIcon icon={faPlus as any} />
Expand All @@ -189,7 +196,7 @@ export const EditElectionEventKeys: React.FC<EditElectionEventKeysProps> = (prop
<Typography variant="h4" paragraph>
{t("electionEventScreen.keys.emptyHeader")}
</Typography>
{canCreateCeremony ? (
{canCreateCeremony && !isUnencrypted ? (
<>
<Typography variant="body1" paragraph>
{t("common.resources.noResult.askCreate")}
Expand Down Expand Up @@ -255,6 +262,13 @@ export const EditElectionEventKeys: React.FC<EditElectionEventKeysProps> = (prop

return (
<>
{/* Notification for Plaintext Elections */}
{isUnencrypted && !showCeremony && !showTrusteeCeremony && (
<Alert severity="info" sx={{marginBottom: 2}}>
{t("electionEventScreen.keys.notify.plaintextNoKeys")}
</Alert>
)}

{/* Show the notification if the conditions are met */}
{canTrusteeCeremony && activeCeremony && !showCeremony && !showTrusteeCeremony && (
<Alert severity="info">
Expand Down Expand Up @@ -315,7 +329,8 @@ export const EditElectionEventKeys: React.FC<EditElectionEventKeysProps> = (prop
withExport={false}
actionLabel="common.label.add"
doAction={() => setShowCeremony(true)}
withAction={canCreateCeremony}
// Disable Create action if unencrypted
withAction={canCreateCeremony && !isUnencrypted}
/>
}
>
Expand Down
38 changes: 25 additions & 13 deletions packages/admin-portal/src/resources/Tally/ListTally.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import {theme, IconButton, Dialog} from "@sequentech/ui-essentials"
import {AuthContext, AuthContextValues} from "@/providers/AuthContextProvider"
import {ResourceListStyles} from "@/components/styles/ResourceListStyles"
import {faPlus} from "@fortawesome/free-solid-svg-icons"
import {EAllowTally} from "@sequentech/ui-core"
import {EElectionEventContestEncryptionPolicy} from "@sequentech/ui-core"
import {
ETallyType,
IExecutionStatus,
Expand Down Expand Up @@ -204,6 +204,12 @@ export const ListTally: React.FC<ListAreaProps> = () => {
),
[keysCeremonies?.list_keys_ceremony?.items]
)
const isUnencryptedPolicy = useMemo(
() =>
electionEventRecord?.presentation?.contest_encryption_policy ===
EElectionEventContestEncryptionPolicy.PLAINTEXT,
electionEventRecord?.presentation?.contest_encryption_policy
)

const keysCeremonyIds = useMemo(
() => keysCeremonies?.list_keys_ceremony?.items?.map((ceremony) => ceremony?.id) ?? [],
Expand All @@ -217,7 +223,9 @@ export const ListTally: React.FC<ListAreaProps> = () => {
setIsCreatingTally(true)
setCreatingFlag(ETallyType.ELECTORAL_RESULTS)
}}
disabled={!isKeyCeremonyFinished || !isPublished || isCreatingTally}
disabled={
(!isKeyCeremonyFinished && !isUnencryptedPolicy) || !isPublished || isCreatingTally
}
style={{height: "10px"}}
sx={{marginBottom: "10px"}}
>
Expand All @@ -231,24 +239,26 @@ export const ListTally: React.FC<ListAreaProps> = () => {
<Button
label={String(t("electionEventScreen.tally.create.createInitializationReportButton"))}
onClick={() => setCreatingFlag(ETallyType.INITIALIZATION_REPORT)}
disabled={!isKeyCeremonyFinished || !isPublished}
disabled={(!isKeyCeremonyFinished && !isUnencryptedPolicy) || !isPublished}
>
{isListActions ? <Add /> : <IconButton icon={faPlus as any} fontSize="24px" />}
</Button>
)

const Empty = () => (
<ResourceListStyles.EmptyBox>
{canCreateCeremony && !isKeyCeremonyFinished && (
{canCreateCeremony && !isKeyCeremonyFinished && !isUnencryptedPolicy && (
<Alert severity="warning">
{t("electionEventScreen.tally.notify.noKeysTally")}
</Alert>
)}
{canCreateCeremony && isKeyCeremonyFinished && !isPublished && (
<Alert severity="warning">
{t("electionEventScreen.tally.notify.noPublication")}
</Alert>
)}
{canCreateCeremony &&
(isKeyCeremonyFinished || isUnencryptedPolicy) &&
!isPublished && (
<Alert severity="warning">
{t("electionEventScreen.tally.notify.noPublication")}
</Alert>
)}
<Typography variant="h4" paragraph>
{t("electionEventScreen.tally.emptyHeader")}
</Typography>
Expand Down Expand Up @@ -442,10 +452,12 @@ export const ListTally: React.FC<ListAreaProps> = () => {
filter={{
tenant_id: tenantId || undefined,
election_event_id: electionEventRecord?.id || undefined,
keys_ceremony_id: {
format: "hasura-raw-query",
value: {_in: keysCeremonyIds},
},
keys_ceremony_id: isUnencryptedPolicy
? undefined
: {
format: "hasura-raw-query",
value: {_in: keysCeremonyIds},
},
}}
storeKey={false}
filters={Filters}
Expand Down
Loading
Loading