Skip to content

Commit ab07ce4

Browse files
im-adithyarolznz
andauthored
chore: switch to encryption tag from versioning (#1093)
* chore: switch to encryption tag from versioning * chore: also support versioning * chore: check version tag first * Chore: nwc encryption feedback (#1137) * chore: code cleanup, and responses for invalid encryption tag / unable to decrypt * chore: add constants for encryption types * chore: update test to cover when encryption tag is passed with no version * chore: explain why we respond using nip-44 encryption if decryption fails --------- Co-authored-by: Roland Bewick <roland.bewick@gmail.com> Co-authored-by: Roland <33993199+rolznz@users.noreply.github.com>
1 parent 3e1d16d commit ab07ce4

File tree

10 files changed

+309
-136
lines changed

10 files changed

+309
-136
lines changed

constants/constants.go

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,20 @@ const INVOICE_METADATA_MAX_LENGTH = 4096
4949

5050
// errors used by NIP-47 and the transaction service
5151
const (
52-
ERROR_INTERNAL = "INTERNAL"
53-
ERROR_NOT_IMPLEMENTED = "NOT_IMPLEMENTED"
54-
ERROR_QUOTA_EXCEEDED = "QUOTA_EXCEEDED"
55-
ERROR_INSUFFICIENT_BALANCE = "INSUFFICIENT_BALANCE"
56-
ERROR_UNAUTHORIZED = "UNAUTHORIZED"
57-
ERROR_EXPIRED = "EXPIRED"
58-
ERROR_RESTRICTED = "RESTRICTED"
59-
ERROR_BAD_REQUEST = "BAD_REQUEST"
60-
ERROR_NOT_FOUND = "NOT_FOUND"
61-
ERROR_UNSUPPORTED_VERSION = "UNSUPPORTED_VERSION"
62-
ERROR_OTHER = "OTHER"
52+
ERROR_INTERNAL = "INTERNAL"
53+
ERROR_NOT_IMPLEMENTED = "NOT_IMPLEMENTED"
54+
ERROR_QUOTA_EXCEEDED = "QUOTA_EXCEEDED"
55+
ERROR_INSUFFICIENT_BALANCE = "INSUFFICIENT_BALANCE"
56+
ERROR_UNAUTHORIZED = "UNAUTHORIZED"
57+
ERROR_EXPIRED = "EXPIRED"
58+
ERROR_RESTRICTED = "RESTRICTED"
59+
ERROR_BAD_REQUEST = "BAD_REQUEST"
60+
ERROR_NOT_FOUND = "NOT_FOUND"
61+
ERROR_UNSUPPORTED_ENCRYPTION = "UNSUPPORTED_ENCRYPTION"
62+
ERROR_OTHER = "OTHER"
63+
)
64+
65+
const (
66+
ENCRYPTION_TYPE_NIP04 = "nip04"
67+
ENCRYPTION_TYPE_NIP44_V2 = "nip44_v2"
6368
)

nip47/cipher/cipher.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,33 @@ package cipher
33
import (
44
"fmt"
55

6+
"github.com/getAlby/hub/constants"
67
"github.com/nbd-wtf/go-nostr/nip04"
78
"github.com/nbd-wtf/go-nostr/nip44"
89
)
910

1011
const (
11-
SUPPORTED_VERSIONS = "1.0 0.0"
12+
SUPPORTED_VERSIONS = "0.0 1.0"
13+
SUPPORTED_ENCRYPTIONS = "nip44_v2 nip04"
1214
)
1315

1416
type Nip47Cipher struct {
15-
version string
17+
encryption string
1618
pubkey string
1719
privkey string
1820
sharedSecret []byte
1921
conversationKey [32]byte
2022
}
2123

22-
func NewNip47Cipher(version, pubkey, privkey string) (*Nip47Cipher, error) {
23-
_, err := isVersionSupported(version)
24+
func NewNip47Cipher(encryption, pubkey, privkey string) (*Nip47Cipher, error) {
25+
_, err := isEncryptionSupported(encryption)
2426
if err != nil {
2527
return nil, err
2628
}
2729

2830
var ss []byte
2931
var ck [32]byte
30-
if version == "0.0" {
32+
if encryption == constants.ENCRYPTION_TYPE_NIP04 {
3133
ss, err = nip04.ComputeSharedSecret(pubkey, privkey)
3234
if err != nil {
3335
return nil, err
@@ -40,7 +42,7 @@ func NewNip47Cipher(version, pubkey, privkey string) (*Nip47Cipher, error) {
4042
}
4143

4244
return &Nip47Cipher{
43-
version: version,
45+
encryption: encryption,
4446
pubkey: pubkey,
4547
privkey: privkey,
4648
sharedSecret: ss,
@@ -49,7 +51,7 @@ func NewNip47Cipher(version, pubkey, privkey string) (*Nip47Cipher, error) {
4951
}
5052

5153
func (c *Nip47Cipher) Encrypt(message string) (msg string, err error) {
52-
if c.version == "0.0" {
54+
if c.encryption == constants.ENCRYPTION_TYPE_NIP04 {
5355
msg, err = nip04.Encrypt(message, c.sharedSecret)
5456
if err != nil {
5557
return "", err
@@ -64,7 +66,7 @@ func (c *Nip47Cipher) Encrypt(message string) (msg string, err error) {
6466
}
6567

6668
func (c *Nip47Cipher) Decrypt(content string) (payload string, err error) {
67-
if c.version == "0.0" {
69+
if c.encryption == constants.ENCRYPTION_TYPE_NIP04 {
6870
payload, err = nip04.Decrypt(content, c.sharedSecret)
6971
if err != nil {
7072
return "", err
@@ -78,10 +80,10 @@ func (c *Nip47Cipher) Decrypt(content string) (payload string, err error) {
7880
return payload, nil
7981
}
8082

81-
func isVersionSupported(version string) (bool, error) {
82-
if version == "1.0" || version == "0.0" {
83+
func isEncryptionSupported(encryption string) (bool, error) {
84+
if encryption == constants.ENCRYPTION_TYPE_NIP44_V2 || encryption == constants.ENCRYPTION_TYPE_NIP04 {
8385
return true, nil
8486
}
8587

86-
return false, fmt.Errorf("invalid version: %s", version)
88+
return false, fmt.Errorf("invalid encryption: %s", encryption)
8789
}

nip47/cipher/cipher_test.go

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,22 @@ import (
44
"fmt"
55
"testing"
66

7+
"github.com/getAlby/hub/constants"
78
"github.com/nbd-wtf/go-nostr"
89

910
"github.com/stretchr/testify/assert"
1011
)
1112

1213
func TestCipher(t *testing.T) {
13-
doTestCipher(t, "0.0")
14-
doTestCipher(t, "1.0")
14+
doTestCipher(t, constants.ENCRYPTION_TYPE_NIP04)
15+
doTestCipher(t, constants.ENCRYPTION_TYPE_NIP44_V2)
1516
}
1617

17-
func doTestCipher(t *testing.T, version string) {
18+
func doTestCipher(t *testing.T, encryption string) {
1819
reqPrivateKey := nostr.GeneratePrivateKey()
1920
reqPubkey, err := nostr.GetPublicKey(reqPrivateKey)
2021

21-
nip47Cipher, err := NewNip47Cipher(version, reqPubkey, reqPrivateKey)
22+
nip47Cipher, err := NewNip47Cipher(encryption, reqPubkey, reqPrivateKey)
2223
assert.NoError(t, err)
2324

2425
payload := "test payload"
@@ -29,19 +30,19 @@ func doTestCipher(t *testing.T, version string) {
2930
assert.Equal(t, payload, decrypted)
3031
}
3132

32-
func TestCipher_UnsupportedVersions(t *testing.T) {
33-
doTestCipher_UnsupportedVersions(t, "1")
34-
doTestCipher_UnsupportedVersions(t, "x.1")
35-
doTestCipher_UnsupportedVersions(t, "1.x")
36-
doTestCipher_UnsupportedVersions(t, "2.0")
37-
doTestCipher_UnsupportedVersions(t, "0.5")
33+
func TestCipher_UnsupportedEncrptions(t *testing.T) {
34+
doTestCipher_UnsupportedEncrptions(t, "nip44")
35+
doTestCipher_UnsupportedEncrptions(t, "nip44_v0")
36+
doTestCipher_UnsupportedEncrptions(t, "nip44_v1")
37+
doTestCipher_UnsupportedEncrptions(t, "nip44v2")
38+
doTestCipher_UnsupportedEncrptions(t, "nip-44")
3839
}
3940

40-
func doTestCipher_UnsupportedVersions(t *testing.T, version string) {
41+
func doTestCipher_UnsupportedEncrptions(t *testing.T, encryption string) {
4142
reqPrivateKey := nostr.GeneratePrivateKey()
4243
reqPubkey, err := nostr.GetPublicKey(reqPrivateKey)
4344

44-
_, err = NewNip47Cipher(version, reqPubkey, reqPrivateKey)
45+
_, err = NewNip47Cipher(encryption, reqPubkey, reqPrivateKey)
4546
assert.Error(t, err)
46-
assert.Equal(t, fmt.Sprintf("invalid version: %s", version), err.Error())
47+
assert.Equal(t, fmt.Sprintf("invalid encryption: %s", encryption), err.Error())
4748
}

nip47/event_handler.go

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,20 +93,31 @@ func (svc *nip47Service) HandleEvent(ctx context.Context, relay nostrmodels.Rela
9393
}
9494
}
9595

96-
version := "0.0"
97-
vTag := event.Tags.GetFirst([]string{"v"})
96+
encryption := constants.ENCRYPTION_TYPE_NIP04
97+
encryptionTag := event.Tags.GetFirst([]string{"encryption"})
98+
if encryptionTag != nil {
99+
encryption = encryptionTag.Value()
100+
}
98101

99-
if vTag != nil && vTag.Value() != "" {
100-
version = vTag.Value()
102+
// TODO: Remove version tag after 01-06-2025
103+
if encryptionTag == nil {
104+
vTag := event.Tags.GetFirst([]string{"v"})
105+
if vTag != nil && vTag.Value() != "" {
106+
version := vTag.Value()
107+
if version == "1.0" {
108+
encryption = constants.ENCRYPTION_TYPE_NIP44_V2
109+
}
110+
}
101111
}
102112

103-
nip47Cipher, err := cipher.NewNip47Cipher(version, app.AppPubkey, appWalletPrivKey)
113+
nip47Cipher, err := cipher.NewNip47Cipher(encryption, app.AppPubkey, appWalletPrivKey)
104114
if err != nil {
115+
cipherErr := err
105116
logger.Logger.WithFields(logrus.Fields{
106117
"requestEventNostrId": event.ID,
107118
"eventKind": event.Kind,
108119
"appId": app.ID,
109-
"version": version,
120+
"encryption": encryption,
110121
}).WithError(err).Error("Failed to initialize cipher")
111122

112123
requestEvent.State = db.REQUEST_EVENT_STATE_HANDLER_ERROR
@@ -116,6 +127,37 @@ func (svc *nip47Service) HandleEvent(ctx context.Context, relay nostrmodels.Rela
116127
"appPubkey": event.PubKey,
117128
}).WithError(err).Error("Failed to save state to nostr event")
118129
}
130+
131+
// whenever we are unable to handle the request encryption, we always respond with our preferred encryption
132+
// re-create the cipher with NIP-44 to send an error response
133+
nip47Cipher, err := cipher.NewNip47Cipher(constants.ENCRYPTION_TYPE_NIP44_V2, app.AppPubkey, appWalletPrivKey)
134+
135+
if err != nil {
136+
logger.Logger.WithFields(logrus.Fields{
137+
"requestEventNostrId": event.ID,
138+
"eventKind": event.Kind,
139+
"appId": app.ID,
140+
"encryption": encryption,
141+
}).WithError(err).Error("Failed to initialize cipher")
142+
return
143+
}
144+
145+
nip47Response = &models.Response{
146+
Error: &models.Error{
147+
Code: constants.ERROR_UNSUPPORTED_ENCRYPTION,
148+
Message: cipherErr.Error(),
149+
},
150+
}
151+
152+
resp, err := svc.CreateResponse(event, nip47Response, nostr.Tags{}, nip47Cipher, appWalletPrivKey)
153+
if err != nil {
154+
logger.Logger.WithFields(logrus.Fields{
155+
"requestEventNostrId": event.ID,
156+
"eventKind": event.Kind,
157+
}).WithError(err).Error("Failed to process event")
158+
}
159+
svc.publishResponseEvent(ctx, relay, &requestEvent, resp, &app)
160+
119161
return
120162
}
121163

@@ -154,6 +196,7 @@ func (svc *nip47Service) HandleEvent(ctx context.Context, relay nostrmodels.Rela
154196

155197
payload, err := nip47Cipher.Decrypt(event.Content)
156198
if err != nil {
199+
decryptionErr := err
157200
logger.Logger.WithFields(logrus.Fields{
158201
"requestEventNostrId": event.ID,
159202
"eventKind": event.Kind,
@@ -168,6 +211,36 @@ func (svc *nip47Service) HandleEvent(ctx context.Context, relay nostrmodels.Rela
168211
}).WithError(err).Error("Failed to save state to nostr event")
169212
}
170213

214+
// whenever we are unable to handle the request encryption, we always respond with our preferred encryption
215+
// re-create the cipher with NIP-44 to send an error response
216+
nip47Cipher, err := cipher.NewNip47Cipher(constants.ENCRYPTION_TYPE_NIP44_V2, app.AppPubkey, appWalletPrivKey)
217+
218+
if err != nil {
219+
logger.Logger.WithFields(logrus.Fields{
220+
"requestEventNostrId": event.ID,
221+
"eventKind": event.Kind,
222+
"appId": app.ID,
223+
"encryption": encryption,
224+
}).WithError(err).Error("Failed to initialize cipher")
225+
return
226+
}
227+
228+
nip47Response = &models.Response{
229+
Error: &models.Error{
230+
Code: constants.ERROR_INTERNAL,
231+
Message: fmt.Sprintf("failed to decrypt: %s", decryptionErr.Error()),
232+
},
233+
}
234+
235+
resp, err := svc.CreateResponse(event, nip47Response, nostr.Tags{}, nip47Cipher, appWalletPrivKey)
236+
if err != nil {
237+
logger.Logger.WithFields(logrus.Fields{
238+
"requestEventNostrId": event.ID,
239+
"eventKind": event.Kind,
240+
}).WithError(err).Error("Failed to process event")
241+
}
242+
svc.publishResponseEvent(ctx, relay, &requestEvent, resp, &app)
243+
171244
return
172245
}
173246
nip47Request := &models.Request{}

nip47/event_handler_shared_wallet_pubkey_test.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/stretchr/testify/require"
77

8+
"github.com/getAlby/hub/constants"
89
"github.com/getAlby/hub/tests"
910
)
1011

@@ -13,77 +14,77 @@ func TestHandleResponse_SharedWalletPubkey_Nip04_WithPermission(t *testing.T) {
1314
require.NoError(t, err)
1415
defer svc.Remove()
1516

16-
doTestHandleResponse_WithPermission(t, svc, tests.CreateAppWithSharedWalletPubkey, "0.0")
17+
doTestHandleResponse_WithPermission(t, svc, tests.CreateAppWithSharedWalletPubkey, constants.ENCRYPTION_TYPE_NIP04)
1718
}
1819

1920
func TestHandleResponse_SharedWalletPubkey_Nip44_WithPermission(t *testing.T) {
2021
svc, err := tests.CreateTestService(t)
2122
require.NoError(t, err)
2223
defer svc.Remove()
2324

24-
doTestHandleResponse_WithPermission(t, svc, tests.CreateAppWithSharedWalletPubkey, "1.0")
25+
doTestHandleResponse_WithPermission(t, svc, tests.CreateAppWithSharedWalletPubkey, constants.ENCRYPTION_TYPE_NIP44_V2)
2526
}
2627

2728
func TestHandleResponse_SharedWalletPubkey_Nip04_DuplicateRequest(t *testing.T) {
2829
svc, err := tests.CreateTestService(t)
2930
require.NoError(t, err)
3031
defer svc.Remove()
3132

32-
doTestHandleResponse_DuplicateRequest(t, svc, tests.CreateAppWithSharedWalletPubkey, "0.0")
33+
doTestHandleResponse_DuplicateRequest(t, svc, tests.CreateAppWithSharedWalletPubkey, constants.ENCRYPTION_TYPE_NIP04)
3334
}
3435

3536
func TestHandleResponse_SharedWalletPubkey_Nip44_DuplicateRequest(t *testing.T) {
3637
svc, err := tests.CreateTestService(t)
3738
require.NoError(t, err)
3839
defer svc.Remove()
3940

40-
doTestHandleResponse_DuplicateRequest(t, svc, tests.CreateAppWithSharedWalletPubkey, "1.0")
41+
doTestHandleResponse_DuplicateRequest(t, svc, tests.CreateAppWithSharedWalletPubkey, constants.ENCRYPTION_TYPE_NIP44_V2)
4142
}
4243

4344
func TestHandleResponse_SharedWalletPubkey_Nip04_NoPermission(t *testing.T) {
4445
svc, err := tests.CreateTestService(t)
4546
require.NoError(t, err)
4647
defer svc.Remove()
4748

48-
doTestHandleResponse_NoPermission(t, svc, tests.CreateAppWithSharedWalletPubkey, "0.0")
49+
doTestHandleResponse_NoPermission(t, svc, tests.CreateAppWithSharedWalletPubkey, constants.ENCRYPTION_TYPE_NIP04)
4950
}
5051

5152
func TestHandleResponse_SharedWalletPubkey_Nip44_NoPermission(t *testing.T) {
5253
svc, err := tests.CreateTestService(t)
5354
require.NoError(t, err)
5455
defer svc.Remove()
5556

56-
doTestHandleResponse_NoPermission(t, svc, tests.CreateAppWithSharedWalletPubkey, "1.0")
57+
doTestHandleResponse_NoPermission(t, svc, tests.CreateAppWithSharedWalletPubkey, constants.ENCRYPTION_TYPE_NIP44_V2)
5758
}
5859

5960
func TestHandleResponse_SharedWalletPubkey_Nip04_IncorrectPubkey(t *testing.T) {
6061
svc, err := tests.CreateTestService(t)
6162
require.NoError(t, err)
6263
defer svc.Remove()
6364

64-
doTestHandleResponse_IncorrectPubkey(t, svc, tests.CreateAppWithSharedWalletPubkey, "0.0")
65+
doTestHandleResponse_IncorrectPubkey(t, svc, tests.CreateAppWithSharedWalletPubkey, constants.ENCRYPTION_TYPE_NIP04)
6566
}
6667

6768
func TestHandleResponse_SharedWalletPubkey_Nip44_IncorrectPubkey(t *testing.T) {
6869
svc, err := tests.CreateTestService(t)
6970
require.NoError(t, err)
7071
defer svc.Remove()
7172

72-
doTestHandleResponse_IncorrectPubkey(t, svc, tests.CreateAppWithSharedWalletPubkey, "1.0")
73+
doTestHandleResponse_IncorrectPubkey(t, svc, tests.CreateAppWithSharedWalletPubkey, constants.ENCRYPTION_TYPE_NIP44_V2)
7374
}
7475

7576
func TestHandleResponse_SharedWalletPubkey_Nip04_OldRequestForPayment(t *testing.T) {
7677
svc, err := tests.CreateTestService(t)
7778
require.NoError(t, err)
7879
defer svc.Remove()
7980

80-
doTestHandleResponse_OldRequestForPayment(t, svc, tests.CreateAppWithSharedWalletPubkey, "0.0")
81+
doTestHandleResponse_OldRequestForPayment(t, svc, tests.CreateAppWithSharedWalletPubkey, constants.ENCRYPTION_TYPE_NIP04)
8182
}
8283

8384
func TestHandleResponse_SharedWalletPubkey_Nip44_OldRequestForPayment(t *testing.T) {
8485
svc, err := tests.CreateTestService(t)
8586
require.NoError(t, err)
8687
defer svc.Remove()
8788

88-
doTestHandleResponse_OldRequestForPayment(t, svc, tests.CreateAppWithSharedWalletPubkey, "1.0")
89+
doTestHandleResponse_OldRequestForPayment(t, svc, tests.CreateAppWithSharedWalletPubkey, constants.ENCRYPTION_TYPE_NIP44_V2)
8990
}

0 commit comments

Comments
 (0)