@konemono/nostr-login 1.7.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierrc.json +13 -0
- package/README.md +167 -0
- package/dist/const/index.d.ts +1 -0
- package/dist/iife-module.d.ts +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.esm.js +18 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/modules/AuthNostrService.d.ts +84 -0
- package/dist/modules/BannerManager.d.ts +20 -0
- package/dist/modules/ModalManager.d.ts +25 -0
- package/dist/modules/Nip46.d.ts +56 -0
- package/dist/modules/Nostr.d.ts +34 -0
- package/dist/modules/NostrExtensionService.d.ts +17 -0
- package/dist/modules/NostrParams.d.ts +8 -0
- package/dist/modules/Popup.d.ts +7 -0
- package/dist/modules/ProcessManager.d.ts +10 -0
- package/dist/modules/Signer.d.ts +9 -0
- package/dist/modules/index.d.ts +8 -0
- package/dist/types.d.ts +72 -0
- package/dist/unpkg.js +17 -0
- package/dist/utils/index.d.ts +27 -0
- package/dist/utils/nip44.d.ts +9 -0
- package/index.html +30 -0
- package/package.json +28 -0
- package/rollup.config.js +55 -0
- package/src/const/index.ts +1 -0
- package/src/iife-module.ts +81 -0
- package/src/index.ts +347 -0
- package/src/modules/AuthNostrService.ts +756 -0
- package/src/modules/BannerManager.ts +146 -0
- package/src/modules/ModalManager.ts +635 -0
- package/src/modules/Nip46.ts +441 -0
- package/src/modules/Nostr.ts +107 -0
- package/src/modules/NostrExtensionService.ts +99 -0
- package/src/modules/NostrParams.ts +18 -0
- package/src/modules/Popup.ts +27 -0
- package/src/modules/ProcessManager.ts +67 -0
- package/src/modules/Signer.ts +25 -0
- package/src/modules/index.ts +8 -0
- package/src/types.ts +124 -0
- package/src/utils/index.ts +326 -0
- package/src/utils/nip44.ts +185 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { chacha20 } from "@noble/ciphers/chacha"
|
|
2
|
+
import { concatBytes, randomBytes, utf8ToBytes } from "@noble/hashes/utils"
|
|
3
|
+
import { equalBytes } from "@noble/ciphers/utils";
|
|
4
|
+
import { secp256k1 } from "@noble/curves/secp256k1"
|
|
5
|
+
import {
|
|
6
|
+
expand as hkdf_expand,
|
|
7
|
+
extract as hkdf_extract,
|
|
8
|
+
} from "@noble/hashes/hkdf"
|
|
9
|
+
import { sha256 } from "@noble/hashes/sha256"
|
|
10
|
+
import { hmac } from "@noble/hashes/hmac";
|
|
11
|
+
import { base64 } from "@scure/base";
|
|
12
|
+
import { getPublicKey } from 'nostr-tools'
|
|
13
|
+
|
|
14
|
+
// from https://github.com/nbd-wtf/nostr-tools
|
|
15
|
+
|
|
16
|
+
const decoder = new TextDecoder()
|
|
17
|
+
|
|
18
|
+
const u = {
|
|
19
|
+
minPlaintextSize: 0x0001, // 1b msg => padded to 32b
|
|
20
|
+
maxPlaintextSize: 0xffff, // 65535 (64kb-1) => padded to 64kb
|
|
21
|
+
|
|
22
|
+
utf8Encode: utf8ToBytes,
|
|
23
|
+
utf8Decode(bytes: Uint8Array): string {
|
|
24
|
+
return decoder.decode(bytes);
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
getConversationKey(privkeyA: string, pubkeyB: string): Uint8Array {
|
|
28
|
+
const sharedX = secp256k1
|
|
29
|
+
.getSharedSecret(privkeyA, "02" + pubkeyB)
|
|
30
|
+
.subarray(1, 33);
|
|
31
|
+
return hkdf_extract(sha256, sharedX, "nip44-v2");
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
getMessageKeys(conversationKey: Uint8Array, nonce: Uint8Array) {
|
|
35
|
+
const keys = hkdf_expand(sha256, conversationKey, nonce, 76);
|
|
36
|
+
return {
|
|
37
|
+
chacha_key: keys.subarray(0, 32),
|
|
38
|
+
chacha_nonce: keys.subarray(32, 44),
|
|
39
|
+
hmac_key: keys.subarray(44, 76),
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
calcPaddedLen(len: number): number {
|
|
44
|
+
if (!Number.isSafeInteger(len) || len < 1)
|
|
45
|
+
throw new Error("expected positive integer");
|
|
46
|
+
if (len <= 32) return 32;
|
|
47
|
+
const nextPower = 1 << (Math.floor(Math.log2(len - 1)) + 1);
|
|
48
|
+
const chunk = nextPower <= 256 ? 32 : nextPower / 8;
|
|
49
|
+
return chunk * (Math.floor((len - 1) / chunk) + 1);
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
writeU16BE(num: number): Uint8Array {
|
|
53
|
+
if (
|
|
54
|
+
!Number.isSafeInteger(num) ||
|
|
55
|
+
num < u.minPlaintextSize ||
|
|
56
|
+
num > u.maxPlaintextSize
|
|
57
|
+
)
|
|
58
|
+
throw new Error(
|
|
59
|
+
"invalid plaintext size: must be between 1 and 65535 bytes"
|
|
60
|
+
);
|
|
61
|
+
const arr = new Uint8Array(2);
|
|
62
|
+
new DataView(arr.buffer).setUint16(0, num, false);
|
|
63
|
+
return arr;
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
pad(plaintext: string): Uint8Array {
|
|
67
|
+
const unpadded = u.utf8Encode(plaintext);
|
|
68
|
+
const unpaddedLen = unpadded.length;
|
|
69
|
+
const prefix = u.writeU16BE(unpaddedLen);
|
|
70
|
+
const suffix = new Uint8Array(u.calcPaddedLen(unpaddedLen) - unpaddedLen);
|
|
71
|
+
return concatBytes(prefix, unpadded, suffix);
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
unpad(padded: Uint8Array): string {
|
|
75
|
+
const unpaddedLen = new DataView(padded.buffer).getUint16(0);
|
|
76
|
+
const unpadded = padded.subarray(2, 2 + unpaddedLen);
|
|
77
|
+
if (
|
|
78
|
+
unpaddedLen < u.minPlaintextSize ||
|
|
79
|
+
unpaddedLen > u.maxPlaintextSize ||
|
|
80
|
+
unpadded.length !== unpaddedLen ||
|
|
81
|
+
padded.length !== 2 + u.calcPaddedLen(unpaddedLen)
|
|
82
|
+
)
|
|
83
|
+
throw new Error("invalid padding");
|
|
84
|
+
return u.utf8Decode(unpadded);
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
hmacAad(key: Uint8Array, message: Uint8Array, aad: Uint8Array): Uint8Array {
|
|
88
|
+
if (aad.length !== 32)
|
|
89
|
+
throw new Error("AAD associated data must be 32 bytes");
|
|
90
|
+
const combined = concatBytes(aad, message);
|
|
91
|
+
return hmac(sha256, key, combined);
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
// metadata: always 65b (version: 1b, nonce: 32b, max: 32b)
|
|
95
|
+
// plaintext: 1b to 0xffff
|
|
96
|
+
// padded plaintext: 32b to 0xffff
|
|
97
|
+
// ciphertext: 32b+2 to 0xffff+2
|
|
98
|
+
// raw payload: 99 (65+32+2) to 65603 (65+0xffff+2)
|
|
99
|
+
// compressed payload (base64): 132b to 87472b
|
|
100
|
+
decodePayload(payload: string): {
|
|
101
|
+
nonce: Uint8Array;
|
|
102
|
+
ciphertext: Uint8Array;
|
|
103
|
+
mac: Uint8Array;
|
|
104
|
+
} {
|
|
105
|
+
if (typeof payload !== "string")
|
|
106
|
+
throw new Error("payload must be a valid string");
|
|
107
|
+
const plen = payload.length;
|
|
108
|
+
if (plen < 132 || plen > 87472)
|
|
109
|
+
throw new Error("invalid payload length: " + plen);
|
|
110
|
+
if (payload[0] === "#") throw new Error("unknown encryption version");
|
|
111
|
+
let data: Uint8Array;
|
|
112
|
+
try {
|
|
113
|
+
data = base64.decode(payload);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
throw new Error("invalid base64: " + (error as Error).message);
|
|
116
|
+
}
|
|
117
|
+
const dlen = data.length;
|
|
118
|
+
if (dlen < 99 || dlen > 65603)
|
|
119
|
+
throw new Error("invalid data length: " + dlen);
|
|
120
|
+
const vers = data[0];
|
|
121
|
+
if (vers !== 2) throw new Error("unknown encryption version " + vers);
|
|
122
|
+
return {
|
|
123
|
+
nonce: data.subarray(1, 33),
|
|
124
|
+
ciphertext: data.subarray(33, -32),
|
|
125
|
+
mac: data.subarray(-32),
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export function encryptNip44(
|
|
131
|
+
plaintext: string,
|
|
132
|
+
conversationKey: Uint8Array,
|
|
133
|
+
nonce: Uint8Array = randomBytes(32)
|
|
134
|
+
): string {
|
|
135
|
+
const { chacha_key, chacha_nonce, hmac_key } = u.getMessageKeys(
|
|
136
|
+
conversationKey,
|
|
137
|
+
nonce
|
|
138
|
+
);
|
|
139
|
+
const padded = u.pad(plaintext);
|
|
140
|
+
const ciphertext = chacha20(chacha_key, chacha_nonce, padded);
|
|
141
|
+
const mac = u.hmacAad(hmac_key, ciphertext, nonce);
|
|
142
|
+
return base64.encode(
|
|
143
|
+
concatBytes(new Uint8Array([2]), nonce, ciphertext, mac)
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function decryptNip44(payload: string, conversationKey: Uint8Array): string {
|
|
148
|
+
const { nonce, ciphertext, mac } = u.decodePayload(payload);
|
|
149
|
+
const { chacha_key, chacha_nonce, hmac_key } = u.getMessageKeys(
|
|
150
|
+
conversationKey,
|
|
151
|
+
nonce
|
|
152
|
+
);
|
|
153
|
+
const calculatedMac = u.hmacAad(hmac_key, ciphertext, nonce);
|
|
154
|
+
if (!equalBytes(calculatedMac, mac)) throw new Error("invalid MAC");
|
|
155
|
+
const padded = chacha20(chacha_key, chacha_nonce, ciphertext);
|
|
156
|
+
return u.unpad(padded);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export class Nip44 {
|
|
160
|
+
private cache = new Map<string, Uint8Array>()
|
|
161
|
+
|
|
162
|
+
public createKey(privkey: string, pubkey: string) {
|
|
163
|
+
return u.getConversationKey(privkey, pubkey)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private getKey(privkey: string, pubkey: string, extractable?: boolean) {
|
|
167
|
+
const id = getPublicKey(privkey) + pubkey
|
|
168
|
+
let cryptoKey = this.cache.get(id)
|
|
169
|
+
if (cryptoKey) return cryptoKey
|
|
170
|
+
|
|
171
|
+
const key = this.createKey(privkey, pubkey)
|
|
172
|
+
this.cache.set(id, key)
|
|
173
|
+
return key
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
public encrypt(privkey: string, pubkey: string, text: string): string {
|
|
177
|
+
const key = this.getKey(privkey, pubkey)
|
|
178
|
+
return encryptNip44(text, key)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
public decrypt(privkey: string, pubkey: string, data: string): string {
|
|
182
|
+
const key = this.getKey(privkey, pubkey)
|
|
183
|
+
return decryptNip44(data, key)
|
|
184
|
+
}
|
|
185
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "esnext",
|
|
4
|
+
"module": "esnext",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"forceConsistentCasingInFileNames": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"outDir": "./dist",
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*.ts"],
|
|
14
|
+
"exclude": ["node_modules"],
|
|
15
|
+
}
|