Add @cosmjs/crypto

This commit is contained in:
Simon Warta 2020-06-05 13:01:25 +02:00
parent b67579af98
commit a762c896f3
55 changed files with 7880 additions and 1 deletions

3
NOTICE
View File

@ -8,6 +8,9 @@ The code in packages/faucet was forked from https://github.com/iov-one/iov-fauce
The code in packages/cli was forked from https://github.com/iov-one/iov-core/tree/v2.0.0-alpha.7/packages/iov-cli
on 2020-02-06.
The code in packages/crypto was forked from https://github.com/iov-one/iov-core/tree/v2.3.2/packages/iov-crypto
on 2020-06-05.
Copyright 2018-2020 IOV SAS
Copyright 2020 Confio UO
Copyright 2020 Simon Warta

View File

@ -0,0 +1,8 @@
node_modules/
build/
custom_types/
dist/
docs/
generated/
types/

3
packages/crypto/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
build/
dist/
docs/

16
packages/crypto/README.md Normal file
View File

@ -0,0 +1,16 @@
# @cosmjs/crypto
[![npm version](https://img.shields.io/npm/v/@cosmjs/crypto.svg)](https://www.npmjs.com/package/@cosmjs/crypto)
@iov/cosmjs contains low-level cryptographic functionality used in other @cosmjs
libraries. Little of it is implemented here, but mainly it is a curation of
external libraries along with correctness tests. We add type-safety, some more
checks, and a simple API to these libraries. This can also be freely imported
outside of CosmJS based applications.
## License
This package is part of the cosmwasm-js repository, licensed under the Apache
License 2.0 (see
[NOTICE](https://github.com/confio/cosmwasm-js/blob/master/NOTICE) and
[LICENSE](https://github.com/confio/cosmwasm-js/blob/master/LICENSE)).

View File

@ -0,0 +1,26 @@
#!/usr/bin/env node
require("source-map-support").install();
const defaultSpecReporterConfig = require("../../jasmine-spec-reporter.config.json");
// setup Jasmine
const Jasmine = require("jasmine");
const jasmine = new Jasmine();
jasmine.loadConfig({
spec_dir: "build",
spec_files: ["**/*.spec.js"],
helpers: [],
random: false,
seed: null,
stopSpecOnExpectationFailure: false,
});
jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 15 * 1000;
// setup reporter
const { SpecReporter } = require("jasmine-spec-reporter");
const reporter = new SpecReporter({ ...defaultSpecReporterConfig });
// initialize and execute
jasmine.env.clearReporters();
jasmine.addReporter(reporter);
jasmine.execute();

View File

@ -0,0 +1,50 @@
module.exports = function (config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: ".",
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ["jasmine"],
// list of files / patterns to load in the browser
files: ["dist/web/tests.js"],
client: {
jasmine: {
random: false,
timeoutInterval: 15000,
},
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ["progress", "kjhtml"],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
// Use debug until Firefox starting issues on Travis are understood
// https://travis-ci.com/iov-one/iov-core/jobs/174888518
logLevel: config.LOG_DEBUG,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ["Firefox"],
browserNoActivityTimeout: 90000,
// Keep brower open for debugging. This is overridden by yarn scripts
singleRun: false,
});
};

View File

@ -0,0 +1 @@
Directory used to trigger lerna package updates for all packages

View File

@ -0,0 +1,65 @@
{
"name": "@cosmjs/crypto",
"version": "0.8.0",
"description": "Cryptography resources for blockchain projects",
"contributors": [
"IOV SAS <admin@iov.one>",
"Simon Warta"
],
"license": "Apache-2.0",
"main": "build/index.js",
"types": "types/index.d.ts",
"files": [
"build/",
"types/",
"*.md",
"!*.spec.*",
"!**/testdata/"
],
"repository": {
"type": "git",
"url": "https://github.com/CosmWasm/cosmwasm-js/tree/master/packages/crypto"
},
"publishConfig": {
"access": "public"
},
"scripts": {
"docs": "shx rm -rf docs && typedoc --options typedoc.js",
"lint": "eslint --max-warnings 0 \"**/*.{js,ts}\" && tslint -t verbose --project .",
"format": "prettier --write --loglevel warn \"./src/**/*.ts\"",
"format-text": "prettier --write --prose-wrap always --print-width 80 \"./*.md\"",
"test-node": "node jasmine-testrunner.js",
"test-edge": "yarn pack-web && karma start --single-run --browsers Edge",
"test-firefox": "yarn pack-web && karma start --single-run --browsers Firefox",
"test-chrome": "yarn pack-web && karma start --single-run --browsers ChromeHeadless",
"test-safari": "yarn pack-web && karma start --single-run --browsers Safari",
"test": "yarn build-or-skip && yarn test-node",
"move-types": "shx rm -r ./types/* && shx mv build/types/* ./types && rm -rf ./types/testdata && shx rm -f ./types/*.spec.d.ts",
"format-types": "prettier --write --loglevel warn \"./types/**/*.d.ts\"",
"build": "shx rm -rf ./build && tsc && yarn move-types && yarn format-types",
"build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build",
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js"
},
"dependencies": {
"@iov/encoding": "^2.3.2",
"bip39": "^3.0.2",
"bn.js": "^4.11.8",
"elliptic": "^6.4.0",
"js-sha3": "^0.8.0",
"libsodium-wrappers": "^0.7.6",
"pbkdf2": "^3.0.16",
"ripemd160": "^2.0.2",
"sha.js": "^2.4.11",
"type-tagger": "^1.0.0",
"unorm": "^1.5.0"
},
"devDependencies": {
"@types/bn.js": "^4.11.6",
"@types/elliptic": "^6.4.12",
"@types/libsodium-wrappers": "^0.7.7",
"@types/pbkdf2": "^3.0.0",
"@types/ripemd160": "^2.0.0",
"@types/sha.js": "^2.4.0",
"@types/unorm": "^1.3.27"
}
}

View File

@ -0,0 +1,416 @@
import { fromHex } from "@iov/encoding";
import { Bip39 } from "./bip39";
import { EnglishMnemonic } from "./englishmnemonic";
import bip39Vectors from "./testdata/bip39.json";
describe("Bip39", () => {
describe("encode", () => {
it("can encode to mnemonic", () => {
// Test vectors from https://github.com/trezor/python-mnemonic/blob/b502451a33a440783926e04428115e0bed87d01f/vectors.json
// plus similar vectors generated for the missing lengths 15 and 21 words
const { "12": vec12, "15": vec15, "18": vec18, "21": vec21, "24": vec24 } = bip39Vectors.encoding;
for (const vectors of [vec12, vec15, vec18, vec21, vec24]) {
for (const { entropy, mnemonic } of vectors) {
expect(Bip39.encode(fromHex(entropy)).toString()).toEqual(mnemonic);
}
}
});
it("throws for invalid input", () => {
// invalid input length
expect(() => Bip39.encode(fromHex(""))).toThrowError(/invalid input length/);
expect(() => Bip39.encode(fromHex("00"))).toThrowError(/invalid input length/);
expect(() => Bip39.encode(fromHex("000000000000000000000000000000"))).toThrowError(
/invalid input length/,
);
expect(() => Bip39.encode(fromHex("0000000000000000000000000000000000"))).toThrowError(
/invalid input length/,
);
expect(() => Bip39.encode(fromHex("0000000000000000000000000000000000000000000000"))).toThrowError(
/invalid input length/,
);
expect(() => Bip39.encode(fromHex("00000000000000000000000000000000000000000000000000"))).toThrowError(
/invalid input length/,
);
expect(() =>
Bip39.encode(fromHex("00000000000000000000000000000000000000000000000000000000000000")),
).toThrowError(/invalid input length/);
expect(() =>
Bip39.encode(fromHex("000000000000000000000000000000000000000000000000000000000000000000")),
).toThrowError(/invalid input length/);
});
});
describe("decode", () => {
it("can decode from mnemonic", () => {
const { "12": vec12, "15": vec15, "18": vec18, "21": vec21, "24": vec24 } = bip39Vectors.encoding;
for (const vectors of [vec12, vec15, vec18, vec21, vec24]) {
for (const { entropy, mnemonic } of vectors) {
expect(Bip39.decode(new EnglishMnemonic(mnemonic))).toEqual(fromHex(entropy));
}
}
});
});
describe("mnemonicToSeed", () => {
it("can calculate seed from mnemonic (trezor test vectors)", async () => {
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
"TREZOR",
),
).toEqual(
fromHex(
"c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic("legal winner thank year wave sausage worth useful legal winner thank yellow"),
"TREZOR",
),
).toEqual(
fromHex(
"2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"letter advice cage absurd amount doctor acoustic avoid letter advice cage above",
),
"TREZOR",
),
).toEqual(
fromHex(
"d71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f12eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic("zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong"),
"TREZOR",
),
).toEqual(
fromHex(
"ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a13332572917f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent",
),
"TREZOR",
),
).toEqual(
fromHex(
"035895f2f481b1b0f01fcf8c289c794660b289981a78f8106447707fdd9666ca06da5a9a565181599b79f53b844d8a71dd9f439c52a3d7b3e8a79c906ac845fa",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will",
),
"TREZOR",
),
).toEqual(
fromHex(
"f2b94508732bcbacbcc020faefecfc89feafa6649a5491b8c952cede496c214a0c7b3c392d168748f2d4a612bada0753b52a1c7ac53c1e93abd5c6320b9e95dd",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always",
),
"TREZOR",
),
).toEqual(
fromHex(
"107d7c02a5aa6f38c58083ff74f04c607c2d2c0ecc55501dadd72d025b751bc27fe913ffb796f841c49b1d33b610cf0e91d3aa239027f5e99fe4ce9e5088cd65",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic("zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when"),
"TREZOR",
),
).toEqual(
fromHex(
"0cd6e5d827bb62eb8fc1e262254223817fd068a74b5b449cc2f667c3f1f985a76379b43348d952e2265b4cd129090758b3e3c2c49103b5051aac2eaeb890a528",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
),
"TREZOR",
),
).toEqual(
fromHex(
"bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title",
),
"TREZOR",
),
).toEqual(
fromHex(
"bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146ad717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless",
),
"TREZOR",
),
).toEqual(
fromHex(
"c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61af0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote",
),
"TREZOR",
),
).toEqual(
fromHex(
"dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic("ozone drill grab fiber curtain grace pudding thank cruise elder eight picnic"),
"TREZOR",
),
).toEqual(
fromHex(
"274ddc525802f7c828d8ef7ddbcdc5304e87ac3535913611fbbfa986d0c9e5476c91689f9c8a54fd55bd38606aa6a8595ad213d4c9c9f9aca3fb217069a41028",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog",
),
"TREZOR",
),
).toEqual(
fromHex(
"628c3827a8823298ee685db84f55caa34b5cc195a778e52d45f59bcf75aba68e4d7590e101dc414bc1bbd5737666fbbef35d1f1903953b66624f910feef245ac",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"hamster diagram private dutch cause delay private meat slide toddler razor book happy fancy gospel tennis maple dilemma loan word shrug inflict delay length",
),
"TREZOR",
),
).toEqual(
fromHex(
"64c87cde7e12ecf6704ab95bb1408bef047c22db4cc7491c4271d170a1b213d20b385bc1588d9c7b38f1b39d415665b8a9030c9ec653d75e65f847d8fc1fc440",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic("scheme spot photo card baby mountain device kick cradle pact join borrow"),
"TREZOR",
),
).toEqual(
fromHex(
"ea725895aaae8d4c1cf682c1bfd2d358d52ed9f0f0591131b559e2724bb234fca05aa9c02c57407e04ee9dc3b454aa63fbff483a8b11de949624b9f1831a9612",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"horn tenant knee talent sponsor spell gate clip pulse soap slush warm silver nephew swap uncle crack brave",
),
"TREZOR",
),
).toEqual(
fromHex(
"fd579828af3da1d32544ce4db5c73d53fc8acc4ddb1e3b251a31179cdb71e853c56d2fcb11aed39898ce6c34b10b5382772db8796e52837b54468aeb312cfc3d",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"panda eyebrow bullet gorilla call smoke muffin taste mesh discover soft ostrich alcohol speed nation flash devote level hobby quick inner drive ghost inside",
),
"TREZOR",
),
).toEqual(
fromHex(
"72be8e052fc4919d2adf28d5306b5474b0069df35b02303de8c1729c9538dbb6fc2d731d5f832193cd9fb6aeecbc469594a70e3dd50811b5067f3b88b28c3e8d",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic("cat swing flag economy stadium alone churn speed unique patch report train"),
"TREZOR",
),
).toEqual(
fromHex(
"deb5f45449e615feff5640f2e49f933ff51895de3b4381832b3139941c57b59205a42480c52175b6efcffaa58a2503887c1e8b363a707256bdd2b587b46541f5",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"light rule cinnamon wrap drastic word pride squirrel upgrade then income fatal apart sustain crack supply proud access",
),
"TREZOR",
),
).toEqual(
fromHex(
"4cbdff1ca2db800fd61cae72a57475fdc6bab03e441fd63f96dabd1f183ef5b782925f00105f318309a7e9c3ea6967c7801e46c8a58082674c860a37b93eda02",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"all hour make first leader extend hole alien behind guard gospel lava path output census museum junior mass reopen famous sing advance salt reform",
),
"TREZOR",
),
).toEqual(
fromHex(
"26e975ec644423f4a4c4f4215ef09b4bd7ef924e85d1d17c4cf3f136c2863cf6df0a475045652c57eb5fb41513ca2a2d67722b77e954b4b3fc11f7590449191d",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"vessel ladder alter error federal sibling chat ability sun glass valve picture",
),
"TREZOR",
),
).toEqual(
fromHex(
"2aaa9242daafcee6aa9d7269f17d4efe271e1b9a529178d7dc139cd18747090bf9d60295d0ce74309a78852a9caadf0af48aae1c6253839624076224374bc63f",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"scissors invite lock maple supreme raw rapid void congress muscle digital elegant little brisk hair mango congress clump",
),
"TREZOR",
),
).toEqual(
fromHex(
"7b4a10be9d98e6cba265566db7f136718e1398c71cb581e1b2f464cac1ceedf4f3e274dc270003c670ad8d02c4558b2f8e39edea2775c9e232c7cb798b069e88",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"void come effort suffer camp survey warrior heavy shoot primary clutch crush open amazing screen patrol group space point ten exist slush involve unfold",
),
"TREZOR",
),
).toEqual(
fromHex(
"01f5bced59dec48e362f2c45b5de68b9fd6c92c6634f44d6d40aab69056506f0e35524a518034ddc1192e1dacd32c1ed3eaa3c3b131c88ed8e7e54c49a5d0998",
),
);
});
it("can calculate seed from mnemonic (no password)", async () => {
// custom test vectors using
// $ git clone https://github.com/trezor/python-mnemonic.git && cd python-mnemonic
// $ python3 -m venv venv
// $ source venv/bin/activate
// $ pip install wheel bip32utils
// $ pip install -r requirements.txt
// patch generate_vectors.py to your needs
// $ python generate_vectors.py
// empty password
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic("robust pipe raise illness symptom crowd trip will slow assault recipe oven"),
"",
),
).toEqual(
fromHex(
"5539eed11e1096e9d52f69f15ad3d7c6547a40a3865b9517dbcbb03c31f231900622f58616d64d2d1cc0440f31d67fb0b2699a5fc885f796c746e0f844477093",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"pair ethics august street tornado spare present under capital raise cross current main craft stone clutch tray all",
),
"",
),
).toEqual(
fromHex(
"1272467e954cec4e0ad720002d037a3aaf795a57ffbeea6aaa0c242d410eb52050292447aa2c68470a07ecc80171edfa9e027793265047be3128d94e867a4f99",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"allow finger front connect strategy purchase journey distance trouble guitar honey alpha giraffe canal junk vintage chronic blade gate custom soap flip first mix",
),
"",
),
).toEqual(
fromHex(
"476a41ac016b5bdf9f114456929975a036ae326e2efdca441ac5a0949ef89ab9246dc9e49a5d2d64d1926eb9dbe17576cb010471c2a821b216202acdf3d7a27b",
),
);
// no password
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic("robust pipe raise illness symptom crowd trip will slow assault recipe oven"),
),
).toEqual(
fromHex(
"5539eed11e1096e9d52f69f15ad3d7c6547a40a3865b9517dbcbb03c31f231900622f58616d64d2d1cc0440f31d67fb0b2699a5fc885f796c746e0f844477093",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"pair ethics august street tornado spare present under capital raise cross current main craft stone clutch tray all",
),
),
).toEqual(
fromHex(
"1272467e954cec4e0ad720002d037a3aaf795a57ffbeea6aaa0c242d410eb52050292447aa2c68470a07ecc80171edfa9e027793265047be3128d94e867a4f99",
),
);
expect(
await Bip39.mnemonicToSeed(
new EnglishMnemonic(
"allow finger front connect strategy purchase journey distance trouble guitar honey alpha giraffe canal junk vintage chronic blade gate custom soap flip first mix",
),
),
).toEqual(
fromHex(
"476a41ac016b5bdf9f114456929975a036ae326e2efdca441ac5a0949ef89ab9246dc9e49a5d2d64d1926eb9dbe17576cb010471c2a821b216202acdf3d7a27b",
),
);
});
});
});

View File

@ -0,0 +1,51 @@
import { fromHex, toHex } from "@iov/encoding";
import * as bip39 from "bip39";
import { pbkdf2 } from "pbkdf2";
import * as unorm from "unorm";
import { EnglishMnemonic } from "./englishmnemonic";
export class Bip39 {
public static encode(entropy: Uint8Array): EnglishMnemonic {
const allowedEntropyLengths: readonly number[] = [16, 20, 24, 28, 32];
if (allowedEntropyLengths.indexOf(entropy.length) === -1) {
throw new Error("invalid input length");
}
return new EnglishMnemonic(bip39.entropyToMnemonic(toHex(entropy)));
}
public static decode(mnemonic: EnglishMnemonic): Uint8Array {
return fromHex(bip39.mnemonicToEntropy(mnemonic.toString()));
}
public static async mnemonicToSeed(mnemonic: EnglishMnemonic, password?: string): Promise<Uint8Array> {
// reimplementation of bip39.mnemonicToSeed using the asynchronous
// interface of https://www.npmjs.com/package/pbkdf2
const mnemonicBytes = Buffer.from(unorm.nfkd(mnemonic.toString()), "utf8");
const salt = "mnemonic" + (password ? unorm.nfkd(password) : "");
const saltBytes = Buffer.from(salt, "utf8");
return this.pbkdf2(mnemonicBytes, saltBytes, 2048, 64, "sha512");
}
// convert pbkdf2's calllback interface to Promise interface
private static async pbkdf2(
secret: Uint8Array,
salt: Uint8Array,
iterations: number,
keylen: number,
digest: string,
): Promise<Uint8Array> {
return new Promise<any>((resolve, reject) => {
// TODO: Patch @types/pbkdf2 to allow Uint8Array as secret and salt argument
pbkdf2(Buffer.from(secret), Buffer.from(salt), iterations, keylen, digest, (err, derivedKey) => {
if (err) {
reject(err);
} else {
resolve(new Uint8Array(derivedKey));
}
});
});
}
}

View File

@ -0,0 +1,247 @@
import { fromAscii, fromBase64, fromHex } from "@iov/encoding";
import { EnglishMnemonic } from "./englishmnemonic";
import { Sha256 } from "./sha";
import wordlists from "./testdata/bip39_wordlists.json";
describe("EnglishMnemonic", () => {
describe("wordlist", () => {
it("matches the words from the bitcoin/bips/bip-0039 spec", () => {
const lineFeed = 0x0a;
const bip39EnglishTxt = fromBase64(wordlists.english);
// Ensure we loaded the correct english.txt from https://github.com/bitcoin/bips/tree/master/bip-0039
const checksum = new Sha256(bip39EnglishTxt).digest();
expect(checksum).toEqual(fromHex("2f5eed53a4727b4bf8880d8f3f199efc90e58503646d9ff8eff3a2ed3b24dbda"));
// tslint:disable-next-line: readonly-array
const wordsFromSpec: string[] = [];
let start = 0; // the start cursor marks the first byte of the word
let end = 0; // the end cursor marks the line feed byte
while (end < bip39EnglishTxt.length - 1) {
end = start;
while (bip39EnglishTxt[end] !== lineFeed) end++;
const slice = bip39EnglishTxt.slice(start, end);
wordsFromSpec.push(fromAscii(slice));
start = end + 1;
}
expect(EnglishMnemonic.wordlist).toEqual(wordsFromSpec);
});
});
// tslint:disable:no-unused-expression
it("works for valid inputs", () => {
expect(() => {
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
);
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon address",
);
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent",
);
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon admit",
);
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
);
}).not.toThrow();
});
it("rejects invalid whitespacing", () => {
// extra space (leading, middle, trailing)
expect(
() =>
new EnglishMnemonic(
" abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about ",
),
).toThrowError(/invalid mnemonic format/i);
// newline, tab
expect(
() =>
new EnglishMnemonic(
"abandon\nabandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"abandon\tabandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
});
it("rejects disallowed letters", () => {
// Disallowed letters in words (capital, number, special char)
expect(
() =>
new EnglishMnemonic(
"Abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon Abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"route66 abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon route66 abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"lötkolben abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon lötkolben abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid mnemonic format/i);
});
it("word counts other than 12, 15, 18, 21, 24", () => {
// too few and too many words (11, 13, 17, 19, 23, 25)
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid word count(.*)got: 11/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
),
).toThrowError(/invalid word count(.*)got: 13/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent",
),
).toThrowError(/invalid word count(.*)got: 17/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent",
),
).toThrowError(/invalid word count(.*)got: 19/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
),
).toThrowError(/invalid word count(.*)got: 23/i);
expect(
() =>
new EnglishMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
),
).toThrowError(/invalid word count(.*)got: 25/i);
});
it("rejects invalid checksums", () => {
// 12x, 15x, 18x, 21x, 24x "zoo"
expect(() => new EnglishMnemonic("zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo")).toThrowError(
/invalid mnemonic checksum/i,
);
expect(
() => new EnglishMnemonic("zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo"),
).toThrowError(/invalid mnemonic checksum/i);
expect(
() => new EnglishMnemonic("zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo"),
).toThrowError(/invalid mnemonic checksum/i);
expect(
() =>
new EnglishMnemonic(
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo",
),
).toThrowError(/invalid mnemonic checksum/i);
expect(
() =>
new EnglishMnemonic(
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo",
),
).toThrowError(/invalid mnemonic checksum/i);
});
it("rejects valid mnemonics of other languages", () => {
// valid Spanish and Italian bip39 mnemonics
expect(
() =>
new EnglishMnemonic(
"humo odio oriente colina taco fingir salto geranio glaciar academia suave vigor",
),
).toThrowError(/contains invalid word/i);
expect(
() =>
new EnglishMnemonic(
"yema folleto tos llave obtener natural fruta deseo laico sopa novato lazo imponer afinar vena hoja zarza cama",
),
).toThrowError(/contains invalid word/i);
expect(
() =>
new EnglishMnemonic(
"burla plaza arroz ronda pregunta vacuna veloz boina retiro exento prensa tortuga cabeza pilar anual molino molde fiesta masivo jefe leve fatiga clase plomo",
),
).toThrowError(/contains invalid word/i);
expect(
() =>
new EnglishMnemonic(
"braccio trincea armonia emiro svedese lepre stridulo metallo baldo rasente potassio rilassato",
),
).toThrowError(/contains invalid word/i);
expect(
() =>
new EnglishMnemonic(
"riparato arrosto globulo singolo bozzolo roba pirolisi ultimato padrone munto leggero avanzato monetario guanto lorenzo latino inoltrare modulo",
),
).toThrowError(/contains invalid word/i);
expect(
() =>
new EnglishMnemonic(
"promessa mercurio spessore snodo trave risata mecenate vichingo ceto orecchino vissuto risultato canino scarso futile fune epilogo uovo inedito apatico folata egoismo rifugio coma",
),
).toThrowError(/contains invalid word/i);
});
// tslint:enable:no-unused-expression
describe("toString", () => {
it("works", () => {
const original =
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
const mnemonic = new EnglishMnemonic(original);
expect(mnemonic.toString()).toEqual(original);
});
});
});

View File

@ -0,0 +1,40 @@
import * as bip39 from "bip39";
export class EnglishMnemonic {
public static readonly wordlist: readonly string[] = bip39.wordlists.english;
// list of space separated lower case words (1 or more)
private static readonly mnemonicMatcher = /^[a-z]+( [a-z]+)*$/;
private readonly data: string;
public constructor(mnemonic: string) {
if (!EnglishMnemonic.mnemonicMatcher.test(mnemonic)) {
throw new Error("Invalid mnemonic format");
}
const words = mnemonic.split(" ");
const allowedWordsLengths: readonly number[] = [12, 15, 18, 21, 24];
if (allowedWordsLengths.indexOf(words.length) === -1) {
throw new Error(
`Invalid word count in mnemonic (allowed: ${allowedWordsLengths} got: ${words.length})`,
);
}
for (const word of words) {
if (EnglishMnemonic.wordlist.indexOf(word) === -1) {
throw new Error("Mnemonic contains invalid word");
}
}
// Throws with informative error message if mnemonic is not valid
// tslint:disable-next-line:no-unused-expression
bip39.mnemonicToEntropy(mnemonic);
this.data = mnemonic;
}
public toString(): string {
return this.data;
}
}

View File

@ -0,0 +1,5 @@
export interface HashFunction {
readonly blockSize: number;
readonly update: (_: Uint8Array) => HashFunction;
readonly digest: () => Uint8Array;
}

View File

@ -0,0 +1,241 @@
import { fromHex } from "@iov/encoding";
import { Hmac } from "./hmac";
import { Sha1, Sha256, Sha512 } from "./sha";
describe("HMAC", () => {
it("can perform HMAC(SHA1) according to Botan test vectors", () => {
// https://github.com/randombit/botan/blob/a5a260c/src/tests/data/mac/hmac.vec
{
const hmac = new Hmac(Sha1, fromHex("0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B"));
hmac.update(fromHex("4869205468657265"));
expect(hmac.digest()).toEqual(fromHex("B617318655057264E28BC0B6FB378C8EF146BE00"));
}
{
const hmac = new Hmac(Sha1, fromHex("0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C"));
hmac.update(fromHex("546573742057697468205472756E636174696F6E"));
expect(hmac.digest()).toEqual(fromHex("4C1A03424B55E07FE7F27BE1D58BB9324A9A5A04"));
}
{
const hmac = new Hmac(Sha1, fromHex("4CA0EF38F1794B28A8F8EE110EE79D48CE13BE25"));
hmac.update(
fromHex(
"54657374205573696E67204C6172676572205468616E20426C6F636B2D53697A65204B6579202D2048617368204B6579204669727374",
),
);
expect(hmac.digest()).toEqual(fromHex("AA4AE5E15272D00E95705637CE8A3B55ED402112"));
}
{
const hmac = new Hmac(Sha1, fromHex("4CA0EF38F1794B28A8F8EE110EE79D48CE13BE25"));
hmac.update(
fromHex(
"54657374205573696E67204C6172676572205468616E20426C6F636B2D53697A65204B657920616E64204C6172676572205468616E204F6E6520426C6F636B2D53697A652044617461",
),
);
expect(hmac.digest()).toEqual(fromHex("E8E99D0F45237D786D6BBAA7965C7808BBFF1A91"));
}
{
const hmac = new Hmac(Sha1, fromHex("0102030405060708090A0B0C0D0E0F10111213141516171819"));
hmac.update(
fromHex(
"CDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCD",
),
);
expect(hmac.digest()).toEqual(fromHex("4C9007F4026250C6BC8414F9BF50C86C2D7235DA"));
}
{
const hmac = new Hmac(Sha1, fromHex("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"));
hmac.update(
fromHex(
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
),
);
expect(hmac.digest()).toEqual(fromHex("125D7342B9AC11CD91A39AF48AA17B4F63F175D3"));
}
});
it("can perform HMAC(SHA256) according to Botan test vectors", () => {
// https://github.com/randombit/botan/blob/a5a260c/src/tests/data/mac/hmac.vec#L60
{
const hmac = new Hmac(
Sha256,
fromHex("0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20"),
);
hmac.update(fromHex("616263"));
expect(hmac.digest()).toEqual(
fromHex("A21B1F5D4CF4F73A4DD939750F7A066A7F98CC131CB16A6692759021CFAB8181"),
);
}
{
const hmac = new Hmac(Sha256, fromHex("4A656665"));
hmac.update(fromHex("7768617420646F2079612077616E7420666F72206E6F7468696E673F"));
expect(hmac.digest()).toEqual(
fromHex("5BDCC146BF60754E6A042426089575C75A003F089D2739839DEC58B964EC3843"),
);
}
{
const hmac = new Hmac(
Sha256,
fromHex("0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20"),
);
hmac.update(
fromHex(
"6162636462636465636465666465666765666768666768696768696A68696A6B696A6B6C6A6B6C6D6B6C6D6E6C6D6E6F6D6E6F706E6F70716162636462636465636465666465666765666768666768696768696A68696A6B696A6B6C6A6B6C6D6B6C6D6E6C6D6E6F6D6E6F706E6F7071",
),
);
expect(hmac.digest()).toEqual(
fromHex("470305FC7E40FE34D3EEB3E773D95AAB73ACF0FD060447A5EB4595BF33A9D1A3"),
);
}
{
const hmac = new Hmac(
Sha256,
fromHex("0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B"),
);
hmac.update(fromHex("4869205468657265"));
expect(hmac.digest()).toEqual(
fromHex("198A607EB44BFBC69903A0F1CF2BBDC5BA0AA3F3D9AE3C1C7A3B1696A0B68CF7"),
);
}
{
const hmac = new Hmac(
Sha256,
fromHex("0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C"),
);
hmac.update(fromHex("546573742057697468205472756E636174696F6E"));
expect(hmac.digest()).toEqual(
fromHex("7546AF01841FC09B1AB9C3749A5F1C17D4F589668A587B2700A9C97C1193CF42"),
);
}
});
it("can perform HMAC(SHA512) according to RFC 4231", () => {
// Test Case 17 from https://tools.ietf.org/html/rfc4231#section-4
{
const hmac = new Hmac(Sha512, fromHex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"));
hmac.update(fromHex("4869205468657265"));
expect(hmac.digest()).toEqual(
fromHex(
"87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854",
),
);
}
{
const hmac = new Hmac(Sha512, fromHex("4a656665"));
hmac.update(fromHex("7768617420646f2079612077616e7420666f72206e6f7468696e673f"));
expect(hmac.digest()).toEqual(
fromHex(
"164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737",
),
);
}
{
const hmac = new Hmac(Sha512, fromHex("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
hmac.update(
fromHex(
"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
),
);
expect(hmac.digest()).toEqual(
fromHex(
"fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb",
),
);
}
{
const hmac = new Hmac(Sha512, fromHex("0102030405060708090a0b0c0d0e0f10111213141516171819"));
hmac.update(
fromHex(
"cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
),
);
expect(hmac.digest()).toEqual(
fromHex(
"b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd",
),
);
}
{
const hmac = new Hmac(Sha512, fromHex("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"));
hmac.update(fromHex("546573742057697468205472756e636174696f6e"));
expect(hmac.digest().slice(0, 16)).toEqual(fromHex("415fad6271580a531d4179bc891d87a6"));
}
{
const hmac = new Hmac(
Sha512,
fromHex(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
),
);
hmac.update(
fromHex(
"54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b6579202d2048617368204b6579204669727374",
),
);
expect(hmac.digest()).toEqual(
fromHex(
"80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598",
),
);
}
{
const hmac = new Hmac(
Sha512,
fromHex(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
),
);
hmac.update(
fromHex(
"5468697320697320612074657374207573696e672061206c6172676572207468616e20626c6f636b2d73697a65206b657920616e642061206c6172676572207468616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565647320746f20626520686173686564206265666f7265206265696e6720757365642062792074686520484d414320616c676f726974686d2e",
),
);
expect(hmac.digest()).toEqual(
fromHex(
"e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58",
),
);
}
});
it("can perform incremental hashing", () => {
const hmac = new Hmac(Sha1, fromHex("0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B"));
// full message: 4869205468657265
hmac.update(fromHex(""));
hmac.update(fromHex("48"));
hmac.update(fromHex(""));
hmac.update(fromHex(""));
hmac.update(fromHex("69"));
hmac.update(fromHex("20"));
hmac.update(fromHex("5468"));
hmac.update(fromHex("657265"));
hmac.update(fromHex(""));
expect(hmac.digest()).toEqual(fromHex("B617318655057264E28BC0B6FB378C8EF146BE00"));
});
it("works with empty keys", () => {
// Generated using Python 3
// hmac.new(b'', bytearray.fromhex("7061756c"), hashlib.sha256).hexdigest()
{
const hmac = new Hmac(Sha256, fromHex(""));
hmac.update(fromHex("7061756c"));
expect(hmac.digest()).toEqual(
fromHex("50972b73add1dbbbe6884104d0f91efcef184e0aef6e485a075b3cab1f70e572"),
);
}
{
const hmac = new Hmac(Sha256, fromHex(""));
hmac.update(fromHex("70"));
expect(hmac.digest()).toEqual(
fromHex("a1d75fa8dc0d1a84cf6df6f0e55cb52d89d44acb26e786c9329cf8dc8804a94e"),
);
}
{
const hmac = new Hmac(Sha256, fromHex(""));
hmac.update(fromHex(""));
expect(hmac.digest()).toEqual(
fromHex("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"),
);
}
});
});

View File

@ -0,0 +1,49 @@
import { HashFunction } from "./hash";
export class Hmac<H extends HashFunction> implements HashFunction {
public readonly blockSize: number;
private readonly messageHasher: H;
private readonly oKeyPad: Uint8Array;
private readonly iKeyPad: Uint8Array;
private readonly hash: (data: Uint8Array) => Uint8Array;
public constructor(hashFunctionConstructor: new () => H, originalKey: Uint8Array) {
// This implementation is based on https://en.wikipedia.org/wiki/HMAC#Implementation
// with the addition of incremental hashing support. Thus part of the algorithm
// is in the constructor and the rest in digest().
const blockSize = new hashFunctionConstructor().blockSize;
this.hash = (data) => new hashFunctionConstructor().update(data).digest();
let key = originalKey;
if (key.length > blockSize) {
key = this.hash(key);
}
if (key.length < blockSize) {
const zeroPadding = new Uint8Array(blockSize - key.length);
key = new Uint8Array([...key, ...zeroPadding]);
}
// tslint:disable-next-line:no-bitwise
this.oKeyPad = key.map((keyByte) => keyByte ^ 0x5c);
// tslint:disable-next-line:no-bitwise
this.iKeyPad = key.map((keyByte) => keyByte ^ 0x36);
this.messageHasher = new hashFunctionConstructor();
this.blockSize = blockSize;
this.update(this.iKeyPad);
}
public update(data: Uint8Array): Hmac<H> {
this.messageHasher.update(data);
return this;
}
public digest(): Uint8Array {
const innerHash = this.messageHasher.digest();
return this.hash(new Uint8Array([...this.oKeyPad, ...innerHash]));
}
}

View File

@ -0,0 +1,30 @@
export { Bip39 } from "./bip39";
export { EnglishMnemonic } from "./englishmnemonic";
export { HashFunction } from "./hash";
export { Hmac } from "./hmac";
export { Keccak256 } from "./keccak";
export {
Xchacha20poly1305Ietf,
Xchacha20poly1305IetfCiphertext,
Xchacha20poly1305IetfKey,
Xchacha20poly1305IetfMessage,
Xchacha20poly1305IetfNonce,
Argon2id,
Argon2idOptions,
Ed25519,
Ed25519Keypair,
} from "./libsodium";
export { Random } from "./random";
export { Ripemd160 } from "./ripemd";
export { Secp256k1, Secp256k1Keypair } from "./secp256k1";
export { ExtendedSecp256k1Signature, Secp256k1Signature } from "./secp256k1signature";
export { Sha1, Sha256, Sha512 } from "./sha";
export {
pathToString,
stringToPath,
Slip10,
Slip10Curve,
Slip10RawIndex,
Slip10Result,
slip10CurveFromString,
} from "./slip10";

View File

@ -0,0 +1,28 @@
import { fromHex, toHex } from "@iov/encoding";
import { Keccak256 } from "./keccak";
import keccakVectors from "./testdata/keccak.json";
describe("Keccak256", () => {
it("exists", () => {
expect(Keccak256).toBeTruthy();
});
it("works for empty input", () => {
{
const hash = new Keccak256(new Uint8Array([])).digest();
expect(toHex(hash)).toEqual("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");
}
{
const hash = new Keccak256().digest();
expect(toHex(hash)).toEqual("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");
}
});
it("works for all the Botan test vectors", () => {
// https://github.com/randombit/botan/blob/2.8.0/src/tests/data/hash/keccak.vec#L806
for (const { in: input, out: output } of keccakVectors.keccak256) {
expect(new Keccak256(fromHex(input)).digest()).toEqual(fromHex(output));
}
});
});

View File

@ -0,0 +1,26 @@
import jssha3 from "js-sha3";
import { HashFunction } from "./hash";
export class Keccak256 implements HashFunction {
public readonly blockSize = 512 / 8;
private readonly impl: jssha3.Hasher;
public constructor(firstData?: Uint8Array) {
this.impl = jssha3.keccak256.create();
if (firstData) {
this.update(firstData);
}
}
public update(data: Uint8Array): Keccak256 {
this.impl.update(data);
return this;
}
public digest(): Uint8Array {
return new Uint8Array(this.impl.digest());
}
}

View File

@ -0,0 +1,948 @@
/* tslint:disable:no-bitwise */
import { fromHex, toAscii } from "@iov/encoding";
import {
Argon2id,
Argon2idOptions,
Ed25519,
Ed25519Keypair,
Xchacha20poly1305Ietf,
Xchacha20poly1305IetfCiphertext,
Xchacha20poly1305IetfKey,
Xchacha20poly1305IetfMessage,
Xchacha20poly1305IetfNonce,
} from "./libsodium";
describe("Libsodium", () => {
describe("Argon2id", () => {
// we use relatively week values here to avoid slowing down test execution
it("works for 1 MiB memory and opsLimit = 5", async () => {
const options: Argon2idOptions = {
outputLength: 32,
opsLimit: 5,
memLimitKib: 1024,
};
const salt = toAscii("ABCDEFGHIJKLMNOP");
// echo -n "123" | ./argon2 ABCDEFGHIJKLMNOP -id -v 13 -k 1024 -t 5
await Argon2id.execute("123", salt, options).then((result) =>
expect(result).toEqual(fromHex("3c5d010180ba0cf5b6b858cba23b318e42d33088983c404598599c3b029ecac6")),
);
await Argon2id.execute("!'§$%&/()", salt, options).then((result) =>
expect(result).toEqual(fromHex("b0268bd63015c3d8f866f9be385507b466a9bfc75f271c2c1e97c00bf53224ba")),
);
await Argon2id.execute("ö", salt, options).then((result) =>
expect(result).toEqual(fromHex("b113fc7863dbc87b7d1366c3b468d3864a2473ce46e90ed3641fff87ada561f7")),
);
await Argon2id.execute("😎", salt, options).then((result) =>
expect(result).toEqual(fromHex("dc92db2a69a5607a75472e1581ac0851292ed9a2606f1000f62fa2efc97964e0")),
);
});
it("works for 8 MiB memory and opsLimit = 2", async () => {
const options: Argon2idOptions = {
outputLength: 32,
opsLimit: 2,
memLimitKib: 8 * 1024,
};
const salt = toAscii("ABCDEFGHIJKLMNOP");
// echo -n "123" | ./argon2 ABCDEFGHIJKLMNOP -id -v 13 -k 8192 -t 2
await Argon2id.execute("123", salt, options).then((result) =>
expect(result).toEqual(fromHex("3ee950488d26ce691657b1d753f562139857b61a58f234d6cb0ce84c4cc27328")),
);
await Argon2id.execute("!'§$%&/()", salt, options).then((result) =>
expect(result).toEqual(fromHex("ab410498b44942a28f9d0dde72f0398edf104021ee41bb80412464975817a8a1")),
);
await Argon2id.execute("ö", salt, options).then((result) =>
expect(result).toEqual(fromHex("f80c502bc3fe7b191f6e7e06359955d5dbd23f532548b7058ecbcf77a58e683d")),
);
await Argon2id.execute("😎", salt, options).then((result) =>
expect(result).toEqual(fromHex("474d9445596d2600ba3dc9bbe87d21ed4879e2445cafb10fcb69c5c3ab8ecbc7")),
);
});
it("works for 10 MiB memory and opsLimit = 1", async () => {
const options: Argon2idOptions = {
outputLength: 32,
opsLimit: 1,
memLimitKib: 10 * 1024,
};
const salt = toAscii("ABCDEFGHIJKLMNOP");
// echo -n "123" | ./argon2 ABCDEFGHIJKLMNOP -id -v 13 -k 10240 -t 1
await Argon2id.execute("123", salt, options).then((result) =>
expect(result).toEqual(fromHex("f1832edbd41c209546eafd01f3aae28390de39bc13ff38981c4fc0c1ceaa05e3")),
);
await Argon2id.execute("!'§$%&/()", salt, options).then((result) =>
expect(result).toEqual(fromHex("30c74f405d148fd5c882a0f4238aad9ed85ef255adc102411d22736d68f76f76")),
);
await Argon2id.execute("ö", salt, options).then((result) =>
expect(result).toEqual(fromHex("b80a62f11e7a058194a8ddd80d341c47e0f3b6c41c72ee15b7926788e9963e8f")),
);
await Argon2id.execute("😎", salt, options).then((result) =>
expect(result).toEqual(fromHex("b868aa1875de2edc57bc22de1fc75f9d19f451067c529565f73c61958088b5e9")),
);
});
it("works for different output lengths", async () => {
// echo -n "123" | ./argon2 ABCDEFGHIJKLMNOP -id -v 13 -k 1024 -t 5 -l 16
const salt = toAscii("ABCDEFGHIJKLMNOP");
// libsodium does not support output length < 16
const data = new Map<number, string>();
data.set(16, "01a5ea70c68132b474bdb7f996f55a5a");
data.set(24, "14cf66110e167ebdbea968328bba3f40113077bc359acbe8");
data.set(32, "3c5d010180ba0cf5b6b858cba23b318e42d33088983c404598599c3b029ecac6");
data.set(
48,
"1141dc209086803b06fe0835be055ed592289c9baf9a9db6cd584cd63c2712ca0efc989017a73d6dafb7211a9d09413f",
);
data.set(
96,
"cdb42cddd7190d0ab2453571f644ebf1214177886a51639f23518e1c92d73a196cadddf927bbc8fac59ab3615325642920d7dd73171c7b63f17e1ae173a7b6372bac7525a3230ab1edf6e3ed5c971321186c00a544c79d96bc65263eb5a85d50",
);
data.set(
128,
"c0108a962f59c30b6af298025ad8b8027791cc91f74b96a01a92993e41871e391516e831210bdd3ae20fe501b9c2279d59d42ebc777286088d56a87f30eea04829b9903cb05a468f320e4aced531c7b10631463141a9cbd903dbad4c9b43b2ca0c56ff5a0093179924685e061979e49a593719bb3373152856df922b0007bd9f",
);
data.set(
192,
"092aeca103de921794a97abfd4f0dd1e51de29c62f372e2a984f72d280c12067316db192e47d37ccfd07243bb1ea9a14f7a361a1ab3f5c4be70fb33fea868d9047bdf9ccc52cde1f1cefbb77923b236a690f30f03ebd2ebf72cd47e2acf28627d64b6bd0fe1f8fb2e598017c4892413b83df2ab4c210b51bd730644fa042fee64653a33fbc81dc715c1e05ed4592b71dee1b3fa080b3d332bd96b50c9a1b1c71b7b4e131517dcb63ab628679d20a386f98948d8b9ecf99f32611c9f747abb2d5",
);
data.set(
256,
"94aaf5677f2c0ad13d504bbbfe9b05bbcb8194c8415c119c9d3c170fbcff0e0a42ffa48c11085c6c61f0942d88d32e0da3408099991148db876e29fc5ca80b8425ac0a09987393d7c67fc62ff21fb9713442f3a67690350a871d99bedaecb7c86c357410631c89eedf04c97e386ecb5c0028d53f2d1d6aacba67d2e7bd23792689367dfd777eb28ff4de1753955dcb5f85f34a03684089590927ebd09c251cec4abb7f717ebed22690116938c5ca8404ae7814e9391c4f3c023bafad92b26899f94b6b3dc13ebc7fa693a9233a73f3b2f06b337af3a848b006e8c53bf24b79ca50df8f638304a8671f6949fde9239e0bfa78b5a7ddf424b808a0bfcd2b4fbb20",
);
for (const length of data.keys()) {
const options: Argon2idOptions = {
outputLength: length,
opsLimit: 5,
memLimitKib: 1024,
};
await Argon2id.execute("123", salt, options).then((result) =>
expect(result).toEqual(fromHex(data.get(length)!)),
);
}
});
it("throw for invalid salt lengths", async () => {
const password = "123";
const options: Argon2idOptions = {
outputLength: 32,
opsLimit: 1,
memLimitKib: 10 * 1024,
};
// 8 bytes
await Argon2id.execute(password, fromHex("aabbccddeeff0011"), options)
.then(() => fail("Argon2id with invalid salt length must not resolve"))
.catch((e) => expect(e).toMatch(/invalid salt length/));
// 15 bytes
await Argon2id.execute(password, fromHex("aabbccddeeff001122334455667788"), options)
.then(() => fail("Argon2id with invalid salt length must not resolve"))
.catch((e) => expect(e).toMatch(/invalid salt length/));
// 17 bytes
await Argon2id.execute(password, fromHex("aabbccddeeff00112233445566778899aa"), options)
.then(() => fail("Argon2id with invalid salt length must not resolve"))
.catch((e) => expect(e).toMatch(/invalid salt length/));
// 32 bytes
await Argon2id.execute(
password,
fromHex("aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899"),
options,
)
.then(() => fail("Argon2id with invalid salt length must not resolve"))
.catch((e) => expect(e).toMatch(/invalid salt length/));
});
});
describe("Ed25519Keypair", () => {
it("loads from Libsodium private key", () => {
const keypair = Ed25519Keypair.fromLibsodiumPrivkey(
fromHex(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
),
);
expect(keypair.privkey).toEqual(
fromHex("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
);
expect(keypair.pubkey).toEqual(
fromHex("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
);
});
it("exports Libsodium private key", () => {
const keypair = new Ed25519Keypair(
fromHex("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
fromHex("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
);
expect(keypair.toLibsodiumPrivkey()).toEqual(
fromHex(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
),
);
});
});
describe("Ed25519", () => {
it("exists", () => {
expect(Ed25519).toBeTruthy();
});
it("generates keypairs", async () => {
{
// ok
const seed = fromHex("43a9c17ccbb0e767ea29ce1f10813afde5f1e0a7a504e89b4d2cc2b952b8e0b9");
const keypair = await Ed25519.makeKeypair(seed);
expect(keypair).toBeTruthy();
expect(keypair.pubkey).toBeTruthy();
expect(keypair.privkey).toBeTruthy();
expect(keypair.pubkey.byteLength).toEqual(32);
expect(keypair.privkey.byteLength).toEqual(32);
expect(keypair.privkey).toEqual(seed);
}
{
// Test secret to public conversion (TEST 14 from https://tools.ietf.org/html/rfc8032#section-7.1)
const privkey1 = fromHex("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60");
const pubkey1 = (await Ed25519.makeKeypair(privkey1)).pubkey;
expect(pubkey1).toEqual(fromHex("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"));
const privkey2 = fromHex("4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb");
const pubkey2 = (await Ed25519.makeKeypair(privkey2)).pubkey;
expect(pubkey2).toEqual(fromHex("3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c"));
const privkey3 = fromHex("c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7");
const pubkey3 = (await Ed25519.makeKeypair(privkey3)).pubkey;
expect(pubkey3).toEqual(fromHex("fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025"));
const privkey4 = fromHex("f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5");
const pubkey4 = (await Ed25519.makeKeypair(privkey4)).pubkey;
expect(pubkey4).toEqual(fromHex("278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e"));
}
{
// seed too short
const seed = fromHex("43a9c17ccbb0e767ea29ce1f10813afde5f1e0a7a504e89b4d2cc2b952b8e0");
await Ed25519.makeKeypair(seed)
.then(() => {
fail("promise must not resolve");
})
.catch((error) => {
expect(error.message).toContain("invalid seed length");
});
}
{
// seed too long
const seed = fromHex("43a9c17ccbb0e767ea29ce1f10813afde5f1e0a7a504e89b4d2cc2b952b8e0b9aa");
await Ed25519.makeKeypair(seed)
.then(() => {
fail("promise must not resolve");
})
.catch((error) => {
expect(error.message).toContain("invalid seed length");
});
}
});
it("generates keypairs deterministically", async () => {
const seedA1 = fromHex("43a9c17ccbb0e767ea29ce1f10813afde5f1e0a7a504e89b4d2cc2b952b8e0b9");
const seedA2 = fromHex("43a9c17ccbb0e767ea29ce1f10813afde5f1e0a7a504e89b4d2cc2b952b8e0b9");
const seedB1 = fromHex("c0c42a0276d456ee007faae2cc7d1bc8925dd74983726d548e10da14c3aed12a");
const seedB2 = fromHex("c0c42a0276d456ee007faae2cc7d1bc8925dd74983726d548e10da14c3aed12a");
const keypairA1 = await Ed25519.makeKeypair(seedA1);
const keypairA2 = await Ed25519.makeKeypair(seedA2);
const keypairB1 = await Ed25519.makeKeypair(seedB1);
const keypairB2 = await Ed25519.makeKeypair(seedB2);
expect(keypairA1).toEqual(keypairA2);
expect(keypairB1).toEqual(keypairB2);
expect(keypairA1).not.toEqual(keypairB1);
expect(keypairA2).not.toEqual(keypairB2);
});
it("creates signatures", async () => {
const seed = fromHex("43a9c17ccbb0e767ea29ce1f10813afde5f1e0a7a504e89b4d2cc2b952b8e0b9");
const keypair = await Ed25519.makeKeypair(seed);
const message = new Uint8Array([0x11, 0x22]);
const signature = await Ed25519.createSignature(message, keypair);
expect(signature).toBeTruthy();
expect(signature.byteLength).toEqual(64);
});
it("creates signatures deterministically", async () => {
const seed = fromHex("43a9c17ccbb0e767ea29ce1f10813afde5f1e0a7a504e89b4d2cc2b952b8e0b9");
const keypair = await Ed25519.makeKeypair(seed);
const message = new Uint8Array([0x11, 0x22]);
const signature1 = await Ed25519.createSignature(message, keypair);
const signature2 = await Ed25519.createSignature(message, keypair);
expect(signature1).toEqual(signature2);
});
it("verifies signatures", async () => {
const seed = fromHex("43a9c17ccbb0e767ea29ce1f10813afde5f1e0a7a504e89b4d2cc2b952b8e0b9");
const keypair = await Ed25519.makeKeypair(seed);
const message = new Uint8Array([0x11, 0x22]);
const signature = await Ed25519.createSignature(message, keypair);
{
// valid
const ok = await Ed25519.verifySignature(signature, message, keypair.pubkey);
expect(ok).toEqual(true);
}
{
// message corrupted
const corruptedMessage = message.map((x, i) => (i === 0 ? x ^ 0x01 : x));
const ok = await Ed25519.verifySignature(signature, corruptedMessage, keypair.pubkey);
expect(ok).toEqual(false);
}
{
// signature corrupted
const corruptedSignature = signature.map((x, i) => (i === 0 ? x ^ 0x01 : x));
const ok = await Ed25519.verifySignature(corruptedSignature, message, keypair.pubkey);
expect(ok).toEqual(false);
}
{
// wrong pubkey
const otherSeed = fromHex("91099374790843e29552c3cfa5e9286d6c77e00a2c109aaf3d0a307081314a09");
const wrongPubkey = (await Ed25519.makeKeypair(otherSeed)).pubkey;
const ok = await Ed25519.verifySignature(signature, message, wrongPubkey);
expect(ok).toEqual(false);
}
});
it("works with RFC8032 test vectors", async () => {
{
// TEST 1 from https://tools.ietf.org/html/rfc8032#section-7.1
const keypair = new Ed25519Keypair(
fromHex("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"),
fromHex("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"),
);
const message = fromHex("");
const signature = await Ed25519.createSignature(message, keypair);
expect(signature).toEqual(
fromHex(
"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b",
),
);
const valid = await Ed25519.verifySignature(signature, message, keypair.pubkey);
expect(valid).toEqual(true);
}
{
// TEST 2 from https://tools.ietf.org/html/rfc8032#section-7.1
const keypair = new Ed25519Keypair(
fromHex("4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb"),
fromHex("3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c"),
);
const message = fromHex("72");
const signature = await Ed25519.createSignature(message, keypair);
expect(signature).toEqual(
fromHex(
"92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00",
),
);
const valid = await Ed25519.verifySignature(signature, message, keypair.pubkey);
expect(valid).toEqual(true);
}
{
// TEST 3 from https://tools.ietf.org/html/rfc8032#section-7.1
const keypair = new Ed25519Keypair(
fromHex("c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7"),
fromHex("fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025"),
);
const message = fromHex("af82");
const signature = await Ed25519.createSignature(message, keypair);
expect(signature).toEqual(
fromHex(
"6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a",
),
);
const valid = await Ed25519.verifySignature(signature, message, keypair.pubkey);
expect(valid).toEqual(true);
}
{
// TEST 1024 from https://tools.ietf.org/html/rfc8032#section-7.1
const keypair = new Ed25519Keypair(
fromHex("f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5"),
fromHex("278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e"),
);
const message = fromHex(
"08b8b2b733424243760fe426a4b54908632110a66c2f6591eabd3345e3e4eb98fa6e264bf09efe12ee50f8f54e9f77b1e355f6c50544e23fb1433ddf73be84d879de7c0046dc4996d9e773f4bc9efe5738829adb26c81b37c93a1b270b20329d658675fc6ea534e0810a4432826bf58c941efb65d57a338bbd2e26640f89ffbc1a858efcb8550ee3a5e1998bd177e93a7363c344fe6b199ee5d02e82d522c4feba15452f80288a821a579116ec6dad2b3b310da903401aa62100ab5d1a36553e06203b33890cc9b832f79ef80560ccb9a39ce767967ed628c6ad573cb116dbefefd75499da96bd68a8a97b928a8bbc103b6621fcde2beca1231d206be6cd9ec7aff6f6c94fcd7204ed3455c68c83f4a41da4af2b74ef5c53f1d8ac70bdcb7ed185ce81bd84359d44254d95629e9855a94a7c1958d1f8ada5d0532ed8a5aa3fb2d17ba70eb6248e594e1a2297acbbb39d502f1a8c6eb6f1ce22b3de1a1f40cc24554119a831a9aad6079cad88425de6bde1a9187ebb6092cf67bf2b13fd65f27088d78b7e883c8759d2c4f5c65adb7553878ad575f9fad878e80a0c9ba63bcbcc2732e69485bbc9c90bfbd62481d9089beccf80cfe2df16a2cf65bd92dd597b0707e0917af48bbb75fed413d238f5555a7a569d80c3414a8d0859dc65a46128bab27af87a71314f318c782b23ebfe808b82b0ce26401d2e22f04d83d1255dc51addd3b75a2b1ae0784504df543af8969be3ea7082ff7fc9888c144da2af58429ec96031dbcad3dad9af0dcbaaaf268cb8fcffead94f3c7ca495e056a9b47acdb751fb73e666c6c655ade8297297d07ad1ba5e43f1bca32301651339e22904cc8c42f58c30c04aafdb038dda0847dd988dcda6f3bfd15c4b4c4525004aa06eeff8ca61783aacec57fb3d1f92b0fe2fd1a85f6724517b65e614ad6808d6f6ee34dff7310fdc82aebfd904b01e1dc54b2927094b2db68d6f903b68401adebf5a7e08d78ff4ef5d63653a65040cf9bfd4aca7984a74d37145986780fc0b16ac451649de6188a7dbdf191f64b5fc5e2ab47b57f7f7276cd419c17a3ca8e1b939ae49e488acba6b965610b5480109c8b17b80e1b7b750dfc7598d5d5011fd2dcc5600a32ef5b52a1ecc820e308aa342721aac0943bf6686b64b2579376504ccc493d97e6aed3fb0f9cd71a43dd497f01f17c0e2cb3797aa2a2f256656168e6c496afc5fb93246f6b1116398a346f1a641f3b041e989f7914f90cc2c7fff357876e506b50d334ba77c225bc307ba537152f3f1610e4eafe595f6d9d90d11faa933a15ef1369546868a7f3a45a96768d40fd9d03412c091c6315cf4fde7cb68606937380db2eaaa707b4c4185c32eddcdd306705e4dc1ffc872eeee475a64dfac86aba41c0618983f8741c5ef68d3a101e8a3b8cac60c905c15fc910840b94c00a0b9d0",
);
const signature = await Ed25519.createSignature(message, keypair);
expect(signature).toEqual(
fromHex(
"0aab4c900501b3e24d7cdf4663326a3a87df5e4843b2cbdb67cbf6e460fec350aa5371b1508f9f4528ecea23c436d94b5e8fcd4f681e30a6ac00a9704a188a03",
),
);
const valid = await Ed25519.verifySignature(signature, message, keypair.pubkey);
expect(valid).toEqual(true);
}
});
});
describe("Xchacha20poly1305Ietf", () => {
it("can encrypt and decypt simple data", async () => {
const key = fromHex(
"1324cdddc4b94e625bbabcac862c9429ba011e2184a1ccad60e7c3f6ff4916d8",
) as Xchacha20poly1305IetfKey;
const nonce = fromHex("000000000000000000000000000000000000000000000000") as Xchacha20poly1305IetfNonce;
const originalMessage = new Uint8Array([0x11, 0x22, 0x33, 0x44]) as Xchacha20poly1305IetfMessage;
const ciphertext = await Xchacha20poly1305Ietf.encrypt(originalMessage, key, nonce);
expect(ciphertext).toBeTruthy();
expect(ciphertext.length).toEqual(4 /* message length */ + 16 /* tag length*/);
const decrypted = await Xchacha20poly1305Ietf.decrypt(ciphertext, key, nonce);
expect(decrypted).toBeTruthy();
expect(decrypted).toEqual(originalMessage);
});
it("throws when encrypting with wrong key length", async () => {
const nonce = fromHex("000000000000000000000000000000000000000000000000") as Xchacha20poly1305IetfNonce;
const message = new Uint8Array([]) as Xchacha20poly1305IetfMessage;
{
// empty
const key = fromHex("") as Xchacha20poly1305IetfKey;
await Xchacha20poly1305Ietf.encrypt(message, key, nonce)
.then(() => fail("encryption must not succeed"))
.catch((error) => expect(error).toMatch(/invalid key length/));
}
{
// 31 bytes
const key = fromHex(
"1324cdddc4b94e625bbabcac862c9429ba011e2184a1ccad60e7c3f6ff4916",
) as Xchacha20poly1305IetfKey;
await Xchacha20poly1305Ietf.encrypt(message, key, nonce)
.then(() => fail("encryption must not succeed"))
.catch((error) => expect(error).toMatch(/invalid key length/));
}
{
// 33 bytes
const key = fromHex(
"1324cdddc4b94e625bbabcac862c9429ba011e2184a1ccad60e7c3f6ff4916d8aa",
) as Xchacha20poly1305IetfKey;
await Xchacha20poly1305Ietf.encrypt(message, key, nonce)
.then(() => fail("encryption must not succeed"))
.catch((error) => expect(error).toMatch(/invalid key length/));
}
{
// 64 bytes
const key = fromHex(
"1324cdddc4b94e625bbabcac862c9429ba011e2184a1ccad60e7c3f6ff4916d81324cdddc4b94e625bbabcac862c9429ba011e2184a1ccad60e7c3f6ff4916d8",
) as Xchacha20poly1305IetfKey;
await Xchacha20poly1305Ietf.encrypt(message, key, nonce)
.then(() => fail("encryption must not succeed"))
.catch((error) => expect(error).toMatch(/invalid key length/));
}
});
it("decryption fails with wrong ciphertext/key/nonce", async () => {
const key = fromHex(
"1324cdddc4b94e625bbabcac862c9429ba011e2184a1ccad60e7c3f6ff4916d8",
) as Xchacha20poly1305IetfKey;
const nonce = fromHex("000000000000000000000000000000000000000000000000") as Xchacha20poly1305IetfNonce;
const originalMessage = new Uint8Array([0x11, 0x22, 0x33, 0x44]) as Xchacha20poly1305IetfMessage;
const ciphertext = await Xchacha20poly1305Ietf.encrypt(originalMessage, key, nonce);
expect(ciphertext).toBeTruthy();
expect(ciphertext.length).toEqual(4 /* message length */ + 16 /* tag length*/);
{
// baseline
expect(await Xchacha20poly1305Ietf.decrypt(ciphertext, key, nonce)).toEqual(originalMessage);
}
{
// corrupted ciphertext
const corruptedCiphertext = ciphertext.map((x, i) =>
i === 0 ? x ^ 0x01 : x,
) as Xchacha20poly1305IetfCiphertext;
await Xchacha20poly1305Ietf.decrypt(corruptedCiphertext, key, nonce).then(
() => fail("promise must not resolve"),
(error) => expect(error.message).toMatch(/ciphertext cannot be decrypted using that key/i),
);
}
{
// corrupted key
const corruptedKey = key.map((x, i) => (i === 0 ? x ^ 0x01 : x)) as Xchacha20poly1305IetfKey;
await Xchacha20poly1305Ietf.decrypt(ciphertext, corruptedKey, nonce).then(
() => fail("promise must not resolve"),
(error) => expect(error.message).toMatch(/ciphertext cannot be decrypted using that key/i),
);
}
{
// corrupted nonce
const corruptedNonce = nonce.map((x, i) => (i === 0 ? x ^ 0x01 : x)) as Xchacha20poly1305IetfNonce;
await Xchacha20poly1305Ietf.decrypt(ciphertext, key, corruptedNonce).then(
() => fail("promise must not resolve"),
(error) => expect(error.message).toMatch(/ciphertext cannot be decrypted using that key/i),
);
}
});
it("encrypt conforms to Botan implementation ", async () => {
// Test data generated by
// echo -n "<message>" | ./botan encryption --mode=chacha20poly1305 --iv=000000000000000000000000000000000000000000000000 --ad= --key=0000000000000000000000000000000000000000000000000000000000000000 | xxd -p -c 1000000
const makeMessage = (hex: string): Xchacha20poly1305IetfMessage =>
fromHex(hex) as Xchacha20poly1305IetfMessage;
// Tested messages:
// empty, "a", "ab", "abc", 577 (prime) random bytes, 1024 random bytes
{
// zero key, zero nonce
const key = fromHex(
"0000000000000000000000000000000000000000000000000000000000000000",
) as Xchacha20poly1305IetfKey;
const nonce = fromHex(
"000000000000000000000000000000000000000000000000",
) as Xchacha20poly1305IetfNonce;
expect(await Xchacha20poly1305Ietf.encrypt(makeMessage(""), key, nonce)).toEqual(
fromHex("8f3b945a51906dc8600de9f8962d00e6"),
);
expect(await Xchacha20poly1305Ietf.encrypt(makeMessage("61"), key, nonce)).toEqual(
fromHex("19841f4c8866efab6c6b5329aef9e36752"),
);
expect(await Xchacha20poly1305Ietf.encrypt(makeMessage("6162"), key, nonce)).toEqual(
fromHex("19fcc0cbdcffdf83f822811687b79930dcf7"),
);
expect(await Xchacha20poly1305Ietf.encrypt(makeMessage("616263"), key, nonce)).toEqual(
fromHex("19fcf5f6e7aba23c37d7a59e4c8f061e15f1f6"),
);
expect(
await Xchacha20poly1305Ietf.encrypt(
makeMessage(
"ad376ccca21922a93f532f98bcede77577c5fb857ab280215b7ead7321844f23a42349e9095f394f028f0c731d43db471e39a008a79f5932cb709f2e48743f7a77bd3ee87f93fca8f33ff792daf289e7d4577299f52e0808222311df5e1bdf97c844daa0e9c62123c5df2f3ddc7f873052ee6ee282fa65a7a54d8b91c8e32f628b96c55bdc7db6a43ab156de528dd00e92a27e384addd02fb91cfff0bcf5a64f81738f82b49677661ffedc91af07f3e1ad62aef54daa9980ba4a221a5986c7c29d6867fa9e5db4ff9066da136afdd733b15a404d2dc195885ab0d82e1d1cc5cbae4011ce83146f079702813693483624c53c31775995e96b71def34a7d82c53b9bf4d7f612e89fdeb237f4df28e69f8cb8a3bc04c883915d3416a26c8e23c78efeb1ed658d0d6e7f95bc75c1652014084169569afac77c5b039d7db15c01a5b270b38a1e3d37fb1723a2de1b8ff0333d26ca5514473d3e98f815c894623779653018b7c7abcfe0cdc16f76b773332da2ef861cb7322aebbc64c880b7d805edd416cb231b18d910f0a93924f186fa490feb02ee5d21c17e76c526527951824f0c25dd658731c568d2beeb4619602362d3f07a5458399f68089dd196ef3db907730d48fa1fb4c5ea3dc6e9fc4493dc9662de0d87308be5cdbb8355e2b3a4c483d09ad71d97439950719cf88a6a0d1b616e0960ef2a320f4258a66f83d29f6a8d906427f4d94c0043bfd173a04c06c2e0e48e5c77d32578ee75bf553855f5daa8e376aca79b131bad85a0e78e224babd103ca84e7a3562610409b1c9b52b42548cd6c",
),
key,
nonce,
),
).toEqual(
fromHex(
"d5a9fa454739afd6e6b2dc5d09d9f83d98dd5abb3b3b188c81a33ad048bed75c2aa1a53cc81c02b1d3204506114cc4b58278d4a8fc8ec3916298ac165a94ff84d32320086cd98c0127a3d372112a75dc7629e55ac704dc8fd55be9033409f03254fae947912c0f737e8626f44b63c8dc7d7d3cddf57348b06fe80af61627f9a5a282b8d49cd2adf5191dd7855ffa5b931295773744691ba0b99305de8581184046c074148a8488d021122ddec21482bde85029aecd09907371ae67abc6cfc44655c3b49e7df3e2fac6b73b2803746bd1b4d17b30f5c2dfc31a7c33be5c737330e855d7630155523e3fb85c55a5ff95fd0754f4e9c5f0d88827bb28caa9af2d8f3e52b5d0957dd37d77af5df644bc483a6583d3b1e32d3b5dcbe7a6684ff8c0e44c7250744f705fce4e588f3a2ae65cac0c2b218d455911a2415fd839d3ef288d7447d6e561f4d70e93a739f6890ee3b6edcd3089266bd17d73a0c7fb96a1cdf96b2b7b18f38fe73bb6eb437a37588628ab51894b421137d1032a480643f5b36bb894ae82b8927fa7e87c906b90a3379b3f0aabee718a1e87f31dbfa3f8b1ebb5df9fdf96cd93ecdfcd421ded6299929676fa58ed3034f13474fa278b0a2c62b136e211e7a33e437e24386aae16524adc17557112fdaf484c5e6a038e8513cb2cb157816d6c85406aaae5514ca5b900dd18a90147311a37c82538e1f9adbad1bdc993264475cc54f61e54e048d63190cf138f3cdb5faf52e954d1caa8081a614beb44b2d9bea7e30d51513b4c3fd77e1cfd84e58588f733116c9be6b15cd4f72e3d602c2fe77f7880b91f62197c547df9d2",
),
);
expect(
await Xchacha20poly1305Ietf.encrypt(
makeMessage(
"09b7b8c14569057a7c4cdc611b839bda48123c1aba86a3e1ac6ca981c7a8c1885c9d95c661d3ff530aaf9f07f8233049ebe92297fb3a708352c59fa703087011117215142f10843b976862579be7cea3d8112d3ae69f58ed9d9684da5c51123a73e5b08627ef83b1f8feb3ef91ca8f1be327468e0cc2b3bffc1a8ef1291cedf80ff8320b90f0d17fb623c447e65f4fa48a17327d427d1aa6bb445c61dda9cc9b5c1611675e618fe6b79ab9bf045cfe0b1295aa72ff1c73d6641fb942a831506c0d268c628abff8925c011d222c443b73f18e994077f1c7a893123ed400cf2f11b8fa144c0d8fc5afcd2960281f067f1329cd4abf15a1a701762121b1e103db9538f989443fbc824d39fab22b622ec98632e957adbb39dd956f31b42af3629d8fcd461c9d4519105c0f308c7c45888583832c3c659b17b0733bc7257a9c00899a4ba9933c211579480e5c30d6837241a59d044280df466d55d0b46dcfa2db4c809023d77c2503ed3afd82489c98b0949baac0a00403af770e65c45607b615912eaf7c727b15cf976e742c1cb75fc160966dda4504edc7322a9479cb2617286c85b1412b9ffc067be3f9d2fd49568a29fe40115cf76de6dd7cc3f7e833bafbdffc39097150fe14960582a39d10102aa86718f59b102c89441806c80acfe300ea415be03162e729bd92a75b4e18a33470409034bc7fc7e9fb6c4823d6bfc77d75046c0927922dae805c7ced7a4a6fb4f46a15ee6b145e0dae56e6480ac726f7ccd5296fb2c82b5ebc2239eac2d81cea4fa789e0187134a270b6e74fc7932983e6e993e391ecc0dc9cb5963a644c6ece2eba6564533a330861eaac6120f84959e5b41ccbbe2255db17106a9955a94964ba07c5b1d1a0159f3f1e8f76f65181c17e3d616398df19fe6d8625922f5a0fd384b3238761a2003e5b5101c0c795c48ceb3ec72d9f7f5cf42df449075153f642f0ffb01414a5f6c7985855d752fd7c5a71d8eead5cf51681bb67adf267d5fb290933dff2aa25eff736616cb6437738b0121e4d14bdaf5e7e8009631ecb1f30bf574c7e3c6a79cd571dd08a38054cec4dce6fb7805a0fc8c729d75f1233bc85149e0bd1d13895855f71f2f57eda6101ed274c9e41468c28b97f5f969cc849ef5027c5afd57c24ae85e16e9659cd3954c9535d3218b20f191982df2dfad4fd13d0e33b24d83274ed6066814d70d41f84874c6072b9515aa5d2f0716d851a84533fd43f21c902fb54320e04fd0b7d05d169137482f82d46b275672a7c8aa51e4546ab494f0aa2dee67d5ea2476c0a7463af609727d52ac54054ee7f454f9420d5e9f5e28357c2b0745a466087d505919ae4923586649b5b3e845929c78ff4c22c6d5f8a31eb8eedb3351e6d5f9ce68dbb318a5d62bc116bdea32a77c46492a3ca52f06a00a14420daee72b97673ef6d4a3bc35a",
),
key,
nonce,
),
).toEqual(
fromHex(
"71292e48a0498805a5ad2fa4aeb78492a70a9d24fb0f3b4c76b13e22ae9259f7d21f7913a090c4addb00d672f42c2fbb77a85637a02bea20fb2dac9f11e8b0efb5ec0bf43c5af49243f446b7503f32987a6fbaf9d4b58c6a6aee7c0636433d9fef5b83615f05ade143a7ba2606d6c0f7ccb414b17b4b9ea836bf0f96f7d83b3f26ec4f84d05fca2e958f451ceb28c4390a203b724cc9d129bbcba64fe4dd72949ba5eaf160737050897648f0694f8f5757a72d297fbf7a25affbfcf3377853e8c58d5f066911ae970ad0fc1945cd8791f405a23daff28de3d3ded54441a099eafeefd2e18fcef8966593bd4b29b1dccaeba58f2189c496e22044fa31352e33219d5feb62b829ceeefc621b020e741e30efc938189097779590c0b02e32b99ae57f85a18c876421edd4d476870a4ecd27ce6e4b722489dd8a790580f213ee04a54f5dcfc77dd65551be59d73b858c912e5603271dbe1082b05b0162a0564df81ccb101ba37d43eacc8a067d51dcdb3f11ee1735f87394ab6302269eb62de5cf910123ffe2b584f8393569a82d49981e02b9d200b7bd8c52dba24226fcbe1bc83c4b03918e0050ffee8a7ba6bd5430d9bbc6915042644d44402adc59578d6eba3e02a39aa8e9ef3f4660720bfa95a474d9d1ad6d325ac3c1efdbf7ebf2c2d7a2bd7060ad98c835ad899146953e0b9611f381fd5212c4fc8efccb74b494f5ad2929c1bdf50f14629715b3ca3a4ebf478441883d201c24da12ca0dccf83adb37057db487a78a86e2f2aac85c77b66896e0b8bee01a57ea7353e710c8ba70c172590ac2843520e26a2bea555abb2e08be3ee158c73325f2d28053c8adf0ac529051b729a15ef40033d9308208768db4f3af4470304de164d7cf7db5fbd80bd82007bb66703d5b5d8d3716579399736fd81e1e4b0f8df407c5aff52dcc7d4b9104203a45f32888921398704f74f022346778e4feec6acdfea405d24a3cfba770fe230dc17eb0f50f6290b1f3ddfcf9d3a0171fc914da5281f9e5d955a8c75ba3b472b4e724bfe11aa2b720c4c99b6f9cd40eaf957dc39941025a23cff213892a03c840555d984ab370a74ce8fee6fefaf9bc80e390d5cc258a277974304bcbb83a2eebcfe080ef886640685a7174862b89133e35cbb42819d9949d5077aafc752be3fbda1405193d2be0733e91b6a0b45388a039e5fe50d5bcf98472c8be6217fe5595c3198d5db6c1856d9db97c484182cf1071b3473beb4f9df03efbbee998dfa49da014fff5b996d93bd8a79b2a3931666a49eefc13cbbf56185e2ea9924ff1ea632ad72859984397529de6b6c500f5fab65813810606f4449afcf57a59f6a7fd7140bb8a59b87fe0d8de65398938c0b7a8697eda50c175296c3a82a93de00a40f623d62c02adff2ca8ccdab1357a009d7e7f6f461152c28fe917fc56d94fd66546c9691ed640692515e4bdd1d7ae0a2d1c",
),
);
}
{
// zero key, random nonce
const key = fromHex(
"0000000000000000000000000000000000000000000000000000000000000000",
) as Xchacha20poly1305IetfKey;
const nonce = fromHex(
"623ac6e73c2c00951bf4c7490e4692f8e30f5f8c1c4196da",
) as Xchacha20poly1305IetfNonce;
expect(await Xchacha20poly1305Ietf.encrypt(makeMessage(""), key, nonce)).toEqual(
fromHex("ee5f3601ce18d227df5d5a8b2ddb05e3"),
);
expect(await Xchacha20poly1305Ietf.encrypt(makeMessage("61"), key, nonce)).toEqual(
fromHex("0c5a3d0c311a2a9598ce968549a8d6a6f9"),
);
expect(await Xchacha20poly1305Ietf.encrypt(makeMessage("6162"), key, nonce)).toEqual(
fromHex("0ce46df742a0423e9954a903c9f5bf54412e"),
);
expect(await Xchacha20poly1305Ietf.encrypt(makeMessage("616263"), key, nonce)).toEqual(
fromHex("0ce4be1fd7c793ac56c6d734955514bd8391a3"),
);
expect(
await Xchacha20poly1305Ietf.encrypt(
makeMessage(
"ad376ccca21922a93f532f98bcede77577c5fb857ab280215b7ead7321844f23a42349e9095f394f028f0c731d43db471e39a008a79f5932cb709f2e48743f7a77bd3ee87f93fca8f33ff792daf289e7d4577299f52e0808222311df5e1bdf97c844daa0e9c62123c5df2f3ddc7f873052ee6ee282fa65a7a54d8b91c8e32f628b96c55bdc7db6a43ab156de528dd00e92a27e384addd02fb91cfff0bcf5a64f81738f82b49677661ffedc91af07f3e1ad62aef54daa9980ba4a221a5986c7c29d6867fa9e5db4ff9066da136afdd733b15a404d2dc195885ab0d82e1d1cc5cbae4011ce83146f079702813693483624c53c31775995e96b71def34a7d82c53b9bf4d7f612e89fdeb237f4df28e69f8cb8a3bc04c883915d3416a26c8e23c78efeb1ed658d0d6e7f95bc75c1652014084169569afac77c5b039d7db15c01a5b270b38a1e3d37fb1723a2de1b8ff0333d26ca5514473d3e98f815c894623779653018b7c7abcfe0cdc16f76b773332da2ef861cb7322aebbc64c880b7d805edd416cb231b18d910f0a93924f186fa490feb02ee5d21c17e76c526527951824f0c25dd658731c568d2beeb4619602362d3f07a5458399f68089dd196ef3db907730d48fa1fb4c5ea3dc6e9fc4493dc9662de0d87308be5cdbb8355e2b3a4c483d09ad71d97439950719cf88a6a0d1b616e0960ef2a320f4258a66f83d29f6a8d906427f4d94c0043bfd173a04c06c2e0e48e5c77d32578ee75bf553855f5daa8e376aca79b131bad85a0e78e224babd103ca84e7a3562610409b1c9b52b42548cd6c",
),
key,
nonce,
),
).toEqual(
fromHex(
"c0b1b1d9acd58a7e8bc324343f0f9bf4eca174c4f9056b7e2dda6f185ea7587d032b3b202df5db9ae73ace5f994b366cb82d05175b6af3f7b93f53a8bd3bf99f69dae2c5bdcdf9bb74c21706687956db8d9879a833ebaf7d70d74b52c750026f5dd14dc03c6376848443f0dc18fa39d2acfaaf41276789621aa6b844af8a3e00e0dc807ec113343a9a14c4e205064f113af0aaa01f00a0cee37e8f0f54d9818248f60f9f1bd2ff5c615f7ca72c6d71085b0ef79f51d8978cabf6c6dc31d526aa0a692748882bda4e2684014b7bdd2e598b1fed56b9ec4038a10a32be2c9e1b88d99d76d17646326ae4a8513bf030dc19af0e83a69219231dec942d72abd963a3e2fc854494f70539e5a92076f32431334bdba71aa81bb96fad0daa4e64a99f0d3bbc781554ba52bdac4690216faff2d25c229d2d69ae16da7d3a8adbc0424a3a923e562d3064dcc78155b4223a73c2587f4bc77c0ebe9592264c74437d95e976f11aef5e26db2ae7b54a4034301a21bce58aee4f51b8c4d09b2b188121c012f5a476580573b497cbf26f96c03dd22f2128f1e8e0669857a87d6a60741cef746b0af6c56adaff445f7736c65265d3a38305421b0b9e6161dcc2eac86f1d97a253c9df8594cf654e1c85ac6a323935b9a30911422f2514bc62aca52dacc4fea99fb2f28c71ca79737d8968b5d420143f4d313d3affda4a584877f08d59f7b83c7a16045c6d9d39ae696905139a322d02be29ce99a01493f9e3f38fbcb48b7c66d30c8b78c703eb1079eb5077f07ef5ecb281d15bedd35db5decd343c85ebe5471fd40004c24d3ad31878c5e92c98143728d2",
),
);
expect(
await Xchacha20poly1305Ietf.encrypt(
makeMessage(
"09b7b8c14569057a7c4cdc611b839bda48123c1aba86a3e1ac6ca981c7a8c1885c9d95c661d3ff530aaf9f07f8233049ebe92297fb3a708352c59fa703087011117215142f10843b976862579be7cea3d8112d3ae69f58ed9d9684da5c51123a73e5b08627ef83b1f8feb3ef91ca8f1be327468e0cc2b3bffc1a8ef1291cedf80ff8320b90f0d17fb623c447e65f4fa48a17327d427d1aa6bb445c61dda9cc9b5c1611675e618fe6b79ab9bf045cfe0b1295aa72ff1c73d6641fb942a831506c0d268c628abff8925c011d222c443b73f18e994077f1c7a893123ed400cf2f11b8fa144c0d8fc5afcd2960281f067f1329cd4abf15a1a701762121b1e103db9538f989443fbc824d39fab22b622ec98632e957adbb39dd956f31b42af3629d8fcd461c9d4519105c0f308c7c45888583832c3c659b17b0733bc7257a9c00899a4ba9933c211579480e5c30d6837241a59d044280df466d55d0b46dcfa2db4c809023d77c2503ed3afd82489c98b0949baac0a00403af770e65c45607b615912eaf7c727b15cf976e742c1cb75fc160966dda4504edc7322a9479cb2617286c85b1412b9ffc067be3f9d2fd49568a29fe40115cf76de6dd7cc3f7e833bafbdffc39097150fe14960582a39d10102aa86718f59b102c89441806c80acfe300ea415be03162e729bd92a75b4e18a33470409034bc7fc7e9fb6c4823d6bfc77d75046c0927922dae805c7ced7a4a6fb4f46a15ee6b145e0dae56e6480ac726f7ccd5296fb2c82b5ebc2239eac2d81cea4fa789e0187134a270b6e74fc7932983e6e993e391ecc0dc9cb5963a644c6ece2eba6564533a330861eaac6120f84959e5b41ccbbe2255db17106a9955a94964ba07c5b1d1a0159f3f1e8f76f65181c17e3d616398df19fe6d8625922f5a0fd384b3238761a2003e5b5101c0c795c48ceb3ec72d9f7f5cf42df449075153f642f0ffb01414a5f6c7985855d752fd7c5a71d8eead5cf51681bb67adf267d5fb290933dff2aa25eff736616cb6437738b0121e4d14bdaf5e7e8009631ecb1f30bf574c7e3c6a79cd571dd08a38054cec4dce6fb7805a0fc8c729d75f1233bc85149e0bd1d13895855f71f2f57eda6101ed274c9e41468c28b97f5f969cc849ef5027c5afd57c24ae85e16e9659cd3954c9535d3218b20f191982df2dfad4fd13d0e33b24d83274ed6066814d70d41f84874c6072b9515aa5d2f0716d851a84533fd43f21c902fb54320e04fd0b7d05d169137482f82d46b275672a7c8aa51e4546ab494f0aa2dee67d5ea2476c0a7463af609727d52ac54054ee7f454f9420d5e9f5e28357c2b0745a466087d505919ae4923586649b5b3e845929c78ff4c22c6d5f8a31eb8eedb3351e6d5f9ce68dbb318a5d62bc116bdea32a77c46492a3ca52f06a00a14420daee72b97673ef6d4a3bc35a",
),
key,
nonce,
),
).toEqual(
fromHex(
"643165d44ba5adadc8dcd7cd9861e75bd376b35b393148bedac86beab88bd6d6fb95e70f45791d86ef1a5d2b7c2bdd624dfd878807cfda46208a5321f647b6f40f15c939ed4e8128109582c3296c119f81de260b205aff98cf62de57c51acfc2e67027e6f24ad416b9626c0e554f31f91d33872da95f5f7a43f1bd244e75fc9a64b2772e8d9e53e11686567bb1d4d0bb2245e6e517a06a47e1262c9e3585eb569593917af12507dcc93b198987367ce2e4f9f318e36e7dda75a35d84c062b1049a27ccd09cc99623eae3c67a3d64c219cbcb345be3dc121868a8d444314df152cf277353f8dd98c2be83b0257c7e952e43fff86ede2d6d77eb6bff8937587d0d41f1dbf6b9a318aa6e646682b9ec6739c1914cb3dba1f5a7f62abc0819e8c50c084b89ed9cae2c9e36ca699c4f0763599e67f7d2087edaf24560d21000436612a9244f0f2c465e98acab5aef36f1b0c0c485d0e896c5c65f0eedd118bd79dc9351218fe5a817271089a77e1fdb999885a0cc52fc603d58629a27ce314fd06e0f1dc109657ea210552f7aae86e4e906b8ae2943b9aa9e1bf42c35f92b5a4557e29e6a8b72173c576e300f7d02537ae8aeb52913a4ca18d4a89cccb6b39ad57adcfd9e0edb85b43224c1e60b66bac387a6cfe95e0f827835c12938c5d0833ac00e73c5a0846ec99e9eb2cb71a68e3b2e63a86969aa2face17c99bcd834afafc4ee1e2a8f26fc976d8ac49bc99c5b5b1630b27c85676fe6b9c0aa928e26585102e553486d943bae01de725d3b0a29b47216c2b5a43fb1d9d528b16760447643e93b2bfce4247dce0e9167c26d776ce5db7044411f3d1d4f51c400f0b14f65f68f395af75568955bbd1662b9e85c2b9651d0296be568d410af43d78a7202d357f7093644bbab734db1f8e2c4eb8f950b4e20122c0f079d3980539e8a7a22b0444d8e548f17a21c5cd01a1fdf237cd55e6031921488598919c6ded91d33169240d1dd5684de3368c0148cf82bbf9587a118ce770e5371b34b97a50cfeb2f685142dfa609225f6ed6c754c567eba859d794e89bd3b88f768bed757ecd2fbee8216816d4b15b6f39fb59b583353478372b6e395d845b57aef27dcdf40ee5e906e62fd6b5ae4ec071d95a3d9dc688b7aa1fe9bed64070e8881cbb7e10d37da67306d8c9c6f71dc175f055bba88271dfdaf0a88a4c5460fcf83c0b9ef58e75ee18a75bee1ddd3de18d130bc90327e2338cf2f640256d02ecffe9fa113c02c168c97845df2549c62504d1e6054d80caa18821222dbf73707816c7513c92574a8257bd84507b8706f9eea99d02910c3a7e5d4ed9d37d5f22888288e158a0c665bc5be491c514d763851e22a4852c8b8178cac89745949f264db4d4b3ac711ddc3b2e556aa10d957e60734e45b3f5b84a800752da82f36d820474126703c734515123b1054723a741d2840a8e935690158c47c8aa185",
),
);
}
{
// random key, zero nonce
const key = fromHex(
"1e1d2cd50e63f38a81275236f5ccc04c06dbe7a9b050eb1e38cb196c4125bbe2",
) as Xchacha20poly1305IetfKey;
const nonce = fromHex(
"000000000000000000000000000000000000000000000000",
) as Xchacha20poly1305IetfNonce;
expect(await Xchacha20poly1305Ietf.encrypt(makeMessage(""), key, nonce)).toEqual(
fromHex("6142f36622e4becf1fad773123d067e8"),
);
expect(await Xchacha20poly1305Ietf.encrypt(makeMessage("61"), key, nonce)).toEqual(
fromHex("94f1c39995a77dd01265112ea5f5c3470c"),
);
expect(await Xchacha20poly1305Ietf.encrypt(makeMessage("6162"), key, nonce)).toEqual(
fromHex("94f1ba350b17e0b580bb7541812ffcaf4eda"),
);
expect(await Xchacha20poly1305Ietf.encrypt(makeMessage("616263"), key, nonce)).toEqual(
fromHex("94f1a17b016c87a9a1135b57acc427edea2b6e"),
);
expect(
await Xchacha20poly1305Ietf.encrypt(
makeMessage(
"ad376ccca21922a93f532f98bcede77577c5fb857ab280215b7ead7321844f23a42349e9095f394f028f0c731d43db471e39a008a79f5932cb709f2e48743f7a77bd3ee87f93fca8f33ff792daf289e7d4577299f52e0808222311df5e1bdf97c844daa0e9c62123c5df2f3ddc7f873052ee6ee282fa65a7a54d8b91c8e32f628b96c55bdc7db6a43ab156de528dd00e92a27e384addd02fb91cfff0bcf5a64f81738f82b49677661ffedc91af07f3e1ad62aef54daa9980ba4a221a5986c7c29d6867fa9e5db4ff9066da136afdd733b15a404d2dc195885ab0d82e1d1cc5cbae4011ce83146f079702813693483624c53c31775995e96b71def34a7d82c53b9bf4d7f612e89fdeb237f4df28e69f8cb8a3bc04c883915d3416a26c8e23c78efeb1ed658d0d6e7f95bc75c1652014084169569afac77c5b039d7db15c01a5b270b38a1e3d37fb1723a2de1b8ff0333d26ca5514473d3e98f815c894623779653018b7c7abcfe0cdc16f76b773332da2ef861cb7322aebbc64c880b7d805edd416cb231b18d910f0a93924f186fa490feb02ee5d21c17e76c526527951824f0c25dd658731c568d2beeb4619602362d3f07a5458399f68089dd196ef3db907730d48fa1fb4c5ea3dc6e9fc4493dc9662de0d87308be5cdbb8355e2b3a4c483d09ad71d97439950719cf88a6a0d1b616e0960ef2a320f4258a66f83d29f6a8d906427f4d94c0043bfd173a04c06c2e0e48e5c77d32578ee75bf553855f5daa8e376aca79b131bad85a0e78e224babd103ca84e7a3562610409b1c9b52b42548cd6c",
),
key,
nonce,
),
).toEqual(
fromHex(
"58a4ae64d149c7fe648d93f0a293055b08c72302a457aa99d6892b258b9d5bcc59332df250f735d98ff32351907738efbc21ef0412e9a00ae7f2fecedcd6ae52abe266f1e17990ce2f958bfdd6bc32ca70f0734788d44318d7fc86c687059da9ac6b859c5286b3941b2847891ea87a56e81a3d7d543dea2c984492ff8345ab8f52ec69d0ce1392dc42f1df875b9170b198b04ee4d9f7cfd430c145066a9b4592b828d5785e600b655982797557827cfc8c9e2bd55df3fc956f5199c59974dc2e1550361487181516f322b44d7d1649dd072932e0225bf929e1c6d905055051ccc779edbdd9c8643f318a6f564197e3187b27f84b4e153317e860542a3bbe541738c9e6115da21bd23cc40a8ef8b205f824fd4bcf4abfd88cfb4436a4e651c39a14412fe68da9a1a1abf62674ff001e31a6d2b435828321686e1b135a51d65e8117dcf31aab6b8120ebcc6a8c048d47685c9a0fd58ee629b9a7ee73734082da893484ef9de37a291881ff808192d0726c41f1b04fbd687331e1e8a71cc1f84186556c6bc81545413c52a1cc21e42675929369340f93d55396ce25e1fdd819015e8295d529b85032f21aeefadfb1aaac0e1d3e04b5fba0150a9b9796a628e5627713443aed99f480cd7495b5eb5a4c65d2b9bad43fce9b7d24a18cf3e8e983942ed476f2deb38aa3ff38f0b84a3432e5fb25647a3ca0d2a563e8655f93cd1be4f2672375740a469b75dcdd66fc5f3645e751caadfcf11ec3efdf1015160b3035134b44acc6fef3b5fd00d9c7e713590d1021ab6c2ced3316a813a10969e3da876fe152b8253ddd4f853fdc9954e3f3349de7",
),
);
expect(
await Xchacha20poly1305Ietf.encrypt(
makeMessage(
"09b7b8c14569057a7c4cdc611b839bda48123c1aba86a3e1ac6ca981c7a8c1885c9d95c661d3ff530aaf9f07f8233049ebe92297fb3a708352c59fa703087011117215142f10843b976862579be7cea3d8112d3ae69f58ed9d9684da5c51123a73e5b08627ef83b1f8feb3ef91ca8f1be327468e0cc2b3bffc1a8ef1291cedf80ff8320b90f0d17fb623c447e65f4fa48a17327d427d1aa6bb445c61dda9cc9b5c1611675e618fe6b79ab9bf045cfe0b1295aa72ff1c73d6641fb942a831506c0d268c628abff8925c011d222c443b73f18e994077f1c7a893123ed400cf2f11b8fa144c0d8fc5afcd2960281f067f1329cd4abf15a1a701762121b1e103db9538f989443fbc824d39fab22b622ec98632e957adbb39dd956f31b42af3629d8fcd461c9d4519105c0f308c7c45888583832c3c659b17b0733bc7257a9c00899a4ba9933c211579480e5c30d6837241a59d044280df466d55d0b46dcfa2db4c809023d77c2503ed3afd82489c98b0949baac0a00403af770e65c45607b615912eaf7c727b15cf976e742c1cb75fc160966dda4504edc7322a9479cb2617286c85b1412b9ffc067be3f9d2fd49568a29fe40115cf76de6dd7cc3f7e833bafbdffc39097150fe14960582a39d10102aa86718f59b102c89441806c80acfe300ea415be03162e729bd92a75b4e18a33470409034bc7fc7e9fb6c4823d6bfc77d75046c0927922dae805c7ced7a4a6fb4f46a15ee6b145e0dae56e6480ac726f7ccd5296fb2c82b5ebc2239eac2d81cea4fa789e0187134a270b6e74fc7932983e6e993e391ecc0dc9cb5963a644c6ece2eba6564533a330861eaac6120f84959e5b41ccbbe2255db17106a9955a94964ba07c5b1d1a0159f3f1e8f76f65181c17e3d616398df19fe6d8625922f5a0fd384b3238761a2003e5b5101c0c795c48ceb3ec72d9f7f5cf42df449075153f642f0ffb01414a5f6c7985855d752fd7c5a71d8eead5cf51681bb67adf267d5fb290933dff2aa25eff736616cb6437738b0121e4d14bdaf5e7e8009631ecb1f30bf574c7e3c6a79cd571dd08a38054cec4dce6fb7805a0fc8c729d75f1233bc85149e0bd1d13895855f71f2f57eda6101ed274c9e41468c28b97f5f969cc849ef5027c5afd57c24ae85e16e9659cd3954c9535d3218b20f191982df2dfad4fd13d0e33b24d83274ed6066814d70d41f84874c6072b9515aa5d2f0716d851a84533fd43f21c902fb54320e04fd0b7d05d169137482f82d46b275672a7c8aa51e4546ab494f0aa2dee67d5ea2476c0a7463af609727d52ac54054ee7f454f9420d5e9f5e28357c2b0745a466087d505919ae4923586649b5b3e845929c78ff4c22c6d5f8a31eb8eedb3351e6d5f9ce68dbb318a5d62bc116bdea32a77c46492a3ca52f06a00a14420daee72b97673ef6d4a3bc35a",
),
key,
nonce,
),
).toEqual(
fromHex(
"fc247a693639e02d2792600905fd79f43710e49d64638959219b2fd76db1d567a18df1dd387bf3c587d3b0257517d3e149f16d9b4e4c89bb7e47fe4797aae139cd2d4d0db1fae85d4bc21e3897a9758e7cb62ce49b6513fd684913c3854f500417caefba9caf11062609db5b531d727d59d31511da053c34c113979f62ba6915d6829e80829ef507ce634d1eef43ef1b800502a1d157055d3299e6970bc72f46654d4b9db497f3e5f1e61c5bfcd9711633692f52ef4516c3b104029d68c34b80851edd8c93fa597b3f45737c3bafa59d47fdebed786bab0928643fff1883bb16d1c3e83f5753ce976ba18e48cdd9aa2f97d6838302217d7def9f86d1a73f4ab99bc4b8a370f60641b7094c7ab27a53f2aeb7a06639059444a06320e29b10999b27b6de1e45bddf82317adfc9dfa88fba6497decae353ed4056414b9191d772a92cc6ea38b749037fc6328441080f35f0e7541841169d7a748f4fd628806eef6c94bf8f266db624efbd12beaa7953cb5504b70cfc8cedef83e0e471acafe83d7cecdb3aa81853c6a28fb4f4673d1d5c0b15b19f565fd31fca9f7a78a29eb322d716099b31759321c35dd7418f8703e723ad550c1aafd9a07ec5b1e87aafa7baf82705b1a2d325fcf530dfd4bfd9ba5bd77f42c81f69f7f48724111b94ae47fdbf1541de2b173a4e1c03537c389a1df4d5bc30296955341c5706290afe950c1c666f0da63f6be858967143bcfa36405169ca78b13b8a6b83cc860d2784d81d51251487b995c6b6a45a99d48b1d441893b462cf93fe8fb7765e6ff255a87e7c294b1e69e2483a56586d5e422bf9095e7b2aac6305ae4112024e02966b6d6494c4252c44f2ff807b27597891f95b2c2781444ff402155f2e34477dca2d5926934bba819561cdf7fa3e6e1009db51a4d99f2c364854b20b5e4f7713a409ebd31e16e0f6505158fdd2406a085c66ec2af17aa2e197f22094cf6167168243b5e73e45286b1fcc8b66f28058a0a6b2554f641780c4e31d8b6f47f34e0e978ecd3127ae148561a8c18c1f36b5349ae74fddc3caa4ba88c31a12a42f918c10f0bf56defcc8bcfb0975c2d550160d381c9b323d87d927f490abd1c992e490aa0c069437c34998f8b0e8e6f97ea010195387d512d2bef913fcfee4c088a36b0e03c5e14296cc991c2efaf9f27f2c44aa5734defac0138de2049b03ace45b3f4db3865be3157924ede7ee0e381fefb4c418363326f5c0d72ca1e9b9bd2cac73e3e793b1afc8101f3cd57aa7edb9d49777f447637d423821cde426e9f56083d0fd0c39816d08434f04ddc1eeeeb9fe6bc4c985d33c66f64a221f63f2d2f9fc2fa68ccd8b797ca84fbb92fda61093490492bdceb5796de563592f97531a15edb2b45b15008207ebb27fc4d9dfef4014e3f347af35c1e5686df24491b88477c90180a89d845299097a54f97d71ac3b07e6722602dd823434",
),
);
}
{
// random key, random nonce
const key = fromHex(
"1e1d2cd50e63f38a81275236f5ccc04c06dbe7a9b050eb1e38cb196c4125bbe2",
) as Xchacha20poly1305IetfKey;
const nonce = fromHex(
"623ac6e73c2c00951bf4c7490e4692f8e30f5f8c1c4196da",
) as Xchacha20poly1305IetfNonce;
expect(await Xchacha20poly1305Ietf.encrypt(makeMessage(""), key, nonce)).toEqual(
fromHex("7e51936afe05c64a87fcd64cf811f22d"),
);
expect(await Xchacha20poly1305Ietf.encrypt(makeMessage("61"), key, nonce)).toEqual(
fromHex("fb5530e69e505cb1d3d5344f354e8e96d5"),
);
expect(await Xchacha20poly1305Ietf.encrypt(makeMessage("6162"), key, nonce)).toEqual(
fromHex("fb4927d0dbb75b8d866c0f891195be725cfe"),
);
expect(await Xchacha20poly1305Ietf.encrypt(makeMessage("616263"), key, nonce)).toEqual(
fromHex("fb4984e6af4f702a738693d59104c6690da890"),
);
expect(
await Xchacha20poly1305Ietf.encrypt(
makeMessage(
"ad376ccca21922a93f532f98bcede77577c5fb857ab280215b7ead7321844f23a42349e9095f394f028f0c731d43db471e39a008a79f5932cb709f2e48743f7a77bd3ee87f93fca8f33ff792daf289e7d4577299f52e0808222311df5e1bdf97c844daa0e9c62123c5df2f3ddc7f873052ee6ee282fa65a7a54d8b91c8e32f628b96c55bdc7db6a43ab156de528dd00e92a27e384addd02fb91cfff0bcf5a64f81738f82b49677661ffedc91af07f3e1ad62aef54daa9980ba4a221a5986c7c29d6867fa9e5db4ff9066da136afdd733b15a404d2dc195885ab0d82e1d1cc5cbae4011ce83146f079702813693483624c53c31775995e96b71def34a7d82c53b9bf4d7f612e89fdeb237f4df28e69f8cb8a3bc04c883915d3416a26c8e23c78efeb1ed658d0d6e7f95bc75c1652014084169569afac77c5b039d7db15c01a5b270b38a1e3d37fb1723a2de1b8ff0333d26ca5514473d3e98f815c894623779653018b7c7abcfe0cdc16f76b773332da2ef861cb7322aebbc64c880b7d805edd416cb231b18d910f0a93924f186fa490feb02ee5d21c17e76c526527951824f0c25dd658731c568d2beeb4619602362d3f07a5458399f68089dd196ef3db907730d48fa1fb4c5ea3dc6e9fc4493dc9662de0d87308be5cdbb8355e2b3a4c483d09ad71d97439950719cf88a6a0d1b616e0960ef2a320f4258a66f83d29f6a8d906427f4d94c0043bfd173a04c06c2e0e48e5c77d32578ee75bf553855f5daa8e376aca79b131bad85a0e78e224babd103ca84e7a3562610409b1c9b52b42548cd6c",
),
key,
nonce,
),
).toEqual(
fromHex(
"371c8bc350aca6a7aeb8eed66276f2a11f4f48147aa0871bc693603beeec2c68fc89dff91dcc2b7799960f40c3a8cb2c9d98d0c8fb22e3823f066e0f73e95c1420a386aec2060c21e8cd2cd79f3c14cc6af7d249acd2cd7945318b0184fdacb6eba33eb7e4498431c9ed484f24c1c4ddd8a8f3ed0f82d01e0afe595ef389c3dcad0688443cdebb3bbc0f8b34e8b6455012c77fed003d433e267ac0aea55618ef95ba3726484f6982900bc12304c2dcd89101301a9dc8eb350dc24532c7f46d9f34565d6abb6546c2fd8c74a1fd2a715b81d54cc93bd12b315ec070c81cfa072472a5b9e89ec545a0d1895e112abca47b3d2a610f1d06ccf80c04529a01794cc5498fe6ecf48cdb97f8b591b1688d6706b72cbc480f463016f177b7d2b941d1e48ac7a735e7f84fcf42004aa2c37941930a4f5b98ab3c68f32667f4f814c12c0e17d2c7599b2d248d51e3e7dae9f1df5441f1290bc26e2e7043ead46958c2abbc76ed2bc8e01befbd9a4d55af491d3187815b90cb1dc6016b5e15698a5b6cf41166d291118f19a25e6fdce0a04a1ecfa4a7ee66f8d21774921b9a31fe72a20de610338c7c041e2c7980e28d6da6b769f321e142ed2cc4334ec87e974052f39442d1a87c28846c1bfee8fd41254cc2359dcc885b95f1b8d64d8ca5bc82035a52f7ce50ae9456dbec99b6a2c4f29c4d3f140edfb0adce7b990db7bfe8f440c1674c33b5bde7814c12bccc1968fef2282c927b08f95b06e48716191ec95bd0c65f18f88e8fe2e626eee92bcbb960df674320a2591e82e973ccac49d67dc3f34897e51b9089520ae8275be009c0b5695325e7dd",
),
);
expect(
await Xchacha20poly1305Ietf.encrypt(
makeMessage(
"09b7b8c14569057a7c4cdc611b839bda48123c1aba86a3e1ac6ca981c7a8c1885c9d95c661d3ff530aaf9f07f8233049ebe92297fb3a708352c59fa703087011117215142f10843b976862579be7cea3d8112d3ae69f58ed9d9684da5c51123a73e5b08627ef83b1f8feb3ef91ca8f1be327468e0cc2b3bffc1a8ef1291cedf80ff8320b90f0d17fb623c447e65f4fa48a17327d427d1aa6bb445c61dda9cc9b5c1611675e618fe6b79ab9bf045cfe0b1295aa72ff1c73d6641fb942a831506c0d268c628abff8925c011d222c443b73f18e994077f1c7a893123ed400cf2f11b8fa144c0d8fc5afcd2960281f067f1329cd4abf15a1a701762121b1e103db9538f989443fbc824d39fab22b622ec98632e957adbb39dd956f31b42af3629d8fcd461c9d4519105c0f308c7c45888583832c3c659b17b0733bc7257a9c00899a4ba9933c211579480e5c30d6837241a59d044280df466d55d0b46dcfa2db4c809023d77c2503ed3afd82489c98b0949baac0a00403af770e65c45607b615912eaf7c727b15cf976e742c1cb75fc160966dda4504edc7322a9479cb2617286c85b1412b9ffc067be3f9d2fd49568a29fe40115cf76de6dd7cc3f7e833bafbdffc39097150fe14960582a39d10102aa86718f59b102c89441806c80acfe300ea415be03162e729bd92a75b4e18a33470409034bc7fc7e9fb6c4823d6bfc77d75046c0927922dae805c7ced7a4a6fb4f46a15ee6b145e0dae56e6480ac726f7ccd5296fb2c82b5ebc2239eac2d81cea4fa789e0187134a270b6e74fc7932983e6e993e391ecc0dc9cb5963a644c6ece2eba6564533a330861eaac6120f84959e5b41ccbbe2255db17106a9955a94964ba07c5b1d1a0159f3f1e8f76f65181c17e3d616398df19fe6d8625922f5a0fd384b3238761a2003e5b5101c0c795c48ceb3ec72d9f7f5cf42df449075153f642f0ffb01414a5f6c7985855d752fd7c5a71d8eead5cf51681bb67adf267d5fb290933dff2aa25eff736616cb6437738b0121e4d14bdaf5e7e8009631ecb1f30bf574c7e3c6a79cd571dd08a38054cec4dce6fb7805a0fc8c729d75f1233bc85149e0bd1d13895855f71f2f57eda6101ed274c9e41468c28b97f5f969cc849ef5027c5afd57c24ae85e16e9659cd3954c9535d3218b20f191982df2dfad4fd13d0e33b24d83274ed6066814d70d41f84874c6072b9515aa5d2f0716d851a84533fd43f21c902fb54320e04fd0b7d05d169137482f82d46b275672a7c8aa51e4546ab494f0aa2dee67d5ea2476c0a7463af609727d52ac54054ee7f454f9420d5e9f5e28357c2b0745a466087d505919ae4923586649b5b3e845929c78ff4c22c6d5f8a31eb8eedb3351e6d5f9ce68dbb318a5d62bc116bdea32a77c46492a3ca52f06a00a14420daee72b97673ef6d4a3bc35a",
),
key,
nonce,
),
).toEqual(
fromHex(
"939c5fceb7dc8174eda71d2fc5188e0e20988f8bba94a4db318164c908c0a2c3043703d67540ed6b91b69c3426c8202268485257a787ca33a6b36e863895137f466cad52928574b28c9ab912de29538866b18deabf639d9cfa841e0486b7611b500254912a6026a3f4ccd49d6974ccf66961db8181ba060653a95c3e1276014629687f147053dce0309d19ad5c64dafa0a7233a8089d89b72422633fc40a723b48dfa9c3a2b89102386fa40daf99d1322ef6349d2f7e0163d397de6a3643fa31a418b6f2af870aaf31ebb390bb939d1bc10195c461e17911976296320129edfe641fbc6a105eef088ba2bf0fa6f2ed4cd1db1ac7513282920bfb80619df8526bea82b85ed9d8c6047378d7452245310c3d6657e17cfc7cdeaa50a194c4008be5b93056cd2fec31ecd88cb31fe3d1d018c80a3167caeca4db1e3dac33d4c000262cc8de7b870fa6d27c1d0917e573adccfa3f3e9f5a157dbd6b4b7132982e9e59d6d64b736ed7e24aa6a06b84a29e88bec41d2c782c439dd95f19bf3a357c88ebdf65c071820f25c0b2c9d8e69325e63d2136cda11e1138ce4ac5a8a134082e6f84afc264c9dd3f48c7db363d901e22de918a4a4278bd863a9658e99cd5b14ccde5e9f767cebd67c6acb72071cf340b980a7047b556d45fee093854fe449e3b660f678261f26b017a8d01008032622e3a978be3f83b9d203959f3bd9918d69fd83b9b6eace0e2d15f6187b2f89b5e381ce0bae59c7d91c7354003fbc903eb3b2ea74d9ab1de63ff4eb2c6f59a8826dd84e13de1508bf7ac5a358521026eee39c1e45a5bd889404da32f6cee415928e01b723cea9165a487073989adbd4ed139dc32fd9912f750bfc0f4d7113a24e2855218ec99d5de1c87276727134cd778989937c00c30c26e7b0c91f39aa1eed52089f55092bac70bd91b190ae573490713c00cc47915540a8e15e7fdbd02874d9f8de11e3d91b0add39b27cddbb91e9cc11958896d5f43cfaff9b672bd18af5260e28c48c12fde57d34f0e9df7199f514bac7e06d164fc96ca69170c5194f040ed14d45a07766e87afa919fe4f082e3d95c6e73c3fe26cf742f71152d9ac4a1e554830075b1812ab5acde59b98bcc1e1894935e7a1bd66bc7187a3d2555f10589d60dd93e5d997fe6ffe18fe787eeab5ccfb80c012c34e4fb4fb30387e0ba9d9e2d07876f3598b95b92ece64a0f3b4aa70fbf8415ffa195a076689947c5e539098856bab5950f0bccf839c283a5972e8e70000883ddcee167eb2c424b72a82753a7f4ef14bc66ba621331e37392fce0eae3d820059e0316b5b7f1cb46a2ef026a6a942745267918cccbd535af4bc3b1e14d9b543946840c824f21730a233d01cc438cb2ac5435135a627c77508433c8c657f87bfffea9bcefe9b8e21f4e7c64a39fe3766896f4e32829bee81c2d7f12464067b734245b5246814f986f35517fee6ec",
),
);
}
});
it("decrypt conforms to Botan implementation ", async () => {
// same data as in the encryption tests, but reversed
const makeCiphertext = (hex: string): Xchacha20poly1305IetfCiphertext =>
fromHex(hex) as Xchacha20poly1305IetfCiphertext;
{
// zero key, zero nonce
const key = fromHex(
"0000000000000000000000000000000000000000000000000000000000000000",
) as Xchacha20poly1305IetfKey;
const nonce = fromHex(
"000000000000000000000000000000000000000000000000",
) as Xchacha20poly1305IetfNonce;
expect(
await Xchacha20poly1305Ietf.decrypt(makeCiphertext("8f3b945a51906dc8600de9f8962d00e6"), key, nonce),
).toEqual(fromHex(""));
expect(
await Xchacha20poly1305Ietf.decrypt(
makeCiphertext("19841f4c8866efab6c6b5329aef9e36752"),
key,
nonce,
),
).toEqual(fromHex("61"));
expect(
await Xchacha20poly1305Ietf.decrypt(
makeCiphertext("19fcc0cbdcffdf83f822811687b79930dcf7"),
key,
nonce,
),
).toEqual(fromHex("6162"));
expect(
await Xchacha20poly1305Ietf.decrypt(
makeCiphertext("19fcf5f6e7aba23c37d7a59e4c8f061e15f1f6"),
key,
nonce,
),
).toEqual(fromHex("616263"));
expect(
await Xchacha20poly1305Ietf.decrypt(
makeCiphertext(
"d5a9fa454739afd6e6b2dc5d09d9f83d98dd5abb3b3b188c81a33ad048bed75c2aa1a53cc81c02b1d3204506114cc4b58278d4a8fc8ec3916298ac165a94ff84d32320086cd98c0127a3d372112a75dc7629e55ac704dc8fd55be9033409f03254fae947912c0f737e8626f44b63c8dc7d7d3cddf57348b06fe80af61627f9a5a282b8d49cd2adf5191dd7855ffa5b931295773744691ba0b99305de8581184046c074148a8488d021122ddec21482bde85029aecd09907371ae67abc6cfc44655c3b49e7df3e2fac6b73b2803746bd1b4d17b30f5c2dfc31a7c33be5c737330e855d7630155523e3fb85c55a5ff95fd0754f4e9c5f0d88827bb28caa9af2d8f3e52b5d0957dd37d77af5df644bc483a6583d3b1e32d3b5dcbe7a6684ff8c0e44c7250744f705fce4e588f3a2ae65cac0c2b218d455911a2415fd839d3ef288d7447d6e561f4d70e93a739f6890ee3b6edcd3089266bd17d73a0c7fb96a1cdf96b2b7b18f38fe73bb6eb437a37588628ab51894b421137d1032a480643f5b36bb894ae82b8927fa7e87c906b90a3379b3f0aabee718a1e87f31dbfa3f8b1ebb5df9fdf96cd93ecdfcd421ded6299929676fa58ed3034f13474fa278b0a2c62b136e211e7a33e437e24386aae16524adc17557112fdaf484c5e6a038e8513cb2cb157816d6c85406aaae5514ca5b900dd18a90147311a37c82538e1f9adbad1bdc993264475cc54f61e54e048d63190cf138f3cdb5faf52e954d1caa8081a614beb44b2d9bea7e30d51513b4c3fd77e1cfd84e58588f733116c9be6b15cd4f72e3d602c2fe77f7880b91f62197c547df9d2",
),
key,
nonce,
),
).toEqual(
fromHex(
"ad376ccca21922a93f532f98bcede77577c5fb857ab280215b7ead7321844f23a42349e9095f394f028f0c731d43db471e39a008a79f5932cb709f2e48743f7a77bd3ee87f93fca8f33ff792daf289e7d4577299f52e0808222311df5e1bdf97c844daa0e9c62123c5df2f3ddc7f873052ee6ee282fa65a7a54d8b91c8e32f628b96c55bdc7db6a43ab156de528dd00e92a27e384addd02fb91cfff0bcf5a64f81738f82b49677661ffedc91af07f3e1ad62aef54daa9980ba4a221a5986c7c29d6867fa9e5db4ff9066da136afdd733b15a404d2dc195885ab0d82e1d1cc5cbae4011ce83146f079702813693483624c53c31775995e96b71def34a7d82c53b9bf4d7f612e89fdeb237f4df28e69f8cb8a3bc04c883915d3416a26c8e23c78efeb1ed658d0d6e7f95bc75c1652014084169569afac77c5b039d7db15c01a5b270b38a1e3d37fb1723a2de1b8ff0333d26ca5514473d3e98f815c894623779653018b7c7abcfe0cdc16f76b773332da2ef861cb7322aebbc64c880b7d805edd416cb231b18d910f0a93924f186fa490feb02ee5d21c17e76c526527951824f0c25dd658731c568d2beeb4619602362d3f07a5458399f68089dd196ef3db907730d48fa1fb4c5ea3dc6e9fc4493dc9662de0d87308be5cdbb8355e2b3a4c483d09ad71d97439950719cf88a6a0d1b616e0960ef2a320f4258a66f83d29f6a8d906427f4d94c0043bfd173a04c06c2e0e48e5c77d32578ee75bf553855f5daa8e376aca79b131bad85a0e78e224babd103ca84e7a3562610409b1c9b52b42548cd6c",
),
);
expect(
await Xchacha20poly1305Ietf.decrypt(
makeCiphertext(
"71292e48a0498805a5ad2fa4aeb78492a70a9d24fb0f3b4c76b13e22ae9259f7d21f7913a090c4addb00d672f42c2fbb77a85637a02bea20fb2dac9f11e8b0efb5ec0bf43c5af49243f446b7503f32987a6fbaf9d4b58c6a6aee7c0636433d9fef5b83615f05ade143a7ba2606d6c0f7ccb414b17b4b9ea836bf0f96f7d83b3f26ec4f84d05fca2e958f451ceb28c4390a203b724cc9d129bbcba64fe4dd72949ba5eaf160737050897648f0694f8f5757a72d297fbf7a25affbfcf3377853e8c58d5f066911ae970ad0fc1945cd8791f405a23daff28de3d3ded54441a099eafeefd2e18fcef8966593bd4b29b1dccaeba58f2189c496e22044fa31352e33219d5feb62b829ceeefc621b020e741e30efc938189097779590c0b02e32b99ae57f85a18c876421edd4d476870a4ecd27ce6e4b722489dd8a790580f213ee04a54f5dcfc77dd65551be59d73b858c912e5603271dbe1082b05b0162a0564df81ccb101ba37d43eacc8a067d51dcdb3f11ee1735f87394ab6302269eb62de5cf910123ffe2b584f8393569a82d49981e02b9d200b7bd8c52dba24226fcbe1bc83c4b03918e0050ffee8a7ba6bd5430d9bbc6915042644d44402adc59578d6eba3e02a39aa8e9ef3f4660720bfa95a474d9d1ad6d325ac3c1efdbf7ebf2c2d7a2bd7060ad98c835ad899146953e0b9611f381fd5212c4fc8efccb74b494f5ad2929c1bdf50f14629715b3ca3a4ebf478441883d201c24da12ca0dccf83adb37057db487a78a86e2f2aac85c77b66896e0b8bee01a57ea7353e710c8ba70c172590ac2843520e26a2bea555abb2e08be3ee158c73325f2d28053c8adf0ac529051b729a15ef40033d9308208768db4f3af4470304de164d7cf7db5fbd80bd82007bb66703d5b5d8d3716579399736fd81e1e4b0f8df407c5aff52dcc7d4b9104203a45f32888921398704f74f022346778e4feec6acdfea405d24a3cfba770fe230dc17eb0f50f6290b1f3ddfcf9d3a0171fc914da5281f9e5d955a8c75ba3b472b4e724bfe11aa2b720c4c99b6f9cd40eaf957dc39941025a23cff213892a03c840555d984ab370a74ce8fee6fefaf9bc80e390d5cc258a277974304bcbb83a2eebcfe080ef886640685a7174862b89133e35cbb42819d9949d5077aafc752be3fbda1405193d2be0733e91b6a0b45388a039e5fe50d5bcf98472c8be6217fe5595c3198d5db6c1856d9db97c484182cf1071b3473beb4f9df03efbbee998dfa49da014fff5b996d93bd8a79b2a3931666a49eefc13cbbf56185e2ea9924ff1ea632ad72859984397529de6b6c500f5fab65813810606f4449afcf57a59f6a7fd7140bb8a59b87fe0d8de65398938c0b7a8697eda50c175296c3a82a93de00a40f623d62c02adff2ca8ccdab1357a009d7e7f6f461152c28fe917fc56d94fd66546c9691ed640692515e4bdd1d7ae0a2d1c",
),
key,
nonce,
),
).toEqual(
fromHex(
"09b7b8c14569057a7c4cdc611b839bda48123c1aba86a3e1ac6ca981c7a8c1885c9d95c661d3ff530aaf9f07f8233049ebe92297fb3a708352c59fa703087011117215142f10843b976862579be7cea3d8112d3ae69f58ed9d9684da5c51123a73e5b08627ef83b1f8feb3ef91ca8f1be327468e0cc2b3bffc1a8ef1291cedf80ff8320b90f0d17fb623c447e65f4fa48a17327d427d1aa6bb445c61dda9cc9b5c1611675e618fe6b79ab9bf045cfe0b1295aa72ff1c73d6641fb942a831506c0d268c628abff8925c011d222c443b73f18e994077f1c7a893123ed400cf2f11b8fa144c0d8fc5afcd2960281f067f1329cd4abf15a1a701762121b1e103db9538f989443fbc824d39fab22b622ec98632e957adbb39dd956f31b42af3629d8fcd461c9d4519105c0f308c7c45888583832c3c659b17b0733bc7257a9c00899a4ba9933c211579480e5c30d6837241a59d044280df466d55d0b46dcfa2db4c809023d77c2503ed3afd82489c98b0949baac0a00403af770e65c45607b615912eaf7c727b15cf976e742c1cb75fc160966dda4504edc7322a9479cb2617286c85b1412b9ffc067be3f9d2fd49568a29fe40115cf76de6dd7cc3f7e833bafbdffc39097150fe14960582a39d10102aa86718f59b102c89441806c80acfe300ea415be03162e729bd92a75b4e18a33470409034bc7fc7e9fb6c4823d6bfc77d75046c0927922dae805c7ced7a4a6fb4f46a15ee6b145e0dae56e6480ac726f7ccd5296fb2c82b5ebc2239eac2d81cea4fa789e0187134a270b6e74fc7932983e6e993e391ecc0dc9cb5963a644c6ece2eba6564533a330861eaac6120f84959e5b41ccbbe2255db17106a9955a94964ba07c5b1d1a0159f3f1e8f76f65181c17e3d616398df19fe6d8625922f5a0fd384b3238761a2003e5b5101c0c795c48ceb3ec72d9f7f5cf42df449075153f642f0ffb01414a5f6c7985855d752fd7c5a71d8eead5cf51681bb67adf267d5fb290933dff2aa25eff736616cb6437738b0121e4d14bdaf5e7e8009631ecb1f30bf574c7e3c6a79cd571dd08a38054cec4dce6fb7805a0fc8c729d75f1233bc85149e0bd1d13895855f71f2f57eda6101ed274c9e41468c28b97f5f969cc849ef5027c5afd57c24ae85e16e9659cd3954c9535d3218b20f191982df2dfad4fd13d0e33b24d83274ed6066814d70d41f84874c6072b9515aa5d2f0716d851a84533fd43f21c902fb54320e04fd0b7d05d169137482f82d46b275672a7c8aa51e4546ab494f0aa2dee67d5ea2476c0a7463af609727d52ac54054ee7f454f9420d5e9f5e28357c2b0745a466087d505919ae4923586649b5b3e845929c78ff4c22c6d5f8a31eb8eedb3351e6d5f9ce68dbb318a5d62bc116bdea32a77c46492a3ca52f06a00a14420daee72b97673ef6d4a3bc35a",
),
);
}
{
// zero key, random nonce
const key = fromHex(
"0000000000000000000000000000000000000000000000000000000000000000",
) as Xchacha20poly1305IetfKey;
const nonce = fromHex(
"623ac6e73c2c00951bf4c7490e4692f8e30f5f8c1c4196da",
) as Xchacha20poly1305IetfNonce;
expect(
await Xchacha20poly1305Ietf.decrypt(makeCiphertext("ee5f3601ce18d227df5d5a8b2ddb05e3"), key, nonce),
).toEqual(fromHex(""));
expect(
await Xchacha20poly1305Ietf.decrypt(
makeCiphertext("0c5a3d0c311a2a9598ce968549a8d6a6f9"),
key,
nonce,
),
).toEqual(fromHex("61"));
expect(
await Xchacha20poly1305Ietf.decrypt(
makeCiphertext("0ce46df742a0423e9954a903c9f5bf54412e"),
key,
nonce,
),
).toEqual(fromHex("6162"));
expect(
await Xchacha20poly1305Ietf.decrypt(
makeCiphertext("0ce4be1fd7c793ac56c6d734955514bd8391a3"),
key,
nonce,
),
).toEqual(fromHex("616263"));
expect(
await Xchacha20poly1305Ietf.decrypt(
makeCiphertext(
"c0b1b1d9acd58a7e8bc324343f0f9bf4eca174c4f9056b7e2dda6f185ea7587d032b3b202df5db9ae73ace5f994b366cb82d05175b6af3f7b93f53a8bd3bf99f69dae2c5bdcdf9bb74c21706687956db8d9879a833ebaf7d70d74b52c750026f5dd14dc03c6376848443f0dc18fa39d2acfaaf41276789621aa6b844af8a3e00e0dc807ec113343a9a14c4e205064f113af0aaa01f00a0cee37e8f0f54d9818248f60f9f1bd2ff5c615f7ca72c6d71085b0ef79f51d8978cabf6c6dc31d526aa0a692748882bda4e2684014b7bdd2e598b1fed56b9ec4038a10a32be2c9e1b88d99d76d17646326ae4a8513bf030dc19af0e83a69219231dec942d72abd963a3e2fc854494f70539e5a92076f32431334bdba71aa81bb96fad0daa4e64a99f0d3bbc781554ba52bdac4690216faff2d25c229d2d69ae16da7d3a8adbc0424a3a923e562d3064dcc78155b4223a73c2587f4bc77c0ebe9592264c74437d95e976f11aef5e26db2ae7b54a4034301a21bce58aee4f51b8c4d09b2b188121c012f5a476580573b497cbf26f96c03dd22f2128f1e8e0669857a87d6a60741cef746b0af6c56adaff445f7736c65265d3a38305421b0b9e6161dcc2eac86f1d97a253c9df8594cf654e1c85ac6a323935b9a30911422f2514bc62aca52dacc4fea99fb2f28c71ca79737d8968b5d420143f4d313d3affda4a584877f08d59f7b83c7a16045c6d9d39ae696905139a322d02be29ce99a01493f9e3f38fbcb48b7c66d30c8b78c703eb1079eb5077f07ef5ecb281d15bedd35db5decd343c85ebe5471fd40004c24d3ad31878c5e92c98143728d2",
),
key,
nonce,
),
).toEqual(
fromHex(
"ad376ccca21922a93f532f98bcede77577c5fb857ab280215b7ead7321844f23a42349e9095f394f028f0c731d43db471e39a008a79f5932cb709f2e48743f7a77bd3ee87f93fca8f33ff792daf289e7d4577299f52e0808222311df5e1bdf97c844daa0e9c62123c5df2f3ddc7f873052ee6ee282fa65a7a54d8b91c8e32f628b96c55bdc7db6a43ab156de528dd00e92a27e384addd02fb91cfff0bcf5a64f81738f82b49677661ffedc91af07f3e1ad62aef54daa9980ba4a221a5986c7c29d6867fa9e5db4ff9066da136afdd733b15a404d2dc195885ab0d82e1d1cc5cbae4011ce83146f079702813693483624c53c31775995e96b71def34a7d82c53b9bf4d7f612e89fdeb237f4df28e69f8cb8a3bc04c883915d3416a26c8e23c78efeb1ed658d0d6e7f95bc75c1652014084169569afac77c5b039d7db15c01a5b270b38a1e3d37fb1723a2de1b8ff0333d26ca5514473d3e98f815c894623779653018b7c7abcfe0cdc16f76b773332da2ef861cb7322aebbc64c880b7d805edd416cb231b18d910f0a93924f186fa490feb02ee5d21c17e76c526527951824f0c25dd658731c568d2beeb4619602362d3f07a5458399f68089dd196ef3db907730d48fa1fb4c5ea3dc6e9fc4493dc9662de0d87308be5cdbb8355e2b3a4c483d09ad71d97439950719cf88a6a0d1b616e0960ef2a320f4258a66f83d29f6a8d906427f4d94c0043bfd173a04c06c2e0e48e5c77d32578ee75bf553855f5daa8e376aca79b131bad85a0e78e224babd103ca84e7a3562610409b1c9b52b42548cd6c",
),
);
expect(
await Xchacha20poly1305Ietf.decrypt(
makeCiphertext(
"643165d44ba5adadc8dcd7cd9861e75bd376b35b393148bedac86beab88bd6d6fb95e70f45791d86ef1a5d2b7c2bdd624dfd878807cfda46208a5321f647b6f40f15c939ed4e8128109582c3296c119f81de260b205aff98cf62de57c51acfc2e67027e6f24ad416b9626c0e554f31f91d33872da95f5f7a43f1bd244e75fc9a64b2772e8d9e53e11686567bb1d4d0bb2245e6e517a06a47e1262c9e3585eb569593917af12507dcc93b198987367ce2e4f9f318e36e7dda75a35d84c062b1049a27ccd09cc99623eae3c67a3d64c219cbcb345be3dc121868a8d444314df152cf277353f8dd98c2be83b0257c7e952e43fff86ede2d6d77eb6bff8937587d0d41f1dbf6b9a318aa6e646682b9ec6739c1914cb3dba1f5a7f62abc0819e8c50c084b89ed9cae2c9e36ca699c4f0763599e67f7d2087edaf24560d21000436612a9244f0f2c465e98acab5aef36f1b0c0c485d0e896c5c65f0eedd118bd79dc9351218fe5a817271089a77e1fdb999885a0cc52fc603d58629a27ce314fd06e0f1dc109657ea210552f7aae86e4e906b8ae2943b9aa9e1bf42c35f92b5a4557e29e6a8b72173c576e300f7d02537ae8aeb52913a4ca18d4a89cccb6b39ad57adcfd9e0edb85b43224c1e60b66bac387a6cfe95e0f827835c12938c5d0833ac00e73c5a0846ec99e9eb2cb71a68e3b2e63a86969aa2face17c99bcd834afafc4ee1e2a8f26fc976d8ac49bc99c5b5b1630b27c85676fe6b9c0aa928e26585102e553486d943bae01de725d3b0a29b47216c2b5a43fb1d9d528b16760447643e93b2bfce4247dce0e9167c26d776ce5db7044411f3d1d4f51c400f0b14f65f68f395af75568955bbd1662b9e85c2b9651d0296be568d410af43d78a7202d357f7093644bbab734db1f8e2c4eb8f950b4e20122c0f079d3980539e8a7a22b0444d8e548f17a21c5cd01a1fdf237cd55e6031921488598919c6ded91d33169240d1dd5684de3368c0148cf82bbf9587a118ce770e5371b34b97a50cfeb2f685142dfa609225f6ed6c754c567eba859d794e89bd3b88f768bed757ecd2fbee8216816d4b15b6f39fb59b583353478372b6e395d845b57aef27dcdf40ee5e906e62fd6b5ae4ec071d95a3d9dc688b7aa1fe9bed64070e8881cbb7e10d37da67306d8c9c6f71dc175f055bba88271dfdaf0a88a4c5460fcf83c0b9ef58e75ee18a75bee1ddd3de18d130bc90327e2338cf2f640256d02ecffe9fa113c02c168c97845df2549c62504d1e6054d80caa18821222dbf73707816c7513c92574a8257bd84507b8706f9eea99d02910c3a7e5d4ed9d37d5f22888288e158a0c665bc5be491c514d763851e22a4852c8b8178cac89745949f264db4d4b3ac711ddc3b2e556aa10d957e60734e45b3f5b84a800752da82f36d820474126703c734515123b1054723a741d2840a8e935690158c47c8aa185",
),
key,
nonce,
),
).toEqual(
fromHex(
"09b7b8c14569057a7c4cdc611b839bda48123c1aba86a3e1ac6ca981c7a8c1885c9d95c661d3ff530aaf9f07f8233049ebe92297fb3a708352c59fa703087011117215142f10843b976862579be7cea3d8112d3ae69f58ed9d9684da5c51123a73e5b08627ef83b1f8feb3ef91ca8f1be327468e0cc2b3bffc1a8ef1291cedf80ff8320b90f0d17fb623c447e65f4fa48a17327d427d1aa6bb445c61dda9cc9b5c1611675e618fe6b79ab9bf045cfe0b1295aa72ff1c73d6641fb942a831506c0d268c628abff8925c011d222c443b73f18e994077f1c7a893123ed400cf2f11b8fa144c0d8fc5afcd2960281f067f1329cd4abf15a1a701762121b1e103db9538f989443fbc824d39fab22b622ec98632e957adbb39dd956f31b42af3629d8fcd461c9d4519105c0f308c7c45888583832c3c659b17b0733bc7257a9c00899a4ba9933c211579480e5c30d6837241a59d044280df466d55d0b46dcfa2db4c809023d77c2503ed3afd82489c98b0949baac0a00403af770e65c45607b615912eaf7c727b15cf976e742c1cb75fc160966dda4504edc7322a9479cb2617286c85b1412b9ffc067be3f9d2fd49568a29fe40115cf76de6dd7cc3f7e833bafbdffc39097150fe14960582a39d10102aa86718f59b102c89441806c80acfe300ea415be03162e729bd92a75b4e18a33470409034bc7fc7e9fb6c4823d6bfc77d75046c0927922dae805c7ced7a4a6fb4f46a15ee6b145e0dae56e6480ac726f7ccd5296fb2c82b5ebc2239eac2d81cea4fa789e0187134a270b6e74fc7932983e6e993e391ecc0dc9cb5963a644c6ece2eba6564533a330861eaac6120f84959e5b41ccbbe2255db17106a9955a94964ba07c5b1d1a0159f3f1e8f76f65181c17e3d616398df19fe6d8625922f5a0fd384b3238761a2003e5b5101c0c795c48ceb3ec72d9f7f5cf42df449075153f642f0ffb01414a5f6c7985855d752fd7c5a71d8eead5cf51681bb67adf267d5fb290933dff2aa25eff736616cb6437738b0121e4d14bdaf5e7e8009631ecb1f30bf574c7e3c6a79cd571dd08a38054cec4dce6fb7805a0fc8c729d75f1233bc85149e0bd1d13895855f71f2f57eda6101ed274c9e41468c28b97f5f969cc849ef5027c5afd57c24ae85e16e9659cd3954c9535d3218b20f191982df2dfad4fd13d0e33b24d83274ed6066814d70d41f84874c6072b9515aa5d2f0716d851a84533fd43f21c902fb54320e04fd0b7d05d169137482f82d46b275672a7c8aa51e4546ab494f0aa2dee67d5ea2476c0a7463af609727d52ac54054ee7f454f9420d5e9f5e28357c2b0745a466087d505919ae4923586649b5b3e845929c78ff4c22c6d5f8a31eb8eedb3351e6d5f9ce68dbb318a5d62bc116bdea32a77c46492a3ca52f06a00a14420daee72b97673ef6d4a3bc35a",
),
);
}
{
// random key, zero nonce
const key = fromHex(
"1e1d2cd50e63f38a81275236f5ccc04c06dbe7a9b050eb1e38cb196c4125bbe2",
) as Xchacha20poly1305IetfKey;
const nonce = fromHex(
"000000000000000000000000000000000000000000000000",
) as Xchacha20poly1305IetfNonce;
expect(
await Xchacha20poly1305Ietf.decrypt(makeCiphertext("6142f36622e4becf1fad773123d067e8"), key, nonce),
).toEqual(fromHex(""));
expect(
await Xchacha20poly1305Ietf.decrypt(
makeCiphertext("94f1c39995a77dd01265112ea5f5c3470c"),
key,
nonce,
),
).toEqual(fromHex("61"));
expect(
await Xchacha20poly1305Ietf.decrypt(
makeCiphertext("94f1ba350b17e0b580bb7541812ffcaf4eda"),
key,
nonce,
),
).toEqual(fromHex("6162"));
expect(
await Xchacha20poly1305Ietf.decrypt(
makeCiphertext("94f1a17b016c87a9a1135b57acc427edea2b6e"),
key,
nonce,
),
).toEqual(fromHex("616263"));
expect(
await Xchacha20poly1305Ietf.decrypt(
makeCiphertext(
"58a4ae64d149c7fe648d93f0a293055b08c72302a457aa99d6892b258b9d5bcc59332df250f735d98ff32351907738efbc21ef0412e9a00ae7f2fecedcd6ae52abe266f1e17990ce2f958bfdd6bc32ca70f0734788d44318d7fc86c687059da9ac6b859c5286b3941b2847891ea87a56e81a3d7d543dea2c984492ff8345ab8f52ec69d0ce1392dc42f1df875b9170b198b04ee4d9f7cfd430c145066a9b4592b828d5785e600b655982797557827cfc8c9e2bd55df3fc956f5199c59974dc2e1550361487181516f322b44d7d1649dd072932e0225bf929e1c6d905055051ccc779edbdd9c8643f318a6f564197e3187b27f84b4e153317e860542a3bbe541738c9e6115da21bd23cc40a8ef8b205f824fd4bcf4abfd88cfb4436a4e651c39a14412fe68da9a1a1abf62674ff001e31a6d2b435828321686e1b135a51d65e8117dcf31aab6b8120ebcc6a8c048d47685c9a0fd58ee629b9a7ee73734082da893484ef9de37a291881ff808192d0726c41f1b04fbd687331e1e8a71cc1f84186556c6bc81545413c52a1cc21e42675929369340f93d55396ce25e1fdd819015e8295d529b85032f21aeefadfb1aaac0e1d3e04b5fba0150a9b9796a628e5627713443aed99f480cd7495b5eb5a4c65d2b9bad43fce9b7d24a18cf3e8e983942ed476f2deb38aa3ff38f0b84a3432e5fb25647a3ca0d2a563e8655f93cd1be4f2672375740a469b75dcdd66fc5f3645e751caadfcf11ec3efdf1015160b3035134b44acc6fef3b5fd00d9c7e713590d1021ab6c2ced3316a813a10969e3da876fe152b8253ddd4f853fdc9954e3f3349de7",
),
key,
nonce,
),
).toEqual(
fromHex(
"ad376ccca21922a93f532f98bcede77577c5fb857ab280215b7ead7321844f23a42349e9095f394f028f0c731d43db471e39a008a79f5932cb709f2e48743f7a77bd3ee87f93fca8f33ff792daf289e7d4577299f52e0808222311df5e1bdf97c844daa0e9c62123c5df2f3ddc7f873052ee6ee282fa65a7a54d8b91c8e32f628b96c55bdc7db6a43ab156de528dd00e92a27e384addd02fb91cfff0bcf5a64f81738f82b49677661ffedc91af07f3e1ad62aef54daa9980ba4a221a5986c7c29d6867fa9e5db4ff9066da136afdd733b15a404d2dc195885ab0d82e1d1cc5cbae4011ce83146f079702813693483624c53c31775995e96b71def34a7d82c53b9bf4d7f612e89fdeb237f4df28e69f8cb8a3bc04c883915d3416a26c8e23c78efeb1ed658d0d6e7f95bc75c1652014084169569afac77c5b039d7db15c01a5b270b38a1e3d37fb1723a2de1b8ff0333d26ca5514473d3e98f815c894623779653018b7c7abcfe0cdc16f76b773332da2ef861cb7322aebbc64c880b7d805edd416cb231b18d910f0a93924f186fa490feb02ee5d21c17e76c526527951824f0c25dd658731c568d2beeb4619602362d3f07a5458399f68089dd196ef3db907730d48fa1fb4c5ea3dc6e9fc4493dc9662de0d87308be5cdbb8355e2b3a4c483d09ad71d97439950719cf88a6a0d1b616e0960ef2a320f4258a66f83d29f6a8d906427f4d94c0043bfd173a04c06c2e0e48e5c77d32578ee75bf553855f5daa8e376aca79b131bad85a0e78e224babd103ca84e7a3562610409b1c9b52b42548cd6c",
),
);
expect(
await Xchacha20poly1305Ietf.decrypt(
makeCiphertext(
"fc247a693639e02d2792600905fd79f43710e49d64638959219b2fd76db1d567a18df1dd387bf3c587d3b0257517d3e149f16d9b4e4c89bb7e47fe4797aae139cd2d4d0db1fae85d4bc21e3897a9758e7cb62ce49b6513fd684913c3854f500417caefba9caf11062609db5b531d727d59d31511da053c34c113979f62ba6915d6829e80829ef507ce634d1eef43ef1b800502a1d157055d3299e6970bc72f46654d4b9db497f3e5f1e61c5bfcd9711633692f52ef4516c3b104029d68c34b80851edd8c93fa597b3f45737c3bafa59d47fdebed786bab0928643fff1883bb16d1c3e83f5753ce976ba18e48cdd9aa2f97d6838302217d7def9f86d1a73f4ab99bc4b8a370f60641b7094c7ab27a53f2aeb7a06639059444a06320e29b10999b27b6de1e45bddf82317adfc9dfa88fba6497decae353ed4056414b9191d772a92cc6ea38b749037fc6328441080f35f0e7541841169d7a748f4fd628806eef6c94bf8f266db624efbd12beaa7953cb5504b70cfc8cedef83e0e471acafe83d7cecdb3aa81853c6a28fb4f4673d1d5c0b15b19f565fd31fca9f7a78a29eb322d716099b31759321c35dd7418f8703e723ad550c1aafd9a07ec5b1e87aafa7baf82705b1a2d325fcf530dfd4bfd9ba5bd77f42c81f69f7f48724111b94ae47fdbf1541de2b173a4e1c03537c389a1df4d5bc30296955341c5706290afe950c1c666f0da63f6be858967143bcfa36405169ca78b13b8a6b83cc860d2784d81d51251487b995c6b6a45a99d48b1d441893b462cf93fe8fb7765e6ff255a87e7c294b1e69e2483a56586d5e422bf9095e7b2aac6305ae4112024e02966b6d6494c4252c44f2ff807b27597891f95b2c2781444ff402155f2e34477dca2d5926934bba819561cdf7fa3e6e1009db51a4d99f2c364854b20b5e4f7713a409ebd31e16e0f6505158fdd2406a085c66ec2af17aa2e197f22094cf6167168243b5e73e45286b1fcc8b66f28058a0a6b2554f641780c4e31d8b6f47f34e0e978ecd3127ae148561a8c18c1f36b5349ae74fddc3caa4ba88c31a12a42f918c10f0bf56defcc8bcfb0975c2d550160d381c9b323d87d927f490abd1c992e490aa0c069437c34998f8b0e8e6f97ea010195387d512d2bef913fcfee4c088a36b0e03c5e14296cc991c2efaf9f27f2c44aa5734defac0138de2049b03ace45b3f4db3865be3157924ede7ee0e381fefb4c418363326f5c0d72ca1e9b9bd2cac73e3e793b1afc8101f3cd57aa7edb9d49777f447637d423821cde426e9f56083d0fd0c39816d08434f04ddc1eeeeb9fe6bc4c985d33c66f64a221f63f2d2f9fc2fa68ccd8b797ca84fbb92fda61093490492bdceb5796de563592f97531a15edb2b45b15008207ebb27fc4d9dfef4014e3f347af35c1e5686df24491b88477c90180a89d845299097a54f97d71ac3b07e6722602dd823434",
),
key,
nonce,
),
).toEqual(
fromHex(
"09b7b8c14569057a7c4cdc611b839bda48123c1aba86a3e1ac6ca981c7a8c1885c9d95c661d3ff530aaf9f07f8233049ebe92297fb3a708352c59fa703087011117215142f10843b976862579be7cea3d8112d3ae69f58ed9d9684da5c51123a73e5b08627ef83b1f8feb3ef91ca8f1be327468e0cc2b3bffc1a8ef1291cedf80ff8320b90f0d17fb623c447e65f4fa48a17327d427d1aa6bb445c61dda9cc9b5c1611675e618fe6b79ab9bf045cfe0b1295aa72ff1c73d6641fb942a831506c0d268c628abff8925c011d222c443b73f18e994077f1c7a893123ed400cf2f11b8fa144c0d8fc5afcd2960281f067f1329cd4abf15a1a701762121b1e103db9538f989443fbc824d39fab22b622ec98632e957adbb39dd956f31b42af3629d8fcd461c9d4519105c0f308c7c45888583832c3c659b17b0733bc7257a9c00899a4ba9933c211579480e5c30d6837241a59d044280df466d55d0b46dcfa2db4c809023d77c2503ed3afd82489c98b0949baac0a00403af770e65c45607b615912eaf7c727b15cf976e742c1cb75fc160966dda4504edc7322a9479cb2617286c85b1412b9ffc067be3f9d2fd49568a29fe40115cf76de6dd7cc3f7e833bafbdffc39097150fe14960582a39d10102aa86718f59b102c89441806c80acfe300ea415be03162e729bd92a75b4e18a33470409034bc7fc7e9fb6c4823d6bfc77d75046c0927922dae805c7ced7a4a6fb4f46a15ee6b145e0dae56e6480ac726f7ccd5296fb2c82b5ebc2239eac2d81cea4fa789e0187134a270b6e74fc7932983e6e993e391ecc0dc9cb5963a644c6ece2eba6564533a330861eaac6120f84959e5b41ccbbe2255db17106a9955a94964ba07c5b1d1a0159f3f1e8f76f65181c17e3d616398df19fe6d8625922f5a0fd384b3238761a2003e5b5101c0c795c48ceb3ec72d9f7f5cf42df449075153f642f0ffb01414a5f6c7985855d752fd7c5a71d8eead5cf51681bb67adf267d5fb290933dff2aa25eff736616cb6437738b0121e4d14bdaf5e7e8009631ecb1f30bf574c7e3c6a79cd571dd08a38054cec4dce6fb7805a0fc8c729d75f1233bc85149e0bd1d13895855f71f2f57eda6101ed274c9e41468c28b97f5f969cc849ef5027c5afd57c24ae85e16e9659cd3954c9535d3218b20f191982df2dfad4fd13d0e33b24d83274ed6066814d70d41f84874c6072b9515aa5d2f0716d851a84533fd43f21c902fb54320e04fd0b7d05d169137482f82d46b275672a7c8aa51e4546ab494f0aa2dee67d5ea2476c0a7463af609727d52ac54054ee7f454f9420d5e9f5e28357c2b0745a466087d505919ae4923586649b5b3e845929c78ff4c22c6d5f8a31eb8eedb3351e6d5f9ce68dbb318a5d62bc116bdea32a77c46492a3ca52f06a00a14420daee72b97673ef6d4a3bc35a",
),
);
}
{
// random key, random nonce
const key = fromHex(
"1e1d2cd50e63f38a81275236f5ccc04c06dbe7a9b050eb1e38cb196c4125bbe2",
) as Xchacha20poly1305IetfKey;
const nonce = fromHex(
"623ac6e73c2c00951bf4c7490e4692f8e30f5f8c1c4196da",
) as Xchacha20poly1305IetfNonce;
expect(
await Xchacha20poly1305Ietf.decrypt(makeCiphertext("7e51936afe05c64a87fcd64cf811f22d"), key, nonce),
).toEqual(fromHex(""));
expect(
await Xchacha20poly1305Ietf.decrypt(
makeCiphertext("fb5530e69e505cb1d3d5344f354e8e96d5"),
key,
nonce,
),
).toEqual(fromHex("61"));
expect(
await Xchacha20poly1305Ietf.decrypt(
makeCiphertext("fb4927d0dbb75b8d866c0f891195be725cfe"),
key,
nonce,
),
).toEqual(fromHex("6162"));
expect(
await Xchacha20poly1305Ietf.decrypt(
makeCiphertext("fb4984e6af4f702a738693d59104c6690da890"),
key,
nonce,
),
).toEqual(fromHex("616263"));
expect(
await Xchacha20poly1305Ietf.decrypt(
makeCiphertext(
"371c8bc350aca6a7aeb8eed66276f2a11f4f48147aa0871bc693603beeec2c68fc89dff91dcc2b7799960f40c3a8cb2c9d98d0c8fb22e3823f066e0f73e95c1420a386aec2060c21e8cd2cd79f3c14cc6af7d249acd2cd7945318b0184fdacb6eba33eb7e4498431c9ed484f24c1c4ddd8a8f3ed0f82d01e0afe595ef389c3dcad0688443cdebb3bbc0f8b34e8b6455012c77fed003d433e267ac0aea55618ef95ba3726484f6982900bc12304c2dcd89101301a9dc8eb350dc24532c7f46d9f34565d6abb6546c2fd8c74a1fd2a715b81d54cc93bd12b315ec070c81cfa072472a5b9e89ec545a0d1895e112abca47b3d2a610f1d06ccf80c04529a01794cc5498fe6ecf48cdb97f8b591b1688d6706b72cbc480f463016f177b7d2b941d1e48ac7a735e7f84fcf42004aa2c37941930a4f5b98ab3c68f32667f4f814c12c0e17d2c7599b2d248d51e3e7dae9f1df5441f1290bc26e2e7043ead46958c2abbc76ed2bc8e01befbd9a4d55af491d3187815b90cb1dc6016b5e15698a5b6cf41166d291118f19a25e6fdce0a04a1ecfa4a7ee66f8d21774921b9a31fe72a20de610338c7c041e2c7980e28d6da6b769f321e142ed2cc4334ec87e974052f39442d1a87c28846c1bfee8fd41254cc2359dcc885b95f1b8d64d8ca5bc82035a52f7ce50ae9456dbec99b6a2c4f29c4d3f140edfb0adce7b990db7bfe8f440c1674c33b5bde7814c12bccc1968fef2282c927b08f95b06e48716191ec95bd0c65f18f88e8fe2e626eee92bcbb960df674320a2591e82e973ccac49d67dc3f34897e51b9089520ae8275be009c0b5695325e7dd",
),
key,
nonce,
),
).toEqual(
fromHex(
"ad376ccca21922a93f532f98bcede77577c5fb857ab280215b7ead7321844f23a42349e9095f394f028f0c731d43db471e39a008a79f5932cb709f2e48743f7a77bd3ee87f93fca8f33ff792daf289e7d4577299f52e0808222311df5e1bdf97c844daa0e9c62123c5df2f3ddc7f873052ee6ee282fa65a7a54d8b91c8e32f628b96c55bdc7db6a43ab156de528dd00e92a27e384addd02fb91cfff0bcf5a64f81738f82b49677661ffedc91af07f3e1ad62aef54daa9980ba4a221a5986c7c29d6867fa9e5db4ff9066da136afdd733b15a404d2dc195885ab0d82e1d1cc5cbae4011ce83146f079702813693483624c53c31775995e96b71def34a7d82c53b9bf4d7f612e89fdeb237f4df28e69f8cb8a3bc04c883915d3416a26c8e23c78efeb1ed658d0d6e7f95bc75c1652014084169569afac77c5b039d7db15c01a5b270b38a1e3d37fb1723a2de1b8ff0333d26ca5514473d3e98f815c894623779653018b7c7abcfe0cdc16f76b773332da2ef861cb7322aebbc64c880b7d805edd416cb231b18d910f0a93924f186fa490feb02ee5d21c17e76c526527951824f0c25dd658731c568d2beeb4619602362d3f07a5458399f68089dd196ef3db907730d48fa1fb4c5ea3dc6e9fc4493dc9662de0d87308be5cdbb8355e2b3a4c483d09ad71d97439950719cf88a6a0d1b616e0960ef2a320f4258a66f83d29f6a8d906427f4d94c0043bfd173a04c06c2e0e48e5c77d32578ee75bf553855f5daa8e376aca79b131bad85a0e78e224babd103ca84e7a3562610409b1c9b52b42548cd6c",
),
);
expect(
await Xchacha20poly1305Ietf.decrypt(
makeCiphertext(
"939c5fceb7dc8174eda71d2fc5188e0e20988f8bba94a4db318164c908c0a2c3043703d67540ed6b91b69c3426c8202268485257a787ca33a6b36e863895137f466cad52928574b28c9ab912de29538866b18deabf639d9cfa841e0486b7611b500254912a6026a3f4ccd49d6974ccf66961db8181ba060653a95c3e1276014629687f147053dce0309d19ad5c64dafa0a7233a8089d89b72422633fc40a723b48dfa9c3a2b89102386fa40daf99d1322ef6349d2f7e0163d397de6a3643fa31a418b6f2af870aaf31ebb390bb939d1bc10195c461e17911976296320129edfe641fbc6a105eef088ba2bf0fa6f2ed4cd1db1ac7513282920bfb80619df8526bea82b85ed9d8c6047378d7452245310c3d6657e17cfc7cdeaa50a194c4008be5b93056cd2fec31ecd88cb31fe3d1d018c80a3167caeca4db1e3dac33d4c000262cc8de7b870fa6d27c1d0917e573adccfa3f3e9f5a157dbd6b4b7132982e9e59d6d64b736ed7e24aa6a06b84a29e88bec41d2c782c439dd95f19bf3a357c88ebdf65c071820f25c0b2c9d8e69325e63d2136cda11e1138ce4ac5a8a134082e6f84afc264c9dd3f48c7db363d901e22de918a4a4278bd863a9658e99cd5b14ccde5e9f767cebd67c6acb72071cf340b980a7047b556d45fee093854fe449e3b660f678261f26b017a8d01008032622e3a978be3f83b9d203959f3bd9918d69fd83b9b6eace0e2d15f6187b2f89b5e381ce0bae59c7d91c7354003fbc903eb3b2ea74d9ab1de63ff4eb2c6f59a8826dd84e13de1508bf7ac5a358521026eee39c1e45a5bd889404da32f6cee415928e01b723cea9165a487073989adbd4ed139dc32fd9912f750bfc0f4d7113a24e2855218ec99d5de1c87276727134cd778989937c00c30c26e7b0c91f39aa1eed52089f55092bac70bd91b190ae573490713c00cc47915540a8e15e7fdbd02874d9f8de11e3d91b0add39b27cddbb91e9cc11958896d5f43cfaff9b672bd18af5260e28c48c12fde57d34f0e9df7199f514bac7e06d164fc96ca69170c5194f040ed14d45a07766e87afa919fe4f082e3d95c6e73c3fe26cf742f71152d9ac4a1e554830075b1812ab5acde59b98bcc1e1894935e7a1bd66bc7187a3d2555f10589d60dd93e5d997fe6ffe18fe787eeab5ccfb80c012c34e4fb4fb30387e0ba9d9e2d07876f3598b95b92ece64a0f3b4aa70fbf8415ffa195a076689947c5e539098856bab5950f0bccf839c283a5972e8e70000883ddcee167eb2c424b72a82753a7f4ef14bc66ba621331e37392fce0eae3d820059e0316b5b7f1cb46a2ef026a6a942745267918cccbd535af4bc3b1e14d9b543946840c824f21730a233d01cc438cb2ac5435135a627c77508433c8c657f87bfffea9bcefe9b8e21f4e7c64a39fe3766896f4e32829bee81c2d7f12464067b734245b5246814f986f35517fee6ec",
),
key,
nonce,
),
).toEqual(
fromHex(
"09b7b8c14569057a7c4cdc611b839bda48123c1aba86a3e1ac6ca981c7a8c1885c9d95c661d3ff530aaf9f07f8233049ebe92297fb3a708352c59fa703087011117215142f10843b976862579be7cea3d8112d3ae69f58ed9d9684da5c51123a73e5b08627ef83b1f8feb3ef91ca8f1be327468e0cc2b3bffc1a8ef1291cedf80ff8320b90f0d17fb623c447e65f4fa48a17327d427d1aa6bb445c61dda9cc9b5c1611675e618fe6b79ab9bf045cfe0b1295aa72ff1c73d6641fb942a831506c0d268c628abff8925c011d222c443b73f18e994077f1c7a893123ed400cf2f11b8fa144c0d8fc5afcd2960281f067f1329cd4abf15a1a701762121b1e103db9538f989443fbc824d39fab22b622ec98632e957adbb39dd956f31b42af3629d8fcd461c9d4519105c0f308c7c45888583832c3c659b17b0733bc7257a9c00899a4ba9933c211579480e5c30d6837241a59d044280df466d55d0b46dcfa2db4c809023d77c2503ed3afd82489c98b0949baac0a00403af770e65c45607b615912eaf7c727b15cf976e742c1cb75fc160966dda4504edc7322a9479cb2617286c85b1412b9ffc067be3f9d2fd49568a29fe40115cf76de6dd7cc3f7e833bafbdffc39097150fe14960582a39d10102aa86718f59b102c89441806c80acfe300ea415be03162e729bd92a75b4e18a33470409034bc7fc7e9fb6c4823d6bfc77d75046c0927922dae805c7ced7a4a6fb4f46a15ee6b145e0dae56e6480ac726f7ccd5296fb2c82b5ebc2239eac2d81cea4fa789e0187134a270b6e74fc7932983e6e993e391ecc0dc9cb5963a644c6ece2eba6564533a330861eaac6120f84959e5b41ccbbe2255db17106a9955a94964ba07c5b1d1a0159f3f1e8f76f65181c17e3d616398df19fe6d8625922f5a0fd384b3238761a2003e5b5101c0c795c48ceb3ec72d9f7f5cf42df449075153f642f0ffb01414a5f6c7985855d752fd7c5a71d8eead5cf51681bb67adf267d5fb290933dff2aa25eff736616cb6437738b0121e4d14bdaf5e7e8009631ecb1f30bf574c7e3c6a79cd571dd08a38054cec4dce6fb7805a0fc8c729d75f1233bc85149e0bd1d13895855f71f2f57eda6101ed274c9e41468c28b97f5f969cc849ef5027c5afd57c24ae85e16e9659cd3954c9535d3218b20f191982df2dfad4fd13d0e33b24d83274ed6066814d70d41f84874c6072b9515aa5d2f0716d851a84533fd43f21c902fb54320e04fd0b7d05d169137482f82d46b275672a7c8aa51e4546ab494f0aa2dee67d5ea2476c0a7463af609727d52ac54054ee7f454f9420d5e9f5e28357c2b0745a466087d505919ae4923586649b5b3e845929c78ff4c22c6d5f8a31eb8eedb3351e6d5f9ce68dbb318a5d62bc116bdea32a77c46492a3ca52f06a00a14420daee72b97673ef6d4a3bc35a",
),
);
}
});
});
});

View File

@ -0,0 +1,130 @@
// Keep all classes requiring libsodium-js in one file as having multiple
// requiring of the libsodium-wrappers module currently crashes browsers
//
// libsodium.js API: https://gist.github.com/webmaster128/b2dbe6d54d36dd168c9fabf441b9b09c
import sodium from "libsodium-wrappers";
import { As } from "type-tagger";
export type Xchacha20poly1305IetfKey = Uint8Array & As<"xchacha20poly1305ietf-key">;
export type Xchacha20poly1305IetfMessage = Uint8Array & As<"xchacha20poly1305ietf-message">;
export type Xchacha20poly1305IetfNonce = Uint8Array & As<"xchacha20poly1305ietf-nonce">;
export type Xchacha20poly1305IetfCiphertext = Uint8Array & As<"xchacha20poly1305ietf-ciphertext">;
export interface Argon2idOptions {
// in bytes
readonly outputLength: number;
// integer between 1 and 4294967295
readonly opsLimit: number;
// memory limit measured in KiB (like argon2 command line tool)
// Note: only ~ 16 MiB of memory are available using the non-sumo version of libsodium
readonly memLimitKib: number;
}
export class Argon2id {
public static async execute(
password: string,
salt: Uint8Array,
options: Argon2idOptions,
): Promise<Uint8Array> {
await sodium.ready;
return sodium.crypto_pwhash(
options.outputLength,
password,
salt, // libsodium only supports 16 byte salts and will throw when you don't respect that
options.opsLimit,
options.memLimitKib * 1024,
sodium.crypto_pwhash_ALG_ARGON2ID13,
);
}
}
export class Ed25519Keypair {
// a libsodium privkey has the format `<ed25519 privkey> + <ed25519 pubkey>`
public static fromLibsodiumPrivkey(libsodiumPrivkey: Uint8Array): Ed25519Keypair {
if (libsodiumPrivkey.length !== 64) {
throw new Error(`Unexpected key length ${libsodiumPrivkey.length}. Must be 64.`);
}
return new Ed25519Keypair(libsodiumPrivkey.slice(0, 32), libsodiumPrivkey.slice(32, 64));
}
public readonly privkey: Uint8Array;
public readonly pubkey: Uint8Array;
public constructor(privkey: Uint8Array, pubkey: Uint8Array) {
this.privkey = privkey;
this.pubkey = pubkey;
}
public toLibsodiumPrivkey(): Uint8Array {
return new Uint8Array([...this.privkey, ...this.pubkey]);
}
}
export class Ed25519 {
/**
* Generates a keypair deterministically from a given 32 bytes seed.
*
* This seed equals the Ed25519 private key.
* For implementation details see crypto_sign_seed_keypair in
* https://download.libsodium.org/doc/public-key_cryptography/public-key_signatures.html
* and diagram on https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/
*/
public static async makeKeypair(seed: Uint8Array): Promise<Ed25519Keypair> {
await sodium.ready;
const keypair = sodium.crypto_sign_seed_keypair(seed);
return Ed25519Keypair.fromLibsodiumPrivkey(keypair.privateKey);
}
public static async createSignature(message: Uint8Array, keyPair: Ed25519Keypair): Promise<Uint8Array> {
await sodium.ready;
return sodium.crypto_sign_detached(message, keyPair.toLibsodiumPrivkey());
}
public static async verifySignature(
signature: Uint8Array,
message: Uint8Array,
pubkey: Uint8Array,
): Promise<boolean> {
await sodium.ready;
return sodium.crypto_sign_verify_detached(signature, message, pubkey);
}
}
export class Xchacha20poly1305Ietf {
public static async encrypt(
message: Xchacha20poly1305IetfMessage,
key: Xchacha20poly1305IetfKey,
nonce: Xchacha20poly1305IetfNonce,
): Promise<Xchacha20poly1305IetfCiphertext> {
await sodium.ready;
const additionalData = null;
return sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
message,
additionalData,
null, // secret nonce: unused and should be null (https://download.libsodium.org/doc/secret-key_cryptography/aead/chacha20-poly1305/xchacha20-poly1305_construction)
nonce,
key,
) as Xchacha20poly1305IetfCiphertext;
}
public static async decrypt(
ciphertext: Xchacha20poly1305IetfCiphertext,
key: Xchacha20poly1305IetfKey,
nonce: Xchacha20poly1305IetfNonce,
): Promise<Xchacha20poly1305IetfMessage> {
await sodium.ready;
const additionalData = null;
return sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
null, // secret nonce: unused and should be null (https://download.libsodium.org/doc/secret-key_cryptography/aead/chacha20-poly1305/xchacha20-poly1305_construction)
ciphertext,
additionalData,
nonce,
key,
) as Xchacha20poly1305IetfMessage;
}
}

View File

@ -0,0 +1,38 @@
import { isUint8Array } from "@iov/encoding";
import { Random } from "./random";
describe("Random", () => {
it("returns an Uint8Array", () => {
const data = Random.getBytes(5);
expect(isUint8Array(data)).toEqual(true);
});
it("creates random bytes", () => {
{
const bytes = Random.getBytes(0);
expect(bytes.length).toEqual(0);
}
{
const bytes = Random.getBytes(1);
expect(bytes.length).toEqual(1);
}
{
const bytes = Random.getBytes(32);
expect(bytes.length).toEqual(32);
}
{
const bytes = Random.getBytes(4096);
expect(bytes.length).toEqual(4096);
}
{
const bytes1 = Random.getBytes(32);
const bytes2 = Random.getBytes(32);
expect(bytes1).not.toEqual(bytes2);
}
});
});

View File

@ -0,0 +1,27 @@
declare const window: any;
declare const self: any;
export class Random {
/**
* Returns `count` cryptographically secure random bytes
*/
public static getBytes(count: number): Uint8Array {
try {
const globalObject = typeof window === "object" ? window : self;
const cryptoApi =
typeof globalObject.crypto !== "undefined" ? globalObject.crypto : globalObject.msCrypto;
const out = new Uint8Array(count);
cryptoApi.getRandomValues(out);
return out;
} catch {
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const crypto = require("crypto");
return new Uint8Array([...crypto.randomBytes(count)]);
} catch {
throw new Error("No secure random number generator found");
}
}
}
}

View File

@ -0,0 +1,28 @@
import { fromHex } from "@iov/encoding";
import { Ripemd160 } from "./ripemd";
import ripemdVectors from "./testdata/ripemd.json";
describe("Ripemd160", () => {
it("exists", () => {
expect(Ripemd160).toBeTruthy();
});
it("works for empty input", () => {
{
const hash = new Ripemd160(new Uint8Array([])).digest();
expect(hash).toEqual(fromHex("9c1185a5c5e9fc54612808977ee8f548b2258d31"));
}
{
const hash = new Ripemd160().digest();
expect(hash).toEqual(fromHex("9c1185a5c5e9fc54612808977ee8f548b2258d31"));
}
});
it("works for all the Botan test vectors", () => {
// https://github.com/randombit/botan/blob/2.12.1/src/tests/data/hash/ripemd160.vec
for (const { in: input, out: output } of ripemdVectors.ripemd160) {
expect(new Ripemd160(fromHex(input)).digest()).toEqual(fromHex(output));
}
});
});

View File

@ -0,0 +1,24 @@
import RIPEMD160 from "ripemd160";
import { HashFunction } from "./hash";
export class Ripemd160 implements HashFunction {
public readonly blockSize = 512 / 8;
private readonly impl = new RIPEMD160();
public constructor(firstData?: Uint8Array) {
if (firstData) {
this.update(firstData);
}
}
public update(data: Uint8Array): Ripemd160 {
this.impl.update(Buffer.from(data));
return this;
}
public digest(): Uint8Array {
return Uint8Array.from(this.impl.digest());
}
}

View File

@ -0,0 +1,589 @@
/* tslint:disable:no-bitwise */
import { fromHex } from "@iov/encoding";
import { Secp256k1 } from "./secp256k1";
import { ExtendedSecp256k1Signature, Secp256k1Signature } from "./secp256k1signature";
import { Sha256 } from "./sha";
describe("Secp256k1", () => {
// How to generate Secp256k1 test vectors:
// $ git clone https://github.com/pyca/cryptography.git && cd cryptography
// $ python2 -m virtualenv venv
// $ source venv/bin/activate
// $ pip install cryptography cryptography_vectors pytest ecdsa
// $ curl https://patch-diff.githubusercontent.com/raw/webmaster128/cryptography/pull/1.diff | git apply
//
// optionally normalize signatures to lowS representation:
// $ curl https://patch-diff.githubusercontent.com/raw/webmaster128/cryptography/pull/2.diff | git apply
//
// $ python ./docs/development/custom-vectors/secp256k1/generate_secp256k1.py > secp256k1_test_vectors.txt
it("encodes public key uncompressed when making a keypair", async () => {
// example data generated by OpenSSL (caution: LibreSSL 2.2.7 sometimes adds a leading 00 for the privkey):
// openssl ecparam -name secp256k1 -genkey | openssl ec -text -noout -conv_form uncompressed
const privkey = fromHex("5b1d5975dfdfb0027802265241d891e4af744cd39e78595658afaa7ac801d1d3");
const keypair = await Secp256k1.makeKeypair(privkey);
expect(keypair.pubkey).toEqual(
fromHex(
"043e1113575cf01f9281381f626deccf76cdc85a320052297f7cae3548ea024d0b9fbec8a56d345a2984050be859c96471ef6aa4669ff31a659ce1d32db372f9b9",
),
);
});
it("preserves private key when making a keypair", async () => {
const privkey = fromHex("8c8bc2bc7954db5ef751e3e84b4e99bbe387a90d8019d8066c0e1e8bf33e713f");
const keypair = await Secp256k1.makeKeypair(privkey);
expect(keypair.privkey).toEqual(
fromHex("8c8bc2bc7954db5ef751e3e84b4e99bbe387a90d8019d8066c0e1e8bf33e713f"),
);
});
it("can load private keys", async () => {
expect(
await Secp256k1.makeKeypair(
fromHex("5eaf4344dab73d0caee1fd03607bb969074fb217f076896c2125f8607feab7b1"),
),
).toBeTruthy();
expect(
await Secp256k1.makeKeypair(
fromHex("f7ac570ea2844e29e7f3b3c6a724ee1f47d3de8c2175a69abae94ae871573d0e"),
),
).toBeTruthy();
expect(
await Secp256k1.makeKeypair(
fromHex("e4ade2a5232a7c6f37e7b854a774e25e6047ee7c6d63e8304ae04fa190bc1732"),
),
).toBeTruthy();
// smallest and largest allowed values: 1 and N-1 (from https://crypto.stackexchange.com/a/30273)
expect(
await Secp256k1.makeKeypair(
fromHex("0000000000000000000000000000000000000000000000000000000000000001"),
),
).toBeTruthy();
expect(
await Secp256k1.makeKeypair(
fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140"),
),
).toBeTruthy();
// too short and too long
await Secp256k1.makeKeypair(fromHex("e4ade2a5232a7c6f37e7b854a774e25e6047ee7c6d63e8304ae04fa190bc17"))
.then(() => fail("promise must be rejected"))
.catch((error) => expect(error.message).toContain("not a valid secp256k1 private key"));
await Secp256k1.makeKeypair(fromHex("e4ade2a5232a7c6f37e7b854a774e25e6047ee7c6d63e8304ae04fa190bc1732aa"))
.then(() => fail("promise must be rejected"))
.catch((error) => expect(error.message).toContain("not a valid secp256k1 private key"));
// value out of range (too small)
await Secp256k1.makeKeypair(fromHex("0000000000000000000000000000000000000000000000000000000000000000"))
.then(() => fail("promise must be rejected"))
.catch((error) => expect(error.message).toContain("not a valid secp256k1 private key"));
// value out of range (>= n)
await Secp256k1.makeKeypair(fromHex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))
.then(() => fail("promise must be rejected"))
.catch((error) => expect(error.message).toContain("not a valid secp256k1 private key"));
await Secp256k1.makeKeypair(fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"))
.then(() => fail("promise must be rejected"))
.catch((error) => expect(error.message).toContain("not a valid secp256k1 private key"));
});
it("creates signatures", async () => {
const privkey = fromHex("43a9c17ccbb0e767ea29ce1f10813afde5f1e0a7a504e89b4d2cc2b952b8e0b9");
const keypair = await Secp256k1.makeKeypair(privkey);
const messageHash = new Uint8Array([0x11, 0x22]);
const signature = (await Secp256k1.createSignature(messageHash, keypair.privkey)).toDer();
expect(signature).toBeTruthy();
expect(signature.byteLength).toBeGreaterThanOrEqual(70);
expect(signature.byteLength).toBeLessThanOrEqual(72);
});
it("creates signatures deterministically", async () => {
const privkey = fromHex("43a9c17ccbb0e767ea29ce1f10813afde5f1e0a7a504e89b4d2cc2b952b8e0b9");
const keypair = await Secp256k1.makeKeypair(privkey);
const messageHash = new Uint8Array([0x11, 0x22]);
const signature1 = await Secp256k1.createSignature(messageHash, keypair.privkey);
const signature2 = await Secp256k1.createSignature(messageHash, keypair.privkey);
expect(signature1).toEqual(signature2);
});
it("throws for empty message hash in signing", async () => {
const privkey = fromHex("43a9c17ccbb0e767ea29ce1f10813afde5f1e0a7a504e89b4d2cc2b952b8e0b9");
const keypair = await Secp256k1.makeKeypair(privkey);
const messageHash = new Uint8Array([]);
await Secp256k1.createSignature(messageHash, keypair.privkey)
.then(() => fail("must not resolve"))
.catch((error) => expect(error).toMatch(/message hash must not be empty/i));
});
it("throws for message hash longer than 32 bytes in signing", async () => {
const privkey = fromHex("43a9c17ccbb0e767ea29ce1f10813afde5f1e0a7a504e89b4d2cc2b952b8e0b9");
const keypair = await Secp256k1.makeKeypair(privkey);
const messageHash = fromHex("11223344556677889900aabbccddeeff11223344556677889900aabbccddeeff11");
await Secp256k1.createSignature(messageHash, keypair.privkey)
.then(() => fail("must not resolve"))
.catch((error) => expect(error).toMatch(/message hash length must not exceed 32 bytes/i));
});
it("verifies signatures", async () => {
const privkey = fromHex("43a9c17ccbb0e767ea29ce1f10813afde5f1e0a7a504e89b4d2cc2b952b8e0b9");
const keypair = await Secp256k1.makeKeypair(privkey);
const messageHash = new Uint8Array([0x11, 0x22]);
const signature = await Secp256k1.createSignature(messageHash, keypair.privkey);
{
// valid
const ok = await Secp256k1.verifySignature(signature, messageHash, keypair.pubkey);
expect(ok).toEqual(true);
}
{
// messageHash corrupted
const corruptedMessageHash = messageHash.map((x, i) => (i === 0 ? x ^ 0x01 : x));
const ok = await Secp256k1.verifySignature(signature, corruptedMessageHash, keypair.pubkey);
expect(ok).toEqual(false);
}
{
// signature corrupted
const corruptedSignature = Secp256k1Signature.fromDer(
signature.toDer().map((x, i) => (i === 5 ? x ^ 0x01 : x)),
);
const ok = await Secp256k1.verifySignature(corruptedSignature, messageHash, keypair.pubkey);
expect(ok).toEqual(false);
}
{
// wrong pubkey
const otherPrivkey = fromHex("91099374790843e29552c3cfa5e9286d6c77e00a2c109aaf3d0a307081314a09");
const wrongPubkey = (await Secp256k1.makeKeypair(otherPrivkey)).pubkey;
const ok = await Secp256k1.verifySignature(signature, messageHash, wrongPubkey);
expect(ok).toEqual(false);
}
});
it("throws for empty message hash in verification", async () => {
const dummySignature = Secp256k1Signature.fromDer(
fromHex(
"304602210083de9be443bcf480892b8c8ca1d5ee65c79a315642c3f7b5305aff3065fda2780221009747932122b93cec42cad8ee4630a8f6cbe127578b8c495b4ab927275f657658",
),
);
const keypair = await Secp256k1.makeKeypair(
fromHex("43a9c17ccbb0e767ea29ce1f10813afde5f1e0a7a504e89b4d2cc2b952b8e0b9"),
);
const messageHash = new Uint8Array([]);
await Secp256k1.verifySignature(dummySignature, messageHash, keypair.pubkey)
.then(() => fail("must not resolve"))
.catch((error) => expect(error).toMatch(/message hash must not be empty/i));
});
it("throws for message hash longer than 32 bytes in verification", async () => {
const dummySignature = Secp256k1Signature.fromDer(
fromHex(
"304602210083de9be443bcf480892b8c8ca1d5ee65c79a315642c3f7b5305aff3065fda2780221009747932122b93cec42cad8ee4630a8f6cbe127578b8c495b4ab927275f657658",
),
);
const keypair = await Secp256k1.makeKeypair(
fromHex("43a9c17ccbb0e767ea29ce1f10813afde5f1e0a7a504e89b4d2cc2b952b8e0b9"),
);
const messageHash = fromHex("11223344556677889900aabbccddeeff11223344556677889900aabbccddeeff11");
await Secp256k1.verifySignature(dummySignature, messageHash, keypair.privkey)
.then(() => fail("must not resolve"))
.catch((error) => expect(error).toMatch(/message hash length must not exceed 32 bytes/i));
});
it("verifies unnormalized pyca/cryptography signatures", async () => {
// signatures are mixed lowS and non-lowS, prehash type is sha256
const data: readonly {
readonly message: Uint8Array;
readonly privkey: Uint8Array;
readonly signature: Uint8Array;
}[] = [
{
message: fromHex(
"5c868fedb8026979ebd26f1ba07c27eedf4ff6d10443505a96ecaf21ba8c4f0937b3cd23ffdc3dd429d4cd1905fb8dbcceeff1350020e18b58d2ba70887baa3a9b783ad30d3fbf210331cdd7df8d77defa398cdacdfc2e359c7ba4cae46bb74401deb417f8b912a1aa966aeeba9c39c7dd22479ae2b30719dca2f2206c5eb4b7",
),
privkey: fromHex("21142a7e90031ea750c9fa1ba1beae16782386be438133bd43195826ae2e25f0"),
signature: fromHex(
"30440220207082eb2c3dfa0b454e0906051270ba4074ac93760ba9e7110cd94714751111022051eb0dbbc9920e72146fb564f99d039802bf6ef2561446eb126ef364d21ee9c4",
),
},
{
message: fromHex(
"17cd4a74d724d55355b6fb2b0759ca095298e3fd1856b87ca1cb2df5409058022736d21be071d820b16dfc441be97fbcea5df787edc886e759475469e2128b22f26b82ca993be6695ab190e673285d561d3b6d42fcc1edd6d12db12dcda0823e9d6079e7bc5ff54cd452dad308d52a15ce9c7edd6ef3dad6a27becd8e001e80f",
),
privkey: fromHex("824282b6069fe3df857ce37204df4312c35750ee7a0f5e5fd8181666d5e46fb2"),
signature: fromHex(
"30440220626d61b7be1488b563e8a85bfb623b2331903964b5c0476c9f9ad29144f076fe02202002a2c0ab5e48626bf761cf677dfeede9c7309d2436d4b8c2b89f21ee2ebc6a",
),
},
{
message: fromHex(
"db0d31717b04802adbbae1997487da8773440923c09b869e12a57c36dda34af11b8897f266cd81c02a762c6b74ea6aaf45aaa3c52867eb8f270f5092a36b498f88b65b2ebda24afe675da6f25379d1e194d093e7a2f66e450568dbdffebff97c4597a00c96a5be9ba26deefcca8761c1354429622c8db269d6a0ec0cc7a8585c",
),
privkey: fromHex("5c0da42cec87a6b7a173514965343c30013386c0fe9b39203ed7af43ea425944"),
signature: fromHex(
"304602210083de9be443bcf480892b8c8ca1d5ee65c79a315642c3f7b5305aff3065fda2780221009747932122b93cec42cad8ee4630a8f6cbe127578b8c495b4ab927275f657658",
),
},
{
message: fromHex(
"47c9deddfd8c841a63a99be96e972e40fa035ae10d929babfc86c437b9d5d495577a45b7f8a35ce3f880e7d8ae8cd8eb685cf41f0506e68046ccf5559232c674abb9c3683829dcc8002683c4f4ca3a29a7bfde20d96dd0f1a0ead847dea18f297f220f94932536ca4deacedc2c6701c3ee50e28e358dcc54cdbf69daf0eb87f6",
),
privkey: fromHex("ab30a326599165b48c65ab8d3c77d312d7b2ea4853f721e18cc278628a866980"),
signature: fromHex(
"30440220723da69da81c8f6b081a9a728b9bba785d2067e0ed769675f8a7563d22ed8a1602203a993793cf39b96b3cd625df0e06f206e17579cd8ebcb7e704174c3d94dba684",
),
},
{
message: fromHex(
"f15433188c2bbc93b2150bb2f34d36ba8ae49f8f7e4e81aed651c8fb2022b2a7e851c4dbbbc21c14e27380316bfdebb0a049246349537dba687581c1344e40f75afd2735bb21ea074861de6801d28b22e8beb76fdd25598812b2061ca3fba229daf59a4ab416704543b02e16b8136c22acc7e197748ae19b5cbbc160fdc3a8cd",
),
privkey: fromHex("1560b9fa5229f623a9c556132da4fc0e58633f39ce6421d25b5a6cde4ad7e019"),
signature: fromHex(
"304502200e0c5228e6783bee4d0406f4f7b7d79f705f0dbb55126966f79e631bd8b23079022100faae33aec5b0fafd3413c14bfdef9c7c9ac6abd06c923c48ab136a2c56826118",
),
},
{
message: fromHex(
"1bc796124b87793b7f7fdd53b896f8f0d0f2d2be36d1944e3c2a0ac5c6b2839f59a4b4fad200f8035ec98630c51ef0d40863a5ddd69b703d73f06b4afae8ad1a88e19b1b26e8c10b7bff953c05eccc82fd771b220910165f3a906b7c931683e431998d1fd06c32dd11b4f872bf980d547942f22124c7e50c9523321aee23a36d",
),
privkey: fromHex("42f7d48e1c90f3d20252713f7c7c6ce8492c2b99bcef198927de334cda6bad00"),
signature: fromHex(
"3046022100b9d3962edadc893f8eeff379f136c7b8fc6ea824a5afc6cbda7e3cb4c7a1e860022100bb1c1f901cf450edfdce20686352bb0cf0a643301123140ec87c92480d7f9d6a",
),
},
{
message: fromHex(
"18e55ac264031da435b613fc9dc6c4aafc49aae8ddf6f220d523415896ff915fae5c5b2e6aed61d88e5721823f089c46173afc5d9b47fd917834c85284f62dda6ed2d7a6ff10eb553b9312b05dad7decf7f73b69479c02f14ea0a2aa9e05ec07396cd37c28795c90e590631137102315635d702278e352aa41d0826adadff5e1",
),
privkey: fromHex("6fe5b986c18da5d4fbcea6f602f469ac039085247ccb97b6292992363ea1d21c"),
signature: fromHex(
"30460221009369ab86afae5e22ed5f4012964804d2a19c36b8b58cf2855205b1cfcc937422022100a27dfc38d899b78edcf38a1b2b53578e72270b083d7d69424c4b4a7d25d39f4d",
),
},
{
message: fromHex(
"a5290666c97294d090f8da898e555cbd33990579e5e95498444bfb318b4aa1643e0d4348425e21c7c6f99f9955f3048f56c22b68c4a516af5c90ed5268acc9c5a20fec0200c2a282a90e20d3c46d4ecdda18ba18b803b19263de2b79238da921707a0864799cdee9f02913b40681c02c6923070688844b58fe415b7d71ea6845",
),
privkey: fromHex("fbf02ff086b215d057130a509346b64eb63bec0e38db692e07ad24c6ca8fe210"),
signature: fromHex(
"3045022100c5e439cef76b28dc0fe9d260763bec05b5e795ac8d90b25d9fccbc1918bc32f302201b06144e6b191224d5eda822a5b3b2026af6aa7f25a9061c9e81c312728aa94a",
),
},
{
message: fromHex(
"13ad0600229c2a66b2f11617f69c7210ad044c49265dc98ec3c64f56e56a083234d277d404e2c40523c414ad23af5cc2f91a47fe59e7ca572f7fe1d3d3cfceaedadac4396749a292a38e92727273272335f12b2acea21cf069682e67d7e7d7a31ab5bb8e472298a9451aeae6f160f36e6623c9b632b9c93371a002818addc243",
),
privkey: fromHex("474a7dc7f5033b6bf5e3027254cd0dbd956f16f61874b2992839a867f607d0dd"),
signature: fromHex(
"3045022100ee8615a5fab6fc674e6d3d9cde8da2b18dece076ae94d96662e16109db12d72002203171705cdab2b3d34c58e556c80358c105807e98243f5754b70b771071308b94",
),
},
{
message: fromHex(
"51ad843da5eafc177d49a50a82609555e52773c5dfa14d7c02db5879c11a6b6e2e0860df38452dc579d763f91a83ade23b73f4fcbd703f35dd6ecfbb4c9578d5b604ed809c8633e6ac5679a5f742ce94fea3b97b5ba8a29ea28101a7b35f9eaa894dda54e3431f2464d18faf8342b7c59dfe0598c0ab29a14622a08eea70126b",
),
privkey: fromHex("e8a2939a46e6bb7e706e419c0101d39f0494935b17fe3ca907b2ea3558d6ab3a"),
signature: fromHex(
"3046022100f753c447161aa3a58e5deeca31797f21484fb0ec3a7fe6e464ab1914896f253b02210099640fbcce1f25fd66744b046e0dfd57fa23070555f438af6c5e5828d47e9fa7",
),
},
{
message: fromHex(
"678b505467d55ce01aec23fd4851957137c3a1de3ff2c673ec95a577aa9fb011b4b4a8eb7a0e6f391d4236a35b7e769692ace5851d7c53700e180fa522d3d37dbaa496163f3de6d96391e38ff83271e621f2458729ff74de462cdce6b3029f308d4eb8aef036357b9de06d68558e0388a6e88af91340c875050b8c91c4e26fc8",
),
privkey: fromHex("08ce8f7118eda55b008f6eb3281a445a3ddbc5209d5ac16c09dbf40fe4bbc22c"),
signature: fromHex(
"30440220439fd0423bde36a1616a6fa4343bb7e07a6b3f6dc629aa8c93c91831055e476c022020998a26ae4b96ef36d48d83e8af0288f0bbc2db5ca5c8271a42f3fdc478fcb2",
),
},
{
message: fromHex(
"9bf457159f0d44b78d0e151ee53c41cecd98fb4e4129fcda8cc84a758636f84dcad9032f3ec422219d8a7ec61ea89f45d19cab3c3d451de1a634e3d2532231bc03031973d7150cf8e83d8b6a34f25fc136446878e3851b780abdca069c8e981b3ea3f1bf1ff6e47a03f97aed64c1cc90dd00389fa21bb973f142af5e8ceccef4",
),
privkey: fromHex("820be5c5e14e802300ca024fce318910f00470f6c3eabb12e7f3fac9383cf247"),
signature: fromHex(
"304502204ce72a83cf1d148db4d1e46e2f773c677f72933c40d7100b9192750a1c8222a80221009d5fbd67ce89ba8c79df9dc3b42922026a8498921c2bdb4ea8f36496d88c2cfb",
),
},
{
message: fromHex(
"2469172b7a046e6112dfe365590dfddb7c045cccd4ab353edc3076091aad1c780a9a73ff93f3dbf9e2189c5d1fdd6f6167d0ae8cc0f53dc8950e60dd0410e23589999d4ce4fa49e268774defd4edce01c05b205014b63591a041745bfffc6ae4d72d3add353e49478106653cc735b07b0fe665c42d0e6766e525bb9718264c87",
),
privkey: fromHex("d92d6170e63bc33647e6dcdf1981771ecd57e11d47d73138696fbf49a430c3ab"),
signature: fromHex(
"304502201f1e1fb673e9a7dee09961c6824b473189904deb4f0d8e28da51f77f4de2efe6022100ae8df1fcdb226fac8b46e494720e45f6d9a5350174faaf22e47b6329ee6c5e1b",
),
},
{
message: fromHex(
"6f8983e74f304c3657cffde0682b42699cb2c3475b925058ff37292c40a0aa296690ad129730339ac60cf784225b2fd3db58297c8ce5889df7a48d3e74a363ae4135e8a234cab53ca4c11c031d561a6cf7dd47b925ed5bc4c2794ba7b74a868b0c3da31ff1e4540d0768612192a236d86f74fb8c73f375b71c62f1648c0e6126",
),
privkey: fromHex("a70eb435feaeb6ccda7d3ebd3c4ae40b60643bc933f37ad1aca41dd086e8ae50"),
signature: fromHex(
"30460221009cf7d941dcbbbe61c2a6f5112cb518094e79e5d203891de2247e75fd532c3f21022100fc5a04579b2526f2543efd2a57e82b647da08b6924bff39cf021398a56ad70de",
),
},
{
message: fromHex(
"6fbe6f0f178fdc8a3ad1a8eecb02d37108c5831281fe85e3ff8eeb66ca1082a217b6d602439948f828e140140412544f994da75b6efc203b295235deca060ecfc7b71f05e5af2acc564596772ddbfb4078b4665f6b85f4e70641af26e31f6a14e5c88604459df4eeeed9b77b33c4b82a3c1458bd2fd1dc7214c04f9c79c8f09b",
),
privkey: fromHex("34a677d6f0c132eeffc3451b61e5d55969399699019ac929e6fdb5215d37be5e"),
signature: fromHex(
"3045022059cd6c2a30227afbd693d87b201d0989435d6e116c144276a5223466a822c0f2022100b01495efda969b3fd3a2c05aa098a4e04b0d0e748726fc6174627da15b143799",
),
},
{
message: fromHex(
"2b49de971bb0f705a3fb5914eb7638d72884a6c3550667dbfdf301adf26bde02f387fd426a31be6c9ff8bfe8690c8113c88576427f1466508458349fc86036afcfb66448b947707e791e71f558b2bf4e7e7507773aaf4e9af51eda95cbce0a0f752b216f8a54a045d47801ff410ee411a1b66a516f278327df2462fb5619470e",
),
privkey: fromHex("2258cdecaf3510bc398d08c000245cadceadcf149022730d93b176b4844713e1"),
signature: fromHex(
"30460221009eaf69170aeba44966afe957295526ee9852b5034c18dc5aeef3255c8567838a022100ebd4c8de2c22b5cb8803d6e070186786f6d5dae2202b9f899276fa31a66cb3bb",
),
},
{
message: fromHex(
"1fa7201d96ad4d190415f2656d1387fa886afc38e5cd18b8c60da367acf32c627d2c9ea19ef3f030e559fc2a21695cdbb65ddf6ba36a70af0d3fa292a32de31da6acc6108ab2be8bd37843338f0c37c2d62648d3d49013edeb9e179dadf78bf885f95e712fcdfcc8a172e47c09ab159f3a00ed7b930f628c3c48257e92fc7407",
),
privkey: fromHex("a67cf8cead99827c7956327aa04ab30cfd2d67f21b78f28a35694ece51052a61"),
signature: fromHex(
"304402210091058d1b912514940e1002855cc930c01a21234bad88f607f213af495c32b69f021f5d387ce3de25f1b9bad1fb180de110686d91b461ae2972fa4e4a7018519870",
),
},
{
message: fromHex(
"74715fe10748a5b98b138f390f7ca9629c584c5d6ad268fc455c8de2e800b73fa1ea9aaee85de58baa2ce9ce68d822fc31842c6b153baef3a12bf6b4541f74af65430ae931a64c8b4950ad1c76b31aea8c229b3623390e233c112586aa5907bbe419841f54f0a7d6d19c003b91dc84bbb59b14ec477a1e9d194c137e21c75bbb",
),
privkey: fromHex("4f1050422c4fce146bab0d735a70a91d6447210964b064309f90315c986be400"),
signature: fromHex(
"3046022100fe43eb9c38b506d118e20f8605ac8954fc0406efd306ba7ea5b07577a2735d15022100d589e91bf5014c7c360342ad135259dd7ae684e2c21234d7a912b43d148fcf19",
),
},
{
message: fromHex(
"d10131982dd1a1d839aba383cd72855bf41061c0cb04dfa1acad3181f240341d744ca6002b52f25fb3c63f16d050c4a4ef2c0ebf5f16ce987558f4b9d4a5ad3c6b81b617de00e04ba32282d8bf223bfedbb325b741dfdc8f56fa85c65d42f05f6a1330d8cc6664ad32050dd7b9e3993f4d6c91e5e12cbd9e82196e009ad22560",
),
privkey: fromHex("79506f5f68941c60a0d7c62595652a5f42f2b9f5aa2b6456af1c56a79a346c2f"),
signature: fromHex(
"3046022100ccdbbd2500043bf7f705536d5984ab5f05fdc0fa3cf464d8c88f861e3fc8e54c022100d5c6342c08dcd8242e1daf3595cae968e320a025aa45ec4bc725795da3d1becb",
),
},
{
message: fromHex(
"ef9dbd90ded96ad627a0a987ab90537a3e7acc1fdfa991088e9d999fd726e3ce1e1bd89a7df08d8c2bf51085254c89dc67bc21e8a1a93f33a38c18c0ce3880e958ac3e3dbe8aec49f981821c4ac6812dd29fab3a9ebe7fbd799fb50f12021b48d1d9abca8842547b3b99befa612cc8b4ca5f9412e0352e72ab1344a0ac2913db",
),
privkey: fromHex("4c53b8e372f70593afb08fb0f3ba228e1bd2430f562414e9bd1b89e53becbac8"),
signature: fromHex(
"304402205c707b6df7667324f950216b933d28e307a0223b24d161bc5887208d7f880b3a02204b7bc56586dc51d806ac3ad72807bc62d1d06d0812f121bd91e9770d84885c39",
),
},
];
for (const [index, row] of data.entries()) {
const pubkey = (await Secp256k1.makeKeypair(row.privkey)).pubkey;
const messageHash = new Sha256(row.message).digest();
const isValid = await Secp256k1.verifySignature(
Secp256k1Signature.fromDer(row.signature),
messageHash,
pubkey,
);
expect(isValid).withContext(`(index ${index})`).toEqual(true);
}
});
it("matches normalized pyca/cryptography signatures", async () => {
// signatures are normalized to lowS, prehash type is sha256
const data: readonly {
readonly message: Uint8Array;
readonly privkey: Uint8Array;
readonly signature: Uint8Array;
}[] = [
{
message: fromHex(
"5c868fedb8026979ebd26f1ba07c27eedf4ff6d10443505a96ecaf21ba8c4f0937b3cd23ffdc3dd429d4cd1905fb8dbcceeff1350020e18b58d2ba70887baa3a9b783ad30d3fbf210331cdd7df8d77defa398cdacdfc2e359c7ba4cae46bb74401deb417f8b912a1aa966aeeba9c39c7dd22479ae2b30719dca2f2206c5eb4b7",
),
privkey: fromHex("1812bcfaa7566ba0724846d47dd4cc39306a506382cba33710ce6abd4d86553c"),
signature: fromHex(
"3044022045c0b7f8c09a9e1f1cea0c25785594427b6bf8f9f878a8af0b1abbb48e16d09202200d8becd0c220f67c51217eecfd7184ef0732481c843857e6bc7fc095c4f6b788",
),
},
{
message: fromHex(
"17cd4a74d724d55355b6fb2b0759ca095298e3fd1856b87ca1cb2df5409058022736d21be071d820b16dfc441be97fbcea5df787edc886e759475469e2128b22f26b82ca993be6695ab190e673285d561d3b6d42fcc1edd6d12db12dcda0823e9d6079e7bc5ff54cd452dad308d52a15ce9c7edd6ef3dad6a27becd8e001e80f",
),
privkey: fromHex("1aea57ea357cecc13b876b76a1825f433ff603d76e6794fdb55aeda481b9482b"),
signature: fromHex(
"304402204e0ea79d4a476276e4b067facdec7460d2c98c8a65326a6e5c998fd7c650611402200e45aea5034af973410e65cf97651b3f2b976e3fc79c6a93065ed7cb69a2ab5a",
),
},
{
message: fromHex(
"db0d31717b04802adbbae1997487da8773440923c09b869e12a57c36dda34af11b8897f266cd81c02a762c6b74ea6aaf45aaa3c52867eb8f270f5092a36b498f88b65b2ebda24afe675da6f25379d1e194d093e7a2f66e450568dbdffebff97c4597a00c96a5be9ba26deefcca8761c1354429622c8db269d6a0ec0cc7a8585c",
),
privkey: fromHex("03708999fddd22091e93a8fd6b2205b662089a97507623cb5ce04240bcae55b8"),
signature: fromHex(
"3045022100f25b86e1d8a11d72475b3ed273b0781c7d7f6f9e1dae0dd5d3ee9b84f3fab891022063d9c4e1391de077244583e9a6e3d8e8e1f236a3bf5963735353b93b1a3ba935",
),
},
{
message: fromHex(
"47c9deddfd8c841a63a99be96e972e40fa035ae10d929babfc86c437b9d5d495577a45b7f8a35ce3f880e7d8ae8cd8eb685cf41f0506e68046ccf5559232c674abb9c3683829dcc8002683c4f4ca3a29a7bfde20d96dd0f1a0ead847dea18f297f220f94932536ca4deacedc2c6701c3ee50e28e358dcc54cdbf69daf0eb87f6",
),
privkey: fromHex("44da7ab9eab17b93175bf4d5388c6b334f35a3283215b9e602a264d2e831fea3"),
signature: fromHex(
"3045022100f2cab57d108aaf7c9c9dd061404447d59f968d1468b25dd827d624b64601c32a022077558dbf7bf90885b9128c371959085e9dd1b7d8a5c45b7265e8e7d9f125c008",
),
},
{
message: fromHex(
"f15433188c2bbc93b2150bb2f34d36ba8ae49f8f7e4e81aed651c8fb2022b2a7e851c4dbbbc21c14e27380316bfdebb0a049246349537dba687581c1344e40f75afd2735bb21ea074861de6801d28b22e8beb76fdd25598812b2061ca3fba229daf59a4ab416704543b02e16b8136c22acc7e197748ae19b5cbbc160fdc3a8cd",
),
privkey: fromHex("5452b1bf1b1d96929f7ee3637a1bca637490f97634e6d164aeddda3dbb243a26"),
signature: fromHex(
"3045022100d702bec0f058f5e18f5fcdb204f79250562f11121f5513ae1006c9b93ddafb11022063de551c508405a280a21fb007b660542b58fcd3256b7cea45e3f2ebe9a29ecd",
),
},
{
message: fromHex(
"1bc796124b87793b7f7fdd53b896f8f0d0f2d2be36d1944e3c2a0ac5c6b2839f59a4b4fad200f8035ec98630c51ef0d40863a5ddd69b703d73f06b4afae8ad1a88e19b1b26e8c10b7bff953c05eccc82fd771b220910165f3a906b7c931683e431998d1fd06c32dd11b4f872bf980d547942f22124c7e50c9523321aee23a36d",
),
privkey: fromHex("a4095a3d464d20ea154f4312c087bd22c8a92207717cca40b1f3267e13cbf05c"),
signature: fromHex(
"3045022100ae17ab6a3bd2ccd0901cc3904103e825895540bf416a5f717b74b529512e4c1802204bc049a8a2287cfccea77fb3769755ba92c35154c635448cf633244edf4f6fe1",
),
},
{
message: fromHex(
"18e55ac264031da435b613fc9dc6c4aafc49aae8ddf6f220d523415896ff915fae5c5b2e6aed61d88e5721823f089c46173afc5d9b47fd917834c85284f62dda6ed2d7a6ff10eb553b9312b05dad7decf7f73b69479c02f14ea0a2aa9e05ec07396cd37c28795c90e590631137102315635d702278e352aa41d0826adadff5e1",
),
privkey: fromHex("4babce8321dcd2b5e3ac936e278519fb4b9be96688bbefb2e87d53b863a349b8"),
signature: fromHex(
"3044022003b51d02eac41f2969fc36c816c9772da21a139376b09d1c8809bb8f543be62f02200629c1396ae304d2c2e7b63890d91e56dfc3459f4d664cb914c7ff2a12a21925",
),
},
{
message: fromHex(
"a5290666c97294d090f8da898e555cbd33990579e5e95498444bfb318b4aa1643e0d4348425e21c7c6f99f9955f3048f56c22b68c4a516af5c90ed5268acc9c5a20fec0200c2a282a90e20d3c46d4ecdda18ba18b803b19263de2b79238da921707a0864799cdee9f02913b40681c02c6923070688844b58fe415b7d71ea6845",
),
privkey: fromHex("02f98a6eb5320fc0c65f3eb2911b8500bedf47230bdfa2ba5e1c0c1c0bf9fc0a"),
signature: fromHex(
"30440220400f52f4c4925b4b8886706331535230fafb6455c3a3eef6fbf19a82593812300220727cc4b3341d7d95d0dc404d910dc009b3b5f21baadc0c4ee199a46e558d7f56",
),
},
{
message: fromHex(
"13ad0600229c2a66b2f11617f69c7210ad044c49265dc98ec3c64f56e56a083234d277d404e2c40523c414ad23af5cc2f91a47fe59e7ca572f7fe1d3d3cfceaedadac4396749a292a38e92727273272335f12b2acea21cf069682e67d7e7d7a31ab5bb8e472298a9451aeae6f160f36e6623c9b632b9c93371a002818addc243",
),
privkey: fromHex("f2a08a52b0edbaa64dacb3244666d4fdb684e6cc995bed81ec9d86c58f9999de"),
signature: fromHex(
"3045022100b2927afc8856b7e14d02e01e7aa3c76951a4621bfde5d794adda165b51dbe198022006eee6e0b087143ed06933cba699fbe4097ba7d7b038b173cbbd183718a86d43",
),
},
{
message: fromHex(
"51ad843da5eafc177d49a50a82609555e52773c5dfa14d7c02db5879c11a6b6e2e0860df38452dc579d763f91a83ade23b73f4fcbd703f35dd6ecfbb4c9578d5b604ed809c8633e6ac5679a5f742ce94fea3b97b5ba8a29ea28101a7b35f9eaa894dda54e3431f2464d18faf8342b7c59dfe0598c0ab29a14622a08eea70126b",
),
privkey: fromHex("e27f10d444fa50b53283863941619b11d585b27018b0e29301371371b45e3c65"),
signature: fromHex(
"3044022100fe9717965673fbe585780e18d892a3cfa77b59ac2f44f5337a3e58ce6ecd4409021f155459b19d2e9a2e676d7d8d48a9303391ffb9befdd3a57324306d69e0e0ab",
),
},
];
for (const [index, row] of data.entries()) {
const keypair = await Secp256k1.makeKeypair(row.privkey);
const messageHash = new Sha256(row.message).digest();
// create signature
const calculatedSignature = await Secp256k1.createSignature(messageHash, row.privkey);
// verify calculated signature
const ok1 = await Secp256k1.verifySignature(calculatedSignature, messageHash, keypair.pubkey);
expect(ok1).withContext(`(index ${index})`).toEqual(true);
// verify original signature
const ok2 = await Secp256k1.verifySignature(
Secp256k1Signature.fromDer(row.signature),
messageHash,
keypair.pubkey,
);
expect(ok2).withContext(`(index ${index})`).toEqual(true);
// compare signatures
expect(calculatedSignature.toDer()).withContext(`(index ${index})`).toEqual(row.signature);
}
});
describe("recoverPubkey", () => {
it("can recover pubkey", async () => {
{
// Test data from https://github.com/ethereumjs/ethereumjs-util/blob/v6.1.0/test/index.js#L496
const expectedPubkey = (
await Secp256k1.makeKeypair(
fromHex("3c9229289a6125f7fdf1885a77bb12c37a8d3b4962d936f7e3084dece32a3ca1"),
)
).pubkey;
const signature = new ExtendedSecp256k1Signature(
fromHex("99e71a99cb2270b8cac5254f9e99b6210c6c10224a1579cf389ef88b20a1abe9"),
fromHex("129ff05af364204442bdb53ab6f18a99ab48acc9326fa689f228040429e3ca66"),
0,
);
const messageHash = fromHex("82ff40c0a986c6a5cfad4ddf4c3aa6996f1a7837f9c398e17e5de5cbd5a12b28");
const pubkey = Secp256k1.recoverPubkey(signature, messageHash);
expect(pubkey).toEqual(expectedPubkey);
}
{
// Test data from https://github.com/randombit/botan/blob/2.9.0/src/tests/data/pubkey/ecdsa_key_recovery.vec
const expectedPubkeyX = "F3F8BB913AA68589A2C8C607A877AB05252ADBD963E1BE846DDEB8456942AEDC";
const expectedPubkeyY = "A2ED51F08CA3EF3DAC0A7504613D54CD539FC1B3CBC92453CD704B6A2D012B2C";
const expectedPubkey = fromHex(`04${expectedPubkeyX}${expectedPubkeyY}`);
const r = fromHex("E30F2E6A0F705F4FB5F8501BA79C7C0D3FAC847F1AD70B873E9797B17B89B390");
const s = fromHex("81F1A4457589F30D76AB9F89E748A68C8A94C30FE0BAC8FB5C0B54EA70BF6D2F");
const signature = new ExtendedSecp256k1Signature(r, s, 0);
const messageHash = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
const pubkey = Secp256k1.recoverPubkey(signature, messageHash);
expect(pubkey).toEqual(expectedPubkey);
}
});
});
describe("compressPubkey", () => {
it("throws for a pubkey with invalid length", () => {
const pubkey = fromHex("aa".repeat(32));
expect(() => Secp256k1.compressPubkey(pubkey)).toThrowError(/invalid pubkey length/i);
});
it("returns a compressed pubkey unchanged", () => {
const pubkey = fromHex("02d41a0aa167b21699429eab224bc03f2cd386f0af5d20cefbd0336f1544aea24f");
expect(Secp256k1.compressPubkey(pubkey)).toEqual(pubkey);
});
it("compresses an uncompressed pubkey", () => {
// Test data generated at https://iancoleman.io/bitcoin-key-compression/
const pubkey = fromHex(
"044f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c7029013b587a681e836cc187a8164b98a5848a2b89b3173315fdd0740d5032e259cd5",
);
const compressed = fromHex("034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c70290");
expect(Secp256k1.compressPubkey(pubkey)).toEqual(compressed);
});
});
describe("trimRecoveryByte", () => {
it("throws for a signature with invalid length", () => {
const signature = fromHex("aa".repeat(66));
expect(() => Secp256k1.trimRecoveryByte(signature)).toThrowError(/invalid signature length/i);
});
it("returns a trimmed signature", () => {
const signature = fromHex("aa".repeat(64));
expect(Secp256k1.trimRecoveryByte(signature)).toEqual(signature);
});
it("trims a signature with recovery byte", () => {
const signature = fromHex("aa".repeat(64) + "bb");
expect(Secp256k1.trimRecoveryByte(signature)).toEqual(fromHex("aa".repeat(64)));
});
});
});

View File

@ -0,0 +1,137 @@
import { fromHex, toHex } from "@iov/encoding";
import BN from "bn.js";
import elliptic from "elliptic";
import { As } from "type-tagger";
import { ExtendedSecp256k1Signature, Secp256k1Signature } from "./secp256k1signature";
const secp256k1 = new elliptic.ec("secp256k1");
const secp256k1N = new BN("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", "hex");
interface Keypair {
readonly pubkey: Uint8Array;
readonly privkey: Uint8Array;
}
export type Secp256k1Keypair = Keypair & As<"secp256k1-keypair">;
export class Secp256k1 {
public static async makeKeypair(privkey: Uint8Array): Promise<Secp256k1Keypair> {
if (privkey.length !== 32) {
// is this check missing in secp256k1.validatePrivateKey?
// https://github.com/bitjson/bitcoin-ts/issues/4
throw new Error("input data is not a valid secp256k1 private key");
}
const keypair = secp256k1.keyFromPrivate(privkey);
if (keypair.validate().result !== true) {
throw new Error("input data is not a valid secp256k1 private key");
}
// range test that is not part of the elliptic implementation
const privkeyAsBigInteger = new BN(privkey);
if (privkeyAsBigInteger.gte(secp256k1N)) {
// not strictly smaller than N
throw new Error("input data is not a valid secp256k1 private key");
}
const out: Keypair = {
privkey: fromHex(keypair.getPrivate("hex")),
// encodes uncompressed as
// - 1-byte prefix "04"
// - 32-byte x coordinate
// - 32-byte y coordinate
pubkey: Uint8Array.from(keypair.getPublic("array")),
};
return out as Secp256k1Keypair;
}
// Creates a signature that is
// - deterministic (RFC 6979)
// - lowS signature
// - DER encoded
public static async createSignature(
messageHash: Uint8Array,
privkey: Uint8Array,
): Promise<ExtendedSecp256k1Signature> {
if (messageHash.length === 0) {
throw new Error("Message hash must not be empty");
}
if (messageHash.length > 32) {
throw new Error("Message hash length must not exceed 32 bytes");
}
const keypair = secp256k1.keyFromPrivate(privkey);
// the `canonical` option ensures creation of lowS signature representations
const { r, s, recoveryParam } = keypair.sign(messageHash, { canonical: true });
if (typeof recoveryParam !== "number") throw new Error("Recovery param missing");
return new ExtendedSecp256k1Signature(
Uint8Array.from(r.toArray()),
Uint8Array.from(s.toArray()),
recoveryParam,
);
}
public static async verifySignature(
signature: Secp256k1Signature,
messageHash: Uint8Array,
pubkey: Uint8Array,
): Promise<boolean> {
if (messageHash.length === 0) {
throw new Error("Message hash must not be empty");
}
if (messageHash.length > 32) {
throw new Error("Message hash length must not exceed 32 bytes");
}
const keypair = secp256k1.keyFromPublic(pubkey);
// From https://github.com/indutny/elliptic:
//
// Sign the message's hash (input must be an array, or a hex-string)
//
// Signature MUST be either:
// 1) DER-encoded signature as hex-string; or
// 2) DER-encoded signature as buffer; or
// 3) object with two hex-string properties (r and s); or
// 4) object with two buffer properties (r and s)
//
// Uint8Array is not a Buffer, but elliptic seems to be happy with the interface
// common to both types. Uint8Array is not an array of ints but the interface is
// similar
try {
return keypair.verify(messageHash, signature.toDer());
} catch (error) {
return false;
}
}
public static recoverPubkey(signature: ExtendedSecp256k1Signature, messageHash: Uint8Array): Uint8Array {
const signatureForElliptic = { r: toHex(signature.r()), s: toHex(signature.s()) };
const point = secp256k1.recoverPubKey(messageHash, signatureForElliptic, signature.recovery);
const keypair = secp256k1.keyFromPublic(point);
return fromHex(keypair.getPublic(false, "hex"));
}
public static compressPubkey(pubkey: Uint8Array): Uint8Array {
switch (pubkey.length) {
case 33:
return pubkey;
case 65:
return Uint8Array.from(secp256k1.keyFromPublic(pubkey).getPublic(true, "array"));
default:
throw new Error("Invalid pubkey length");
}
}
public static trimRecoveryByte(signature: Uint8Array): Uint8Array {
switch (signature.length) {
case 64:
return signature;
case 65:
return signature.slice(0, 64);
default:
throw new Error("Invalid signature length");
}
}
}

View File

@ -0,0 +1,162 @@
import { fromHex } from "@iov/encoding";
import { ExtendedSecp256k1Signature, Secp256k1Signature } from "./secp256k1signature";
describe("Secp256k1Signature", () => {
describe("fromFixedLength", () => {
it("works", () => {
const data = fromHex(
"000000000000000000000000000000000000000000000000000000000000223300000000000000000000000000000000000000000000000000000000000000aa",
);
const signature = Secp256k1Signature.fromFixedLength(data);
expect(signature.r()).toEqual(new Uint8Array([0x22, 0x33]));
expect(signature.s()).toEqual(new Uint8Array([0xaa]));
});
it("throws for invalid length", () => {
const data = fromHex(
"000000000000000000000000000000000000000000000000000000000000223300000000000000000000000000000000000000000000000000000000000000aa01",
);
expect(() => Secp256k1Signature.fromFixedLength(data)).toThrowError(/got invalid data length/i);
});
});
it("can be constructed", () => {
const signature = new Secp256k1Signature(new Uint8Array([0x22, 0x33]), new Uint8Array([0xaa]));
expect(signature).toBeTruthy();
});
it("can get r and s", () => {
const signature = new Secp256k1Signature(new Uint8Array([0x22, 0x33]), new Uint8Array([0xaa]));
expect(signature.r()).toEqual(new Uint8Array([0x22, 0x33]));
expect(signature.s()).toEqual(new Uint8Array([0xaa]));
});
it("can padd r and s", () => {
const signature = new Secp256k1Signature(new Uint8Array([0x22, 0x33]), new Uint8Array([0xaa]));
expect(signature.r(2)).toEqual(fromHex("2233"));
expect(signature.r(5)).toEqual(fromHex("0000002233"));
expect(signature.r(32)).toEqual(
fromHex("0000000000000000000000000000000000000000000000000000000000002233"),
);
expect(signature.s(1)).toEqual(fromHex("AA"));
expect(signature.s(2)).toEqual(fromHex("00AA"));
expect(signature.s(7)).toEqual(fromHex("000000000000AA"));
});
it("throws when output size of r or s is too small", () => {
const signature = new Secp256k1Signature(
new Uint8Array([0x22, 0x33, 0x44]),
new Uint8Array([0xaa, 0xbb]),
);
expect(() => signature.r(0)).toThrowError(/length too small to hold parameter r/i);
expect(() => signature.r(1)).toThrowError(/length too small to hold parameter r/i);
expect(() => signature.r(2)).toThrowError(/length too small to hold parameter r/i);
expect(() => signature.s(0)).toThrowError(/length too small to hold parameter s/i);
expect(() => signature.s(1)).toThrowError(/length too small to hold parameter s/i);
});
it("throws for r with leading 0", () => {
expect(
() =>
new Secp256k1Signature(
fromHex("00F25B86E1D8A11D72475B3ED273B0781C7D7F6F9E1DAE0DD5D3EE9B84F3FAB891"),
new Uint8Array([0xaa]),
),
).toThrowError(/unsigned integer r must be encoded as unpadded big endian./i);
});
it("throws for s with leading 0", () => {
expect(
() =>
new Secp256k1Signature(
new Uint8Array([0xaa]),
fromHex("00F25B86E1D8A11D72475B3ED273B0781C7D7F6F9E1DAE0DD5D3EE9B84F3FAB891"),
),
).toThrowError(/unsigned integer s must be encoded as unpadded big endian./i);
});
it("can encode to DER", () => {
// Signature 3045022100f25b86e1d8a11d72475b3ed273b0781c7d7f6f9e1dae0dd5d3ee9b84f3fab891022063d9c4e1391de077244583e9a6e3d8e8e1f236a3bf5963735353b93b1a3ba935
// decoded by http://asn1-playground.oss.com/
const signature = new Secp256k1Signature(
fromHex("F25B86E1D8A11D72475B3ED273B0781C7D7F6F9E1DAE0DD5D3EE9B84F3FAB891"),
fromHex("63D9C4E1391DE077244583E9A6E3D8E8E1F236A3BF5963735353B93B1A3BA935"),
);
expect(signature.toDer()).toEqual(
fromHex(
"3045022100f25b86e1d8a11d72475b3ed273b0781c7d7f6f9e1dae0dd5d3ee9b84f3fab891022063d9c4e1391de077244583e9a6e3d8e8e1f236a3bf5963735353b93b1a3ba935",
),
);
});
it("can decode from DER", () => {
// Signature 3045022100f25b86e1d8a11d72475b3ed273b0781c7d7f6f9e1dae0dd5d3ee9b84f3fab891022063d9c4e1391de077244583e9a6e3d8e8e1f236a3bf5963735353b93b1a3ba935
// decoded by http://asn1-playground.oss.com/
const signature = Secp256k1Signature.fromDer(
fromHex(
"3045022100f25b86e1d8a11d72475b3ed273b0781c7d7f6f9e1dae0dd5d3ee9b84f3fab891022063d9c4e1391de077244583e9a6e3d8e8e1f236a3bf5963735353b93b1a3ba935",
),
);
expect(signature.toDer()).toEqual(
fromHex(
"3045022100f25b86e1d8a11d72475b3ed273b0781c7d7f6f9e1dae0dd5d3ee9b84f3fab891022063d9c4e1391de077244583e9a6e3d8e8e1f236a3bf5963735353b93b1a3ba935",
),
);
expect(signature.r()).toEqual(
fromHex("F25B86E1D8A11D72475B3ED273B0781C7D7F6F9E1DAE0DD5D3EE9B84F3FAB891"),
);
expect(signature.s()).toEqual(
fromHex("63D9C4E1391DE077244583E9A6E3D8E8E1F236A3BF5963735353B93B1A3BA935"),
);
});
});
describe("ExtendedSecp256k1Signature", () => {
it("can be constructed", () => {
const signature = new ExtendedSecp256k1Signature(new Uint8Array([0x22, 0x33]), new Uint8Array([0xaa]), 1);
expect(signature.recovery).toEqual(1);
});
it("throws for recovery param out of range", () => {
expect(() => new ExtendedSecp256k1Signature(fromHex("aa"), fromHex("bb"), Number.NaN)).toThrowError(
/the recovery parameter must be an integer/i,
);
expect(
() => new ExtendedSecp256k1Signature(fromHex("aa"), fromHex("bb"), Number.NEGATIVE_INFINITY),
).toThrowError(/the recovery parameter must be an integer/i);
expect(
() => new ExtendedSecp256k1Signature(fromHex("aa"), fromHex("bb"), Number.POSITIVE_INFINITY),
).toThrowError(/the recovery parameter must be an integer/i);
expect(() => new ExtendedSecp256k1Signature(fromHex("aa"), fromHex("bb"), 1.1)).toThrowError(
/the recovery parameter must be an integer/i,
);
expect(() => new ExtendedSecp256k1Signature(fromHex("aa"), fromHex("bb"), -1)).toThrowError(
/the recovery parameter must be one of 0, 1, 2, 3/i,
);
expect(() => new ExtendedSecp256k1Signature(fromHex("aa"), fromHex("bb"), 5)).toThrowError(
/the recovery parameter must be one of 0, 1, 2, 3/i,
);
});
it("can be encoded as fixed length", () => {
const signature = new ExtendedSecp256k1Signature(new Uint8Array([0x22, 0x33]), new Uint8Array([0xaa]), 1);
expect(signature.toFixedLength()).toEqual(
fromHex(
"000000000000000000000000000000000000000000000000000000000000223300000000000000000000000000000000000000000000000000000000000000aa01",
),
);
});
it("can be decoded from fixed length", () => {
const signature = ExtendedSecp256k1Signature.fromFixedLength(
fromHex(
"000000000000000000000000000000000000000000000000000000000000223300000000000000000000000000000000000000000000000000000000000000aa01",
),
);
expect(signature.r()).toEqual(new Uint8Array([0x22, 0x33]));
expect(signature.s()).toEqual(new Uint8Array([0xaa]));
expect(signature.recovery).toEqual(1);
});
});

View File

@ -0,0 +1,179 @@
function trimLeadingNullBytes(inData: Uint8Array): Uint8Array {
let numberOfLeadingNullBytes = 0;
for (const byte of inData) {
if (byte === 0x00) {
numberOfLeadingNullBytes++;
} else {
break;
}
}
return inData.slice(numberOfLeadingNullBytes);
}
const derTagInteger = 0x02;
export class Secp256k1Signature {
/**
* Takes the pair of integers (r, s) as 2x32 byte of binary data.
*
* Note: This is the format Cosmos SDK uses natively.
*
* @param data a 64 byte value containing integers r and s.
*/
public static fromFixedLength(data: Uint8Array): Secp256k1Signature {
if (data.length !== 64) {
throw new Error(`Got invalid data length: ${data.length}. Expected 2x 32 bytes for the pair (r, s)`);
}
return new Secp256k1Signature(
trimLeadingNullBytes(data.slice(0, 32)),
trimLeadingNullBytes(data.slice(32, 64)),
);
}
public static fromDer(data: Uint8Array): Secp256k1Signature {
let pos = 0;
if (data[pos++] !== 0x30) {
throw new Error("Prefix 0x30 expected");
}
const bodyLength = data[pos++];
if (data.length - pos !== bodyLength) {
throw new Error("Data length mismatch detected");
}
// r
const rTag = data[pos++];
if (rTag !== derTagInteger) {
throw new Error("INTEGER tag expected");
}
const rLength = data[pos++];
if (rLength >= 0x80) {
throw new Error("Decoding length values above 127 not supported");
}
const rData = data.slice(pos, pos + rLength);
pos += rLength;
// s
const sTag = data[pos++];
if (sTag !== derTagInteger) {
throw new Error("INTEGER tag expected");
}
const sLength = data[pos++];
if (sLength >= 0x80) {
throw new Error("Decoding length values above 127 not supported");
}
const sData = data.slice(pos, pos + sLength);
pos += sLength;
return new Secp256k1Signature(
// r/s data can contain leading 0 bytes to express integers being non-negative in DER
trimLeadingNullBytes(rData),
trimLeadingNullBytes(sData),
);
}
private readonly data: {
readonly r: Uint8Array;
readonly s: Uint8Array;
};
public constructor(r: Uint8Array, s: Uint8Array) {
if (r.length > 32 || r.length === 0 || r[0] === 0x00) {
throw new Error("Unsigned integer r must be encoded as unpadded big endian.");
}
if (s.length > 32 || s.length === 0 || s[0] === 0x00) {
throw new Error("Unsigned integer s must be encoded as unpadded big endian.");
}
this.data = {
r: r,
s: s,
};
}
public r(length?: number): Uint8Array {
if (length === undefined) {
return this.data.r;
} else {
const paddingLength = length - this.data.r.length;
if (paddingLength < 0) {
throw new Error("Length too small to hold parameter r");
}
const padding = new Uint8Array(paddingLength);
return new Uint8Array([...padding, ...this.data.r]);
}
}
public s(length?: number): Uint8Array {
if (length === undefined) {
return this.data.s;
} else {
const paddingLength = length - this.data.s.length;
if (paddingLength < 0) {
throw new Error("Length too small to hold parameter s");
}
const padding = new Uint8Array(paddingLength);
return new Uint8Array([...padding, ...this.data.s]);
}
}
public toDer(): Uint8Array {
// DER supports negative integers but our data is unsigned. Thus we need to prepend
// a leading 0 byte when the higest bit is set to differentiate nagative values
const rEncoded = this.data.r[0] >= 0x80 ? new Uint8Array([0, ...this.data.r]) : this.data.r;
const sEncoded = this.data.s[0] >= 0x80 ? new Uint8Array([0, ...this.data.s]) : this.data.s;
const rLength = rEncoded.length;
const sLength = sEncoded.length;
const data = new Uint8Array([derTagInteger, rLength, ...rEncoded, derTagInteger, sLength, ...sEncoded]);
return new Uint8Array([0x30, data.length, ...data]);
}
}
/**
* A Secp256k1Signature plus the recovery parameter
*/
export class ExtendedSecp256k1Signature extends Secp256k1Signature {
/**
* Decode extended signature from the simple fixed length encoding
* described in toFixedLength().
*/
public static fromFixedLength(data: Uint8Array): ExtendedSecp256k1Signature {
if (data.length !== 65) {
throw new Error(`Got invalid data length ${data.length}. Expected 32 + 32 + 1`);
}
return new ExtendedSecp256k1Signature(
trimLeadingNullBytes(data.slice(0, 32)),
trimLeadingNullBytes(data.slice(32, 64)),
data[64],
);
}
public readonly recovery: number;
public constructor(r: Uint8Array, s: Uint8Array, recovery: number) {
super(r, s);
if (!Number.isInteger(recovery)) {
throw new Error("The recovery parameter must be an integer.");
}
if (recovery < 0 || recovery > 4) {
throw new Error("The recovery parameter must be one of 0, 1, 2, 3.");
}
this.recovery = recovery;
}
/**
* A simple custom encoding that encodes the extended signature as
* r (32 bytes) | s (32 bytes) | recovery param (1 byte)
* where | denotes concatenation of bonary data.
*/
public toFixedLength(): Uint8Array {
return new Uint8Array([...this.r(32), ...this.s(32), this.recovery]);
}
}

View File

@ -0,0 +1,28 @@
import { fromHex, toHex } from "@iov/encoding";
import { Sha256 } from "./sha";
import shaVectors from "./testdata/sha.json";
describe("Sha256", () => {
it("exists", () => {
expect(Sha256).toBeTruthy();
});
it("works for empty input", () => {
{
const hash = new Sha256(new Uint8Array([])).digest();
expect(toHex(hash)).toEqual("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
}
{
const hash = new Sha256().digest();
expect(toHex(hash)).toEqual("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
}
});
it("works for all the Botan test vectors", () => {
// https://github.com/randombit/botan/blob/2.6.0/src/tests/data/hash/sha2_32.vec#L13
for (const { in: input, out: output } of shaVectors.sha256) {
expect(new Sha256(fromHex(input)).digest()).toEqual(fromHex(output));
}
});
});

View File

@ -0,0 +1,73 @@
import { Hash } from "crypto";
import shajs from "sha.js";
import { HashFunction } from "./hash";
export class Sha1 implements HashFunction {
public readonly blockSize = 512 / 8;
private readonly impl: Hash;
public constructor(firstData?: Uint8Array) {
this.impl = shajs("sha1");
if (firstData) {
this.update(firstData);
}
}
public update(data: Uint8Array): Sha1 {
this.impl.update(data);
return this;
}
public digest(): Uint8Array {
return new Uint8Array(this.impl.digest());
}
}
export class Sha256 implements HashFunction {
public readonly blockSize = 512 / 8;
private readonly impl: Hash;
public constructor(firstData?: Uint8Array) {
this.impl = shajs("sha256");
if (firstData) {
this.update(firstData);
}
}
public update(data: Uint8Array): Sha256 {
this.impl.update(data);
return this;
}
public digest(): Uint8Array {
return new Uint8Array(this.impl.digest());
}
}
export class Sha512 implements HashFunction {
public readonly blockSize = 1024 / 8;
private readonly impl: Hash;
public constructor(firstData?: Uint8Array) {
this.impl = shajs("sha512");
if (firstData) {
this.update(firstData);
}
}
public update(data: Uint8Array): Sha512 {
this.impl.update(data);
return this;
}
public digest(): Uint8Array {
return new Uint8Array(this.impl.digest());
}
}

View File

@ -0,0 +1,472 @@
import { fromHex } from "@iov/encoding";
import {
pathToString,
Slip10,
Slip10Curve,
slip10CurveFromString,
Slip10RawIndex,
stringToPath,
} from "./slip10";
describe("Slip10", () => {
it("has working slip10CurveFromString()", () => {
expect(slip10CurveFromString("Bitcoin seed")).toEqual(Slip10Curve.Secp256k1);
expect(slip10CurveFromString("ed25519 seed")).toEqual(Slip10Curve.Ed25519);
expect(() => slip10CurveFromString("something else")).toThrowError(/unknown curve/i);
expect(() => slip10CurveFromString("")).toThrowError(/unknown curve/i);
});
describe("Test vector 1 for secp256k1", () => {
// https://github.com/satoshilabs/slips/blob/master/slip-0010.md#test-vector-1-for-secp256k1
const seed = fromHex("000102030405060708090a0b0c0d0e0f");
it("can derive path m", () => {
const path: readonly Slip10RawIndex[] = [];
const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path);
expect(derived.chainCode).toEqual(
fromHex("873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508"),
);
expect(derived.privkey).toEqual(
fromHex("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"),
);
});
it("can derive path m/0'", () => {
const path: readonly Slip10RawIndex[] = [Slip10RawIndex.hardened(0)];
const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path);
expect(derived.chainCode).toEqual(
fromHex("47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141"),
);
expect(derived.privkey).toEqual(
fromHex("edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea"),
);
});
it("can derive path m/0'/1", () => {
const path: readonly Slip10RawIndex[] = [Slip10RawIndex.hardened(0), Slip10RawIndex.normal(1)];
const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path);
expect(derived.chainCode).toEqual(
fromHex("2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19"),
);
expect(derived.privkey).toEqual(
fromHex("3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368"),
);
});
it("can derive path m/0'/1/2'", () => {
const path: readonly Slip10RawIndex[] = [
Slip10RawIndex.hardened(0),
Slip10RawIndex.normal(1),
Slip10RawIndex.hardened(2),
];
const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path);
expect(derived.chainCode).toEqual(
fromHex("04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f"),
);
expect(derived.privkey).toEqual(
fromHex("cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca"),
);
});
it("can derive path m/0'/1/2'/2", () => {
const path: readonly Slip10RawIndex[] = [
Slip10RawIndex.hardened(0),
Slip10RawIndex.normal(1),
Slip10RawIndex.hardened(2),
Slip10RawIndex.normal(2),
];
const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path);
expect(derived.chainCode).toEqual(
fromHex("cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd"),
);
expect(derived.privkey).toEqual(
fromHex("0f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4"),
);
});
it("can derive path m/0'/1/2'/2/1000000000", () => {
const path: readonly Slip10RawIndex[] = [
Slip10RawIndex.hardened(0),
Slip10RawIndex.normal(1),
Slip10RawIndex.hardened(2),
Slip10RawIndex.normal(2),
Slip10RawIndex.normal(1000000000),
];
const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path);
expect(derived.chainCode).toEqual(
fromHex("c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e"),
);
expect(derived.privkey).toEqual(
fromHex("471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8"),
);
});
});
describe("Test vector 2 for secp256k1", () => {
// https://github.com/satoshilabs/slips/blob/master/slip-0010.md#test-vector-2-for-secp256k1
const seed = fromHex(
"fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
);
it("can derive path m", () => {
const path: readonly Slip10RawIndex[] = [];
const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path);
expect(derived.chainCode).toEqual(
fromHex("60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689"),
);
expect(derived.privkey).toEqual(
fromHex("4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e"),
);
});
it("can derive path m/0", () => {
const path: readonly Slip10RawIndex[] = [Slip10RawIndex.normal(0)];
const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path);
expect(derived.chainCode).toEqual(
fromHex("f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c"),
);
expect(derived.privkey).toEqual(
fromHex("abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e"),
);
});
it("can derive path m/0/2147483647'", () => {
const path: readonly Slip10RawIndex[] = [Slip10RawIndex.normal(0), Slip10RawIndex.hardened(2147483647)];
const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path);
expect(derived.chainCode).toEqual(
fromHex("be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9"),
);
expect(derived.privkey).toEqual(
fromHex("877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93"),
);
});
it("can derive path m/0/2147483647'/1", () => {
const path: readonly Slip10RawIndex[] = [
Slip10RawIndex.normal(0),
Slip10RawIndex.hardened(2147483647),
Slip10RawIndex.normal(1),
];
const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path);
expect(derived.chainCode).toEqual(
fromHex("f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb"),
);
expect(derived.privkey).toEqual(
fromHex("704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7"),
);
});
it("can derive path m/0/2147483647'/1/2147483646'", () => {
const path: readonly Slip10RawIndex[] = [
Slip10RawIndex.normal(0),
Slip10RawIndex.hardened(2147483647),
Slip10RawIndex.normal(1),
Slip10RawIndex.hardened(2147483646),
];
const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path);
expect(derived.chainCode).toEqual(
fromHex("637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29"),
);
expect(derived.privkey).toEqual(
fromHex("f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d"),
);
});
it("can derive path m/0/2147483647'/1/2147483646'/2", () => {
const path: readonly Slip10RawIndex[] = [
Slip10RawIndex.normal(0),
Slip10RawIndex.hardened(2147483647),
Slip10RawIndex.normal(1),
Slip10RawIndex.hardened(2147483646),
Slip10RawIndex.normal(2),
];
const derived = Slip10.derivePath(Slip10Curve.Secp256k1, seed, path);
expect(derived.chainCode).toEqual(
fromHex("9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271"),
);
expect(derived.privkey).toEqual(
fromHex("bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23"),
);
});
});
describe("Test vector 1 for ed25519", () => {
// https://github.com/satoshilabs/slips/blob/master/slip-0010.md#test-vector-1-for-ed25519
const seed = fromHex("000102030405060708090a0b0c0d0e0f");
it("can derive path m", () => {
const path: readonly Slip10RawIndex[] = [];
const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path);
expect(derived.chainCode).toEqual(
fromHex("90046a93de5380a72b5e45010748567d5ea02bbf6522f979e05c0d8d8ca9fffb"),
);
expect(derived.privkey).toEqual(
fromHex("2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e7"),
);
});
it("can derive path m/0'", () => {
const path: readonly Slip10RawIndex[] = [Slip10RawIndex.hardened(0)];
const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path);
expect(derived.chainCode).toEqual(
fromHex("8b59aa11380b624e81507a27fedda59fea6d0b779a778918a2fd3590e16e9c69"),
);
expect(derived.privkey).toEqual(
fromHex("68e0fe46dfb67e368c75379acec591dad19df3cde26e63b93a8e704f1dade7a3"),
);
});
it("can derive path m/0'/1'", () => {
const path: readonly Slip10RawIndex[] = [Slip10RawIndex.hardened(0), Slip10RawIndex.hardened(1)];
const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path);
expect(derived.chainCode).toEqual(
fromHex("a320425f77d1b5c2505a6b1b27382b37368ee640e3557c315416801243552f14"),
);
expect(derived.privkey).toEqual(
fromHex("b1d0bad404bf35da785a64ca1ac54b2617211d2777696fbffaf208f746ae84f2"),
);
});
it("can derive path m/0'/1'/2'", () => {
const path: readonly Slip10RawIndex[] = [
Slip10RawIndex.hardened(0),
Slip10RawIndex.hardened(1),
Slip10RawIndex.hardened(2),
];
const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path);
expect(derived.chainCode).toEqual(
fromHex("2e69929e00b5ab250f49c3fb1c12f252de4fed2c1db88387094a0f8c4c9ccd6c"),
);
expect(derived.privkey).toEqual(
fromHex("92a5b23c0b8a99e37d07df3fb9966917f5d06e02ddbd909c7e184371463e9fc9"),
);
});
it("can derive path m/0'/1'/2'/2'", () => {
const path: readonly Slip10RawIndex[] = [
Slip10RawIndex.hardened(0),
Slip10RawIndex.hardened(1),
Slip10RawIndex.hardened(2),
Slip10RawIndex.hardened(2),
];
const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path);
expect(derived.chainCode).toEqual(
fromHex("8f6d87f93d750e0efccda017d662a1b31a266e4a6f5993b15f5c1f07f74dd5cc"),
);
expect(derived.privkey).toEqual(
fromHex("30d1dc7e5fc04c31219ab25a27ae00b50f6fd66622f6e9c913253d6511d1e662"),
);
});
it("can derive path m/0'/1'/2'/2'/1000000000'", () => {
const path: readonly Slip10RawIndex[] = [
Slip10RawIndex.hardened(0),
Slip10RawIndex.hardened(1),
Slip10RawIndex.hardened(2),
Slip10RawIndex.hardened(2),
Slip10RawIndex.hardened(1000000000),
];
const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path);
expect(derived.chainCode).toEqual(
fromHex("68789923a0cac2cd5a29172a475fe9e0fb14cd6adb5ad98a3fa70333e7afa230"),
);
expect(derived.privkey).toEqual(
fromHex("8f94d394a8e8fd6b1bc2f3f49f5c47e385281d5c17e65324b0f62483e37e8793"),
);
});
});
describe("Test vector 2 for ed25519", () => {
// https://github.com/satoshilabs/slips/blob/master/slip-0010.md#test-vector-2-for-ed25519
const seed = fromHex(
"fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
);
it("can derive path m", () => {
const path: readonly Slip10RawIndex[] = [];
const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path);
expect(derived.chainCode).toEqual(
fromHex("ef70a74db9c3a5af931b5fe73ed8e1a53464133654fd55e7a66f8570b8e33c3b"),
);
expect(derived.privkey).toEqual(
fromHex("171cb88b1b3c1db25add599712e36245d75bc65a1a5c9e18d76f9f2b1eab4012"),
);
});
it("can derive path m/0'", () => {
const path: readonly Slip10RawIndex[] = [Slip10RawIndex.hardened(0)];
const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path);
expect(derived.chainCode).toEqual(
fromHex("0b78a3226f915c082bf118f83618a618ab6dec793752624cbeb622acb562862d"),
);
expect(derived.privkey).toEqual(
fromHex("1559eb2bbec5790b0c65d8693e4d0875b1747f4970ae8b650486ed7470845635"),
);
});
it("can derive path m/0'/2147483647'", () => {
const path: readonly Slip10RawIndex[] = [
Slip10RawIndex.hardened(0),
Slip10RawIndex.hardened(2147483647),
];
const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path);
expect(derived.chainCode).toEqual(
fromHex("138f0b2551bcafeca6ff2aa88ba8ed0ed8de070841f0c4ef0165df8181eaad7f"),
);
expect(derived.privkey).toEqual(
fromHex("ea4f5bfe8694d8bb74b7b59404632fd5968b774ed545e810de9c32a4fb4192f4"),
);
});
it("can derive path m/0'/2147483647'/1'", () => {
const path: readonly Slip10RawIndex[] = [
Slip10RawIndex.hardened(0),
Slip10RawIndex.hardened(2147483647),
Slip10RawIndex.hardened(1),
];
const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path);
expect(derived.chainCode).toEqual(
fromHex("73bd9fff1cfbde33a1b846c27085f711c0fe2d66fd32e139d3ebc28e5a4a6b90"),
);
expect(derived.privkey).toEqual(
fromHex("3757c7577170179c7868353ada796c839135b3d30554bbb74a4b1e4a5a58505c"),
);
});
it("can derive path m/0'/2147483647'/1'/2147483646'", () => {
const path: readonly Slip10RawIndex[] = [
Slip10RawIndex.hardened(0),
Slip10RawIndex.hardened(2147483647),
Slip10RawIndex.hardened(1),
Slip10RawIndex.hardened(2147483646),
];
const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path);
expect(derived.chainCode).toEqual(
fromHex("0902fe8a29f9140480a00ef244bd183e8a13288e4412d8389d140aac1794825a"),
);
expect(derived.privkey).toEqual(
fromHex("5837736c89570de861ebc173b1086da4f505d4adb387c6a1b1342d5e4ac9ec72"),
);
});
it("can derive path m/0'/2147483647'/1'/2147483646'/2'", () => {
const path: readonly Slip10RawIndex[] = [
Slip10RawIndex.hardened(0),
Slip10RawIndex.hardened(2147483647),
Slip10RawIndex.hardened(1),
Slip10RawIndex.hardened(2147483646),
Slip10RawIndex.hardened(2),
];
const derived = Slip10.derivePath(Slip10Curve.Ed25519, seed, path);
expect(derived.chainCode).toEqual(
fromHex("5d70af781f3a37b829f0d060924d5e960bdc02e85423494afc0b1a41bbe196d4"),
);
expect(derived.privkey).toEqual(
fromHex("551d333177df541ad876a60ea71f00447931c0a9da16f227c11ea080d7391b8d"),
);
});
});
describe("pathToString", () => {
it("works for no component", () => {
// See https://github.com/bitcoin/bips/blob/master/bip-0032/derivation.png from BIP32
expect(pathToString([])).toEqual("m");
});
it("works for normal components", () => {
const one = Slip10RawIndex.normal(1);
expect(pathToString([one])).toEqual("m/1");
expect(pathToString([one, one])).toEqual("m/1/1");
expect(pathToString([one, one, one])).toEqual("m/1/1/1");
const min = Slip10RawIndex.normal(0);
expect(pathToString([min])).toEqual("m/0");
const max = Slip10RawIndex.normal(2 ** 31 - 1);
expect(pathToString([max])).toEqual("m/2147483647");
});
it("works for hardened components", () => {
const one = Slip10RawIndex.hardened(1);
expect(pathToString([one])).toEqual("m/1'");
expect(pathToString([one, one])).toEqual("m/1'/1'");
expect(pathToString([one, one, one])).toEqual("m/1'/1'/1'");
const min = Slip10RawIndex.hardened(0);
expect(pathToString([min])).toEqual("m/0'");
const max = Slip10RawIndex.hardened(2 ** 31 - 1);
expect(pathToString([max])).toEqual("m/2147483647'");
});
it("works for mixed components", () => {
const one = Slip10RawIndex.normal(1);
const two = Slip10RawIndex.hardened(2);
expect(pathToString([one, two, two, one])).toEqual("m/1/2'/2'/1");
});
});
describe("stringToPath", () => {
it("works for no component", () => {
// See https://github.com/bitcoin/bips/blob/master/bip-0032/derivation.png from BIP32
expect(stringToPath("m")).toEqual([]);
});
it("throws for broken start", () => {
expect(() => stringToPath("")).toThrowError(/must start with 'm'/);
expect(() => stringToPath("M")).toThrowError(/must start with 'm'/);
expect(() => stringToPath("/1/1")).toThrowError(/must start with 'm'/);
});
it("works for normal components", () => {
const one = Slip10RawIndex.normal(1);
expect(stringToPath("m/1")).toEqual([one]);
expect(stringToPath("m/1/1")).toEqual([one, one]);
expect(stringToPath("m/1/1/1")).toEqual([one, one, one]);
const min = Slip10RawIndex.normal(0);
expect(stringToPath("m/0")).toEqual([min]);
const max = Slip10RawIndex.normal(2 ** 31 - 1);
expect(stringToPath("m/2147483647")).toEqual([max]);
});
it("errors for syntax error in component", () => {
expect(() => stringToPath("m/ 1/1/1")).toThrowError(/syntax error/i);
expect(() => stringToPath("m/-1/1/1")).toThrowError(/syntax error/i);
expect(() => stringToPath("m//1/1")).toThrowError(/syntax error/i);
expect(() => stringToPath("m/1*/1/1")).toThrowError(/syntax error/i);
expect(() => stringToPath("m/1/1/1 ")).toThrowError(/syntax error/i);
expect(() => stringToPath("m/1''/1/1")).toThrowError(/syntax error/i);
expect(() => stringToPath("m/1 '/1/1")).toThrowError(/syntax error/i);
expect(() => stringToPath("m/'/1/1")).toThrowError(/syntax error/i);
expect(() => stringToPath('m/1"/1/1')).toThrowError(/syntax error/i);
});
it("errors for value too high", () => {
expect(() => stringToPath("m/2147483648/1/1")).toThrowError(/value too high/i);
expect(() => stringToPath("m/1/9007199254740991/1")).toThrowError(/value too high/i);
expect(() => stringToPath("m/1/1/9007199254740992")).toThrowError(/not in int53 range/i);
});
it("works for hardened components", () => {
const one = Slip10RawIndex.hardened(1);
expect(stringToPath("m/1'")).toEqual([one]);
expect(stringToPath("m/1'/1'")).toEqual([one, one]);
expect(stringToPath("m/1'/1'/1'")).toEqual([one, one, one]);
const min = Slip10RawIndex.hardened(0);
expect(stringToPath("m/0'")).toEqual([min]);
const max = Slip10RawIndex.hardened(2 ** 31 - 1);
expect(stringToPath("m/2147483647'")).toEqual([max]);
});
it("works for mixed components", () => {
const one = Slip10RawIndex.normal(1);
const two = Slip10RawIndex.hardened(2);
expect(stringToPath("m/1/2'/2'/1")).toEqual([one, two, two, one]);
});
});
});

View File

@ -0,0 +1,212 @@
import { fromHex, toAscii, Uint32, Uint53 } from "@iov/encoding";
import BN from "bn.js";
import elliptic from "elliptic";
import { Hmac } from "./hmac";
import { Sha512 } from "./sha";
export interface Slip10Result {
readonly chainCode: Uint8Array;
readonly privkey: Uint8Array;
}
/**
* Raw values must match the curve string in SLIP-0010 master key generation
*
* @see https://github.com/satoshilabs/slips/blob/master/slip-0010.md#master-key-generation
*/
export enum Slip10Curve {
Secp256k1 = "Bitcoin seed",
Ed25519 = "ed25519 seed",
}
/**
* Reverse mapping of Slip10Curve
*/
export function slip10CurveFromString(curveString: string): Slip10Curve {
switch (curveString) {
case Slip10Curve.Ed25519:
return Slip10Curve.Ed25519;
case Slip10Curve.Secp256k1:
return Slip10Curve.Secp256k1;
default:
throw new Error(`Unknown curve string: '${curveString}'`);
}
}
export class Slip10RawIndex extends Uint32 {
public static hardened(hardenedIndex: number): Slip10RawIndex {
return new Slip10RawIndex(hardenedIndex + 2 ** 31);
}
public static normal(normalIndex: number): Slip10RawIndex {
return new Slip10RawIndex(normalIndex);
}
public isHardened(): boolean {
return this.data >= 2 ** 31;
}
}
const secp256k1 = new elliptic.ec("secp256k1");
// Universal private key derivation accoring to
// https://github.com/satoshilabs/slips/blob/master/slip-0010.md
export class Slip10 {
public static derivePath(
curve: Slip10Curve,
seed: Uint8Array,
path: readonly Slip10RawIndex[],
): Slip10Result {
let result = this.master(curve, seed);
for (const rawIndex of path) {
result = this.child(curve, result.privkey, result.chainCode, rawIndex);
}
return result;
}
private static master(curve: Slip10Curve, seed: Uint8Array): Slip10Result {
const i = new Hmac(Sha512, toAscii(curve)).update(seed).digest();
const il = i.slice(0, 32);
const ir = i.slice(32, 64);
if (curve !== Slip10Curve.Ed25519 && (this.isZero(il) || this.isGteN(curve, il))) {
return this.master(curve, i);
}
return {
chainCode: ir,
privkey: il,
};
}
private static child(
curve: Slip10Curve,
parentPrivkey: Uint8Array,
parentChainCode: Uint8Array,
rawIndex: Slip10RawIndex,
): Slip10Result {
let i: Uint8Array;
if (rawIndex.isHardened()) {
const payload = new Uint8Array([0x00, ...parentPrivkey, ...rawIndex.toBytesBigEndian()]);
i = new Hmac(Sha512, parentChainCode).update(payload).digest();
} else {
if (curve === Slip10Curve.Ed25519) {
throw new Error("Normal keys are not allowed with ed25519");
} else {
// Step 1 of https://github.com/satoshilabs/slips/blob/master/slip-0010.md#private-parent-key--private-child-key
// Calculate I = HMAC-SHA512(Key = c_par, Data = ser_P(point(k_par)) || ser_32(i)).
// where the functions point() and ser_p() are defined in BIP-0032
const data = new Uint8Array([
...Slip10.serializedPoint(curve, new BN(parentPrivkey)),
...rawIndex.toBytesBigEndian(),
]);
i = new Hmac(Sha512, parentChainCode).update(data).digest();
}
}
return this.childImpl(curve, parentPrivkey, parentChainCode, rawIndex, i);
}
/**
* Implementation of ser_P(point(k_par)) from BIP-0032
*
* @see https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
*/
private static serializedPoint(curve: Slip10Curve, p: BN): Uint8Array {
switch (curve) {
case Slip10Curve.Secp256k1:
return fromHex(secp256k1.g.mul(p).encodeCompressed("hex"));
default:
throw new Error("curve not supported");
}
}
private static childImpl(
curve: Slip10Curve,
parentPrivkey: Uint8Array,
parentChainCode: Uint8Array,
rawIndex: Slip10RawIndex,
i: Uint8Array,
): Slip10Result {
// step 2 (of the Private parent key → private child key algorithm)
const il = i.slice(0, 32);
const ir = i.slice(32, 64);
// step 3
const returnChainCode = ir;
// step 4
if (curve === Slip10Curve.Ed25519) {
return {
chainCode: returnChainCode,
privkey: il,
};
}
// step 5
const n = this.n(curve);
const returnChildKeyAsNumber = new BN(il).add(new BN(parentPrivkey)).mod(n);
const returnChildKey = Uint8Array.from(returnChildKeyAsNumber.toArray("be", 32));
// step 6
if (this.isGteN(curve, il) || this.isZero(returnChildKey)) {
const newI = new Hmac(Sha512, parentChainCode)
.update(new Uint8Array([0x01, ...ir, ...rawIndex.toBytesBigEndian()]))
.digest();
return this.childImpl(curve, parentPrivkey, parentChainCode, rawIndex, newI);
}
// step 7
return {
chainCode: returnChainCode,
privkey: returnChildKey,
};
}
private static isZero(privkey: Uint8Array): boolean {
return privkey.every((byte) => byte === 0);
}
private static isGteN(curve: Slip10Curve, privkey: Uint8Array): boolean {
const keyAsNumber = new BN(privkey);
return keyAsNumber.gte(this.n(curve));
}
private static n(curve: Slip10Curve): BN {
switch (curve) {
case Slip10Curve.Secp256k1:
return new BN("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);
default:
throw new Error("curve not supported");
}
}
}
export function pathToString(path: readonly Slip10RawIndex[]): string {
return path.reduce((current, component): string => {
const componentString = component.isHardened()
? `${component.toNumber() - 2 ** 31}'`
: component.toString();
return current + "/" + componentString;
}, "m");
}
export function stringToPath(input: string): readonly Slip10RawIndex[] {
if (!input.startsWith("m")) throw new Error("Path string must start with 'm'");
let rest = input.slice(1);
const out = new Array<Slip10RawIndex>();
while (rest) {
const match = rest.match(/^\/([0-9]+)('?)/);
if (!match) throw new Error("Syntax error while reading path component");
const [fullMatch, numberString, apostrophe] = match;
const value = Uint53.fromString(numberString).toNumber();
if (value >= 2 ** 31) throw new Error("Component value too high. Must not exceed 2**31-1.");
if (apostrophe) out.push(Slip10RawIndex.hardened(value));
else out.push(Slip10RawIndex.normal(value));
rest = rest.slice(fullMatch.length);
}
return out;
}

174
packages/crypto/src/testdata/bip39.json vendored Normal file
View File

@ -0,0 +1,174 @@
{
"encoding": {
"12": [
{
"entropy": "00000000000000000000000000000000",
"mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
},
{
"entropy": "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
"mnemonic": "legal winner thank year wave sausage worth useful legal winner thank yellow"
},
{
"entropy": "80808080808080808080808080808080",
"mnemonic": "letter advice cage absurd amount doctor acoustic avoid letter advice cage above"
},
{
"entropy": "ffffffffffffffffffffffffffffffff",
"mnemonic": "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong"
},
{
"entropy": "9e885d952ad362caeb4efe34a8e91bd2",
"mnemonic": "ozone drill grab fiber curtain grace pudding thank cruise elder eight picnic"
},
{
"entropy": "c0ba5a8e914111210f2bd131f3d5e08d",
"mnemonic": "scheme spot photo card baby mountain device kick cradle pact join borrow"
},
{
"entropy": "23db8160a31d3e0dca3688ed941adbf3",
"mnemonic": "cat swing flag economy stadium alone churn speed unique patch report train"
},
{
"entropy": "f30f8c1da665478f49b001d94c5fc452",
"mnemonic": "vessel ladder alter error federal sibling chat ability sun glass valve picture"
}
],
"15": [
{
"entropy": "0000000000000000000000000000000000000000",
"mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon address"
},
{
"entropy": "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
"mnemonic": "legal winner thank year wave sausage worth useful legal winner thank year wave sausage wise"
},
{
"entropy": "8080808080808080808080808080808080808080",
"mnemonic": "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor accident"
},
{
"entropy": "ffffffffffffffffffffffffffffffffffffffff",
"mnemonic": "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrist"
},
{
"entropy": "610b25967cdcca9d59875f5cb50b0ea754333118",
"mnemonic": "genre float grain whale smile exchange gravity typical frequent portion senior exchange drip obtain glow"
},
{
"entropy": "232c38b9a99e10d2253fa1aa2739a8e9c3b112d0",
"mnemonic": "case gift common farm three harbor neutral vintage pretty degree health squeeze deposit maximum donor"
},
{
"entropy": "c9a0ed1374180cd8f8c5cc0bc9c719dfb67add59",
"mnemonic": "situate alter dynamic trial liar hockey toast ridge armed evolve shoe satoshi guilt huge grass"
},
{
"entropy": "ef38bbc81a5fa1756dfd654b0cc1a1759ce1ed94",
"mnemonic": "upset shift velvet cruise wheel rival retire protect enrich gravity hair twenty sock walnut fat"
}
],
"18": [
{
"entropy": "000000000000000000000000000000000000000000000000",
"mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent"
},
{
"entropy": "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
"mnemonic": "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will"
},
{
"entropy": "808080808080808080808080808080808080808080808080",
"mnemonic": "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always"
},
{
"entropy": "ffffffffffffffffffffffffffffffffffffffffffffffff",
"mnemonic": "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when"
},
{
"entropy": "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b",
"mnemonic": "gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog"
},
{
"entropy": "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3",
"mnemonic": "horn tenant knee talent sponsor spell gate clip pulse soap slush warm silver nephew swap uncle crack brave"
},
{
"entropy": "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0",
"mnemonic": "light rule cinnamon wrap drastic word pride squirrel upgrade then income fatal apart sustain crack supply proud access"
},
{
"entropy": "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05",
"mnemonic": "scissors invite lock maple supreme raw rapid void congress muscle digital elegant little brisk hair mango congress clump"
}
],
"21": [
{
"entropy": "00000000000000000000000000000000000000000000000000000000",
"mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon admit"
},
{
"entropy": "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
"mnemonic": "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year viable"
},
{
"entropy": "80808080808080808080808080808080808080808080808080808080",
"mnemonic": "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd apart"
},
{
"entropy": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"mnemonic": "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo veteran"
},
{
"entropy": "8a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc7",
"mnemonic": "media soon raw edge embody palm reason pave right danger clay occur modify conduct ethics vessel journey toast cradle stuff laundry"
},
{
"entropy": "49208c3112119c4770d44d1a9726b44135748bda464954338f3c1d91",
"mnemonic": "empty afford arrange category border casual select meadow box rice public list firm echo harbor since feature organ someone depth bitter"
},
{
"entropy": "dc8a247c5379f57c24dbfbd6a0699a8282875dd0a3f0f39b8fc020a4",
"mnemonic": "symptom eye business plunge palm safe nature legal stove addict grit agree chronic puzzle dream lawn vicious symbol useless donor eagle"
},
{
"entropy": "a66afdf103bf42b5858cd1c23a20f7e5114edb111880d634b53454bf",
"mnemonic": "please fitness labor alter vintage food bike olive season speed digital sketch belt horn dutch avoid stomach pizza escape practice walnut"
}
],
"24": [
{
"entropy": "0000000000000000000000000000000000000000000000000000000000000000",
"mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art"
},
{
"entropy": "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
"mnemonic": "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title"
},
{
"entropy": "8080808080808080808080808080808080808080808080808080808080808080",
"mnemonic": "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless"
},
{
"entropy": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"mnemonic": "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote"
},
{
"entropy": "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c",
"mnemonic": "hamster diagram private dutch cause delay private meat slide toddler razor book happy fancy gospel tennis maple dilemma loan word shrug inflict delay length"
},
{
"entropy": "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863",
"mnemonic": "panda eyebrow bullet gorilla call smoke muffin taste mesh discover soft ostrich alcohol speed nation flash devote level hobby quick inner drive ghost inside"
},
{
"entropy": "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad",
"mnemonic": "all hour make first leader extend hole alien behind guard gospel lava path output census museum junior mass reopen famous sing advance salt reform"
},
{
"entropy": "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f",
"mnemonic": "void come effort suffer camp survey warrior heavy shoot primary clutch crush open amazing screen patrol group space point ten exist slush involve unfold"
}
]
}
}

File diff suppressed because one or more lines are too long

1076
packages/crypto/src/testdata/keccak.json vendored Normal file

File diff suppressed because one or more lines are too long

383
packages/crypto/src/testdata/ripemd.json vendored Normal file
View File

@ -0,0 +1,383 @@
{
"ripemd160": [
{
"in": "ac",
"out": "faf1685349fe8b832bb2ff1cba482583b3560f3f"
},
{
"in": "e9ec",
"out": "c4eb5003f5fc64e122194ef6fba841b30b1e8761"
},
{
"in": "74524b",
"out": "d5904a1d8ba51b972bcf6d00681a5f0306c57ac5"
},
{
"in": "e233e3b4",
"out": "9cea3480ef59215c514c4fff9b813677120aa8aa"
},
{
"in": "445a2f97ad",
"out": "6ae5414cbe327cdd20fd8887f2f94d7b69222ab7"
},
{
"in": "a242802b76ec",
"out": "5dca1535835d75adfa1b204c5cde68c2ca8f1d67"
},
{
"in": "cbec35d2fc21e9",
"out": "c9809ca68c9013f34b7525936db84b5737c591da"
},
{
"in": "e8f3385f8b210a7e",
"out": "497049f4d70e7b42a3f30582c27cc22da5a51cd7"
},
{
"in": "61a82b5b622c48f6be",
"out": "df01106caf345f6e084dc5a46137ead345d445d9"
},
{
"in": "4ac97ba5461eaf9228fe",
"out": "c44507e89c39b4e9965fc9610b6b82985b1a2b57"
},
{
"in": "0e0b7d71b3cdf458b883b5",
"out": "39f567e3b8a7ae93be5ad8e35067d8b2ac81b9ce"
},
{
"in": "43a4a8265fb24f3279d6cde3",
"out": "f6342d8150996d93ca43369a69a502f14dde041f"
},
{
"in": "5cc368c293058fa49906602624",
"out": "893a6a07ee0a2669baf707ed3ba2376125ef9654"
},
{
"in": "0a52feeb91c5d0355fbb35e93f5d",
"out": "5162732ef94afe500679f550cbecf8043b70ada0"
},
{
"in": "a3fc5e1467219db6add73f59808955",
"out": "3dd7b8c0d2278884b5a5c22a3caa061131a5ad7a"
},
{
"in": "ed45b0bc151b3a739d710ab3af72a32b",
"out": "5ba207c3a45289463c98a21e2138c3758d2ddc4a"
},
{
"in": "af7f01692a1aa195e2a3856dd936285f2b",
"out": "d744fc79d37aa33e16c60dd7973b7e0d1e790828"
},
{
"in": "5028d7983e49966980ac8a564caeddcce890",
"out": "5906becdaf00a5c700d548efa92fb23fcd1c8d6f"
},
{
"in": "c17757bf03b8661b4407d06e4e47e8b6c678fb",
"out": "2b7202910cdbd3cda240affaa5929d6f82febc48"
},
{
"in": "5c4172923580ed789714245e42fda873f405ee97",
"out": "053dafe5970069c38f5575a54c4bbad3fdc34dbc"
},
{
"in": "74f77522ad9fbc0e45da88354ee4f5552eac5f02f7",
"out": "0b72df59fcc468cc4bef4b0a77da59dd4ea4486b"
},
{
"in": "33a75fb9cdbfbc9a257b122be7ed337bd65cb1ffe9a8",
"out": "1b8a94bcefe1e6cd9000a70ab342fe7930e62220"
},
{
"in": "8d7042cbdcd336c86e7732a1e633bdcc137380afc37d25",
"out": "f18c667a28aa595890e689d6cd19fae3f9fb7d7c"
},
{
"in": "d9419ba075c613f701e19de6449e21a22fe4bb58067a5b0c",
"out": "d656a0f985e186388c2a211ae0ec8ea30b059505"
},
{
"in": "8e5a281fa95e7e9b42ec0cb0b9a35b54851257ac78494078b4",
"out": "aebecedafad9edfdc204ac1303d14f549ee12ccc"
},
{
"in": "3e01a429466e67653b7b07751f8e497929a0de5515fba7d69fd6",
"out": "1640abee310d1860d7944f0b9929d3e4264ad358"
},
{
"in": "1cf65ab71ad544d423cb904f064a5d227a01761d6a6188296201e1",
"out": "0181503434c565b41eff7c432e220bcc290f0a71"
},
{
"in": "5781599b64981d4657b3c4751da4d7594bf446e386621ec838c30d84",
"out": "07d728840ab1449d31cbadcd5466d887edfd4650"
},
{
"in": "44c392b435a8ce90062f7ba756d23fd7a3582f23c61dbe8a06bcaa2b62",
"out": "4adaccc7626aabf56320040e42174bf284d4333c"
},
{
"in": "053aee544bd93733c6faa8af9b89d1d7c1067832273f23eb6bb5d1d7abf4",
"out": "a993d46bfd0705cb8692f6d6b88ae5dcb20cbcce"
},
{
"in": "7a641e5243317176e497ddd286e47a2a36def6451a4a44aeba12c03cfefa57",
"out": "c0ceed6ac5a60323ef0b6d07535ad5264eef0aff"
},
{
"in": "3bd0af34c27efca58ce047f265f1cfc8be60280cbfa57225afe6b353ec1b1b86",
"out": "aed53c23187adcf3c2d85fe77aa3ffa7cacc85ad"
},
{
"in": "5c861109b9a1fdfb6e9b61737461f722e6582c94c9b406ded106dff2fbacdf5b07",
"out": "ca900eed8f00a8144c60756317ca7b23fe5797de"
},
{
"in": "d0416b0ba3b19ee355186ac2a09acfe91964f229de3d4119ad5ab4a09cf0fe10dd2a",
"out": "b2421a5314d8311c18773decc2798300a36ce63d"
},
{
"in": "f7077210023297cf642c3755338ebd2ca234c22269ffad31a7bc6b12ebbffccede293d",
"out": "797538f704ef4fb5d7e93dde8b9c742eab19010f"
},
{
"in": "169ed88895e743a171581730d9ce32b90e557c2ac178f00ad15c13b2639bea59ab5cf95b",
"out": "2ba71c7b2be43af1825a3bbc7dccd104a8f8ab74"
},
{
"in": "747ef1d2ffe2942444429b7b6d46631c7bc8c00a63d44176014e8594d9f2e09451e956b0f7",
"out": "5eedcddccab146ba30c9613d5b4825ddf6d0df57"
},
{
"in": "546e13efd694f71819385ee6f9a8022eb57b0e6173ad2042984aacbb6cf506102317ddda6773",
"out": "5bcde7dee110935114f5422194178110a0c8558e"
},
{
"in": "8d0ad66f63ee8211654eb12362f5ce0f7a0672a3e77a0f3fbb6e6986c13abd2debd6e7f916fc7f",
"out": "54fd721965d4c6dbebe9e3d2b5a334957ea3f0bc"
},
{
"in": "6120f98d23ddabf5ca10022cf22a58cc89cd516f6b9ad1901a6c450b89569bd75b6ed43d1ab1ed1f",
"out": "560fd1cf282e07a4fbbcb495ea33c550cec1cc1a"
},
{
"in": "7497fea1d85825b29dbece7c90f52b1b4760393693b31f56eb366571d49cb6b0ee854bde58baa64fbe",
"out": "b61aa965cc9a9a1ae9a7cc0087e7417cf942a0a2"
},
{
"in": "2470b0926239d1a1b94f85204edde011000e3d08db9f59e86a8cf7217c48c5dbe5cd00480ffe00a22b89",
"out": "81586567893ffa39a8079018f46b9ff14811c590"
},
{
"in": "56199f4131af9aef9bb9f1cefd8552e75e1b607770a461e6deba3664972908df469b7e065d0db531608171",
"out": "6b74a4ab1c06e029004f14db759c344dbb770b1b"
},
{
"in": "554809f00123973a8308b8d3d5f10ee4e5514da51f615e1f606957e1febc87ba9c369f6ecf29f99cdd57733f",
"out": "fbf816d9a81a8605487f6529255ddc43d031065b"
},
{
"in": "4315b59de93ec8ab4bcd8efd4fee3b87ca4194c24211ea0e79055ddb1fba7511203fd56c0154ae12db25abb434",
"out": "f465d07419a5f3aade344ba5d64a5133eba1b873"
},
{
"in": "7c2d0a5131f7c83789b1cba64e5e6b3485e3340ada2135008fbf1862bf711c7a65592fd4a49c511e29cf910003f5",
"out": "c9d8494fddf45d298164d5bda3df18ab29434d58"
},
{
"in": "7597db5290120386e158890c7bcd70dfffc100184a8339f98e7ebb17b23b6a88772f4a05f2e0a7b2059dea991bf154",
"out": "ce9ab0cf93f471ab913043df76270514ad281ef7"
},
{
"in": "b17ba36d07623609ada5cf2d8f878025f02c3a4f578021bdec12345a53780c71ff9ad5f280bf8e1039e5643389f478ee",
"out": "2b42fe8018b985141e4ec9604f2c75d454155ee5"
},
{
"in": "ab1fd81a1d2128a9767132079c36929bd76336157054fa889ad09b117dd753166cdc9ae51eea15ddf8d7b3113f8534df77",
"out": "22954d720f8259b5f04d288a453dfb54712be71c"
},
{
"in": "53ff3194fd863938021f2033f2e31f508af10e8d0b309fc2e3477495141732bdbda9cba44f83b5459fca222a894783741c7b",
"out": "544e6cace0c57a4af18f0913d951191869ef933d"
},
{
"in": "160ea25128e0509398038a0f490ba73c62a03e58997174594fda1b45924f406dd3bce56253021b446d27ed16cd7ac0eefeaa48",
"out": "6a56c5cb8cb5f4e64ef06fb32d94188884682a2d"
},
{
"in": "f18ce863efd88f83ecdaa3acc57786ad1a961b31446436d4e8f86f5999d475ea783ba3473d09c60d0ca6fe3d8bc16d5b5460c296",
"out": "d85dc07c16a44c4defef238ebfdb7fd14c73a788"
},
{
"in": "890d6a3eb3eaa9c75fb27d643257a8196387a5a49556ca3e81351b2668e56fe1d0e15ba1139c59b48f879664f6cb3b80a9baba6ded",
"out": "50f41fff64a49d8753883f71792305a0eb0ecbc3"
},
{
"in": "370a044bc71750bbcd57491db54dd6335f426299a3fdd0fbf6c419856cf2fec70fbbaaf6ad0adc2b31681be7437f1de7d02393034e00",
"out": "a31c9760489983399ea803bc7945914b7ca59142"
},
{
"in": "64bf8d78e7e1ba0f32f6d4d4f1aa68b12965a7f942b2b152a47869750ddab6f1f33c82ba1e75ef76f1b2d9769016cbac063717fb06184e",
"out": "e2401f5aa89927a7f51d4d6345b482b135edbd32"
},
{
"in": "f434cdf6be6104121a533fb7f7d4f100ff1f0f6ddaa76297a32bdae535cf040cb38be51cddb96107e1045cf24444bf482d9ea131cd163a53",
"out": "d95ab29a9f3c2a2db6374ff7a0deecee521ba792"
},
{
"in": "1b99beb5458c96d9d402d85cb1ecb797d352ac8c04f3ffaca76a33b365ceedce9adba53a5bf381eca73d863da273fbc47fb3f9a9f6f25829c7",
"out": "220f85e79731c094c8d457cf97296f89efe404e6"
},
{
"in": "034a75357af6255f0b55856fa240fdcdebafadbfbcb500d17e1830a3d074b252ab2777333a9381d63dbd4db685a72a67e41825b331f0e183daa9",
"out": "a0671c3badc8d07c59f6551017b00211b3c35b9c"
},
{
"in": "9c17ddd3617c5522491deb7ff1bf56076acd10ee7c482d606247ebd6d67f8062a3e9ecf29d938e04879c062ffea08c2f87f3be840c50294ffa1de9",
"out": "47fad7e0d17a05345e130fdf458071900e90371d"
},
{
"in": "147c4fdd4bb30f38661c0a4c57cfccf5de5369b089edcb74af24e814ad4bc17a466fbd2501e745ae16d2d0c976d3dcf2f93e2e7088733bc6f4013f28",
"out": "fca67427fac3bfff73fbc789a4d8fc0058b1729a"
},
{
"in": "719f6946f69e185fbdd545ef118f9527630025acf9b62df5c38e69c9855c7b63e905eefd099e0635d2c40815a01b5ea79585da3e4f1a2ec97c46dd1f00",
"out": "7eef5539213a672c7599b489b0f80b954b0e9d1d"
},
{
"in": "997c62cf0780fdd3164780d38ba784905e4a87bf1da252e13f6c320040193e5cad1eab40a6db7291df54952c1dbe3b76541856d2b71591d0794435243372",
"out": "7e882963afd9163ac13f7e4c1a7dca9fc4c6ccf7"
},
{
"in": "d93d8e8987e0d01bdaa37b1afc53a07c5a7f46273d1f77a472d83d0f9d9b241599afc51846c16e33d1c1e2e532bed264c49b2a9919f324834b097509933798",
"out": "fd21687491f7f8d2a8edd82db8f08137d6c1499f"
},
{
"in": "cc54b091b967d610be6d6d889f909f843419091cdae2c4447b8c830045b12760964ad8d05eb0285a428baca3e7b7398a37f7982238288732710ec97b5e32122a",
"out": "bb391a32d4f84199a444f6e9476b908209044624"
},
{
"in": "61b9077fd4a0e2e73060b4b26e86fc7a8131e0e305a5d874bbcd28bedfa46fa84dd89431fef6e16e4b5b3781956dfdff1c1843db12329f6c1c365f9a91b3a1f15b",
"out": "e3d6e5f7196b310a56089e76220a59d523dda37a"
},
{
"in": "9caffbb1325c6579b094c4ee8f5b1eb9ccdc5ee861ffa090e055ff2ce6323295afca2de193f15e5fe64ab7ac655b9e99145ce1786f266bfac834a0ff367fd174540c",
"out": "954d88771139f64710e8f31a0b990e8a1e569a74"
},
{
"in": "bc15671078b9ad440eb3c16fe28a33216db848f19a289a6c805655053311a2299f9c7d091d3aa130e0df2e318e75402d54528a9c67fb4b14d754d4dc41e1c06787669e",
"out": "69f3a729309b288ee077582807dbeec2ed5d4360"
},
{
"in": "",
"out": "9c1185a5c5e9fc54612808977ee8f548b2258d31"
},
{
"in": "61",
"out": "0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"
},
{
"in": "616263",
"out": "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"
},
{
"in": "6d65737361676520646967657374",
"out": "5d0689ef49d2fae572b881b123a85ffa21595f36"
},
{
"in": "6162636465666768696a6b6c6d6e6f707172737475767778797a",
"out": "f71c27109c692c1b56bbdceb5b9d2865b3708dbc"
},
{
"in": "6162636462636465636465666465666765666768666768696768696a68696a6b696a6b6c6a6b6c6d6b6c6d6e6c6d6e6f6d6e6f706e6f7071",
"out": "12a053384a9c0c88e405a06c27dcf49ada62eb2b"
},
{
"in": "4142434445464748494a4b4c4d4e4f505152535455565758595a6162636465666768696a6b6c6d6e6f707172737475767778797a30313233343536373839",
"out": "b0e20b6e3116640286ed3a87a5713079b21f5189"
},
{
"in": "3132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930",
"out": "9b752e45573d4b39f4dbd3323cab82bf63326bfb"
},
{
"in": "35b8a2f1b5f332f159afb86212dcc4de02b3fba3794b0e5fe74a4ed9f1a55f55ae7711c6fee3178a410e3979d8b07594d769ad733ea00d64dcc1d601b871f918f1fdd914c2b6f9ec49b8ac13a62a07e1091e0dd024e645bf3b602b4706583060cf9502e4ea6911ac9eb90e13816530b7683babaae225781440c35e8031515c45d5e4f5493faed6b0457dd661b713895fb9d22646ecad7c07187b65aa508fe4f5b15a5c428264910143581512dcfbda974902037d82451e4b1e12ad3364c8f848e2b2a7d53d25f30c06aa6c62784de8b8b6d097559c62d340c8103d51e0c303b1781f5a95aa6ad1b7efe3db9e8d8be9d1271afc4da9b0928642818965c922ddbb0b0a53e6d2ece3672e9568d0e285177bc5e5a9c2349788f8987267ad5ed26092a552ad3674344de7fd79b9e33b855da47f8ca0a70ee806945a6417c92c97cdc14c6bd9b7b25a2d89feb62c594fd11a7cf3bea1ef3018b26b0e401ea8c7b8f07dac07b77acb78c3f45a73ba9fc32301eb3a19dff000f3413ae0e925481d8f93d387cd458d749ba848bd4a92b9b7aeb0e244e479abc4a85f50e94c7299e06bf0daa1587f49907cd94c426c33acfc31e08d183a4353a36d8c6d39aaa1af20519629c01a45bdcd60b115cd490e9873d74fbea994b75bb7fee9f2167850afaf42873d44543e3d6359a0a0a140705d4021de256ab12c38184bf822b9b69d7fb059c32ee332d86ab9df26d1cf832381555cad1688a362bdb1f2982a55aaa37e5137b95b5961e8f354ea30ecb13b98dc697605556df4c4eede6593016b7bc30862c55c6651da8a3589dd3e9ee42495374ba7e6c6bdeff8cc078559dcd99581061348b13fae03259832ead8bb84fab48ebf725c6c5037539fc373791f21d34b56de04a6cbc591b798b7cae2ad8bd5bf9e963559c981286f26802fd1a49a5a906ab814b34fb514a8ed0fec7b67a550a59fbeda7b0685e69add13b68a69e070cf2c9e8d6cf4caee4aba732ba20a8ba6c3db8bffd392cc2d14d46367d451f0a3b77a411b128224aa473247c4a9a446606cea8414e9c09de13301c8e60b2ee51e9801e1cffc6e9ef283238e2ad209e5d48105d48044bfd28bd9f32c53d5d37ccb158fe597983cbb45a05f1b9148f25772ecf3667554247cdb2a95a99e06275c36c6e4fd779dc9f8fa457516ea4cd020f6726e3a32df65e34bd85ddfe16b285a0afb0f3e2dd8954a9e636bca5cf7fed181b041d14d223f10ad0de9e23372dd7a61718cab6b9cc7c1d88d98697356811ac0b834228a299f6eef970e11d59e9fd307dacd40256ad7beb203175ccabf70d9137bc29e2c7b0494c3fa6d338b027bb993e870b02ec68a08ec8a24bd163d5ab43411ab7bcf1a09f64a7eafe15a0c350f5d6f0e68c1a83fece5227112f8e6f8ef11149b3188970a279c9559bf11e7a4ef9f72c9c1e9b58ee48ff880aa",
"out": "b48c57caa63ffcc261e4b3532e75cba497b4fb6c"
}
]
}

1564
packages/crypto/src/testdata/sha.json vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"outDir": "build",
"declarationDir": "build/types",
"rootDir": "src"
},
"include": [
"src/**/*"
]
}

View File

@ -0,0 +1,3 @@
{
"extends": "../../tslint.json"
}

View File

@ -0,0 +1,14 @@
const packageJson = require("./package.json");
module.exports = {
src: ["./src"],
out: "docs",
exclude: "**/*.spec.ts",
target: "es6",
name: `${packageJson.name} Documentation`,
readme: "README.md",
mode: "file",
excludeExternals: true,
excludeNotExported: true,
excludePrivate: true,
};

7
packages/crypto/types/bip39.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
import { EnglishMnemonic } from "./englishmnemonic";
export declare class Bip39 {
static encode(entropy: Uint8Array): EnglishMnemonic;
static decode(mnemonic: EnglishMnemonic): Uint8Array;
static mnemonicToSeed(mnemonic: EnglishMnemonic, password?: string): Promise<Uint8Array>;
private static pbkdf2;
}

View File

@ -0,0 +1,7 @@
export declare class EnglishMnemonic {
static readonly wordlist: readonly string[];
private static readonly mnemonicMatcher;
private readonly data;
constructor(mnemonic: string);
toString(): string;
}

5
packages/crypto/types/hash.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
export interface HashFunction {
readonly blockSize: number;
readonly update: (_: Uint8Array) => HashFunction;
readonly digest: () => Uint8Array;
}

11
packages/crypto/types/hmac.d.ts vendored Normal file
View File

@ -0,0 +1,11 @@
import { HashFunction } from "./hash";
export declare class Hmac<H extends HashFunction> implements HashFunction {
readonly blockSize: number;
private readonly messageHasher;
private readonly oKeyPad;
private readonly iKeyPad;
private readonly hash;
constructor(hashFunctionConstructor: new () => H, originalKey: Uint8Array);
update(data: Uint8Array): Hmac<H>;
digest(): Uint8Array;
}

30
packages/crypto/types/index.d.ts vendored Normal file
View File

@ -0,0 +1,30 @@
export { Bip39 } from "./bip39";
export { EnglishMnemonic } from "./englishmnemonic";
export { HashFunction } from "./hash";
export { Hmac } from "./hmac";
export { Keccak256 } from "./keccak";
export {
Xchacha20poly1305Ietf,
Xchacha20poly1305IetfCiphertext,
Xchacha20poly1305IetfKey,
Xchacha20poly1305IetfMessage,
Xchacha20poly1305IetfNonce,
Argon2id,
Argon2idOptions,
Ed25519,
Ed25519Keypair,
} from "./libsodium";
export { Random } from "./random";
export { Ripemd160 } from "./ripemd";
export { Secp256k1, Secp256k1Keypair } from "./secp256k1";
export { ExtendedSecp256k1Signature, Secp256k1Signature } from "./secp256k1signature";
export { Sha1, Sha256, Sha512 } from "./sha";
export {
pathToString,
stringToPath,
Slip10,
Slip10Curve,
Slip10RawIndex,
Slip10Result,
slip10CurveFromString,
} from "./slip10";

8
packages/crypto/types/keccak.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
import { HashFunction } from "./hash";
export declare class Keccak256 implements HashFunction {
readonly blockSize: number;
private readonly impl;
constructor(firstData?: Uint8Array);
update(data: Uint8Array): Keccak256;
digest(): Uint8Array;
}

45
packages/crypto/types/libsodium.d.ts vendored Normal file
View File

@ -0,0 +1,45 @@
import { As } from "type-tagger";
export declare type Xchacha20poly1305IetfKey = Uint8Array & As<"xchacha20poly1305ietf-key">;
export declare type Xchacha20poly1305IetfMessage = Uint8Array & As<"xchacha20poly1305ietf-message">;
export declare type Xchacha20poly1305IetfNonce = Uint8Array & As<"xchacha20poly1305ietf-nonce">;
export declare type Xchacha20poly1305IetfCiphertext = Uint8Array & As<"xchacha20poly1305ietf-ciphertext">;
export interface Argon2idOptions {
readonly outputLength: number;
readonly opsLimit: number;
readonly memLimitKib: number;
}
export declare class Argon2id {
static execute(password: string, salt: Uint8Array, options: Argon2idOptions): Promise<Uint8Array>;
}
export declare class Ed25519Keypair {
static fromLibsodiumPrivkey(libsodiumPrivkey: Uint8Array): Ed25519Keypair;
readonly privkey: Uint8Array;
readonly pubkey: Uint8Array;
constructor(privkey: Uint8Array, pubkey: Uint8Array);
toLibsodiumPrivkey(): Uint8Array;
}
export declare class Ed25519 {
/**
* Generates a keypair deterministically from a given 32 bytes seed.
*
* This seed equals the Ed25519 private key.
* For implementation details see crypto_sign_seed_keypair in
* https://download.libsodium.org/doc/public-key_cryptography/public-key_signatures.html
* and diagram on https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/
*/
static makeKeypair(seed: Uint8Array): Promise<Ed25519Keypair>;
static createSignature(message: Uint8Array, keyPair: Ed25519Keypair): Promise<Uint8Array>;
static verifySignature(signature: Uint8Array, message: Uint8Array, pubkey: Uint8Array): Promise<boolean>;
}
export declare class Xchacha20poly1305Ietf {
static encrypt(
message: Xchacha20poly1305IetfMessage,
key: Xchacha20poly1305IetfKey,
nonce: Xchacha20poly1305IetfNonce,
): Promise<Xchacha20poly1305IetfCiphertext>;
static decrypt(
ciphertext: Xchacha20poly1305IetfCiphertext,
key: Xchacha20poly1305IetfKey,
nonce: Xchacha20poly1305IetfNonce,
): Promise<Xchacha20poly1305IetfMessage>;
}

6
packages/crypto/types/random.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
export declare class Random {
/**
* Returns `count` cryptographically secure random bytes
*/
static getBytes(count: number): Uint8Array;
}

8
packages/crypto/types/ripemd.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
import { HashFunction } from "./hash";
export declare class Ripemd160 implements HashFunction {
readonly blockSize: number;
private readonly impl;
constructor(firstData?: Uint8Array);
update(data: Uint8Array): Ripemd160;
digest(): Uint8Array;
}

20
packages/crypto/types/secp256k1.d.ts vendored Normal file
View File

@ -0,0 +1,20 @@
import { As } from "type-tagger";
import { ExtendedSecp256k1Signature, Secp256k1Signature } from "./secp256k1signature";
interface Keypair {
readonly pubkey: Uint8Array;
readonly privkey: Uint8Array;
}
export declare type Secp256k1Keypair = Keypair & As<"secp256k1-keypair">;
export declare class Secp256k1 {
static makeKeypair(privkey: Uint8Array): Promise<Secp256k1Keypair>;
static createSignature(messageHash: Uint8Array, privkey: Uint8Array): Promise<ExtendedSecp256k1Signature>;
static verifySignature(
signature: Secp256k1Signature,
messageHash: Uint8Array,
pubkey: Uint8Array,
): Promise<boolean>;
static recoverPubkey(signature: ExtendedSecp256k1Signature, messageHash: Uint8Array): Uint8Array;
static compressPubkey(pubkey: Uint8Array): Uint8Array;
static trimRecoveryByte(signature: Uint8Array): Uint8Array;
}
export {};

View File

@ -0,0 +1,34 @@
export declare class Secp256k1Signature {
/**
* Takes the pair of integers (r, s) as 2x32 byte of binary data.
*
* Note: This is the format Cosmos SDK uses natively.
*
* @param data a 64 byte value containing integers r and s.
*/
static fromFixedLength(data: Uint8Array): Secp256k1Signature;
static fromDer(data: Uint8Array): Secp256k1Signature;
private readonly data;
constructor(r: Uint8Array, s: Uint8Array);
r(length?: number): Uint8Array;
s(length?: number): Uint8Array;
toDer(): Uint8Array;
}
/**
* A Secp256k1Signature plus the recovery parameter
*/
export declare class ExtendedSecp256k1Signature extends Secp256k1Signature {
/**
* Decode extended signature from the simple fixed length encoding
* described in toFixedLength().
*/
static fromFixedLength(data: Uint8Array): ExtendedSecp256k1Signature;
readonly recovery: number;
constructor(r: Uint8Array, s: Uint8Array, recovery: number);
/**
* A simple custom encoding that encodes the extended signature as
* r (32 bytes) | s (32 bytes) | recovery param (1 byte)
* where | denotes concatenation of bonary data.
*/
toFixedLength(): Uint8Array;
}

22
packages/crypto/types/sha.d.ts vendored Normal file
View File

@ -0,0 +1,22 @@
import { HashFunction } from "./hash";
export declare class Sha1 implements HashFunction {
readonly blockSize: number;
private readonly impl;
constructor(firstData?: Uint8Array);
update(data: Uint8Array): Sha1;
digest(): Uint8Array;
}
export declare class Sha256 implements HashFunction {
readonly blockSize: number;
private readonly impl;
constructor(firstData?: Uint8Array);
update(data: Uint8Array): Sha256;
digest(): Uint8Array;
}
export declare class Sha512 implements HashFunction {
readonly blockSize: number;
private readonly impl;
constructor(firstData?: Uint8Array);
update(data: Uint8Array): Sha512;
digest(): Uint8Array;
}

40
packages/crypto/types/slip10.d.ts vendored Normal file
View File

@ -0,0 +1,40 @@
import { Uint32 } from "@iov/encoding";
export interface Slip10Result {
readonly chainCode: Uint8Array;
readonly privkey: Uint8Array;
}
/**
* Raw values must match the curve string in SLIP-0010 master key generation
*
* @see https://github.com/satoshilabs/slips/blob/master/slip-0010.md#master-key-generation
*/
export declare enum Slip10Curve {
Secp256k1 = "Bitcoin seed",
Ed25519 = "ed25519 seed",
}
/**
* Reverse mapping of Slip10Curve
*/
export declare function slip10CurveFromString(curveString: string): Slip10Curve;
export declare class Slip10RawIndex extends Uint32 {
static hardened(hardenedIndex: number): Slip10RawIndex;
static normal(normalIndex: number): Slip10RawIndex;
isHardened(): boolean;
}
export declare class Slip10 {
static derivePath(curve: Slip10Curve, seed: Uint8Array, path: readonly Slip10RawIndex[]): Slip10Result;
private static master;
private static child;
/**
* Implementation of ser_P(point(k_par)) from BIP-0032
*
* @see https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
*/
private static serializedPoint;
private static childImpl;
private static isZero;
private static isGteN;
private static n;
}
export declare function pathToString(path: readonly Slip10RawIndex[]): string;
export declare function stringToPath(input: string): readonly Slip10RawIndex[];

View File

@ -0,0 +1,17 @@
const glob = require("glob");
const path = require("path");
const target = "web";
const distdir = path.join(__dirname, "dist", "web");
module.exports = [
{
// bundle used for Karma tests
target: target,
entry: glob.sync("./build/**/*.spec.js"),
output: {
path: distdir,
filename: "tests.js",
},
},
];

View File

@ -984,7 +984,7 @@
dependencies:
"@types/babel-types" "*"
"@types/bn.js@^4.11.6":
"@types/bn.js@*", "@types/bn.js@^4.11.6":
version "4.11.6"
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c"
integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==
@ -1026,6 +1026,13 @@
resolved "https://registry.yarnpkg.com/@types/diff/-/diff-4.0.2.tgz#2e9bb89f9acc3ab0108f0f3dc4dbdcf2fff8a99c"
integrity sha512-mIenTfsIe586/yzsyfql69KRnA75S8SVXQbTLpDejRrjH0QSJcpu3AUOi/Vjnt9IOsXKxPhJfGpQUNMueIU1fQ==
"@types/elliptic@^6.4.12":
version "6.4.12"
resolved "https://registry.yarnpkg.com/@types/elliptic/-/elliptic-6.4.12.tgz#e8add831f9cc9a88d9d84b3733ff669b68eaa124"
integrity sha512-gP1KsqoouLJGH6IJa28x7PXb3cRqh83X8HCLezd2dF+XcAIMKYv53KV+9Zn6QA561E120uOqZBQ+Jy/cl+fviw==
dependencies:
"@types/bn.js" "*"
"@types/eslint-visitor-keys@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
@ -1123,6 +1130,11 @@
"@types/abstract-leveldown" "*"
"@types/node" "*"
"@types/libsodium-wrappers@^0.7.7":
version "0.7.7"
resolved "https://registry.yarnpkg.com/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.7.tgz#cdb25e85458612ec80f0157c3815fac187d0b6d2"
integrity sha512-Li91pVKcLvQJK3ZolwCPo85oxf2gKBCApgnesRxYg4OVYchLXcJB2eivX8S87vfQVv6ZRnyCO1lLDosZGJfpRg==
"@types/mime@*":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d"
@ -1153,6 +1165,13 @@
resolved "https://registry.yarnpkg.com/@types/pako/-/pako-1.0.1.tgz#33b237f3c9aff44d0f82fe63acffa4a365ef4a61"
integrity sha512-GdZbRSJ3Cv5fiwT6I0SQ3ckeN2PWNqxd26W9Z2fCK1tGrrasGy4puvNFtnddqH9UJFMQYXxEuuB7B8UK+LLwSg==
"@types/pbkdf2@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/pbkdf2/-/pbkdf2-3.0.0.tgz#5d9ca5f12a78a08cc89ad72883ad4a30af359229"
integrity sha512-6J6MHaAlBJC/eVMy9jOwj9oHaprfutukfW/Dyt0NEnpQ/6HN6YQrpvLwzWdWDeWZIdenjGHlbYDzyEODO5Z+2Q==
dependencies:
"@types/node" "*"
"@types/random-js@^1.0.31":
version "1.0.31"
resolved "https://registry.yarnpkg.com/@types/random-js/-/random-js-1.0.31.tgz#18a8bcc075afa504421e638fcbe021f27e802941"
@ -1163,6 +1182,13 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
"@types/ripemd160@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/ripemd160/-/ripemd160-2.0.0.tgz#d33e49cf66edf4668828030d4aa80116bbf8ae81"
integrity sha512-LD6AO/+8cAa1ghXax9NG9iPDLPUEGB2WWPjd//04KYfXxTwHvlDEfL0NRjrM5z9XWBi6WbKw75Are0rDyn3PSA==
dependencies:
"@types/node" "*"
"@types/serve-static@*":
version "1.13.3"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1"
@ -1171,6 +1197,18 @@
"@types/express-serve-static-core" "*"
"@types/mime" "*"
"@types/sha.js@^2.4.0":
version "2.4.0"
resolved "https://registry.yarnpkg.com/@types/sha.js/-/sha.js-2.4.0.tgz#bce682ef860b40f419d024fa08600c3b8d24bb01"
integrity sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==
dependencies:
"@types/node" "*"
"@types/unorm@^1.3.27":
version "1.3.28"
resolved "https://registry.yarnpkg.com/@types/unorm/-/unorm-1.3.28.tgz#580141162f2fd221faae2b2d68da6c839402c375"
integrity sha512-l3uh18vcvkQ964HSK7Tx0YbhxN/Hj+k1w3nLT08n770lngqVKljmF7Ht4e7elFbx6L2WYse97whtpJOo8MHtxQ==
"@types/yargs-parser@*":
version "15.0.0"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"