CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-libsodium-wrappers-sumo

The Sodium cryptographic library compiled to pure JavaScript (wrappers, sumo variant)

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

sign.mddocs/

Digital Signatures

Digital signature functions provide message authentication, integrity, and non-repudiation using Ed25519 elliptic curve cryptography. Signatures prove that a message was signed by the holder of a private key without revealing the private key.

Key Pair Generation

Generate Random Key Pair

/**
 * Generate a random Ed25519 key pair for signing
 * @returns Object with publicKey and privateKey properties
 */
function crypto_sign_keypair(): {
  publicKey: Uint8Array;   // 32 bytes - can be shared publicly
  privateKey: Uint8Array;  // 64 bytes - keep secret
};

Generate Key Pair from Seed

/**
 * Generate deterministic Ed25519 key pair from seed
 * @param seed - 32-byte seed for reproducible key generation
 * @returns Object with publicKey and privateKey properties
 */
function crypto_sign_seed_keypair(seed: Uint8Array): {
  publicKey: Uint8Array;
  privateKey: Uint8Array;
};

Digital Signatures

Attached Signatures

Attached signatures include the message within the signed data.

/**
 * Sign a message (signature includes the message)
 * @param message - Data to sign
 * @param privateKey - 64-byte private signing key
 * @returns Uint8Array - Signed message (message + signature)
 */
function crypto_sign(
  message: Uint8Array,
  privateKey: Uint8Array
): Uint8Array;

/**
 * Verify and extract message from signed data
 * @param signedMessage - Signed message from crypto_sign
 * @param publicKey - 32-byte public verification key
 * @returns Uint8Array - Original message if signature is valid
 * @throws Error if signature verification fails
 */
function crypto_sign_open(
  signedMessage: Uint8Array,
  publicKey: Uint8Array
): Uint8Array;

Detached Signatures (Recommended)

Detached signatures are separate from the message, allowing independent storage.

/**
 * Create detached signature for a message
 * @param message - Data to sign
 * @param privateKey - 64-byte private signing key
 * @returns Uint8Array - 64-byte signature
 */
function crypto_sign_detached(
  message: Uint8Array,
  privateKey: Uint8Array
): Uint8Array;

/**
 * Verify detached signature for a message
 * @param signature - 64-byte signature to verify
 * @param message - Original message data
 * @param publicKey - 32-byte public verification key
 * @returns boolean - True if signature is valid
 */
function crypto_sign_verify_detached(
  signature: Uint8Array,
  message: Uint8Array,
  publicKey: Uint8Array
): boolean;

Streaming Signatures

For large messages that don't fit in memory, use streaming signature operations.

Initialize Streaming

/**
 * Initialize streaming signature state
 * @returns Uint8Array - State object for streaming operations
 */
function crypto_sign_init(): Uint8Array;

Update with Data

/**
 * Add data chunk to streaming signature
 * @param state_address - State from crypto_sign_init
 * @param message_chunk - Data chunk to add to signature
 */
function crypto_sign_update(
  state_address: any,
  message_chunk: Uint8Array
): void;

Finalize Signature

/**
 * Finalize streaming signature and create signature
 * @param state_address - State from init/update operations
 * @param privateKey - 64-byte private signing key
 * @returns Uint8Array - 64-byte signature
 */
function crypto_sign_final_create(
  state_address: any,
  privateKey: Uint8Array
): Uint8Array;

/**
 * Finalize streaming signature and verify existing signature
 * @param state_address - State from init/update operations
 * @param signature - 64-byte signature to verify
 * @param publicKey - 32-byte public verification key
 * @returns boolean - True if signature is valid
 */
function crypto_sign_final_verify(
  state_address: any,
  signature: Uint8Array,
  publicKey: Uint8Array
): boolean;

Key Management

Extract Public Key from Private Key

/**
 * Extract public key from Ed25519 private key
 * @param privateKey - 64-byte Ed25519 private key
 * @returns Uint8Array - 32-byte public key
 */
function crypto_sign_ed25519_sk_to_pk(privateKey: Uint8Array): Uint8Array;

/**
 * Extract seed from Ed25519 private key
 * @param privateKey - 64-byte Ed25519 private key
 * @returns Uint8Array - 32-byte seed
 */
function crypto_sign_ed25519_sk_to_seed(privateKey: Uint8Array): Uint8Array;

Key Conversion

Convert between Ed25519 signing keys and Curve25519 encryption keys.

/**
 * Convert Ed25519 public key to Curve25519 public key
 * @param edPk - 32-byte Ed25519 public key
 * @returns Uint8Array - 32-byte Curve25519 public key
 */
function crypto_sign_ed25519_pk_to_curve25519(edPk: Uint8Array): Uint8Array;

/**
 * Convert Ed25519 private key to Curve25519 private key
 * @param edSk - 64-byte Ed25519 private key
 * @returns Uint8Array - 32-byte Curve25519 private key
 */
function crypto_sign_ed25519_sk_to_curve25519(edSk: Uint8Array): Uint8Array;

Constants

const crypto_sign_BYTES: number;           // 64 (signature length)
const crypto_sign_PUBLICKEYBYTES: number;  // 32 (public key length)
const crypto_sign_SECRETKEYBYTES: number;  // 64 (private key length)
const crypto_sign_SEEDBYTES: number;       // 32 (seed length)
const crypto_sign_MESSAGEBYTES_MAX: number; // Maximum message size

// Ed25519-specific constants (same values)
const crypto_sign_ed25519_BYTES: number;           // 64
const crypto_sign_ed25519_PUBLICKEYBYTES: number;  // 32
const crypto_sign_ed25519_SECRETKEYBYTES: number;  // 64
const crypto_sign_ed25519_SEEDBYTES: number;       // 32
const crypto_sign_ed25519_MESSAGEBYTES_MAX: number;

Usage Examples

Basic Digital Signatures

import _sodium from 'libsodium-wrappers-sumo';
await _sodium.ready;
const sodium = _sodium;

// Generate signing key pair
const { publicKey, privateKey } = sodium.crypto_sign_keypair();

// Message to sign
const message = sodium.from_string('This message is authentic');

// Create detached signature
const signature = sodium.crypto_sign_detached(message, privateKey);

// Verify signature
const isValid = sodium.crypto_sign_verify_detached(signature, message, publicKey);

console.log('Signature valid:', isValid); // true
console.log('Signature length:', signature.length); // 64 bytes
console.log('Public key length:', publicKey.length); // 32 bytes

Attached Signatures

// Sign message with attached signature
const signedMessage = sodium.crypto_sign(message, privateKey);

console.log('Original message length:', message.length);
console.log('Signed message length:', signedMessage.length); // +64 bytes

// Verify and extract original message
const verifiedMessage = sodium.crypto_sign_open(signedMessage, publicKey);

console.log('Messages match:', 
  sodium.memcmp(message, verifiedMessage)); // true

Deterministic Key Generation

// Generate reproducible keys from seed
const seed = sodium.randombytes_buf(sodium.crypto_sign_SEEDBYTES);
const keyPair1 = sodium.crypto_sign_seed_keypair(seed);
const keyPair2 = sodium.crypto_sign_seed_keypair(seed);

// Keys are identical
console.log('Public keys match:', 
  sodium.memcmp(keyPair1.publicKey, keyPair2.publicKey)); // true

console.log('Private keys match:', 
  sodium.memcmp(keyPair1.privateKey, keyPair2.privateKey)); // true

Streaming Signatures for Large Data

// Sign large data in chunks
function signLargeData(chunks, privateKey) {
  const state = sodium.crypto_sign_init();
  
  for (const chunk of chunks) {
    sodium.crypto_sign_update(state, chunk);
  }
  
  return sodium.crypto_sign_final_create(state, privateKey);
}

// Verify large data in chunks
function verifyLargeData(chunks, signature, publicKey) {
  const state = sodium.crypto_sign_init();
  
  for (const chunk of chunks) {
    sodium.crypto_sign_update(state, chunk);
  }
  
  return sodium.crypto_sign_final_verify(state, signature, publicKey);
}

// Usage with large file
const { publicKey, privateKey } = sodium.crypto_sign_keypair();
const largeData = new Uint8Array(10 * 1024 * 1024); // 10MB
sodium.randombytes_buf_into(largeData);

// Split into chunks
const chunkSize = 64 * 1024; // 64KB chunks
const chunks = [];
for (let i = 0; i < largeData.length; i += chunkSize) {
  chunks.push(largeData.subarray(i, i + chunkSize));
}

console.time('Sign large data');
const signature = signLargeData(chunks, privateKey);
console.timeEnd('Sign large data');

console.time('Verify large data');
const isValid = verifyLargeData(chunks, signature, publicKey);
console.timeEnd('Verify large data');

console.log('Large data signature valid:', isValid); // true

Document Signing System

class DocumentSigner {
  constructor() {
    const { publicKey, privateKey } = sodium.crypto_sign_keypair();
    this.publicKey = publicKey;
    this.privateKey = privateKey;
  }
  
  signDocument(document) {
    const documentBytes = sodium.from_string(JSON.stringify(document));
    const signature = sodium.crypto_sign_detached(documentBytes, this.privateKey);
    
    return {
      document,
      signature: sodium.to_base64(signature),
      publicKey: sodium.to_hex(this.publicKey),
      timestamp: new Date().toISOString()
    };
  }
  
  verifyDocument(signedDoc) {
    try {
      const documentBytes = sodium.from_string(JSON.stringify(signedDoc.document));
      const signature = sodium.from_base64(signedDoc.signature);
      const publicKey = sodium.from_hex(signedDoc.publicKey);
      
      return sodium.crypto_sign_verify_detached(signature, documentBytes, publicKey);
    } catch (e) {
      return false;
    }
  }
  
  getPublicKeyHex() {
    return sodium.to_hex(this.publicKey);
  }
}

// Usage
const signer = new DocumentSigner();
const document = {
  title: 'Important Contract',
  content: 'Contract terms and conditions...',
  amount: 10000
};

const signedDoc = signer.signDocument(document);
console.log('Signed document:', signedDoc);

const isValid = signer.verifyDocument(signedDoc);
console.log('Document signature valid:', isValid); // true

// Try to tamper with document
const tamperedDoc = { ...signedDoc };
tamperedDoc.document.amount = 50000;

const isTamperedValid = signer.verifyDocument(tamperedDoc);
console.log('Tampered document valid:', isTamperedValid); // false

Multi-Signature Validation

class MultiSigner {
  constructor(requiredSignatures = 2) {
    this.requiredSignatures = requiredSignatures;
    this.signers = [];
  }
  
  addSigner(name) {
    const { publicKey, privateKey } = sodium.crypto_sign_keypair();
    this.signers.push({
      name,
      publicKey,
      privateKey: privateKey // In practice, each signer keeps their own private key
    });
    return sodium.to_hex(publicKey);
  }
  
  signMessage(message, signerIndex) {
    if (signerIndex >= this.signers.length) {
      throw new Error('Invalid signer index');
    }
    
    const messageBytes = sodium.from_string(message);
    const signature = sodium.crypto_sign_detached(
      messageBytes, 
      this.signers[signerIndex].privateKey
    );
    
    return {
      signerName: this.signers[signerIndex].name,
      publicKey: sodium.to_hex(this.signers[signerIndex].publicKey),
      signature: sodium.to_base64(signature)
    };
  }
  
  verifyMultiSignature(message, signatures) {
    if (signatures.length < this.requiredSignatures) {
      return false;
    }
    
    const messageBytes = sodium.from_string(message);
    let validSignatures = 0;
    
    for (const sig of signatures) {
      try {
        const publicKey = sodium.from_hex(sig.publicKey);
        const signature = sodium.from_base64(sig.signature);
        
        if (sodium.crypto_sign_verify_detached(signature, messageBytes, publicKey)) {
          validSignatures++;
        }
      } catch (e) {
        // Invalid signature format
        continue;
      }
    }
    
    return validSignatures >= this.requiredSignatures;
  }
}

// Usage
const multiSigner = new MultiSigner(2); // Require 2 signatures

const alice = multiSigner.addSigner('Alice');
const bob = multiSigner.addSigner('Bob');
const charlie = multiSigner.addSigner('Charlie');

const message = 'Execute transaction: transfer $1000';

// Multiple parties sign
const signatures = [
  multiSigner.signMessage(message, 0), // Alice
  multiSigner.signMessage(message, 1), // Bob
];

const isValid = multiSigner.verifyMultiSignature(message, signatures);
console.log('Multi-signature valid:', isValid); // true

Key Conversion for Hybrid Cryptography

// Convert signing keys for use with encryption
const { publicKey: signPublicKey, privateKey: signPrivateKey } = sodium.crypto_sign_keypair();

// Convert to encryption keys
const encryptPublicKey = sodium.crypto_sign_ed25519_pk_to_curve25519(signPublicKey);
const encryptPrivateKey = sodium.crypto_sign_ed25519_sk_to_curve25519(signPrivateKey);

console.log('Sign public key:', sodium.to_hex(signPublicKey));
console.log('Encrypt public key:', sodium.to_hex(encryptPublicKey));

// Now you can use the same identity for both signing and encryption
const message = sodium.from_string('Signed and encrypted message');

// Sign with Ed25519
const signature = sodium.crypto_sign_detached(message, signPrivateKey);

// Encrypt with Curve25519 (need recipient's key)
const recipientKeys = sodium.crypto_box_keypair();
const nonce = sodium.randombytes_buf(sodium.crypto_box_NONCEBYTES);
const ciphertext = sodium.crypto_box_easy(message, nonce, recipientKeys.publicKey, encryptPrivateKey);

console.log('Message signed and encrypted with same identity');

Security Considerations

  • Private Key Security: Keep private keys absolutely secret - signatures prove key possession
  • Message Integrity: Signatures protect against message tampering
  • Non-Repudiation: Valid signatures prove the signer cannot deny signing
  • Public Key Authentication: Verify public key authenticity through trusted channels
  • Replay Protection: Include timestamps or sequence numbers in signed messages
  • Key Rotation: Consider periodic key rotation for long-term security

Algorithm Details

Ed25519 provides:

  • 128-bit security level: Equivalent to 3072-bit RSA
  • Fast verification: Optimized for signature verification
  • Small signatures: Only 64 bytes per signature
  • Deterministic: Same message and key always produce same signature
  • Side-channel resistant: Designed to prevent timing and power analysis attacks

Digital signatures are essential for authentication, integrity, and non-repudiation in modern cryptographic systems.

docs

aead.md

auth.md

box.md

hash.md

index.md

key-derivation.md

secretbox.md

sign.md

streaming.md

utilities.md

tile.json