Skip to content

Commit

Permalink
feat: switch to pure esm, simplify types, simplify crypto, update vms…
Browse files Browse the repository at this point in the history
… and compiler

BREAKING CHANGE: requires esm, modifies some crypto interfaces, renames many exports for
consistency, expands the program state available to vms and compilers

fixes #31, re #53, fixes #72
  • Loading branch information
bitjson committed May 14, 2022
1 parent 411ff61 commit c80044f
Show file tree
Hide file tree
Showing 259 changed files with 34,512 additions and 25,270 deletions.
1 change: 1 addition & 0 deletions .ava.bench.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export default {
files: ['build/main/**/*.bench.js'],
workerThreads: false,
verbose: true,
};
30 changes: 28 additions & 2 deletions .cspell.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
{
"version": "0.1",
"version": "0.2",
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/master/cspell.schema.json",
"language": "en",
"words": [
"ACTIVEBYTECODE",
"ANYONECANPAY",
"asmcrypto",
"auditability",
"auditable",
"bchn",
"bchreg",
"bchtest",
"bcoin",
Expand All @@ -19,11 +21,13 @@
"bitcore",
"bitfield",
"bitflags",
"bitjson",
"BOOLAND",
"BOOLOR",
"bytecode",
"camelcase",
"cashaddr",
"CASHTOKENS",
"CHECKDATASIG",
"CHECKDATASIGVERIFY",
"CHECKLOCKTIMEVERIFY",
Expand All @@ -41,10 +45,12 @@
"combinators",
"convertbits",
"cyclomatic",
"Datacarrier",
"deno",
"deserialization",
"deserialize",
"devtools",
"Dreyzehner",
"DYNAMICTOP",
"ecdsa",
"elliptic's",
Expand All @@ -55,7 +61,12 @@
"FROMALTSTACK",
"GREATERTHAN",
"GREATERTHANOREQUAL",
"HMAC",
"IFDUP",
"INPUTBYTECODE",
"INPUTINDEX",
"INPUTSEQUENCENUMBER",
"Ints",
"INVALIDOPCODE",
"LESSTHAN",
"LESSTHANOREQUAL",
Expand All @@ -71,16 +82,22 @@
"malleation",
"malloc",
"minification",
"MINIMALBOOL",
"MINIMALIF",
"monospace",
"multisig",
"nops",
"NOTIF",
"nullary",
"NULLDUMMY",
"NULLFAIL",
"NUMEQUAL",
"NUMEQUALVERIFY",
"NUMNOTEQUAL",
"OUTPOINTINDEX",
"OUTPOINTTXHASH",
"OUTPUTBYTECODE",
"OUTPUTVALUE",
"parsimmon",
"performant",
"plusplus",
Expand All @@ -98,12 +115,14 @@
"reversebytes",
"ripemd",
"RSHIFT",
"rustup",
"satoshi",
"satoshis",
"schnorr",
"seckey",
"secp",
"secp256k1",
"sigchecks",
"sighash",
"skippable",
"SMALLINTEGER",
Expand All @@ -118,12 +137,18 @@
"tpub",
"tsdoc",
"txid",
"TXINPUTCOUNT",
"TXLOCKTIME",
"TXOUTPUTCOUNT",
"TXVERSION",
"typeof",
"Uint",
"uncompress",
"unintuitive",
"untrusted",
"utxo",
"UTXOBYTECODE",
"UTXOVALUE",
"VERIF",
"VERNOTIF",
"wasm",
Expand All @@ -141,5 +166,6 @@
"tsconfig.json",
"node_modules/**",
"src/**/*.base64.ts"
]
],
"ignoreRegExpList": ["Base64", "HexValues"]
}
11 changes: 9 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
},
{ "selector": "typeLike", "format": ["PascalCase"] },
{ "selector": "enumMember", "format": ["camelCase", "UPPER_CASE"] } // Allow UPPER_CASE for opcodes
]
],
"import/no-internal-modules": ["error"],
"import/extensions": ["error", "always"]
},
"overrides": [
/*
Expand All @@ -44,7 +46,12 @@
}
]
}
]
],
"functional/no-expression-statement": "off",
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/no-magic-numbers": "off",
"functional/immutable-data": "off",
"functional/no-return-void": "off"
}
}
]
Expand Down
28 changes: 3 additions & 25 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Below you'll find the conventions we're trying to follow. Of course, please feel

## Design Goals

This library should provide the primitives needed to hack on Bitcoin and Bitcoin-related ideas.
This library should provide the primitives needed to hack on bitcoin and bitcoin-related ideas.

1. **flexible** - Consumers should be able to import only the functionality they need
2. **simple** - Functions should be simple and return one type
Expand All @@ -19,36 +19,14 @@ This library should provide the primitives needed to hack on Bitcoin and Bitcoin
- **trust the caller** - Runtime type-checking is a code smell. If the function accepts a string, assume it's been given a string. TypeScript definitions should expose improperly called functions to the developer at compile time, don't re-implement it at runtime. (Where TypeScript's lack of dependent types prevents us from checking the validity of an input at compile time, resist the urge to check it at runtime. Trust that the caller can test their code themselves.)
- **simple > ergonomic** - Clever, javascript-y interfaces are fun until they're limiting. We export simple primitives; other projects can wrap this library to provide friendlier interfaces.
- **clarity > performance** - Performance is a secondary goal. If our consumers need to squeeze out performance from a single machine, they should switch to something lower-level. The best way to speed up a consumer of this library is to parallelize it across more hardware.
- **don't overvalue historical names** - Many Bitcoin implementations make imprecise (and even misleading) naming choices for historical reasons. We make little effort to match the type/function names of other Bitcoin implementations; names should be chosen to improve clarity.
- **don't overvalue historical names** - Many bitcoin implementations make imprecise (and even misleading) naming choices for historical reasons. We make little effort to match the type/function names of other bitcoin implementations; names should be chosen to improve clarity.
- **don't add package dependencies** - This library should be as simple and stable as possible. Generally, if something is hard enough to warrant bringing in a dependency, it's something this library should provide. (Can you compile and expose a WASM version?)

## Some Practical Details

- **accept `readonly`, return mutable** - We should always return mutable types to allow consumers the option of mutating results without running afoul of type-checking. For the same reason, when we accept a value, we should always accept it as `readonly` for maximum flexibility.
- **use `eslint-disable-next-line` or `eslint-disable-line`** - It's ok to disable eslint; in some cases, rules should be disabled every time they're hit (e.g. `no-bitwise`). By using single-line disables, we clearly mark intentional deviations from our conventions.
- **avoid Hungarian notation & name prefixing** – Including the type of a variable in its name is a code smell: a name should clearly describe only one concept, and types are the business of the type system. Likewise, using prefixes to distinguish between an interface and an instance typically indicates the concepts should be simplified. E.g. `IChecker` and `Checker` – this is likely made unnecessarily complex to accommodate an object-oriented style. Consider replacing with a single function (or if instantiation is required, an object containing only stateless functions).
- **don't throw things** – instead, return a result which can be either successful or an error. This strategy encourages a more functional approach to problems, and pragmatically, [TypeScript does not yet offer a `throws` clause or otherwise](https://github.com/microsoft/TypeScript/issues/13219), so only this strategy allows errors to be well-typed. A good pattern is `() => string | ResultType`, where ResultType is the desired output, and error messages are returned as a string. Consumers can easily use `typeof result === 'string'` to narrow the resulting type. When errors are more complex or `ResultType` is also a string, use an object with a `success` property, e.g. `() => { success: true, bytecode: Uint8Array } | { success: false, errors: ErrorType[] }`.
- **don't throw things** – instead, return a result that can be either a success or error type. This strategy encourages a more functional approach to problems, and pragmatically, [TypeScript does not yet offer a `throws` clause or otherwise](https://github.com/microsoft/TypeScript/issues/13219), so only this strategy allows errors to be well-typed. A good pattern is `() => string | ResultType`, where ResultType is the desired output, and error messages are returned as a string. Consumers can easily use `typeof result === 'string'` to narrow the resulting type. When errors are more complex or `ResultType` is also a string, use an object with a `success` property, e.g. `() => { success: true, bytecode: Uint8Array } | { success: false, errors: ErrorType[] }`.
- **test the import** – when importing modules within the library, aim to import from a sibling or a sibling of the closest mutual parent module (this helps to avoid import cycles), rather than importing from a higher-level export (like `lib.ts`). When importing modules within test files, always import directly from the top-level `lib.ts` file – this ensures that intended public functionality is available and working as expected. (Note: this is also enforced by our eslint configuration.)
- **try the formatting utilities** – especially when writing tests for large, complex objects, the `stringify` and `stringifyTestVector` utilities can save you a lot of time.

## Areas for Improvement

### Thinner WASM Implementations/Imports

One area where we could improve in terms of the [`flexibility` Design Goal](../README.md#Design-Goals) (`Consumers should be able to import only the functionality they need`) is with WASM implementations.

While WASM can't currently be tree-shaken (in [the "live code inclusion" sense](https://medium.com/@Rich_Harris/tree-shaking-versus-dead-code-elimination-d3765df85c80)), we might be able to provide thinned-down versions of different WASM modules for use-cases which don't require the full module.

Our method for instantiating and wrapping the WASM module also prevents tree-shaking of unused wrapper code. (And breaks slightly from a purely-functional programming style.)

It may be better to instead provide each wrapper method as an individually exported function (which accepts a WASM object of the proper shape, as well as the parameters it currently accepts). E.g. rather than creating an object full of methods like:

```
wasm.method(param, param)
```

We would use pure-looking methods which accept the WASM object (fundamentally, you can't get much purer when using WASM):

```
methodWasm(wasm, param, param)
```
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ coverage
*.log
report.*.json
scratch
gitignore.*

src/lib/bin/**/*.html
src/lib/bin/**/*.js
Expand Down
12 changes: 4 additions & 8 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,20 @@
"request": "launch",
"name": "Debug Active Spec",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/ava",
"runtimeArgs": ["debug", "--break", "--serial", "${file}"],
"port": 9229,
"runtimeArgs": ["--serial", "${file}"],
"outputCapture": "std",
"skipFiles": ["<node_internals>/**/*.js"],
"skipFiles": ["<node_internals>/**", "**/node_modules/**"],
"preLaunchTask": "npm: build"
// "smartStep": true
},
{
// Use this one if you're already running `yarn watch`
"type": "node",
"request": "launch",
"name": "Debug Active Spec (no build)",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/ava",
"runtimeArgs": ["debug", "--break", "--serial", "${file}"],
"port": 9229,
"runtimeArgs": ["--serial", "${file}"],
"outputCapture": "std",
"skipFiles": ["<node_internals>/**/*.js"]
// "smartStep": true
"skipFiles": ["<node_internals>/**", "**/node_modules/**"]
}
]
}
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
"editor.semanticHighlighting.enabled": true,
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"cSpell.enabled": true
"cSpell.enabled": true,
"typescript.preferences.importModuleSpecifierEnding": "js"
}
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2018 Jason Dreyzehner
Copyright (c) Jason Dreyzehner

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
35 changes: 20 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</p>

<p align="center">
An ultra-lightweight JavaScript library for Bitcoin, Bitcoin Cash, and Bitauth
An ultra-lightweight JavaScript library for Bitcoin Cash, Bitcoin, and Bitauth
applications.
<br />
<br />
Expand Down Expand Up @@ -49,7 +49,7 @@ Libauth has **no dependencies** and works in all JavaScript environments, includ
Libauth is designed to be **flexible**, **lightweight**, and **easily auditable**. Rather than providing a single, overarching, object-oriented API, all functionality is composed from simple functions. This has several benefits:

- **Flexibility** – Even highly-complex functionality is built-up from simpler functions. These lower-level functions can be used to experiment, tweak, and remix your own higher-level methods without maintaining a fork of the library.
- **Smaller application bundles** – Applications can import only the methods they need, eliminating the unused code (via [dead-code elimination](https://webpack.js.org/guides/tree-shaking/)).
- **Smaller application bundles** – Applications can import only the methods they need, eliminating the unused code (via [dead-code elimination](https://rollupjs.org/guide/en/#tree-shaking)).
- **Better auditability** – Beyond having no dependencies of its own, Libauth's [functional programming](https://en.wikipedia.org/wiki/Functional_programming) approach makes auditing critical code easier: smaller bundles, smaller functions, and less churn between versions (fewer cascading changes to object-oriented interfaces).
- **Fully-portable** – No platform-specific APIs are ever used, so the same code paths are used across all JavaScript environments (reducing the auditable "surface area" and simplifying library development).

Expand All @@ -66,31 +66,36 @@ yarn add @bitauth/libauth
And import the functionality you need:

```typescript
import { instantiateSecp256k1 } from '@bitauth/libauth';
import { secp256k1 } from '@bitauth/libauth';
import { msgHash, pubkey, sig } from './somewhere';

(async () => {
const secp256k1 = await instantiateSecp256k1();
secp256k1.verifySignatureDERLowS(sig, pubkey, msgHash)
? console.log('🚀 Signature valid')
: console.log('❌ Signature invalid');
})();
secp256k1.verifySignatureDERLowS(sig, pubkey, msgHash)
? console.log('🚀 Signature valid')
: console.log('❌ Signature invalid');
```

### Typescript Types
Note, Libauth is a [pure ESM package](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c), so Node.js v12 or higher is required (or Deno), and [using ESM is recommended](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-move-my-commonjs-project-to-esm).

### Web Usage

For web projects, a bundler with [dead-code elimination](https://rollupjs.org/guide/en/#tree-shaking) (A.K.A. "tree shaking") is **strongly recommended** – Libauth is designed to minimize application code size, and dead-code elimination will improve load performance in nearly all applications.

**Note**: `@bitauth/libauth` uses [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt), [`WebAssembly`](https://developer.mozilla.org/en-US/docs/WebAssembly), and `es2017` features for some functionality. While support is required to use this functionality (Node.js v10 LTS or later), other parts of the library will continue to work in older environments. To include the necessary TypeScript library files in you application, add `"lib": ["es2017", "esnext.bigint", "dom"]` to your `tsconfig.json`.
Consider [Parcel](https://parceljs.org/), [Rollup](https://rollupjs.org/), [Webpack](https://webpack.js.org/), or a bundler designed for your web framework.

### Using with Deno
### Deno Usage

Deno is a great runtime for quickly working with Libauth. You can import from the latest module build:
Deno is a great runtime for working with Libauth. You can import the library from `unpkg.com`:

```ts
import { hexToBin } from 'https://unpkg.com/@bitauth/libauth/build/module/index.js';
import { hexToBin } from 'https://unpkg.com/@bitauth/libauth/build/index.js';

console.log(hexToBin('beef'));
```

### Typescript Types

Libauth uses [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt), [`WebAssembly`](https://developer.mozilla.org/en-US/docs/WebAssembly), and `es2017` features for some functionality. To type-check this library in you application (without [`skipLibCheck`](https://www.typescriptlang.org/tsconfig#skipLibCheck)), your `tsconfig.json` will need a minimum `target` of `es2020` or `lib` must include `es2017` and `esnext.bigint`. If your application is not already importing types for `WebAssembly`, you may also need to add `dom` to `lib`.

## Stable API

The following APIs are considered stable, and will only include breaking changes in major version upgrades.
Expand Down Expand Up @@ -124,7 +129,7 @@ Libauth also exports new, potentially unstable APIs. As these APIs stabilize, th

Pull Requests welcome! Please see [`CONTRIBUTING.md`](.github/CONTRIBUTING.md) for details.

This library requires [Yarn](https://yarnpkg.com/) for development. If you don't have Yarn, make sure you have `Node.js` installed (which ships with `npm`), then run `npm install -g yarn`. Once Yarn is installed:
This library requires [Yarn](https://yarnpkg.com/) for development. If you don't have Yarn, make sure you have `Node.js` installed (ships with `npm`), then run `npm install -g yarn`. Once Yarn is installed:

```sh
# use --recursive to clone the secp256k1 submodule
Expand Down
4 changes: 2 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ Thank you for your interest in Libauth's security!

## Supported Versions

Only the latest major version (currently `1.*`) is regularly supported.
Only the latest major version is regularly supported.

Critical security issues may be backported to previous versions, but we recommend all users migrate to new major versions within 6 months of release.
Critical security updates may be backported to previous versions, but we recommend all users migrate to new major versions within 6 months of release.

## Reporting a Vulnerability

Expand Down
9 changes: 0 additions & 9 deletions config/tsconfig.module.json

This file was deleted.

Loading

0 comments on commit c80044f

Please sign in to comment.