malleable.mx raw
1 // Anti-malleability defenses for homomorphic ciphertexts.
2 package ring
3
4 import (
5 "crypto/rand"
6 "io"
7
8 "crypto/sha3"
9 )
10
11 func NoiseFlood(pk *KEMPublicKey, ct *HECiphertext, floodBits int32) (result *HECiphertext) {
12 return NoiseFloodFrom(pk, ct, floodBits, rand.Reader)
13 }
14
15 func NoiseFloodFrom(pk *KEMPublicKey, ct *HECiphertext, floodBits int32, rng io.Reader) (result *HECiphertext) {
16 p := pk.P.Ring
17 q := p.Q
18
19 floodU := New(p)
20 floodV := New(p)
21
22 bound := uint32(1) << uint32(floodBits)
23
24 var buf [4]byte
25 for i := int32(0); i < p.N; i++ {
26 io.ReadFull(rng, buf[:])
27 val := uint32(buf[0])<<16 | uint32(buf[1])<<8 | uint32(buf[2])
28 val = val % (2 * bound)
29 noise := int64(val) - int64(bound)
30 noise = noise & ^int64(1)
31 if noise >= 0 {
32 floodU.Coeffs[i] = uint32(noise) % q
33 } else {
34 floodU.Coeffs[i] = q - (uint32(-noise) % q)
35 }
36
37 io.ReadFull(rng, buf[:])
38 val = uint32(buf[0])<<16 | uint32(buf[1])<<8 | uint32(buf[2])
39 val = val % (2 * bound)
40 noise = int64(val) - int64(bound)
41 noise = noise & ^int64(1)
42 if noise >= 0 {
43 floodV.Coeffs[i] = uint32(noise) % q
44 } else {
45 floodV.Coeffs[i] = q - (uint32(-noise) % q)
46 }
47 }
48
49 return &HECiphertext{
50 U: Add(ct.U, floodU),
51 V: Add(ct.V, floodV),
52 NoiseEstimate: ct.NoiseEstimate + float64(bound),
53 params: ct.params,
54 }
55 }
56
57 type SecureHEResult struct {
58 Ciphertext *HECiphertext
59 Tag []byte
60 Signature *GPVSignature
61 }
62
63 func WrapResult(pk *KEMPublicKey, ct *HECiphertext, sessionID []byte, gpvSK *GPVSecretKey) (result *SecureHEResult) {
64 return WrapResultFrom(pk, ct, sessionID, gpvSK, rand.Reader)
65 }
66
67 func WrapResultFrom(pk *KEMPublicKey, ct *HECiphertext, sessionID []byte, gpvSK *GPVSecretKey, rng io.Reader) (result *SecureHEResult) {
68 ct = RerandomizeFrom(pk, ct, rng)
69
70 floodBits := int32(8)
71 ct = NoiseFloodFrom(pk, ct, floodBits, rng)
72
73 tag := computeTag(ct, sessionID)
74
75 var sig *GPVSignature
76 if gpvSK != nil {
77 sig = GPVSignFrom(gpvSK, tag, rng)
78 }
79
80 return &SecureHEResult{
81 Ciphertext: ct,
82 Tag: tag,
83 Signature: sig,
84 }
85 }
86
87 func VerifyResult(result *SecureHEResult, sessionID []byte, gpvPK *GPVPublicKey) (ok bool) {
88 expectedTag := computeTag(result.Ciphertext, sessionID)
89 if int32(len(result.Tag)) != int32(len(expectedTag)) {
90 return false
91 }
92 for i := int32(0); i < int32(len(result.Tag)); i++ {
93 if result.Tag[i] != expectedTag[i] {
94 return false
95 }
96 }
97
98 if result.Signature != nil {
99 if gpvPK == nil {
100 return false
101 }
102 if !GPVVerify(gpvPK, result.Tag, result.Signature) {
103 return false
104 }
105 }
106
107 return true
108 }
109
110 func computeTag(ct *HECiphertext, sessionID []byte) (tag []byte) {
111 h := sha3.NewSHAKE256()
112 h.Write([]byte("he-result-tag-v1"))
113 h.Write(sessionID)
114
115 uBytes := Serialize(ct.U)
116 vBytes := Serialize(ct.V)
117 h.Write(uBytes)
118 h.Write(vBytes)
119
120 tag = []byte{:32}
121 h.Read(tag)
122 return tag
123 }
124
125 type SessionWrapper struct {
126 SessionID []byte
127 HEPK *KEMPublicKey
128 GPVPK *GPVPublicKey
129 GPVSK *GPVSecretKey
130 }
131
132 func NewSession(hePK *KEMPublicKey, gpvPK *GPVPublicKey, gpvSK *GPVSecretKey) (sw *SessionWrapper) {
133 sessionID := []byte{:32}
134 rand.Read(sessionID)
135
136 return &SessionWrapper{
137 SessionID: sessionID,
138 HEPK: hePK,
139 GPVPK: gpvPK,
140 GPVSK: gpvSK,
141 }
142 }
143
144 func (sw *SessionWrapper) Wrap(ct *HECiphertext) (result *SecureHEResult) {
145 return WrapResult(sw.HEPK, ct, sw.SessionID, sw.GPVSK)
146 }
147
148 func (sw *SessionWrapper) Verify(result *SecureHEResult) (ok bool) {
149 return VerifyResult(result, sw.SessionID, sw.GPVPK)
150 }
151