The Sodium cryptographic library compiled to pure JavaScript (wrappers, sumo variant)
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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.
/**
* 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 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;
};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 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;For large messages that don't fit in memory, use streaming signature operations.
/**
* Initialize streaming signature state
* @returns Uint8Array - State object for streaming operations
*/
function crypto_sign_init(): Uint8Array;/**
* 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 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;/**
* 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;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;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;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// 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// 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// 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); // trueclass 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); // falseclass 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// 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');Ed25519 provides:
Digital signatures are essential for authentication, integrity, and non-repudiation in modern cryptographic systems.