diff --git a/README.md b/README.md index b3e9ee007f48954c4c9c4b9da6d719017654a6cc..f5bf889c61c1273fc38fe76b2be776fdd21541c1 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,24 @@ -# Cryptographic Computing - Handin 5 +# Cryptographic Computing - Handin 6 -A Go implementation of a passive secure two-party protocol for blood type compatibility based on Yao's garbled circuit protocol. +A Go implementation of a passive secure two-party protocol for blood type compatibility based on a homomorphic encryption scheme. ## Implementation -The protocol is implemented in `cmd/handin5/main.go` with tests in `cmd/handin5/main_test.go`. +The protocol is implemented in `cmd/handin6/main.go` with tests in `cmd/handin6/main_test.go`. For the implementation we have implemented support packages which can be reused: - `internal/crypto/elgamal` implements textbook ElGamal encryption. - `internal/crypto/oblivious` implements a 1 out of n oblivious transfer protocol. -- `internal/crypto/garbled` implements Yao's garbled circuits. +- `internal/crypto/garbled` implements Yao's garbled circuits. +- `internal/crypto/homomorphic` implements the homomorphic encryption scheme. + +### Notes on implementation. + +1. For the security parameters we choose 1000-bit p, 10^6-bit q, 40-bit r and 1000 elements in the public key. We choose those values + because they improve the performance of the scheme. These parameters guarantee that we decrypt the result after evaluating the blood compatibility function. + +2. In the homomorphic encryption scheme, we need to choose a random subset S. We do this by shuffling a list containing the numbers [1,...,n] + and then choosing the k first numbers in the list, where k is a random integer in the range [1,...,n). This ensures we always add some noise to the encryption. ## Requirements @@ -19,11 +28,11 @@ The protocol is tested using [Go 1.17](https://golang.org/dl/), but will likely You can run the tests with go: ``` -go test ./cmd/handin5 +go test ./cmd/handin6 ``` This executes the protocol tests. This includes a test that tries the protocol on all combinations of recipient and donor blood types. To run all tests in the repository (could take some time): ``` go test ./... -``` \ No newline at end of file +``` diff --git a/cmd/handin6/main.go b/cmd/handin6/main.go new file mode 100644 index 0000000000000000000000000000000000000000..d67c8e3f79782bd468668f27139f496bc987fcde --- /dev/null +++ b/cmd/handin6/main.go @@ -0,0 +1,119 @@ +package main + +import ( + "crycomp/internal/blood" + "crycomp/internal/crypto/homomorphic" + "crycomp/internal/crypto/util" + "crypto/rand" + "fmt" + "math/big" +) + +func main() { + bloodA, bloodB := blood.Type_ABn, blood.Type_ABn + z, err := RunProtocol(bloodA, bloodB) + if err != nil { + fmt.Println("Protocol failed with error:", err) + } else if z == blood.Table[bloodA][bloodB] { + fmt.Println("Protocol succeded") + } else { + fmt.Printf("Protocol failed, output was %t, but should be %t", z, blood.Table[bloodA][bloodB]) + } +} + +type Party struct { + conn chan *big.Int +} + +func (alice *Party) RunAlice(x int, priv *homomorphic.PrivateKey) (result bool, err error) { + xval := make([]int, 3) + //Negate values + for i, val := range util.Int2Bools(x, 3) { + if !val { + xval[i] = 1 + } + } + + // Send encoded input ints to bob + for i := 0; i < 3; i++ { + c, err := homomorphic.Encrypt(rand.Reader, xval[i], &priv.PublicKey) + if err != nil { + return false, err + } + alice.conn <- c + } + + //Receive and decode result from bob + result = homomorphic.Decrypt(<-alice.conn, priv) == 1 //Convert 1 -> true + // result = alice.HEProt.Decode(<-alice.conn, *alice.sKey) == 1 //Convert 1 -> true + return +} + +func (bob *Party) RunBob(y int, pub *homomorphic.PublicKey) (err error) { + c_y := make([]*big.Int, 3) + for i, val := range util.Int2Bools(y, 3) { + bit := 0 + if val { + bit = 1 + } + c_y[i], err = homomorphic.Encrypt(rand.Reader, bit, pub) + if err != nil { + return + } + // c_y[i] = bob.HEProt.Encode(bit) + } + + //Receive ciphertexts from alice + c_x := make([]*big.Int, 3) + for i := range c_x { + c_x[i] = <-bob.conn + } + + c, err := evaluate(c_x, c_y, pub) + if err != nil { + return + } + bob.conn <- c + // bob.conn <- *bob.HEProt.Eval(c_x, c_y) + return +} + +func evaluate(x, y []*big.Int, pub *homomorphic.PublicKey) (result *big.Int, err error) { + one, err := homomorphic.Encrypt(rand.Reader, 1, pub) + if err != nil { + return + } + tmp := make([]*big.Int, 3) + + for i := 0; i < 3; i++ { + v := new(big.Int).Mul(x[i], y[i]) + tmp[i] = v.Add(v, one) + } + + result = new(big.Int).Mul(tmp[0], tmp[1]) + result.Mul(result, tmp[2]) + return +} + +// RunProtocol runs the protocol between receiving blood type x and donor blood +// type y. +func RunProtocol(x, y int) (z bool, err error) { + conn := make(chan *big.Int) + Alice := &Party{conn} + Bob := &Party{conn} + privKey, err := homomorphic.GenerateKey(rand.Reader, 1000, 1000000, 40, 1000) + if err != nil { + return + } + + // Concurrently run Bob + go func() { + err := Bob.RunBob(y, &privKey.PublicKey) + if err != nil { + panic(err) // TODO do not panic + } + }() + + z, err = Alice.RunAlice(x, privKey) + return +} diff --git a/cmd/handin6/main_test.go b/cmd/handin6/main_test.go new file mode 100644 index 0000000000000000000000000000000000000000..271ea644fca7dc8fc3a273bf75a61ad1f27e2f54 --- /dev/null +++ b/cmd/handin6/main_test.go @@ -0,0 +1,26 @@ +package main + +import ( + "crycomp/internal/blood" + "fmt" + "testing" +) + +func TestProtocol(t *testing.T) { + // Runs the protocol for all combinations of recipient and donor blood types. + n := len(blood.Table) + + for x := 0; x < n; x++ { + for y := 0; y < n; y++ { + testName := fmt.Sprintf("(x=%s,y=%s)", blood.Names[x], blood.Names[y]) + t.Run(testName, func(t *testing.T) { + z, err := RunProtocol(x, y) + if err != nil { + t.Errorf("Protocol error: %s", err) + } else if z != blood.Table[x][y] { + t.Fatalf("Failed blood compatibility test for index [%d,%d]. Expected %t, got %t", x, y, !z, z) + } + }) + } + } +} diff --git a/internal/circuit/circuit.go b/internal/circuit/circuit.go new file mode 100644 index 0000000000000000000000000000000000000000..11e4f374c5fffca11e92ceaed0745e4f9cb00514 --- /dev/null +++ b/internal/circuit/circuit.go @@ -0,0 +1,50 @@ +package circuit + +type Wire = int +type WireValue = bool + +type Circuit struct { + wires []WireValue +} + +// // The function that evaluates this gate. +// fun gateFun +// // The input wire indices +// inputs []int +// // The output wire index +// output int +// } + +// type Builder struct { +// numInputs int +// gates []Gate +// } + +// func NewBuilder(numInputs int) *Builder { +// return &Builder{ +// numInputs: numInputs, +// gates: make([]*gate, 0), +// } +// } + +// func (b *Builder) AddNotGate(in int) (out int) { +// b.AddGate() +// } + +// func (b *Builder) allocWires(count int) (wires []int) { + +// } + +// func btoi(b bool) (i int) { +// if b { +// i = 1 +// } +// return +// } + +// func itob(i int) (b bool) { +// if i != 0 { +// b = true +// } +// return +// } diff --git a/internal/circuit/circuit_test.go b/internal/circuit/circuit_test.go new file mode 100644 index 0000000000000000000000000000000000000000..2d11cbebad0f9589f416e614605b04c0e3b4084d --- /dev/null +++ b/internal/circuit/circuit_test.go @@ -0,0 +1,30 @@ +package circuit + +import "testing" + +func TestBuilderInputs(t *testing.T) { + // b := NewBuilder() + + // A_in := b.AddInputs(3) + // if len(A_in) != 3 { + // t.Errorf("Expected length of A_in to be 3 (was %d)", len(A_in)) + // } + // for i := 0; i < len(A_in); i++ { + // if A_in[i] != i { + // t.Errorf("Expected A_in[%d] == %d (was %d)", i, i, A_in[i]) + // } + // if b.wires[A_in[i]] != i { + // t.Errorf("Expected b.wires[%d] == %d (was %d)", A_in[i], i, b.wires[A_in[i]]) + // } + // } + + // B_in := b.AddInputs(1) + // if len(B_in) != 1 { + // t.Errorf("Expected length of B_in to be 1 (was %d)", len(B_in)) + // } + // for i := 0; i < len(B_in); i++ { + // if B_in[i] != i+3 { + // t.Errorf("Expected B_in[%d] == %d (was %d)", i, i+3, B_in[i]) + // } + // } +} diff --git a/internal/circuit/gate.go b/internal/circuit/gate.go new file mode 100644 index 0000000000000000000000000000000000000000..58d8a323b2374c2515dc3ee5351d67438b08c29d --- /dev/null +++ b/internal/circuit/gate.go @@ -0,0 +1,46 @@ +package circuit + +type Gate interface { + Evalaute(c *Circuit) +} + +//////////////// NOT //////////////// + +type NotGate struct { + in Wire + out Wire +} + +func (g *NotGate) Evaluate(c *Circuit) { + c.wires[g.out] = !c.wires[g.in] +} + +//////////////// AND //////////////// + +type AndGate struct { + in []Wire + out Wire +} + +func (g *AndGate) Evaluate(c *Circuit) { + r := true + for w := range g.in { + r = r && c.wires[w] + } + c.wires[g.out] = r +} + +//////////////// OR ///////////////// + +type OrGate struct { + in []Wire + out Wire +} + +func (g *OrGate) Evaluate(c *Circuit) { + r := false + for w := range g.in { + r = r || c.wires[w] + } + c.wires[g.out] = r +} diff --git a/internal/circuit/gate_test.go b/internal/circuit/gate_test.go new file mode 100644 index 0000000000000000000000000000000000000000..1a09397a2b2f601169628d80a87e122b71180270 --- /dev/null +++ b/internal/circuit/gate_test.go @@ -0,0 +1,92 @@ +package circuit + +import "testing" + +func TestNotGate(t *testing.T) { + c := &Circuit{wires: make([]bool, 2)} + g := &NotGate{in: 0, out: 1} + + g.Evaluate(c) + if c.wires[1] != true { + t.Errorf("Expected NOT gate: (0) -> (1)") + } + c.wires[0] = true + g.Evaluate(c) + if c.wires[1] != false { + t.Errorf("Expected NOT gate: (1) -> (0)") + } +} + +func TestAndGate(t *testing.T) { + c := &Circuit{wires: make([]bool, 3)} + g := &AndGate{in: []Wire{0, 1}, out: 2} + + g.Evaluate(c) + if c.wires[2] != false { + t.Errorf("Expected AND gate: (0, 0) -> (0)") + } + c.wires[0] = true + g.Evaluate(c) + if c.wires[2] != false { + t.Errorf("Expected AND gate: (1, 0) -> (0)") + } + c.wires[1] = true + g.Evaluate(c) + if c.wires[2] != true { + t.Errorf("Expected AND gate: (1, 1) -> (1)") + } + c.wires[0] = false + g.Evaluate(c) + if c.wires[2] != false { + t.Errorf("Expected AND gate: (0, 1) -> (0)") + } + + c.wires = []bool{true, true, false, false} + g = &AndGate{in: []Wire{0, 1, 2}, out: 3} + g.Evaluate(c) + if c.wires[3] != false { + t.Errorf("Expected AND gate: (1, 1, 0) -> (0)") + } + c.wires[2] = true + g.Evaluate(c) + if c.wires[3] != true { + t.Errorf("Expected AND gate: (1, 1, 1) -> (1)") + } +} + +func TestOrGate(t *testing.T) { + c := &Circuit{wires: make([]bool, 3)} + g := &OrGate{in: []Wire{0, 1}, out: 2} + + g.Evaluate(c) + if c.wires[2] != false { + t.Errorf("Expected OR gate: (0, 0) -> (0)") + } + c.wires[0] = true + g.Evaluate(c) + if c.wires[2] != true { + t.Errorf("Expected OR gate: (1, 0) -> (1)") + } + c.wires[1] = true + g.Evaluate(c) + if c.wires[2] != true { + t.Errorf("Expected OR gate: (1, 1) -> (1)") + } + c.wires[0] = false + g.Evaluate(c) + if c.wires[2] != true { + t.Errorf("Expected OR gate: (0, 1) -> (1)") + } + + c.wires = []bool{false, false, true, false} + g = &OrGate{in: []Wire{0, 1, 2}, out: 3} + g.Evaluate(c) + if c.wires[3] != true { + t.Errorf("Expected OR gate: (0, 0, 1) -> (1)") + } + c.wires[2] = false + g.Evaluate(c) + if c.wires[3] != false { + t.Errorf("Expected OR gate: (0, 0, 0) -> (0)") + } +} diff --git a/internal/crypto/elgamal/elgamal.go b/internal/crypto/elgamal/elgamal.go index 74dcf69eb640efd8a4de90d84df1880d7607f788..7c1d1dadad5a01954a9987f4650972b4b84abcdd 100644 --- a/internal/crypto/elgamal/elgamal.go +++ b/internal/crypto/elgamal/elgamal.go @@ -56,7 +56,7 @@ func Encrypt(random io.Reader, pub *PublicKey, msg []byte) (c1, c2 *big.Int, err } m = encodeMessage(pub, m) - r, err := util.RandInt(random, pub.Q) + r, err := util.RandIntn(random, pub.Q) if err != nil { return } diff --git a/internal/crypto/homomorphic/dhe.go b/internal/crypto/homomorphic/dhe.go new file mode 100644 index 0000000000000000000000000000000000000000..fd196429501feadcce067cb29c3b4e85de545b33 --- /dev/null +++ b/internal/crypto/homomorphic/dhe.go @@ -0,0 +1,95 @@ +package homomorphic + +import ( + "crycomp/internal/crypto/util" + "io" + "math/big" +) + +var two = big.NewInt(2) + +type Params struct { + pLen int + qLen int + rLen int + n int +} + +func NewParams(pLen, qLen, rLen, m int) *Params { + return &Params{ + pLen: pLen, + qLen: qLen, + rLen: rLen, + n: m, + } +} + +type PublicKey struct { + y []*big.Int + n int +} + +type PrivateKey struct { + PublicKey + p *big.Int +} + +func GenerateKey(random io.Reader, pLen, qLen, rLen, n int) (priv *PrivateKey, err error) { + // Chose a random big secret odd integer p. + p, err := util.RandIntk(random, pLen) + if err != nil { + return + } + p.SetBit(p, 0, 1) // Make p odd. + + y := make([]*big.Int, n) + var q *big.Int + var r *big.Int + + for i := 0; i < n; i++ { + q, err = util.RandIntk(random, qLen) + if err != nil { + return + } + r, err = util.RandIntk(random, rLen) + if err != nil { + return + } + y[i] = q.Mul(p, q).Add(q, r.Mul(r, two)) + } + + priv = &PrivateKey{ + PublicKey: PublicKey{ + y: y, + n: n, + }, + p: p, + } + return +} + +func Encrypt(random io.Reader, m int, pub *PublicKey) (c *big.Int, err error) { + // Sample random subset S of [0...n] + S := make([]int, pub.n) + for i := 0; i < pub.n; i++ { + S[i] = i + 1 + } + util.Shuffle(pub.n, func(i, j int) { S[i], S[j] = S[j], S[i] }) + sLen, err := util.RandIntn(random, big.NewInt(int64(pub.n))) + if err != nil { + return + } + S = S[:sLen.Int64()] + + c = big.NewInt(int64(m)) + for i := range S { + c = c.Add(c, pub.y[S[i]-1]) + } + return +} + +func Decrypt(c *big.Int, priv *PrivateKey) int { + m := new(big.Int).Mod(c, priv.p) + m.Mod(m, two) + return int(m.Int64()) +} diff --git a/internal/crypto/oblivious/oblivious.go b/internal/crypto/oblivious/oblivious.go index 86c0210dc307b07a3e3e6645fe146dc5c4176a9f..615baed0750247e360edb2ae2f2b0bd3a954b728 100644 --- a/internal/crypto/oblivious/oblivious.go +++ b/internal/crypto/oblivious/oblivious.go @@ -21,7 +21,7 @@ type Encryption struct { // using elgamal.GenerateKey. The way the key is generated the corresponding // private key is unknown. func oGenElgamel(random io.Reader, params *group.Params) (pub *elgamal.PublicKey, err error) { - s, err := util.RandInt(random, params.P) + s, err := util.RandIntn(random, params.P) if err != nil { return } diff --git a/internal/crypto/util/util.go b/internal/crypto/util/util.go index b8c463aaa383af987672cbf8b8d7b5213cde5145..63a1f0061f11076d20c5285b7eada79994ca0bc9 100644 --- a/internal/crypto/util/util.go +++ b/internal/crypto/util/util.go @@ -42,8 +42,8 @@ func XOR(a, b []byte) (dst []byte) { var one = big.NewInt(1) -// RandInt returns a random integer in the range [1, n). -func RandInt(random io.Reader, n *big.Int) (r *big.Int, err error) { +// RandIntn returns a random integer in the range [1, n). +func RandIntn(random io.Reader, n *big.Int) (r *big.Int, err error) { tmp := new(big.Int).Set(n) tmp.Sub(tmp, one) @@ -56,12 +56,35 @@ func RandInt(random io.Reader, n *big.Int) (r *big.Int, err error) { return } +// RandIntk returns a random k-bit integer. +func RandIntk(random io.Reader, k int) (r *big.Int, err error) { + // Calculate how many bytes are needed to have k bits. + bLen := (k + 7) / 8 + // Fill bytes with random data + bytes := make([]byte, bLen) + _, err = io.ReadFull(random, bytes) + if err != nil { + return + } + // Clear the bits that exceed k bits + bytes[0] &= 0xff >> (8*len(bytes) - k) + r = new(big.Int).SetBytes(bytes) + // Set the k-1 bit. This ensures a k-bits integer + // and the distribution is maintained (all other bits are random) + r.SetBit(r, k-1, 1) + return +} + type cryptoSource [8]byte func Perm(n int) []int { return mRand.New(&cryptoSource{}).Perm(n) } +func Shuffle(n int, swap func(i, j int)) { + mRand.New(&cryptoSource{}).Shuffle(n, swap) +} + func (s *cryptoSource) Int63() int64 { _, err := cRand.Read(s[:]) if err != nil {