diff --git a/src/contracts/paillierHE.ts b/src/contracts/paillierHE.ts new file mode 100644 index 00000000..9c16c725 --- /dev/null +++ b/src/contracts/paillierHE.ts @@ -0,0 +1,66 @@ +import { SmartContract, assert, method, prop } from 'scrypt-ts' + +export class PaillierHE extends SmartContract { + // max # of bits for e = ceil(log2(n)) + // priv key: 2048 bits + // n: 2048*2 = 4096 bits + static readonly N = 4096 + + @prop() + nSquare: bigint + + @prop(true) + x: bigint + + constructor(nSquare: bigint, x: bigint) { + super(...arguments) + this.nSquare = nSquare + this.x = x + } + + @method() + public add(toAdd: bigint) { + this.x = PaillierHE.addCT(this.x, toAdd, this.nSquare) + assert(true) + } + + @method() + public mul(factor: bigint) { + this.x = PaillierHE.mulCT(this.x, factor, this.nSquare) + assert(true) + } + + @method() + static addCT(ct0: bigint, ct1: bigint, nSquare: bigint): bigint { + return (ct0 * ct1) % nSquare + } + + @method() + static mulCT(ct0: bigint, k: bigint, nSquare: bigint): bigint { + return PaillierHE.modExp(ct0, k, nSquare) + } + + // x^y % m + @method() + static modExp(x: bigint, y: bigint, m: bigint): bigint { + let res: bigint = 1n + x = x % m + + if (x != 0n) { + for (let i = 0; i < PaillierHE.N; i++) { + if (y >= 0n) { + // If y is odd, multiply x with result + if (y % 2n) res = (res * x) % m + + // y >> 1 + y = y / 2n + x = (x * x) % m + } + } + } else { + res = 0n + } + + return res + } +} diff --git a/tests/elGamalHE.test.ts b/tests/elGamalHE.test.ts index 459fdbce..7a57ef75 100644 --- a/tests/elGamalHE.test.ts +++ b/tests/elGamalHE.test.ts @@ -24,7 +24,7 @@ function decrypt(ct: CT, k: bigint): Point { return SECP256K1.addPoints(ct.c2, kNegc1) } -describe('Test SmartContract `PartialHE`', () => { +describe('Test SmartContract `ElGamalHE`', () => { // For mG maps mG.x to m let lookupTable: Map diff --git a/tests/paillierHE.test.ts b/tests/paillierHE.test.ts new file mode 100644 index 00000000..66f95159 --- /dev/null +++ b/tests/paillierHE.test.ts @@ -0,0 +1,77 @@ +import { expect, use } from 'chai' +import * as paillierBigint from 'paillier-bigint' +import { PaillierHE } from '../src/contracts/paillierHE' + +import chaiAsPromised from 'chai-as-promised' +import { getDefaultSigner } from './utils/helper' +import { MethodCallOptions } from 'scrypt-ts' +use(chaiAsPromised) + +describe('Test SmartContract `PaillierHE`', () => { + before(() => { + PaillierHE.loadArtifact() + }) + + it('should pass the public method unit test successfully.', async () => { + // (asynchronous) creation of a random private, public key pair for the Paillier cryptosystem + const { publicKey, privateKey } = + await paillierBigint.generateRandomKeys(2048) + + const x = publicKey.encrypt(0n) + const instance = new PaillierHE(publicKey._n2, x) + await instance.connect(getDefaultSigner()) + + await instance.deploy(1) + + // Set current instance to be the deployed one. + let currentInstance = instance + + for (let i = 0; i < 5; ++i) { + const nextInstance = currentInstance.next() + + // Add encrypted amount (100) to the contract commulative value. + const toAdd = publicKey.encrypt(100n) + nextInstance.x = PaillierHE.addCT( + currentInstance.x, + toAdd, + publicKey._n2 + ) + + // Call add method. + const callContractAdd = async () => + currentInstance.methods.add(toAdd, { + next: { + instance: nextInstance, + balance: 1, + }, + } as MethodCallOptions) + await expect(callContractAdd()).not.rejected + + currentInstance = nextInstance + + // Multiply encrypted amount. + const k = 5n + nextInstance.x = PaillierHE.mulCT( + currentInstance.x, + k, + publicKey._n2 + ) + + // Call mul method. + const callContractMul = async () => + currentInstance.methods.mul(k, { + next: { + instance: nextInstance, + balance: 1, + }, + } as MethodCallOptions) + await expect(callContractMul()).not.rejected + + currentInstance = nextInstance + } + + // Decrypt and check end result. + const m = privateKey.decrypt(currentInstance.x) + await expect(m == 390500n).to.be.true + }) +})