Skip to content

Commit 4709c17

Browse files
committed
Expand BIP39 support, docs, and tests
1 parent 8e032c2 commit 4709c17

24 files changed

Lines changed: 3466 additions & 195 deletions

.cspell.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"endianness",
7777
"EQUALVERIFY",
7878
"esnext",
79+
"exfiltration",
7980
"FORKID",
8081
"FROMALTSTACK",
8182
"gitter",
@@ -90,6 +91,7 @@
9091
"Ints",
9192
"INVALIDOPCODE",
9293
"ipfs",
94+
"Jaro",
9395
"LESSTHAN",
9496
"LESSTHANOREQUAL",
9597
"libauth",

.github/CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ This library should provide the primitives needed to hack on bitcoin and bitcoin
2727
- **accept immutable, 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 generally avoid mutating it.
2828
- **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.
2929
- **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).
30-
- **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[] }`.
30+
- **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. `() => string | { success: true, phrase: string }` or `() => { success: true, bytecode: Uint8Array } | { success: false, errors: ErrorType[] }`.
3131
- Exception: errors that can never happen during correct usage of a function may be either 1) detected by type checking or 2) thrown. For example, if a function always expects 32-byte Uint8Array inputs (like `encodeCashAddress`), an incorrectly sized Uint8Array implies incorrect usage of the function by the implementing application. Such implementation errors should ideally be detected at development time and never occur at runtime.
3232
- **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.)
3333
- **try the formatting utilities** – especially when writing tests for large, complex objects, the `stringify` and `stringifyTestVector` utilities can save you a lot of time.

.github/workflows/ci.yaml

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ jobs:
55
build-and-test:
66
runs-on: ubuntu-latest
77
strategy:
8+
fail-fast: false
89
matrix:
9-
node: [18, latest]
10-
name: Test Node ${{ matrix.node }}
10+
node: [18, 20, latest]
11+
runner_index: [0, 1, 2, 3]
12+
runners_per_version: [4]
13+
name: Test Node ${{ matrix.node }} (${{ matrix.runner_index }}/${{ matrix.runners_per_version }})
1114
steps:
1215
- uses: actions/checkout@v4
1316
with:
@@ -17,24 +20,11 @@ jobs:
1720
with:
1821
node-version: ${{ matrix.node }}
1922
- run: yarn install --immutable --immutable-cache
20-
- run: yarn test
21-
22-
build-and-test-with-coverage:
23-
runs-on: ubuntu-latest
24-
strategy:
25-
matrix:
26-
node: [20]
27-
name: Test Node ${{ matrix.node }}
28-
steps:
29-
- uses: actions/checkout@v4
30-
with:
31-
submodules: 'recursive'
32-
- name: Install Node.js
33-
uses: actions/setup-node@v4
34-
with:
35-
node-version: ${{ matrix.node }}
36-
- run: yarn install --immutable --immutable-cache
37-
- run: yarn test
23+
- run: yarn build
24+
- run: yarn test:unit
25+
env:
26+
CI_NODE_INDEX: ${{ matrix.runner_index }}
27+
CI_NODE_TOTAL: ${{ matrix.runners_per_version }}
3828
- run: yarn cov:lcov
3929
- name: Upload test coverage
4030
uses: codecov/codecov-action@v4
@@ -43,8 +33,9 @@ jobs:
4333
token: ${{ secrets.CODECOV_TOKEN }}
4434
verbose: true
4535

46-
check-yarn-cache:
36+
check-policies:
4737
runs-on: ubuntu-latest
38+
name: Check Policies
4839
steps:
4940
- name: Check out repository code
5041
uses: actions/checkout@v4
@@ -55,3 +46,5 @@ jobs:
5546
with:
5647
node-version: '20'
5748
- run: yarn install --immutable --immutable-cache --check-cache
49+
- run: yarn build
50+
- run: yarn test:policies

.pnp.cjs

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/encodings-and-formats.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ These functions include:
5555
- `encodeBase58AddressFormat`
5656
- `encodeBech32`
5757
- `encodeBip39Mnemonic`
58+
- `encodeBip39MnemonicNonStandard`
5859
- `encodeCashAddress`
5960
- `encodeCashAddressFormat`
6061
- `encodeCashAddressNonStandard`
@@ -95,6 +96,7 @@ The `decode*` utility functions include:
9596
- `decodeBase58AddressFormat`
9697
- `decodeBech32`
9798
- `decodeBip39Mnemonic`
99+
- `decodeBip39MnemonicNonStandard`
98100
- `decodeBitcoinSignature`
99101
- `decodeCashAddress`
100102
- `decodeCashAddressFormat`

package.json

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@
5151
"gen:schema-TODO": "//TODO: use ajv compile --code-esm option after merge: https://github.com/ajv-validator/ajv-cli/pull/200",
5252
"gen:templates": "yarn build && node 'build/lib/transaction/fixtures/generate-templates.spec.helper.js' 'p2pkh' > src/lib/transaction/fixtures/templates/p2pkh.json && node 'build/lib/transaction/fixtures/generate-templates.spec.helper.js' '2-of-3' > src/lib/transaction/fixtures/templates/2-of-3.json && node 'build/lib/transaction/fixtures/generate-templates.spec.helper.js' '2-of-2-recoverable' > src/lib/transaction/fixtures/templates/2-of-2-recoverable.json && node 'build/lib/transaction/fixtures/generate-templates.spec.helper.js' 'sig-of-sig' > src/lib/transaction/fixtures/templates/sig-of-sig.json && node 'build/lib/transaction/fixtures/generate-templates.spec.helper.js' 'cash-channels-v1' > src/lib/transaction/fixtures/templates/cash-channels-v1.json && prettier 'src/lib/transaction/fixtures/templates/*.json' --write",
5353
"gen:graph": "mkdir -p temp && madge --image temp/deps-$(date +\"%FT%H%M\").svg build/index.js",
54-
"test": "yarn build && yarn test:deps && yarn test:schemas && yarn test:lint && yarn test:cycles && yarn test:unit",
54+
"test": "yarn build && yarn test:policies && yarn test:unit",
55+
"test:policies": "yarn test:deps && yarn test:schemas && yarn test:lint && yarn test:cycles",
5556
"test:deps": "node -e \"import('./package.json', { assert: { type: 'json' } }).then(p => typeof p.dependencies === 'undefined' ? console.log('No dependencies found.') : (console.error('Dependencies are not allowed.') && process.exit(1)));\"",
5657
"test:schemas:unchanged": "yarn gen:schema && node -e \"child_process.exec('git status src/lib/schema --porcelain | head -c1 | wc -c', (err, stdout) => stdout.trim() === '0' ? process.exit(0) : process.exit(1) )\"",
5758
"test:schemas": "yarn test:schemas:unchanged && echo \"Schemas are up to date.\" || echo \"Error: one or more schemas are outdated. Please review and commit the changes in src/lib/schema.\"",
@@ -61,6 +62,7 @@
6162
"test:spelling": "cspell \"{README.md,.github/*.md,src/**/*.ts}\"",
6263
"test:cycles": "madge --circular build/index.js",
6364
"test:unit": "c8 ava",
65+
"test:fast": "echo '\n\nUsage:\n yarn test:fast src/lib/key/bip39.spec.ts # run a single test file \n yarn test:fast --match=\"!*crypto*\" --match=\"!*vmb_tests*\" # include/exclude tests with names matching pattern(s) \n\n' && ava -v",
6466
"test:unit:script_tests": "c8 ava --match='*[script_tests]*' --serial",
6567
"test:unit:vmb_tests": "c8 ava src/lib/vmb-tests/bch-vmb-tests.spec.ts --serial",
6668
"test:unit:vmb_test": "node 'build/lib/vmb-tests/bch-vmb-test.spec.helper.js'",
@@ -69,7 +71,7 @@
6971
"bench:test": "ava --config .ava.bench.config.js --serial --timeout=2m 2>&1 | tee bench.log",
7072
"bench:browser-deps": "cpy '.yarn/artifacts/*.js' build/bench",
7173
"watch": "yarn build -w",
72-
"watch:test": "echo 'Usage: yarn watch:test --match=\"*pattern*\"' && ava -v --watch",
74+
"watch:test": "echo '\n\nUsage:\n yarn watch:test src/lib/key/bip39.spec.ts # watch a single test file \n yarn watch:test --match=\"!*crypto*\" --match=\"!*vmb_tests*\" # include/exclude tests with names matching pattern(s) \n\n' && ava -v --watch",
7375
"cov": "yarn build && yarn test:unit && yarn cov:html && yarn cov:lcov && open-cli coverage/index.html",
7476
"cov:html": "c8 report --reporter=html",
7577
"cov:lcov": "c8 report --reporter=lcov",
@@ -107,6 +109,7 @@
107109
"ajv-cli": "^5.0.0",
108110
"asmcrypto.js": "^2.3.2",
109111
"ava": "^6.0.1",
112+
"bip39": "^3.1.0",
110113
"bitcore-lib-cash": "^10.0.23",
111114
"c8": "^9.0.0",
112115
"chuhai": "^1.2.0",
@@ -147,17 +150,18 @@
147150
"SECURITY.md"
148151
],
149152
"ava": {
150-
"timeout": "20s",
153+
"timeout": "60s",
151154
"typescript": {
152155
"compile": false,
153156
"rewritePaths": {
154157
"src/": "build/"
155158
}
156159
},
157160
"nodeArguments": [
158-
"--experimental-json-modules"
159-
],
160-
"workerThreads": false
161+
"--experimental-json-modules",
162+
"--experimental-global-webcrypto",
163+
"# ^ needed for node v18"
164+
]
161165
},
162166
"config": {
163167
"commitizen": {
@@ -198,7 +202,8 @@
198202
"**/*.bench.js",
199203
"**/*.spec.js",
200204
"**/*.spec.helper.js",
201-
".pnp.*"
205+
".pnp.*",
206+
"**/ajv/validate-*.js"
202207
]
203208
},
204209
"sideEffects": false,

src/lib/address/cash-address.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,18 @@ export enum CashAddressCorrectionError {
685685
tooManyErrors = 'This address has more than 2 errors and cannot be corrected.',
686686
}
687687

688+
export type CashAddressCorrection = {
689+
/**
690+
* The corrected address in CashAddressFormat (including the prefix).
691+
*/
692+
address: string;
693+
/**
694+
* An array of up to two numbers (in ascending order) indicating the index of
695+
* each corrected character within the corrected address.
696+
*/
697+
corrections: [] | [number, number] | [number];
698+
};
699+
688700
/**
689701
* Attempt to correct up to 2 errors in a CashAddress. The CashAddress must be
690702
* properly formed (include a prefix and only contain Bech32 characters).
@@ -730,7 +742,7 @@ export const attemptCashAddressFormatErrorCorrection = (address: string) => {
730742
return {
731743
address: cashAddressPolynomialToCashAddress(polynomial),
732744
corrections: [],
733-
};
745+
} as CashAddressCorrection;
734746
}
735747

736748
const syndromes: { [index: string]: number } = {};
@@ -752,7 +764,7 @@ export const attemptCashAddressFormatErrorCorrection = (address: string) => {
752764
return {
753765
address: cashAddressPolynomialToCashAddress(polynomial),
754766
corrections: [term],
755-
};
767+
} as CashAddressCorrection;
756768
}
757769
// eslint-disable-next-line no-bitwise
758770
const s0 = (BigInt(correct) ^ BigInt(originalChecksum)).toString();
@@ -778,7 +790,7 @@ export const attemptCashAddressFormatErrorCorrection = (address: string) => {
778790
return {
779791
address: cashAddressPolynomialToCashAddress(polynomial),
780792
corrections: [correctionIndex1, correctionIndex2].sort((a, b) => a - b),
781-
};
793+
} as CashAddressCorrection;
782794
}
783795
}
784796

src/lib/bin/hashes.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,9 @@ export const instantiateRustWasm = async (
2727
* Since `__wbindgen_malloc` isn't exposed to consumers, this error
2828
* can only be encountered if the code below is broken.
2929
*/
30+
/* c8 ignore next 10 */
3031
// eslint-disable-next-line camelcase, @typescript-eslint/naming-convention
31-
__wbindgen_throw: /* istanbul ignore next */ (
32-
ptr: number,
33-
len: number,
34-
) => {
32+
__wbindgen_throw: (ptr: number, len: number) => {
3533
// eslint-disable-next-line functional/no-throw-statements
3634
throw new Error(
3735
// eslint-disable-next-line @typescript-eslint/no-use-before-define

src/lib/bin/secp256k1/secp256k1-wasm.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,8 @@ const isLittleEndian = (buffer: ArrayBuffer): boolean => {
232232
heap32[0] = 1668509029;
233233
heap16[1] = 25459;
234234
return heapU8[2] !== 115 || heapU8[3] !== 99
235-
? /* istanbul ignore next */ notLittleEndian
235+
? /* c8 ignore next */
236+
notLittleEndian
236237
: littleEndian;
237238
};
238239

@@ -266,7 +267,7 @@ export const instantiateSecp256k1WasmBytes = async (
266267
maximum: TOTAL_MEMORY / WASM_PAGE_SIZE,
267268
});
268269

269-
/* istanbul ignore if */
270+
/* c8 ignore next 9 */
270271
if (!isLittleEndian(wasmMemory.buffer)) {
271272
/*
272273
* note: this block is excluded from test coverage. It's A) hard to test
@@ -308,34 +309,32 @@ export const instantiateSecp256k1WasmBytes = async (
308309
const env = {
309310
DYNAMICTOP_PTR,
310311
STACKTOP,
311-
___setErrNo: /* istanbul ignore next */ (value: number) => {
312+
/* c8 ignore start */
313+
___setErrNo: (value: number) => {
312314
if (getErrNoLocation !== undefined) {
313315
heap32[getErrNoLocation() >> 2] = value;
314316
}
315317
return value;
316318
},
317-
_abort: /* istanbul ignore next */ (err = 'Secp256k1 Error') => {
319+
_abort: (err = 'Secp256k1 Error') => {
318320
throw new Error(err);
319321
},
320322
// eslint-disable-next-line camelcase
321-
_emscripten_memcpy_big: /* istanbul ignore next */ (
322-
dest: number,
323-
src: number,
324-
num: number,
325-
) => {
323+
_emscripten_memcpy_big: (dest: number, src: number, num: number) => {
326324
heapU8.set(heapU8.subarray(src, src + num), dest);
327325
return dest;
328326
},
329-
abort: /* istanbul ignore next */ (err = 'Secp256k1 Error') => {
327+
abort: (err = 'Secp256k1 Error') => {
330328
throw new Error(err);
331329
},
332-
abortOnCannotGrowMemory: /* istanbul ignore next */ () => {
330+
abortOnCannotGrowMemory: () => {
333331
throw new Error('Secp256k1 Error: abortOnCannotGrowMemory was called.');
334332
},
335-
enlargeMemory: /* istanbul ignore next */ () => {
333+
enlargeMemory: () => {
336334
throw new Error('Secp256k1 Error: enlargeMemory was called.');
337335
},
338336
getTotalMemory: () => TOTAL_MEMORY,
337+
/* c8 ignore stop */
339338
};
340339

341340
const info = {

0 commit comments

Comments
 (0)