Crypto library

In short:

async function testRSA() {
    let packedKey = await (await io.openRead("../test/pregenerated_key.private.unikey")).allBytes();
    let privateKey = new crypto.PrivateKey(packedKey);

    let newPrivateKey = await crypto.PrivateKey.generate(4096);

    // signing with assymmetric keys

    let s1 = await privateKey.sign("data to sign");
    let s2 = await privateKey.sign("data to sign!");

    assert(!equalArrays(s1,s2));
    assert(s1.length == 256);

    let publicKey = privateKey.publicKey;
    assert(await publicKey.verify("data to sign", s1));
    assert(!await publicKey.verify("data to sign!", s1));
    assert(await publicKey.verify("data to sign!", s2));

    // encrypting with assymmetric keys
    let plain = "go read THIS!";
    let cipherText = await publicKey.encrypt(plain);
    let plain1 = utf8Decode(await privateKey.decrypt(cipherText));
    assert(plain == plain1);

    // using symmetric key with ETA
    let sk1 = new crypto.SymmetricKey();
    let sk2 = new crypto.SymmetricKey();
    assert(!equalArrays(sk1.packed, sk2.packed));
    let sk11 = new crypto.SymmetricKey(sk1.packed);
    assert(sk1.equals(sk11));

    cipherText = sk1.etaEncrypt(plain);
    assert(utf8Decode(sk11.etaDecrypt(cipherText)) == plain);
}

Private keys

Construct from the packed private key

let key = new crypto.PrivateKey(packed_uint8Array);

Generate a new key

let key = await crypto.PrivateKey.generate(2048);

Genenerates synchronously new private key (in a separate thread) with a given bit sthrength that should be at least 2048 bits.

Sign a string or the binary data

let signature = await key.sign(data[, hashType]);
  • data could be either a string (then will be converted to utf8 bytes) or typed Uint8Array.
  • hashType, defaults to sha3-256, could be any of: crypto.SHA256, crypto.SHA512, crypto.SHA3_256, crypto.SHA3_384, crypto.SHA3_512.

returns the signature in form of Uint8Array.

Decrypting data

Decrypting data encrypted with corresponding public key:

let plainText = await key.decrypt(cipherText);
  • cipherText: Uint8Array with ecrnypted data.

returns the decrypted data as Uint8Array.

throws (async) crypto.Exception if cipherText is corrupt so bad so can not be decrypted. Note that it is not guaranteed that the ciperText is properly decrypted even if the promise resolves. If the key does not match one used to encrypt, the data could be arbitrary. The recommended way is to ecnrypt some symmetric key with PublicKey, and use decrypted symmetric key to process the data. Symmetric key provides safe authenticated encryption (AE) and allow to effectively (e.g. fast) encypt also large data.

Get packed private key

To obtain binary representation:

let packed = key.packed;

returns cached binary representation of a key as Uint8Array.

Get the address of the private key

let address1 = key.shortAddress;
let address2 = key.longAddress;

returns short or long KeyAddress of the corresponding public key. Uses cache for both key address and public key.

See also u8 KeyAddress.

Public keys

Construct from the packed public key

let key = new crypto.PublicKey(packed_uint8Array);

Construct from the private key

let publicKey = privateKey.publicKey;

This propery is cached and can be effectively called multiple times.

Verify a signature

let ok = await key.verify(data, signature[, hashType]);
  • data: data to check the signature against. Could be either a string (then utf8 representatin will be used) or an Uint8Array.
  • signature to check, should be obtained with privateKey.sign() or like.
  • hashType: optional, should match one used when creating the signature, see privateKey.sign() above.

returns true if the signature is valid, false in all other cases.

Get the address of the public key

let address1 = key.shortAddress;
let address2 = key.longAddress;

returns short or long KeyAddress. Uses cache for both key address and public key.

See also U8 KeyAddress.

Get the packed public key

To obtain binary representation:

let packed = key.packed;

returns cached binary representation of a key as Uint8Array.

Public key fingerprints

let fp = publicKey.fingerprints

Symmetric keys

Universa symmetric keys provide robust authenticated encryption and decryption of arbitrary sized binary data. Note that these methods are synchronous (because they are fast). The actual processing is implemented in C++.

in short:

unit.test("symmetric keys", () => {
    let plain = "go read THIS";
    let sk1 = new crypto.SymmetricKey();
    let sk2 = new crypto.SymmetricKey();
    expect.notEqualArrays(sk1.packed, sk2.packed);
    let sk11 = new crypto.SymmetricKey(sk1.packed);
    expect.equal(sk1, sk11);

    let cipherText = sk1.etaEncrypt(plain);
    expect.equal(utf8Decode(sk11.etaDecrypt(cipherText)), plain);
    expect.throws(crypto.Exception, () => sk2.etaDecrypt(cipherText));
});

If the data can't be encrypted (e.g. tampered or wrong key), key.etaDecrypt(cipherText) will raise crypto.Exception.

KeyAddress

See Java API for more on key addresses.

Constructing KeyAddress

Usually, you get it from publicKey.shortAddress, publicKey.longAddress or corresponding properties of the provate key, which just get the public key properties.

from packed binary

let keyAddress = new KeyAddress(packed);
  • packed should be a Uint8Array instance with binary packed data.

from string

String representation uses a special encoding, safe58 to get non ambigous representation of the binary packed form. The packed property of the KeyAddress returns a binary backed address.

let keyAddress = new KeyAddress(string);
  • packed should be a valid string representation. Use toString() to get it.

String representation

let addressString = keyAddress.toString();

returns just as expected, Universa safe58 represetnation of the KeyAddress.

Binary packed representation

let addressString = keyAddress.packed;

returns Universa binary represntation of the KeyAddress in the Unit8Array form.

Check the key is matching

let ska = publicKey.shortAddress;
let lka = publicKey.shortAddress;
assert(ska.match(publicKey));
assert(lka.match(publicKey));

HashId

Pre-imported in crypto package and as simple as:

// constructing from binary byte or a string, utf8 will be applied
let x = crypto.HashId.of("hello, world");

// from a binary digest
let y = crypto.HashId.withDigest(x.digest);

// from a base64 encoded digest
let z = crypto.HashId.withBase64Digest(x.base64);

// get the binary digest (Uint8Array)
console.log(x.digest);

// get the string digest (base64)
console.log(x.base64);

// Check digests are equal
assert(x.equals(y));
assert(z.equals(x));

Hash functions

let binaryDigest = crypto.diegst(hashType, data);

Here is an example:

let dd = crypto.digest(crypto.SHA256, "hello, world");
assert(btoa(dd) == "Ccp+TqpuiunH0mEWcSkYSINkTQffuny/vEyKLgg2DVs=");
  • data should be eqithe a binary Uint8Array instance or a string, in which case the UTF8 ecnoding will be applied.

returns binary digest, Uint8Array.

Supported hash types as for now are:

  • crypto.SHA256
  • crypto.SHA512
  • crypto.SHA3_256
  • crypto.SHA3_384
  • crypto.SHA3_512

Error reporting

In case of any errors the crypto module throws (or rejects) the crypto.Exception class instances.