Skip to main content

Overview

shyware is a shared anonymous wrapper architecture, not just a voting daemon. The cleanest way to read the system is:
  1. shyvoting demonstrates the two-list anonymous ballot apparatus.
  2. shywire and shycontracts demonstrate the same structural separation in value transfer and financing flows that are immediately legible to regulated operators.
  3. shycustody demonstrates pooled-asset custody, issuance, redemption, and demurrage as a distinct commodity/warehouse species that uses shywire as its transfer rail without collapsing into it.
  4. shyshares demonstrates that the same apparatus generalizes into weighted governance with canonical queued actions.
Within that family, the voting and governance surfaces support multiple identity tiers. Both use a device-held or user-held secret; both prevent oracle forgery. The difference is what the IDV provider and receipt store operator can see. Choose by loading or omitting a ZK verifier at startup.

Preferred non-ZK voting/governance tier — IDV-attested public key; receipt store linkable under lawful process

Voter device                Canonical runtime             Managed signing boundary
    │                             │                                │
    ├─ generate (sk_v, pk_v) ─────┤  Ed25519, per-poll, ephemeral │
    │                             │                                │
    ├─ request IDV attest ────────►                                │
    │   SHA-256(pk_v ‖ poll_id)   │                                │
    │◄── provider signature ──────┤                                │
    │                             │                                │
    ├─ sign ballot_nonce:poll_id──┤  voter_sig = Sign(sk_v, msg)  │
    │   discard sk_v immediately  │                                │
    │                             │                                │
    ├─ submit canonical write ────►                                │
    │                             ├─ verify voter_sig              │
    │                             ├─ verify IDV attestation        │
    │                             ├─ dedup on identity_hash (L2)   │
    │                             ├─ store VoteRecord (List 1)     │
    │                             ├─ store VoterRecord (List 2)    │
    │                             │                                │
    │          close / settle ────►                                │
    │                             ├─ compute Merkle roots          │
    │                             ├─ sign(root1‖root2‖N) ─────────►
    │                             │◄── signature + public key ─────┤
    │                             ├─ store Tally                   │
identity_hash = SHA-256(voter_pub_key ‖ poll_id) Because sk_v is generated on the device at vote time and discarded immediately after voter_sig is produced, the IDV provider can never hold it. A forged ballot would require inverting a random Ed25519 keypair — structural oracle prevention without ZK overhead.

High-assurance ZK tier — commitment-blind; receipt store unlinkable even under subpoena

Voter device                Canonical runtime             Managed signing boundary
    │                             │                                │
    ├─ compute commitment ────────┤                                │
    │   commitment = MiMC(secret) │  private witness, never on-chain
    │                             │                                │
    ├─ request Didit attest ──────►                                │
    │   SHA-256(commitment‖poll)  │                                │
    │◄── didit_commitment_sig ────┤  absorbed as circuit witness  │
    │                             │                                │
    ├─ prove nullifier ───────────┤                                │
    │   Groth16: secret→C AND     │                                │
    │   MiMC(secret,poll)==N      │                                │
    │   commitment + didit_sig    │                                │
    │   are PRIVATE WITNESS ONLY  │                                │
    │                             │                                │
    ├─ POST /ballots (TxType=2) ──►                                │
    │   {zk_nullifier,            │                                │
    │    zk_nullifier_proof,      │                                │
    │    voter_pub_key,           │                                │
    │    voter_sig,               │                                │
    │    choices, ballot_nonce}   │                                │
    │                             ├─ verify voter_sig              │
    │                             ├─ verify Groth16 proof          │
    │                             ├─ dedup on nullifier (List 2)   │
    │                             ├─ store VoteRecord (List 1)     │
    │                             ├─ store VoterRecord (List 2)    │
identity_hash = ZKNullifier = MiMC(person_secret, poll_id) commitment and didit_commitment_sig are private circuit witness inputs and must not appear in the transaction payload. The on-chain record contains only the nullifier and the Groth16 proof. This is what makes the ZK tier oracle-free with respect to even the linkage store operator — there is no on-chain field the IDV provider can use to correlate nullifier → commitment → identity.

List 1 / List 2 separation

The anonymity guarantee rests on a single structural property: no common key exists between List 1 and List 2.
List 1 — voteDirections (LevelDB prefix "vote:")
  key:   ballot_id = H(ballot_nonce)    ← random, chosen by voter
  value: VoteRecord { ballot_id, poll_id, choices, timestamp, height }

List 2 — voterRegistry (LevelDB prefix "voter:")
  key:   "pollID:identityHash"
  value: VoterRecord { poll_id, identity_hash, height }

Preferred:  identity_hash = SHA-256(voter_pub_key ‖ poll_id)
ZK tier:    identity_hash = MiMC(person_secret, poll_id)
ballot_id is the hash of a random client-generated nonce. identity_hash is derived from the voter’s ephemeral device key (or ZK secret). These two values share no derivation path in either tier.

ZK nullifier circuit (high-assurance tier)

The Groth16 circuit proves two statements simultaneously without revealing person_secret:
witness (private): person_secret, commitment, didit_commitment_sig
public:            nullifier, poll_id

constraints:
  MiMC(person_secret)          == commitment   // Didit-attested binding (private)
  MiMC(person_secret, poll_id) == nullifier    // per-poll dedup key (public)
The circuit is compiled over BN254 (EIP-197 precompile). Production requires a Groth16 MPC ceremony. See zkp for setup instructions.

IDV binding

Preferred non-ZK tier: an IDV provider such as Didit issues an Ed25519 signature over SHA-256(voter_pub_key ‖ poll_id) after completing biometric identity verification. The runtime verifies this signature using the configured provider public key. This binds the ephemeral voter device key — and therefore identity_hash — to a real, verified person. Neither the IDV provider nor the canonical runtime can link the ballot_id to the voter’s identity without the ballot_nonce, which the voter retains in the off-chain receipt store. ZK tier: the IDV provider issues an Ed25519 signature over SHA-256(commitment ‖ poll_id) where commitment = MiMC(person_secret). This signature is absorbed as a private circuit witness and never appears on-chain.

Count-match invariant

At every committed block:
len(voteDirections) == len(voterRegistry) == Tally.TotalVotes
This invariant is enforced by state.ExecuteTx before every ballot is accepted. A missing or extra record in either list causes executeBallotCast to return an error and the transaction to be rejected.

Managed tally signing

At poll close, executePollClose calls signer.Sign with the payload:
SHA-256(
  sorted ballot_ids Merkle root (List 1)
  ‖ sorted identity_hashes Merkle root (List 2)
  ‖ TotalVotes (big-endian int64)
  ‖ sorted option counts
)
The preferred deployment posture uses a managed signing boundary such as AWS KMS + Azure Managed HSM. The verifying key is stored in Tally.PublicKey (DER-encoded) so any party can verify the signature with the verify package and no operator dependency.

Sybil-audit signal (receipt confirmation)

After a poll closes, voters can submit TxTypeConfirmReceipt transactions to acknowledge that their identity_hash appears in List 2. The aggregate confirmed_count K vs total N is returned in the tally. The gap N-K is an observable Sybil signal: voters who cannot confirm their receipt may have had their participation fabricated. No biometrics, no ZK, no new oracle — the mechanism is structurally present in the two-list protocol.