On-chain Homomorphic Cryptography

May 05, 2023

@trizin

Homomorphic encryption is a kind of encryption that lets you work with data even when it's encrypted, without having to decrypt it first. This is helpful when you need to share data securely, vote, or do computations where everyone needs to use the same data but still keep it private.

There are different kinds of homomorphic encryption:

  • Partially Homomorphic Encryption (PHE)
  • Fully Homomorphic Encryption (FHE)

Getting fully homomorphic encryption to work on Ethereum is pretty tough. But for this example, we'll just focus on partially homomorphic encryption, which lets you add together encrypted data.

Elgamal Cryptosystem

The basic Elgamal cryptosystem is based on modular arithmetic and the discrete logarithm problem. It was invented by Taher Elgamal in 1985 and is widely used for secure communication.

The ElGamal cryptosystem involves a public key and a private key. The public key consists of a large prime number pp, a generator gg of the group ZpZ_p^*, and an element h=ga(modp)h = g^a \pmod p, where aa is a randomly chosen integer between 11 and p2p-2. The private key is the integer aa.

To encrypt a message mm, the sender chooses a random integer kk between 11 and p2p-2, computes c1=gk(modp)c_1 = g^k \pmod p and c2=hkm(modp)c_2 = h^k \cdot m \pmod p, and sends the pair (c1,c2)(c_1, c_2) to the receiver.

To decrypt the ciphertext (c1,c2)(c_1, c_2), the receiver computes s=c1a(modp)s = c_1^a \pmod p and then computes m=c2s1(modp)m = c_2 \cdot s^{-1} \pmod p, where s1s^{-1} is the modular inverse of ss modulo pp.

c2c1a(modp)=hkmgka(modp)=gkamgka(modp)=m\frac{c_2}{c_1^a} \pmod p =\frac{h^k \cdot m}{g^{ka}} \pmod p=\frac{g^{ka} \cdot m}{g^{ka}} \pmod p = m

Homomorphic properties

The basic ElGamal cryptosystem is multiplicative. To multiply two ciphertexts (c1,c2)(c_1,c_2) and (d1,d2)(d_1,d_2), their product can be computed as:

(c1,c2)(d1,d2)=(c1d1,c2d2)(c_1,c_2) \cdot (d_1,d_2) = (c_1d_1, c_2d_2)

Decrypting the resulting ciphertext using the private key aa, the following is obtained:

c2d2c1d1a(modp)=gk1agk2am1m2gk1agk2a(modp)=m1m2\frac{c_2d_2}{{c_1d_1}^a} \pmod p =\frac{g^{k_1a} \cdot g^{k_2a} \cdot m_1 \cdot m_2}{g^{k_1a} \cdot g^{k_2a}} \pmod p=m_1 \cdot m_2

Therefore, the ElGamal cryptosystem exhibits the homomorphic property of multiplication. This means that multiplying two ciphertexts together yields a new ciphertext that, when decrypted, reveals the product of the original plaintexts.

The standard ElGamal does not have homomorphic properties for addition. However, this can be achieved by using the multiplicative property and exponents, such as 2325=282^3 \cdot 2^5 = 2^8. Thus, encrypting gmg^m allows for additive homomorphism such that:

(c1,c2)(d1,d2)=(c1d1,c2d2)(c_1,c_2) \cdot (d_1,d_2) = (c_1d_1, c_2d_2)

Decrypting the resulting ciphertext using the private key aa, the following is obtained:

c2d2c1d1a(modp)=gk1agk2agm1gm2gk1agk2a(modp)=gm1+m2\frac{c_2d_2}{{c_1d_1}^a} \pmod p =\frac{g^{k_1a} \cdot g^{k_2a} \cdot g^{m_1} \cdot g^{m_2}}{g^{k_1a} \cdot g^{k_2a}} \pmod p=g^{m_1 + m_2}

This means that multiplying two ciphertexts together yields a new ciphertext that, when decrypted, reveals the sum of the original plaintexts' exponents. It is important to note that this technique provides additive homomorphism in the exponent space and not in the plaintext space directly. To obtain the sum of the original plaintexts, one would need to take the logarithm of the decrypted result, such that:

logg(gm1+m2)=m1+m2\log_g (g^{m1+m2}) = m1+m2

Computing m1+m2m1+m2 is feasible as long as the sum of encrypted values remains small. However, when the sum becomes very large, it might become impractical due to the difficulty of solving the discrete logarithm problem.

Elgamal cryptosystem exhibits homomorphic properties for multiplication but not for addition in its standard form. However, by encrypting gmg^m instead of mm, it is possible to achieve additive homomorphism in the exponent space, which is very useful!

Security

ElGamal cryptosystem is based on the hardness of the Discrete Logarithm Problem (DLP), which means that its security relies on the difficulty of finding the discrete logarithm in a finite field. In other words, given a large prime number p, a generator g, and an element y in the group generated by g, it is computationally infeasible to find the exponent x such that gxy(modp)g^x \equiv y \pmod{p}.

The security of ElGamal can be enhanced by selecting larger prime numbers for p and using secure random number generators for selecting key pairs. The choice of large primes makes it more difficult for an attacker to solve the DLP through brute force or other methods like Pollard's rho algorithm or Index Calculus.

ElGamal is considered to be semantically secure under chosen-plaintext attacks (CPA), which means that an attacker cannot gain any information about the plaintext by observing multiple ciphertexts created using the same public key.

The suggested prime number size for ElGamal cryptosystem is at least 2048 bits for general use and 3072 bits or more for sensitive applications or long-term security.

This isn't going to work since EVM supports numbers up to 256 bits unless some special library is being used.

The good news is, the security of Elgamal can be increased by the use of Elliptic Curves, aka ECC-ElGamal. The key size of ECC-ElGamal can be much smaller while providing the same level of security as traditional ElGamal cryptosystems. For example, a 256-bit key in ECC-ElGamal offers roughly the same level of security as a 3072-bit key in traditional ElGamal.

Elliptic Curves

Elliptic curves are algebraic structures that are used extensively in modern cryptography due to their unique properties and high efficiency. They are defined by a mathematical equation in the form:

y2=x3+ax+by^2 = x^3 + ax + b

where x and y are the coordinates on the curve, and a and b are constants that define the specific curve. The set of points (x, y) satisfying this equation, along with an additional point at infinity called the identity element, form an elliptic curve group.

There are some properties used to define an Elliptic Curve that you should know about:

  • N: N is the order of the elliptic curve group, which is the number of points on the curve (including the point at infinity). It is an important parameter because it determines the size of the group and influences the security level of cryptographic operations based on that curve. The larger N is, the more secure the elliptic curve cryptography will be against attacks such as brute-force and Pollard's rho algorithm.
  • P: P is the prime number that defines the finite field over which the elliptic curve is defined. The coordinates of the points on the curve and operations on these points are performed modulo P. A larger prime P provides a larger field, which offers better security against certain attacks while also increasing computation complexity.
  • A and B: These are coefficients that define the shape of the elliptic curve using the equation y2=x3+ax+by^2 = x^3 + ax + b
  • G: This is the base point (or generator point) of the elliptic curve, which is a specific point on the curve with coordinates (x, y) that is used as a starting point for various operations in elliptic curve cryptography. The order of G is a large prime number, and it plays an important role in ensuring the security of cryptographic schemes based on elliptic curves.

[!warning]

It's strongly recommended to use the elliptic curves that are proven to be secure, don't use your own unless you're a cryptographer.

You can find the parameters of secp256r1 Elliptic Curve from this link. This is the curve used in the example above.

ECC-Elgamal

ECC-ElGamal is an adaptation of the ElGamal cryptosystem that utilizes elliptic curves for improved security and efficiency. In ECC-ElGamal, the public and private keys, as well as the encryption and decryption operations, are based on elliptic curve arithmetic rather than modular arithmetic.

You can read more about Elliptic Curves from this awesome guide.

Key Generation, Encryption and Decryption

Key Generation

  • Choose an elliptic curve EE over a finite field FpF_p with a large prime number pp.
  • Choose a base point GG as generator on the curve EE, which generates a subgroup of large prime order nn.
  • Select a random private key x(1<x<n)x (1 < x < n).
  • Compute the public key y=xGy = x\cdot{G}

Encryption

  • Choose a random integer k(1<k<n)k (1 < k < n)
  • Compute C1=kGC_1 = k \cdot G
  • Compute C2=ky+GmC_2 = k \cdot y + G \cdot m, where mm is the secret message.
  • The encrypted message is the pair of points (C1,C2)(C_1, C_2)

Decryption

  • The decrypted message MM is obtained by calculating M=C2xC1M = C_2 - x \cdot C_1 which when substituted in the formula is equal to:
kxG+GmkxG=Gmk \cdot x \cdot G + G \cdot m - k \cdot x \cdot G = G \cdot m
  • This gives the original point GmG \cdot m, from which the secret message mm can be extracted. Extracting the message requires solving the Elliptic Curve Discrete Logarithm Problem (ECDLP), which is finding the integer mm such that Gm=MG \cdot m = M. This can be computed easily as long as MM remains small.

Homomorphic Properties

ECC-Elgamal supports additive homomorphism. The encrypted sum of two cipher-texts (C1,C2)(C_1, C_2) and (D1,D2)(D_1,D_2) can be calculated as:

(C1,C2)+(D1,D2)=(C1+D1,C2+D2)(C1+D1)=G(k1+k2)(C2+D2)=y(k1+k2)+G(m1+m2)\begin{aligned} (C_1, C_2) + (D_1, D_2) &= (C_1+D_1, C_2+D_2) \\ (C_1 + D_1) &= G\cdot(k_1+k_2) \\ (C_2 + D_2) &= y\cdot(k_1+k_2) + G\cdot(m_1 + m_2) \end{aligned}

Pretty straightforward, huh? The cipher-text can also be multiplied by some integer nn simply by multiplying both points by n:

n(C1,C2)=(nC1,nC2)nC1=G(nk)nC2=y(nk)+G(nm)\begin{aligned} n \cdot (C_1, C_2) &= (n \cdot C_1, n \cdot C_2) \\ n \cdot C_1 &= G \cdot (n \cdot k) \\ n \cdot C_2 &= y \cdot (n \cdot k) + G \cdot (n \cdot m) \end{aligned}

It isn't possible to multiply two cipher-texts with each other because doing so would involve multiplying two points on the elliptic curve, which is not directly supported in ECC. Multiplying points on an elliptic curve does not have a straightforward meaning or interpretation like adding points does, and it is not part of the standard operations in ECC.

This is good enough for our purposes, time for the implementation!

Building ECC-Elgamal Voting System on Ethereum

Here is a high-level overview of the voting phase:

sequenceDiagram participant Organization participant User participant SmartContract Organization ->> Organization: Generate key pair Organization ->> SmartContract: Public key SmartContract ->> User: Public key User->>SmartContract: Encrypted vote, stake and proof SmartContract -->> SmartContract: Verify the zk proof SmartContract -->> SmartContract: Homomorphically update the sum of stake weighted encrypted votes SmartContract->>User: Encrypted sum is accessible

Elliptic Curve

The Elliptic Curve secp256r1 is used in this example which is a secure and widely-used elliptic curve cryptography (ECC) algorithm. It is also known as prime256v1 or NIST P-256, and is part of the Suite B cryptographic algorithms recommended by the National Security Agency (NSA).

Threshold Encryption

The organization needs to create a pair of keys - a public key and a private key. The public key is submitted to the smart contract, while the private key is kept secret by the organization. Although there's an issue; the private key allows the owner to view each person's vote.

To make sure that no one person can see individual votes, the organization can use a method called threshold encryption. This means breaking the private key into several pieces and giving each piece to a different trusted member of the organization or a third-party. A predetermined number of these pieces (the threshold) are required to decrypt the results, ensuring that no single individual can access the information without collaboration from others.

However, I'm not going to apply threshold encryption for the sake of simplicity.

Dependencies

To get started, you will first need to have Python installed on your computer. If you don't already have it, you can download Python from the official website: https://www.python.org/downloads/

Once you have Python installed, you will need to install a few additional packages to work with elliptic curve cryptography and Ethereum development. You can do this by using the Python package manager, pip.

Open your command prompt or terminal and enter the following command:

$ pip install tinyec eth-brownie pycrypto

Diving into the code

You can access the full code for this demo via this Github repo.

We'll use only one file named main.py inside scripts folder. Begin by importing the packages.

# imports import brownie import tinyec.ec as ec from tinyec import registry from Crypto.Random import get_random_bytes from brownie import VotingContract

Next, define the organization and voter addresses.

accounts = brownie.accounts admin = accounts[0] voter1 = accounts[1] voter2 = accounts[2]

Set global parameters of the curve

curve = registry.get_curve("secp256r1") n = curve.field.n G = curve.g

Define encryption and decryption functions

Write decrypt and encrypt functions following the guidelines provided in the ECC-ElGamal section.

Encryption
def encrypt(v, n, y, G): k = random.randint(1, n - 1) C1 = k * G C2 = k * y + G * v return C1, C2, k
Decryption
def decrypt(C1, C2 x): S = x * C1 M = C2 - S i = 0 while True: if i * G == M: return i i += 1

In the decrypt function, the while loop is required to find the original value of M and it should be computationally feasible as long as the M remains small, see the Decryption part of ECC-Elgamal section to learn more.

Generating the key-pair and deploying the smart contract

Here are the functions to generate the private and public key-pair and deploy the smart contract, passing the public key as a parameter.

def genkey(G, n): x = int.from_bytes(get_random_bytes(32), "big") % n # private key y = x * G # public key return x, y def deploy_voting_contract(): x, y = genkey(G, n) contract = VotingContract.deploy(y.x, y.y, {"from": admin}) # deploy the contract return x, y, contract

The constructor function in the smart contract takes two integers, yx and yy which represent the components of the public key.

contract VotingContract { ... // Define the parameters of the secp256r1 elliptic curve uint256 private constant N = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551; uint256 private constant A = 0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc; uint256 private constant B = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b; uint256 private constant P = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff; uint256 private constant Gx = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296; uint256 private constant Gy = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5; Point private G = Point(Gx, Gy); // Generator point Point public Y; // Public key, to be set by the constructor constructor(uint256 yx, uint256 yy) { Y = Point(yx, yy); require(EllipticCurve.isOnCurve(Y.x, Y.y, A, B, P), "not on curve haha"); } ... }

Casting a vote and homomorphically updating the weighted encrypted sum

Encrypting the vote, generating a ZK proof and submitting it to the smart contract with the stake amount.

def cast_vote(voter, vote, contract, stake): (yx, yy) = contract.Y() y = ec.Point(curve, yx, yy) C1, C2, k = encrypt(vote, n, y, G) # generate a zkp to prove that the vote is either 1 or 0. proof = generate_proof(vote, C1, C2, k, n, G, y) proof_flat = to_contract_proof(proof) C1_x = int(C1.x) C1_y = int(C1.y) C2_x = int(C2.x) C2_y = int(C2.y) contract.castVote(C1_x, C1_y, C2_x, C2_y, proof_flat, stake, {"from": voter}) print(f"Voter {voter.address} casted their vote.")

The smart contract must perform several verification steps before accepting a vote. First, it needs to ensure that the submitted point is on the specified elliptic curve. Next, it must verify that the voter has not already cast a vote. Additionally, the contract should validate the provided proof. If all these conditions are satisfied, the contract can then update the global stake-weighted encrypted sum utilizing the homomorphic properties of the encryption system.

contract VotingContract { ... uint256 public counter; uint256 public stakes; // sum of total stake mapping(uint256 => Vote) public votes; mapping(address => bool) public voted; Vote public encryptedSum; struct Vote { Point C1; Point C2; address voter; } struct Point { uint256 x; uint256 y; } function castVote( uint256 c1x, uint256 c1y, uint256 c2x, uint256 c2y, uint256[12] memory proof, uint256 stake ) external { require(EllipticCurve.isOnCurve(c1x, c1y, A, B, P), "not on curve"); require(EllipticCurve.isOnCurve(c2x, c2y, A, B, P), "not on curve"); require(voted[msg.sender] == false, "already voted"); Point memory C1 = Point(c1x, c1y); Point memory C2 = Point(c2x, c2y); Vote memory vote = Vote(Point(c1x, c1y), Point(c2x, c2y), msg.sender); require(verify_proof(C1, C2, proof), "invalid proof"); vote = homoMul(vote, stake); votes[counter] = vote; voted[msg.sender] = true; if (encryptedSum.C1.x == 0) { encryptedSum = vote; } else { encryptedSum = homoSum(vote, encryptedSum); } counter++; stakes += stake; } ...

[!info] See the Appendix to learn more about how the ZKP is generated and used.

The homoSum and homoMul functions are the implementation of the Homomorphic operations I've discussed in the ECC-Elgamal section. This example utilizes the Solidity Elliptic Curve Library for all Elliptic Curve calculations. While it might be helpful to be familiar with these concepts, it's not a requirement for understanding the code. By simply reviewing the code below and reading through the ECC-Elgamal section, you should be able to grasp what's happening.

contract VotingContract { ... function homoMul( Vote memory a, uint256 scalar ) internal pure returns (Vote memory) { Point memory C1_prime = mulPoint(a.C1, scalar); Point memory C2_prime = mulPoint(a.C2, scalar); return Vote(C1_prime, C2_prime, a.voter); } function homoSum( Vote memory a, Vote memory b ) internal pure returns (Vote memory) { Point memory C1_prime = sumPoint(a.C1, b.C1); Point memory C2_prime = sumPoint(a.C2, b.C2); return Vote(C1_prime, C2_prime, address(0)); } ... }

Retrieving the weighted encrypted sum

The encrypted sum is dynamically updated with each vote cast and stored in the smart contract as a variable:

Vote public encryptedSum;

This makes sure that the sum is securely kept and can be easily accessed when needed.

Add a function to retrieve the stake weighted encrypted sum from the smart contract:

def get_encrypted_sum(contract): # Read the encrpyted sum from the contract (c1x, c1y), (c2x, c2y), _ = contract.encryptedSum() C1 = ec.Point(curve, c1x, c1y) C2 = ec.Point(curve, c2x, c2y) return C1, C2

Then simply define a function for getting and decrypting the value.

def decrypt_weighted_sum(contract, x) C1, C2 = get_encrypted_sum(contract) return decrypt(C1, C2, x)

Running end to end

Now we've defined all the necessary functions, lets run the voting end to end and get the results

def main(): x, y, contract = deploy_voting_contract() print( f"Generated keypair and deployed contract on address {contract.address}") vote1 = 0 stake1 = 100 vote2 = 1 stake2 = 400 vote3 = randint(0, 1) stake3 = randint(10, 300) print("Casting votes:") cast_vote(voter1, vote1, contract, stake1) cast_vote(voter2, vote2, contract, stake2) cast_vote(admin, vote3, contract, stake3) stake_weighted_sum = decrypt_weighted_sum(contract, x) print("The decrypted sum is: ", stake_weighted_sum) total_staked = contract.stakes() print("Total staked: ", total_staked) print("The result is: ", stake_weighted_sum / total_staked)

Now that we've defined the main function, let's run it and see the results!

$ brownie run scripts/main.py

When you run this code, you should see output similar to the following (the specific numbers will vary due to randomness in voter3's (admin account) vote and stake):

Running 'scripts/main.py::main'... Transaction sent: 0x0878a2c7998d9773c6acc858f73e125376ad2902c89d07fcefea98e90626c732 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 0 VotingContract.constructor confirmed Block: 1 Gas used: 1407733 (11.73%) VotingContract deployed at: 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87 Generated keypair and deployed contract on address 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87 Casting votes: Transaction sent: 0xcb88a0dff3c27abef32f24f75edb971dd4a255a1640810c7190c9df0afb6af94 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 0 VotingContract.castVote confirmed Block: 2 Gas used: 6037408 (50.31%) Voter 0x33A4622B82D4c04a53e170c638B944ce27cffce3 casted their vote. Transaction sent: 0xc7a351459337fc779c872be043a26b0f098cd1b41e9ea02d607b714037f4b144 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 0 VotingContract.castVote confirmed Block: 3 Gas used: 6128902 (51.07%) Voter 0x0063046686E46Dc6F15918b61AE2B121458534a5 casted their vote. Transaction sent: 0x29757cb3ab615f7da3382214dac90bcf7e5e9642cb4e43ea8648cd4fdd4ec330 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 1 VotingContract.castVote confirmed Block: 4 Gas used: 6136637 (51.14%) Voter 0x66aB6D9362d4F35596279692F0251Db635165871 casted their vote. The encrypted sum is: 105654366233090499591198513210858722372750478798119417185280439608589640843974 39689509440070944699923380691499880889670088247588116169371956777417155135763 40660548797260281084351261115343785494879792948620353906184858168600511919016 12545077553564661190620888470623152802965127403671946839759257780050123219485 The decrypted sum is: 400 Total staked: 527 The result is: 0.7590132827324478

This output shows that three votes were cast, with varying stakes. The stake-weighted sum of these votes was then decrypted, and the final result was calculated as a ratio of this sum to the total staked.

In this example, the result is approximately 0.759, indicating that option 1 received a majority of the stake-weighted votes.

Wrap-up

In this tutorial, I've demonstrated how to build an ECC-Elgamal based voting system on Ethereum. Started by discussing the basics of elliptic curve cryptography and the ECC-Elgamal encryption scheme, followed by an overview of how to use homomorphic properties for vote tallying. I then walked through implementing a simple voting contract on Ethereum and showed how to cast encrypted votes, update the sum homomorphically and decrypt the weighted sum of votes.

This example demonstrates a basic implementation of a privacy-preserving voting system, but there are many possible enhancements and optimizations that could be made. One of them is using threshold encryption.

I hope this tutorial has provided you with a solid foundation in understanding privacy-preserving technologies in the context of blockchain applications and inspires you to explore further possibilities in this exciting field.

Appendix

Disjunctive Chaum-Pedersen ZKP

This example uses a Disjunctive Chaum-Pedersen zero knowledge proof to prove that the vote is either 1 or 0. I'll mathematically explain how the proof works soon.

Here's how the proof is generated in code:

def generate_proof(v, C1, C2, k, n, G, y): if v == 1: j = randint(1, n - 1) c0 = randint(1, n - 1) f0 = randint(1, n - 1) a0 = (G * f0) - (C1 * c0) b0 = (y * f0) - (C2 * c0) a1 = G * j b1 = y * j c = custom_hash([C1.x, C1.y, C2.x, C2.y, a0.x, a0.y, a1.x, a1.y, b0.x, b0.y, b1.x, b1.y]) % n c1 = (c - c0) % n f1 = j + (c1 * k) f1 = f1 % n return a0, a1, b0, b1, c0, c1, f0, f1 else: j = randint(1, n - 1) c1 = randint(1, n - 1) f1 = randint(1, n - 1) a1 = (G * f1) - (C1 * c1) b1 = (y * f1) - (C2 - G) * c1 a0 = G * j b0 = y * j c = custom_hash([C1.x, C1.y, C2.x, C2.y, a0.x, a0.y, a1.x, a1.y, b0.x, b0.y, b1.x, b1.y]) % n c0 = (c - c1) % n f0 = j + c0 * k f0 = f0 % n return a0, a1, b0, b1, c0, c1, f0, f1

The verification of the proof is done on the smart contract side with the following code:

function verify_proof( Point memory C1, Point memory C2, uint256[12] memory p ) internal view returns (bool) { // [0] a0x // [1] a0y // [2] b0x // [3] b0y // [4] a1x // [5] a1y // [6] b1x // [7] b1y // [8] c0 // [9] c1 // [10] f0 // [11] f1 bytes32 h = keccak256( abi.encodePacked( [ C1.x, C1.y, C2.x, C2.y, p[0], p[1], p[4], p[5], p[2], p[3], p[6], p[7] ] ) ); uint256 c = uint256(h) % N; bool s0 = addmod(p[8], p[9], N) == c; bool s1 = equals( mulPoint(G, p[10]), sumPoint(Point(p[0], p[1]), mulPoint(C1, p[8])) ); bool s2 = equals( mulPoint(G, p[11]), sumPoint(Point(p[4], p[5]), mulPoint(C1, p[9])) ); bool s3 = equals( mulPoint(Y, p[10]), sumPoint(Point(p[2], p[3]), mulPoint(C2, p[8])) ); bool s4 = equals( mulPoint(Y, p[11]), sumPoint(Point(p[6], p[7]), mulPoint(subPoint(C2, G), p[9])) ); return s0 && s1 && s2 && s3 && s4; }