-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathx3dh.go
188 lines (150 loc) · 4.41 KB
/
x3dh.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
package x3dh
import (
"errors"
"hash"
hkdf "golang.org/x/crypto/hkdf"
)
// returned in case the pre key bundle signature is invalid
var PreKeyBundleInvalidSignature = errors.New("the signature of the received pre key bundle is invalid")
type ProtocolInitialisation struct {
RemoteIdKey PublicKey
RemoteEphemeralKey PublicKey
MyOneTimePreKey *PrivateKey
MySignedPreKey PrivateKey
}
type InitializedProtocol struct {
SharedSecret SharedSecret
UsedOneTimePreKey *PublicKey
UsedSignedPreKey PublicKey
EphemeralKey PublicKey
}
// create a new X3dh key agreement protocol
// info is just your protocol name. Something like ("pangea")
// myIDKey is your curve25519 key pair
func New(c Curve, h hash.Hash, info string, myIDKey KeyPair) X3dh {
return X3dh{
curve: c,
hash: h,
info: info,
idKeyPair: myIDKey,
}
}
// the shared secret between you and the partner
type SharedSecret = [32]byte
type X3dh struct {
curve Curve
hash hash.Hash
info string
idKeyPair KeyPair
}
// kdf derives the final secret between you and your partner
// from "key material", which is made of a few concatenated
// byte slices (output from diffie hellman key exchanges)
func (x *X3dh) kdf(keyMaterial []byte) (SharedSecret, error) {
// create reader
r := hkdf.New(
func() hash.Hash {
return x.hash
},
append(x.curve.PreFix(), keyMaterial...),
make([]byte, 32), []byte(x.info),
)
// fill the shared secret
var secret [32]byte
_, err := r.Read(secret[:])
return secret, err
}
func (x *X3dh) NewKeyPair() (KeyPair, error) {
return x.curve.GenerateKeyPair()
}
// calculate a shared secret based on a received preKeyBundle
func (x *X3dh) CalculateSecret(b PreKeyBundle) (InitializedProtocol, error) {
// verify that the signature of the pre key bundle is valid
valid, err := b.ValidSignature()
if err != nil {
return InitializedProtocol{}, err
}
if !valid {
return InitializedProtocol{}, PreKeyBundleInvalidSignature
}
// create ephemeral key
ephemeralKey, err := x.curve.GenerateKeyPair()
if err != nil {
return InitializedProtocol{}, err
}
// first step with our identity private key
// and remote signed pre key
dh1 := x.curve.KeyExchange(DHPair{
PrivateKey: x.idKeyPair.PrivateKey,
PublicKey: b.SignedPreKey(),
})
// second step with our ephemeral key
// and the remote identity key
dh2 := x.curve.KeyExchange(DHPair{
PrivateKey: ephemeralKey.PrivateKey,
PublicKey: b.IdentityKey(),
})
// third step with our ephemeral key
// and the remote signed pre key
dh3 := x.curve.KeyExchange(DHPair{
PrivateKey: ephemeralKey.PrivateKey,
PublicKey: b.SignedPreKey(),
})
// concat the byte sequences
// (dh1 || dh2 || dh3)
km := append(dh1[:], dh2[:]...)
km = append(km, dh3[:]...)
// only execute this step if the one time pre key is present.
// the protocol can work without it tho it's recommended to use them.
if b.OneTimePreKey() != nil {
// fourth step with our ephemeral key
// and the remote one time pre key
dh4 := x.curve.KeyExchange(DHPair{
PrivateKey: ephemeralKey.PrivateKey,
PublicKey: *b.OneTimePreKey(),
})
km = append(km, dh4[:]...)
}
s, err := x.kdf(km)
return InitializedProtocol{
SharedSecret: s,
UsedOneTimePreKey: b.OneTimePreKey(),
UsedSignedPreKey: b.SignedPreKey(),
EphemeralKey: ephemeralKey.PublicKey,
}, err
}
// calculate secret based on received on data from initial message
func (x *X3dh) SecretFromRemote(c ProtocolInitialisation) (SharedSecret, error) {
// first step with our signed pre key
// and the remote id key
dh1 := x.curve.KeyExchange(DHPair{
PrivateKey: c.MySignedPreKey,
PublicKey: c.RemoteIdKey,
})
// second step with our identity key
// and the remote ephemeral key
dh2 := x.curve.KeyExchange(DHPair{
PrivateKey: x.idKeyPair.PrivateKey,
PublicKey: c.RemoteEphemeralKey,
})
// third step with our signed pre key
// and the remote ephemeral key
dh3 := x.curve.KeyExchange(DHPair{
PrivateKey: c.MySignedPreKey,
PublicKey: c.RemoteEphemeralKey,
})
// concat all the byte arrays
km := append(dh1[:], dh2[:]...)
km = append(km, dh3[:]...)
// only calculate with the one time pre key if
if c.MyOneTimePreKey != nil {
// fourth step with our one time pre key
// and the remote ephemeral key
dh4 := x.curve.KeyExchange(DHPair{
PrivateKey: *c.MyOneTimePreKey,
PublicKey: c.RemoteEphemeralKey,
})
km = append(km, dh4[:]...)
}
return x.kdf(km)
}