diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f37d33b..12cdb59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,15 @@ jobs: - run: npm ci - run: npm test + node-windows: + + runs-on: windows-latest + + steps: + - uses: actions/checkout@v3 + - run: npm ci + - run: npm test + deno: runs-on: ubuntu-latest @@ -39,7 +48,7 @@ jobs: with: deno-version: v1.x # - run: deno run --quiet --allow-read --allow-env --allow-net --allow-hrtime ./test.mjs - - run: deno run --quiet ./test.mjs + - run: deno run --quiet --allow-read ./test.mjs bun: diff --git a/base32.mjs b/base32.mjs new file mode 100644 index 0000000..367ad7c --- /dev/null +++ b/base32.mjs @@ -0,0 +1,35 @@ +/** @type {(uint5: number) => string} */ +const toBase32 = uint5 => '0123456789abcdefghjkmnpqrstvwxyz'[uint5] + +/** @type {(uint256: bigint) => number} */ +const getParityBit = uint256 => { + let xor = uint256 ^ (uint256 >> 128n) + xor ^= xor >> 64n + xor ^= xor >> 32n + xor ^= xor >> 16n + xor ^= xor >> 8n + xor ^= xor >> 4n + xor ^= xor >> 2n + xor ^= xor >> 1n + return Number(xor & 1n) +} + +/** @type {(bu224: bigint) => string} */ +const toAddress = bu224 => { + let address = '' + const parity = getParityBit(bu224) + for (let j = 0; j < 45; j++) { + let uint5 = Number(bu224 & 0b11111n) + if (j == 44) { + uint5 += parity << 4 + } + address += toBase32(uint5) + bu224 >>= 5n + } + return address +} + +export default { + toAddress, + getParityBit +} \ No newline at end of file diff --git a/digest256.mjs b/digest256.mjs index 7e584fa..260dd09 100644 --- a/digest256.mjs +++ b/digest256.mjs @@ -1,5 +1,5 @@ import sha224 from './sha224.mjs' -const { compress } = sha224 +const { compress2 } = sha224 const maxLength = 248n @@ -31,7 +31,7 @@ const merge = a => b => { const data = getData(a) | (getData(b) << lenA); return data | (lenAB << maxLength) } - return compress(a | (b << 256n)) + return compress2(a)(b) } export default { diff --git a/examples/small.txt b/examples/small.txt new file mode 100644 index 0000000..ead6ae8 --- /dev/null +++ b/examples/small.txt @@ -0,0 +1 @@ +Base32 is an encoding method based on the base-32 numeral system. It uses an alphabet of 32 digits, each of which represents a different combination of 5 bits (25). Since base32 is not very widely adopted, the question of notation—which characters to use to represent the 32 digits—is not as settled as in the case of more well-known numeral systems (such as hexadecimal), though RFCs and unofficial and de-facto standards exist. One way to represent Base32 numbers in a human-readable way is by using the digits 0–9 and the twenty-two upper-case letters A–V. However, many other variations are used in different contexts. Historically, Baudot code could be considered a modified (stateful) base32 code. \ No newline at end of file diff --git a/index.mjs b/index.mjs index 9e89817..e8ffcc4 100644 --- a/index.mjs +++ b/index.mjs @@ -1,20 +1,6 @@ import fs from 'node:fs' - -/** @type {(uint5: number) => string} */ -const toBase32 = uint5 => '0123456789abcdefghjkmnpqrstvwxyz'[uint5] - -/** @type {(uint256: bigint) => number} */ -const getParityBit = uint256 => { - let xor = uint256 ^ (uint256 >> 128n) - xor ^= xor >> 64n - xor ^= xor >> 32n - xor ^= xor >> 16n - xor ^= xor >> 8n - xor ^= xor >> 4n - xor ^= xor >> 2n - xor ^= xor >> 1n - return Number(xor & 1n) -} +import base32 from './base32.mjs' +const { toAddress } = base32 /** @type {(hash: string) => string} */ const getPath = hash => `${hash.substring(0, 2)}/${hash.substring(2, 4)}/${hash.substring(4)}` @@ -36,16 +22,7 @@ const getBuffer = root => { hash += BigInt(data[i + j]) << BigInt(8 * j) } console.log(hash) - let address = '' - const parity = getParityBit(hash) - for(let j = 0; j < 45; j++) { - let uint5 = Number(hash & 0b11111n) - if (j == 44) { - uint5 += parity << 4 - } - address += toBase32(uint5) - hash >>= 5n - } + const address = toAddress(hash) console.log(address) buffer = Buffer.concat([buffer, getBuffer(`parts/${getPath(address)}`)]) } @@ -63,10 +40,6 @@ const get = root => file => { } } -export default{ - getParityBit -} - //get('mnb8j83rgrch8hgb8rbz28d64ec2wranzbzxcy4ebypd8')('out') //get('2va87tc3cqebgg6wagd9dwe36e2vgcpdxjd26enj4c0xh')('out') //get('d963x31mwgb8svqe0jmkxh8ar1f8p2dawebnan4aj6hvd')('out') \ No newline at end of file diff --git a/sha224.mjs b/sha224.mjs index 033fbc1..2eb5d6e 100644 --- a/sha224.mjs +++ b/sha224.mjs @@ -151,6 +151,10 @@ const compress = w => { return x | u32Mask7 } +/** @type {(a256: bigint) => (b256: bigint) => bigint} */ +const compress2 = a256 => b256 => compress(a256 | (b256 << 256n)) + export default { - compress + compress, + compress2 } \ No newline at end of file diff --git a/subtree.mjs b/subtree.mjs index 980ac14..2550801 100644 --- a/subtree.mjs +++ b/subtree.mjs @@ -43,6 +43,9 @@ const height = a => b => { return leadingZero256(v) } +/** @type {(bu256: bigint) => State} */ +const newState = bu256 => [[bu256, bu256, 0n]] + /** @type {(state: State) => (last0: bigint) => bigint} */ const end = state => last0 => { while (true) { @@ -81,5 +84,7 @@ const push = state => last0 => { export default { highestOne256, height, - push + push, + newState, + end } \ No newline at end of file diff --git a/test.mjs b/test.mjs index 6ed9850..92e9a8c 100644 --- a/test.mjs +++ b/test.mjs @@ -1,12 +1,16 @@ -import index from './index.mjs' +import base32 from './base32.mjs' import sha224 from './sha224.mjs' import digest256 from './digest256.mjs' import subtree from './subtree.mjs' -/** @typedef {import('./subtree.mjs').State} State */ -const { getParityBit } = index +import tree from './tree.mjs' +import fs from 'node:fs' +/** @typedef {import('./subtree.mjs').State} StateSubTree */ +/** @typedef {import('./tree.mjs').State} StateTree */ +const { toAddress, getParityBit } = base32 const { compress } = sha224 const { merge, byteToDigest, len } = digest256 -const { highestOne256, height, push } = subtree +const { highestOne256, height, push: pushSubTree } = subtree +const { push: pushTree, end: endTree } = tree console.log(`test start`) @@ -159,34 +163,46 @@ console.log(`test start`) let b = byteToDigest(0b10) let c = byteToDigest(0b11) { - /** @type {State} */ + /** @type {StateSubTree} */ let state = [] - push(state)(a) + pushSubTree(state)(a) if (state.length !== 1) { throw state.length } const state0 = state[0] if (state0[0] !== a) { throw state0[0] } if (state0[1] !== a) { throw state0[1] } if (state0[2] !== 0n) { throw state0[2] } - const ab = push(state)(b) + const ab = pushSubTree(state)(b) const mergeAB = merge(a)(b) if (ab !== mergeAB) { throw ab } state = state if (state.length !== 0) { throw state.length } } { - /** @type {State} */ + /** @type {StateSubTree} */ let state = [] - let result = push(state)(c) + let result = pushSubTree(state)(c) if (result !== null) { throw result } - result = push(state)(b) + result = pushSubTree(state)(b) if (result !== null) { throw result } if (state.length !== 2 ) { throw state.length } - result = push(state)(a) + result = pushSubTree(state)(a) if (result !== null) { throw result } if (state.length !== 2 ) { throw state.length } - result = push(state)(a) + result = pushSubTree(state)(a) const mergeCB_AA = merge(merge(c)(b))(merge(a)(a)) if (result != mergeCB_AA) { throw result } } } +} + +{ + const data = fs.readFileSync(`examples/small.txt`) + /** @type {StateTree} */ + let tree = [] + for (let byte of data) { + pushTree(tree)(byte) + } + const digest = endTree(tree) + const result = toAddress(digest) + if (result !== 'vqfrc4k5j9ftnrqvzj40b67abcnd9pdjk62sq7cpbg7xe') { throw result } } \ No newline at end of file diff --git a/tree.mjs b/tree.mjs new file mode 100644 index 0000000..9c47f4b --- /dev/null +++ b/tree.mjs @@ -0,0 +1,46 @@ +import digest256 from './digest256.mjs' +import subtree from './subtree.mjs' +import sha224 from './sha224.mjs' +/** @typedef {import('./subtree.mjs').State} SubTreeState */ +const { byteToDigest } = digest256 +const { newState: newSubTree, push: pushSubTree, end: endSubTree } = subtree +const { compress } = sha224 + +/** + * @typedef {SubTreeState[]} State + */ + +const mask244 = ((1n << 224n) - 1n) + +/** @type {(state: State) => (nu8: number) => void} */ +const push = state => nu8 => { + let i = 0 + let last0 = byteToDigest(nu8) + while (true) { + let subTree = state[i] + if (subTree === undefined) { + state.push(newSubTree(last0)) + return + } + const last1 = pushSubTree(subTree)(last0) + if (last1 === null) { + return + } + last0 = last1 + i += 1 + } +} + +/** @type {(state: State) => bigint} */ +const end = state => { + let last0 = 0n + for (let subTree of state) { + last0 = endSubTree(subTree)(last0) + } + return compress(last0) & mask244 +} + +export default { + push, + end +} \ No newline at end of file