{"id":"cdb01ee42061d39e7ca9e235bd2c2d4f1b2dd00ba9b45bc3aa97c8b787e846b5","pubkey":"de7ecd1e2976a6adb2ffa5f4db81a7d812c8bb6698aa00dcf1e76adb55efd645","created_at":1752302305,"kind":30817,"tags":[["d","sigil"],["title","SIGIL"],["client","nostrhub.io"]],"content":"# NIP-XX: **SIGIL – Secure Interoperable Generic JSON Layer**\n\n|  Field      |  Value                                           |\n| ----------- | ------------------------------------------------ |\n| **NIP**     |  *(TBD – to be assigned by editors)*             |\n| **Title**   |  SIGIL – Secure Interoperable Generic JSON Layer |\n| **Author**  |  *@melvincarvalho*                               |\n| **Status**  |  Draft                                           |\n| **Type**    |  Standards Track                                 |\n| **Created** |  2025‑07‑11                                      |\n| **License** |  CC0‑1.0                                         |\n\n---\n\n## 1  Abstract\n\nSIGIL defines a deterministic envelope that lets *any* JSON object be hashed and authenticated with a Nostr key‑pair using RFC 8785 canonicalisation and BIP‑340 Schnorr signatures.  The envelope keeps the familiar `id` and `pubkey`/`sig` fields while removing the 6‑item Nostr signing tuple, making the format suitable for off‑relay storage, cross‑protocol bridges and application‑specific events.\n\n## 2  Motivation\n\n* \\*\\*Flexibility \\*\\* – Applications often need to sign richer JSON than the simple `[0, pubkey, created_at, kind, tags, content]` tuple.\n* \\*\\*Determinism \\*\\* – JSON must be turned into a single, byte‑for‑byte stable representation before hashing.\n* \\*\\*Interoperability \\*\\* – By re‑using Schnorr/BIP‑340 and the existing Nostr key‑space, libraries, wallets and relays can adopt SIGIL with minimal changes.\n\n## 3  Terminology\n\n* **Event** – a JSON object that *must* contain at least the fields in §4.\n* **Canonical JSON** – the result of applying RFC 8785 (JCS) to an Event.\n* **SIGIL ID** – the 32‑byte SHA‑256 digest of Canonical JSON *with the `sig` field empty*.\n\n## 4  Envelope Format\n\n```jsonc\n{\n  \"created_at\": 1688540400,   // Unix seconds\n  \"kind\":        1,\n  \"tags\":        [[\"p\",\"npub1…\"]],\n  \"content\":     \"Hello, SIGIL!\",\n  \"pubkey\":      \"ab…cd\",        // 32‑byte compressed SEC\n  \"sig\":         \"\",             // 64‑byte hex Schnorr\n  \"id\":          \"\"              // 32‑byte hex – derived, see §5\n}\n```\n\nAdditional application keys are *allowed* but MUST NOT begin with `sig`, `pubkey` or `id`.\n\n## 5  Signing Procedure\n\n1. Prepare an Event object with **all** keys except `sig` and `id`.\n2. Insert `sig:\"\"` (empty string) **temporarily**.\n3. **Canonicalise** the Event using RFC 8785.\n4. Compute `id = sha256(canonical_bytes)`.\n5. Sign the *canonical bytes* with the private key per BIP‑340.\n6. Encode the 64‑byte signature and the 33‑byte public key as lower‑case hexadecimal.\n7. Store `id`, `pubkey`, `sig` back into the Event.\n\n## 6  Verification\n\nTo verify a candidate Event:\n\n1. Ensure required fields exist and are hex‑encoded of the correct length.\n2. Copy the Event; set its `sig` field to the empty string.\n3. Canonicalise the copy using RFC 8785.\n4. Re‑hash; the digest **must** equal the `id` field.\n5. Verify the Schnorr signature (`sig`) over the canonical bytes with `pubkey`.\n\n## 7  Reference Implementation (Node 18+)\n\n```js\n// npm i canonicalize @noble/secp256k1\nimport canonicalize from 'canonicalize';\nimport { schnorr } from '@noble/secp256k1';\nimport { createHash, randomBytes } from 'crypto';\n\nconst canonBytes = (obj) => Buffer.from(canonicalize(obj), 'utf8');\n\nconst hash = (buf) => createHash('sha256').update(buf).digest();\n\nexport async function signEvent(event, sk) {\n  const ev = { ...event, sig: '' };\n  const can = canonBytes(ev);\n  ev.id = hash(can).toString('hex');\n  ev.pubkey = Buffer.from(await schnorr.getPublicKey(sk, true)).toString('hex');\n  ev.sig = Buffer.from(await schnorr.sign(can, sk)).toString('hex');\n  return ev;\n}\n\nexport async function verifyEvent(ev) {\n  const { sig, ...copy } = ev;\n  copy.sig = '';\n  const can = canonBytes(copy);\n  if (hash(can).toString('hex') !== ev.id) return false;\n  return schnorr.verify(ev.sig, can, ev.pubkey);\n}\n\n// quick demo\n(async () => {\n  const sk = randomBytes(32);\n  const evt = await signEvent({\n    kind: 1,\n    created_at: Math.floor(Date.now() / 1000),\n    tags: [],\n    content: 'Hello SIGIL!'\n  }, sk);\n  console.log('verified?', await verifyEvent(evt));\n})();\n```\n\n## 8  Rationale\n\n* **RFC 8785** delivers a battle‑tested, language‑agnostic canonical form suitable for cryptographic transforms.\n* **BIP‑340** Schnorr offers smaller signatures and easier security proofs than ECDSA.\n\n## 9  Compatibility\n\nExisting relays that ignore unknown keys can forward SIGIL events unchanged.  Clients that understand SIGIL can validate and display them; others can treat `content` as opaque text.\n\n## 10  Security Considerations\n\n* Private keys **must** be generated with high‑entropy sources.\n* A failed signature verification MUST invalidate the whole Event.\n* When embedding binary data, always use base64/hex – raw control characters are disallowed by RFC 8785.\n\n## 11  Test Vectors\n\nTBD (include BIP‑340 test vectors serialised as SIGIL).\n\n## 12  References\n\n* RFC 8785 – *JSON Canonicalisation Scheme (JCS)*\n* BIP 340 – *Schnorr Signatures for secp256k1*\n\n---\n\nEnd of specification.","sig":"0e0600544e7a65a8bf12f1080735dfc902aae66872dc0930f01bb45ce85ceb4d74cd4d27e23cf2d81773cd60346515006429bd90ad394faa4543c749843d34a0"}