From 70ae60cfb0315f2c4fe55b5624e4b8ee77138f42 Mon Sep 17 00:00:00 2001 From: fuzzc0re Date: Tue, 18 Oct 2022 13:55:07 +0300 Subject: [PATCH] Separated merkle, added root from proof and single element array merkle is sha512 --- .gitignore | 1 + README.md | 6 +- __tests__/hash.test.ts | 271 +---------------- __tests__/merkle.test.ts | 339 ++++++++++++++++++++++ package-lock.json | 76 ++--- package.json | 2 +- scripts/compileMethods.js | 1 + scripts/dcryptoMethodsModule.d.ts | 6 + src/c/dcrypto.c | 91 ++++++ src/hash/index.ts | 8 +- src/hash/memory.ts | 33 --- src/index.ts | 18 +- src/{hash => merkle}/getMerkleProof.ts | 10 +- src/{hash => merkle}/getMerkleRoot.ts | 3 +- src/merkle/getMerkleRootFromProof.ts | 94 ++++++ src/merkle/index.ts | 28 ++ src/merkle/memory.ts | 54 ++++ src/{hash => merkle}/verifyMerkleProof.ts | 0 18 files changed, 682 insertions(+), 359 deletions(-) create mode 100644 __tests__/merkle.test.ts rename src/{hash => merkle}/getMerkleProof.ts (94%) rename src/{hash => merkle}/getMerkleRoot.ts (98%) create mode 100644 src/merkle/getMerkleRootFromProof.ts create mode 100644 src/merkle/index.ts create mode 100644 src/merkle/memory.ts rename src/{hash => merkle}/verifyMerkleProof.ts (100%) diff --git a/.gitignore b/.gitignore index 6c787cb..cb063da 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ dist *valgrind*.txt .npmrc deliberative*.tgz +dcryptoMethodsModule.js # Logs logs diff --git a/README.md b/README.md index 6e2e92e..45e1a41 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,10 @@ The [symmetric](src/symmetric) directory contains AEAD encryption/decryption wit The [mnemonic](src/mnemonic) directory contains all the relevant to mnemonic generation functions. -The [hash](src/hash) directory contains a sha512 hashing function, a Merkle root getter function, a Merkle -proof artifacts getter and a verification function. +The [hash](src/hash) directory contains a sha512 hashing function and an Argon2 with optional salt hashing function. + +The [merkle](src/merkle) directory contains a Merkle root getter function, a Merkle +proof artifacts getter, a root from proof getter and a proof verification function. The [shamir](src/shamir) directory contains a WASM implementation of a cryptographic technique called [Shamir's secret sharing](https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing), which allows one to split a secret into random shares that can only recreate it if a threshold of them is combined. diff --git a/__tests__/hash.test.ts b/__tests__/hash.test.ts index ad76253..98dd52e 100644 --- a/__tests__/hash.test.ts +++ b/__tests__/hash.test.ts @@ -1,7 +1,6 @@ import dcrypto from "../src"; -jest.setTimeout(10000); -describe("Sha512 and Merkle root test suite.", () => { +describe("Sha512 and Argon2 test suite.", () => { test("Public key SHA512 hash works.", async () => { const mnemonic = await dcrypto.generateMnemonic(); const keypair = await dcrypto.keyPairFromMnemonic(mnemonic); @@ -9,269 +8,9 @@ describe("Sha512 and Merkle root test suite.", () => { expect(hash.length).toBe(64); }); - test("Merkle root calculation works.", async () => { - const tree: Uint8Array[] = []; - for (let i = 0; i < 201; i++) { - const rand = await dcrypto.randomBytes(128); - tree.push(rand); - } - - const root = await dcrypto.getMerkleRoot(tree); - const root2 = await dcrypto.getMerkleRoot(tree); - - expect(root.length).toBe(64); - expect(root[0]).toBe(root2[0]); - expect(root[1]).toBe(root2[1]); - expect(root[63]).toBe(root2[63]); - }); - - test("Merkle proof verification works for odd number of elements.", async () => { - const tree: Uint8Array[] = []; - const element = new Uint8Array(128); - const elements = 201; - const elementIndex = 99; - for (let i = 0; i < elements; i++) { - const rand = await dcrypto.randomBytes(128); - - if (i === elementIndex) element.set([...rand]); - - tree.push(rand); - } - - const root = await dcrypto.getMerkleRoot(tree); - - const proof = await dcrypto.getMerkleProof(tree, tree[elementIndex]); - - const elementHash = await dcrypto.sha512(element); - - const verification = await dcrypto.verifyMerkleProof( - elementHash, - root, - proof, - ); - - expect(verification).toBe(true); - }); - - test("Merkle proof verification works for even number of elements.", async () => { - const tree: Uint8Array[] = []; - const element = new Uint8Array(128); - const elements = 200; - const elementIndex = 99; - for (let i = 0; i < elements; i++) { - const rand = await dcrypto.randomBytes(128); - - if (i === elementIndex) element.set([...rand]); - - tree.push(rand); - } - - const root = await dcrypto.getMerkleRoot(tree); - - const proof = await dcrypto.getMerkleProof(tree, tree[elementIndex]); - - const elementHash = await dcrypto.sha512(element); - - const verification = await dcrypto.verifyMerkleProof( - elementHash, - root, - proof, - ); - - expect(verification).toBe(true); - }); - - it("Should throw an error when faced with false data.", async () => { - const tree: Uint8Array[] = []; - const element = new Uint8Array(128); - const elements = 201; - const elementIndex = 99; - for (let i = 0; i < elements; i++) { - const rand = await dcrypto.randomBytes(128); - - if (i === elementIndex) element.set([...rand]); - - tree.push(rand); - } - - const root = await dcrypto.getMerkleRoot(tree); - const proof = await dcrypto.getMerkleProof(tree, tree[elementIndex]); - const elementHash = await dcrypto.sha512(element); - - await expect( - dcrypto.verifyMerkleProof( - elementHash, - root, - proof.slice(0, proof.length - 1), - ), - ).rejects.toThrow("Proof length not multiple of 65."); - - const proofWrongPosition = Uint8Array.from([...proof]); - proofWrongPosition[dcrypto.constants.crypto_hash_sha512_BYTES] = 2; - await expect( - dcrypto.verifyMerkleProof(elementHash, root, proofWrongPosition), - ).rejects.toThrow("Proof artifact position is neither left nor right."); - - const proofWrongByte = Uint8Array.from([...proof]); - proofWrongByte[1] = proof[1] === 255 ? 254 : proof[1] + 1; - - const verification = await dcrypto.verifyMerkleProof( - elementHash, - root, - proofWrongByte, - ); - - expect(verification).toBe(false); - }); - - const len = 64; - const arr1 = new Uint8Array(len); - const arr2 = new Uint8Array(len); - arr1.fill(1); - arr2.fill(2); - - const arr3 = new Uint8Array(len); - arr3.fill(1); - - const arr4 = new Uint8Array(len); - arr4.fill(4); - - const arrayOfArrays1: Uint8Array[] = []; - arrayOfArrays1.push(arr2); - arrayOfArrays1.push(arr4); - arrayOfArrays1.push(arr2); - - interface SomeRandomInterface { - val1: string; - val2: string; - val3: string; - } - - const arr5: SomeRandomInterface = { - val1: "1", - val2: "2", - val3: "3", - }; - - const arr6: SomeRandomInterface = { - val1: "5", - val2: "6", - val3: "7", - }; - - const arr7: SomeRandomInterface = { - val1: "10", - val2: "20", - val3: "30", - }; - - const arrayOfArrays3: SomeRandomInterface[] = [arr5, arr6, arr7]; - - const numberToUint8Array = (n: number): Uint8Array => { - return Uint8Array.of( - (n & 0xff000000) >> 24, - (n & 0x00ff0000) >> 16, - (n & 0x0000ff00) >> 8, - (n & 0x000000ff) >> 0, - ); - }; - - const someRandomInterfaceSerializer = (item: SomeRandomInterface) => { - const uint8 = new Uint8Array(4 * 3 * Uint8Array.BYTES_PER_ELEMENT); - - uint8.set(numberToUint8Array(Number(item.val1))); - uint8.set( - numberToUint8Array(Number(item.val2)), - 4 * Uint8Array.BYTES_PER_ELEMENT, - ); - uint8.set( - numberToUint8Array(Number(item.val3)), - 8 * Uint8Array.BYTES_PER_ELEMENT, - ); - - return uint8; - }; - - it("Should be possible to get Merkle root and proof from non-Uint8 data.", async () => { - const root = await dcrypto.getMerkleRoot( - arrayOfArrays3, - someRandomInterfaceSerializer, - ); - - const arr6Serialized = someRandomInterfaceSerializer(arr6); - const proof1 = await dcrypto.getMerkleProof( - arrayOfArrays3, - arr6Serialized, - someRandomInterfaceSerializer, - ); - - const arrayOfArrays3Serialized: Uint8Array[] = []; - for (let i = 0; i < arrayOfArrays3.length; i++) { - arrayOfArrays3Serialized.push( - someRandomInterfaceSerializer(arrayOfArrays3[i]), - ); - } - const proof2 = await dcrypto.getMerkleProof( - arrayOfArrays3Serialized, - arr6, - someRandomInterfaceSerializer, - ); - - const elementHash = await dcrypto.sha512(arr6Serialized); - const verification1 = await dcrypto.verifyMerkleProof( - elementHash, - root, - proof1, - ); - - const verification2 = await dcrypto.verifyMerkleProof( - elementHash, - root, - proof2, - ); - - expect(verification1).toBe(true); - expect(verification2).toBe(true); - - const root1 = await dcrypto.getMerkleRoot( - [arr6], - someRandomInterfaceSerializer, - ); - expect(root1.length).toBe(64); - - const root2 = await dcrypto.getMerkleRoot([arr6Serialized]); - expect(root2.length).toBe(64); - }); - - it("Should throw errors when trying to get merkle root with wrong data.", async () => { - await expect( - dcrypto.getMerkleRoot([], someRandomInterfaceSerializer), - ).rejects.toThrow("Cannot calculate Merkle root of tree with no leaves."); - - await expect(dcrypto.getMerkleRoot([arr6])).rejects.toThrow( - "Tree leaf not Uint8Array, needs serializer.", - ); - - await expect(dcrypto.getMerkleRoot(arrayOfArrays3)).rejects.toThrow( - "Tree leaf not Uint8Array, needs serializer.", - ); - }); - - it("Should throw errors when trying to get merkle proof with wrong data.", async () => { - await expect( - dcrypto.getMerkleProof([], arr6, someRandomInterfaceSerializer), - ).rejects.toThrow( - "Cannot calculate Merkle proof of element of empty tree.", - ); - - await expect( - dcrypto.getMerkleProof([arr6], arr6, someRandomInterfaceSerializer), - ).rejects.toThrow( - "No point in calculating proof of a tree with single leaf.", - ); - - await expect(dcrypto.getMerkleProof(arrayOfArrays3, arr6)).rejects.toThrow( - "It is mandatory to provide a serializer for non-Uint8Array items", - ); + test("Mnemonic Argon2 hash works.", async () => { + const mnemonic = await dcrypto.generateMnemonic(); + const hash = await dcrypto.argon2(mnemonic); + expect(hash.length).toBe(32); }); }); diff --git a/__tests__/merkle.test.ts b/__tests__/merkle.test.ts new file mode 100644 index 0000000..246b222 --- /dev/null +++ b/__tests__/merkle.test.ts @@ -0,0 +1,339 @@ +import dcrypto from "../src"; + +describe("Merkle test suite.", () => { + test("Merkle root calculation works.", async () => { + const tree: Uint8Array[] = []; + for (let i = 0; i < 201; i++) { + const rand = await dcrypto.randomBytes(128); + tree.push(rand); + } + + const root = await dcrypto.getMerkleRoot(tree); + const root2 = await dcrypto.getMerkleRoot(tree); + + expect(root.length).toBe(64); + expect(root[0]).toBe(root2[0]); + expect(root[1]).toBe(root2[1]); + expect(root[63]).toBe(root2[63]); + }); + + test("Merkle proof should be able to recalculate Merkle root.", async () => { + const tree: Uint8Array[] = []; + const element = new Uint8Array(128); + const elements = 201; + const elementIndex = 99; + for (let i = 0; i < elements; i++) { + const rand = await dcrypto.randomBytes(128); + + if (i === elementIndex) element.set([...rand]); + + tree.push(rand); + } + + const root = await dcrypto.getMerkleRoot(tree); + + const proof = await dcrypto.getMerkleProof(tree, tree[elementIndex]); + + const elementHash = await dcrypto.sha512(element); + + const rootCalculated = await dcrypto.getMerkleRootFromProof( + elementHash, + proof, + ); + + let arraysAreEqual = true; + for (let i = 0; i < dcrypto.constants.crypto_hash_sha512_BYTES; i++) { + if (root[i] !== rootCalculated[i]) { + arraysAreEqual = false; + break; + } + } + + expect(arraysAreEqual).toBe(true); + + const proofWrongPosition = Uint8Array.from([...proof]); + proofWrongPosition[dcrypto.constants.crypto_hash_sha512_BYTES] = 2; + await expect( + dcrypto.getMerkleRootFromProof(elementHash, proofWrongPosition), + ).rejects.toThrow("Proof artifact position is neither left nor right."); + }); + + test("Merkle proof verification works for odd number of elements.", async () => { + const tree: Uint8Array[] = []; + const element = new Uint8Array(128); + const elements = 201; + const elementIndex = 99; + for (let i = 0; i < elements; i++) { + const rand = await dcrypto.randomBytes(128); + + if (i === elementIndex) element.set([...rand]); + + tree.push(rand); + } + + const root = await dcrypto.getMerkleRoot(tree); + + const proof = await dcrypto.getMerkleProof(tree, tree[elementIndex]); + + const elementHash = await dcrypto.sha512(element); + + const verification = await dcrypto.verifyMerkleProof( + elementHash, + root, + proof, + ); + + expect(verification).toBe(true); + }); + + test("Merkle proof verification works for even number of elements.", async () => { + const tree: Uint8Array[] = []; + const element = new Uint8Array(128); + const elements = 200; + const elementIndex = 99; + for (let i = 0; i < elements; i++) { + const rand = await dcrypto.randomBytes(128); + + if (i === elementIndex) element.set([...rand]); + + tree.push(rand); + } + + const root = await dcrypto.getMerkleRoot(tree); + + const proof = await dcrypto.getMerkleProof(tree, tree[elementIndex]); + + const elementHash = await dcrypto.sha512(element); + + const verification = await dcrypto.verifyMerkleProof( + elementHash, + root, + proof, + ); + + expect(verification).toBe(true); + }); + + it("Should throw an error when faced with false data.", async () => { + const tree: Uint8Array[] = []; + const element = new Uint8Array(128); + const elements = 201; + const elementIndex = 99; + for (let i = 0; i < elements; i++) { + const rand = await dcrypto.randomBytes(128); + + if (i === elementIndex) element.set([...rand]); + + tree.push(rand); + } + + const root = await dcrypto.getMerkleRoot(tree); + const proof = await dcrypto.getMerkleProof(tree, tree[elementIndex]); + const elementHash = await dcrypto.sha512(element); + + await expect( + dcrypto.verifyMerkleProof( + elementHash, + root, + proof.slice(0, proof.length - 1), + ), + ).rejects.toThrow("Proof length not multiple of 65."); + + const proofWrongPosition = Uint8Array.from([...proof]); + proofWrongPosition[dcrypto.constants.crypto_hash_sha512_BYTES] = 2; + await expect( + dcrypto.verifyMerkleProof(elementHash, root, proofWrongPosition), + ).rejects.toThrow("Proof artifact position is neither left nor right."); + + const proofWrongByte = Uint8Array.from([...proof]); + proofWrongByte[1] = proof[1] === 255 ? 254 : proof[1] + 1; + + const verification = await dcrypto.verifyMerkleProof( + elementHash, + root, + proofWrongByte, + ); + + expect(verification).toBe(false); + }); + + const len = 64; + const arr1 = new Uint8Array(len); + const arr2 = new Uint8Array(len); + arr1.fill(1); + arr2.fill(2); + + const arr3 = new Uint8Array(len); + arr3.fill(1); + + const arr4 = new Uint8Array(len); + arr4.fill(4); + + const arrayOfArrays1: Uint8Array[] = []; + arrayOfArrays1.push(arr2); + arrayOfArrays1.push(arr4); + arrayOfArrays1.push(arr2); + + interface SomeRandomInterface { + val1: string; + val2: string; + val3: string; + } + + const arr5: SomeRandomInterface = { + val1: "1", + val2: "2", + val3: "3", + }; + + const arr6: SomeRandomInterface = { + val1: "5", + val2: "6", + val3: "7", + }; + + const arr7: SomeRandomInterface = { + val1: "10", + val2: "20", + val3: "30", + }; + + const arrayOfArrays3: SomeRandomInterface[] = [arr5, arr6, arr7]; + + const numberToUint8Array = (n: number): Uint8Array => { + return Uint8Array.of( + (n & 0xff000000) >> 24, + (n & 0x00ff0000) >> 16, + (n & 0x0000ff00) >> 8, + (n & 0x000000ff) >> 0, + ); + }; + + const someRandomInterfaceSerializer = (item: SomeRandomInterface) => { + const uint8 = new Uint8Array(4 * 3 * Uint8Array.BYTES_PER_ELEMENT); + + uint8.set(numberToUint8Array(Number(item.val1))); + uint8.set( + numberToUint8Array(Number(item.val2)), + 4 * Uint8Array.BYTES_PER_ELEMENT, + ); + uint8.set( + numberToUint8Array(Number(item.val3)), + 8 * Uint8Array.BYTES_PER_ELEMENT, + ); + + return uint8; + }; + + it("Should be possible to get Merkle root and proof from non-Uint8 data.", async () => { + const root = await dcrypto.getMerkleRoot( + arrayOfArrays3, + someRandomInterfaceSerializer, + ); + + const arr6Serialized = someRandomInterfaceSerializer(arr6); + const proof1 = await dcrypto.getMerkleProof( + arrayOfArrays3, + arr6Serialized, + someRandomInterfaceSerializer, + ); + + const arrayOfArrays3Serialized: Uint8Array[] = []; + for (let i = 0; i < arrayOfArrays3.length; i++) { + arrayOfArrays3Serialized.push( + someRandomInterfaceSerializer(arrayOfArrays3[i]), + ); + } + const proof2 = await dcrypto.getMerkleProof( + arrayOfArrays3Serialized, + arr6, + someRandomInterfaceSerializer, + ); + + const elementHash = await dcrypto.sha512(arr6Serialized); + const verification1 = await dcrypto.verifyMerkleProof( + elementHash, + root, + proof1, + ); + + const verification2 = await dcrypto.verifyMerkleProof( + elementHash, + root, + proof2, + ); + + expect(verification1).toBe(true); + expect(verification2).toBe(true); + + const root1 = await dcrypto.getMerkleRoot( + [arr6], + someRandomInterfaceSerializer, + ); + expect(root1.length).toBe(64); + + const root2 = await dcrypto.getMerkleRoot([arr6Serialized]); + expect(root2.length).toBe(64); + }); + + it("Should be possible to get Merkle root for one-element arrays.", async () => { + const proof = await dcrypto.getMerkleProof( + [arr6], + arr6, + someRandomInterfaceSerializer, + ); + + expect(proof).toStrictEqual( + new Uint8Array(dcrypto.constants.crypto_hash_sha512_BYTES + 1).fill(1), + ); + + const root = await dcrypto.getMerkleRoot( + [arr6], + someRandomInterfaceSerializer, + ); + + const arr6Serialized = someRandomInterfaceSerializer(arr6); + const elementHash = await dcrypto.sha512(arr6Serialized); + expect(elementHash).toStrictEqual(root); + + const rootFromProof = await dcrypto.getMerkleRootFromProof( + elementHash, + proof, + ); + expect(rootFromProof).toStrictEqual(root); + + const verification = await dcrypto.verifyMerkleProof( + elementHash, + root, + proof, + ); + + expect(verification).toBe(true); + }); + + it("Should throw errors when trying to get merkle root with wrong data.", async () => { + await expect( + dcrypto.getMerkleRoot([], someRandomInterfaceSerializer), + ).rejects.toThrow("Cannot calculate Merkle root of tree with no leaves."); + + await expect(dcrypto.getMerkleRoot([arr6])).rejects.toThrow( + "Tree leaf not Uint8Array, needs serializer.", + ); + + await expect(dcrypto.getMerkleRoot(arrayOfArrays3)).rejects.toThrow( + "Tree leaf not Uint8Array, needs serializer.", + ); + }); + + it("Should throw errors when trying to get merkle proof with wrong data.", async () => { + await expect( + dcrypto.getMerkleProof([], arr6, someRandomInterfaceSerializer), + ).rejects.toThrow( + "Cannot calculate Merkle proof of element of empty tree.", + ); + + await expect(dcrypto.getMerkleProof(arrayOfArrays3, arr6)).rejects.toThrow( + "It is mandatory to provide a serializer for non-Uint8Array items", + ); + }); +}); diff --git a/package-lock.json b/package-lock.json index 23c1236..628026b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@deliberative/crypto", - "version": "0.6.9", + "version": "0.6.10", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@deliberative/crypto", - "version": "0.6.9", + "version": "0.6.10", "license": "Apache-2.0", "devDependencies": { "@rollup/plugin-commonjs": "^23.0.0", @@ -1549,9 +1549,9 @@ } }, "node_modules/@types/jest": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.1.2.tgz", - "integrity": "sha512-y+nlX0h87U0R+wsGn6EBuoRWYyv3KFtwRNP3QWp9+k2tJ2/bqcGS3UxD7jgT+tiwJWWq3UsyV4Y+T6rsMT4XMg==", + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.0.tgz", + "integrity": "sha512-KO7bPV21d65PKwv3LLsD8Jn3E05pjNjRZvkm+YTacWhVmykAb07wW6IkZUmQAltwQafNcDUEUrMO2h3jeBSisg==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -2289,9 +2289,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001420", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001420.tgz", - "integrity": "sha512-OnyeJ9ascFA9roEj72ok2Ikp7PHJTKubtEJIQ/VK3fdsS50q4KWy+Z5X0A1/GswEItKX0ctAp8n4SYDE7wTu6A==", + "version": "1.0.30001421", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001421.tgz", + "integrity": "sha512-Sw4eLbgUJAEhjLs1Fa+mk45sidp1wRn5y6GtDpHGBaNJ9OCDJaVh2tIaWWUnGfuXfKf1JCBaIarak3FkVAvEeA==", "dev": true, "funding": [ { @@ -2653,9 +2653,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.283", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.283.tgz", - "integrity": "sha512-g6RQ9zCOV+U5QVHW9OpFR7rdk/V7xfopNXnyAamdpFgCHgZ1sjI8VuR1+zG2YG/TZk+tQ8mpNkug4P8FU0fuOA==", + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", "dev": true }, "node_modules/emittery": { @@ -3096,9 +3096,9 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "39.3.12", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.3.12.tgz", - "integrity": "sha512-wgfDKG1nIsGKP9/3Y/J8WfBPiDMEx3N+/79szvdaZbwBlL4CvoYK/zgeg9cBgoz4MflKd5u1VHQZTTLQwYNQ2Q==", + "version": "39.3.13", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.3.13.tgz", + "integrity": "sha512-yF16kYmoz8pcEZXxX2kdaBwWFvXrUpxuF+ZgG/0PKLKcT9lGKFi4Mn0Mk/KqJeMgUprFDCzNTjnzYGf8tdNrAA==", "dev": true, "dependencies": { "@es-joy/jsdoccomment": "~0.32.0", @@ -6398,9 +6398,9 @@ } }, "node_modules/rollup": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.2.2.tgz", - "integrity": "sha512-tw8NITEB/A8aa8F+mmIJ7fQ7Abej0R9ugR1ZzsCqb7P8HWVIVdneN69BMTDjhk0qbUsewDSJSDTcVuCTogs8JA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.2.3.tgz", + "integrity": "sha512-qfadtkY5kl0F5e4dXVdj2D+GtOdifasXHFMiL1SMf9ADQDv5Eti6xReef9FKj+iQPR2pvtqWna57s/PjARY4fg==", "dev": true, "bin": { "rollup": "dist/bin/rollup" @@ -7081,9 +7081,9 @@ } }, "node_modules/typedoc": { - "version": "0.23.16", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.23.16.tgz", - "integrity": "sha512-rumYsCeNRXlyuZVzefD7050n7ptL2uudsCJg50dY0v/stKniqIlRpvx/F/6expC0/Q6Dbab+g/JpZuB7Sw90FA==", + "version": "0.23.17", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.23.17.tgz", + "integrity": "sha512-3rtNubo1dK0pvs6ixpMAq4pESULd5/JNUqJbdyZoeilI14reb1RNVomN4fMgIadd0RMX1aenYjJSSMBOJ+/+0Q==", "dev": true, "dependencies": { "lunr": "^2.3.9", @@ -8610,9 +8610,9 @@ } }, "@types/jest": { - "version": "29.1.2", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.1.2.tgz", - "integrity": "sha512-y+nlX0h87U0R+wsGn6EBuoRWYyv3KFtwRNP3QWp9+k2tJ2/bqcGS3UxD7jgT+tiwJWWq3UsyV4Y+T6rsMT4XMg==", + "version": "29.2.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.0.tgz", + "integrity": "sha512-KO7bPV21d65PKwv3LLsD8Jn3E05pjNjRZvkm+YTacWhVmykAb07wW6IkZUmQAltwQafNcDUEUrMO2h3jeBSisg==", "dev": true, "requires": { "expect": "^29.0.0", @@ -9136,9 +9136,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001420", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001420.tgz", - "integrity": "sha512-OnyeJ9ascFA9roEj72ok2Ikp7PHJTKubtEJIQ/VK3fdsS50q4KWy+Z5X0A1/GswEItKX0ctAp8n4SYDE7wTu6A==", + "version": "1.0.30001421", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001421.tgz", + "integrity": "sha512-Sw4eLbgUJAEhjLs1Fa+mk45sidp1wRn5y6GtDpHGBaNJ9OCDJaVh2tIaWWUnGfuXfKf1JCBaIarak3FkVAvEeA==", "dev": true }, "chalk": { @@ -9409,9 +9409,9 @@ } }, "electron-to-chromium": { - "version": "1.4.283", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.283.tgz", - "integrity": "sha512-g6RQ9zCOV+U5QVHW9OpFR7rdk/V7xfopNXnyAamdpFgCHgZ1sjI8VuR1+zG2YG/TZk+tQ8mpNkug4P8FU0fuOA==", + "version": "1.4.284", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz", + "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", "dev": true }, "emittery": { @@ -9754,9 +9754,9 @@ } }, "eslint-plugin-jsdoc": { - "version": "39.3.12", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.3.12.tgz", - "integrity": "sha512-wgfDKG1nIsGKP9/3Y/J8WfBPiDMEx3N+/79szvdaZbwBlL4CvoYK/zgeg9cBgoz4MflKd5u1VHQZTTLQwYNQ2Q==", + "version": "39.3.13", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-39.3.13.tgz", + "integrity": "sha512-yF16kYmoz8pcEZXxX2kdaBwWFvXrUpxuF+ZgG/0PKLKcT9lGKFi4Mn0Mk/KqJeMgUprFDCzNTjnzYGf8tdNrAA==", "dev": true, "requires": { "@es-joy/jsdoccomment": "~0.32.0", @@ -12202,9 +12202,9 @@ } }, "rollup": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.2.2.tgz", - "integrity": "sha512-tw8NITEB/A8aa8F+mmIJ7fQ7Abej0R9ugR1ZzsCqb7P8HWVIVdneN69BMTDjhk0qbUsewDSJSDTcVuCTogs8JA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.2.3.tgz", + "integrity": "sha512-qfadtkY5kl0F5e4dXVdj2D+GtOdifasXHFMiL1SMf9ADQDv5Eti6xReef9FKj+iQPR2pvtqWna57s/PjARY4fg==", "dev": true, "requires": { "fsevents": "~2.3.2" @@ -12714,9 +12714,9 @@ "dev": true }, "typedoc": { - "version": "0.23.16", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.23.16.tgz", - "integrity": "sha512-rumYsCeNRXlyuZVzefD7050n7ptL2uudsCJg50dY0v/stKniqIlRpvx/F/6expC0/Q6Dbab+g/JpZuB7Sw90FA==", + "version": "0.23.17", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.23.17.tgz", + "integrity": "sha512-3rtNubo1dK0pvs6ixpMAq4pESULd5/JNUqJbdyZoeilI14reb1RNVomN4fMgIadd0RMX1aenYjJSSMBOJ+/+0Q==", "dev": true, "requires": { "lunr": "^2.3.9", diff --git a/package.json b/package.json index b128ec3..345324b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@deliberative/crypto", "description": "Libsodium and Shamir secret sharing wasm module for nodejs and the browser.", - "version": "0.6.9", + "version": "0.6.10", "repository": { "type": "git", "url": "https://github.com/deliberative/crypto.git" diff --git a/scripts/compileMethods.js b/scripts/compileMethods.js index e7c8a05..17f6589 100644 --- a/scripts/compileMethods.js +++ b/scripts/compileMethods.js @@ -85,6 +85,7 @@ _random_bytes,\ _random_number_in_range,\ _get_merkle_root,\ _get_merkle_proof,\ +_get_merkle_root_from_proof,\ _verify_merkle_proof \ -s EXPORT_NAME=dcryptoMethodsModule \ -I${libsodiumIncludePath} \ diff --git a/scripts/dcryptoMethodsModule.d.ts b/scripts/dcryptoMethodsModule.d.ts index e55e6d8..a1082af 100644 --- a/scripts/dcryptoMethodsModule.d.ts +++ b/scripts/dcryptoMethodsModule.d.ts @@ -115,6 +115,12 @@ export interface DCryptoMethodsModule extends EmscriptenModule { element_hash: number, // Uint8Array.byteOffset proof: number, // Uint8Array.byteOffset ): number; + _get_merkle_root_from_proof( + PROOF_LEN: number, + element_hash: number, // Uint8Array.byteOffset + proof: number, // Uint8Array.byteOffset + root: number, // Uint8Array.byteOffset + ): number; _verify_merkle_proof( PROOF_LEN: number, element_hash: number, // Uint8Array.byteOffset diff --git a/src/c/dcrypto.c b/src/c/dcrypto.c index d8743a5..2658622 100644 --- a/src/c/dcrypto.c +++ b/src/c/dcrypto.c @@ -704,6 +704,73 @@ get_merkle_proof( return k * (crypto_hash_sha512_BYTES + 1); } +__attribute__((used)) int +get_merkle_root_from_proof(const int PROOF_LEN, + const uint8_t element_hash[crypto_hash_sha512_BYTES], + const uint8_t proof[PROOF_LEN], + uint8_t root[crypto_hash_sha512_BYTES]) +{ + if (PROOF_LEN % (crypto_hash_sha512_BYTES + 1) != 0) return -1; + int NODES_LEN = PROOF_LEN / (crypto_hash_sha512_BYTES + 1); + + memcpy(root, element_hash, crypto_hash_sha512_BYTES); + + if (NODES_LEN == 1) + { + bool isOne = true; + for (int i = 0; i < crypto_hash_sha512_BYTES + 1; i++) + { + if (proof[i] != 1) + { + isOne = false; + break; + } + } + + // Single element tree + if (isOne) return 0; + } + + size_t i, position; + + uint8_t *concat_hashes = malloc(2 * crypto_hash_sha512_BYTES); + + for (i = 0; i < NODES_LEN; i++) + { + position + = proof[i * (crypto_hash_sha512_BYTES + 1) + crypto_hash_sha512_BYTES]; + if (position != 0 && position != 1) + { + free(concat_hashes); + + return -1; + } + + // Proof artifact goes to the left + if (position == 0) + { + memcpy(concat_hashes, &proof[i * (crypto_hash_sha512_BYTES + 1)], + crypto_hash_sha512_BYTES); + memcpy(&concat_hashes[crypto_hash_sha512_BYTES], root, + crypto_hash_sha512_BYTES); + } + else + { + memcpy(concat_hashes, root, crypto_hash_sha512_BYTES); + memcpy(&concat_hashes[crypto_hash_sha512_BYTES], + &proof[i * (crypto_hash_sha512_BYTES + 1)], + crypto_hash_sha512_BYTES); + } + + int res + = crypto_hash_sha512(root, concat_hashes, 2 * crypto_hash_sha512_BYTES); + } + + free(concat_hashes); + + return 0; +} + __attribute__((used)) int verify_merkle_proof(const int PROOF_LEN, const uint8_t element_hash[crypto_hash_sha512_BYTES], @@ -715,6 +782,30 @@ verify_merkle_proof(const int PROOF_LEN, size_t i, position; + if (NODES_LEN == 1) + { + bool isOne = true; + for (i = 0; i < crypto_hash_sha512_BYTES + 1; i++) + { + if (proof[i] != 1) + { + isOne = false; + break; + } + } + + // Single element tree + if (isOne) + { + for (i = 0; i < crypto_hash_sha512_BYTES; i++) + { + if (element_hash[i] != root[i]) return 1; + } + + return 0; + } + } + uint8_t *hash = malloc(crypto_hash_sha512_BYTES); memcpy(hash, element_hash, crypto_hash_sha512_BYTES); uint8_t *concat_hashes = malloc(2 * crypto_hash_sha512_BYTES); diff --git a/src/hash/index.ts b/src/hash/index.ts index 8e976bb..d113da5 100644 --- a/src/hash/index.ts +++ b/src/hash/index.ts @@ -14,15 +14,11 @@ // limitations under the License. import sha512 from "./sha512"; -import getMerkleRoot from "./getMerkleRoot"; -import getMerkleProof from "./getMerkleProof"; -import verifyMerkleProof from "./verifyMerkleProof"; +import argon2 from "./argon2"; import memory from "./memory"; export default { sha512, - getMerkleRoot, - getMerkleProof, - verifyMerkleProof, + argon2, memory, }; diff --git a/src/hash/memory.ts b/src/hash/memory.ts index 42ec367..92bf3ac 100644 --- a/src/hash/memory.ts +++ b/src/hash/memory.ts @@ -40,40 +40,7 @@ const argon2Memory = (mnemonicLen: number): WebAssembly.Memory => { return new WebAssembly.Memory({ initial: pages, maximum: pages }); }; -const getMerkleRootMemory = (leavesLen: number): WebAssembly.Memory => { - const memoryLen = (2 * leavesLen + 3) * crypto_hash_sha512_BYTES; - const memoryPages = memoryLenToPages(memoryLen); - - return new WebAssembly.Memory({ - initial: memoryPages, - maximum: memoryPages, - }); -}; - -const getMerkleProofMemory = (leavesLen: number): WebAssembly.Memory => { - const memoryLen = (3 * leavesLen + 4) * crypto_hash_sha512_BYTES + leavesLen; - const memoryPages = memoryLenToPages(memoryLen); - - return new WebAssembly.Memory({ - initial: memoryPages, - maximum: memoryPages, - }); -}; - -const verifyMerkleProofMemory = (proofLen: number): WebAssembly.Memory => { - const memoryLen = proofLen + 5 * crypto_hash_sha512_BYTES; - const memoryPages = memoryLenToPages(memoryLen); - - return new WebAssembly.Memory({ - initial: memoryPages, - maximum: memoryPages, - }); -}; - export default { sha512Memory, argon2Memory, - getMerkleRootMemory, - getMerkleProofMemory, - verifyMerkleProofMemory, }; diff --git a/src/index.ts b/src/index.ts index 9fd2380..2bc4652 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,7 @@ import asymmetric from "./asymmetric"; import symmetric from "./symmetric"; import mnemonic from "./mnemonic"; import hash from "./hash"; +import merkle from "./merkle"; import shamir from "./shamir"; import utils from "./utils"; @@ -40,9 +41,12 @@ const dcrypto = { decryptSymmetricKey: symmetric.decrypt, sha512: hash.sha512, - getMerkleRoot: hash.getMerkleRoot, - getMerkleProof: hash.getMerkleProof, - verifyMerkleProof: hash.verifyMerkleProof, + argon2: hash.argon2, + + getMerkleRoot: merkle.getMerkleRoot, + getMerkleProof: merkle.getMerkleProof, + getMerkleRootFromProof: merkle.getMerkleRootFromProof, + verifyMerkleProof: merkle.verifyMerkleProof, splitSecret: shamir.splitSecret, restoreSecret: shamir.restoreSecret, @@ -77,9 +81,11 @@ const dcrypto = { decryptSymmetricKey: symmetric.memory.decryptMemory, sha512: hash.memory.sha512Memory, - getMerkleRoot: hash.memory.getMerkleRootMemory, - getMerkleProof: hash.memory.getMerkleProofMemory, - verifyMerkleProof: hash.memory.verifyMerkleProofMemory, + argon2: hash.memory.argon2Memory, + + getMerkleRoot: merkle.memory.getMerkleRootMemory, + getMerkleProof: merkle.memory.getMerkleProofMemory, + verifyMerkleProof: merkle.memory.verifyMerkleProofMemory, splitSecret: shamir.memory.splitSecretMemory, restoreSecret: shamir.memory.restoreSecretMemory, diff --git a/src/hash/getMerkleProof.ts b/src/merkle/getMerkleProof.ts similarity index 94% rename from src/hash/getMerkleProof.ts rename to src/merkle/getMerkleProof.ts index b569e8e..f46e3d3 100644 --- a/src/hash/getMerkleProof.ts +++ b/src/merkle/getMerkleProof.ts @@ -13,10 +13,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import sha512 from "./sha512"; - import dcryptoMemory from "./memory"; +import sha512 from "../hash/sha512"; import isUint8Array from "../utils/isUint8Array"; import dcryptoMethodsModule from "../c/build/dcryptoMethodsModule"; @@ -43,9 +42,10 @@ const getMerkleProof = async ( if (treeLen === 0) { throw new Error("Cannot calculate Merkle proof of element of empty tree."); } else if (treeLen === 1) { - throw new Error( - "No point in calculating proof of a tree with single leaf.", - ); + return new Uint8Array(crypto_hash_sha512_BYTES + 1).fill(1); + // throw new Error( + // "No point in calculating proof of a tree with single leaf.", + // ); } const leavesAreUint8Arrays = isUint8Array(tree[0]); diff --git a/src/hash/getMerkleRoot.ts b/src/merkle/getMerkleRoot.ts similarity index 98% rename from src/hash/getMerkleRoot.ts rename to src/merkle/getMerkleRoot.ts index 2d8e59c..c8d5534 100644 --- a/src/hash/getMerkleRoot.ts +++ b/src/merkle/getMerkleRoot.ts @@ -13,10 +13,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import sha512 from "./sha512"; - import dcryptoMemory from "./memory"; +import sha512 from "../hash/sha512"; import isUint8Array from "../utils/isUint8Array"; import dcryptoMethodsModule from "../c/build/dcryptoMethodsModule"; diff --git a/src/merkle/getMerkleRootFromProof.ts b/src/merkle/getMerkleRootFromProof.ts new file mode 100644 index 0000000..0a37357 --- /dev/null +++ b/src/merkle/getMerkleRootFromProof.ts @@ -0,0 +1,94 @@ +// Copyright (C) 2022 Deliberative Technologies P.C. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import dcryptoMemory from "./memory"; + +import dcryptoMethodsModule from "../c/build/dcryptoMethodsModule"; + +import { crypto_hash_sha512_BYTES } from "../utils/interfaces"; + +/** + * @function + * Calculates the Merkle root from the element hash and its Merkle proof. + * + * @param hash: The hash of the base element in question. + * @param proof: The first element is the first leave that was added for the calculation etc. The last + * byte is either 0 or 1, indicating whether it is to the left or to the right in the tree. + * + * @returns The Merkle root + */ +const getMerkleRootFromProof = async ( + hash: Uint8Array, + proof: Uint8Array, +): Promise => { + const proofLen = proof.length; + // if (proofLen % (crypto_hash_sha512_BYTES + 1) !== 0) + // throw new Error("Proof length not multiple of 65."); + + const wasmMemory = dcryptoMemory.verifyMerkleProofMemory(proofLen); + const module = await dcryptoMethodsModule({ + wasmMemory, + }); + + const ptr1 = module._malloc(crypto_hash_sha512_BYTES); + const elementHash = new Uint8Array( + module.HEAP8.buffer, + ptr1, + crypto_hash_sha512_BYTES, + ); + elementHash.set([...hash]); + + const ptr2 = module._malloc(proofLen); + const proofArray = new Uint8Array(module.HEAP8.buffer, ptr2, proofLen); + proofArray.set([...proof]); + + const ptr3 = module._malloc(crypto_hash_sha512_BYTES); + const rootArray = new Uint8Array( + module.HEAP8.buffer, + ptr3, + crypto_hash_sha512_BYTES, + ); + + const result = module._get_merkle_root_from_proof( + proofLen, + elementHash.byteOffset, + proofArray.byteOffset, + rootArray.byteOffset, + ); + + module._free(ptr1); + module._free(ptr2); + + switch (result) { + case 0: { + const proof = Uint8Array.from([...rootArray]); + module._free(ptr3); + + return proof; + } + + case -1: { + module._free(ptr3); + throw new Error("Proof artifact position is neither left nor right."); + } + + default: { + module._free(ptr3); + throw new Error("Unexpected error occured."); + } + } +}; + +export default getMerkleRootFromProof; diff --git a/src/merkle/index.ts b/src/merkle/index.ts new file mode 100644 index 0000000..cad5a97 --- /dev/null +++ b/src/merkle/index.ts @@ -0,0 +1,28 @@ +// Copyright (C) 2022 Deliberative Technologies P.C. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import getMerkleRoot from "./getMerkleRoot"; +import getMerkleProof from "./getMerkleProof"; +import getMerkleRootFromProof from "./getMerkleRootFromProof"; +import verifyMerkleProof from "./verifyMerkleProof"; +import memory from "./memory"; + +export default { + getMerkleRoot, + getMerkleProof, + getMerkleRootFromProof, + verifyMerkleProof, + memory, +}; diff --git a/src/merkle/memory.ts b/src/merkle/memory.ts new file mode 100644 index 0000000..8b6bd30 --- /dev/null +++ b/src/merkle/memory.ts @@ -0,0 +1,54 @@ +// Copyright (C) 2022 Deliberative Technologies P.C. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import memoryLenToPages from "../utils/memoryLenToPages"; + +import { crypto_hash_sha512_BYTES } from "../utils/interfaces"; + +const getMerkleRootMemory = (leavesLen: number): WebAssembly.Memory => { + const memoryLen = (2 * leavesLen + 3) * crypto_hash_sha512_BYTES; + const memoryPages = memoryLenToPages(memoryLen); + + return new WebAssembly.Memory({ + initial: memoryPages, + maximum: memoryPages, + }); +}; + +const getMerkleProofMemory = (leavesLen: number): WebAssembly.Memory => { + const memoryLen = (3 * leavesLen + 4) * crypto_hash_sha512_BYTES + leavesLen; + const memoryPages = memoryLenToPages(memoryLen); + + return new WebAssembly.Memory({ + initial: memoryPages, + maximum: memoryPages, + }); +}; + +const verifyMerkleProofMemory = (proofLen: number): WebAssembly.Memory => { + const memoryLen = proofLen + 5 * crypto_hash_sha512_BYTES; + const memoryPages = memoryLenToPages(memoryLen); + + return new WebAssembly.Memory({ + initial: memoryPages, + maximum: memoryPages, + }); +}; + +export default { + getMerkleRootMemory, + getMerkleProofMemory, + verifyMerkleProofMemory, +}; diff --git a/src/hash/verifyMerkleProof.ts b/src/merkle/verifyMerkleProof.ts similarity index 100% rename from src/hash/verifyMerkleProof.ts rename to src/merkle/verifyMerkleProof.ts