2 Commits
Author SHA1 Message Date
thePR0M3TH3ANandClaude Opus 4.7 a8c855fc5c Premium step 1 — soft-gate crypto primitive
First piece of PREMIUM_DESIGNS_FEATURE.md §6. Adds the static
client-side decryption layer for premium designs: HKDF-SHA256 over
an obfuscated per-epoch pepper + a per-design public salt, then
AES-256-GCM with the event coordinate as additional-authenticated-
data so ciphertext can't be copy-pasted to a different event.

noble bundle
  - Add @noble/hashes/hkdf to the bundle entry; expose as
    window.ArtstrNoble.hkdf and .hmac. Bundle size 14.0 KB → 16.5 KB.

Premium IIFE (new section in index.html)
  - _softGatePepperParts — per-epoch lookup of 4x8-byte fragments
    ("palette seed I..IV"). For epoch 2026-05. Sub-strings don't
    look like a "premium key"; reconstruction concats at use time.
    Comment explicitly calls this obfuscation, not security, per
    PREMIUM_DESIGNS_FEATURE.md §2 / §6.3.
  - SUPPORTED_EPOCHS / CURRENT_EPOCH — adding a future epoch is
    one entry in the parts table.
  - pepperBytesForEpoch(epoch) — reconstructs the 32-byte pepper.
  - deriveKey({ epoch, creatorPubkey, premiumAddress, schemaVersion,
    publicDesignSalt, puzzleNonce }) -> 32 bytes via HKDF-SHA256
    with the pepper as IKM, salt = publicDesignSalt, info =
    "artstr-softgate-v1|<epoch>|<creator>|<addr>|v<schema>" (and an
    optional "|p<nonce>" for the §6.7 CPU-puzzle hook).
  - encodeAad(creatorPubkey, dTag) -> UTF-8 of
    "30078:<creator>:<dTag>" so AES-GCM authenticates the
    coordinate.
  - encryptPayload(payloadObj, coord, { epoch? }) — random
    32-byte salt + 12-byte IV; returns the spec §5.2 envelope
    (base64 fields, softgate metadata, etc.).
  - decryptPayload(envelope, coord) — reverses the above.
  - Self-test runs on every load: round-trip a known payload AND
    verify AAD/creator/schema mismatches all reject the decrypt
    (event-coordinate binding). Single console.warn on failure.

Tested via CDP — all green:
  - Round-trip with random keypair: ok
  - AAD mismatch (different dTag) -> AES-GCM rejects: ok
  - Creator pubkey mismatch -> KDF info changes -> rejects: ok
  - Schema version mismatch -> KDF info changes -> rejects: ok
  - KDF deterministic with identical inputs: ok
  - Output length = 32 bytes (AES-256): ok
  - Pepper reconstruction = 32 bytes: ok
  - Envelope size for tiny payload = 577 bytes; ~63 KB headroom
    under typical relay limits, plenty for an embedded preview
    JPEG in step 2.
  - Regression: NWC crypto self-test still passes; existing editor
    still loads and renders layers.

Code only; nothing in the publish/feed flow uses this yet. Next
step (PREMIUM_DESIGNS_FEATURE.md §19 item 2) is the watermarked
preview pipeline.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-28 20:54:24 -04:00
thePR0M3TH3ANandClaude Opus 4.7 7eae78e909 Phase 0 — crypto bundle for NWC
Bundle @noble/secp256k1 v3.1.0 + @noble/hashes v2.2.0 as an IIFE
(noble-bundle.min.js, 15.8 KB) and load it before the main script.
The bundle exposes window.ArtstrNoble with schnorr, getPublicKey,
getSharedSecret, sha256, hex/randomBytes utils, plus secpUtils.
(noble v3 keeps hash functions external; the bundle wires
hashes.sha256 + hashes.hmacSha256 so schnorr.sign / verify work.)

On top of that, add an `NWC` IIFE in the main script with:
  - sharedSecret(privHex, peerPubHex) — NIP-04 32-byte ECDH x-coord
    (handles x-only 32-byte and full 33-byte pubkey hex).
  - encrypt04 / decrypt04 — AES-256-CBC via crypto.subtle, 16-byte
    random IV, `<base64ct>?iv=<base64iv>` wire format.
  - getEventHash + signEventWithKey — NIP-01 event hashing + schnorr
    signing using a *passed-in* hex privkey (the NWC connection's
    secret, not the user's identity).
  - verifyEvent — for kind-9735 receipt verification later.
  - getPubkeyHex — schnorr x-only pubkey from a hex privkey.

Phase-0 self-test runs once at load: BIP-340 test vector #1 derives
the expected x-only pubkey AND verifies the published signature
through window.ArtstrNoble.schnorr; a NIP-04 round-trip encrypts
+ decrypts a known plaintext between two random keypairs; a
synthetic Nostr event is signed and verified through our helpers.
All checks pass silently; any failure logs a single console.warn
so the user sees "Crypto self-test failed — NWC features will not
work" if a bad bundle ever ships.

Verified via CDP:
  - derivedPub matches BIP-340 vector 1: dff1d77f...502ba659
  - sigVerify on the vector signature: true
  - nip04Roundtrip: true
  - eventSigned + eventVerify: true
  - No console errors loading the app; existing custom-art editor
    smoke test (add a color layer, render) still works.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-28 13:23:08 -04:00