Merge pull request #196 from CosmWasm/add-iov-deps

Fork IOV dependencies
This commit is contained in:
Simon Warta 2020-06-09 18:09:54 +02:00 committed by GitHub
commit 16eb718b45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
177 changed files with 10399 additions and 133 deletions

9
NOTICE
View File

@ -8,6 +8,15 @@ 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.
The code in packages/utils was forked from https://github.com/iov-one/iov-core/tree/v2.3.2/packages/iov-utils
on 2020-06-05.
The code in packages/encoding and packages/math was forked from https://github.com/iov-one/iov-core/tree/v2.3.2/packages/iov-encoding
on 2020-06-05.
Copyright 2018-2020 IOV SAS
Copyright 2020 Confio UO
Copyright 2020 Simon Warta

View File

@ -20,9 +20,7 @@
"type": "git",
"url": "https://github.com/CosmWasm/cosmjs/tree/master/packages/bcp"
},
"publishConfig": {
"access": "public"
},
"private": true,
"scripts": {
"docs": "shx rm -rf docs && typedoc --options typedoc.js",
"format": "prettier --write --loglevel warn \"./src/**/*.ts\"",
@ -39,12 +37,13 @@
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js"
},
"dependencies": {
"@cosmjs/crypto": "^0.8.0",
"@cosmjs/encoding": "^0.8.0",
"@cosmjs/math": "^0.8.0",
"@cosmjs/sdk38": "^0.8.0",
"@cosmjs/utils": "^0.8.0",
"@iov/bcp": "^2.3.2",
"@iov/crypto": "^2.3.2",
"@iov/encoding": "^2.3.2",
"@iov/stream": "^2.3.2",
"@iov/utils": "^2.3.2",
"bn.js": "^5.1.1",
"fast-deep-equal": "^3.1.1",
"readonly-date": "^1.0.0",

View File

@ -1,5 +1,5 @@
import { fromBase64, fromHex } from "@cosmjs/encoding";
import { Algorithm, PubkeyBytes } from "@iov/bcp";
import { fromBase64, fromHex } from "@iov/encoding";
import { pubkeyToAddress } from "./address";

View File

@ -1,7 +1,7 @@
import { Secp256k1 } from "@cosmjs/crypto";
import { toBase64 } from "@cosmjs/encoding";
import { PubKey, pubkeyToAddress as sdkPubkeyToAddress, pubkeyType } from "@cosmjs/sdk38";
import { Address, Algorithm, PubkeyBundle } from "@iov/bcp";
import { Secp256k1 } from "@iov/crypto";
import { toBase64 } from "@iov/encoding";
// See https://github.com/tendermint/tendermint/blob/f2ada0a604b4c0763bda2f64fac53d506d3beca7/docs/spec/blockchain/encoding.md#public-key-cryptography
export function pubkeyToAddress(pubkey: PubkeyBundle, prefix: string): Address {

View File

@ -1,6 +1,6 @@
import { Sha256 } from "@cosmjs/crypto";
import { toHex, toUtf8 } from "@cosmjs/encoding";
import { ChainId } from "@iov/bcp";
import { Sha256 } from "@iov/crypto";
import { toHex, toUtf8 } from "@iov/encoding";
const hashedPrefix = "hashed-";

View File

@ -1,5 +1,5 @@
import { toUtf8 } from "@cosmjs/encoding";
import { PostableBytes, PrehashType } from "@iov/bcp";
import { toUtf8 } from "@iov/encoding";
import { CosmosCodec } from "./cosmoscodec";
import { chainId, nonce, sendTxJson, signedTxBin, signedTxEncodedJson, signedTxJson } from "./testdata.spec";

View File

@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/camelcase */
import { Bech32, fromUtf8, toUtf8 } from "@cosmjs/encoding";
import { isStdTx, makeSignBytes, StdTx } from "@cosmjs/sdk38";
import {
Address,
@ -14,7 +15,6 @@ import {
TxCodec,
UnsignedTransaction,
} from "@iov/bcp";
import { Bech32, fromUtf8, toUtf8 } from "@iov/encoding";
import { pubkeyToAddress } from "./address";
import { Caip5 } from "./caip5";

View File

@ -1,4 +1,7 @@
import { Random, Secp256k1, Secp256k1Signature, Sha256 } from "@cosmjs/crypto";
import { Bech32, fromBase64 } from "@cosmjs/encoding";
import { decodeSignature } from "@cosmjs/sdk38";
import { assert } from "@cosmjs/utils";
import {
Account,
Address,
@ -17,10 +20,7 @@ import {
TransactionState,
UnsignedTransaction,
} from "@iov/bcp";
import { Random, Secp256k1, Secp256k1Signature, Sha256 } from "@iov/crypto";
import { Bech32, fromBase64 } from "@iov/encoding";
import { HdPaths, Secp256k1HdWallet, UserProfile } from "@iov/keycontrol";
import { assert } from "@iov/utils";
import BN from "bn.js";
import { CosmosConnection, TokenConfiguration } from "./cosmosconnection";

View File

@ -1,3 +1,5 @@
import { fromUtf8 } from "@cosmjs/encoding";
import { Uint53 } from "@cosmjs/math";
import {
CosmosClient,
findSequenceForSignedTx,
@ -35,7 +37,6 @@ import {
TxCodec,
UnsignedTransaction,
} from "@iov/bcp";
import { fromUtf8, Uint53 } from "@iov/encoding";
import { concat, DefaultValueProducer, ValueAndUpdates } from "@iov/stream";
import equal from "fast-deep-equal";
import { ReadonlyDate } from "readonly-date";

View File

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/camelcase */
import { fromBase64, fromHex } from "@cosmjs/encoding";
import { Coin, IndexedTx, Msg, PubKey, StdSignature } from "@cosmjs/sdk38";
import { Address, Algorithm, SendTransaction, TokenTicker } from "@iov/bcp";
import { fromBase64, fromHex } from "@iov/encoding";
import {
decodeAmount,

View File

@ -1,3 +1,5 @@
import { fromBase64 } from "@cosmjs/encoding";
import { Decimal } from "@cosmjs/math";
import {
Coin,
IndexedTx,
@ -29,7 +31,6 @@ import {
TransactionId,
UnsignedTransaction,
} from "@iov/bcp";
import { Decimal, fromBase64 } from "@iov/encoding";
import { BankToken } from "./types";

View File

@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/camelcase */
import { fromBase64 } from "@cosmjs/encoding";
import {
Address,
Algorithm,
@ -11,7 +12,6 @@ import {
SignedTransaction,
TokenTicker,
} from "@iov/bcp";
import { fromBase64 } from "@iov/encoding";
import {
buildSignedTx,

View File

@ -1,4 +1,6 @@
/* eslint-disable @typescript-eslint/camelcase */
import { Secp256k1 } from "@cosmjs/crypto";
import { toBase64 } from "@cosmjs/encoding";
import {
Coin,
CosmosSdkTx,
@ -19,8 +21,6 @@ import {
SignedTransaction,
UnsignedTransaction,
} from "@iov/bcp";
import { Secp256k1 } from "@iov/crypto";
import { toBase64 } from "@iov/encoding";
import { BankToken } from "./types";

View File

@ -1,3 +1,4 @@
import { fromBase64, toUtf8 } from "@cosmjs/encoding";
import {
Address,
Algorithm,
@ -12,7 +13,6 @@ import {
TokenTicker,
TransactionId,
} from "@iov/bcp";
import { fromBase64, toUtf8 } from "@iov/encoding";
import data from "./testdata/cosmoshub.json";

View File

@ -1,3 +1,4 @@
import { Decimal } from "@cosmjs/math";
import { Coin, IndexedTx, Msg, PubKey, StdFee, StdSignature, StdTx } from "@cosmjs/sdk38";
import {
Amount,
@ -12,7 +13,6 @@ import {
SignedTransaction,
UnsignedTransaction,
} from "@iov/bcp";
import { Decimal } from "@iov/encoding";
import { BankToken } from "./types";
export declare function decodePubkey(pubkey: PubKey): PubkeyBundle;
export declare function decodeSignature(signature: string): SignatureBytes;

View File

@ -39,10 +39,11 @@
],
"dependencies": {
"@cosmjs/cosmwasm": "^0.8.0",
"@cosmjs/crypto": "^0.8.0",
"@cosmjs/encoding": "^0.8.0",
"@cosmjs/math": "^0.8.0",
"@cosmjs/sdk38": "^0.8.0",
"@iov/crypto": "^2.3.2",
"@iov/encoding": "^2.3.2",
"@iov/utils": "^2.3.2",
"@cosmjs/utils": "^0.8.0",
"axios": "^0.19.2",
"babylon": "^6.18.0",
"colors": "^1.3.3",

View File

@ -59,6 +59,27 @@ export function main(originalArgs: readonly string[]): void {
"UploadResult",
],
],
[
"@cosmjs/crypto",
[
"Bip39",
"Ed25519",
"Ed25519Keypair",
"EnglishMnemonic",
"Random",
"Secp256k1",
"Sha256",
"Sha512",
"Slip10",
"Slip10Curve",
"Slip10RawIndex",
],
],
[
"@cosmjs/encoding",
["fromAscii", "fromBase64", "fromHex", "fromUtf8", "toAscii", "toBase64", "toHex", "toUtf8", "Bech32"],
],
["@cosmjs/math", ["Decimal", "Int53", "Uint32", "Uint53", "Uint64"]],
[
"@cosmjs/sdk38",
[
@ -82,43 +103,7 @@ export function main(originalArgs: readonly string[]): void {
"StdTx",
],
],
[
"@iov/crypto",
[
"Bip39",
"Ed25519",
"Ed25519Keypair",
"EnglishMnemonic",
"Random",
"Secp256k1",
"Sha256",
"Sha512",
"Slip10",
"Slip10Curve",
"Slip10RawIndex",
],
],
[
"@iov/encoding",
[
"fromAscii",
"fromBase64",
"fromHex",
"fromUtf8",
"toAscii",
"toBase64",
"toHex",
"toUtf8",
"Bech32",
"Decimal",
// integers
"Int53",
"Uint32",
"Uint53",
"Uint64",
],
],
["@iov/utils", ["assert", "sleep"]],
["@cosmjs/utils", ["assert", "sleep"]],
]);
console.info(colors.green("Initializing session for you. Have fun!"));

View File

@ -36,10 +36,11 @@
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js"
},
"dependencies": {
"@cosmjs/crypto": "^0.8.0",
"@cosmjs/encoding": "^0.8.0",
"@cosmjs/math": "^0.8.0",
"@cosmjs/sdk38": "^0.8.0",
"@iov/crypto": "^2.3.2",
"@iov/encoding": "^2.3.2",
"@iov/utils": "^2.3.2",
"@cosmjs/utils": "^0.8.0",
"axios": "^0.19.0",
"fast-deep-equal": "^3.1.1",
"pako": "^1.0.11"

View File

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/camelcase */
import { Uint53 } from "@cosmjs/math";
import { Coin, CosmosSdkTx, isMsgSend, makeSignBytes, MsgSend, Secp256k1Pen } from "@cosmjs/sdk38";
import { Uint53 } from "@iov/encoding";
import { assert, sleep } from "@iov/utils";
import { assert, sleep } from "@cosmjs/utils";
import { CosmWasmClient } from "./cosmwasmclient";
import { isMsgExecuteContract, isMsgInstantiateContract } from "./msgs";

View File

@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/camelcase */
import { Sha256 } from "@cosmjs/crypto";
import { Bech32, fromHex, fromUtf8, toAscii, toBase64 } from "@cosmjs/encoding";
import { makeSignBytes, MsgSend, Secp256k1Pen, StdFee } from "@cosmjs/sdk38";
import { Sha256 } from "@iov/crypto";
import { Bech32, fromHex, fromUtf8, toAscii, toBase64 } from "@iov/encoding";
import { assert, sleep } from "@iov/utils";
import { assert, sleep } from "@cosmjs/utils";
import { ReadonlyDate } from "readonly-date";
import { Code, CosmWasmClient, PrivateCosmWasmClient } from "./cosmwasmclient";

View File

@ -1,3 +1,5 @@
import { Sha256 } from "@cosmjs/crypto";
import { fromBase64, fromHex, toHex } from "@cosmjs/encoding";
import {
BroadcastMode,
Coin,
@ -7,8 +9,6 @@ import {
PubKey,
StdTx,
} from "@cosmjs/sdk38";
import { Sha256 } from "@iov/crypto";
import { fromBase64, fromHex, toHex } from "@iov/encoding";
import { Log, parseLogs } from "./logs";
import { RestClient } from "./restclient";

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/camelcase */
import { isNonNullObject } from "@iov/encoding";
import { isNonNullObject } from "@cosmjs/utils";
export interface Attribute {
readonly key: string;

View File

@ -1,4 +1,6 @@
/* eslint-disable @typescript-eslint/camelcase */
import { Sha256 } from "@cosmjs/crypto";
import { Bech32, fromAscii, fromBase64, fromHex, toAscii, toBase64, toHex } from "@cosmjs/encoding";
import {
Coin,
encodeBech32Pubkey,
@ -14,9 +16,7 @@ import {
StdSignature,
StdTx,
} from "@cosmjs/sdk38";
import { Sha256 } from "@iov/crypto";
import { Bech32, fromAscii, fromBase64, fromHex, toAscii, toBase64, toHex } from "@iov/encoding";
import { assert, sleep } from "@iov/utils";
import { assert, sleep } from "@cosmjs/utils";
import { ReadonlyDate } from "readonly-date";
import { findAttribute, parseLogs } from "./logs";

View File

@ -1,5 +1,5 @@
import { fromBase64, fromUtf8, toHex, toUtf8 } from "@cosmjs/encoding";
import { BroadcastMode, CosmosSdkTx, RestClient as BaseRestClient } from "@cosmjs/sdk38";
import { fromBase64, fromUtf8, toHex, toUtf8 } from "@iov/encoding";
import { JsonObject, Model, parseWasmData, WasmData } from "./types";

View File

@ -1,7 +1,7 @@
import { Sha256 } from "@cosmjs/crypto";
import { toHex } from "@cosmjs/encoding";
import { Coin, Secp256k1Pen } from "@cosmjs/sdk38";
import { Sha256 } from "@iov/crypto";
import { toHex } from "@iov/encoding";
import { assert } from "@iov/utils";
import { assert } from "@cosmjs/utils";
import { PrivateCosmWasmClient } from "./cosmwasmclient";
import { RestClient } from "./restclient";

View File

@ -1,6 +1,6 @@
import { Sha256 } from "@cosmjs/crypto";
import { toBase64, toHex } from "@cosmjs/encoding";
import { BroadcastMode, Coin, coins, makeSignBytes, MsgSend, StdFee, StdSignature } from "@cosmjs/sdk38";
import { Sha256 } from "@iov/crypto";
import { toBase64, toHex } from "@iov/encoding";
import pako from "pako";
import { isValidBuilder } from "./builder";

View File

@ -1,5 +1,5 @@
import { Random } from "@iov/crypto";
import { Bech32, fromBase64 } from "@iov/encoding";
import { Random } from "@cosmjs/crypto";
import { Bech32, fromBase64 } from "@cosmjs/encoding";
import hackatom from "./testdata/contract.json";

View File

@ -1,4 +1,4 @@
import { fromBase64, fromHex } from "@iov/encoding";
import { fromBase64, fromHex } from "@cosmjs/encoding";
export interface WasmData {
// key is hex-encoded

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)
This package 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 cosmjs repository, licensed under the Apache
License 2.0 (see
[NOTICE](https://github.com/CosmWasm/cosmjs/blob/master/NOTICE) and
[LICENSE](https://github.com/CosmWasm/cosmjs/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,67 @@
{
"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/cosmjs/tree/master/packages/crypto"
},
"publishConfig": {
"access": "public"
},
"scripts": {
"docs": "shx rm -rf docs && typedoc --options typedoc.js",
"lint": "eslint --max-warnings 0 \"**/*.{js,ts}\"",
"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": {
"@cosmjs/encoding": "^0.8.0",
"@cosmjs/math": "^0.8.0",
"@cosmjs/utils": "^0.8.0",
"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 "@cosmjs/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 "@cosmjs/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,242 @@
import { fromAscii, fromBase64, fromHex } from "@cosmjs/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"));
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);
});
});
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);
});
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,39 @@
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
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 "@cosmjs/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]);
}
// eslint-disable-next-line no-bitwise
this.oKeyPad = key.map((keyByte) => keyByte ^ 0x5c);
// eslint-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 "@cosmjs/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 @@
/* eslint-disable no-bitwise */
import { fromHex, toAscii } from "@cosmjs/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 "@cosmjs/utils";
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 "@cosmjs/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 @@
/* eslint-disable no-bitwise */
import { fromHex } from "@cosmjs/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 "@cosmjs/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 "@cosmjs/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 "@cosmjs/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 "@cosmjs/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,213 @@
import { fromHex, toAscii } from "@cosmjs/encoding";
import { Uint32, Uint53 } from "@cosmjs/math";
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,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 "@cosmjs/math";
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

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

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

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

View File

@ -0,0 +1,24 @@
# @cosmjs/encoding
[![npm version](https://img.shields.io/npm/v/@cosmjs/encoding.svg)](https://www.npmjs.com/package/@cosmjs/encoding)
This package is an extension to the JavaScript standard library that is not
bound to blockchain products. It provides basic hex/base64/ascii encoding to
Uint8Array that doesn't rely on Buffer and also provides better error messages
on invalid input.
## Convert between bech32 and hex addresses
```
>> Bech32.encode("tiov", fromHex("1234ABCD0000AA0000FFFF0000AA00001234ABCD"))
'tiov1zg62hngqqz4qqq8lluqqp2sqqqfrf27dzrrmea'
>> toHex(Bech32.decode("tiov1zg62hngqqz4qqq8lluqqp2sqqqfrf27dzrrmea").data)
'1234abcd0000aa0000ffff0000aa00001234abcd'
```
## License
This package is part of the cosmjs repository, licensed under the Apache
License 2.0 (see
[NOTICE](https://github.com/CosmWasm/cosmjs/blob/master/NOTICE) and
[LICENSE](https://github.com/CosmWasm/cosmjs/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,47 @@
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
logLevel: config.LOG_INFO,
// 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,48 @@
{
"name": "@cosmjs/encoding",
"version": "0.8.0",
"description": "Encoding helpers for blockchain projects",
"contributors": ["IOV SAS <admin@iov.one>"],
"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/cosmjs/tree/master/packages/encoding"
},
"publishConfig": {
"access": "public"
},
"scripts": {
"docs": "shx rm -rf docs && typedoc --options typedoc.js",
"lint": "eslint --max-warnings 0 \"**/*.{js,ts}\"",
"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": {
"base64-js": "^1.3.0",
"bech32": "^1.1.4",
"readonly-date": "^1.0.0"
},
"devDependencies": {
"@types/base64-js": "^1.2.5"
}
}

View File

@ -0,0 +1,26 @@
import { fromAscii, toAscii } from "./ascii";
describe("ascii", () => {
it("encodes to ascii", () => {
expect(toAscii("")).toEqual(new Uint8Array([]));
expect(toAscii("abc")).toEqual(new Uint8Array([0x61, 0x62, 0x63]));
expect(toAscii(" ?=-n|~+-*/\\")).toEqual(
new Uint8Array([0x20, 0x3f, 0x3d, 0x2d, 0x6e, 0x7c, 0x7e, 0x2b, 0x2d, 0x2a, 0x2f, 0x5c]),
);
expect(() => toAscii("ö")).toThrow();
expect(() => toAscii("ß")).toThrow();
});
it("decodes from ascii", () => {
expect(fromAscii(new Uint8Array([]))).toEqual("");
expect(fromAscii(new Uint8Array([0x61, 0x62, 0x63]))).toEqual("abc");
expect(
fromAscii(new Uint8Array([0x20, 0x3f, 0x3d, 0x2d, 0x6e, 0x7c, 0x7e, 0x2b, 0x2d, 0x2a, 0x2f, 0x5c])),
).toEqual(" ?=-n|~+-*/\\");
expect(() => fromAscii(new Uint8Array([0x00]))).toThrow();
expect(() => fromAscii(new Uint8Array([0x7f]))).toThrow();
expect(() => fromAscii(new Uint8Array([0xff]))).toThrow();
});
});

View File

@ -0,0 +1,31 @@
export function toAscii(input: string): Uint8Array {
const toNums = (str: string): readonly number[] =>
str.split("").map((x: string) => {
const charCode = x.charCodeAt(0);
// 0x000x1F control characters
// 0x200x7E printable characters
// 0x7F delete character
// 0x800xFF out of 7 bit ascii range
if (charCode < 0x20 || charCode > 0x7e) {
throw new Error("Cannot encode character that is out of printable ASCII range: " + charCode);
}
return charCode;
});
return Uint8Array.from(toNums(input));
}
export function fromAscii(data: Uint8Array): string {
const fromNums = (listOfNumbers: readonly number[]): readonly string[] =>
listOfNumbers.map((x: number): string => {
// 0x000x1F control characters
// 0x200x7E printable characters
// 0x7F delete character
// 0x800xFF out of 7 bit ascii range
if (x < 0x20 || x > 0x7e) {
throw new Error("Cannot decode character that is out of printable ASCII range: " + x);
}
return String.fromCharCode(x);
});
return fromNums(Array.from(data)).join("");
}

View File

@ -0,0 +1,65 @@
import { fromBase64, toBase64 } from "./base64";
describe("base64", () => {
it("encodes to base64", () => {
expect(toBase64(new Uint8Array([]))).toEqual("");
expect(toBase64(new Uint8Array([0x00]))).toEqual("AA==");
expect(toBase64(new Uint8Array([0x00, 0x00]))).toEqual("AAA=");
expect(toBase64(new Uint8Array([0x00, 0x00, 0x00]))).toEqual("AAAA");
expect(toBase64(new Uint8Array([0x00, 0x00, 0x00, 0x00]))).toEqual("AAAAAA==");
expect(toBase64(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00]))).toEqual("AAAAAAA=");
expect(toBase64(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))).toEqual("AAAAAAAA");
expect(toBase64(new Uint8Array([0x61]))).toEqual("YQ==");
expect(toBase64(new Uint8Array([0x62]))).toEqual("Yg==");
expect(toBase64(new Uint8Array([0x63]))).toEqual("Yw==");
expect(toBase64(new Uint8Array([0x61, 0x62, 0x63]))).toEqual("YWJj");
});
it("decodes from base64", () => {
expect(fromBase64("")).toEqual(new Uint8Array([]));
expect(fromBase64("AA==")).toEqual(new Uint8Array([0x00]));
expect(fromBase64("AAA=")).toEqual(new Uint8Array([0x00, 0x00]));
expect(fromBase64("AAAA")).toEqual(new Uint8Array([0x00, 0x00, 0x00]));
expect(fromBase64("AAAAAA==")).toEqual(new Uint8Array([0x00, 0x00, 0x00, 0x00]));
expect(fromBase64("AAAAAAA=")).toEqual(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00]));
expect(fromBase64("AAAAAAAA")).toEqual(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00]));
expect(fromBase64("YQ==")).toEqual(new Uint8Array([0x61]));
expect(fromBase64("Yg==")).toEqual(new Uint8Array([0x62]));
expect(fromBase64("Yw==")).toEqual(new Uint8Array([0x63]));
expect(fromBase64("YWJj")).toEqual(new Uint8Array([0x61, 0x62, 0x63]));
// invalid length
expect(() => fromBase64("a")).toThrow();
expect(() => fromBase64("aa")).toThrow();
expect(() => fromBase64("aaa")).toThrow();
// proper length including invalid character
expect(() => fromBase64("aaa!")).toThrow();
expect(() => fromBase64("aaa*")).toThrow();
expect(() => fromBase64("aaaä")).toThrow();
// proper length plus invalid character
expect(() => fromBase64("aaaa!")).toThrow();
expect(() => fromBase64("aaaa*")).toThrow();
expect(() => fromBase64("aaaaä")).toThrow();
// extra spaces
expect(() => fromBase64("aaaa ")).toThrow();
expect(() => fromBase64(" aaaa")).toThrow();
expect(() => fromBase64("aa aa")).toThrow();
expect(() => fromBase64("aaaa\n")).toThrow();
expect(() => fromBase64("\naaaa")).toThrow();
expect(() => fromBase64("aa\naa")).toThrow();
// position of =
expect(() => fromBase64("=aaa")).toThrow();
expect(() => fromBase64("==aa")).toThrow();
// concatenated base64 strings should not be supported
// see https://github.com/beatgammit/base64-js/issues/42
expect(() => fromBase64("AAA=AAA=")).toThrow();
// wrong number of =
expect(() => fromBase64("a===")).toThrow();
});
});

View File

@ -0,0 +1,12 @@
import * as base64js from "base64-js";
export function toBase64(data: Uint8Array): string {
return base64js.fromByteArray(data);
}
export function fromBase64(base64String: string): Uint8Array {
if (!base64String.match(/^[a-zA-Z0-9+/]*={0,2}$/)) {
throw new Error("Invalid base64 string format");
}
return base64js.toByteArray(base64String);
}

View File

@ -0,0 +1,19 @@
import { Bech32 } from "./bech32";
import { fromHex } from "./hex";
describe("Bech32", () => {
// test data generate using https://github.com/nym-zone/bech32
// bech32 -e -h eth 9d4e856e572e442f0a4b2763e72d08a0e99d8ded
const ethAddressRaw = fromHex("9d4e856e572e442f0a4b2763e72d08a0e99d8ded");
it("encodes", () => {
expect(Bech32.encode("eth", ethAddressRaw)).toEqual("eth1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw");
});
it("decodes", () => {
expect(Bech32.decode("eth1n48g2mjh9ezz7zjtya37wtgg5r5emr0drkwlgw")).toEqual({
prefix: "eth",
data: ethAddressRaw,
});
});
});

View File

@ -0,0 +1,16 @@
import * as bech32 from "bech32";
export class Bech32 {
public static encode(prefix: string, data: Uint8Array): string {
const address = bech32.encode(prefix, bech32.toWords(data));
return address;
}
public static decode(address: string): { readonly prefix: string; readonly data: Uint8Array } {
const decodedAddress = bech32.decode(address);
return {
prefix: decodedAddress.prefix,
data: new Uint8Array(bech32.fromWords(decodedAddress.words)),
};
}
}

View File

@ -0,0 +1,44 @@
import { fromHex, toHex } from "./hex";
describe("fromHex", () => {
it("works", () => {
// simple
expect(fromHex("")).toEqual(new Uint8Array([]));
expect(fromHex("00")).toEqual(new Uint8Array([0x00]));
expect(fromHex("01")).toEqual(new Uint8Array([0x01]));
expect(fromHex("10")).toEqual(new Uint8Array([0x10]));
expect(fromHex("11")).toEqual(new Uint8Array([0x11]));
expect(fromHex("112233")).toEqual(new Uint8Array([0x11, 0x22, 0x33]));
expect(fromHex("0123456789abcdef")).toEqual(
new Uint8Array([0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]),
);
// capital letters
expect(fromHex("AA")).toEqual(new Uint8Array([0xaa]));
expect(fromHex("aAbBcCdDeEfF")).toEqual(new Uint8Array([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]));
// error
expect(() => fromHex("a")).toThrow();
expect(() => fromHex("aaa")).toThrow();
expect(() => fromHex("a!")).toThrow();
expect(() => fromHex("a ")).toThrow();
expect(() => fromHex("aa ")).toThrow();
expect(() => fromHex(" aa")).toThrow();
expect(() => fromHex("a a")).toThrow();
expect(() => fromHex("gg")).toThrow();
});
});
describe("toHex", () => {
it("works", () => {
expect(toHex(new Uint8Array([]))).toEqual("");
expect(toHex(new Uint8Array([0x00]))).toEqual("00");
expect(toHex(new Uint8Array([0x01]))).toEqual("01");
expect(toHex(new Uint8Array([0x10]))).toEqual("10");
expect(toHex(new Uint8Array([0x11]))).toEqual("11");
expect(toHex(new Uint8Array([0x11, 0x22, 0x33]))).toEqual("112233");
expect(toHex(new Uint8Array([0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]))).toEqual(
"0123456789abcdef",
);
});
});

View File

@ -0,0 +1,23 @@
export function toHex(data: Uint8Array): string {
let out = "";
for (const byte of data) {
out += ("0" + byte.toString(16)).slice(-2);
}
return out;
}
export function fromHex(hexstring: string): Uint8Array {
if (hexstring.length % 2 !== 0) {
throw new Error("hex string length must be a multiple of 2");
}
const listOfInts: number[] = [];
for (let i = 0; i < hexstring.length; i += 2) {
const hexByteAsString = hexstring.substr(i, 2);
if (!hexByteAsString.match(/[0-9a-f]{2}/i)) {
throw new Error("hex string contains invalid characters");
}
listOfInts.push(parseInt(hexByteAsString, 16));
}
return new Uint8Array(listOfInts);
}

View File

@ -0,0 +1,6 @@
export { fromAscii, toAscii } from "./ascii";
export { fromBase64, toBase64 } from "./base64";
export { Bech32 } from "./bech32";
export { fromHex, toHex } from "./hex";
export { fromRfc3339, toRfc3339 } from "./rfc3339";
export { fromUtf8, toUtf8 } from "./utf8";

View File

@ -0,0 +1,178 @@
import { fromRfc3339, toRfc3339 } from "./rfc3339";
describe("RFC3339", () => {
it("parses dates with different time zones", () => {
// time zone +/- 0
expect(fromRfc3339("2002-10-02T11:12:13+00:00")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12, 13)));
expect(fromRfc3339("2002-10-02T11:12:13-00:00")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12, 13)));
expect(fromRfc3339("2002-10-02T11:12:13Z")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12, 13)));
// time zone positive (full hours)
expect(fromRfc3339("2002-10-02T11:12:13+01:00")).toEqual(new Date(Date.UTC(2002, 9, 2, 11 - 1, 12, 13)));
expect(fromRfc3339("2002-10-02T11:12:13+02:00")).toEqual(new Date(Date.UTC(2002, 9, 2, 11 - 2, 12, 13)));
expect(fromRfc3339("2002-10-02T11:12:13+03:00")).toEqual(new Date(Date.UTC(2002, 9, 2, 11 - 3, 12, 13)));
expect(fromRfc3339("2002-10-02T11:12:13+11:00")).toEqual(new Date(Date.UTC(2002, 9, 2, 11 - 11, 12, 13)));
// time zone negative (full hours)
expect(fromRfc3339("2002-10-02T11:12:13-01:00")).toEqual(new Date(Date.UTC(2002, 9, 2, 11 + 1, 12, 13)));
expect(fromRfc3339("2002-10-02T11:12:13-02:00")).toEqual(new Date(Date.UTC(2002, 9, 2, 11 + 2, 12, 13)));
expect(fromRfc3339("2002-10-02T11:12:13-03:00")).toEqual(new Date(Date.UTC(2002, 9, 2, 11 + 3, 12, 13)));
expect(fromRfc3339("2002-10-02T11:12:13-11:00")).toEqual(new Date(Date.UTC(2002, 9, 2, 11 + 11, 12, 13)));
// time zone positive (minutes only)
expect(fromRfc3339("2002-10-02T11:12:13+00:01")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12 - 1, 13)));
expect(fromRfc3339("2002-10-02T11:12:13+00:30")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12 - 30, 13)));
expect(fromRfc3339("2002-10-02T11:12:13+00:45")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12 - 45, 13)));
// time zone negative (minutes only)
expect(fromRfc3339("2002-10-02T11:12:13-00:01")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12 + 1, 13)));
expect(fromRfc3339("2002-10-02T11:12:13-00:30")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12 + 30, 13)));
expect(fromRfc3339("2002-10-02T11:12:13-00:45")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12 + 45, 13)));
// time zone positive (hours and minutes)
expect(fromRfc3339("2002-10-02T11:12:13+01:01")).toEqual(
new Date(Date.UTC(2002, 9, 2, 11 - 1, 12 - 1, 13)),
);
expect(fromRfc3339("2002-10-02T11:12:13+04:30")).toEqual(
new Date(Date.UTC(2002, 9, 2, 11 - 4, 12 - 30, 13)),
);
expect(fromRfc3339("2002-10-02T11:12:13+10:20")).toEqual(
new Date(Date.UTC(2002, 9, 2, 11 - 10, 12 - 20, 13)),
);
// time zone negative (hours and minutes)
expect(fromRfc3339("2002-10-02T11:12:13-01:01")).toEqual(
new Date(Date.UTC(2002, 9, 2, 11 + 1, 12 + 1, 13)),
);
expect(fromRfc3339("2002-10-02T11:12:13-04:30")).toEqual(
new Date(Date.UTC(2002, 9, 2, 11 + 4, 12 + 30, 13)),
);
expect(fromRfc3339("2002-10-02T11:12:13-10:20")).toEqual(
new Date(Date.UTC(2002, 9, 2, 11 + 10, 12 + 20, 13)),
);
});
it("parses dates with milliseconds", () => {
expect(fromRfc3339("2002-10-02T11:12:13.000Z")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 0)));
expect(fromRfc3339("2002-10-02T11:12:13.123Z")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 123)));
expect(fromRfc3339("2002-10-02T11:12:13.999Z")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 999)));
});
it("parses dates with low precision fractional seconds", () => {
// 1 digit
expect(fromRfc3339("2002-10-02T11:12:13.0Z")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 0)));
expect(fromRfc3339("2002-10-02T11:12:13.1Z")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 100)));
expect(fromRfc3339("2002-10-02T11:12:13.9Z")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 900)));
// 2 digit
expect(fromRfc3339("2002-10-02T11:12:13.00Z")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 0)));
expect(fromRfc3339("2002-10-02T11:12:13.12Z")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 120)));
expect(fromRfc3339("2002-10-02T11:12:13.99Z")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 990)));
});
it("parses dates with high precision fractional seconds", () => {
// everything after the 3rd digit is truncated
// 4 digits
expect(fromRfc3339("2002-10-02T11:12:13.0000Z")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 0)));
expect(fromRfc3339("2002-10-02T11:12:13.1234Z")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 123)));
expect(fromRfc3339("2002-10-02T11:12:13.9999Z")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 999)));
// 5 digits
expect(fromRfc3339("2002-10-02T11:12:13.00000Z")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 0)));
expect(fromRfc3339("2002-10-02T11:12:13.12345Z")).toEqual(
new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 123)),
);
expect(fromRfc3339("2002-10-02T11:12:13.99999Z")).toEqual(
new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 999)),
);
// 6 digits
expect(fromRfc3339("2002-10-02T11:12:13.000000Z")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 0)));
expect(fromRfc3339("2002-10-02T11:12:13.123456Z")).toEqual(
new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 123)),
);
expect(fromRfc3339("2002-10-02T11:12:13.999999Z")).toEqual(
new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 999)),
);
// 7 digits
expect(fromRfc3339("2002-10-02T11:12:13.0000000Z")).toEqual(
new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 0)),
);
expect(fromRfc3339("2002-10-02T11:12:13.1234567Z")).toEqual(
new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 123)),
);
expect(fromRfc3339("2002-10-02T11:12:13.9999999Z")).toEqual(
new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 999)),
);
// 8 digits
expect(fromRfc3339("2002-10-02T11:12:13.00000000Z")).toEqual(
new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 0)),
);
expect(fromRfc3339("2002-10-02T11:12:13.12345678Z")).toEqual(
new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 123)),
);
expect(fromRfc3339("2002-10-02T11:12:13.99999999Z")).toEqual(
new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 999)),
);
// 9 digits
expect(fromRfc3339("2002-10-02T11:12:13.000000000Z")).toEqual(
new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 0)),
);
expect(fromRfc3339("2002-10-02T11:12:13.123456789Z")).toEqual(
new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 123)),
);
expect(fromRfc3339("2002-10-02T11:12:13.999999999Z")).toEqual(
new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 999)),
);
});
it("accepts space separators", () => {
// https://tools.ietf.org/html/rfc3339#section-5.6
// Applications using this syntax may choose, for the sake of readability,
// to specify a full-date and full-time separated by (say) a space character.
expect(fromRfc3339("2002-10-02 11:12:13Z")).toEqual(new Date(Date.UTC(2002, 9, 2, 11, 12, 13)));
});
it("throws for invalid format", () => {
// extra whitespace
expect(() => fromRfc3339(" 2002-10-02T11:12:13Z")).toThrow();
expect(() => fromRfc3339("2002-10-02T11:12:13Z ")).toThrow();
expect(() => fromRfc3339("2002-10-02T11:12:13 Z")).toThrow();
// wrong date separators
expect(() => fromRfc3339("2002:10-02T11:12:13Z")).toThrow();
expect(() => fromRfc3339("2002-10:02T11:12:13Z")).toThrow();
// wrong time separators
expect(() => fromRfc3339("2002-10-02T11-12:13Z")).toThrow();
expect(() => fromRfc3339("2002-10-02T11:12-13Z")).toThrow();
// wrong separator
expect(() => fromRfc3339("2002-10-02TT11:12:13Z")).toThrow();
expect(() => fromRfc3339("2002-10-02 T11:12:13Z")).toThrow();
expect(() => fromRfc3339("2002-10-02T 11:12:13Z")).toThrow();
expect(() => fromRfc3339("2002-10-02t11:12:13Z")).toThrow();
expect(() => fromRfc3339("2002-10-02x11:12:13Z")).toThrow();
expect(() => fromRfc3339("2002-10-02311:12:13Z")).toThrow();
expect(() => fromRfc3339("2002-10-02.11:12:13Z")).toThrow();
// wrong time zone
expect(() => fromRfc3339("2002-10-02T11:12:13")).toThrow();
expect(() => fromRfc3339("2002-10-02T11:12:13z")).toThrow();
expect(() => fromRfc3339("2002-10-02T11:12:13 00:00")).toThrow();
expect(() => fromRfc3339("2002-10-02T11:12:13+0000")).toThrow();
// wrong fractional seconds
expect(() => fromRfc3339("2018-07-30T19:21:12345Z")).toThrow();
expect(() => fromRfc3339("2018-07-30T19:21:12.Z")).toThrow();
});
it("encodes dates", () => {
expect(toRfc3339(new Date(Date.UTC(0, 0, 1, 0, 0, 0)))).toEqual("1900-01-01T00:00:00.000Z");
expect(toRfc3339(new Date(Date.UTC(2002, 9, 2, 11, 12, 13, 456)))).toEqual("2002-10-02T11:12:13.456Z");
});
});

View File

@ -0,0 +1,58 @@
import { ReadonlyDate } from "readonly-date";
const rfc3339Matcher = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})(\.\d{1,9})?((?:[+-]\d{2}:\d{2})|Z)$/;
function padded(integer: number, length = 2): string {
const filled = "00000" + integer.toString();
return filled.substring(filled.length - length);
}
export function fromRfc3339(str: string): ReadonlyDate {
const matches = rfc3339Matcher.exec(str);
if (!matches) {
throw new Error("Date string is not in RFC3339 format");
}
const year = +matches[1];
const month = +matches[2];
const day = +matches[3];
const hour = +matches[4];
const minute = +matches[5];
const second = +matches[6];
// fractional seconds match either undefined or a string like ".1", ".123456789"
const milliSeconds = matches[7] ? Math.floor(+matches[7] * 1000) : 0;
let tzOffsetSign: number;
let tzOffsetHours: number;
let tzOffsetMinutes: number;
// if timezone is undefined, it must be Z or nothing (otherwise the group would have captured).
if (matches[8] === "Z") {
tzOffsetSign = 1;
tzOffsetHours = 0;
tzOffsetMinutes = 0;
} else {
tzOffsetSign = matches[8].substring(0, 1) === "-" ? -1 : 1;
tzOffsetHours = +matches[8].substring(1, 3);
tzOffsetMinutes = +matches[8].substring(4, 6);
}
const tzOffset = tzOffsetSign * (tzOffsetHours * 60 + tzOffsetMinutes) * 60; // seconds
return new ReadonlyDate(
ReadonlyDate.UTC(year, month - 1, day, hour, minute, second, milliSeconds) - tzOffset * 1000,
);
}
export function toRfc3339(date: Date | ReadonlyDate): string {
const year = date.getUTCFullYear();
const month = padded(date.getUTCMonth() + 1);
const day = padded(date.getUTCDate());
const hour = padded(date.getUTCHours());
const minute = padded(date.getUTCMinutes());
const second = padded(date.getUTCSeconds());
const ms = padded(date.getUTCMilliseconds(), 3);
return `${year}-${month}-${day}T${hour}:${minute}:${second}.${ms}Z`;
}

View File

@ -0,0 +1,62 @@
import { fromUtf8, toUtf8 } from "./utf8";
describe("utf8", () => {
it("encodes ascii strings", () => {
expect(toUtf8("")).toEqual(new Uint8Array([]));
expect(toUtf8("abc")).toEqual(new Uint8Array([0x61, 0x62, 0x63]));
expect(toUtf8(" ?=-n|~+-*/\\")).toEqual(
new Uint8Array([0x20, 0x3f, 0x3d, 0x2d, 0x6e, 0x7c, 0x7e, 0x2b, 0x2d, 0x2a, 0x2f, 0x5c]),
);
});
it("decodes ascii string", () => {
expect(fromUtf8(new Uint8Array([]))).toEqual("");
expect(fromUtf8(new Uint8Array([0x61, 0x62, 0x63]))).toEqual("abc");
expect(
fromUtf8(new Uint8Array([0x20, 0x3f, 0x3d, 0x2d, 0x6e, 0x7c, 0x7e, 0x2b, 0x2d, 0x2a, 0x2f, 0x5c])),
).toEqual(" ?=-n|~+-*/\\");
});
it("encodes null character", () => {
expect(toUtf8("\u0000")).toEqual(new Uint8Array([0x00]));
});
it("decodes null byte", () => {
expect(fromUtf8(new Uint8Array([0x00]))).toEqual("\u0000");
});
it("encodes Basic Multilingual Plane strings", () => {
expect(toUtf8("ö")).toEqual(new Uint8Array([0xc3, 0xb6]));
expect(toUtf8("¥")).toEqual(new Uint8Array([0xc2, 0xa5]));
expect(toUtf8("Ф")).toEqual(new Uint8Array([0xd0, 0xa4]));
expect(toUtf8("ⱴ")).toEqual(new Uint8Array([0xe2, 0xb1, 0xb4]));
expect(toUtf8("ⵘ")).toEqual(new Uint8Array([0xe2, 0xb5, 0x98]));
});
it("decodes Basic Multilingual Plane strings", () => {
expect(fromUtf8(new Uint8Array([0xc3, 0xb6]))).toEqual("ö");
expect(fromUtf8(new Uint8Array([0xc2, 0xa5]))).toEqual("¥");
expect(fromUtf8(new Uint8Array([0xd0, 0xa4]))).toEqual("Ф");
expect(fromUtf8(new Uint8Array([0xe2, 0xb1, 0xb4]))).toEqual("ⱴ");
expect(fromUtf8(new Uint8Array([0xe2, 0xb5, 0x98]))).toEqual("ⵘ");
});
it("encodes Supplementary Multilingual Plane strings", () => {
// U+1F0A1
expect(toUtf8("🂡")).toEqual(new Uint8Array([0xf0, 0x9f, 0x82, 0xa1]));
// U+1034A
expect(toUtf8("𐍊")).toEqual(new Uint8Array([0xf0, 0x90, 0x8d, 0x8a]));
});
it("decodes Supplementary Multilingual Plane strings", () => {
// U+1F0A1
expect(fromUtf8(new Uint8Array([0xf0, 0x9f, 0x82, 0xa1]))).toEqual("🂡");
// U+1034A
expect(fromUtf8(new Uint8Array([0xf0, 0x90, 0x8d, 0x8a]))).toEqual("𐍊");
});
it("throws on invalid utf8 bytes", () => {
// Broken UTF8 example from https://github.com/nodejs/node/issues/16894
expect(() => fromUtf8(new Uint8Array([0xf0, 0x80, 0x80]))).toThrow();
});
});

View File

@ -0,0 +1,36 @@
// Global symbols in some environments
// https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder
// https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder
declare const TextEncoder: any | undefined;
declare const TextDecoder: any | undefined;
function isValidUtf8(data: Uint8Array): boolean {
const toStringAndBack = Buffer.from(Buffer.from(data).toString("utf8"), "utf8");
return Buffer.compare(Buffer.from(data), toStringAndBack) === 0;
}
export function toUtf8(str: string): Uint8Array {
// Browser and future nodejs (https://github.com/nodejs/node/issues/20365)
if (typeof TextEncoder !== "undefined") {
return new TextEncoder().encode(str);
}
// Use Buffer hack instead of nodejs util.TextEncoder to ensure
// webpack does not bundle the util module for browsers.
return new Uint8Array(Buffer.from(str, "utf8"));
}
export function fromUtf8(data: Uint8Array): string {
// Browser and future nodejs (https://github.com/nodejs/node/issues/20365)
if (typeof TextDecoder !== "undefined") {
return new TextDecoder("utf-8", { fatal: true }).decode(data);
}
// Use Buffer hack instead of nodejs util.TextDecoder to ensure
// webpack does not bundle the util module for browsers.
// Buffer.toString has no fatal option
if (!isValidUtf8(data)) {
throw new Error("Invalid UTF8 data");
}
return Buffer.from(data).toString("utf8");
}

Some files were not shown because too many files have changed in this diff Show More