The Internet Computer provides a suite of cryptography packages for signature creation, verification, and hashing operations. These packages are optimized for use in both canister code and application development.
Overview
These packages provide battle-tested cryptographic primitives that work seamlessly on the Internet Computer platform, including support for WebAssembly environments.
All cryptography packages follow strict security practices and are maintained by the DFINITY cryptography team.
Packages
ic-ed25519
Package: ic-ed25519 v0.6.0
Purpose: Ed25519 signature creation and verification
[dependencies]
ic-ed25519 = "0.6.0"
Features:
- Generate Ed25519 key pairs
- Sign messages with Ed25519 private keys
- Verify Ed25519 signatures
- PKCS#8 key encoding/decoding
- Support for canister signatures
- Zeroization of private keys
ic-secp256k1
Package: ic-secp256k1 v0.3.0
Purpose: ECDSA and Schnorr signatures over secp256k1 curve
[dependencies]
ic-secp256k1 = "0.3.0"
Features:
- ECDSA signature creation and verification (Bitcoin/Ethereum compatible)
- Schnorr signature support
- Public key derivation
- Key serialization (PEM, DER)
- Secure key generation
ic-secp256r1
Package: ic-secp256r1 v0.3.0
Purpose: ECDSA signatures over secp256r1 (P-256/prime256v1) curve
[dependencies]
ic-secp256r1 = "0.3.0"
Features:
- ECDSA signature creation and verification
- NIST P-256 curve operations
- WebAuthn compatibility
- Key serialization
ic-sha3
Package: ic-sha3 v1.0.0
Purpose: SHA-3 and Keccak hashing
[dependencies]
ic-sha3 = "1.0.0"
Features:
- SHA-3 family (SHA3-224, SHA3-256, SHA3-384, SHA3-512)
- Keccak hashing (Ethereum-compatible)
- Convenience methods for common operations
ic-signature-verification
Package: ic-signature-verification
Purpose: Multi-signature verification utilities
[dependencies]
ic-signature-verification = "0.1"
Features:
- Canister signature verification
- Multi-signature scheme support
- Internet Computer-specific signature validation
Ed25519 Signatures
Ed25519 is a modern signature scheme offering excellent security and performance.
Generate Key Pair
use ic_ed25519::PrivateKey;
// Generate a new random key pair (requires 'rand' feature)
let private_key = PrivateKey::generate();
let public_key = private_key.public_key();
println!("Public key: {:?}", public_key.serialize_raw());
Sign Messages
use ic_ed25519::{PrivateKey, Signature};
let private_key = PrivateKey::generate();
let message = b"Hello, Internet Computer!";
// Sign the message
let signature = private_key.sign_message(message);
println!("Signature: {:?}", signature.as_bytes());
Verify Signatures
use ic_ed25519::{PublicKey, Signature};
let public_key = private_key.public_key();
let message = b"Hello, Internet Computer!";
// Verify the signature
match public_key.verify_signature(message, &signature) {
Ok(()) => println!("Signature is valid!"),
Err(e) => println!("Signature verification failed: {:?}", e),
}
Key Serialization
use ic_ed25519::{PrivateKey, PrivateKeyFormat};
// Serialize to PEM (PKCS#8 v1)
let pem_string = private_key.serialize_pkcs8_pem();
// Deserialize from PEM
let restored_key = PrivateKey::deserialize_pkcs8_pem(&pem_string)
.expect("Failed to deserialize");
// Raw bytes
let raw_bytes = private_key.serialize_raw();
let from_raw = PrivateKey::deserialize_raw(&raw_bytes)
.expect("Failed to deserialize raw");
Canister Signatures
Derive canister-compatible signatures:
use ic_ed25519::{PrivateKey, CanisterId};
use candid::Principal;
let private_key = PrivateKey::generate();
let canister_id = Principal::from_text("aaaaa-aa").unwrap();
// Derive a signature for a specific canister
let canister_sig = private_key.sign_for_canister(&canister_id, b"message");
Secp256k1 Signatures (Bitcoin/Ethereum)
Secp256k1 is the curve used by Bitcoin and Ethereum.
Generate Keys
use ic_secp256k1::PrivateKey;
let private_key = PrivateKey::generate();
let public_key = private_key.public_key();
// Get uncompressed public key (65 bytes)
let uncompressed = public_key.serialize_uncompressed();
// Get compressed public key (33 bytes)
let compressed = public_key.serialize_compressed();
ECDSA Signing
use ic_secp256k1::{PrivateKey, MessageHash};
use sha2::{Sha256, Digest};
let private_key = PrivateKey::generate();
let message = b"Transaction data";
// Hash the message
let mut hasher = Sha256::new();
hasher.update(message);
let hash = hasher.finalize();
let message_hash = MessageHash::from_bytes(&hash)
.expect("Invalid hash");
// Sign
let signature = private_key.sign_digest(&message_hash);
ECDSA Verification
use ic_secp256k1::PublicKey;
let public_key = private_key.public_key();
// Verify signature
match public_key.verify_signature(&message_hash, &signature) {
Ok(()) => println!("Valid signature"),
Err(e) => println!("Invalid signature: {:?}", e),
}
Schnorr Signatures
use ic_secp256k1::{PrivateKey, SchnorrSignature};
let private_key = PrivateKey::generate();
let message = b"Schnorr message";
// Create Schnorr signature
let schnorr_sig = private_key.sign_schnorr(message);
// Verify Schnorr signature
let public_key = private_key.public_key();
public_key.verify_schnorr(message, &schnorr_sig)
.expect("Schnorr verification failed");
Ethereum Address Derivation
use ic_secp256k1::PublicKey;
use sha3::{Keccak256, Digest};
fn ethereum_address(public_key: &PublicKey) -> [u8; 20] {
let uncompressed = public_key.serialize_uncompressed();
// Hash the public key (excluding first byte)
let mut hasher = Keccak256::new();
hasher.update(&uncompressed[1..]);
let hash = hasher.finalize();
// Take last 20 bytes
let mut address = [0u8; 20];
address.copy_from_slice(&hash[12..]);
address
}
Secp256r1 Signatures (NIST P-256)
Secp256r1 (also known as P-256 or prime256v1) is commonly used in WebAuthn and enterprise systems.
Generate and Sign
use ic_secp256r1::{PrivateKey, MessageHash};
let private_key = PrivateKey::generate();
let public_key = private_key.public_key();
// Sign a hashed message
let message_hash = MessageHash::from_bytes(&hash_bytes)
.expect("Invalid hash");
let signature = private_key.sign_digest(&message_hash);
Verify P-256 Signatures
let public_key = private_key.public_key();
match public_key.verify_signature(&message_hash, &signature) {
Ok(()) => println!("P-256 signature valid"),
Err(e) => println!("Verification failed: {:?}", e),
}
WebAuthn Integration
P-256 is commonly used in WebAuthn authentication:
use ic_secp256r1::PublicKey;
// Parse WebAuthn public key
let webauthn_key_bytes = /* from authenticator */;
let public_key = PublicKey::deserialize_der(&webauthn_key_bytes)
.expect("Failed to parse WebAuthn key");
// Verify WebAuthn signature
let auth_data = /* authenticator data */;
let client_data_hash = /* client data hash */;
// Concatenate auth data and client data hash
let mut message = Vec::new();
message.extend_from_slice(&auth_data);
message.extend_from_slice(&client_data_hash);
// Hash and verify
use sha2::{Sha256, Digest};
let hash = Sha256::digest(&message);
let message_hash = MessageHash::from_bytes(&hash).unwrap();
public_key.verify_signature(&message_hash, &signature)
.expect("WebAuthn verification failed");
SHA-3 and Keccak Hashing
SHA-3 Hashing
use ic_sha3::{Sha3_256, Sha3_512};
// SHA3-256
let data = b"Hello, world!";
let hash_256 = Sha3_256::hash(data);
println!("SHA3-256: {:?}", hash_256);
// SHA3-512
let hash_512 = Sha3_512::hash(data);
println!("SHA3-512: {:?}", hash_512);
Keccak Hashing (Ethereum)
use ic_sha3::Keccak256;
let data = b"Ethereum transaction";
let keccak_hash = Keccak256::hash(data);
println!("Keccak-256: {:?}", keccak_hash);
Streaming Hashing
use ic_sha3::{Sha3_256, Digest};
let mut hasher = Sha3_256::new();
hasher.update(b"First part ");
hasher.update(b"Second part");
let result = hasher.finalize();
println!("Hash: {:?}", result);
Hash Verification
use ic_sha3::Keccak256;
fn verify_hash(data: &[u8], expected_hash: &[u8; 32]) -> bool {
let computed_hash = Keccak256::hash(data);
computed_hash.as_ref() == expected_hash
}
Canister Signature Verification
The ic-signature-verification package provides utilities for verifying Internet Computer-specific signatures.
Verify Canister Signatures
use ic_signature_verification::{verify_canister_sig, CanisterSigPublicKey};
use candid::Principal;
let canister_id = Principal::from_text("aaaaa-aa").unwrap();
let message = b"Signed by canister";
let signature = /* canister signature bytes */;
// Create public key from canister ID
let public_key = CanisterSigPublicKey::new(canister_id, /* seed */ vec![]);
// Verify the signature
match verify_canister_sig(&public_key, message, &signature) {
Ok(()) => println!("Canister signature valid"),
Err(e) => println!("Verification failed: {:?}", e),
}
Security Best Practices
Always use cryptographically secure random number generators
Never log or expose private keys
Use zeroization for sensitive key material
Validate all inputs before cryptographic operations
Use appropriate hash functions for your use case
Keep dependencies updated for security patches
Never reuse nonces or randomness in signature schemes. This can lead to private key recovery attacks.
Common Use Cases
Bitcoin Integration
use ic_secp256k1::PrivateKey;
use sha2::{Sha256, Digest};
// Sign Bitcoin transaction
let private_key = PrivateKey::generate();
let tx_bytes = /* Bitcoin transaction bytes */;
// Double SHA-256 (Bitcoin standard)
let hash1 = Sha256::digest(tx_bytes);
let hash2 = Sha256::digest(&hash1);
let message_hash = MessageHash::from_bytes(&hash2).unwrap();
let signature = private_key.sign_digest(&message_hash);
Ethereum Transaction Signing
use ic_secp256k1::PrivateKey;
use ic_sha3::Keccak256;
let private_key = PrivateKey::generate();
let tx_bytes = /* RLP-encoded transaction */;
// Keccak-256 hash
let hash = Keccak256::hash(&tx_bytes);
let message_hash = MessageHash::from_bytes(&hash).unwrap();
let signature = private_key.sign_digest(&message_hash);
// Add recovery ID for Ethereum (v value)
let recovery_id = signature.recovery_id();
let v = 27 + recovery_id; // For non-EIP-155 transactions
Content Addressable Storage
use ic_sha3::Sha3_256;
use hex;
fn content_hash(data: &[u8]) -> String {
let hash = Sha3_256::hash(data);
hex::encode(hash)
}
// Use as content identifier
let file_data = std::fs::read("document.pdf").unwrap();
let content_id = content_hash(&file_data);
println!("Content ID: {}", content_id);
Message Authentication
use ic_ed25519::PrivateKey;
struct SignedMessage {
message: Vec<u8>,
signature: Vec<u8>,
public_key: Vec<u8>,
}
fn sign_message(private_key: &PrivateKey, message: &[u8]) -> SignedMessage {
let signature = private_key.sign_message(message);
let public_key = private_key.public_key();
SignedMessage {
message: message.to_vec(),
signature: signature.as_bytes().to_vec(),
public_key: public_key.serialize_raw().to_vec(),
}
}
fn verify_message(signed: &SignedMessage) -> bool {
let public_key = PublicKey::deserialize_raw(&signed.public_key).unwrap();
let signature = Signature::from_bytes(&signed.signature).unwrap();
public_key.verify_signature(&signed.message, &signature).is_ok()
}
For canister code, be mindful of instruction limits. Cryptographic operations can be expensive. Consider:
- Batching signature verifications
- Caching public key derivations
- Using query calls for verification when possible
- Optimizing hash operations for large data
Testing
Test cryptographic code thoroughly:
#[cfg(test)]
mod tests {
use super::*;
use ic_ed25519::PrivateKey;
#[test]
fn test_sign_and_verify() {
let private_key = PrivateKey::generate();
let public_key = private_key.public_key();
let message = b"test message";
let signature = private_key.sign_message(message);
assert!(public_key.verify_signature(message, &signature).is_ok());
// Verify wrong message fails
let wrong_message = b"wrong message";
assert!(public_key.verify_signature(wrong_message, &signature).is_err());
}
}
Learn More