From 506db708316368076a682638c5e5d34f46d23a93 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Sat, 30 Sep 2023 14:40:52 +0200 Subject: [PATCH] Implement NIP-44 decrypt --- nip44.go | 47 +++++++++++++++++++++++++++++++++++- nip44_test.go | 67 +++++++++++++++++++++++++++------------------------ 2 files changed, 82 insertions(+), 32 deletions(-) diff --git a/nip44.go b/nip44.go index cc1a5d5..38684df 100644 --- a/nip44.go +++ b/nip44.go @@ -1,6 +1,7 @@ package nip44 import ( + "bytes" "crypto/hmac" "crypto/rand" "crypto/sha256" @@ -47,7 +48,7 @@ func Encrypt(key []byte, plaintext string, options *EncryptOptions) (string, err } } if version != 2 { - return "", errors.New("unknown encryption version") + return "", errors.New("unknown version") } if len(salt) != 32 { return "", errors.New("salt must be 32 bytes") @@ -69,6 +70,50 @@ func Encrypt(key []byte, plaintext string, options *EncryptOptions) (string, err return base64.StdEncoding.EncodeToString(concat), nil } +func Decrypt(key []byte, ciphertext string) (string, error) { + var ( + version int = 2 + decoded []byte + dLen int + salt []byte + ciphertext_ []byte + hmac_ []byte + enc []byte + nonce []byte + auth []byte + padded []byte + unpaddedLen uint16 + unpadded []byte + err error + ) + if ciphertext[0:1] == "#" { + return "", errors.New("unknown version") + } + if decoded, err = base64.StdEncoding.DecodeString(ciphertext); err != nil { + return "", err + } + if version = int(decoded[0]); version != 2 { + return "", errors.New("unknown version") + } + dLen = len(decoded) + salt, ciphertext_, hmac_ = decoded[1:33], decoded[33:dLen-32], decoded[dLen-32:] + if enc, nonce, auth, err = messageKeys(key, salt); err != nil { + return "", err + } + if !bytes.Equal(hmac_, sha256Hmac(auth, ciphertext_)) { + return "", errors.New("invalid hmac") + } + if padded, err = chacha20Encrypt(enc, nonce, ciphertext_); err != nil { + return "", err + } + unpaddedLen = binary.BigEndian.Uint16(padded[0:2]) + unpadded = padded[2 : unpaddedLen+2] + if len(unpadded) == 0 || len(unpadded) != int(unpaddedLen) || len(padded) != 2+calcPadding(int(unpaddedLen)) { + return "", errors.New("invalid padding") + } + return string(unpadded), nil +} + func chacha20Encrypt(key []byte, nonce []byte, message []byte) ([]byte, error) { var ( cipher *chacha20.Cipher diff --git a/nip44_test.go b/nip44_test.go index 577ef24..5474092 100644 --- a/nip44_test.go +++ b/nip44_test.go @@ -8,12 +8,13 @@ import ( "github.com/stretchr/testify/assert" ) -func assertEncrypt(t *testing.T, key string, salt string, plaintext string, expected string) { +func assertCrypt(t *testing.T, key string, salt string, plaintext string, expected string) { var ( - k []byte - s []byte - actual string - err error + k []byte + s []byte + actual string + decrypted string + err error ) if k, err = hex.DecodeString(key); err != nil { t.Errorf("hex decode failed for key") @@ -24,11 +25,15 @@ func assertEncrypt(t *testing.T, key string, salt string, plaintext string, expe actual, err = nip44.Encrypt(k, plaintext, &nip44.EncryptOptions{Salt: s}) if assert.NoError(t, err) { assert.Equal(t, expected, actual) + decrypted, err = nip44.Decrypt(k, expected) + if assert.NoError(t, err) { + assert.Equal(t, decrypted, plaintext) + } } } -func TestEncrypt001(t *testing.T) { - assertEncrypt(t, +func TestCrypt001(t *testing.T) { + assertCrypt(t, "c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", "0000000000000000000000000000000000000000000000000000000000000001", "a", @@ -36,8 +41,8 @@ func TestEncrypt001(t *testing.T) { ) } -func TestEncrypt002(t *testing.T) { - assertEncrypt(t, +func TestCrypt002(t *testing.T) { + assertCrypt(t, "c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", "f00000000000000000000000000000f00000000000000000000000000000000f", "๐Ÿ•๐Ÿซƒ", @@ -45,8 +50,8 @@ func TestEncrypt002(t *testing.T) { ) } -func TestEncrypt003(t *testing.T) { - assertEncrypt(t, +func TestCrypt003(t *testing.T) { + assertCrypt(t, "94da47d851b9c1ed33b3b72f35434f56aa608d60e573e9c295f568011f4f50a4", "b635236c42db20f021bb8d1cdff5ca75dd1a0cc72ea742ad750f33010b24f73b", "่กจใƒใ‚A้ท—ล’รฉ๏ผข้€รœรŸยชฤ…รฑไธ‚ใ€๐ €€", @@ -54,8 +59,8 @@ func TestEncrypt003(t *testing.T) { ) } -func TestEncrypt004(t *testing.T) { - assertEncrypt(t, +func TestCrypt004(t *testing.T) { + assertCrypt(t, "ab99c122d4586cdd5c813058aa543d0e7233545dbf6874fc34a3d8d9a18fbbc3", "b20989adc3ddc41cd2c435952c0d59a91315d8c5218d5040573fc3749543acaf", "ability๐Ÿค็š„ ศบศพ", @@ -63,8 +68,8 @@ func TestEncrypt004(t *testing.T) { ) } -func TestEncrypt005(t *testing.T) { - assertEncrypt(t, +func TestCrypt005(t *testing.T) { + assertCrypt(t, "a449f2a85c6d3db0f44c64554a05d11a3c0988d645e4b4b2592072f63662f422", "8d4442713eb9d4791175cb040d98d6fc5be8864d6ec2f89cf0895a2b2b72d1b1", "pepper๐Ÿ‘€ั—ะถะฐะบ", @@ -72,8 +77,8 @@ func TestEncrypt005(t *testing.T) { ) } -func TestEncrypt006(t *testing.T) { - assertEncrypt(t, +func TestCrypt006(t *testing.T) { + assertCrypt(t, "decde9938ffcb14fa7ff300105eb1bf239469af9baf376e69755b9070ae48c47", "2180b52ae645fcf9f5080d81b1f0b5d6f2cd77ff3c986882bb549158462f3407", "( อกยฐ อœส– อกยฐ)", @@ -81,8 +86,8 @@ func TestEncrypt006(t *testing.T) { ) } -func TestEncrypt007(t *testing.T) { - assertEncrypt(t, +func TestCrypt007(t *testing.T) { + assertCrypt(t, "c6f2fde7aa00208c388f506455c31c3fa07caf8b516d43bf7514ee19edcda994", "e4cd5f7ce4eea024bc71b17ad456a986a74ac426c2c62b0a15eb5c5c8f888b68", "ู…ูู†ูŽุงู‚ูŽุดูŽุฉู ุณูุจูู„ู ุงูุณู’ุชูุฎู’ุฏูŽุงู…ู ุงู„ู„ูู‘ุบูŽุฉู ูููŠ ุงู„ู†ูู‘ุธูู…ู ุงู„ู’ู‚ูŽุงุฆูู…ูŽุฉู ูˆูŽูููŠู… ูŠูŽุฎูุตูŽู‘ ุงู„ุชูŽู‘ุทู’ุจููŠู‚ูŽุงุชู ุงู„ู’ุญุงุณููˆุจููŠูŽู‘ุฉูุŒ", @@ -90,8 +95,8 @@ func TestEncrypt007(t *testing.T) { ) } -func TestEncrypt008(t *testing.T) { - assertEncrypt(t, +func TestCrypt008(t *testing.T) { + assertCrypt(t, "c6f2fde7aa00208c388f506455c31c3fa07caf8b516d43bf7514ee19edcda994", "38d1ca0abef9e5f564e89761a86cee04574b6825d3ef2063b10ad75899e4b023", "ุงู„ูƒู„ ููŠ ุงู„ู…ุฌู…ูˆ ุนุฉ (5)", @@ -99,8 +104,8 @@ func TestEncrypt008(t *testing.T) { ) } -func TestEncrypt009(t *testing.T) { - assertEncrypt(t, +func TestCrypt009(t *testing.T) { + assertCrypt(t, "c6f2fde7aa00208c388f506455c31c3fa07caf8b516d43bf7514ee19edcda994", "4f1a31909f3483a9e69c8549a55bbc9af25fa5bbecf7bd32d9896f83ef2e12e0", "๐–‘๐–†๐–Ÿ๐–ž ็คพๆœƒ็ง‘ๅญธ้™ข่ชžๅญธ็ ”็ฉถๆ‰€", @@ -108,8 +113,8 @@ func TestEncrypt009(t *testing.T) { ) } -func TestEncrypt010(t *testing.T) { - assertEncrypt(t, +func TestCrypt010(t *testing.T) { + assertCrypt(t, "c6f2fde7aa00208c388f506455c31c3fa07caf8b516d43bf7514ee19edcda994", "a3e219242d85465e70adcd640b564b3feff57d2ef8745d5e7a0663b2dccceb54", "๐Ÿ™ˆ ๐Ÿ™‰ ๐Ÿ™Š 0๏ธโƒฃ 1๏ธโƒฃ 2๏ธโƒฃ 3๏ธโƒฃ 4๏ธโƒฃ 5๏ธโƒฃ 6๏ธโƒฃ 7๏ธโƒฃ 8๏ธโƒฃ 9๏ธโƒฃ ๐Ÿ”Ÿ Powerู„ูู„ูุตู‘ุจูู„ูู„ุตู‘ุจูุฑุฑู‹ เฅฃ เฅฃh เฅฃ เฅฃๅ†—", @@ -117,24 +122,24 @@ func TestEncrypt010(t *testing.T) { ) } -func TestEncrypt011(t *testing.T) { - assertEncrypt(t, +func TestCrypt011(t *testing.T) { + assertCrypt(t, "7a1ccf5ce5a08e380f590de0c02776623b85a61ae67cfb6a017317e505b7cb51", "a000000000000000000000000000000000000000000000000000000000000001", "โฐโดโตโ‚€โ‚โ‚‚", "AqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2+xmGnjIMPMqqJGmjdYAYZUDUyEEUO3/evHUaO40LePeR91VlMVZ7I+nKJPkaUiKZ3cQiQnA86Uwti2IxepmzOFN", ) } -func TestEncrypt012(t *testing.T) { - assertEncrypt(t, +func TestCrypt012(t *testing.T) { + assertCrypt(t, "aa971537d741089885a0b48f2730a125e15b36033d089d4537a4e1204e76b39e", "b000000000000000000000000000000000000000000000000000000000000002", "A Peer-to-Peer Electronic Cash System", "ArAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACyuqG6RycuPyDPtwxzTcuMQu+is3N5XuWTlvCjligVaVBRydexaylXbsX592MEd3/Jt13BNL/GlpYpGDvLS4Tt/+2s9FX/16e/RDc+czdwXglc4DdSHiq+O06BvvXYfEQOPw=", ) } -func TestEncrypt013(t *testing.T) { - assertEncrypt(t, +func TestCrypt013(t *testing.T) { + assertCrypt(t, "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "A purely peer-to-peer version of electronic cash would allow online payments to be sent directly from one party to another without going through a financial institution. Digital signatures provide part of the solution, but the main benefits are lost if a trusted third party is still required to prevent double-spending.",