This crate provides a range of cryptographic functions, including support for digital signatures, public and private keys, identities, and hashes. It also includes features for Decentralized Identifiers (DIDs) and various encoding schemes.
Features:
- Implements Multikey cryptographic keys and Multisig digital signature codecs.
- Compatible with Rust and Wasm targets, including Node.js and web browsers.
- Allows the list of supported cryptographic algorithms to be customized or expanded with new custom algorithms.
The following Classic digital signature algorithms are included:
| Algorithm | Bits of security | Public key bytes | Signature bytes | Signing time, µs | Verifying time, µs | Memory allocation, bytes |
|---|---|---|---|---|---|---|
| Ed25519 | 128 | 32 | 64 | 28 | 2,555 | 64 |
| Ed448 | 224 | 57 | 114 | 16,060 | 16,540 | 2,757 |
| Secp256k1 | 256 | 33 | 64 | 123 | 165 | 64 |
| P256 | 128 | 33 | 90* | 577 | 673 | 128 |
| P384 | 192 | 49 | 133* | 1,113 | 1,554 | 192 |
| P521 | 256 | 66 | 182* | 1,543 | 2,015 | 264 |
| BLS12-381G1 | 128 | 96 | 48 | 1,513 | 4,967 | 48 |
| 128 | 48 | 96 | ||||
| RSA 3072 | 128 | 422 | 384 | 9,388 | 556 | 24,950 |
| RSA 4096 | 140 | 550 | 512 | 17,860 | 896 | 25,210 |
| X25519 | 128 | 32 | 64 | 148 | 2,889 | 326 |
| 224 | 56 | |||||
| HmacSha256 | 256 | - | 32 | 2 | 2 | 32 |
The following Post-quantum digital signature algorithms are included:
| Algorithm | Security level | Bits of security | Public key bytes | Signature bytes | Signing time, µs | Verifying time, µs | Memory allocation, bytes |
|---|---|---|---|---|---|---|---|
| Falcon512 | 1 | 108 | 897 | 666 | 1,841 | 100 | 157,600 |
| Falcon1024 | 5 | 252 | 1,793 | 1,280 | 3,068 | 215 | 315,300 |
| ML-DSA-44 | 2 | 128* | 1,312 | 2,420 | 712 | 187 | 2,420 |
| ML-DSA-65 | 3 | 192 | 1,952 | 3,309 | 1,318 | 291 | 3,309 |
| ML-DSA-87 | 5 | 256 | 2,592 | 4,627 | 1,469 | 477 | 4,627 |
| MLKEM512 | 1 | 128 | 800 | 247 | 2,987 | 3,239 | |
| FAEST-128f | 1 | 128 | 32 | 6,336 | 30,870 | 25,670 | 433,700 |
| SLH-DSA-shake-128f | 1 | 128 | 32 | 17,088 | 198,000 | 12,590 | 17,088 |
| 3 | 192 | 48 | 35,664 | ||||
| 5 | 256 | 64 | 49,856 |
Note:
- Algorithms in bold are recommended for their high security and performance.
- Algorithms with a
strikethroughare not yet implemented. - * The signature size is not fixed.
Custom digital signature algorithms can be added by implementing SecretKeyTrait, PublicKeyTrait and SignatureTrait, and including them as a custom key variant. See ./examples/custom-keys.rs for a demo.
Create a file keys.rs containing the default implementation of the SecretKey, PublicKey and Signature.
/// Default implementation of key and signature types.
use mysteryn_crypto::multikey::*;
use mysteryn_keys::DefaultKeyFactory;
/// Multikey secret key.
pub type SecretKey = MultikeySecretKey<DefaultKeyFactory>;
/// Multikey public key.
#[allow(dead_code)]
pub type PublicKey = MultikeyPublicKey<DefaultKeyFactory>;
/// Multisig signature.
#[allow(dead_code)]
pub type Signature = Multisig<DefaultKeyFactory>;And use keys (quick-start.rs).
/// Default implementation of key and signature types.
use mysteryn_crypto::multikey::*;
use mysteryn_keys::DefaultKeyFactory;
// keys.rs
////////////////////////////////////////////////////////////////////////////////
/// Multikey secret key.
pub type SecretKey = MultikeySecretKey<DefaultKeyFactory>;
/// Multikey public key.
#[allow(dead_code)]
pub type PublicKey = MultikeyPublicKey<DefaultKeyFactory>;
/// Multisig signature.
#[allow(dead_code)]
pub type Signature = Multisig<DefaultKeyFactory>;
////////////////////////////////////////////////////////////////////////////////
// Use this instead.
// mod keys;
// use keys::*;
use mysteryn_crypto::prelude::*;
use mysteryn_crypto::{multicodec::multicodec_prefix, result::Result};
fn main() -> Result<()> {
let data = "test data";
println!("--- supported key Secp256k1:");
// can create
let secret_key = SecretKey::new(
multicodec_prefix::SECP256K1_SECRET,
None,
None,
Some("secret"),
Some("pub"),
)?;
let public_key = PublicKey::try_from(secret_key.public_key())?;
let did = public_key.get_did_pkh("mys", "")?;
println!("secret {secret_key}\npublic {public_key}\nDID {did}");
// can sign
let signature = secret_key.sign(data.as_bytes(), None)?;
println!("signed \"{data}\"\nsignature {signature}");
// can verify
public_key.verify(data.as_bytes(), &signature)?;
println!("Successfully signed and verified data.");
println!("\n--- not supported key 0x300:");
assert!(SecretKey::new(0x300, None, None, Some("secret"), Some("pub")).is_err());
println!("not supported");
Ok(())
}
/*
Expected output:
```
--- supported key Secp256k1:
secret secret_xahgjgzfsxwdjkxun9wspqzgrfx26kvvsz6vjfx27j2lthzsedvylgpy8denujqk8cjcqdq4e0xq8qxur4vfjxhw35e8fpqxg
public pub_xahgjwwqgrwp6kyqgpyypqz5qkpt7mzxvjqcugmx86l5md6gujt5zxar83mdse558tvsmrsnmsecs6un8u28rq
DID did:pkh:mys:zgVxC5GqCJFVorUp1d1JLNyuWydwkMWHd8vHUW4T7nyZp5H
signed "test data"
signature z5SCDYTvnkBFz5L6tq64PBoypyEJr68eqgD24yWbuWjobxRDVy3tHEWwbz2SQSbQzFhY72R2bNr9rWpmQ9KS4nd91DdRNDECrosem45wBpcHDmLU9dPoi7A
Successfully signed and verified data.
--- not supported key 0x300:
not supported
```
*/import { createSecret, secret2public, public2did, sign, verify, did2public } from "mysteryn-crypto"
const ED25519_SECRET = 0x1300
const secret = createSecret(ED25519_SECRET, null, null, "secret", "pub", null)
console.log("Secret key", secret)
const key = secret2public(secret)
console.log("Public key", key)
const did = public2did(key)
console.log("DID", did)
const obj = {
a: "test",
b: 1
}
console.log("Data", obj)
const data = new TextEncoder().encode(
JSON.stringify(obj)
)
const signature = await sign(data, secret)
console.log("Signature", signature)
// verify with a public key
await verify(data, key, signature)
// verify with the DID
const key2 = did2public(did)
await verify(data, key2, signature)
console.log("Successfully signed and verified data.")
// Expected output:
//
// ```text
// Secret key secret_xa82qzvqqpqyst0kltnd8nxexvv0246g5y4ec3u4v28x9ugv7l4xkdvdzr26hhrgzkmxcza9ey54nq
// Public key pub_xa8tkszqqpqys8hy4d9vwrrz8a0m7jvv56aa0rs63wke4rgdxprvtlr6tj55nflq2q5vjk3nw4623q
// DID did:key:xa8tkszqqpqys8hy4d9vwrrz8a0m7jvv56aa0rs63wke4rgdxprvtlr6tj55nflqdzmuydkftzv3ks
// Data { a: 'test', b: 1 }
// Signature zKEPzYdqYNFuGVUyi97FP7HbKKaT6d4YfkioRLTCbRJkbSrDhifrJDC19teyQbZbdEx3v1XLDTE1nZH9whjEa1ywBUF6GajbwMJyAxSLvrELEbAmNug4E
// Successfully signed and verified data.
// ```This is the encoding scheme for integer numbers, also known as varuint.
The encoding rules are as follows:
- Unsigned integers are serialized 7 bits at a time, starting with the least significant bits.
- The most significant bit (MSB) in each output byte indicates if there is a continuation byte (MSB = 1).
- There are no signed integers.
- Integers are minimally encoded.
See the unsigned-varint specification for more details.
Examples:
1 (0x01) => 00000001 (0x01)
127 (0x7f) => 01111111 (0x7f)
128 (0x80) => 10000000 00000001 (0x8001)
255 (0xff) => 11111111 00000001 (0xff01)
300 (0x012c) => 10101100 00000010 (0xac02)
16384 (0x4000) => 10000000 10000000 00000001 (0x808001)
This is the varuint length-prefixed bytes.
<length-varuint><bytes>
where
length-varuint- length of bytes encoded as a multiformats varint.bytes- raw bytes.
<varbytes> ::= <varuint> N(OCTET)
^ ^
/ \
count of variable number
octets of octets
This is a binary format for encoding secret and public keys.
<multikey-code><codec-code><hrp-varbytes><attributes>
where
multikey-code- the value0x123aencoded as a multiformats varint (0xba24).codec-code- a varuint-encoded multicode for the key algorithm, as specified in the multicodec table. Custom algorithms have this value as zero (0).hrp-varbytes- varbytes of the UTF-8 encoded HRP (Human-Readable Prefix), which can be empty (zero size).attributes- key attributes encoding.
multikey
sigil key HRP
| |
v v
0xba24 <varuint> <hrp> <attributes>
^ ^
| |
key codec sigil key attributes
<hrp> ::= <varbytes>
variable number of attributes
|
______________________
/ \
<attributes> ::= <varuint> N(<varuint>, <varbytes>)
^ ^ ^
/ / |
count of attribute attribute
attributes identifier value
<varbytes> ::= <varuint> N(OCTET)
^ ^
/ \
count of variable number
octets of octets
Key attributes:
KeyIsEncrypted (0x00): A boolean flag indicating if the key data is encrypted.
KeyData (0x01): The key data.
CipherCodec (0x02): The codec sigil specifying the encryption cipher used to encrypt the key data.
CipherKeyLen (0x03): The number of octets in the key encryption key.
CipherNonce (0x04): The nonce value for the key encryption cipher.
KdfCodec (0x05): The codec sigil specifying the key encryption key derivation function.
KdfSalt (0x06): The salt value used in the key encryption key derivation function.
KdfRounds (0x07): The number of rounds used in the key encryption key derivation function.
Threshold (0x08): The number of threshold signature key shares needed to recreate the key.
Limit (0x09): The total number of shares in the split threshold signature key.
ShareIdentifier (0x0a): The identifier for a given threshold key share.
ThresholdData (0x0b): Threshold signing codec-specific data. This is typically used to store the accumulated key shares while gathering enough shares to recreate the key.
AlgorithmName (0x0c): An arbitrary string name for the algorithm. This is optional and is intended to support arbitrary and/or non-standard key types. Used for the custom codec.
KeyType (0x0d): An arbitrary numeric key type attribute. This is optional and is intended to support arbitrary and/or non-standard key types. For the custom codec, 0 or not set means public, and 1 means secret.
PublicHrp (0x0e): The public key's human-readable prefix. This is optional and is used with a secret key only to set the related public key prefix.
See the Multikey Specification for more details.
This is a binary format for encoding digital signatures.
<multisig-code><codec-code><message-varbytes><attributes>
where
multisig-code- the value0x1239encoded as a multiformats varint (0xb924).codec-code- a varuint-encoded multicode of the signature algorithm (which is the same as the public key's code), as specified in the multicodec table. Custom algorithms have this value as zero (0).message-varbytes- the signed data if embedded, or empty with a zero length.attributes- signature attributes encoding.
signing codec sigil signature attributes
| |
v v
0x39 <varuint> <message> <attributes>
^ ^
| |
multisig optional combined
sigil signature message
<message> ::= <varbytes>
variable number of attributes
|
______________________
/ \
<attributes> ::= <varuint> N(<varuint>, <varbytes>)
^ ^ ^
/ / |
count of attribute attribute
attributes identifier value
<varbytes> ::= <varuint> N(OCTET)
^ ^
/ \
count of variable number
octets of octets
Signature attributes:
SigData (0x00): The signature data.
PayloadEncoding (0x01): The sigil specifying the encoding of the signed message.
Scheme (0x02): The threshold signing scheme.
Threshold (0x03): The minimum number of signature shares required to reconstruct the signature.
Limit (0x04): The total number of shares for a threshold signature.
ShareIdentifier (0x05): The identifier for the signature share.
ThresholdData (0x06): Codec-specific threshold signature data, which is typically used to accumulate threshold signature shares.
AlgorithmName (0x07): An arbitrary string name for the algorithm. This is optional and is intended to support arbitrary and/or non-standard signature types. It is the signature algorithm name of the custom signature codec.
Nonce (0x08): Nonce bytes (optional). Used for codecs without signature randomization.
PublicKey (0x09): Raw public key bytes (optional). Used for cases when a public key cannot be found otherwise.
See the Multisig Specification for more details.
This is the format of the Decentralized identifier.
The Multidid binary format:
<multidid-code><method-name-varbytes><method-code><method-specific-id-varbytes><url-varbytes>where
-
multidid-code- the value0x0d1dencoded as a multiformats varint, -
method-name-varbytes- the method name string ("key", "pkh", "pkh:mys", ...), -
method-code- a varint encoded multicode for the DID Method identifier or0x55for a general DID, or0x00for the Identity, -
method-specific-id-varbytes- varbytes, a unique method-specific ID, which may include colons (:):- "did:key": public key bytes.
- "did:pkh" with the Identity (0x00) codec: identity bytes.
- "did:pkh" with the Raw (0x55) codec: a string representing
[<network-id>:][<chain-id>:]<account-id>. - "did:*" with the Raw (0x55) codec: a string of the method-specific ID for general DIDs.
-
url-varbytes- varbytes, an UTF-8 encoded string representing the DID URL parameters.
The DID string format for a general DID:
did:<method>:<url>The DID string format for the did:key:
did:key:<Multibase(<method-code><public-key-bytes>)>[<url>]The DID string format for the did:pkh:
did:pkh:[<network-id>:][<chain-id>:]<account-id>[<url>]When a HRP is used, the part after "did:pkh:" is an address with "_" replaced by ":". For example, the address and its DID:
mys_xarcs8r9x45wzu9kddgphmkextlkuerv8sdvh64vu380gprhkuhsz9awzs255cgunklu
did:pkh:mys:xarcs8r9x45wzu9kddgphmkextlkuerv8sdvh64vu380gprhkuhsz9awzs255cgunkluSee Decentralized Identifiers (DIDs) v1.0 for a detailed description.
Examples:
did:example:123456789abcdefghi
did:key:pub_xa8tkszqmsw43qzqfqsksq2rvlvq8626wvjrcelyjsrd85xlxqzngap0h0jhrfajvgcmwryvztundgd9zw
did:pkh:mys:xarcs8r9x45wzu9kddgphmkextlkuerv8sdvh64vu380gprhkuhsz9awzs255cgunklu
This is a binary format for encoding hashes.
<codec-code><hash-data-varbytes>
where
-
codec-code- a varint encoded multicode for the hash algorithm, -
hash-data-varbytes- hash bytes.
String format:
Multibase(<codec-code><hash-data-varbytes>)See the multihash specification for more details.
This is a format for identities and addresses, which includes a Human-Readable Prefix (HRP) and a hash.
<hrp-varbytes><codec-code><hash-data-varbytes>where
hrp-varbytes- varbytes of the UTF-8 encoded HRP (Human-Readable Prefix), which can be empty (zero size).codec-code- a varint-encoded multicode for the hash algorithm.hash-data-varbytes- hash bytes.
String format:
<hrp>_<Multibase(<codec-code><hash-data-varbytes>)>
This is a variant of Base32 encoding with the human-readable prefix (HRP) and the checksum.
<prefix-string>_xa<Base32(<data><checksum>)>
where
prefix-string- a UTF-8 encoded string prefix._xa- the underscore delimiter (_), the Multibase prefix (x), and the encoding version char (a)*.data- data bytes.checksum- 8 or more bytes of a Reed-Solomon BCH checksum, which is calculated over the concatenation of the prefix and data (checksum(<prefix-string>_xa<data>)).
* The custom Multibase prefix (x) is followed by the encoding character(s). If there are many versions (more than a single character can encode), the Multibase prefix will become (xx) to start a new list.
Alternative variant in the Multibase format, without a HRP (uses the prefix xa):
xa<Base32(<data><checksum>)>
Alternative variant with a constant prefix (is context-dependent):
<prefix-string><Base32(<data><checksum>)>
Alphabet:
qpzry9x8gf2tvdw0s3jn54khce6mua7l
The format is inspired by Bech32. It uses the same alphabet, but the delimiter is _ (as it is easier to see than 1), and it includes a Reed-Solomon BCH checksum and an encoding version. Additionally, this format is not limited by data size.
Examples:
secrettest_xa82qzvqqpqysq4hf8zmg7t5gwn7zahqtg6pg3zmnz5wevvsgpak75ufrlyn0tesny9vkfale342xq
pubtest_xa8tkszqqpqysrhqvweac9lzyhw6ceum9pk2lxaujpv6gqp50uww65ykrqhp83j22gpe8wzx0npm3q
To build this library to the WebAssembly:
-
Install
wasm-pack:This version does not require your "Cargo.toml" to have
crate-type = ["cdylib", "rlib"].cargo install --git https://github.com/druide/wasm-pack.git
-
Build a web package:
wasm-pack build --target web
-
Build a npm module:
wasm-pack build --target nodejs
Node.js example:
cd examples/nodejs
npm run reinstall
npm startRun tests:
cargo testTesting with the WasmEdge or wasmtime (see .cargo/config.toml runner):
cargo test --target wasm32-wasip2 -- --nocaptureTesting in a browser with the wasm-pack:
wasm-pack test --chromeTesting in a browser with the wasm-bindgen-test-runner:
NO_HEADLESS=1 cargo test --target wasm32-unknown-unknown -- --nocapture
# Windows version
set NO_HEADLESS=1 && cargo test --target wasm32-unknown-unknown -- --nocaptureCoverage.
RUSTFLAGS="-C instrument-coverage" cargo test --tests
grcov . --binary-path ./target/debug/deps/ -s . -t html --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/html
grcov . -s . --binary-path ./target/debug/ -t lcov --branch --ignore-not-existing -o ./target/debug/
rm -f *.profrawBenchmarks.
Cargo stands benches as tests, so need --test. As the memory allocator is
global, need to run in one thread. To distinquish benches, their names start
with "bench":
cargo bench -- --test --test-threads=1 -q benchor
cargo b# run native
cargo run --example quick-start
# run in the WasmEdge or wasmtime
cargo run --target wasm32-wasip2 --example quick-start
# compile and run in the WasmEdge
cargo build --target wasm32-wasip2 --release --example quick-start
wasmedge compile --optimize=z target/wasm32-wasip2/release/examples/quick-start.wasm target/wasm32-wasip2/release/examples/quick-start_aot.wasm
wasmedge run target/wasm32-wasip2/release/examples/quick-start_aot.wasm
# compile and run in the wasmtime
cargo build --target wasm32-wasip2 --release --example quick-start
wasmtime compile target/wasm32-wasip2/release/examples/quick-start.wasm -o target/wasm32-wasip2/release/examples/quick-start.cwasm
wasmtime --allow-precompiled target/wasm32-wasip2/release/examples/quick-start.cwasmcargo run --example custom-keysLicensed under the Ethical Use License v1.0.