Skip to content

feat(zk): add ElGamal & AES key derivation - #413

Merged
HealthyBuilder merged 3 commits into
solana-foundation:mainfrom
sonicfromnewyoke:sonic/token2022-zk-kdf
Jun 15, 2026
Merged

feat(zk): add ElGamal & AES key derivation#413
HealthyBuilder merged 3 commits into
solana-foundation:mainfrom
sonicfromnewyoke:sonic/token2022-zk-kdf

Conversation

@sonicfromnewyoke

Copy link
Copy Markdown
Collaborator

Problem

solana-go has no Token-2022 confidential-transfer key-derivation support #412. Rust solana-zk-sdk and JS @solana/zk-sdk ship deterministic KDFs that turn a Solana signer + public seed into an ElGamal secret scalar and an AES-128-GCM-SIV key;
Go users currently have to call into Rust or JS/WASM just to produce the key material a confidential transfer needs.
First step of the port is the KDF itself, byte-for-byte compatible with the reference SDKs.

Summary of Changes

  • AeKey (16 bytes, AES-128-GCM-SIV key): AeKeyFromSeed, AeKeyFromSignature, AeKeyFromSigner, AeKeyFromSeedPhraseAndPassphrase
  • ElGamalSecretKey (32 bytes, canonical Ristretto/Ed25519 scalar mod ell): ElGamalSecretKeyFromSeed, ElGamalSecretKeyFromSignature, ElGamalSecretKeyFromSigner, ElGamalSecretKeyFromSeedPhraseAndPassphrase
  • Signer interface (minimal: Sign(message) -> solana.Signature, error); solana.PrivateKey satisfies it; hardware wallets and remote signers can plug in without changes.
  • Seed-length bounds and default-signature rejection mirror the Rust implementation (ErrSeedTooShort, ErrSeedTooLong, ErrDefaultSignature).

related to #412

@HealthyBuilder

Copy link
Copy Markdown
Collaborator

Hey @sonicfromnewyoke seems a small conflict. Should it be retargeted to the v2 branch? If so, rebase on v2 pls:)

@sonicfromnewyoke
sonicfromnewyoke force-pushed the sonic/token2022-zk-kdf branch from 4a5fd47 to cbda857 Compare May 18, 2026 21:55

// AeKeyFromSignature derives an AeKey from an ed25519 signature by using
// SHA3-512(signature) as the seed. Mirrors AeKey::seed_from_signature +
// from_seed in solana-zk-sdk. No default-signature check is performed here;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shall we add a check for default-signature or add a // SECURITY: warning?

@HealthyBuilder

Copy link
Copy Markdown
Collaborator

Code quality and doc are excellent! @sonicfromnewyoke it would be great to consider add an example under programs/token-2022/zkencryption/examples/

Let's merge it to v2 after reviewed & merged all current PRs in main:)

@sonicfromnewyoke

Copy link
Copy Markdown
Collaborator Author

it would be great to consider add an example under programs/token-2022/zkencryption/examples/

done 🫡

@greptile-apps

greptile-apps Bot commented May 27, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a new zkencryption package under programs/token-2022 that ports the deterministic KDF from Rust solana-zk-sdk to Go, producing byte-for-byte identical ElGamalSecretKey (32-byte scalar) and AeKey (16-byte AES-128-GCM-SIV key) values for the same signer and public seed.

  • Four derivation entry points per key type (FromSeed, FromSignature, FromSigner, FromSeedPhraseAndPassphrase) mirror the Rust SDK API, with matching domain-separation prefixes, seed-length bounds, and default-signature rejection.
  • Cross-SDK correctness is enforced by a JSON fixture of Rust-generated test vectors covering all four paths, verified by the Go test suite.
  • Two new direct dependencies are added: filippo.io/edwards25519 v1.2.0 (wide-reduction scalar arithmetic) and github.com/tyler-smith/go-bip39 v1.1.0 (BIP39 PBKDF2 seed derivation).

Confidence Score: 5/5

Safe to merge; the KDF logic is correct and byte-for-byte verified against Rust reference vectors across all derivation paths.

The core cryptographic derivation — SHA3-512 hashing, edwards25519 wide-reduction via SetUniformBytes, double-hash for the signature paths, and BIP39 PBKDF2 — all match the Rust solana-zk-sdk contract and are confirmed by the cross-SDK test vectors. Seed-bound checks, domain-separation prefixes, and default-signature rejection are all present and tested. The only open item is that the unreachable SetUniformBytes error path drops the original library error rather than wrapping it, but this does not affect correctness.

No files require special attention.

Important Files Changed

Filename Overview
programs/token-2022/zkencryption/elgamal_secret.go New file: ElGamal secret scalar derivation via SHA3-512 + edwards25519 wide-reduction; the SetUniformBytes error path silently drops the original library error instead of wrapping it.
programs/token-2022/zkencryption/ae_key.go New file: AES-128-GCM-SIV key derivation via SHA3-512; seed bounds, domain-separation prefix, and default-signature rejection all mirror the Rust SDK.
programs/token-2022/zkencryption/kdf_test.go Comprehensive cross-SDK test suite: vector-driven tests for all four derivation paths, seed-bound edge cases, default-signature rejection, and signer-error wrapping.
programs/token-2022/zkencryption/signer.go New file: minimal Signer interface satisfied by solana.PrivateKey; clean and correct.
programs/token-2022/zkencryption/errors.go New file: four sentinel errors for seed-bound violations, default signature, and scalar encoding; clear and conventional.
programs/token-2022/zkencryption/testdata/kdf_vectors.json Rust-generated cross-SDK test vectors covering seed, signature, signer, and mnemonic derivation paths; all hex lengths are correct.
programs/token-2022/zkencryption/examples/deriveKeys/deriveKeys.go Runnable example demonstrating deterministic derivation from a wallet signer; illustrates the typical ATA-scoped public seed pattern.
go.mod Adds filippo.io/edwards25519 v1.2.0 and github.com/tyler-smith/go-bip39 v1.1.0 — both well-known, audited libraries.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A([Signer + publicSeed]) -->|Sign domain+seed| B[ed25519 signature 64 bytes]
    B -->|reject if all-zero| C{Default sig?}
    C -->|yes| ERR[ErrDefaultSignature]
    C -->|no| D[SHA3-512 signature]

    E([Raw signature 64 bytes]) --> D2[SHA3-512 signature]

    F([BIP39 mnemonic + passphrase]) -->|PBKDF2-HMAC-SHA512| G[64-byte seed]

    H([Raw seed bytes]) --> BOUNDS{Length bounds check min/max}
    BOUNDS -->|too short / too long| ERR2[ErrSeedTooShort / ErrSeedTooLong]
    BOUNDS -->|ok| HASH[SHA3-512 seed]

    D --> BOUNDS
    D2 --> BOUNDS
    G --> BOUNDS

    HASH -->|first 16 bytes| AE[AeKey 16 bytes]
    HASH -->|SetUniformBytes wide-reduction| EL[ElGamalSecretKey 32-byte scalar mod l]
Loading

Reviews (2): Last reviewed commit: "test: add elgamal too long case" | Re-trigger Greptile

Comment thread programs/token-2022/zkencryption/kdf_test.go
@sonicfromnewyoke
sonicfromnewyoke force-pushed the sonic/token2022-zk-kdf branch from 8a9c84f to 9ce7456 Compare May 27, 2026 20:39
@HealthyBuilder
HealthyBuilder merged commit 9fcbf0c into solana-foundation:main Jun 15, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants