Add importKey, add custom signer for backend, remove db from critical path of RPC with given perms
This commit is contained in:
parent
3552447383
commit
d6bfdb345e
16
src/App.tsx
16
src/App.tsx
@ -122,6 +122,15 @@ function App() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function importKey() {
|
||||||
|
call(async () => {
|
||||||
|
// @ts-ignore
|
||||||
|
const nsec = document.getElementById(`nsec`)?.value
|
||||||
|
await swicCall('importKey', nsec)
|
||||||
|
log('Key imported')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchNewKey() {
|
async function fetchNewKey() {
|
||||||
call(async () => {
|
call(async () => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -136,6 +145,7 @@ function App() {
|
|||||||
|
|
||||||
// subscribe to updates from the service worker
|
// subscribe to updates from the service worker
|
||||||
swicOnRender(() => {
|
swicOnRender(() => {
|
||||||
|
console.log("render")
|
||||||
setRender(r => r + 1)
|
setRender(r => r + 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -161,9 +171,13 @@ function App() {
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button onClick={generateKey}>generate key</button>
|
<button onClick={generateKey}>generate key</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<input id='nsec' placeholder='nsec' />
|
||||||
|
<button onClick={importKey}>import key (DANGER!)</button>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input id='npub' placeholder='npub' />
|
<input id='npub' placeholder='npub' />
|
||||||
<input id='passphrase' placeholder='password' />
|
<input id='passphrase' placeholder='password' />
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import { generatePrivateKey, getPublicKey, nip19 } from 'nostr-tools'
|
import { generatePrivateKey, getPublicKey, nip19 } from 'nostr-tools'
|
||||||
import { dbi, DbKey, DbPending, DbPerm } from './db'
|
import { dbi, DbKey, DbPending, DbPerm } from './db'
|
||||||
import { Keys } from './keys'
|
import { Keys } from './keys'
|
||||||
import NDK, { IEventHandlingStrategy, NDKEvent, NDKNip46Backend, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk'
|
import NDK, { IEventHandlingStrategy, NDKEvent, NDKNip46Backend, NDKPrivateKeySigner, NDKSigner } from '@nostr-dev-kit/ndk'
|
||||||
import { NOAUTHD_URL, WEB_PUSH_PUBKEY, NIP46_RELAYS } from './consts'
|
import { NOAUTHD_URL, WEB_PUSH_PUBKEY, NIP46_RELAYS } from './consts'
|
||||||
|
//import { PrivateKeySigner } from './signer'
|
||||||
|
|
||||||
|
//const PERF_TEST = false
|
||||||
|
|
||||||
export interface KeyInfo {
|
export interface KeyInfo {
|
||||||
npub: string
|
npub: string
|
||||||
@ -14,7 +17,7 @@ interface Key {
|
|||||||
npub: string
|
npub: string
|
||||||
ndk: NDK
|
ndk: NDK
|
||||||
backoff: number
|
backoff: number
|
||||||
signer: NDKPrivateKeySigner
|
signer: NDKSigner
|
||||||
backend: NDKNip46Backend
|
backend: NDKNip46Backend
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +59,7 @@ class EventHandlingStrategyWrapper implements IEventHandlingStrategy {
|
|||||||
remotePubkey: string,
|
remotePubkey: string,
|
||||||
params: string[]
|
params: string[]
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
console.log("handle", { method: this.method, id, remotePubkey, params })
|
console.log(Date.now(), "handle", { method: this.method, id, remotePubkey, params })
|
||||||
const allow = await this.allowCb({
|
const allow = await this.allowCb({
|
||||||
npub: this.npub,
|
npub: this.npub,
|
||||||
id,
|
id,
|
||||||
@ -66,10 +69,10 @@ class EventHandlingStrategyWrapper implements IEventHandlingStrategy {
|
|||||||
})
|
})
|
||||||
if (!allow) return undefined
|
if (!allow) return undefined
|
||||||
return this.body.handle(backend, id, remotePubkey, params)
|
return this.body.handle(backend, id, remotePubkey, params)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
console.log("req", id, "method", this.method, "result", r)
|
console.log(Date.now(), "req", id, "method", this.method, "result", r)
|
||||||
return r
|
return r
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +82,7 @@ export class NoauthBackend {
|
|||||||
private enckeys: DbKey[] = []
|
private enckeys: DbKey[] = []
|
||||||
private keys: Key[] = []
|
private keys: Key[] = []
|
||||||
private perms: DbPerm[] = []
|
private perms: DbPerm[] = []
|
||||||
|
private doneReqIds: string[] = []
|
||||||
private confirmBuffer: Pending[] = []
|
private confirmBuffer: Pending[] = []
|
||||||
private accessBuffer: DbPending[] = []
|
private accessBuffer: DbPending[] = []
|
||||||
private notifCallback: (() => void) | null = null
|
private notifCallback: (() => void) | null = null
|
||||||
@ -199,7 +203,7 @@ export class NoauthBackend {
|
|||||||
},
|
},
|
||||||
body,
|
body,
|
||||||
})
|
})
|
||||||
if (r.status !== 200 && r.status != 201) {
|
if (r.status !== 200 && r.status !== 201) {
|
||||||
console.log("Fetch error", url, method, r.status)
|
console.log("Fetch error", url, method, r.status)
|
||||||
throw new Error("Failed to fetch" + url)
|
throw new Error("Failed to fetch" + url)
|
||||||
}
|
}
|
||||||
@ -394,6 +398,13 @@ export class NoauthBackend {
|
|||||||
params
|
params
|
||||||
}: IAllowCallbackParams): Promise<boolean> {
|
}: IAllowCallbackParams): Promise<boolean> {
|
||||||
|
|
||||||
|
// same reqs usually come on reconnects
|
||||||
|
if (this.doneReqIds.includes(id)) {
|
||||||
|
console.log("request already done", id)
|
||||||
|
// FIXME maybe repeat the reply, but without the Notification?
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
const appNpub = nip19.npubEncode(remotePubkey)
|
const appNpub = nip19.npubEncode(remotePubkey)
|
||||||
const req: DbPending = {
|
const req: DbPending = {
|
||||||
id,
|
id,
|
||||||
@ -403,30 +414,34 @@ export class NoauthBackend {
|
|||||||
params: JSON.stringify(params),
|
params: JSON.stringify(params),
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
}
|
}
|
||||||
if (!await dbi.addPending(req)) {
|
|
||||||
console.log("request already done", id)
|
|
||||||
// FIXME maybe repeat the reply, but without the Notification?
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const self = this
|
const self = this
|
||||||
return new Promise((ok) => {
|
return new Promise(async (ok) => {
|
||||||
|
|
||||||
// called when it's decided whether to allow this or not
|
// called when it's decided whether to allow this or not
|
||||||
const cb = async (allow: boolean, remember: boolean) => {
|
const onAllow = async (manual: boolean, allow: boolean, remember: boolean) => {
|
||||||
|
|
||||||
// confirm
|
// confirm
|
||||||
console.log(allow ? "allowed" : "disallowed", npub, method, params)
|
console.log(Date.now(), allow ? "allowed" : "disallowed", npub, method, params)
|
||||||
await dbi.confirmPending(id, allow)
|
if (manual) {
|
||||||
|
await dbi.confirmPending(id, allow)
|
||||||
|
|
||||||
if (!await dbi.getApp(req.appNpub)) {
|
if (!await dbi.getApp(req.appNpub)) {
|
||||||
await dbi.addApp({
|
await dbi.addApp({
|
||||||
appNpub: req.appNpub,
|
appNpub: req.appNpub,
|
||||||
npub: req.npub,
|
npub: req.npub,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
name: '',
|
name: '',
|
||||||
icon: '',
|
icon: '',
|
||||||
url: ''
|
url: ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// just send to db w/o waiting for it
|
||||||
|
// if (!PERF_TEST)
|
||||||
|
dbi.addConfirmed({
|
||||||
|
...req,
|
||||||
|
allowed: allow
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,6 +474,7 @@ export class NoauthBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// notify UI that it was confirmed
|
// notify UI that it was confirmed
|
||||||
|
// if (!PERF_TEST)
|
||||||
this.updateUI()
|
this.updateUI()
|
||||||
|
|
||||||
// return to let nip46 flow proceed
|
// return to let nip46 flow proceed
|
||||||
@ -467,21 +483,24 @@ export class NoauthBackend {
|
|||||||
|
|
||||||
// check perms
|
// check perms
|
||||||
const perm = this.getPerm(req)
|
const perm = this.getPerm(req)
|
||||||
console.log("perm", req.id, perm)
|
console.log(Date.now(), "perm", req.id, perm)
|
||||||
|
|
||||||
// have perm?
|
// have perm?
|
||||||
if (perm) {
|
if (perm) {
|
||||||
// reply immediately
|
// reply immediately
|
||||||
cb(perm === '1', false)
|
onAllow(false, perm === '1', false)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
// put pending req to db
|
||||||
|
await dbi.addPending(req)
|
||||||
|
|
||||||
// need manual confirmation
|
// need manual confirmation
|
||||||
console.log("need confirm", req)
|
console.log("need confirm", req)
|
||||||
|
|
||||||
// put to a list of pending requests
|
// put to a list of pending requests
|
||||||
this.confirmBuffer.push({
|
this.confirmBuffer.push({
|
||||||
req,
|
req,
|
||||||
cb
|
cb: (allow, remember) => onAllow(true, allow, remember)
|
||||||
})
|
})
|
||||||
|
|
||||||
// show notifs
|
// show notifs
|
||||||
@ -502,7 +521,7 @@ export class NoauthBackend {
|
|||||||
// init relay objects but dont wait until we connect
|
// init relay objects but dont wait until we connect
|
||||||
ndk.connect()
|
ndk.connect()
|
||||||
|
|
||||||
const signer = new NDKPrivateKeySigner(sk)
|
const signer = new NDKPrivateKeySigner(sk) // PrivateKeySigner
|
||||||
const backend = new NDKNip46Backend(ndk, sk, () => Promise.resolve(true))
|
const backend = new NDKNip46Backend(ndk, sk, () => Promise.resolve(true))
|
||||||
this.keys.push({ npub, backend, signer, ndk, backoff })
|
this.keys.push({ npub, backend, signer, ndk, backoff })
|
||||||
|
|
||||||
@ -575,6 +594,12 @@ export class NoauthBackend {
|
|||||||
return k
|
return k
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async importKey(nsec: string) {
|
||||||
|
const k = await this.addKey(nsec)
|
||||||
|
this.updateUI()
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
private async saveKey(npub: string, passphrase: string) {
|
private async saveKey(npub: string, passphrase: string) {
|
||||||
const info = this.enckeys.find(k => k.npub === npub)
|
const info = this.enckeys.find(k => k.npub === npub)
|
||||||
if (!info) throw new Error(`Key ${npub} not found`)
|
if (!info) throw new Error(`Key ${npub} not found`)
|
||||||
@ -661,6 +686,8 @@ export class NoauthBackend {
|
|||||||
let result = undefined
|
let result = undefined
|
||||||
if (method === 'generateKey') {
|
if (method === 'generateKey') {
|
||||||
result = await this.generateKey()
|
result = await this.generateKey()
|
||||||
|
} else if (method === 'importKey') {
|
||||||
|
result = await this.importKey(args[0])
|
||||||
} else if (method === 'saveKey') {
|
} else if (method === 'saveKey') {
|
||||||
result = await this.saveKey(args[0], args[1])
|
result = await this.saveKey(args[0], args[1])
|
||||||
} else if (method === 'fetchKey') {
|
} else if (method === 'fetchKey') {
|
||||||
|
@ -186,4 +186,12 @@ export const dbi = {
|
|||||||
console.log(`db addPending error: ${error}`)
|
console.log(`db addPending error: ${error}`)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
addConfirmed: async (r: DbHistory) => {
|
||||||
|
try {
|
||||||
|
await db.history.add(r)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`db addConfirmed error: ${error}`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
126
src/ende.ts
Normal file
126
src/ende.ts
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
ende stands for encryption decryption
|
||||||
|
*/
|
||||||
|
import { secp256k1 as secp } from '@noble/curves/secp256k1'
|
||||||
|
//import * as secp from "./vendor/secp256k1.js";
|
||||||
|
|
||||||
|
export async function encrypt(
|
||||||
|
publicKey: string,
|
||||||
|
message: string,
|
||||||
|
privateKey: string,
|
||||||
|
): Promise<string> {
|
||||||
|
const key = secp.getSharedSecret(privateKey, "02" + publicKey);
|
||||||
|
const normalizedKey = getNormalizedX(key);
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const iv = Uint8Array.from(randomBytes(16));
|
||||||
|
const plaintext = encoder.encode(message);
|
||||||
|
const cryptoKey = await crypto.subtle.importKey(
|
||||||
|
"raw",
|
||||||
|
normalizedKey,
|
||||||
|
{ name: "AES-CBC" },
|
||||||
|
false,
|
||||||
|
["encrypt"],
|
||||||
|
);
|
||||||
|
const ciphertext = await crypto.subtle.encrypt(
|
||||||
|
{ name: "AES-CBC", iv },
|
||||||
|
cryptoKey,
|
||||||
|
plaintext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const ctb64 = toBase64(new Uint8Array(ciphertext));
|
||||||
|
const ivb64 = toBase64(new Uint8Array(iv.buffer));
|
||||||
|
return `${ctb64}?iv=${ivb64}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function decrypt(
|
||||||
|
privateKey: string,
|
||||||
|
publicKey: string,
|
||||||
|
data: string,
|
||||||
|
): Promise<string | Error> {
|
||||||
|
const key = secp.getSharedSecret(privateKey, "02" + publicKey); // this line is very slow
|
||||||
|
return decrypt_with_shared_secret(data, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function decrypt_with_shared_secret(
|
||||||
|
data: string,
|
||||||
|
sharedSecret: Uint8Array,
|
||||||
|
): Promise<string | Error> {
|
||||||
|
const [ctb64, ivb64] = data.split("?iv=");
|
||||||
|
const normalizedKey = getNormalizedX(sharedSecret);
|
||||||
|
|
||||||
|
const cryptoKey = await crypto.subtle.importKey(
|
||||||
|
"raw",
|
||||||
|
normalizedKey,
|
||||||
|
{ name: "AES-CBC" },
|
||||||
|
false,
|
||||||
|
["decrypt"],
|
||||||
|
);
|
||||||
|
let ciphertext: BufferSource;
|
||||||
|
let iv: BufferSource;
|
||||||
|
try {
|
||||||
|
ciphertext = decodeBase64(ctb64);
|
||||||
|
iv = decodeBase64(ivb64);
|
||||||
|
} catch (e) {
|
||||||
|
return new Error(`failed to decode, ${e}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const plaintext = await crypto.subtle.decrypt(
|
||||||
|
{ name: "AES-CBC", iv },
|
||||||
|
cryptoKey,
|
||||||
|
ciphertext,
|
||||||
|
);
|
||||||
|
const text = utf8Decode(plaintext);
|
||||||
|
return text;
|
||||||
|
} catch (e) {
|
||||||
|
return new Error(`failed to decrypt, ${e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function utf8Encode(str: string) {
|
||||||
|
let encoder = new TextEncoder();
|
||||||
|
return encoder.encode(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function utf8Decode(bin: Uint8Array | ArrayBuffer): string {
|
||||||
|
let decoder = new TextDecoder();
|
||||||
|
return decoder.decode(bin);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toBase64(uInt8Array: Uint8Array) {
|
||||||
|
let strChunks = new Array(uInt8Array.length);
|
||||||
|
let i = 0;
|
||||||
|
// @ts-ignore
|
||||||
|
for (let byte of uInt8Array) {
|
||||||
|
strChunks[i] = String.fromCharCode(byte); // bytes to utf16 string
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return btoa(strChunks.join(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeBase64(base64String: string) {
|
||||||
|
const binaryString = atob(base64String);
|
||||||
|
const length = binaryString.length;
|
||||||
|
const bytes = new Uint8Array(length);
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
bytes[i] = binaryString.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNormalizedX(key: Uint8Array): Uint8Array {
|
||||||
|
return key.slice(1, 33);
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomBytes(bytesLength: number = 32) {
|
||||||
|
return crypto.getRandomValues(new Uint8Array(bytesLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function utf16Encode(str: string): number[] {
|
||||||
|
let array = new Array(str.length);
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
array[i] = str.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
86
src/nip04.ts
Normal file
86
src/nip04.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { randomBytes } from '@noble/hashes/utils'
|
||||||
|
import { secp256k1 } from '@noble/curves/secp256k1'
|
||||||
|
import { base64 } from '@scure/base'
|
||||||
|
import { getPublicKey } from 'nostr-tools'
|
||||||
|
|
||||||
|
export const utf8Decoder = new TextDecoder('utf-8')
|
||||||
|
export const utf8Encoder = new TextEncoder()
|
||||||
|
|
||||||
|
function toBase64(uInt8Array: Uint8Array) {
|
||||||
|
let strChunks = new Array(uInt8Array.length);
|
||||||
|
let i = 0;
|
||||||
|
// @ts-ignore
|
||||||
|
for (let byte of uInt8Array) {
|
||||||
|
strChunks[i] = String.fromCharCode(byte); // bytes to utf16 string
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return btoa(strChunks.join(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromBase64(base64String: string) {
|
||||||
|
const binaryString = atob(base64String);
|
||||||
|
const length = binaryString.length;
|
||||||
|
const bytes = new Uint8Array(length);
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
bytes[i] = binaryString.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNormalizedX(key: Uint8Array): Uint8Array {
|
||||||
|
return key.slice(1, 33)
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Nip04 {
|
||||||
|
private cache = new Map<string, CryptoKey>()
|
||||||
|
|
||||||
|
private async getKey(privkey: string, pubkey: string) {
|
||||||
|
const id = getPublicKey(privkey) + pubkey
|
||||||
|
let cryptoKey = this.cache.get(id)
|
||||||
|
if (cryptoKey) return cryptoKey
|
||||||
|
|
||||||
|
const key = secp256k1.getSharedSecret(privkey, '02' + pubkey)
|
||||||
|
const normalizedKey = getNormalizedX(key)
|
||||||
|
cryptoKey = await crypto.subtle.importKey('raw', normalizedKey, { name: 'AES-CBC' }, false, ['encrypt', 'decrypt'])
|
||||||
|
this.cache.set(id, cryptoKey)
|
||||||
|
return cryptoKey
|
||||||
|
}
|
||||||
|
|
||||||
|
public async encrypt(privkey: string, pubkey: string, text: string): Promise<string> {
|
||||||
|
const t1 = Date.now()
|
||||||
|
const cryptoKey = await this.getKey(privkey, pubkey)
|
||||||
|
const t2 = Date.now()
|
||||||
|
let iv = Uint8Array.from(randomBytes(16))
|
||||||
|
let plaintext = utf8Encoder.encode(text)
|
||||||
|
let ciphertext = await crypto.subtle.encrypt({ name: 'AES-CBC', iv }, cryptoKey, plaintext)
|
||||||
|
const t3 = Date.now()
|
||||||
|
let ctb64 = base64.encode(new Uint8Array(ciphertext))
|
||||||
|
let ivb64 = base64.encode(new Uint8Array(iv.buffer))
|
||||||
|
// let ctb64 = toBase64(new Uint8Array(ciphertext))
|
||||||
|
// let ivb64 = toBase64(new Uint8Array(iv.buffer))
|
||||||
|
|
||||||
|
console.log("nip04_encrypt", text, "t1", t2 - t1, "t2", t3 - t2, "t3", Date.now() - t3)
|
||||||
|
|
||||||
|
return `${ctb64}?iv=${ivb64}`
|
||||||
|
}
|
||||||
|
|
||||||
|
public async decrypt(privkey: string, pubkey: string, data: string): Promise<string> {
|
||||||
|
let [ctb64, ivb64] = data.split('?iv=')
|
||||||
|
|
||||||
|
const cryptoKey = await this.getKey(privkey, pubkey)
|
||||||
|
|
||||||
|
let ciphertext = base64.decode(ctb64)
|
||||||
|
let iv = base64.decode(ivb64)
|
||||||
|
// let ciphertext = fromBase64(ctb64)
|
||||||
|
// let iv = fromBase64(ivb64)
|
||||||
|
|
||||||
|
let plaintext = await crypto.subtle.decrypt({ name: 'AES-CBC', iv }, cryptoKey, ciphertext)
|
||||||
|
|
||||||
|
let text = utf8Decoder.decode(plaintext)
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
70
src/signer.ts
Normal file
70
src/signer.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import type { UnsignedEvent } from "nostr-tools";
|
||||||
|
import { generatePrivateKey, getPublicKey, getSignature } from "nostr-tools";
|
||||||
|
|
||||||
|
import type { NostrEvent } from '@nostr-dev-kit/ndk' // "./ndk-dist";
|
||||||
|
import { NDKUser } from '@nostr-dev-kit/ndk' // "./ndk-dist";
|
||||||
|
import type { NDKSigner } from '@nostr-dev-kit/ndk' // "./ndk-dist";
|
||||||
|
import { Nip04 } from "./nip04";
|
||||||
|
//import { decrypt, encrypt } from "./ende";
|
||||||
|
|
||||||
|
export class PrivateKeySigner implements NDKSigner {
|
||||||
|
private _user: NDKUser | undefined;
|
||||||
|
privateKey?: string;
|
||||||
|
private nip04: Nip04
|
||||||
|
|
||||||
|
public constructor(privateKey?: string) {
|
||||||
|
if (privateKey) {
|
||||||
|
this.privateKey = privateKey;
|
||||||
|
this._user = new NDKUser({
|
||||||
|
hexpubkey: getPublicKey(this.privateKey),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.nip04 = new Nip04()
|
||||||
|
}
|
||||||
|
|
||||||
|
public static generate() {
|
||||||
|
const privateKey = generatePrivateKey();
|
||||||
|
return new PrivateKeySigner(privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async blockUntilReady(): Promise<NDKUser> {
|
||||||
|
if (!this._user) {
|
||||||
|
throw new Error("NDKUser not initialized");
|
||||||
|
}
|
||||||
|
return this._user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async user(): Promise<NDKUser> {
|
||||||
|
await this.blockUntilReady();
|
||||||
|
return this._user as NDKUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async sign(event: NostrEvent): Promise<string> {
|
||||||
|
if (!this.privateKey) {
|
||||||
|
throw Error("Attempted to sign without a private key");
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSignature(event as UnsignedEvent, this.privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async encrypt(recipient: NDKUser, value: string): Promise<string> {
|
||||||
|
if (!this.privateKey) {
|
||||||
|
throw Error("Attempted to encrypt without a private key");
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipientHexPubKey = recipient.hexpubkey;
|
||||||
|
return await this.nip04.encrypt(this.privateKey, recipientHexPubKey, value);
|
||||||
|
// return await encrypt(recipientHexPubKey, value, this.privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async decrypt(sender: NDKUser, value: string): Promise<string> {
|
||||||
|
if (!this.privateKey) {
|
||||||
|
throw Error("Attempted to decrypt without a private key");
|
||||||
|
}
|
||||||
|
|
||||||
|
const senderHexPubKey = sender.hexpubkey;
|
||||||
|
// console.log("nip04_decrypt", value)
|
||||||
|
return await this.nip04.decrypt(this.privateKey, senderHexPubKey, value);
|
||||||
|
// return await decrypt(this.privateKey, senderHexPubKey, value) as string;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user