-
Notifications
You must be signed in to change notification settings - Fork 113
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
165 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
package nip49 | ||
|
||
import ( | ||
"crypto/rand" | ||
"encoding/hex" | ||
"fmt" | ||
"math" | ||
|
||
"github.com/btcsuite/btcd/btcutil/bech32" | ||
"golang.org/x/crypto/chacha20poly1305" | ||
"golang.org/x/crypto/scrypt" | ||
) | ||
|
||
type KeySecurityByte byte | ||
|
||
const ( | ||
KnownToHaveBeenHandledInsecurely KeySecurityByte = 0x00 | ||
NotKnownToHaveBeenHandledInsecurely KeySecurityByte = 0x01 | ||
ClientDoesNotTrackThisData KeySecurityByte = 0x02 | ||
) | ||
|
||
func Decrypt(bech32string string, password string) (secretKey string, err error) { | ||
secb, err := DecryptToBytes(bech32string, password) | ||
return hex.EncodeToString(secb), err | ||
} | ||
|
||
func DecryptToBytes(bech32string string, password string) (secretKey []byte, err error) { | ||
prefix, bits5, err := bech32.DecodeNoLimit(bech32string) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if prefix != "ncryptsec" { | ||
return nil, fmt.Errorf("expected prefix ncryptsec1") | ||
} | ||
|
||
data, err := bech32.ConvertBits(bits5, 5, 8, false) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed translating data into 8 bits: %s", err.Error()) | ||
} | ||
|
||
version := data[0] | ||
if version != 0x02 { | ||
return nil, fmt.Errorf("expected version 0x02, got %v", version) | ||
} | ||
|
||
logn := data[1] | ||
n := int(math.Pow(2, float64(int(logn)))) | ||
salt := data[2 : 2+16] | ||
nonce := data[2+16 : 2+16+24] | ||
ad := data[2+16+24 : 2+16+24+1] | ||
// keySecurityByte := ad[0] | ||
encryptedKey := data[2+16+24+1:] | ||
|
||
key, err := scrypt.Key([]byte(password), salt, n, 8, 1, 32) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to compute key with scrypt: %w", err) | ||
} | ||
|
||
c2p1, err := chacha20poly1305.NewX(key) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to start xchacha20poly1305: %w", err) | ||
} | ||
|
||
return c2p1.Open(nil, nonce, encryptedKey, ad) | ||
} | ||
|
||
func Encrypt(secretKey string, password string, logn uint8, ksb KeySecurityByte) (b32code string, err error) { | ||
skb, err := hex.DecodeString(secretKey) | ||
if err != nil || len(skb) != 32 { | ||
return "", fmt.Errorf("invalid secret key") | ||
} | ||
return EncryptBytes(skb, password, logn, ksb) | ||
} | ||
|
||
func EncryptBytes(secretKey []byte, password string, logn uint8, ksb KeySecurityByte) (b32code string, err error) { | ||
salt := make([]byte, 16) | ||
if _, err := rand.Read(salt); err != nil { | ||
return "", fmt.Errorf("failed to read salt: %w", err) | ||
} | ||
n := int(math.Pow(2, float64(int(logn)))) | ||
key, err := scrypt.Key([]byte(password), salt, n, 8, 1, 32) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to compute key with scrypt: %w", err) | ||
} | ||
|
||
concat := make([]byte, 91) | ||
concat[0] = 0x02 | ||
concat[1] = byte(logn) | ||
copy(concat[2:2+16], salt) | ||
rand.Read(concat[2+16 : 2+16+24]) // nonce | ||
ad := []byte{byte(ksb)} | ||
copy(concat[2+16+24:2+16+24+1], ad) | ||
|
||
c2p1, err := chacha20poly1305.NewX(key) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to start xchacha20poly1305: %w", err) | ||
} | ||
ciphertext := c2p1.Seal(nil, concat[2+16:2+16+24], secretKey, ad) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to encrypt: %w", err) | ||
} | ||
copy(concat[2+16+24+1:], ciphertext) | ||
|
||
fmt.Println(hex.EncodeToString(ciphertext), len(ciphertext), len(concat), len(concat)-(2+16+24+1)) | ||
|
||
bits5, err := bech32.ConvertBits(concat, 8, 5, true) | ||
if err != nil { | ||
return "", err | ||
} | ||
return bech32.Encode("ncryptsec", bits5) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package nip49 | ||
|
||
import ( | ||
"strings" | ||
"testing" | ||
) | ||
|
||
func TestDecryptKeyFromNIPText(t *testing.T) { | ||
ncrypt := "ncryptsec1qgg9947rlpvqu76pj5ecreduf9jxhselq2nae2kghhvd5g7dgjtcxfqtd67p9m0w57lspw8gsq6yphnm8623nsl8xn9j4jdzz84zm3frztj3z7s35vpzmqf6ksu8r89qk5z2zxfmu5gv8th8wclt0h4p" | ||
secretKey, err := Decrypt(ncrypt, "nostr") | ||
if err != nil { | ||
t.Fatalf("failed to decrypt: %s", err) | ||
} | ||
if secretKey != "3501454135014541350145413501453fefb02227e449e57cf4d3a3ce05378683" { | ||
t.Fatalf("decrypted wrongly: %s", secretKey) | ||
} | ||
} | ||
|
||
func TestEncryptAndDecrypt(t *testing.T) { | ||
for i, f := range []struct { | ||
password string | ||
secretkey string | ||
logn uint8 | ||
ksb KeySecurityByte | ||
}{ | ||
{".ksjabdk.aselqwe", "14c226dbdd865d5e1645e72c7470fd0a17feb42cc87b750bab6538171b3a3f8a", 1, 0x00}, | ||
{"skjdaklrnçurbç l", "f7f2f77f98890885462764afb15b68eb5f69979c8046ecb08cad7c4ae6b221ab", 2, 0x01}, | ||
{"777z7z7z7z7z7z7z", "11b25a101667dd9208db93c0827c6bdad66729a5b521156a7e9d3b22b3ae8944", 3, 0x02}, | ||
{".ksjabdk.aselqwe", "14c226dbdd865d5e1645e72c7470fd0a17feb42cc87b750bab6538171b3a3f8a", 7, 0x00}, | ||
{"skjdaklrnçurbç l", "f7f2f77f98890885462764afb15b68eb5f69979c8046ecb08cad7c4ae6b221ab", 8, 0x01}, | ||
{"777z7z7z7z7z7z7z", "11b25a101667dd9208db93c0827c6bdad66729a5b521156a7e9d3b22b3ae8944", 9, 0x02}, | ||
{"", "f7f2f77f98890885462764afb15b68eb5f69979c8046ecb08cad7c4ae6b221ab", 4, 0x00}, | ||
{"", "11b25a101667dd9208db93c0827c6bdad66729a5b521156a7e9d3b22b3ae8944", 5, 0x01}, | ||
{"", "f7f2f77f98890885462764afb15b68eb5f69979c8046ecb08cad7c4ae6b221ab", 1, 0x00}, | ||
{"", "11b25a101667dd9208db93c0827c6bdad66729a5b521156a7e9d3b22b3ae8944", 9, 0x01}, | ||
} { | ||
bech32code, err := Encrypt(f.secretkey, f.password, f.logn, f.ksb) | ||
if err != nil { | ||
t.Fatalf("failed to encrypt %d: %s", i, err) | ||
} | ||
if !strings.HasPrefix(bech32code, "ncryptsec1") || len(bech32code) != 162 { | ||
t.Fatalf("bech32 code is wrong %d: %s", i, bech32code) | ||
} | ||
|
||
secretKey, err := Decrypt(bech32code, f.password) | ||
if err != nil { | ||
t.Fatalf("failed to decrypt %d: %s", i, err) | ||
} | ||
if secretKey != f.secretkey { | ||
t.Fatalf("decrypted to the wrong value %d: %s", i, secretKey) | ||
} | ||
} | ||
} |