mirror of
https://github.com/nostr-protocol/nips.git
synced 2024-12-22 16:35:52 -05:00
Switch from JSON to custom TLV for nip 44
This commit is contained in:
parent
3a37d7c8b9
commit
a1f8a82e73
93
44.md
93
44.md
|
@ -24,12 +24,13 @@ Params:
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
- Alice's private key: `5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a`
|
||||||
|
- Bob's private key: `4b22aa260e4acb7021e32f38a6cdf4b673c6a277755bfce287e370c924dc936d`
|
||||||
|
|
||||||
|
Encrypting the message `hello` from Alice to Bob results in the base-64 encoded tlv payload:
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
AAEBARgeI8gcP/4mnw3mKgtMvD8aGYUnGBlhopoCBd94Ev9i
|
||||||
"ciphertext": "FvQi1H4atMwU+FzUR/0CJ7kowjs+",
|
|
||||||
"nonce": "3dBKd83Pg2Q4Tu2A2e8N++c+ZW2IBc2f",
|
|
||||||
"v": 1
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Other Notes
|
# Other Notes
|
||||||
|
@ -46,52 +47,98 @@ This encryption scheme replaces the one described in NIP-04, which is not secure
|
||||||
import {xchacha20} from "@noble/ciphers/chacha"
|
import {xchacha20} from "@noble/ciphers/chacha"
|
||||||
import {secp256k1} from "@noble/curves/secp256k1"
|
import {secp256k1} from "@noble/curves/secp256k1"
|
||||||
import {sha256} from "@noble/hashes/sha256"
|
import {sha256} from "@noble/hashes/sha256"
|
||||||
import {randomBytes} from "@noble/hashes/utils"
|
import {randomBytes, concatBytes} from "@noble/hashes/utils"
|
||||||
import {base64} from "@scure/base"
|
import {base64} from "@scure/base"
|
||||||
|
|
||||||
export const utf8Decoder = new TextDecoder()
|
export const utf8Decoder = new TextDecoder()
|
||||||
|
|
||||||
export const utf8Encoder = new TextEncoder()
|
export const utf8Encoder = new TextEncoder()
|
||||||
|
|
||||||
|
export type TLV = {[t: number]: Uint8Array[]}
|
||||||
|
|
||||||
|
export function parseTLV(data: Uint8Array): TLV {
|
||||||
|
let result: TLV = {}
|
||||||
|
let rest = data
|
||||||
|
while (rest.length > 0) {
|
||||||
|
let t = rest[0]
|
||||||
|
let l = rest[1]
|
||||||
|
if (!l) throw new Error(`malformed TLV ${t}`)
|
||||||
|
let v = rest.slice(2, 2 + l)
|
||||||
|
rest = rest.slice(2 + l)
|
||||||
|
if (v.length < l) throw new Error(`not enough data to read on TLV ${t}`)
|
||||||
|
result[t] = result[t] || []
|
||||||
|
result[t].push(v)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodeTLV(tlv: TLV): Uint8Array {
|
||||||
|
let entries: Uint8Array[] = []
|
||||||
|
|
||||||
|
Object.entries(tlv).forEach(([t, vs]) => {
|
||||||
|
vs.forEach(v => {
|
||||||
|
let entry = new Uint8Array(v.length + 2)
|
||||||
|
entry.set([parseInt(t)], 0)
|
||||||
|
entry.set([v.length], 1)
|
||||||
|
entry.set(v, 2)
|
||||||
|
entries.push(entry)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return concatBytes(...entries)
|
||||||
|
}
|
||||||
|
|
||||||
export const getSharedSecret = (privkey: string, pubkey: string): Uint8Array =>
|
export const getSharedSecret = (privkey: string, pubkey: string): Uint8Array =>
|
||||||
sha256(secp256k1.getSharedSecret(privkey, "02" + pubkey).subarray(1, 33))
|
sha256(secp256k1.getSharedSecret(privkey, "02" + pubkey).subarray(1, 33))
|
||||||
|
|
||||||
export function encrypt(privkey: string, pubkey: string, text: string, v = 1) {
|
export function encrypt(privkey: string, pubkey: string, text: string, v = 1) {
|
||||||
if (v !== 1) {
|
if (v !== 1) {
|
||||||
throw new Error("NIP44: unknown encryption version")
|
throw new Error('NIP44: unknown encryption version')
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = getSharedSecret(privkey, pubkey)
|
const key = getSharedSecret(privkey, pubkey)
|
||||||
const nonce = randomBytes(24)
|
const nonce = randomBytes(24)
|
||||||
const plaintext = utf8Encoder.encode(text)
|
const plaintext = utf8Encoder.encode(text)
|
||||||
const ciphertext = xchacha20(key, nonce, plaintext)
|
const ciphertext = xchacha20(key, nonce, plaintext)
|
||||||
|
const tlv = encodeTLV({
|
||||||
return JSON.stringify({
|
0: [new Uint8Array([1])],
|
||||||
ciphertext: base64.encode(ciphertext),
|
1: [nonce],
|
||||||
nonce: base64.encode(nonce),
|
2: [ciphertext]
|
||||||
v,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return base64.encode(tlv)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decrypt(privkey: string, pubkey: string, payload: string) {
|
export function decrypt(privkey: string, pubkey: string, payload: string) {
|
||||||
let data
|
let byteArray
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(payload) as {
|
byteArray = base64.decode(payload)
|
||||||
ciphertext: string
|
|
||||||
nonce: string
|
|
||||||
v: number
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error("NIP44: failed to parse payload")
|
throw new Error(`NIP44: failed to base64 decode payload: ${e}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.v !== 1) {
|
let tlv
|
||||||
throw new Error("NIP44: unknown encryption version")
|
try {
|
||||||
|
tlv = parseTLV(byteArray)
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`NIP44: failed to decode tlv: ${e}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tlv[0]?.[0]?.[0] !== 1) {
|
||||||
|
throw new Error(`NIP44: invalid version: ${tlv[0]?.[0]?.[0]}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tlv[1]?.[0]?.length !== 24) {
|
||||||
|
throw new Error(`NIP44: invalid nonce: ${tlv[1]?.[0]}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tlv[2]?.[0]) {
|
||||||
|
throw new Error(`NIP44: missing ciphertext`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const nonce = tlv[1][0]
|
||||||
|
const ciphertext = tlv[2][0]
|
||||||
const key = getSharedSecret(privkey, pubkey)
|
const key = getSharedSecret(privkey, pubkey)
|
||||||
const nonce = base64.decode(data.nonce)
|
|
||||||
const ciphertext = base64.decode(data.ciphertext)
|
|
||||||
const plaintext = xchacha20(key, nonce, ciphertext)
|
const plaintext = xchacha20(key, nonce, ciphertext)
|
||||||
|
|
||||||
return utf8Decoder.decode(plaintext)
|
return utf8Decoder.decode(plaintext)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user