From 0349cc016383d2ad0c3e9589b2c4edb2cd32c3b4 Mon Sep 17 00:00:00 2001 From: Trinidadec Date: Tue, 14 Nov 2023 23:09:40 +0200 Subject: [PATCH] renaming for name consistency (#70) * renaming * fix naming * version 0.3.2 --- .github/workflows/ci.yml | 2 +- tree.mjs => cdt/main-tree.mjs | 24 ++-- digest256.mjs => cdt/node-id.mjs | 14 +-- subtree.mjs => cdt/sub-tree.mjs | 4 +- base32.mjs => crypto/base32.mjs | 10 +- sha224.mjs => crypto/sha224.mjs | 0 forest/index.mjs | 188 +++++++++++++++++++++++++++++++ get.mjs | 188 ------------------------------- index.mjs | 24 ++-- package.json | 2 +- test.mjs | 36 +++--- web-test.mjs | 14 +-- 12 files changed, 252 insertions(+), 254 deletions(-) rename tree.mjs => cdt/main-tree.mjs (70%) rename digest256.mjs => cdt/node-id.mjs (82%) rename subtree.mjs => cdt/sub-tree.mjs (96%) rename base32.mjs => crypto/base32.mjs (85%) rename sha224.mjs => crypto/sha224.mjs (100%) create mode 100644 forest/index.mjs delete mode 100644 get.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99544f6..58b648c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ on: branches: [ "main" ] env: - VERSION: 0.3.1 + VERSION: 0.3.2 jobs: node: diff --git a/tree.mjs b/cdt/main-tree.mjs similarity index 70% rename from tree.mjs rename to cdt/main-tree.mjs index d9a2a0c..7c02576 100644 --- a/tree.mjs +++ b/cdt/main-tree.mjs @@ -1,9 +1,9 @@ -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 +import nodeId from './node-id.mjs' +import subTree from './sub-tree.mjs' +import sha224 from '../crypto/sha224.mjs' +/** @typedef {import('./sub-tree.mjs').State} SubTreeState */ +const { byteToNodeId } = nodeId +const { newState: newSubTree, push: pushSubTree, end: endSubTree } = subTree const { compress } = sha224 /** @@ -13,10 +13,10 @@ const { compress } = sha224 const mask224 = ((1n << 224n) - 1n) /** @type {(state: State) => (nu8: number) => void} */ -const push = state => nu8 => pushDigest(state)(byteToDigest(nu8)) +const push = state => nu8 => pushNodeId(state)(byteToNodeId(nu8)) /** @type {(state: State) => (bu256: bigint) => void} */ -const pushDigest = state => last0 => { +const pushNodeId = state => last0 => { let i = 0 while (true) { let subTree = state[i] @@ -38,11 +38,11 @@ const end = state => compress(internalEnd(state)) & mask224 /** @type {(state: State) => bigint | null} */ const partialEnd = state => { - let digest256 = internalEnd(state) - if (digest256 >> 224n !== 0xffff_ffffn) { + let nodeId = internalEnd(state) + if (nodeId >> 224n !== 0xffff_ffffn) { return null } - return digest256 & mask224 + return nodeId & mask224 } /** @type {(state: State) => bigint} */ @@ -56,7 +56,7 @@ const internalEnd = state => { export default { push, - pushDigest, + pushNodeId, end, partialEnd } \ No newline at end of file diff --git a/digest256.mjs b/cdt/node-id.mjs similarity index 82% rename from digest256.mjs rename to cdt/node-id.mjs index ea1a1cb..d342023 100644 --- a/digest256.mjs +++ b/cdt/node-id.mjs @@ -1,12 +1,10 @@ -import sha224 from './sha224.mjs' +import sha224 from '../crypto/sha224.mjs' const { compress2 } = sha224 const maxLength = 248n const u32Mask = 0xffff_ffffn -const hashMask = u32Mask << 224n; - const byteMask = 0x08n << maxLength const dataMask = (1n << 248n) - 1n @@ -17,7 +15,7 @@ const dataMask = (1n << 248n) - 1n const len = bu256 => (bu256 >> maxLength) & u32Mask /** @type {(nu8: number) => bigint} */ -const byteToDigest = nu8 => BigInt(nu8) | byteMask +const byteToNodeId = nu8 => BigInt(nu8) | byteMask /** @type {(bu256: bigint) => bigint} */ const getData = bu256 => bu256 & dataMask @@ -42,20 +40,20 @@ const merge = a => b => { /** @type {(tail: Uint8Array) => bigint} */ -const tailToDigest = tail => { +const tailToNodeId = tail => { if (tail.length > 31) { throw 'invalid tail' } let result = 0n for(let byte of tail) { - result = merge(result)(byteToDigest(byte)) + result = merge(result)(byteToNodeId(byte)) } return result } export default { merge, - byteToDigest, - tailToDigest, + byteToNodeId, + tailToNodeId, len } \ No newline at end of file diff --git a/subtree.mjs b/cdt/sub-tree.mjs similarity index 96% rename from subtree.mjs rename to cdt/sub-tree.mjs index 2550801..3010bad 100644 --- a/subtree.mjs +++ b/cdt/sub-tree.mjs @@ -1,5 +1,5 @@ -import digest256 from './digest256.mjs' -const { merge } = digest256 +import nodeId from './node-id.mjs' +const { merge } = nodeId /** * @typedef {readonly [bigint, bigint, bigint]} Node diff --git a/base32.mjs b/crypto/base32.mjs similarity index 85% rename from base32.mjs rename to crypto/base32.mjs index 367ad7c..b37a745 100644 --- a/base32.mjs +++ b/crypto/base32.mjs @@ -15,21 +15,21 @@ const getParityBit = uint256 => { } /** @type {(bu224: bigint) => string} */ -const toAddress = bu224 => { - let address = '' +const toBase32Hash = bu224 => { + let result = '' 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) + result += toBase32(uint5) bu224 >>= 5n } - return address + return result } export default { - toAddress, + toBase32Hash, getParityBit } \ No newline at end of file diff --git a/sha224.mjs b/crypto/sha224.mjs similarity index 100% rename from sha224.mjs rename to crypto/sha224.mjs diff --git a/forest/index.mjs b/forest/index.mjs new file mode 100644 index 0000000..9bf4d96 --- /dev/null +++ b/forest/index.mjs @@ -0,0 +1,188 @@ +import base32 from '../crypto/base32.mjs' +import mainTree from '../cdt/main-tree.mjs' +import nodeId from '../cdt/node-id.mjs' +/** @typedef {import('../cdt/main-tree.mjs').State} StateTree */ +/** + * @template T + * @typedef {import('../cdt/sub-tree.mjs').Nullable} Nullable + */ +const { toBase32Hash } = base32 +const { push: pushTree, end: endTree, partialEnd: partialEndTree, pushNodeId } = mainTree +const { tailToNodeId } = nodeId + +/** + * second element is root flag + * @typedef {readonly [string, boolean]} ForestNodeId + */ + +/** + * @typedef {[ForestNodeId, Uint8Array]} ForestNode + */ + +/** + * @template T + * @typedef {readonly['ok', T]} Ok + */ + +/** + * @template E + * @typedef {readonly['error', E]} Error + */ + +/** + * @template T + * @template E + * @typedef {Ok|Error} Result + */ + +/** + * @typedef {readonly Uint8Array[]} OkOutput + */ + +/** + * @typedef { Result } Output +*/ + +/** + * @typedef { Uint8Array } ReadonlyUint8Array + */ + +/** + * @typedef {[ForestNodeId, Nullable]} ForestNodeState + */ + +/** + * @typedef { ForestNodeState[] } State +*/ + +/** + * @typedef {{ + * readonly read: (address: ForestNodeId) => Promise, + * readonly write: (buffer: Uint8Array) => Promise, + * }} Provider +*/ + +/** @type {(state: State) => (forestNode: ForestNode) => void} */ +const insertForestNode = state => forestNode => { + for (let i = 0; i < state.length; i++) { + if (state[i][0][0] === forestNode[0][0]) { + state[i][1] = forestNode[1] + } + } +} + +/** @type {(state: State) => Output} */ +const nextState = state => { + /** @type {Uint8Array[]} */ + let resultBuffer = [] + + while (true) { + const forestNodeLast = state.at(-1) + if (forestNodeLast === undefined) { + return ['ok', resultBuffer] + } + + const forestNodeData = forestNodeLast[1] + if (forestNodeData === null) { + return ['ok', resultBuffer] + } + + state.pop() + + if (forestNodeLast[0][0] === '') { + resultBuffer.push(forestNodeData) + continue + } + + /** @type {StateTree} */ + let verificationTree = [] + const tailLength = forestNodeData[0] + if (tailLength === 32) { + const data = forestNodeData.subarray(1) + for (let byte of data) { + pushTree(verificationTree)(byte) + } + resultBuffer.push(data) + } else { + const tail = forestNodeData.subarray(1, tailLength + 1) + if (tail.length !== 0) { + state.push([['', false], tail]) + } + /** @type {ForestNodeId[]} */ + let children = [] + for (let i = tailLength + 1; i < forestNodeData.length; i += 28) { + let forestNodeHash = 0n + for (let j = 0; j < 28; j++) { + forestNodeHash += BigInt(forestNodeData[i + j]) << BigInt(8 * j) + } + pushNodeId(verificationTree)(forestNodeHash | (0xffff_ffffn << 224n)) + const childBase32Hash = toBase32Hash(forestNodeHash) + children.push([childBase32Hash, false]) + } + pushNodeId(verificationTree)(tailToNodeId(tail)) + const forestNodeHash = forestNodeLast[0][1] ? endTree(verificationTree) : partialEndTree(verificationTree) + if (forestNodeHash === null || toBase32Hash(forestNodeHash) !== forestNodeLast[0][0]) { + return ['error', `verification failed ${forestNodeLast[0][0]}`] + } + + for (let i = children.length - 1; i >= 0; i--) { + state.push([children[i], null]) + } + } + } +} + +/** @type {(provider: Provider) => (root: string) => Promise} */ +const get = ({ read, write }) => async (root) => { + /** @type {State} */ + let state = [[[root, true], null]] + /** @type {[ForestNodeId, Promise] | null} */ + let readPromise = null + /** @type {Promise | null} */ + let writePromise = null + try { + while (true) { + const nodeStateLast = state.at(-1) + if (nodeStateLast === undefined) { + if (writePromise === null) { + return 'unexpected behaviour' + } + await writePromise + return null + } + + if (readPromise !== null) { + const data = await readPromise[1] + insertForestNode(state)([readPromise[0], data]) + } + + for (let i = state.length - 1; i >= 0; i--) { + const nodeStateLastI = state[i] + if (nodeStateLastI[1] === null) { + const nodeId = nodeStateLastI[0] + readPromise = [nodeId, read(nodeId)] + break + } + } + + const next = nextState(state) + if (next[0] === 'error') { + return `${next[1]}` + } + + const writeData = next[1] + for (let buffer of writeData) { + if (writePromise === null) { + writePromise = Promise.resolve() + } + writePromise = writePromise.then(() => write(buffer)) + } + } + } catch (err) { + return `${err}` + } +} + +export default { + get +} \ No newline at end of file diff --git a/get.mjs b/get.mjs deleted file mode 100644 index 868aab7..0000000 --- a/get.mjs +++ /dev/null @@ -1,188 +0,0 @@ -import base32 from './base32.mjs' -import tree from './tree.mjs' -import digest256 from './digest256.mjs' -/** @typedef {import('./tree.mjs').State} StateTree */ -/** - * @template T - * @typedef {import('./subtree.mjs').Nullable} Nullable - */ -const { toAddress } = base32 -const { push: pushTree, end: endTree, partialEnd: partialEndTree, pushDigest } = tree -const { tailToDigest } = digest256 - -/** - * second element is root flag - * @typedef {readonly [string, boolean]} Address - */ - -/** - * @typedef {[Address, Uint8Array]} Block - */ - -/** - * @template T - * @typedef {readonly['ok', T]} Ok - */ - -/** - * @template E - * @typedef {readonly['error', E]} Error - */ - -/** - * @template T - * @template E - * @typedef {Ok|Error} Result - */ - -/** - * @typedef {readonly Uint8Array[]} OkOutput - */ - -/** - * @typedef { Result } Output -*/ - -/** - * @typedef { Uint8Array } ReadonlyUint8Array - */ - -/** - * @typedef {[Address, Nullable]} BlockState - */ - -/** - * @typedef { BlockState[] } State -*/ - -/** - * @typedef {{ - * readonly read: (address: Address) => Promise, - * readonly write: (buffer: Uint8Array) => Promise, - * }} Provider -*/ - -/** @type {(state: State) => (block: Block) => void} */ -const insertBlock = state => block => { - for (let i = 0; i < state.length; i++) { - if (state[i][0][0] === block[0][0]) { - state[i][1] = block[1] - } - } -} - -/** @type {(state: State) => Output} */ -const nextState = state => { - /** @type {Uint8Array[]} */ - let resultBuffer = [] - - while (true) { - const blockLast = state.at(-1) - if (blockLast === undefined) { - return ['ok', resultBuffer] - } - - const blockData = blockLast[1] - if (blockData === null) { - return ['ok', resultBuffer] - } - - state.pop() - - if (blockLast[0][0] === '') { - resultBuffer.push(blockData) - continue - } - - /** @type {StateTree} */ - let verificationTree = [] - const tailLength = blockData[0] - if (tailLength === 32) { - const data = blockData.subarray(1) - for (let byte of data) { - pushTree(verificationTree)(byte) - } - resultBuffer.push(data) - } else { - const tail = blockData.subarray(1, tailLength + 1) - if (tail.length !== 0) { - state.push([['', false], tail]) - } - /** @type {Address[]} */ - let childAddresses = [] - for (let i = tailLength + 1; i < blockData.length; i += 28) { - let hash = 0n - for (let j = 0; j < 28; j++) { - hash += BigInt(blockData[i + j]) << BigInt(8 * j) - } - pushDigest(verificationTree)(hash | (0xffff_ffffn << 224n)) - const childAddress = toAddress(hash) - childAddresses.push([childAddress, false]) - } - pushDigest(verificationTree)(tailToDigest(tail)) - const digest = blockLast[0][1] ? endTree(verificationTree) : partialEndTree(verificationTree) - if (digest === null || toAddress(digest) !== blockLast[0][0]) { - return ['error', `verification failed ${blockLast[0][0]}`] - } - - for (let i = childAddresses.length - 1; i >= 0; i--) { - state.push([childAddresses[i], null]) - } - } - } -} - -/** @type {(provider: Provider) => (root: string) => Promise} */ -const get = ({ read, write }) => async (root) => { - /** @type {State} */ - let state = [[[root, true], null]] - /** @type {[Address, Promise] | null} */ - let readPromise = null - /** @type {Promise | null} */ - let writePromise = null - try { - while (true) { - const blockLast = state.at(-1) - if (blockLast === undefined) { - if (writePromise === null) { - return 'unexpected behaviour' - } - await writePromise - return null - } - - if (readPromise !== null) { - const data = await readPromise[1] - insertBlock(state)([readPromise[0], data]) - } - - for (let i = state.length - 1; i >= 0; i--) { - const blockLastI = state[i] - if (blockLastI[1] === null) { - const address = blockLastI[0] - readPromise = [address, read(address)] - break - } - } - - const next = nextState(state) - if (next[0] === 'error') { - return `${next[1]}` - } - - const writeData = next[1] - for (let buffer of writeData) { - if (writePromise === null) { - writePromise = Promise.resolve() - } - writePromise = writePromise.then(() => write(buffer)) - } - } - } catch (err) { - return `${err}` - } -} - -export default { - get -} \ No newline at end of file diff --git a/index.mjs b/index.mjs index 59be171..8dcaaf5 100644 --- a/index.mjs +++ b/index.mjs @@ -1,30 +1,30 @@ -import getModule from './get.mjs' -/** @typedef {import('./tree.mjs').State} StateTree */ +import getModule from './forest/index.mjs' +/** @typedef {import('./cdt/main-tree.mjs').State} StateTree */ /** * @template T - * @typedef {import('./subtree.mjs').Nullable} Nullable + * @typedef {import('./cdt/sub-tree.mjs').Nullable} Nullable */ -/** @typedef {import('./get.mjs').Address} Address */ +/** @typedef {import('./forest/index.mjs').ForestNodeId} ForestNodeId */ /** @typedef {import('./io/io.mjs').IO} IO */ const { get } = getModule -/** @type {(address: Address) => string} */ -const getPath = ([address, isRoot]) => { +/** @type {(forestNodeId: ForestNodeId) => string} */ +const getPath = ([forestNodeId, isRoot]) => { const dir = isRoot ? 'roots' : 'parts' - return `cdt0/${dir}/${address.substring(0, 2)}/${address.substring(2, 4)}/${address.substring(4)}` + return `cdt0/${dir}/${forestNodeId.substring(0, 2)}/${forestNodeId.substring(2, 4)}/${forestNodeId.substring(4)}` } -/** @type {(hostName: string) => (io: IO) => (address: Address) => Promise} */ -const fetchRead = hostName => ({ fetch }) => address => fetch(`https://${hostName}/${getPath(address)}`) +/** @type {(hostName: string) => (io: IO) => (forestNodeId: ForestNodeId) => Promise} */ +const fetchRead = hostName => ({ fetch }) => forestNodeId => fetch(`https://${hostName}/${getPath(forestNodeId)}`) .then(async (resp) => resp.arrayBuffer().then(buffer => new Uint8Array(buffer))) /** @type {(io: IO) => (root: [string, string]) => Promise} */ const getLocal = io => async ([root, file]) => { const tempFile = `_temp_${root}` await io.write(tempFile, new Uint8Array()) - /** @type {(address: Address) => Promise} */ - const read = address => io.read(getPath(address)) + /** @type {(forestNodeId: ForestNodeId) => Promise} */ + const read = forestNodeId => io.read(getPath(forestNodeId)) /** @type {(buffer: Uint8Array) => Promise} */ const write = buffer => io.append(tempFile, buffer) const error = await get({ read, write })(root) @@ -40,7 +40,7 @@ const getLocal = io => async ([root, file]) => { const getRemote = host => io => async ([root, file]) => { const tempFile = `_temp_${root}` await io.write(tempFile, new Uint8Array()) - /** @type {(address: Address) => Promise} */ + /** @type {(forestNodeId: ForestNodeId) => Promise} */ const read = fetchRead(host)(io) /** @type {(buffer: Uint8Array) => Promise} */ const write = buffer => io.append(tempFile, buffer) diff --git a/package.json b/package.json index cfeaa55..6722f44 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blockset-js", - "version": "0.3.1", + "version": "0.3.2", "description": "BLOCKSET on JavaScript", "keywords": ["blockset", "content-addressable", "storage", "cdt", "content-dependent-tree", "hash"], "main": "index.mjs", diff --git a/test.mjs b/test.mjs index c9f11e9..32ffba0 100644 --- a/test.mjs +++ b/test.mjs @@ -1,20 +1,20 @@ -import base32 from './base32.mjs' -import sha224 from './sha224.mjs' -import digest256 from './digest256.mjs' -import subtree from './subtree.mjs' -import tree from './tree.mjs' +import base32 from './crypto/base32.mjs' +import sha224 from './crypto/sha224.mjs' +import nodeId from './cdt/node-id.mjs' +import subTree from './cdt/sub-tree.mjs' +import mainTree from './cdt/main-tree.mjs' import index from './index.mjs' import ioNode from './io/node.mjs' import fs from 'node:fs' import fsPromises from 'node:fs/promises' -/** @typedef {import('./subtree.mjs').State} StateSubTree */ -/** @typedef {import('./tree.mjs').State} StateTree */ +/** @typedef {import('./cdt/sub-tree.mjs').State} StateSubTree */ +/** @typedef {import('./cdt/main-tree.mjs').State} StateTree */ /** @typedef {import('./io/io.mjs').IO} IO */ -const { toAddress, getParityBit } = base32 +const { toBase32Hash, getParityBit } = base32 const { compress } = sha224 -const { merge, byteToDigest, len } = digest256 -const { highestOne256, height, push: pushSubTree } = subtree -const { push: pushTree, end: endTree } = tree +const { merge, byteToNodeId, len } = nodeId +const { highestOne256, height, push: pushSubTree } = subTree +const { push: pushTree, end: endTree } = mainTree const { getLocal, getRemote } = index const { node, nodeSync } = ioNode @@ -37,9 +37,9 @@ console.log(`test start`) } { - const a = byteToDigest(0x12) + const a = byteToNodeId(0x12) if (a !== 0x0800_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0012n) { throw a.toString(16) } - const b = byteToDigest(0x34) + const b = byteToNodeId(0x34) if (b !== 0x0800_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0034n) { throw b.toString(16) } const lenA = len(a) if (lenA !== 8n) { throw lenA.toString(16) } @@ -165,9 +165,9 @@ console.log(`test start`) } { - let a = byteToDigest(0b01) - let b = byteToDigest(0b10) - let c = byteToDigest(0b11) + let a = byteToNodeId(0b01) + let b = byteToNodeId(0b10) + let c = byteToNodeId(0b11) { /** @type {StateSubTree} */ let state = [] @@ -208,8 +208,8 @@ console.log(`test start`) for (let byte of data) { pushTree(tree)(byte) } - const digest = endTree(tree) - const result = toAddress(digest) + const nodeId = endTree(tree) + const result = toBase32Hash(nodeId) if (result !== 'vqfrc4k5j9ftnrqvzj40b67abcnd9pdjk62sq7cpbg7xe') { throw result } } diff --git a/web-test.mjs b/web-test.mjs index 0df85cf..7f39d9f 100644 --- a/web-test.mjs +++ b/web-test.mjs @@ -1,8 +1,8 @@ -import getModule from './get.mjs' +import forest from './forest/index.mjs' import ioWeb from './io/web.mjs' import index from './index.mjs' -/** @typedef {import('./get.mjs').Address} Address */ -const { get } = getModule +/** @typedef {import('./forest/index.mjs').ForestNodeId} ForestNodeId */ +const { get } = forest const { web } = ioWeb const { fetchRead } = index @@ -16,11 +16,11 @@ d.getElementById('download').addEventListener('click', () => { const host = d.getElementById('input-host').value let buffer = new Uint8Array() const fRead = fetchRead(host)(web) - /** @type {(address: Address) => Promise} */ - const read = address => { + /** @type {(forestNodeId: ForestNodeId) => Promise} */ + const read = forestNodeId => { // @ts-ignore - d.getElementById('log').innerText += `read from ${address}\n` - return fRead(address) + d.getElementById('log').innerText += `read from ${forestNodeId}\n` + return fRead(forestNodeId) } /** @type {(b: Uint8Array) => Promise} */ const write = async (b) => {