Much has been said about a secure voting system in the 2022 elections, but only in a purely speculative manner. There were claims that it was possible to fraudulently manipulate the election results remotely, even though electronic voting machines are not connected to the web. Despite this, it is entirely possible to create a secure and auditable voting system based on elliptic curve cryptography (Discrete Logarithm Problem). However, the most effective approach combines three paradigms of modern cryptography: Blind Signatures, Aggregate Signatures, and Zero-Knowledge Proof (ZKP) using the Fiat–Shamir Heuristic, i.e., non-interactive (NIZK).
By using blind signatures, voter confidentiality is ensured; zero-knowledge proof validates the voter's private key without revealing it; and with aggregate signatures, a single 48-byte digital signature (24 groups of 4 hexadecimal characters) is obtained, capable of verifying all votes in a single operation. This is achieved using BLS (Boneh–Lynn–Shacham) Aggregate Signatures over the BLS12-381 elliptic curve, which offers security between 121-bit and 128-bit.
The IBE (Identity-Based Encryption) scheme is similar to the PKI (Public Key Infrastructure) system but with some key differences. In IBE, a master key is generated, and private keys are derived from the master key and an ID. There is only one public key, which is the master public key. This eliminates the need for a public key directory.
• Master Key: \( sk_{master} \in \mathbb{Z}_n \), where \( \mathbb{Z}_n \) is the finite field used for private keys, and \( n \) is the order of the elliptic curve.
• Master Public Key: \( pk_{master} = sk_{master} \cdot G_2 \), where \( G_2 \) is the base point of the elliptic curve, and \( pk_{master} \) is the master public key.
• User Private Key: \( sk_{user} = sk_{master} \cdot h_{ID} \), where \( sk_{master} \) is the master private key, and \( h_{ID} \) is the hash of the user’s ID.
• User Public Key: \( pk_{user} = pk_{master} \cdot h_{ID} \), computed automatically.
• Commitment: \( C = r \cdot G_2 \), where \( r \) is a securely generated random number, and \( G_2 \) is the base point of the elliptic curve.
• Challenge: \( \chi = H(C \parallel m) \), where \( H(C \parallel m) \) is the hash of the commitment concatenated with the message.
• Response: \( s = sk_{\text{user}} \cdot \chi \), where \( sk_{\text{user}} \) is the user’s private key, and \( \chi \) is the challenge.
• Verification: Compute whether \( e(s \cdot G_1, G_2) = e(G_1, C + (\chi \cdot pk_{\text{user}})) \), where \( e \) is the bilinear pairing.
• If the equality holds, the proof is valid.
• Factor Generation: \( r \in \mathbb{Z}_n \), a securely generated random number.
• Message Blinding: \( m' = r \cdot H(m) \), where \( H(m) \) is the hash of the message.
• Message Unblinding: \( m = r^{-1} \cdot m' \), where \( r^{-1} \) is the inverse of \( r \), computed using the Extended Euclidean Algorithm.
• Message Signing: Compute \( \sigma = H(m) \cdot sk_{user} \), where \( H(m) \) is the hash of the message, and \( sk_{user} \) is the private key.
• Signature Verification: Compute whether \( e(\sigma, G_2) = e(H(m), pk_{user}) \), where \( e \) is the bilinear pairing.
• If this equality holds, the individual signature is valid.
• Message Signing: \( \sigma_i = sk_i \cdot H(m_i) \), where \( H(m_i) \) is the hash of message \( m_i \), and \( sk_i \) is the private key of the corresponding user.
• Signature Aggregation: \( \sigma_{\text{agg}} = \sum_{i} \sigma_i \), where \( \sigma_{\text{agg}} \) is the aggregate signature, and \( \sigma_i \) represents the individual signatures of users.
• Aggregate Signature Verification: \( e(\sigma_{\text{agg}}, G_2) = \prod_{i} e(H(m_i), pk_i) \), where \( e \) is the pairing function.
• If this equality holds, the aggregate signature is valid.
package main import ( "crypto/rand" "crypto/sha256" "fmt" "github.com/cloudflare/circl/ecc/bls12381" "github.com/cloudflare/circl/ecc/bls12381/ff" ) // Generates a secure random scalar func randomScalar() *ff.Scalar { scalar := new(ff.Scalar) buf := make([]byte, 32) // BLS12-381 uses 32 bytes _, err := rand.Read(buf) if err != nil { panic("Error generating random number") } scalar.SetBytes(buf) return scalar } // Hash to Scalar (used to derive values from the ID) func hashToScalar(ID string) *ff.Scalar { hash := sha256.Sum256([]byte(ID)) scalar := new(ff.Scalar) scalar.SetBytes(hash[:]) return scalar } // Generates a user's private key from the master key func generatePrivateKey(masterKey *ff.Scalar, userID string) *ff.Scalar { userScalar := hashToScalar(userID) privateKey := new(ff.Scalar) privateKey.Mul(masterKey, userScalar) // sk = msk ⋅ H(ID) return privateKey } // Generates a user's public key from the master key func generatePublicKeyForUser(masterPublicKey *bls12381.G2, userID string) *bls12381.G2 { userScalar := hashToScalar(userID) publicKey := new(bls12381.G2) publicKey.ScalarMult(userScalar, masterPublicKey) // pk = H(ID) ⋅ mpk return publicKey } // Generates a random blinding factor func generateBlindFactor() *ff.Scalar { return randomScalar() } // Converts a message to a point on curve G₁ func hashToG1(message []byte) *bls12381.G1 { hashMessage := new(bls12381.G1) hashMessage.Hash(message, nil) return hashMessage } // Blinds the message func blindMessage(originalG1 *bls12381.G1, blindFactor *ff.Scalar) *bls12381.G1 { blindedMessage := new(bls12381.G1) blindedMessage.ScalarMult(blindFactor, originalG1) // m' = r ⋅ H(m) return blindedMessage } // Unblinds the blinded message func unblindMessage(blindedMessage *bls12381.G1, blindFactor *ff.Scalar) *bls12381.G1 { inverseBlindFactor := new(ff.Scalar) inverseBlindFactor.Inv(blindFactor) // r⁻¹ originalMessage := new(bls12381.G1) originalMessage.ScalarMult(inverseBlindFactor, blindedMessage) // m = r⁻¹ ⋅ m' return originalMessage } // Signs a message with the private key func signMessage(message []byte, privKey *ff.Scalar) *bls12381.G1 { hashMessage := new(bls12381.G1) hashMessage.Hash(message, nil) signature := new(bls12381.G1) signature.ScalarMult(privKey, hashMessage) // σ = sk ⋅ H(m) return signature } // Verifies an individual signature func verifySignature(message []byte, signature *bls12381.G1, publicKey *bls12381.G2) bool { hashMessage := new(bls12381.G1) hashMessage.Hash(message, nil) e1 := bls12381.Pair(signature, bls12381.G2Generator()) // e(σ, G₂) e2 := bls12381.Pair(hashMessage, publicKey) // e(H(m), pk) return e1.IsEqual(e2) } // Aggregates signatures func aggregateSignatures(signatures []*bls12381.G1) *bls12381.G1 { aggSig := new(bls12381.G1) aggSig.SetIdentity() for _, sig := range signatures { aggSig.Add(aggSig, sig) } return aggSig } // Verifies an aggregated signature func verifyAggregateSignature(blindedMessages []*bls12381.G1, aggSignature *bls12381.G1, pubKeys []*bls12381.G2) bool { hashMessages := make([]*bls12381.G1, len(blindedMessages)) for i, msg := range blindedMessages { hashMessages[i] = new(bls12381.G1) hashMessages[i].Hash(msg.Bytes(), nil) // Hash the message } e1 := bls12381.Pair(aggSignature, bls12381.G2Generator()) // Pairing the aggregated signature with G₂ e2 := bls12381.Pair(hashMessages[0], pubKeys[0]) // Pair the first message with the public key for i := 1; i < len(hashMessages); i++ { e2.Mul(e2, bls12381.Pair(hashMessages[i], pubKeys[i])) // Multiplying pairings } return e1.IsEqual(e2) // Verifies if the pairings match } // Generates commitment for ZKP proof func generateCommitment(secret *ff.Scalar, generator *bls12381.G2) *bls12381.G2 { commitment := new(bls12381.G2) commitment.ScalarMult(secret, generator) // C = r ⋅ G₂ return commitment } // Generates challenge for ZKP proof func generateChallenge(commitment *bls12381.G2, message []byte) *ff.Scalar { hash := sha256.New() hash.Write(commitment.Bytes()) hash.Write(message) challenge := new(ff.Scalar) challenge.SetBytes(hash.Sum(nil)) // χ = H(C || m) return challenge } // Generates response for ZKP proof func generateResponse(secret *ff.Scalar, challenge *ff.Scalar) *ff.Scalar { response := new(ff.Scalar) response.Mul(secret, challenge) // s = sk ⋅ χ return response } // Verifies ZKP proof func verifyProof(commitment *bls12381.G2, challenge *ff.Scalar, response *ff.Scalar, publicKey *bls12381.G2) bool { left := new(bls12381.G1) left.ScalarMult(response, bls12381.G1Generator()) // s ⋅ G₁ leftPair := bls12381.Pair(left, bls12381.G2Generator()) // e(s ⋅ G₁, G₂) right := new(bls12381.G2) right.Add(commitment, new(bls12381.G2)) // C + (χ ⋅ pk) right.ScalarMult(challenge, publicKey) rightPair := bls12381.Pair(bls12381.G1Generator(), right) // e(G₁, C + (χ ⋅ pk)) return leftPair.IsEqual(rightPair) // e(s ⋅ G₁, G₂) == e(G₁, C + (χ ⋅ pk)) } func main() { // Master key setup masterKey := new(ff.Scalar) masterKey.SetUint64(1234567890) basePointG2 := bls12381.G2Generator() var masterPublicKey bls12381.G2 masterPublicKey.ScalarMult(masterKey, basePointG2) // Create individual keys userIDs := []string{"alice@example.com", "bob@example.com", "carol@example.com"} var pubKeys []*bls12381.G2 var privKeys []*ff.Scalar for _, ID := range userIDs { privKey := generatePrivateKey(masterKey, ID) pubKey := generatePublicKeyForUser(&masterPublicKey, ID) privKeys = append(privKeys, privKey) pubKeys = append(pubKeys, pubKey) } // Create messages messages := [][]byte{[]byte("Yes"), []byte("No"), []byte("Yes")} var signatures []*bls12381.G1 var blindedMessages []*bls12381.G1 var blindFactors []*ff.Scalar var originalG1Hashes []*bls12381.G1 // Blind and sign for i, msg := range messages { originalG1 := hashToG1(msg) blindFactor := generateBlindFactor() blindedMsg := blindMessage(originalG1, blindFactor) blindedMessages = append(blindedMessages, blindedMsg) blindFactors = append(blindFactors, blindFactor) originalG1Hashes = append(originalG1Hashes, originalG1) signature := signMessage(blindedMsg.Bytes(), privKeys[i]) signatures = append(signatures, signature) } // Verify individual signatures for i, blindedMsg := range blindedMessages { if verifySignature(blindedMsg.Bytes(), signatures[i], pubKeys[i]) { fmt.Printf("Valid signature for %s\n", userIDs[i]) } else { fmt.Printf("Invalid signature for %s\n", userIDs[i]) } } // Aggregate signatures aggSignature := aggregateSignatures(signatures) // Verify aggregated signature if verifyAggregateSignature(blindedMessages, aggSignature, pubKeys) { fmt.Println("Valid aggregated signature!") } else { fmt.Println("Invalid aggregated signature!") } // Create and verify zero-knowledge proofs for i, userID := range userIDs { commitment := generateCommitment(randomScalar(), basePointG2) challenge := generateChallenge(commitment, messages[i]) response := generateResponse(privKeys[i], challenge) if verifyProof(commitment, challenge, response, pubKeys[i]) { fmt.Printf("Valid ZKP for %s\n", userID) } else { fmt.Printf("Invalid ZKP for %s\n", userID) } } // Verify message integrity fmt.Println("\nMessage Integrity Verification") for i, blindedMsg := range blindedMessages { unblindedG1 := unblindMessage(blindedMsg, blindFactors[i]) if unblindedG1.IsEqual(originalG1Hashes[i]) { fmt.Printf("Message %d validated: the unblinded G1 point matches the original!\n", i+1) fmt.Println(unblindedG1) } else { fmt.Printf("Error in Message %d: the G1 points do not match!\n", i+1) } } }
To ensure the confidentiality of the Blind Factor, we use asymmetric encryption with the same elliptic curve to encrypt the Blind Factor using the user's public key. This key is derived from the product of the master public key and the hash of the user's ID, which can only be decrypted with the user's private key. This operation is performed at the time of signing (voting) and guarantees both the confidentiality and integrity of the Blind Factor.
• Select a random value \( k \in \mathbb{Z}_n \), where \( n \) is the order of group \( G_1 \).
• Compute the point: \( C_1 = k \cdot G_1 \), where \( G_1 \) is the base point in group \( G_1 \), used for encryption.
• Pairing: \( e(C_1, pk_{user}) \), where \( C_1 \) belongs to \( G_1 \) and \( pk_{user} \) belongs to \( G_2 \). This pairing computes an element in the target group \( G_T \).
• Derive the session key: \( S = H(e(C_1, pk_{user})) \), where \( H \) is a cryptographic hash function applied to the pairing result.
• Message hash: \( C_2 = H(m \parallel S) \)
• Encrypt the message: \( C_3[i] = m[i] \oplus S[i \mod \text{len}(S)] \)
• Encrypted text: \( (C_1, C_2, C_3) \), where \( C_1 \) is a point in \( G_1 \), \( C_2 \) is the message hash, and \( C_3 \) is the encrypted message.
• Compute the point: \( C_1' = sk_{user} \cdot C_1 \), where \( sk_{user} \) is the private key (scalar) and \( C_1 \) belongs to \( G_1 \).
• Pairing: \( e(C_1', G_2) \), where \( C_1' \) belongs to \( G_1 \) and \( G_2 \) is the base point in \( G_2 \). This pairing computes an element in the target group \( G_T \).
• Derive the session key: \( S' = H(e(C_1', G_2)) \), where \( H \) is the cryptographic hash function applied to the pairing result.
• Decrypt the message: \( m[i] = C_3[i] \oplus S'[i \mod \text{len}(S')] \)
• Verify integrity: The hash \( H(m \parallel S') \) of the decrypted message must match the original hash.
package main import ( "crypto/rand" "crypto/sha256" "encoding/asn1" "encoding/hex" "fmt" "log" "math/big" "github.com/cloudflare/circl/ecc/bls12381" "github.com/cloudflare/circl/ecc/bls12381/ff" ) // Structure for ASN.1 serialization type CiphertextBLS struct { C1 []byte C2 *big.Int C3 []byte } // Generate the master public key func generateMasterPublicKey(masterSecret *ff.Scalar) *bls12381.G2 { masterPublicKey := new(bls12381.G2) masterPublicKey.ScalarMult(masterSecret, bls12381.G2Generator()) // mpk = msk ⋅ G₂ return masterPublicKey } // Generate the user's private key func generatePrivateKey(masterSecret *ff.Scalar, userID string) *ff.Scalar { hash := sha256.Sum256([]byte(userID)) userSecret := new(ff.Scalar) userSecret.SetBytes(hash[:]) userSecret.Mul(masterSecret, userSecret) // sk = msk ⋅ H(ID) return userSecret } // Generate the user's public key func generatePublicKeyForUser(masterPublicKey *bls12381.G2, userID string) *bls12381.G2 { hash := sha256.Sum256([]byte(userID)) userScalar := new(ff.Scalar) userScalar.SetBytes(hash[:]) userPublicKey := new(bls12381.G2) userPublicKey.ScalarMult(userScalar, masterPublicKey) // pk = H(ID) ⋅ mpk return userPublicKey } // Encryption with BLS12-381 func encryptBLS(message string, publicKey *bls12381.G2) (*bls12381.G1, *big.Int, []byte) { messageBytes := []byte(message) order := new(big.Int).SetBytes(bls12381.Order()) k, err := rand.Int(rand.Reader, order) if err != nil { log.Fatal(err) } kScalar := new(ff.Scalar) kScalar.SetBytes(k.Bytes()) C1 := new(bls12381.G1) C1.ScalarMult(kScalar, bls12381.G1Generator()) // C₁ = k ⋅ G₁ pairingResult := bls12381.Pair(C1, publicKey) // e(C₁, pk) hash := sha256.New() pairingBytes, _ := pairingResult.MarshalBinary() hash.Write(pairingBytes) sessionKey := hash.Sum(nil) // S = H(e(C₁, pk)) hash.Reset() hash.Write(messageBytes) hash.Write(sessionKey) C2 := new(big.Int).SetBytes(hash.Sum(nil)) // C₂ = H(m || S) encryptedMessage := make([]byte, len(messageBytes)) // C3[i] = m[i] ⊕ S[i mod len(S)] for i := range messageBytes { encryptedMessage[i] = messageBytes[i] ^ sessionKey[i%len(sessionKey)] } return C1, C2, encryptedMessage // Encrypted text: (C₁, C₂, C₃) } // Decryption with BLS12-381 func decryptBLS(C1 *bls12381.G1, C2 *big.Int, encryptedMessage []byte, privateKey *ff.Scalar) string { C1MulPrivate := new(bls12381.G1) C1MulPrivate.ScalarMult(privateKey, C1) // C'₁ = sk * C₁ pairingResult := bls12381.Pair(C1MulPrivate, bls12381.G2Generator()) // e(C'₁, G₂) hash := sha256.New() pairingBytes, _ := pairingResult.MarshalBinary() hash.Write(pairingBytes) sessionKey := hash.Sum(nil) // S' = H(e(C'₁, G₂)) decryptedMessage := make([]byte, len(encryptedMessage)) // m[i] = C3[i] ⊕ S'[i mod len(S')] for i := range encryptedMessage { decryptedMessage[i] = encryptedMessage[i] ^ sessionKey[i%len(sessionKey)] } hash.Reset() hash.Write(decryptedMessage) hash.Write(sessionKey) expectedHash := new(big.Int).SetBytes(hash.Sum(nil)) // H(m || S') if expectedHash.Cmp(C2) != 0 { log.Fatal("Error: Integrity compromised") } return string(decryptedMessage) // Decrypted message } // Serialize to ASN.1 func serializeToASN1BLS(C1 *bls12381.G1, C2 *big.Int, encryptedMessage []byte) ([]byte, error) { C1Bytes := C1.Bytes() cipher := CiphertextBLS{ C1: C1Bytes, C2: C2, C3: encryptedMessage, } return asn1.Marshal(cipher) } // Deserialize from ASN.1 func deserializeFromASN1BLS(serialized []byte) (*bls12381.G1, *big.Int, []byte, error) { var cipher CiphertextBLS _, err := asn1.Unmarshal(serialized, &cipher) if err != nil { return nil, nil, nil, err } C1 := new(bls12381.G1) _ = C1.SetBytes(cipher.C1) return C1, cipher.C2, cipher.C3, nil } func main() { // Fixed data userID := "user@example.com" message := "This is a secret message!" masterSecret := new(ff.Scalar) masterSecret.SetUint64(1234567890123456789) // Generate the master public key masterPublicKey := generateMasterPublicKey(masterSecret) // Generate the user's private key privateKey := generatePrivateKey(masterSecret, userID) // Generate the user's public key userPublicKey := generatePublicKeyForUser(masterPublicKey, userID) // Display keys in hexadecimal format fmt.Printf("Master Public Key: %x\n", masterPublicKey.Bytes()) privateKeyBytes, err := privateKey.MarshalBinary() if err != nil { log.Fatal("Error serializing private key: ", err) } fmt.Printf("User Private Key: %x\n", privateKeyBytes) fmt.Printf("User Public Key: %x\n", userPublicKey.Bytes()) // Encrypt the message C1, C2, encryptedMessage := encryptBLS(message, userPublicKey) fmt.Printf("Encrypted message:\nC1 = %x\nC2 = %x\n", C1.Bytes(), C2) fmt.Printf("Encrypted message (hex): %x\n", encryptedMessage) // Serialize the ciphertext serialized, err := serializeToASN1BLS(C1, C2, encryptedMessage) if err != nil { log.Fatal("Error serializing: " + err.Error()) } fmt.Printf("Serialized ciphertext (ASN.1, hex):\n%s\n", hex.EncodeToString(serialized)) // Deserialize the ciphertext desC1, desC2, desEncryptedMessage, err := deserializeFromASN1BLS(serialized) if err != nil { log.Fatal("Error deserializing: " + err.Error()) } // Decrypt the message decryptedMessage := decryptBLS(desC1, desC2, desEncryptedMessage, privateKey) fmt.Printf("Decrypted message: %s\n", decryptedMessage) }
To ensure the confidentiality of the counter of each voting machine or poll worker, we use symmetric encryption with the key shared between the poll worker, electoral authority, and auditor, through secret key sharing between three parties, also utilizing the BLS12-381 elliptic curve.
• Each party (A, B, and C) generates a private value based on its ID multiplied by the master private key \( sk_{\text{master}} \):
• \( a = H(\text{ID}_A) \cdot sk_{\text{master}}, \quad b = H(\text{ID}_B) \cdot sk_{\text{master}}, \quad c = H(\text{ID}_C) \cdot sk_{\text{master}} \)
• Each party calculates the points in \( G_1 \) and \( G_2 \) by multiplying their private value by the master public keys \( pk_{\text{master1}} \) (in \( G_1 \)) and \( pk_{\text{master2}} \) (in \( G_2 \)):
• \( \mathbf{p}_a = a \cdot pk_{\text{master1}}, \quad \mathbf{q}_a = a \cdot pk_{\text{master2}} \)
• \( \mathbf{p}_b = b \cdot pk_{\text{master1}}, \quad \mathbf{q}_b = b \cdot pk_{\text{master2}} \)
• \( \mathbf{p}_c = c \cdot pk_{\text{master1}}, \quad \mathbf{q}_c = c \cdot pk_{\text{master2}} \)
• A calculates its shared key using the points from B and C: \( k_1 = e(\mathbf{p}_b, \mathbf{q}_c)^a \)
• B calculates its shared key using the points from A and C: \( k_2 = e(\mathbf{p}_c, \mathbf{q}_a)^b \)
• C calculates its shared key using the points from A and B: \( k_3 = e(\mathbf{p}_a, \mathbf{q}_b)^c \)
• After calculating the shared keys, check if: \( H(k_1) = H(k_2) = H(k_3) \)
• If the keys are equal, the key exchange process was successful.
package main import ( "crypto/sha256" "fmt" "github.com/cloudflare/circl/ecc/bls12381" "github.com/cloudflare/circl/ecc/bls12381/ff" ) // Hash to Scalar (used to derive values from ID) func hashToScalar(ID string) *ff.Scalar { hash := sha256.Sum256([]byte(ID)) scalar := new(ff.Scalar) scalar.SetBytes(hash[:]) return scalar } // Generate a user's private key from the master key func generatePrivateKey(masterKey *ff.Scalar, userID string) *ff.Scalar { userScalar := hashToScalar(userID) privateKey := new(ff.Scalar) privateKey.Mul(masterKey, userScalar) // sk = msk ⋅ H(ID) return privateKey } // Generate a user's public key in G1 from the master public key of G1 func generatePublicKeyForUserG1(masterPublicKeyG1 *bls12381.G1, userID string) *bls12381.G1 { userScalar := hashToScalar(userID) publicKey := new(bls12381.G1) publicKey.ScalarMult(userScalar, masterPublicKeyG1) // pk = H(ID) ⋅ mpkG1 return publicKey } // Generate a user's public key in G2 from the master public key of G2 func generatePublicKeyForUserG2(masterPublicKeyG2 *bls12381.G2, userID string) *bls12381.G2 { userScalar := hashToScalar(userID) publicKey := new(bls12381.G2) publicKey.ScalarMult(userScalar, masterPublicKeyG2) // pk = H(ID) ⋅ mpkG2 return publicKey } func main() { // Generate the master private key (msk) masterKey := new(ff.Scalar) masterKey.SetUint64(1234567890) // Generate the master public keys (mpkG1) for G1 and (mpkG2) for G2 from the master private key masterPublicKeyG1 := new(bls12381.G1) masterPublicKeyG1.ScalarMult(masterKey, bls12381.G1Generator()) // mpkG1 = msk ⋅ G1 masterPublicKeyG2 := new(bls12381.G2) masterPublicKeyG2.ScalarMult(masterKey, bls12381.G2Generator()) // mpkG2 = msk ⋅ G2 // User IDs userIDs := []string{"user1", "user2", "user3"} // Generate private and public keys for each user var privateKeys map[string]*ff.Scalar = make(map[string]*ff.Scalar) var publicKeysG1 map[string]*bls12381.G1 = make(map[string]*bls12381.G1) var publicKeysG2 map[string]*bls12381.G2 = make(map[string]*bls12381.G2) // Generate private and public keys for each user for _, userID := range userIDs { privateKeys[userID] = generatePrivateKey(masterKey, userID) publicKeysG1[userID] = generatePublicKeyForUserG1(masterPublicKeyG1, userID) publicKeysG2[userID] = generatePublicKeyForUserG2(masterPublicKeyG2, userID) fmt.Printf("User: %s\n", userID) priv, _ := privateKeys[userID].MarshalBinary() fmt.Printf(" PrivateKey: %x\n", priv) fmt.Printf(" PublicKeyG1: %x\n", publicKeysG1[userID].Bytes()) fmt.Printf(" PublicKeyG2: %x\n", publicKeysG2[userID].Bytes()) } // Calculating the shared keys using the pairing // k1 = Pair(publicKeysG1["user2"], publicKeysG2["user3"]) ^ privateKeys["user1"] k1 := bls12381.Pair(publicKeysG1["user2"], publicKeysG2["user3"]) k1.Exp(k1, privateKeys["user1"]) // k2 = Pair(publicKeysG1["user3"], publicKeysG2["user1"]) ^ privateKeys["user2"] k2 := bls12381.Pair(publicKeysG1["user3"], publicKeysG2["user1"]) k2.Exp(k2, privateKeys["user2"]) // k3 = Pair(publicKeysG1["user1"], publicKeysG2["user2"]) ^ privateKeys["user3"] k3 := bls12381.Pair(publicKeysG1["user1"], publicKeysG2["user2"]) k3.Exp(k3, privateKeys["user3"]) // Using MarshalBinary to compare the shared keys k1Bytes, _ := k1.MarshalBinary() k2Bytes, _ := k2.MarshalBinary() k3Bytes, _ := k3.MarshalBinary() fmt.Println("Shared Secrets") fmt.Printf(" Shared1: %x\n", k1Bytes) fmt.Printf(" Shared2: %x\n", k2Bytes) fmt.Printf(" Shared3: %x\n", k3Bytes) if string(k1Bytes) == string(k2Bytes) && string(k2Bytes) == string(k3Bytes) { fmt.Println("The shared keys are equal!") } else { fmt.Println("The shared keys are not equal.") } }
Generate User's Private Key:./edgetk -pkey setup -algorithm bls12381 -master "Master.pem" [-pass "passphrase"] -pub "MasterPublic.pem"
"Parse the Keys:"./edgetk -pkey keygen -algorithm bls12381 -master "Master.pem" [-pass "pass"] -prv "Private.pem" [-passout "pass"] -id "UID"
Generate Signature:./edgetk -pkey text -key "Master.pem" [-pass "passphrase"]
./edgetk -pkey text -key "Private.pem" [-pass "passphrase"]
./edgetk -pkey text -key "MasterPublic.pem"
Transmit Signature:./edgetk -pkey aggregate-vote -key "Private.pem" FILE > sign.txt
Transmit the Blind Vote:sign=$(cat sign.txt|grep "Aggregated"|awk '{print $2}')
Verify the Signature:cat sign.txt|grep "Blind_Message"|awk '{print $2}' > blindvote.txt
./edgetk -pkey verify-aggregate-vote -key "MasterPublic.pem" -pubs "UID" -msgs "blindvote.txt" -signature $sign
echo $?
This example uses the standard US FIPS 180-2 Secure Hash Standard (SHS) SHA2 for pre-hashing.
Copyright (c) 2025 Pedro F. Albanese <pedroalbanese@hotmail.com>
Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.