Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ body:
id: version
attributes:
label: corebc version
placeholder: 1.0.0 or commit hash
placeholder: 1.1.0 or commit hash
validations:
required: true
- type: input
Expand Down
6 changes: 6 additions & 0 deletions .markdownlint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"MD010": {
"code_blocks": false
},
"MD013": true
}
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 1.1.0

- Add the German community mnemonic wordlist from dys2p/wordlists-de as `wordlists.de` and `LangDe` in the extra browser bundle.
- Add the Slovak mnemonic wordlist as `wordlists.sk` and `LangSk` in the extra browser wordlist bundle, preserving upstream word order.

## 1.0.0

- Support the updated Noble hashes, TypeScript 7, Node types, and Rollup dependencies.
Expand Down
70 changes: 53 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# CoreBC

TypeScript and JavaScript tools for Core Blockchain: Ed448 wallets, signing, Core addresses, `xcb_*` JSON-RPC providers, ABI encoding, smart contracts, and token metadata.
TypeScript and JavaScript tools for Core Blockchain: Ed448 wallets, signing,
Core addresses, `xcb_*` JSON-RPC providers, ABI encoding, smart contracts, and
token metadata.

## Install

Requires Node.js **22.12 or newer**, or a modern browser with BigInt and Web Crypto support.
Requires Node.js **22.12 or newer**, or a modern browser with BigInt and Web
Crypto support.

```sh
npm install corebc
Expand All @@ -19,7 +22,10 @@ console.log(await provider.getBlockNumber());
provider.destroy();
```

Use your own Core RPC endpoint. CommonJS is supported with `const { Wallet } = require("corebc")`. The package includes declarations and subpath exports such as `corebc/abi`, `corebc/crypto`, `corebc/providers`, `corebc/cip`, and `corebc/ipfs`.
Use your own Core RPC endpoint. CommonJS is supported with
`const { Wallet } = require("corebc")`. The package includes declarations and
subpath exports such as `corebc/abi`, `corebc/crypto`, `corebc/providers`,
`corebc/cip`, and `corebc/ipfs`.

## Wallets and contracts

Expand All @@ -31,21 +37,35 @@ console.log(wallet.address);
const signature = await wallet.signMessage("Hello Core");
```

Wallets support mnemonics, seed derivation, and encrypted JSON keystores. Wallet KDF limits match Core Web3Dart: PBKDF2 permits up to 10 million iterations; scrypt permits `N <= 1048576`, `r * p <= 1048576`, and at most 256 MiB of estimated memory. Wallets exceeding these limits are rejected. Private keys stay with the caller. Core uses 57-byte Ed448 private and public keys and network-prefixed addresses; Ethereum secp256k1 keys are not interchangeable. Keep private keys and recovery phrases out of logs and source control.
Wallets support mnemonics, seed derivation, and encrypted JSON keystores. Wallet
KDF limits match Core Web3Dart: PBKDF2 permits up to 10 million iterations;
scrypt permits `N <= 1048576`, `r * p <= 1048576`, and at most 256 MiB of
estimated memory. Wallets exceeding these limits are rejected. Private keys stay
with the caller. Core uses 57-byte Ed448 private and public keys and
network-prefixed addresses; Ethereum secp256k1 keys are not interchangeable.
Keep private keys and recovery phrases out of logs and source control.

`Contract` accepts JSON or human-readable ABIs and a provider for reads or signer for writes. Transaction fields use `energyLimit`, `energyPrice`, and `networkId`.
`Contract` accepts JSON or human-readable ABIs and a provider for reads or
signer for writes. Transaction fields use `energyLimit`, `energyPrice`, and
`networkId`.

For compatibility, the existing `sha256` and `sha512` exports compute **SHA3-256 and SHA3-512** respectively. `keccak256` is a separate algorithm. Do not substitute hashes when porting signing code.
For compatibility, the existing `sha256` and `sha512` exports compute **SHA3-256
and SHA3-512** respectively. `keccak256` is a separate algorithm. Do not
substitute hashes when porting signing code.

## CIP metadata

The typed helpers follow the same feature scope as Core Web3Dart:

| Standard | Support |
| ---------------------------------------------------------- | -------------------------------------------------------------------------------- |
| [CIP-150](https://cip.coreblockchain.net/cip/cbc/cip-150/) | Read, enumerate, set, and seal on-chain metadata |
| [CIP-151](https://cip.coreblockchain.net/cip/cbc/cip-151/) | Optional expiration and trading-stop timestamps; inclusive boundary checks |
| [CIP-152](https://cip.coreblockchain.net/cip/cbc/cip-152/) | Resolve `lab` references ending in `lab.json` and validate measurement structure |
- [CIP-150]: Read, enumerate, set, and seal on-chain metadata.
- [CIP-151]: Optional expiration and trading-stop timestamps; inclusive boundary
checks.
- [CIP-152]: Resolve `lab` references ending in `lab.json` and validate
measurement structure.

[CIP-150]: https://cip.coreblockchain.net/cip/cbc/cip-150/
[CIP-151]: https://cip.coreblockchain.net/cip/cbc/cip-151/
[CIP-152]: https://cip.coreblockchain.net/cip/cbc/cip-152/

```ts
import { Cip150MetadataContract, IpfsGateway } from "corebc";
Expand All @@ -59,11 +79,20 @@ const expired = lifecycle.isExpiredAt(BigInt(Math.floor(Date.now() / 1000)));
const lab = await metadata.readLabCertificate(new IpfsGateway(), block);
```

CIP-151 timestamps remain exact `bigint` Unix seconds. Missing lifecycle values impose no limit. These helpers expose metadata; enforcement of transfers or trading belongs to the contract or application. CIP-152 validation checks JSON structure, not issuer authenticity or gateway content integrity.
CIP-151 timestamps remain exact `bigint` Unix seconds. Missing lifecycle values
impose no limit. These helpers expose metadata; enforcement of transfers or
trading belongs to the contract or application. CIP-152 validation checks JSON
structure, not issuer authenticity or gateway content integrity.

## IPFS and custom units

`IpfsGateway` accepts `ipfs://CID/path`, `/ipfs/CID/path`, bare CID references, and HTTP(S) URLs. Its default template is `https://ipf.sk/{cid}`; configure another gateway with `new IpfsGateway({ template: "https://gateway.example/ipfs/{cid}" })`. JSON reads default to a 1 MiB limit and 30-second timeout. Treat metadata URLs as untrusted input; server applications should supply a `fetch` implementation that enforces their outbound network policy.
`IpfsGateway` accepts `ipfs://CID/path`, `/ipfs/CID/path`, bare CID references,
and HTTP(S) URLs. Its default template is `https://ipf.sk/{cid}`; configure
another gateway with
`new IpfsGateway({ template: "https://gateway.example/ipfs/{cid}" })`. JSON
reads default to a 1 MiB limit and 30-second timeout. Treat metadata URLs as
untrusted input; server applications should supply a `fetch` implementation that
enforces their outbound network policy.

```ts
import { CustomUnitToken } from "corebc/cip";
Expand All @@ -81,11 +110,15 @@ if (units) {
}
```

Custom-unit discovery follows the Tone/Core API convention: `supportsUnit`, `supportedUnits`, `preferredUnit`, and `balanceOfUnit`. Balances and multiplier numerator/denominator stay as `bigint`; a zero canonical balance has no defined multiplier. This convention is separate from the numbered CIPs above.
Custom-unit discovery follows the Tone/Core API convention: `supportsUnit`,
`supportedUnits`, `preferredUnit`, and `balanceOfUnit`. Balances and multiplier
numerator/denominator stay as `bigint`; a zero canonical balance has no defined
multiplier. This convention is separate from the numbered CIPs above.

## Browser bundles

ESM and UMD bundles, including minified versions, are in `dist/` in the npm package. Serve them from your application:
ESM and UMD bundles, including minified versions, are in `dist/` in the npm
package. Serve them from your application:

```html
<script type="module">
Expand All @@ -94,7 +127,8 @@ ESM and UMD bundles, including minified versions, are in `dist/` in the npm pack
</script>
```

The UMD bundle `dist/corebc.umd.min.js` exposes `globalThis.corebc`. Secure randomness requires a secure browser context.
The UMD bundle `dist/corebc.umd.min.js` exposes `globalThis.corebc`. Secure
randomness requires a secure browser context.

## Development

Expand All @@ -103,7 +137,9 @@ npm install
npm run check
```

See [CONTRIBUTING.md](CONTRIBUTING.md) for builds, tests, CIP contributions, and release setup. Report problems or request features through the [issue forms](https://github.com/CorePass/corebc.js/issues/new/choose).
See [CONTRIBUTING.md](CONTRIBUTING.md) for builds, tests, CIP contributions, and
release setup. Report problems or request features through the
[issue forms](https://github.com/CorePass/corebc.js/issues/new/choose).

## License

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "corebc",
"version": "1.0.0",
"version": "1.1.0",
"author": "CorePass",
"description": "A complete and compact CoreBC library, for Dapps, wallets and any other tools.",
"browser": {
Expand Down
21 changes: 21 additions & 0 deletions scripts/test-browser.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,25 @@ for (const file of ["dist/corebc.umd.js", "dist/corebc.umd.min.js"]) {
context,
);
}
for (const file of ["wordlists-extra.js", "wordlists-extra.min.js"]) {
const { LangSk, LangDe } = await import(
new URL("../dist/" + file, import.meta.url)
);
const de = LangDe.wordlist();
if (
de.locale !== "de" ||
de.getWord(0) !== "abbau" ||
de.getWordIndex("zyklus") !== 2047
) {
throw new Error("browser German wordlist mismatch");
}
const sk = LangSk.wordlist();
if (
sk.locale !== "sk" ||
sk.getWord(208) !== "chalupa" ||
sk.getWordIndex("zvuk") !== 2047
) {
throw new Error("browser Slovak wordlist mismatch");
}
}
console.log("Browser bundle checks passed");
125 changes: 125 additions & 0 deletions src.ts/_tests/test-wordlists.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { Mnemonic, wordlists } from "../index.js";
import { LangDe } from "../wordlists/lang-de.js";
import { LangSk } from "../wordlists/lang-sk.js";

// Independently calculated SHA-256 mnemonic vectors using upstream word indices.
const vectors = [
[
"00000000000000000000000000000000",
"abakus abakus abakus abakus abakus abakus abakus abakus abakus abakus abakus abrazia",
],
[
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"zvuk zvuk zvuk zvuk zvuk zvuk zvuk zvuk zvuk zvuk zvuk zvuk zvuk zvuk zvuk zvuk zvuk zvuk zvuk zvuk zvuk zvuk zvuk zaliv",
],
[
"000102030405060708090a0b0c0d0e0f10111213",
"abakus anjel kridlo anjel gremium akrobat cisterna clona atlet humanita favorit ciara adept lezec gorila",
],
];

describe("Slovak mnemonic wordlist", () => {
it("preserves all 2048 upstream word indices", () => {
assert.equal(wordlists.sk, LangSk.wordlist());
assert.equal(wordlists.sk.locale, "sk");
const words = Array.from({ length: 2048 }, (_, i) => {
const word = wordlists.sk.getWord(i);
assert.equal(wordlists.sk.getWordIndex(word), i);
return word;
});
assert.equal(new Set(words).size, 2048);
assert.equal(
createHash("sha256")
.update(words.join("\n") + "\n")
.digest("hex"),
"79b7334ea13a4ad25f7bdaf4e82075d888e81839ad69ebdf647e0467c33e0b24",
);
assert.equal(wordlists.sk.getWord(208), "chalupa");
assert.equal(wordlists.sk.getWordIndex("not-a-word"), -1);
for (const index of [-1, 2048, 0.5, NaN]) {
assert.throws(() => wordlists.sk.getWord(index));
}
});
it("matches mnemonic vectors and recovers entropy", () => {
for (const [hex, phrase] of vectors) {
const entropy = "0x" + hex;
assert.equal(
Mnemonic.fromEntropy(entropy, "", wordlists.sk).phrase,
phrase,
);
assert.equal(
Mnemonic.fromPhrase({ phrase, wordlist: wordlists.sk }).entropy,
entropy,
);
assert.equal(Mnemonic.isValidMnemonic(phrase, wordlists.sk), true);
assert.equal(Mnemonic.isValidMnemonic(phrase), false);
}
assert.equal(
Mnemonic.isValidMnemonic(
Array(12).fill("abakus").join(" "),
wordlists.sk,
),
false,
);
});
});

const germanVectors = [
[
"00000000000000000000000000000000",
"abbau abbau abbau abbau abbau abbau abbau abbau abbau abbau abbau abdruck",
],
[
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"zyklus zyklus zyklus zyklus zyklus zyklus zyklus zyklus zyklus zyklus zyklus zyklus zyklus zyklus zyklus zyklus zyklus zyklus zyklus zyklus zyklus zyklus zyklus winzig",
],
[
"000102030405060708090a0b0c0d0e0f10111213",
"abbau ameise korn ameise fordern achse bewahren bieder anliegen gravur esstisch bereich abgrund leer flugzeug",
],
];

describe("German mnemonic wordlist", () => {
it("preserves all 2048 upstream word indices", () => {
assert.equal(wordlists.de, LangDe.wordlist());
assert.equal(wordlists.de.locale, "de");
const words = Array.from({ length: 2048 }, (_, i) => {
const word = wordlists.de.getWord(i);
assert.equal(wordlists.de.getWordIndex(word), i);
return word;
});
assert.equal(new Set(words).size, 2048);
assert.equal(
createHash("sha256")
.update(words.join("\n") + "\n")
.digest("hex"),
"7965dc8c6b413ccb635d3021043365e18df0367bf5413a50a069a98addfe4e1d",
);
assert.equal(wordlists.de.getWord(2047), "zyklus");
assert.equal(wordlists.de.getWordIndex("not-a-word"), -1);
for (const index of [-1, 2048, 0.5, NaN]) {
assert.throws(() => wordlists.de.getWord(index));
}
});
it("matches mnemonic vectors and recovers entropy", () => {
for (const [hex, phrase] of germanVectors) {
const entropy = "0x" + hex;
assert.equal(
Mnemonic.fromEntropy(entropy, "", wordlists.de).phrase,
phrase,
);
assert.equal(
Mnemonic.fromPhrase({ phrase, wordlist: wordlists.de }).entropy,
entropy,
);
assert.equal(Mnemonic.isValidMnemonic(phrase, wordlists.de), true);
assert.equal(Mnemonic.isValidMnemonic(phrase), false);
}
assert.equal(
Mnemonic.isValidMnemonic(Array(12).fill("abbau").join(" "), wordlists.de),
false,
);
});
});
2 changes: 1 addition & 1 deletion src.ts/_version.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/** The current version of corebc; keep in sync with package.json. */
export const version: string = "1.0.0";
export const version: string = "1.1.0";
Loading