Skip to content

Commit 9cf994c

Browse files
authored
Merge pull request #95 from fystack/dev
Implement ECDH for P2P message encryption
2 parents 832e724 + acec1fa commit 9cf994c

32 files changed

Lines changed: 1183 additions & 366 deletions

.github/workflows/ci.yml

Lines changed: 24 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ on:
66
pull_request:
77
branches: ["*"]
88

9+
env:
10+
GO_VERSION: "1.24"
11+
912
jobs:
1013
test:
1114
runs-on: ubuntu-latest
@@ -15,19 +18,10 @@ jobs:
1518
uses: actions/checkout@v4
1619

1720
- name: Set up Go
18-
uses: actions/setup-go@v4
21+
uses: actions/setup-go@v5
1922
with:
20-
go-version: "1.23"
21-
22-
- name: Cache Go modules
23-
uses: actions/cache@v3
24-
with:
25-
path: |
26-
~/.cache/go-build
27-
~/go/pkg/mod
28-
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
29-
restore-keys: |
30-
${{ runner.os }}-go-
23+
go-version: ${{ env.GO_VERSION }}
24+
cache: true
3125

3226
- name: Install dependencies
3327
run: go mod download
@@ -50,9 +44,13 @@ jobs:
5044
uses: actions/checkout@v4
5145

5246
- name: Set up Go
53-
uses: actions/setup-go@v4
47+
uses: actions/setup-go@v5
5448
with:
55-
go-version: "1.23"
49+
go-version: ${{ env.GO_VERSION }}
50+
cache: true
51+
52+
- name: Clean Go build cache
53+
run: go clean -cache -modcache
5654

5755
- name: Run golangci-lint
5856
uses: golangci/golangci-lint-action@v3
@@ -74,19 +72,10 @@ jobs:
7472
uses: actions/checkout@v4
7573

7674
- name: Set up Go
77-
uses: actions/setup-go@v4
75+
uses: actions/setup-go@v5
7876
with:
79-
go-version: "1.23"
80-
81-
- name: Cache Go modules
82-
uses: actions/cache@v3
83-
with:
84-
path: |
85-
~/.cache/go-build
86-
~/go/pkg/mod
87-
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
88-
restore-keys: |
89-
${{ runner.os }}-go-
77+
go-version: ${{ env.GO_VERSION }}
78+
cache: true
9079

9180
- name: Install dependencies
9281
run: go mod download
@@ -153,19 +142,10 @@ jobs:
153142
uses: actions/checkout@v4
154143

155144
- name: Set up Go
156-
uses: actions/setup-go@v4
145+
uses: actions/setup-go@v5
157146
with:
158-
go-version: "1.23"
159-
160-
- name: Cache Go modules
161-
uses: actions/cache@v3
162-
with:
163-
path: |
164-
~/.cache/go-build
165-
~/go/pkg/mod
166-
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
167-
restore-keys: |
168-
${{ runner.os }}-go-
147+
go-version: ${{ env.GO_VERSION }}
148+
cache: true
169149

170150
- name: Install dependencies
171151
run: go mod download
@@ -200,19 +180,10 @@ jobs:
200180
uses: actions/checkout@v4
201181

202182
- name: Set up Go
203-
uses: actions/setup-go@v4
204-
with:
205-
go-version: "1.23"
206-
207-
- name: Cache Go modules
208-
uses: actions/cache@v3
183+
uses: actions/setup-go@v5
209184
with:
210-
path: |
211-
~/.cache/go-build
212-
~/go/pkg/mod
213-
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
214-
restore-keys: |
215-
${{ runner.os }}-go-
185+
go-version: ${{ env.GO_VERSION }}
186+
cache: true
216187

217188
- name: Install dependencies
218189
run: go mod download
@@ -293,19 +264,10 @@ jobs:
293264
uses: actions/checkout@v4
294265

295266
- name: Set up Go
296-
uses: actions/setup-go@v4
297-
with:
298-
go-version: "1.23"
299-
300-
- name: Cache Go modules
301-
uses: actions/cache@v3
267+
uses: actions/setup-go@v5
302268
with:
303-
path: |
304-
~/.cache/go-build
305-
~/go/pkg/mod
306-
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
307-
restore-keys: |
308-
${{ runner.os }}-go-
269+
go-version: ${{ env.GO_VERSION }}
270+
cache: true
309271

310272
- name: Build mpcium
311273
run: go build -v ./cmd/mpcium

.github/workflows/e2e-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
branches: [master]
88

99
env:
10-
GO_VERSION: "1.23"
10+
GO_VERSION: "1.24"
1111
CGO_ENABLED: 0
1212
DOCKER_BUILDKIT: 1
1313
GO_BUILD_FLAGS: -trimpath -ldflags="-s -w"

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,7 @@ e2e/coverage.html
1717
e2e/logs/
1818
# Generated config file (template is tracked)
1919
e2e/config.test.yaml
20+
node0
21+
node1
22+
node2
23+
config.yaml

cmd/mpcium/main.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,6 @@ func runNode(ctx context.Context, c *cli.Command) error {
129129
if err != nil {
130130
logger.Fatal("Failed to connect to NATS", err)
131131
}
132-
defer natsConn.Close()
133132

134133
pubsub := messaging.NewNATSPubSub(natsConn)
135134
keygenBroker, err := messaging.NewJetStreamBroker(ctx, natsConn, event.KeygenBrokerStream, []string{
@@ -159,10 +158,10 @@ func runNode(ctx context.Context, c *cli.Command) error {
159158
reshareResultQueue := mqManager.NewMessageQueue("mpc_reshare_result")
160159
defer reshareResultQueue.Close()
161160

162-
logger.Info("Node is running", "peerID", nodeID, "name", nodeName)
161+
logger.Info("Node is running", "ID", nodeID, "name", nodeName)
163162

164163
peerNodeIDs := GetPeerIDs(peers)
165-
peerRegistry := mpc.NewRegistry(nodeID, peerNodeIDs, consulClient.KV())
164+
peerRegistry := mpc.NewRegistry(nodeID, peerNodeIDs, consulClient.KV(), directMessaging, pubsub, identityStore)
166165

167166
mpcNode := mpc.NewNode(
168167
nodeID,
@@ -194,34 +193,46 @@ func runNode(ctx context.Context, c *cli.Command) error {
194193

195194
timeoutConsumer.Run()
196195
defer timeoutConsumer.Close()
197-
keygenConsumer := eventconsumer.NewKeygenConsumer(natsConn, keygenBroker, pubsub, peerRegistry)
198-
signingConsumer := eventconsumer.NewSigningConsumer(natsConn, signingBroker, pubsub, peerRegistry)
196+
keygenConsumer := eventconsumer.NewKeygenConsumer(natsConn, keygenBroker, pubsub, peerRegistry, genKeyResultQueue)
197+
signingConsumer := eventconsumer.NewSigningConsumer(natsConn, signingBroker, pubsub, peerRegistry, singingResultQueue)
199198

200199
// Make the node ready before starting the signing consumer
201200
if err := peerRegistry.Ready(); err != nil {
202201
logger.Error("Failed to mark peer registry as ready", err)
203202
}
204203
logger.Info("[READY] Node is ready", "nodeID", nodeID)
204+
205+
logger.Info("Starting consumers", "nodeID", nodeID)
205206
appContext, cancel := context.WithCancel(context.Background())
206-
// Setup signal handling to cancel context on termination signals.
207+
//Setup signal handling to cancel context on termination signals.
207208
go func() {
208209
sigChan := make(chan os.Signal, 1)
209210
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
210211
<-sigChan
211212
logger.Warn("Shutdown signal received, canceling context...")
212213
cancel()
213214

215+
// Resign from peer registry first (before closing NATS)
216+
if err := peerRegistry.Resign(); err != nil {
217+
logger.Error("Failed to resign from peer registry", err)
218+
}
219+
214220
// Gracefully close consumers
215221
if err := keygenConsumer.Close(); err != nil {
216222
logger.Error("Failed to close keygen consumer", err)
217223
}
218224
if err := signingConsumer.Close(); err != nil {
219225
logger.Error("Failed to close signing consumer", err)
220226
}
227+
228+
err := natsConn.Drain()
229+
if err != nil {
230+
logger.Error("Failed to drain NATS connection", err)
231+
}
221232
}()
222233

223234
var wg sync.WaitGroup
224-
errChan := make(chan error, 2)
235+
errChan := make(chan error, 3)
225236

226237
wg.Add(1)
227238
go func() {
@@ -250,14 +261,14 @@ func runNode(ctx context.Context, c *cli.Command) error {
250261
logger.Info("All consumers have finished")
251262
close(errChan)
252263
}()
253-
254264
for err := range errChan {
255265
if err != nil {
256266
logger.Error("Consumer error received", err)
257267
cancel()
258268
return err
259269
}
260270
}
271+
261272
return nil
262273
}
263274

config.yaml.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ consul:
55

66
mpc_threshold: 2
77
environment: development
8-
badger_password: "your_badger_password"
8+
badger_password: "F))ysJp?E]ol&I;^"
99
event_initiator_pubkey: "event_initiator_pubkey"
1010
db_path: "."
1111
backup_enabled: true

e2e/reshare_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func testKeyGenerationForResharing(t *testing.T, suite *E2ETestSuite) {
9292
require.NoError(t, err, "Failed to setup keygen result listener")
9393

9494
// Add a small delay to ensure the result listener is fully set up
95-
time.Sleep(2 * time.Second)
95+
time.Sleep(10 * time.Second)
9696

9797
// Trigger key generation for all wallets
9898
for _, walletID := range walletIDs {
@@ -179,7 +179,7 @@ func testResharingAllNodes(t *testing.T, suite *E2ETestSuite) {
179179
require.NoError(t, err, "Failed to setup resharing result listener")
180180

181181
// Wait for listener setup
182-
time.Sleep(2 * time.Second)
182+
time.Sleep(10 * time.Second)
183183

184184
// Test resharing for both key types
185185
for i, walletID := range suite.walletIDs {
@@ -360,7 +360,7 @@ func testSigningAfterResharing(t *testing.T, suite *E2ETestSuite) {
360360
require.NoError(t, err, "Failed to setup signing result listener")
361361

362362
// Wait for listener setup
363-
time.Sleep(2 * time.Second)
363+
time.Sleep(10 * time.Second)
364364

365365
// Test messages to sign
366366
testMessages := []string{

e2e/setup_test_identities.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
# E2E Test Identity Setup Script
44
# This script sets up identities for testing with separate test database paths
5-
65
set -e
76

87
# Number of test nodes

e2e/sign_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ func testKeyGenerationForSigning(t *testing.T, suite *E2ETestSuite) {
150150
require.NoError(t, err, "Failed to setup keygen result listener")
151151

152152
// Add a small delay to ensure the result listener is fully set up
153-
time.Sleep(2 * time.Second)
153+
time.Sleep(10 * time.Second)
154154

155155
// Trigger key generation for all wallets
156156
for _, walletID := range walletIDs {

examples/reshare/main.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ func main() {
4949

5050
resharingMsg := &types.ResharingMessage{
5151
SessionID: uuid.NewString(),
52-
WalletID: "bf2cc849-8e55-47e4-ab73-e17fb1eb690c",
53-
NodeIDs: []string{"d926fa75-72c7-4538-9052-4a064a84981d", "7b1090cd-ffe3-46ff-8375-594dd3204169"}, // new peer IDs
52+
WalletID: "506d2d40-483a-49f1-93c8-27dd4fe9740c",
53+
NodeIDs: []string{"c95c340e-5a18-472d-b9b0-5ac68218213a", "ac37e85f-caca-4bee-8a3a-49a0fe35abff"}, // new peer IDs
5454

55-
NewThreshold: 2, // t+1 <= len(NodeIDs)
55+
NewThreshold: 1, // t+1 <= len(NodeIDs)
5656
KeyType: types.KeyTypeEd25519,
5757
}
5858
err = mpcClient.Resharing(resharingMsg)

pkg/encryption/aes.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"crypto/aes"
55
"crypto/cipher"
66
"crypto/rand"
7+
"errors"
8+
"fmt"
79
)
810

911
func EncryptAESGCM(plain, key []byte) (ciphertext, nonce []byte, err error) {
@@ -34,3 +36,51 @@ func DecryptAESGCM(ciphertext, key, nonce []byte) ([]byte, error) {
3436
}
3537
return aead.Open(nil, nonce, ciphertext, nil)
3638
}
39+
40+
// EncryptAESGCMWithNonceEmbed encrypts plaintext and embeds the nonce at the start of the returned slice.
41+
func EncryptAESGCMWithNonceEmbed(plaintext, key []byte) ([]byte, error) {
42+
block, err := aes.NewCipher(key)
43+
if err != nil {
44+
return nil, fmt.Errorf("failed to create AES cipher: %w", err)
45+
}
46+
47+
aead, err := cipher.NewGCM(block)
48+
if err != nil {
49+
return nil, fmt.Errorf("failed to create GCM: %w", err)
50+
}
51+
52+
nonce := make([]byte, aead.NonceSize())
53+
if _, err := rand.Read(nonce); err != nil {
54+
return nil, fmt.Errorf("failed to generate nonce: %w", err)
55+
}
56+
57+
ciphertext := aead.Seal(nil, nonce, plaintext, nil)
58+
return append(nonce, ciphertext...), nil
59+
}
60+
61+
// DecryptAESGCMWithNonceEmbed decrypts ciphertext where the nonce is embedded at the start of the slice.
62+
func DecryptAESGCMWithNonceEmbed(data, key []byte) ([]byte, error) {
63+
block, err := aes.NewCipher(key)
64+
if err != nil {
65+
return nil, fmt.Errorf("failed to create AES cipher: %w", err)
66+
}
67+
aead, err := cipher.NewGCM(block)
68+
if err != nil {
69+
return nil, fmt.Errorf("failed to create GCM: %w", err)
70+
}
71+
72+
nonceSize := aead.NonceSize()
73+
if len(data) < nonceSize {
74+
return nil, errors.New("ciphertext too short")
75+
}
76+
77+
nonce := data[:nonceSize]
78+
ciphertext := data[nonceSize:]
79+
80+
plaintext, err := aead.Open(nil, nonce, ciphertext, nil)
81+
if err != nil {
82+
return nil, fmt.Errorf("decryption failed: %w", err)
83+
}
84+
85+
return plaintext, nil
86+
}

0 commit comments

Comments
 (0)