upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Staab <shtaab@gmail.com>2023-08-14 17:42:33 -0700
committerJonathan Staab <shtaab@gmail.com>2023-08-14 17:42:33 -0700
commita1f8a82e73dc8324c00c707a89265966a8dc99c4 (patch)
treebece1f738518955da3039fa963ab6f50ae2da25e
parent3a37d7c8b96ffd6bcdddba3bfd472c379ac6b89f (diff)
Switch from JSON to custom TLV for nip 44
-rw-r--r--44.md93
1 files changed, 70 insertions, 23 deletions
diff --git a/44.md b/44.md
index 66bf565..1ff5a8a 100644
--- a/44.md
+++ b/44.md
@@ -24,12 +24,13 @@ Params:
24 24
25Example: 25Example:
26 26
27- Alice's private key: `5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a`
28- Bob's private key: `4b22aa260e4acb7021e32f38a6cdf4b673c6a277755bfce287e370c924dc936d`
29
30Encrypting the message `hello` from Alice to Bob results in the base-64 encoded tlv payload:
31
27``` 32```
28{ 33AAEBARgeI8gcP/4mnw3mKgtMvD8aGYUnGBlhopoCBd94Ev9i
29 "ciphertext": "FvQi1H4atMwU+FzUR/0CJ7kowjs+",
30 "nonce": "3dBKd83Pg2Q4Tu2A2e8N++c+ZW2IBc2f",
31 "v": 1
32}
33``` 34```
34 35
35# Other Notes 36# Other Notes
@@ -46,52 +47,98 @@ This encryption scheme replaces the one described in NIP-04, which is not secure
46import {xchacha20} from "@noble/ciphers/chacha" 47import {xchacha20} from "@noble/ciphers/chacha"
47import {secp256k1} from "@noble/curves/secp256k1" 48import {secp256k1} from "@noble/curves/secp256k1"
48import {sha256} from "@noble/hashes/sha256" 49import {sha256} from "@noble/hashes/sha256"
49import {randomBytes} from "@noble/hashes/utils" 50import {randomBytes, concatBytes} from "@noble/hashes/utils"
50import {base64} from "@scure/base" 51import {base64} from "@scure/base"
51 52
52export const utf8Decoder = new TextDecoder() 53export const utf8Decoder = new TextDecoder()
53 54
54export const utf8Encoder = new TextEncoder() 55export const utf8Encoder = new TextEncoder()
55 56
57export type TLV = {[t: number]: Uint8Array[]}
58
59export function parseTLV(data: Uint8Array): TLV {
60 let result: TLV = {}
61 let rest = data
62 while (rest.length > 0) {
63 let t = rest[0]
64 let l = rest[1]
65 if (!l) throw new Error(`malformed TLV ${t}`)
66 let v = rest.slice(2, 2 + l)
67 rest = rest.slice(2 + l)
68 if (v.length < l) throw new Error(`not enough data to read on TLV ${t}`)
69 result[t] = result[t] || []
70 result[t].push(v)
71 }
72 return result
73}
74
75export function encodeTLV(tlv: TLV): Uint8Array {
76 let entries: Uint8Array[] = []
77
78 Object.entries(tlv).forEach(([t, vs]) => {
79 vs.forEach(v => {
80 let entry = new Uint8Array(v.length + 2)
81 entry.set([parseInt(t)], 0)
82 entry.set([v.length], 1)
83 entry.set(v, 2)
84 entries.push(entry)
85 })
86 })
87
88 return concatBytes(...entries)
89}
90
56export const getSharedSecret = (privkey: string, pubkey: string): Uint8Array => 91export const getSharedSecret = (privkey: string, pubkey: string): Uint8Array =>
57 sha256(secp256k1.getSharedSecret(privkey, "02" + pubkey).subarray(1, 33)) 92 sha256(secp256k1.getSharedSecret(privkey, "02" + pubkey).subarray(1, 33))
58 93
59export function encrypt(privkey: string, pubkey: string, text: string, v = 1) { 94export function encrypt(privkey: string, pubkey: string, text: string, v = 1) {
60 if (v !== 1) { 95 if (v !== 1) {
61 throw new Error("NIP44: unknown encryption version") 96 throw new Error('NIP44: unknown encryption version')
62 } 97 }
63 98
64 const key = getSharedSecret(privkey, pubkey) 99 const key = getSharedSecret(privkey, pubkey)
65 const nonce = randomBytes(24) 100 const nonce = randomBytes(24)
66 const plaintext = utf8Encoder.encode(text) 101 const plaintext = utf8Encoder.encode(text)
67 const ciphertext = xchacha20(key, nonce, plaintext) 102 const ciphertext = xchacha20(key, nonce, plaintext)
68 103 const tlv = encodeTLV({
69 return JSON.stringify({ 104 0: [new Uint8Array([1])],
70 ciphertext: base64.encode(ciphertext), 105 1: [nonce],
71 nonce: base64.encode(nonce), 106 2: [ciphertext]
72 v,
73 }) 107 })
108
109 return base64.encode(tlv)
74} 110}
75 111
76export function decrypt(privkey: string, pubkey: string, payload: string) { 112export function decrypt(privkey: string, pubkey: string, payload: string) {
77 let data 113 let byteArray
114 try {
115 byteArray = base64.decode(payload)
116 } catch (e) {
117 throw new Error(`NIP44: failed to base64 decode payload: ${e}`)
118 }
119
120 let tlv
78 try { 121 try {
79 data = JSON.parse(payload) as { 122 tlv = parseTLV(byteArray)
80 ciphertext: string
81 nonce: string
82 v: number
83 }
84 } catch (e) { 123 } catch (e) {
85 throw new Error("NIP44: failed to parse payload") 124 throw new Error(`NIP44: failed to decode tlv: ${e}`)
125 }
126
127 if (tlv[0]?.[0]?.[0] !== 1) {
128 throw new Error(`NIP44: invalid version: ${tlv[0]?.[0]?.[0]}`)
129 }
130
131 if (tlv[1]?.[0]?.length !== 24) {
132 throw new Error(`NIP44: invalid nonce: ${tlv[1]?.[0]}`)
86 } 133 }
87 134
88 if (data.v !== 1) { 135 if (!tlv[2]?.[0]) {
89 throw new Error("NIP44: unknown encryption version") 136 throw new Error(`NIP44: missing ciphertext`)
90 } 137 }
91 138
139 const nonce = tlv[1][0]
140 const ciphertext = tlv[2][0]
92 const key = getSharedSecret(privkey, pubkey) 141 const key = getSharedSecret(privkey, pubkey)
93 const nonce = base64.decode(data.nonce)
94 const ciphertext = base64.decode(data.ciphertext)
95 const plaintext = xchacha20(key, nonce, ciphertext) 142 const plaintext = xchacha20(key, nonce, ciphertext)
96 143
97 return utf8Decoder.decode(plaintext) 144 return utf8Decoder.decode(plaintext)