SIGIL - Secure Interoperable Generic JSON Layer #65

Open
opened 2025-07-14 22:22:21 +12:00 by melvincarvalho · 1 comment
melvincarvalho commented 2025-07-14 22:22:21 +12:00 (Migrated from github.com)

SIGIL 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.

https://nostrhub.io/naddr1qvzqqqrcvypzphn7e50zja4x4ke0lf05mwq60kqjezakdx92qrw0rem2md27l4j9qqzhx6t8d9kqkdhr00

NIP-XX: SIGIL – Secure Interoperable Generic JSON Layer

 Field   Value 
NIP  (TBD – to be assigned by editors)
Title  SIGIL – Secure Interoperable Generic JSON Layer
Author  @melvincarvalho
Status  Draft
Type  Standards Track
Created  2025‑07‑11
License  CC0‑1.0

1  Abstract

SIGIL 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.

2  Motivation

  • **Flexibility ** – Applications often need to sign richer JSON than the simple [0, pubkey, created_at, kind, tags, content] tuple.
  • **Determinism ** – JSON must be turned into a single, byte‑for‑byte stable representation before hashing.
  • **Interoperability ** – By re‑using Schnorr/BIP‑340 and the existing Nostr key‑space, libraries, wallets and relays can adopt SIGIL with minimal changes.

3  Terminology

  • Event – a JSON object that must contain at least the fields in §4.
  • Canonical JSON – the result of applying RFC 8785 (JCS) to an Event.
  • SIGIL ID – the 32‑byte SHA‑256 digest of Canonical JSON with the sig field empty.

4  Envelope Format

{
  "created_at": 1688540400,   // Unix seconds
  "kind":        1,
  "tags":        [["p","npub1…"]],
  "content":     "Hello, SIGIL!",
  "pubkey":      "ab…cd",        // 32‑byte compressed SEC
  "sig":         "",             // 64‑byte hex Schnorr
  "id":          ""              // 32‑byte hex – derived, see §5
}

Additional application keys are allowed but MUST NOT begin with sig, pubkey or id.

5  Signing Procedure

  1. Prepare an Event object with all keys except sig and id.
  2. Insert sig:"" (empty string) temporarily.
  3. Canonicalise the Event using RFC 8785.
  4. Compute id = sha256(canonical_bytes).
  5. Sign the canonical bytes with the private key per BIP‑340.
  6. Encode the 64‑byte signature and the 33‑byte public key as lower‑case hexadecimal.
  7. Store id, pubkey, sig back into the Event.

6  Verification

To verify a candidate Event:

  1. Ensure required fields exist and are hex‑encoded of the correct length.
  2. Copy the Event; set its sig field to the empty string.
  3. Canonicalise the copy using RFC 8785.
  4. Re‑hash; the digest must equal the id field.
  5. Verify the Schnorr signature (sig) over the canonical bytes with pubkey.

7  Reference Implementation (Node 18+)

// npm i canonicalize @noble/secp256k1
import canonicalize from 'canonicalize';
import { schnorr } from '@noble/secp256k1';
import { createHash, randomBytes } from 'crypto';

const canonBytes = (obj) => Buffer.from(canonicalize(obj), 'utf8');

const hash = (buf) => createHash('sha256').update(buf).digest();

export async function signEvent(event, sk) {
  const ev = { ...event, sig: '' };
  const can = canonBytes(ev);
  ev.id = hash(can).toString('hex');
  ev.pubkey = Buffer.from(await schnorr.getPublicKey(sk, true)).toString('hex');
  ev.sig = Buffer.from(await schnorr.sign(can, sk)).toString('hex');
  return ev;
}

export async function verifyEvent(ev) {
  const { sig, ...copy } = ev;
  copy.sig = '';
  const can = canonBytes(copy);
  if (hash(can).toString('hex') !== ev.id) return false;
  return schnorr.verify(ev.sig, can, ev.pubkey);
}

// quick demo
(async () => {
  const sk = randomBytes(32);
  const evt = await signEvent({
    kind: 1,
    created_at: Math.floor(Date.now() / 1000),
    tags: [],
    content: 'Hello SIGIL!'
  }, sk);
  console.log('verified?', await verifyEvent(evt));
})();

8  Rationale

  • RFC 8785 delivers a battle‑tested, language‑agnostic canonical form suitable for cryptographic transforms.
  • BIP‑340 Schnorr offers smaller signatures and easier security proofs than ECDSA.

9  Compatibility

Existing 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.

10  Security Considerations

  • Private keys must be generated with high‑entropy sources.
  • A failed signature verification MUST invalidate the whole Event.
  • When embedding binary data, always use base64/hex – raw control characters are disallowed by RFC 8785.

11  Test Vectors

TBD (include BIP‑340 test vectors serialised as SIGIL).

12  References

  • RFC 8785 – JSON Canonicalisation Scheme (JCS)
  • BIP 340 – Schnorr Signatures for secp256k1

End of specification.

SIGIL 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. https://nostrhub.io/naddr1qvzqqqrcvypzphn7e50zja4x4ke0lf05mwq60kqjezakdx92qrw0rem2md27l4j9qqzhx6t8d9kqkdhr00 # NIP-XX: **SIGIL – Secure Interoperable Generic JSON Layer** |  Field  |  Value  | | ----------- | ------------------------------------------------ | | **NIP** |  *(TBD – to be assigned by editors)* | | **Title** |  SIGIL – Secure Interoperable Generic JSON Layer | | **Author** |  *@melvincarvalho* | | **Status** |  Draft | | **Type** |  Standards Track | | **Created** |  2025‑07‑11 | | **License** |  CC0‑1.0 | --- ## 1  Abstract SIGIL 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. ## 2  Motivation * \*\*Flexibility \*\* – Applications often need to sign richer JSON than the simple `[0, pubkey, created_at, kind, tags, content]` tuple. * \*\*Determinism \*\* – JSON must be turned into a single, byte‑for‑byte stable representation before hashing. * \*\*Interoperability \*\* – By re‑using Schnorr/BIP‑340 and the existing Nostr key‑space, libraries, wallets and relays can adopt SIGIL with minimal changes. ## 3  Terminology * **Event** – a JSON object that *must* contain at least the fields in §4. * **Canonical JSON** – the result of applying RFC 8785 (JCS) to an Event. * **SIGIL ID** – the 32‑byte SHA‑256 digest of Canonical JSON *with the `sig` field empty*. ## 4  Envelope Format ```jsonc { "created_at": 1688540400, // Unix seconds "kind": 1, "tags": [["p","npub1…"]], "content": "Hello, SIGIL!", "pubkey": "ab…cd", // 32‑byte compressed SEC "sig": "", // 64‑byte hex Schnorr "id": "" // 32‑byte hex – derived, see §5 } ``` Additional application keys are *allowed* but MUST NOT begin with `sig`, `pubkey` or `id`. ## 5  Signing Procedure 1. Prepare an Event object with **all** keys except `sig` and `id`. 2. Insert `sig:""` (empty string) **temporarily**. 3. **Canonicalise** the Event using RFC 8785. 4. Compute `id = sha256(canonical_bytes)`. 5. Sign the *canonical bytes* with the private key per BIP‑340. 6. Encode the 64‑byte signature and the 33‑byte public key as lower‑case hexadecimal. 7. Store `id`, `pubkey`, `sig` back into the Event. ## 6  Verification To verify a candidate Event: 1. Ensure required fields exist and are hex‑encoded of the correct length. 2. Copy the Event; set its `sig` field to the empty string. 3. Canonicalise the copy using RFC 8785. 4. Re‑hash; the digest **must** equal the `id` field. 5. Verify the Schnorr signature (`sig`) over the canonical bytes with `pubkey`. ## 7  Reference Implementation (Node 18+) ```js // npm i canonicalize @noble/secp256k1 import canonicalize from 'canonicalize'; import { schnorr } from '@noble/secp256k1'; import { createHash, randomBytes } from 'crypto'; const canonBytes = (obj) => Buffer.from(canonicalize(obj), 'utf8'); const hash = (buf) => createHash('sha256').update(buf).digest(); export async function signEvent(event, sk) { const ev = { ...event, sig: '' }; const can = canonBytes(ev); ev.id = hash(can).toString('hex'); ev.pubkey = Buffer.from(await schnorr.getPublicKey(sk, true)).toString('hex'); ev.sig = Buffer.from(await schnorr.sign(can, sk)).toString('hex'); return ev; } export async function verifyEvent(ev) { const { sig, ...copy } = ev; copy.sig = ''; const can = canonBytes(copy); if (hash(can).toString('hex') !== ev.id) return false; return schnorr.verify(ev.sig, can, ev.pubkey); } // quick demo (async () => { const sk = randomBytes(32); const evt = await signEvent({ kind: 1, created_at: Math.floor(Date.now() / 1000), tags: [], content: 'Hello SIGIL!' }, sk); console.log('verified?', await verifyEvent(evt)); })(); ``` ## 8  Rationale * **RFC 8785** delivers a battle‑tested, language‑agnostic canonical form suitable for cryptographic transforms. * **BIP‑340** Schnorr offers smaller signatures and easier security proofs than ECDSA. ## 9  Compatibility Existing 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. ## 10  Security Considerations * Private keys **must** be generated with high‑entropy sources. * A failed signature verification MUST invalidate the whole Event. * When embedding binary data, always use base64/hex – raw control characters are disallowed by RFC 8785. ## 11  Test Vectors TBD (include BIP‑340 test vectors serialised as SIGIL). ## 12  References * RFC 8785 – *JSON Canonicalisation Scheme (JCS)* * BIP 340 – *Schnorr Signatures for secp256k1* --- End of specification.
mikedilger commented 2025-07-15 10:50:29 +12:00 (Migrated from github.com)

I like the premise here. We need to broaden things within the context of sovereign identity keypairs. Require only the minimum: pubkey, id, signature.

I noticed a few minor mistakes related to whether to clear or remove the id field and when under sections 5 and 6.

I would call it SIGJL (looks nordic, the J forces pronounciation as Sih-Jill rather than Sih-Gill, the J being JSON).

I personally don't like JSON as a data format, but I can't push back the ocean.

I like the premise here. We need to broaden things within the context of sovereign identity keypairs. Require only the minimum: pubkey, id, signature. I noticed a few minor mistakes related to whether to clear or remove the id field and when under sections 5 and 6. I would call it SIGJL (looks nordic, the J forces pronounciation as Sih-Jill rather than Sih-Gill, the J being JSON). I personally don't like JSON as a data format, but I can't push back the ocean.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
mosaic/nostr-next#65
No description provided.