This commit is contained in:
2023-09-21 21:02:59 +01:00
parent 4b57d57f94
commit 71f7f728fd
69 changed files with 863 additions and 864 deletions

View File

@ -0,0 +1,70 @@
import { pbkdf2 } from "@noble/hashes/pbkdf2";
import { sha256 } from '@noble/hashes/sha256';
import {hmac} from "@noble/hashes/hmac";
import { bytesToHex, hexToBytes, randomBytes } from "@noble/hashes/utils";
import { base64 } from "@scure/base";
import { streamXOR as xchacha20 } from "@stablelib/xchacha20";
export class InvalidPinError extends Error {
constructor(){
super();
}
}
/**
* Pin protected data
*/
export class PinEncrypted {
static readonly #opts = {c: 32, dkLen: 32}
#decrypted?: Uint8Array
#encrypted: PinEncryptedPayload
constructor(enc: PinEncryptedPayload) {
this.#encrypted = enc;
}
get value() {
if(!this.#decrypted) throw new Error("Content has not been decrypted yet");
return bytesToHex(this.#decrypted);
}
decrypt(pin: string) {
const key = pbkdf2(sha256, pin, base64.decode(this.#encrypted.salt), PinEncrypted.#opts);
const ciphertext = base64.decode(this.#encrypted.ciphertext);
const nonce = base64.decode(this.#encrypted.iv);
const plaintext = xchacha20(key, nonce, ciphertext, ciphertext);
if(plaintext.length !== 32) throw new InvalidPinError();
const mac = base64.encode(hmac(sha256, key, plaintext));
if(mac !== this.#encrypted.mac) throw new InvalidPinError();
this.#decrypted = plaintext;
}
toPayload() {
return this.#encrypted;
}
static create(content: string, pin: string) {
const salt = randomBytes(24);
const nonce = randomBytes(24);
const plaintext = hexToBytes(content);
const key = pbkdf2(sha256, pin, salt, PinEncrypted.#opts);
const mac = base64.encode(hmac(sha256, key, plaintext));
const ciphertext = xchacha20(key, nonce, plaintext, plaintext);
const ret = new PinEncrypted({
salt: base64.encode(salt),
ciphertext: base64.encode(ciphertext),
iv: base64.encode(nonce),
mac
});
ret.#decrypted = plaintext;
return ret;
}
}
export interface PinEncryptedPayload {
salt: string, // for KDF
ciphertext: string
iv: string,
mac: string
}

View File

@ -185,8 +185,9 @@ export class EventPublisher {
const thread = EventExt.extractThread(replyTo);
if (thread) {
if (thread.root || thread.replyTo) {
eb.tag(["e", thread.root?.value ?? thread.replyTo?.value ?? "", "", "root"]);
const rootOrReplyAsRoot = thread.root || thread.replyTo;
if (rootOrReplyAsRoot) {
eb.tag(["e", rootOrReplyAsRoot?.value ?? "", rootOrReplyAsRoot?.relay ?? "", "root"]);
}
eb.tag(["e", replyTo.id, replyTo.relays?.[0] ?? "", "reply"]);

View File

@ -1,6 +1,5 @@
import { MessageEncryptor, MessageEncryptorPayload, MessageEncryptorVersion } from "index";
import { base64 } from "@scure/base";
import { randomBytes } from "@noble/hashes/utils";
import { streamXOR as xchacha20 } from "@stablelib/xchacha20";
import { secp256k1 } from "@noble/curves/secp256k1";

View File

@ -27,6 +27,7 @@ export * from "./text";
export * from "./pow";
export * from "./pow-util";
export * from "./query-optimizer";
export * from "./encrypted";
export * from "./impl/nip4";
export * from "./impl/nip44";

View File

@ -300,7 +300,7 @@ export class NostrSystem extends ExternalStore<SystemSnapshot> implements System
{
filters: [{ ...f, ids: [...resultIds] }],
strategy: RequestStrategy.ExplicitRelays,
relay: "",
relay: qSend.relay,
},
cacheResults as Array<TaggedNostrEvent>,
);

View File

@ -202,7 +202,7 @@ export class Query implements QueryBase {
*/
insertCompletedTrace(subq: BuiltRawReqFilter, data: Readonly<Array<TaggedNostrEvent>>) {
const qt = new QueryTrace(
"",
subq.relay,
subq.filters,
"",
() => {

View File

@ -113,7 +113,7 @@ export class RequestBuilder {
const diff = system.QueryOptimizer.getDiff(prev, this.buildRaw());
const ts = unixNowMs() - start;
this.#log("buildDiff %s %d ms +%d %O=>%O", this.id, ts, diff.length, prev, this.buildRaw());
this.#log("buildDiff %s %d ms +%d", this.id, ts, diff.length);
if (diff.length > 0) {
return splitFlatByWriteRelays(system.RelayCache, diff).map(a => {
return {

View File

@ -6,19 +6,8 @@ import { ReqFilter } from "nostr";
export function trimFilters(filters: Array<ReqFilter>) {
const fNew = [];
for (const f of filters) {
let arrays = 0;
for (const [k, v] of Object.entries(f)) {
if (Array.isArray(v)) {
arrays++;
if (v.length === 0) {
delete f[k];
}
}
}
if (arrays > 0 && Object.entries(f).some(v => Array.isArray(v))) {
fNew.push(f);
} else if (arrays === 0) {
const ent = Object.entries(f).filter(([,v]) => Array.isArray(v));
if(ent.every(([,v]) => (v as Array<string | number>).length > 0)) {
fNew.push(f);
}
}