Home

Awesome

cesride

cesride codecov

Cryptographic primitives for use with Composable Event Streaming Representation (CESR).

This library is currently under construction. If you want to help build, see contributing below.

Important Reference Material

Contributing

If you want to contribute, check out the issues. Tags provide some guidance.

When starting a new issue, ensure there are no others working on the same thing to avoid duplicated effort (although alternative implementations are always welcome and considered):

When you find an issue you want to take on:

For better coordination, join the #cesr-dev slack channel using the link at the bottom of this document.

Development

Install dependencies:

cargo install cargo-audit
cargo install cargo-tarpaulin

Change some code and then fix it automatically:

make fix

Commit your changes locally, and run these automated tests (requires brew install wasm-pack):

make clean preflight

You are now ready to open a pull request!

Terminology

cesride is built from cryptographic primitives that are named clearly and concisely. That said, those unfamiliar with the naming strategy but familiar with cryptography may find themselves a bit lost when first working with cesride. Implementation was ported from KERIpy and terminology was carried along with the code. The basics:

Each primitive will have methods attached to it that permit one to generate and parse the qualified base2 or base64 representation. Common methods you'll find:

Qualification

Q: What do you mean, qualified cryptographic material?

A: There are tables of codes similar to a TLV table but omitting the length field for almost all cases (as the primitives are fixed in size). The type or code, in CESR vernacular, conveys enough information to allow the application to parse data with qualified cryptographic material embedded in it.

Here is what we mean:

DKxy2sgzfplyr-tgwIxS19f2OchFHtLwPWD3v4oYimBx - this is prefixed with a D. If we look that code up in the table linked in the answer above, we find this is a transferable (rotatable) Ed25519 public key.

ELEjyRTtmfyp4VpTBTkv_b6KONMS1V8-EW-aGJ5P_QMo - this is prefixed with an E. Again, consulting the table, we learn this is a Blake3 256 digest.

Each primitive can be represented in Base64 or binary, and can be processed from either format.

Examples

use cesride::{common::Tierage, Salter};
// in this example we sign some data and ensure the signature verifies

let authentic = b"abcdefg";
let forgery = b"abcdefgh";
let mut sigers: Vec<Siger> = vec![];

// delay creating sensitive material until the last moment so it is resident in memory for shorter
let salter = Salter::new_with_defaults(Some(Tierage::med))?;

// generate a set of 3 signing keys. the fourth argument here is the code, and when we specify
// none we'll be given an Ed25519 key. the path here (third argument) will be input during key
// stretching so that the same seed can be used for a number of keys with differing paths
let signers = salter.signers(Some(3), None, Some("my-key"), None, None, None, None)?;

// sign some data
for i in 0..signers.len() {
    sigers.push(signers[i].sign_indexed(authentic, false, i as u32, None)?);
}

// verify the signatures
for i in 0..signers.len() {
    // check that verification works if the data and signature match
    assert!(signers[i].verfer().verify(&sigers[i].raw(), authentic)?);
    // verification fails if the data (or signature) does not match
    assert!(!signers[i].verfer().verify(&sigers[i].raw(), forgery)?);
}
use cesride::{Signer, Indexer, Matter};
// here we verify that a cigar primitive and a siger primitive have the same underlying
// cryptographic material

let data = b"abcdefg";

// defaults to Ed25519
let signer = Signer::new_with_defaults(None, None)?;

// create our signatures
let cigar = signer.sign_unindexed(data)?;
let siger = signer.sign_indexed(data, false, 0, None)?;

// compare the raw signatures
assert_eq!(cigar.raw(), siger.raw());
use cesride::{Diger, Matter, matter};
// here we simply print a qualified digest in base64 to stdout after hashing serialized data
// hash digests underpin core concepts of the KERI ecosystem

let data = b"abcdefg";

// derive the digest, opting this time to specify the algorithm
let diger = Diger::new_with_ser(data, Some(matter::Codex::SHA3_512))?;

// output the digest
println!("Blake3 256 digest: #{d}", d=diger.qb64()?);

For more implementation details at this time, see KERIpy.

Entropy

We use two OsRng implementations to obtain our random data. Our private keys are sometimes generated from stock methods in the underlying libraries, which is why we currently include both implementations (the two signing modules depend on traits that are incompatible).

When using Salter, one can produce many deterministic and reproducible results (such as keys) from the same seed material, or use random seed material. In the latter case, we use the more recent of the OsRng implementations directly to fill buffers.

External Dependencies (crates)

Key Stretching

We use 16 bytes of entropy from OsRng in rand_core to seed argon2. For more details on selecting appropriate argon2 parameters, consult this document.

Hashing

cesride supports the following hash algorithms:

Blake3 is recommended for most applications since it outperforms the other algorithms.

Signing

cesride supports the following signing algorithms:

We have planned support for Ed448.

The ECDSA curves (Secp256k1 and Secp256r1) use randomized signatures. Ed25519 is always deterministic. This means that if you need to avoid correlation and want to use Ed25519, you'll need to salt your data for every use case that you do not want correlated. ACDC, for example, takes this into account, allowing for configurable use of Ed25519 by injecting salty nonces in the data to be signed where privacy is a concern.

Community

Bi-weekly Meeting

Information here

Discord