This is an advanced technical reference for implementing blob signing. It covers the exact byte-level process for creating valid SHM signatures.

    The Zero-Fill-Sign Pattern

      SHM uses a specific pattern for signing that ensures the signature is part of the signed content:

      1. Create blob with sig = 64 zero bytes
      2. CBOR-encode the blob
      3. Sign the encoded bytes
      4. Replace zeros with actual signature
      5. CBOR-encode again → final blob

      This ensures verifiers can reconstruct exactly what was signed by replacing the signature with zeros.

    CBOR Encoding Rules

      CBOR encoding must be deterministic for signatures to verify. Key rules:

      // Use canonical CBOR encoding:
      - Maps: keys in lexicographic order
      - Numbers: smallest possible encoding
      - No indefinite-length items
      
      // JavaScript example with cbor-x:
      import { encode } from 'cbor-x';
      const bytes = encode(data);  // Deterministic by default

    Change Blob Fields

      Exact field order and types for a Change blob:

      {
        "@type": "Change",           // String: always "Change"
        "signer": Uint8Array(34),     // Multicodec-prefixed pubkey
        "delegateOf": null | Uint8Array(34),
        "account": Uint8Array(34),    // Target account pubkey
        "path": "/doc-path",          // String
        "genesis": Uint8Array(36),    // CID bytes of first version
        "deps": [Uint8Array(36)],     // Array of CID bytes
        "ops": [...],                 // Operations array
        "hlcTime": BigInt,            // Hybrid logical clock
        "capability": "write",        // String: write|read|comment
        "sig": Uint8Array(64)         // Ed25519 signature
      }

    Public Key Format

      Account IDs (z6Mk...) are multibase-encoded public keys. To convert:

      // Account ID → bytes
      const { base58btc } = require('multiformats/bases/base58');
      const bytes = base58btc.decode('z6MkvYf14...');
      // bytes[0:2] = multicodec prefix (0xed 0x01 = Ed25519 pubkey)
      // bytes[2:34] = 32-byte public key
      
      // For signing, use full 34-byte prefixed key

    CID Bytes

      CIDs in blobs are stored as raw bytes, not strings:

      // String CID → bytes
      import { CID } from 'multiformats/cid';
      const cid = CID.parse('bafy2bzace...');
      const bytes = cid.bytes;  // Uint8Array(36)
      
      // Bytes → string CID
      const cidString = CID.decode(bytes).toString();

    Signing Implementation

      Complete JavaScript signing example:

      import * as ed from '@noble/ed25519';
      import { encode } from 'cbor-x';
      
      async function signChange(change, privateKey) {
        // 1. Insert zero signature
        const toSign = { ...change, sig: new Uint8Array(64) };
        
        // 2. CBOR encode
        const bytes = encode(toSign);
        
        // 3. Sign
        const sig = await ed.signAsync(bytes, privateKey);
        
        // 4. Replace signature
        change.sig = sig;
        
        // 5. Final encoding
        return encode(change);
      }

    Verification Implementation

      import * as ed from '@noble/ed25519';
      import { decode, encode } from 'cbor-x';
      
      async function verifyChange(blob) {
        // 1. Decode
        const change = decode(blob);
        
        // 2. Extract and zero signature
        const sig = change.sig;
        const toVerify = { ...change, sig: new Uint8Array(64) };
        
        // 3. Re-encode
        const bytes = encode(toVerify);
        
        // 4. Extract public key from signer field
        const pubkey = change.signer.slice(2);  // Remove multicodec prefix
        
        // 5. Verify
        return await ed.verifyAsync(sig, bytes, pubkey);
      }

    Common Mistakes

      • Using string CIDs instead of bytes

      • Forgetting multicodec prefix on public keys

      • Non-deterministic CBOR encoding

      • Wrong signature length (must be exactly 64 bytes)

      • Using 32-byte pubkey instead of 34-byte prefixed version

    See the original Blob Signing page for more context, and Cryptographic Signing for the conceptual overview.