Muito se disse sobre um sistema de votação seguro nas eleições de 2022, mas em caráter meramente especulativo. Aventavam haver uma forma de fraudar as eleições à distância, embora as urnas eletrônicas não possuam conexão com a web. A despeito disso, é perfeitamente possível criar um sistema seguro e auditável de votação baseado em criptografia de curva elíptica (Problema de Logaritmo Discreto), contudo, a forma mais eficaz é unindo três paradigmas da criptografia moderna que são Assinaturas Cegas, Assinaturas Agregadas e Prova de Conhecimento Zero (ZKP) Fiat–Shamir Heurística, i.e. não interativa (NIZK). Com o uso de assinaturas cegas, garante-se a confidencialidade do voto, a prova de conhecimento zero valida a chave privada do eleitor sem a revelar, e com o uso de assinaturas agregadas, obtem-se uma única assinatura digital de 48 bytes (24 grupos de 4 caracteres hexadecimais) que é capaz de verificar todos os votos numa única operação. Utilizando Assinaturas Agregadas BLS (Boneh–Lynn–Shacham) sobre a curva elíptica BLS12-381, que oferece algo entre 121-bit e 128-bit de segurança.
O esquema IBE (Identity Based Encryption) é similar ao sistema PKI (Public Key Infrastructure), mas com algumas diferenças-chave. No IBE, uma chave mestra é gerada, e as chaves privadas são derivadas da chave mestra e de um ID. Existe apenas uma chave pública, que é a chave pública mestra. Isto torna desnecessária a existência de um diretório de chaves públicas.
• Chave Mestra: \( sk_{master} \in \mathbb{Z}_n \), onde \( \mathbb{Z}_n \) é o campo finito usado para as chaves privadas e \( n \) é a ordem da curva elíptica.
• Chave Pública Mestra: \( pk_{master} = sk_{master} \cdot G_2 \), onde \( G_2 \) é o ponto base da curva elíptica, e \( pk_{master} \) é a chave pública mestra.
• Chave Privada do Usuário: \( sk_{user} = sk_{master} \cdot h_{ID} \), onde \( sk_{master} \) é a chave privada mestra, e \( h_{ID} \) é o hash do ID do usuário.
• Chave Pública do Usuário: \( pk_{user} = pk_{master} \cdot h_{ID} \), calculada automaticamente.
• Compromisso: \( C = r \cdot G_2 \), onde \( r \) é um número aleatório gerado de forma segura e \( G_2 \) é o ponto base da curva elíptica.
• Desafio: \( \chi = H(C \parallel m) \), onde \( H(C \parallel m) \) é o hash do compromisso concatenado com a mensagem.
• Resposta: \( s = sk_{\text{user}} \cdot \chi \), onde \( sk_{\text{user}} \) é a chave privada do usuário e \( \chi \) é o desafio.
• Verificação: Calcule se \( e(s \cdot G_1, G_2) = e(G_1, C + (\chi \cdot pk_{\text{user}})) \), onde \( e \) é o emparelhamento bilinear.
• Se a igualdade for verdadeira, a prova é válida.
• Geração do fator: \( r \in \mathbb{Z}_n \), número aleatório gerado de forma segura.
• Cegamento da mensagem: \( m' = r \cdot H(m) \), onde \( H(m) \) é o hash da mensagem.
• Descegação da mensagem: \( m = r^{-1} \cdot m' \), onde \( r^{-1} \) é o inverso de \( r \), utilizando o Algoritmo de Euclides Estendido.
• Assinatura da Mensagem: Calcule \( \sigma = H(m) \cdot sk_{user} \), onde \( H(m) \) é o hash da mensagem e \( sk_{user} \) é a chave privada.
• Verificação da Assinatura: Calcule se \( e(\sigma, G_2) = e(H(m), pk_{user}) \), onde \( e \) é o emparelhamento bilinear.
• Se essa igualdade for verdadeira, a assinatura individual é válida.
• Assinatura da Mensagem: \( \sigma_i = sk_i \cdot H(m_i) \), onde \( H(m_i) \) é o hash da mensagem \( m_i \), e \( sk_i \) é a chave privada do usuário da vez.
• Agregação das Assinaturas: \( \sigma_{\text{agg}} = \sum_{i} \sigma_i \), onde \( \sigma_{\text{agg}} \) é a assinatura agregada e \( \sigma_i \) representa as assinaturas individuais dos usuários.
• Verificação da Assinatura Agregada: \( e(\sigma_{\text{agg}}, G_2) = \prod_{i} e(H(m_i), pk_i) \), onde \( e \) é a função de emparelhamento.
• Se essa igualdade for verdadeira, a assinatura agregada é válida.
package main import ( "crypto/rand" "crypto/sha256" "fmt" "github.com/cloudflare/circl/ecc/bls12381" "github.com/cloudflare/circl/ecc/bls12381/ff" ) // Gera um escalar aleatório seguro func randomScalar() *ff.Scalar { scalar := new(ff.Scalar) buf := make([]byte, 32) // BLS12-381 usa 32 bytes _, err := rand.Read(buf) if err != nil { panic("Erro ao gerar número aleatório") } scalar.SetBytes(buf) return scalar } // Hash para Scalar (usado para derivar valores do ID) func hashToScalar(ID string) *ff.Scalar { hash := sha256.Sum256([]byte(ID)) scalar := new(ff.Scalar) scalar.SetBytes(hash[:]) return scalar } // Gera chave privada de um usuário a partir da chave mestra 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 } // Gera chave pública de um usuário a partir da chave mestra 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 } // Gera fator de cegamento aleatório func generateBlindFactor() *ff.Scalar { return randomScalar() } // Converte mensagem para um ponto na curva G₁ func hashToG1(message []byte) *bls12381.G1 { hashMessage := new(bls12381.G1) hashMessage.Hash(message, nil) return hashMessage } // Cega a mensagem func blindMessage(originalG1 *bls12381.G1, blindFactor *ff.Scalar) *bls12381.G1 { blindedMessage := new(bls12381.G1) blindedMessage.ScalarMult(blindFactor, originalG1) // m' = r ⋅ H(m) return blindedMessage } // Descega a mensagem cegada 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 } // Assinar mensagem com a chave privada 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 } // Verifica assinatura individual 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) } // Agrega assinaturas func aggregateSignatures(signatures []*bls12381.G1) *bls12381.G1 { aggSig := new(bls12381.G1) aggSig.SetIdentity() for _, sig := range signatures { aggSig.Add(aggSig, sig) } return aggSig } // Verifica assinatura agregada 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 da mensagem } e1 := bls12381.Pair(aggSignature, bls12381.G2Generator()) // Pareamento da assinatura agregada com G₂ e2 := bls12381.Pair(hashMessages[0], pubKeys[0]) // Par da primeira mensagem com a chave pública for i := 1; i < len(hashMessages); i++ { e2.Mul(e2, bls12381.Pair(hashMessages[i], pubKeys[i])) // Multiplicando emparelhamentos } return e1.IsEqual(e2) // Verifica se os emparelhamentos são iguais } // Gerar compromisso para prova ZKP func generateCommitment(secret *ff.Scalar, generator *bls12381.G2) *bls12381.G2 { commitment := new(bls12381.G2) commitment.ScalarMult(secret, generator) // C = r ⋅ G₂ return commitment } // Gerar desafio para prova ZKP 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 } // Gerar resposta para prova ZKP func generateResponse(secret *ff.Scalar, challenge *ff.Scalar) *ff.Scalar { response := new(ff.Scalar) response.Mul(secret, challenge) // s = sk ⋅ χ return response } // Verificar prova ZKP 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() { // Configuração da chave mestra masterKey := new(ff.Scalar) masterKey.SetUint64(1234567890) basePointG2 := bls12381.G2Generator() var masterPublicKey bls12381.G2 masterPublicKey.ScalarMult(masterKey, basePointG2) // Criar chaves individuais 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) } // Criar mensagens messages := [][]byte{[]byte("Sim"), []byte("Não"), []byte("Sim")} var signatures []*bls12381.G1 var blindedMessages []*bls12381.G1 var blindFactors []*ff.Scalar var originalG1Hashes []*bls12381.G1 // Cegar e assinar 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) } // Verificação de assinaturas individuais for i, blindedMsg := range blindedMessages { if verifySignature(blindedMsg.Bytes(), signatures[i], pubKeys[i]) { fmt.Printf("Assinatura válida para %s\n", userIDs[i]) } else { fmt.Printf("Assinatura inválida para %s\n", userIDs[i]) } } // Agregar assinaturas aggSignature := aggregateSignatures(signatures) // Verificar assinatura agregada if verifyAggregateSignature(blindedMessages, aggSignature, pubKeys) { fmt.Println("Assinatura agregada válida!") } else { fmt.Println("Assinatura agregada inválida!") } // Criar e verificar provas de conhecimento zero 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("Prova ZKP válida para %s\n", userID) } else { fmt.Printf("Prova ZKP inválida para %s\n", userID) } } // Verificação da integridade da mensagem fmt.Println("\nVerificação da Integridade da Mensagem") for i, blindedMsg := range blindedMessages { unblindedG1 := unblindMessage(blindedMsg, blindFactors[i]) if unblindedG1.IsEqual(originalG1Hashes[i]) { fmt.Printf("Mensagem %d validada: o ponto G1 descegado é idêntico ao original!\n", i+1) fmt.Println(unblindedG1) } else { fmt.Printf("Erro na Mensagem %d: os pontos G1 não coincidem!\n", i+1) } } }
Para garantir confidencialidade do Fator de Cegamento, utilizamos criptografia assimétrica com a mesma curva elíptica para criptografar o Fator de Cegamento com a chave pública do usuário, obtida do produto da chave pública mestra e o hash do ID do usuário, que, por sua vez, só pode ser descriptografado com a chave privada do usuário. Essa operação é realizada no ato da assinatura (o voto) e garante confidencialidade e integridade do Fator de Cegamento.
• Selecione um valor aleatório \( k \in \mathbb{Z}_n \), onde \( n \) é a ordem do grupo \( G_1 \).
• Calcule o ponto: \( C_1 = k \cdot G_1 \), onde \( G_1 \) é o ponto base no grupo \( G_1 \), usado para criptografia.
• Emparelhamento: \( e(C_1, pk_{user}) \), onde \( C_1 \) pertence a \( G_1 \) e \( pk_{user} \) pertence a \( G_2 \). Esse emparelhamento computa um elemento no grupo alvo \( G_T \).
• Derive a chave de sessão: \( S = H(e(C_1, pk_{user})) \), onde \( H \) é uma função hash criptográfica aplicada ao resultado do emparelhamento.
• Hash da mensagem: \( C_2 = H(m \parallel S) \)
• Criptografe a mensagem: \( C_3[i] = m[i] \oplus S[i \mod \text{len}(S)] \)
• Texto cifrado: \( (C_1, C_2, C_3) \), onde \( C_1 \) é um ponto em \( G_1 \), \( C_2 \) é o hash da mensagem e \( C_3 \) é a mensagem criptografada.
• Calcule o ponto: \( C_1' = sk_{user} \cdot C_1 \), onde \( sk_{user} \) é a chave privada (scalar) e \( C_1 \) pertence a \( G_1 \).
• Emparelhamento: \( e(C_1', G_2) \), onde \( C_1' \) pertence a \( G_1 \) e \( G_2 \) é o ponto base em \( G_2 \). Esse emparelhamento computa um elemento no grupo alvo \( G_T \).
• Derive a chave de sessão: \( S' = H(e(C_1', G_2)) \), onde \( H \) é a função hash criptográfica aplicada ao resultado do emparelhamento.
• Descriptografe a mensagem: \( m[i] = C_3[i] \oplus S'[i \mod \text{len}(S')] \)
• Verifique a integridade: O hash \( H(m \parallel S') \) da mensagem descriptografada deve corresponder ao hash original.
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" ) // Estrutura para serialização ASN.1 type CiphertextBLS struct { C1 []byte C2 *big.Int C3 []byte } // Geração da chave mestra pública func generateMasterPublicKey(masterSecret *ff.Scalar) *bls12381.G2 { masterPublicKey := new(bls12381.G2) masterPublicKey.ScalarMult(masterSecret, bls12381.G2Generator()) // mpk = msk ⋅ G₂ return masterPublicKey } // Geração da chave privada do usuário 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 } // Geração da chave pública do usuário 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 } // Criptografia com 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 // Texto cifrado: (C₁, C₂, C₃) } // Descriptografia com 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("Erro: Integridade comprometida") } return string(decryptedMessage) // Mensagem descriptografada } // Serializar para 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) } // Desserializar de 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() { // Dados fixos userID := "user@example.com" message := "This is a secret message!" masterSecret := new(ff.Scalar) masterSecret.SetUint64(1234567890123456789) // Geração da chave mestra pública masterPublicKey := generateMasterPublicKey(masterSecret) // Geração da chave privada do usuário privateKey := generatePrivateKey(masterSecret, userID) // Geração da chave pública do usuário userPublicKey := generatePublicKeyForUser(masterPublicKey, userID) // Exibir as chaves no formato hexadecimal fmt.Printf("Master Public Key: %x\n", masterPublicKey.Bytes()) privateKeyBytes, err := privateKey.MarshalBinary() if err != nil { log.Fatal("Erro ao serializar a chave privada: ", err) } fmt.Printf("User Private Key: %x\n", privateKeyBytes) fmt.Printf("User Public Key: %x\n", userPublicKey.Bytes()) // Criptografar a mensagem 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) // Serializar o texto cifrado serialized, err := serializeToASN1BLS(C1, C2, encryptedMessage) if err != nil { log.Fatal("Erro ao serializar: " + err.Error()) } fmt.Printf("Serialized ciphertext (ASN.1, hex):\n%s\n", hex.EncodeToString(serialized)) // Desserializar o texto cifrado desC1, desC2, desEncryptedMessage, err := deserializeFromASN1BLS(serialized) if err != nil { log.Fatal("Erro ao desserializar: " + err.Error()) } // Descriptografar a mensagem decryptedMessage := decryptBLS(desC1, desC2, desEncryptedMessage, privateKey) fmt.Printf("Decrypted message: %s\n", decryptedMessage) }
Para garantir confidencialidade do contador de cada urna ou mesário, utilizamos criptografia simétrica com a chave compartihada entre mesário, autoridade eleitoral e auditor, através do compartilhamento de chave secreta entre três partes, também utilizando a curva elíptica BLS12-381.
• Cada parte (A, B e C) gera um valor privado a partir de seu ID multiplicado pela chave privada mestra \( 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}} \)
• Cada parte calcula os pontos em \( G_1 \) e \( G_2 \) multiplicando seu valor privado pelas chaves públicas mestras \( pk_{\text{master1}} \) (em \( G_1 \)) e \( pk_{\text{master2}} \) (em \( 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 calcula sua chave compartilhada utilizando os pontos de B e C: \( k_1 = e(\mathbf{p}_b, \mathbf{q}_c)^a \)
• B calcula sua chave compartilhada utilizando os pontos de A e C: \( k_2 = e(\mathbf{p}_c, \mathbf{q}_a)^b \)
• C calcula sua chave compartilhada utilizando os pontos de A e B: \( k_3 = e(\mathbf{p}_a, \mathbf{q}_b)^c \)
• Após o cálculo das chaves compartilhadas, calcule se: \( H(k_1) = H(k_2) = H(k_3) \)
• Se as chaves forem iguais, o processo de troca de chaves foi bem-sucedido.
package main import ( "crypto/sha256" "fmt" "github.com/cloudflare/circl/ecc/bls12381" "github.com/cloudflare/circl/ecc/bls12381/ff" ) // Hash para Scalar (usado para derivar valores do ID) func hashToScalar(ID string) *ff.Scalar { hash := sha256.Sum256([]byte(ID)) scalar := new(ff.Scalar) scalar.SetBytes(hash[:]) return scalar } // Gera chave privada de um usuário a partir da chave mestra 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 } // Gera chave pública de um usuário em G1 a partir da chave mestra de 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 } // Gera chave pública de um usuário em G2 a partir da chave mestra de 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() { // Gerar a chave mestra privada (msk) masterKey := new(ff.Scalar) masterKey.SetUint64(1234567890) // Gerar a chave pública mestra (mpkG1) para G1 e (mpkG2) para G2 a partir da chave mestra privada masterPublicKeyG1 := new(bls12381.G1) masterPublicKeyG1.ScalarMult(masterKey, bls12381.G1Generator()) // mpkG1 = msk ⋅ G1 masterPublicKeyG2 := new(bls12381.G2) masterPublicKeyG2.ScalarMult(masterKey, bls12381.G2Generator()) // mpkG2 = msk ⋅ G2 // IDs dos usuários userIDs := []string{"user1", "user2", "user3"} // Gerar chaves privadas e públicas para cada usuário 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) // Gerar chaves privadas e públicas para cada usuário 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()) } // Calculando as chaves compartilhadas usando o par de bilinearidade (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"]) // Usando MarshalBinary para comparar as chaves compartilhadas 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("Chaves compartilhadas são iguais!") } else { fmt.Println("As chaves compartilhadas não são iguais.") } }
Gerar Chave Privada de Usuário:./edgetk -pkey setup -algorithm bls12381 -master "Master.pem" [-pass "passphrase"] -pub "MasterPublic.pem"
Analisar as Chaves:./edgetk -pkey keygen -algorithm bls12381 -master "Master.pem" [-pass "pass"] -prv "Private.pem" [-passout "pass"] -id "UID"
Gerar Assinatura:./edgetk -pkey text -key "Master.pem" [-pass "passphrase"]
./edgetk -pkey text -key "Private.pem" [-pass "passphrase"]
./edgetk -pkey text -key "MasterPublic.pem"
Transmitir Assinatura:./edgetk -pkey aggregate-vote -key "Private.pem" FILE > sign.txt
Transmitir o Voto Cego:sign=$(cat sign.txt|grep "Aggregated"|awk '{print $2}')
Verificar a Assinatura: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 $?
Este exemplo utiliza o padrão US FIPS 180-2 Secure Hash Standard (SHS) SHA2 para derivação de chave de sessão e pré-hash.
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.