From ff4a71f0bafc63310f2f60ba101b0301a2ae56aa Mon Sep 17 00:00:00 2001 From: Marvin Date: Mon, 19 Feb 2018 00:26:21 +0100 Subject: [PATCH] add sign, verify and block creation functions --- .gitignore | 3 +- __tests__/common/data.js | 12 ++- __tests__/hash.js | 136 +++++++++++++++++++++++- __tests__/keys.js | 13 ++- __tests__/signature.js | 99 +++++++++++++++--- __tests__/work.js | 18 ++-- examples/multi-threaded/worker.js | 2 +- package.json | 7 +- rollup.config.js | 10 +- src/index.js | 166 ++++++++++++++++++++++-------- src/native/functions.cpp | 108 +++++++++++-------- tmp/.gitkeep | 0 12 files changed, 448 insertions(+), 126 deletions(-) create mode 100644 tmp/.gitkeep diff --git a/.gitignore b/.gitignore index 9923514..471a3fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ ### App ### /dist/ -/native.js +/tmp/* +!/tmp/.gitkeep ### Node ### diff --git a/__tests__/common/data.js b/__tests__/common/data.js index 2b2f3a8..747b8d5 100644 --- a/__tests__/common/data.js +++ b/__tests__/common/data.js @@ -34,7 +34,7 @@ const INVALID_WORKS = [ 'z000000000995bc3' ] -const INVALID_ACCOUNTS = [ +const INVALID_ADDRESSES = [ 12, 'zrb_1mbj7xi6yrwcuwetzd5535pdqjea5rfpsoqo9nw4gxg8itycgntucp49i1nz', 'xrb_2mbj7xi6yrwcuwetzd5535pdqjea5rfpsoqo9nw4gxg8itycgntucp49i1nz', @@ -49,6 +49,12 @@ const INVALID_AMOUNTS = [ '-1' ] +const INVALID_SIGNATURES = [ + 12, + '974324f8cc42da56f62fc212a17886bdcb18de363d04da84eedc99cb4a33919d14a2cf9de9d534faa6d0b91d01f0622205d898293525e692586c84f2dcf9208', + 'z974324f8cc42da56f62fc212a17886bdcb18de363d04da84eedc99cb4a33919d14a2cf9de9d534faa6d0b91d01f0622205d898293525e692586c84f2dcf9208' +] + module.exports = { INVALID_SEEDS, INVALID_INDEXES, @@ -56,5 +62,7 @@ module.exports = { INVALID_PUBLIC_KEYS, INVALID_HASHES, INVALID_WORKS, - INVALID_ACCOUNTS + INVALID_ADDRESSES, + INVALID_AMOUNTS, + INVALID_SIGNATURES } diff --git a/__tests__/hash.js b/__tests__/hash.js index 25745d2..cf98b08 100644 --- a/__tests__/hash.js +++ b/__tests__/hash.js @@ -4,7 +4,7 @@ const nano = require('../dist/nanocurrency.cjs') const { INVALID_HASHES, INVALID_ADDRESSES, - INVALID_BALANCES + INVALID_AMOUNTS } = require('./common/data') const VALID_SEND_BLOCK = { @@ -38,45 +38,171 @@ beforeAll(nano.init) describe('send', () => { test('creates correct send block', () => { expect( - nano.computeSendBlockHash( + nano.hashSendBlock( VALID_SEND_BLOCK.previous, VALID_SEND_BLOCK.destination, VALID_SEND_BLOCK.balance ) ).toBe(VALID_SEND_BLOCK.hash) }) + + test('throws with invalid previous', () => { + expect.assertions(INVALID_HASHES.length) + for (let invalidHash of INVALID_HASHES) { + expect( + () => nano.hashSendBlock( + invalidHash, + VALID_SEND_BLOCK.destination, + VALID_SEND_BLOCK.balance + ) + ).toThrowError('Previous is not valid') + } + }) + + test('throws with invalid destination', () => { + expect.assertions(INVALID_ADDRESSES.length) + for (let invalidAddress of INVALID_ADDRESSES) { + expect( + () => nano.hashSendBlock( + VALID_SEND_BLOCK.previous, + invalidAddress, + VALID_SEND_BLOCK.balance + ) + ).toThrowError('Destination is not valid') + } + }) + + test('throws with invalid balance', () => { + expect.assertions(INVALID_AMOUNTS.length) + for (let invalidAmount of INVALID_AMOUNTS) { + expect( + () => nano.hashSendBlock( + VALID_SEND_BLOCK.previous, + VALID_SEND_BLOCK.destination, + invalidAmount + ) + ).toThrowError('Balance is not valid') + } + }) }) describe('open', () => { test('creates correct open block', () => { expect( - nano.computeOpenBlockHash( + nano.hashOpenBlock( VALID_OPEN_BLOCK.source, VALID_OPEN_BLOCK.representative, VALID_OPEN_BLOCK.account ) ).toBe(VALID_OPEN_BLOCK.hash) }) + + test('throws with invalid source', () => { + expect.assertions(INVALID_HASHES.length) + for (let invalidHash of INVALID_HASHES) { + expect( + () => nano.hashOpenBlock( + invalidHash, + VALID_OPEN_BLOCK.representative, + VALID_OPEN_BLOCK.account + ) + ).toThrowError('Source is not valid') + } + }) + + test('throws with invalid representative', () => { + expect.assertions(INVALID_ADDRESSES.length) + for (let invalidAddress of INVALID_ADDRESSES) { + expect( + () => nano.hashOpenBlock( + VALID_OPEN_BLOCK.source, + invalidAddress, + VALID_OPEN_BLOCK.account + ) + ).toThrowError('Representative is not valid') + } + }) + + test('throws with invalid account', () => { + expect.assertions(INVALID_ADDRESSES.length) + for (let invalidAddress of INVALID_ADDRESSES) { + expect( + () => nano.hashOpenBlock( + VALID_OPEN_BLOCK.source, + VALID_OPEN_BLOCK.representative, + invalidAddress + ) + ).toThrowError('Account is not valid') + } + }) }) describe('change', () => { test('creates correct change block', () => { expect( - nano.computeChangeBlockHash( + nano.hashChangeBlock( VALID_CHANGE_BLOCK.previous, VALID_CHANGE_BLOCK.representative ) ).toBe(VALID_CHANGE_BLOCK.hash) }) + + test('throws with invalid previous', () => { + expect.assertions(INVALID_HASHES.length) + for (let invalidHash of INVALID_HASHES) { + expect( + () => nano.hashChangeBlock( + invalidHash, + VALID_CHANGE_BLOCK.representative + ) + ).toThrowError('Previous is not valid') + } + }) + + test('throws with invalid representative', () => { + expect.assertions(INVALID_ADDRESSES.length) + for (let invalidAddress of INVALID_ADDRESSES) { + expect( + () => nano.hashChangeBlock( + VALID_CHANGE_BLOCK.previous, + invalidAddress + ) + ).toThrowError('Representative is not valid') + } + }) }) describe('receive', () => { test('creates correct receive block', () => { expect( - nano.computeReceiveBlockHash( + nano.hashReceiveBlock( VALID_RECEIVE_BLOCK.previous, VALID_RECEIVE_BLOCK.source ) ).toBe(VALID_RECEIVE_BLOCK.hash) }) + + test('throws with invalid previous', () => { + expect.assertions(INVALID_HASHES.length) + for (let invalidHash of INVALID_HASHES) { + expect( + () => nano.hashReceiveBlock( + invalidHash, + VALID_RECEIVE_BLOCK.source + ) + ).toThrowError('Previous is not valid') + } + }) + + test('throws with invalid source', () => { + expect.assertions(INVALID_HASHES.length) + for (let invalidHash of INVALID_HASHES) { + expect( + () => nano.hashReceiveBlock( + VALID_RECEIVE_BLOCK.previous, + invalidHash + ) + ).toThrowError('Source is not valid') + } + }) }) diff --git a/__tests__/keys.js b/__tests__/keys.js index f475057..2531ac9 100644 --- a/__tests__/keys.js +++ b/__tests__/keys.js @@ -27,19 +27,23 @@ const KEYS = [ beforeAll(nano.init) describe('seeds', () => { - test('generates different seeds', () => { - expect(nano.generateSeed()).not.toBe(nano.generateSeed()) + test('generates different seeds', async () => { + const seed1 = await nano.generateSeed() + const seed2 = await nano.generateSeed() + expect(seed1).not.toBe(seed2) }) }) describe('secret keys', () => { test('creates correct secret keys', () => { + expect.assertions(KEYS.length) for (let key of KEYS) { expect(nano.computeSecretKey(SEED, key.index)).toBe(key.secretKey) } }) test('throws with invalid seeds', () => { + expect.assertions(INVALID_SEEDS.length) for (let invalidSeed of INVALID_SEEDS) { expect( () => nano.computeSecretKey(invalidSeed, 0) @@ -48,6 +52,7 @@ describe('secret keys', () => { }) test('throws with invalid indexes', () => { + expect.assertions(INVALID_INDEXES.length) for (let invalidIndex of INVALID_INDEXES) { expect( () => nano.computeSecretKey(SEED, invalidIndex) @@ -58,12 +63,14 @@ describe('secret keys', () => { describe('public keys', () => { test('creates correct public keys', () => { + expect.assertions(KEYS.length) for (let key of KEYS) { expect(nano.computePublicKey(key.secretKey)).toBe(key.publicKey) } }) test('throws with invalid secret keys', () => { + expect.assertions(INVALID_SECRET_KEYS.length) for (let invalidSecretKey of INVALID_SECRET_KEYS) { expect( () => nano.computePublicKey(invalidSecretKey) @@ -74,12 +81,14 @@ describe('public keys', () => { describe('addresses', () => { test('creates correct addresses', () => { + expect.assertions(KEYS.length) for (let key of KEYS) { expect(nano.computeAddress(key.publicKey)).toBe(key.address) } }) test('throws with invalid public keys', () => { + expect.assertions(INVALID_PUBLIC_KEYS.length) for (let invalidPublicKey of INVALID_PUBLIC_KEYS) { expect( () => nano.computeAddress(invalidPublicKey) diff --git a/__tests__/signature.js b/__tests__/signature.js index 217e767..af8df77 100644 --- a/__tests__/signature.js +++ b/__tests__/signature.js @@ -1,20 +1,95 @@ /* eslint-env jest */ const nano = require('../dist/nanocurrency.cjs') +const { + INVALID_HASHES, + INVALID_SECRET_KEYS, + INVALID_PUBLIC_KEYS, + INVALID_SIGNATURES +} = require('./common/data') -const VALID_HASH = '7f7122e843b27524f4f1d6bd14aefd1c8f01d36ae8653d37417533c0d4bc2be6' -const VALID_SECRET_KEY = '23b5e95b4c4325ed5af109bfe4acde782dbab0163591d9052963723ae8e72a09' -const VALID_PUBLIC_KEY = '4d312f604f638adf19afac6308ecbbc5881e1b6cd6f53d382775c686bca7535b' -const VALID_SIGNATURE = '4ae39add5ee6d53c81ab8c0281787d6f81b620acca37c24dd4bfdcc85df45db65e86234b2367155382951d52b57d7dc97284b1fc9914db07d5735bd307435300' +const SECRET_KEY = '0000000000000000000000000000000000000000000000000000000000000001' +const PUBLIC_KEY = 'c969ec348895a49e21824e10e6b829edea50ccc26a83ce8986a3b95d12576058' +const HASH = 'f47b23107e5f34b2ce06f562b5c435df72a533251cb414c51b2b62a8f63a00e4' +const SIGNATURE = '5974324f8cc42da56f62fc212a17886bdcb18de363d04da84eedc99cb4a33919d14a2cf9de9d534faa6d0b91d01f0622205d898293525e692586c84f2dcf9208' +const INVALID_SIGNATURE = '8029fcd2f48c685296e525392898d5022260f10d19b0d6aaf435d9ed9fc2a41d91933a4bc99cdee48ad40d363ed81bcdb68871a212cf26f65ad24cc8f4234795' beforeAll(nano.init) -test('signs correctly', () => { - expect( - nano.computeSignature( - VALID_HASH, - VALID_SECRET_KEY, - VALID_PUBLIC_KEY - ) - ).toBe(VALID_SIGNATURE) +describe('sign', () => { + test('signs correctly', () => { + expect( + nano.signBlock( + HASH, + SECRET_KEY + ) + ).toBe(SIGNATURE) + }) + + test('throws with invalid hashes', () => { + expect.assertions(INVALID_HASHES.length) + for (let invalidHash of INVALID_HASHES) { + expect( + () => nano.signBlock(invalidHash, SECRET_KEY) + ).toThrowError('Hash is not valid') + } + }) + + test('throws with invalid secret keys', () => { + expect.assertions(INVALID_SECRET_KEYS.length) + for (let invalidSecretKey of INVALID_SECRET_KEYS) { + expect( + () => nano.signBlock(HASH, invalidSecretKey) + ).toThrowError('Secret key is not valid') + } + }) +}) + +describe('verify', () => { + test('validates correct signature', () => { + expect( + nano.verifyBlock( + HASH, + SIGNATURE, + PUBLIC_KEY + ) + ).toBe(true) + }) + + test('does not validate incorrect signature', () => { + expect( + nano.verifyBlock( + HASH, + INVALID_SIGNATURE, + PUBLIC_KEY + ) + ).toBe(false) + }) + + test('throws with invalid hashes', () => { + expect.assertions(INVALID_HASHES.length) + for (let invalidHash of INVALID_HASHES) { + expect( + () => nano.verifyBlock(invalidHash, SIGNATURE, PUBLIC_KEY) + ).toThrowError('Hash is not valid') + } + }) + + test('throws with invalid signatures', () => { + expect.assertions(INVALID_SIGNATURES.length) + for (let invalidSignature of INVALID_SIGNATURES) { + expect( + () => nano.verifyBlock(HASH, invalidSignature, PUBLIC_KEY) + ).toThrowError('Signature is not valid') + } + }) + + test('throws with invalid public keys', () => { + expect.assertions(INVALID_PUBLIC_KEYS.length) + for (let invalidPublicKey of INVALID_PUBLIC_KEYS) { + expect( + () => nano.verifyBlock(HASH, SIGNATURE, invalidPublicKey) + ).toThrowError('Public key is not valid') + } + }) }) diff --git a/__tests__/work.js b/__tests__/work.js index 42c77ac..6d61b4c 100644 --- a/__tests__/work.js +++ b/__tests__/work.js @@ -30,14 +30,7 @@ describe('validation', () => { }) test('throws with invalid hashes', () => { - for (let invalidHash of INVALID_HASHES) { - expect( - () => nano.validateWork(invalidHash, VALID_WORK.work) - ).toThrowError('Hash is not valid') - } - }) - - test('throws with invalid hashes', () => { + expect.assertions(INVALID_HASHES.length) for (let invalidHash of INVALID_HASHES) { expect( () => nano.validateWork(invalidHash, VALID_WORK.work) @@ -46,6 +39,7 @@ describe('validation', () => { }) test('throws with invalid works', () => { + expect.assertions(INVALID_WORKS.length) for (let invalidWork of INVALID_WORKS) { expect( () => nano.validateWork(VALID_WORK.hash, invalidWork) @@ -56,14 +50,15 @@ describe('validation', () => { describe('generation', () => { test('computes deterministic work', () => { - expect(nano.generateWork(VALID_WORK.hash)) + expect(nano.work(VALID_WORK.hash)) .toBe(VALID_WORK.work) }) test('throws with invalid hashes', () => { + expect.assertions(INVALID_HASHES.length) for (let invalidHash of INVALID_HASHES) { expect( - () => nano.generateWork(invalidHash) + () => nano.work(invalidHash) ).toThrowError('Hash is not valid') } }) @@ -78,9 +73,10 @@ describe('generation', () => { [0, -1], [1, 1] ] + expect.assertions(INVALID_WORKER_PARAMETERS.length) for (let invalidWorkerParameters of INVALID_WORKER_PARAMETERS) { expect( - () => nano.generateWork(VALID_WORK.hash, invalidWorkerParameters[0], invalidWorkerParameters[1]) + () => nano.work(VALID_WORK.hash, invalidWorkerParameters[0], invalidWorkerParameters[1]) ).toThrowError('Worker parameters are not valid') } }) diff --git a/examples/multi-threaded/worker.js b/examples/multi-threaded/worker.js index 4ca3893..418a8c1 100644 --- a/examples/multi-threaded/worker.js +++ b/examples/multi-threaded/worker.js @@ -9,7 +9,7 @@ onmessage = async function ({data}) { postMessage({ type: 'started' }) await NanoCurrency.init() - const work = NanoCurrency.generateWork(blockHash, workerNumber, workerCount) + const work = NanoCurrency.work(blockHash, workerNumber, workerCount) postMessage({ type: 'done', work }) } diff --git a/package.json b/package.json index d778cd7..43ce6b9 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,10 @@ ], "homepage": "https://github.com/marvinroger/nanocurrency-js", "jest": { - "testPathIgnorePatterns": ["/__tests__/common/", "/node_modules/"] + "testPathIgnorePatterns": [ + "/__tests__/common/", + "/node_modules/" + ] }, "keywords": [ "crypto", @@ -45,7 +48,7 @@ "scripts": { "build": "yarn build:native && yarn build:js", "build:js": "rollup -c", - "build:native": "docker run --rm -v $(pwd):/src trzeci/emscripten emcc -o native.js -O3 --closure 1 --llvm-lto 3 -s WASM=1 -s MODULARIZE=1 -s SINGLE_FILE=1 -s 'EXTRA_EXPORTED_RUNTIME_METHODS=[\"cwrap\"]' src/native/functions.cpp src/native/uint128_t/uint128_t.cpp src/native/blake2/ref/blake2b-ref.c src/native/ed25519/src/ge.c src/native/ed25519/src/fe.c src/native/ed25519/src/sc.c src/native/ed25519/src/keypair.c src/native/ed25519/src/sign.c", + "build:native": "docker run --rm -v $(pwd):/src trzeci/emscripten emcc -o tmp/native.js -O3 --closure 1 --llvm-lto 3 -s WASM=1 -s MODULARIZE=1 -s SINGLE_FILE=1 -s 'EXTRA_EXPORTED_RUNTIME_METHODS=[\"cwrap\"]' src/native/functions.cpp src/native/uint128_t/uint128_t.cpp src/native/blake2/ref/blake2b-ref.c src/native/ed25519/src/ge.c src/native/ed25519/src/fe.c src/native/ed25519/src/sc.c src/native/ed25519/src/keypair.c src/native/ed25519/src/sign.c src/native/ed25519/src/verify.c", "lint": "standard", "semantic-release": "semantic-release", "test": "jest", diff --git a/rollup.config.js b/rollup.config.js index 39e0a01..d0b9af4 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -12,14 +12,16 @@ const licenseBanner = ` */ `.trim() +const globals = { fs: 'fs', path: 'path' } + export default [ { input: 'src/index.js', - external: [], + external: ['fs', 'path'], output: [ - { name: 'NanoCurrency', file: pkg.browser, format: 'umd' }, - { file: pkg.main, format: 'cjs' }, - { file: pkg.module, format: 'es' } + { name: 'NanoCurrency', file: pkg.browser, format: 'umd', globals }, + { file: pkg.main, format: 'cjs', globals }, + { file: pkg.module, format: 'es', globals } ], plugins: [ resolve(), diff --git a/src/index.js b/src/index.js index 36465dd..2b78eef 100644 --- a/src/index.js +++ b/src/index.js @@ -3,43 +3,46 @@ * Copyright (c) 2018 Marvin ROGER * Licensed under GPL-3.0 (https://git.io/vAZsK) */ -import Native from '../native' +import Native from '../tmp/native' -const IS_NODE = typeof exports === 'object' && '' + exports === '[object Object]' +const IS_NODE = Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]' let fillRandom = null let instance = null -let _generateWork = null +let _work = null let _validateWork = null let _computeSecretKey = null let _computePublicKey = null let _computeAddress = null -let _computeSignature = null -let _computeReceiveBlockHash = null -let _computeOpenBlockHash = null -let _computeChangeBlockHash = null -let _computeSendBlockHash = null +let _hashReceiveBlock = null +let _hashOpenBlock = null +let _hashChangeBlock = null +let _hashSendBlock = null +let _signBlock = null +let _verifyBlock = null export function init () { return new Promise((resolve, reject) => { try { if (!IS_NODE) { - fillRandom = window.crypto.getRandomValues + fillRandom = self.crypto.getRandomValues // eslint-disable-line } else { - fillRandom = require('crypto').randomFillSync + const {promisify} = require('util') + fillRandom = promisify(require('crypto').randomFill) } Native().then(native => { instance = native - _generateWork = instance.cwrap('emscripten_generate_work', 'string', ['string', 'number', 'number']) + _work = instance.cwrap('emscripten_work', 'string', ['string', 'number', 'number']) _validateWork = instance.cwrap('emscripten_validate_work', 'number', ['string', 'string']) _computeSecretKey = instance.cwrap('emscripten_compute_secret_key', 'string', ['string', 'number']) _computePublicKey = instance.cwrap('emscripten_compute_public_key', 'string', ['string']) _computeAddress = instance.cwrap('emscripten_compute_address', 'string', ['string']) - _computeSignature = instance.cwrap('emscripten_compute_signature', 'string', ['string', 'string', 'string']) - _computeReceiveBlockHash = instance.cwrap('emscripten_compute_receive_block_hash', 'string', ['string', 'string']) - _computeOpenBlockHash = instance.cwrap('emscripten_compute_open_block_hash', 'string', ['string', 'string', 'string']) - _computeChangeBlockHash = instance.cwrap('emscripten_compute_change_block_hash', 'string', ['string', 'string']) - _computeSendBlockHash = instance.cwrap('emscripten_compute_send_block_hash', 'string', ['string', 'string', 'string']) + _hashReceiveBlock = instance.cwrap('emscripten_hash_receive_block', 'string', ['string', 'string']) + _hashOpenBlock = instance.cwrap('emscripten_hash_open_block', 'string', ['string', 'string', 'string']) + _hashChangeBlock = instance.cwrap('emscripten_hash_change_block', 'string', ['string', 'string']) + _hashSendBlock = instance.cwrap('emscripten_hash_send_block', 'string', ['string', 'string', 'string']) + _signBlock = instance.cwrap('emscripten_sign_block', 'string', ['string', 'string']) + _verifyBlock = instance.cwrap('emscripten_verify_block', 'number', ['string', 'string', 'string']) resolve() }) @@ -56,11 +59,11 @@ const checkString = candidate => typeof candidate === 'string' export const checkSeed = hash => checkString(hash) && hash.match(/[0-9a-fA-F]{64}/) export const checkHash = checkSeed export const checkKey = checkSeed -export const checkAddress = address => checkString(address) && address.match(/xrb_[13][0-9a-km-uw-z]/) +export const checkAddress = address => checkString(address) && address.match(/xrb_[13][0-9a-km-uw-z]{59}/) export const checkWork = work => checkString(work) && work.match(/[0-9a-fA-F]{16}/) export const checkSignature = signature => checkString(signature) && signature.match(/[0-9a-fA-F]{128}/) -export function generateWork (blockHash, workerNumber = 0, workerCount = 1) { +export function work (blockHash, workerNumber = 0, workerCount = 1) { checkNotInitialized() if (!checkHash(blockHash)) throw new Error('Hash is not valid') @@ -72,7 +75,7 @@ export function generateWork (blockHash, workerNumber = 0, workerCount = 1) { workerNumber > workerCount - 1 ) throw new Error('Worker parameters are not valid') - const work = _generateWork(blockHash, workerNumber, workerCount) + const work = _work(blockHash, workerNumber, workerCount) return work !== '0000000000000000' ? work : null } @@ -88,11 +91,11 @@ export function validateWork (blockHash, work) { return valid } -export function generateSeed () { +export async function generateSeed () { checkNotInitialized() const seed = new Uint8Array(32) - fillRandom(seed) + await fillRandom(seed) return seed.reduce(function (hex, i) { return hex + ('0' + i.toString(16)).slice(-2) @@ -127,54 +130,131 @@ export function computeAddress (publicKey) { return _computeAddress(publicKey) } -export function computeSignature (blockHash, secretKey, publicKey) { - checkNotInitialized() - - if (!checkHash(blockHash)) throw new Error('Hash is not valid') - if (!checkKey(secretKey)) throw new Error('Secret key is not valid') - if (!checkKey(publicKey)) throw new Error('Public key is not valid') - - return _computeSignature(blockHash, secretKey, publicKey) -} - -export function computeReceiveBlockHash (previous, source) { +export function hashReceiveBlock (previous, source) { checkNotInitialized() if (!checkHash(previous)) throw new Error('Previous is not valid') if (!checkHash(source)) throw new Error('Source is not valid') - return _computeReceiveBlockHash(previous, source) + return _hashReceiveBlock(previous, source) } -export function computeOpenBlockHash (source, representative, account) { +export function hashOpenBlock (source, representative, account) { checkNotInitialized() if (!checkHash(source)) throw new Error('Source is not valid') if (!checkAddress(representative)) throw new Error('Representative is not valid') if (!checkAddress(account)) throw new Error('Account is not valid') - return _computeOpenBlockHash(source, representative, account) + return _hashOpenBlock(source, representative, account) } -export function computeChangeBlockHash (previous, representative) { +export function hashChangeBlock (previous, representative) { checkNotInitialized() if (!checkHash(previous)) throw new Error('Previous is not valid') if (!checkAddress(representative)) throw new Error('Representative is not valid') - return _computeChangeBlockHash(previous, representative) + return _hashChangeBlock(previous, representative) } -export function computeSendBlockHash (previous, destination, amount) { +export function hashSendBlock (previous, destination, balance) { checkNotInitialized() if (!checkHash(previous)) throw new Error('Previous is not valid') if (!checkAddress(destination)) throw new Error('Destination is not valid') - const amountError = new Error('Amount is not valid') - if (amount.length > 39) throw amountError - for (let char of amount) { - if (char < '0' || char > '9') throw amountError + const balanceError = new Error('Balance is not valid') + if (!checkString(balance) || balance.length > 39) throw balanceError + for (let char of balance) { + if (char < '0' || char > '9') throw balanceError + } + + return _hashSendBlock(previous, destination, balance) +} + +export function signBlock (blockHash, secretKey) { + checkNotInitialized() + + if (!checkHash(blockHash)) throw new Error('Hash is not valid') + if (!checkKey(secretKey)) throw new Error('Secret key is not valid') + + return _signBlock(blockHash, secretKey) +} + +export function verifyBlock (blockHash, signature, publicKey) { + checkNotInitialized() + + if (!checkHash(blockHash)) throw new Error('Hash is not valid') + if (!checkSignature(signature)) throw new Error('Signature is not valid') + if (!checkKey(publicKey)) throw new Error('Public key is not valid') + + const valid = _verifyBlock(blockHash, signature, publicKey) === 1 + + return valid +} + +export function createOpenBlock (secretKey, { source, representative, account }) { + const hash = hashOpenBlock(source, representative, account) + const signature = signBlock(hash, secretKey) + + return { + hash, + block: { + type: 'open', + source, + representative, + account, + work: 'TODO', + signature + } + } +} + +export function createReceiveBlock (secretKey, { previous, source }) { + const hash = hashReceiveBlock(previous, source) + const signature = signBlock(hash, secretKey) + + return { + hash, + block: { + type: 'receive', + previous, + source, + work: 'TODO', + signature + } + } +} + +export function createSendBlock (secretKey, { previous, destination, balance }) { + const hash = hashSendBlock(previous, destination, balance) + const signature = signBlock(hash, secretKey) + + return { + hash, + block: { + type: 'send', + previous, + destination, + balance, + work: 'TODO', + signature + } } +} - return _computeSendBlockHash(previous, destination, amount) +export function createChangeBlock (secretKey, { previous, representative }) { + const hash = hashChangeBlock(previous, representative) + const signature = signBlock(hash, secretKey) + + return { + hash, + block: { + type: 'change', + previous, + representative, + work: 'TODO', + signature + } + } } diff --git a/src/native/functions.cpp b/src/native/functions.cpp index 7626bb0..0a72512 100644 --- a/src/native/functions.cpp +++ b/src/native/functions.cpp @@ -174,7 +174,7 @@ bool validate_work(const uint8_t* const block_hash, uint8_t* const work) { const uint64_t MIN_UINT64 = 0x0000000000000000; const uint64_t MAX_UINT64 = 0xffffffffffffffff; -void generate_work(const uint8_t* const block_hash, const uint8_t worker_number, const uint8_t worker_count, uint8_t* const dst) { +void work(const uint8_t* const block_hash, const uint8_t worker_number, const uint8_t worker_count, uint8_t* const dst) { const uint64_t interval = (MAX_UINT64 - MIN_UINT64) / worker_count; const uint64_t lower_bound = MIN_UINT64 + (worker_number * interval); @@ -258,7 +258,7 @@ std::string compute_address(const uint8_t* const public_key) { return address; } -void compute_open_block_hash(const uint8_t* const source, const uint8_t* const representative, const uint8_t* const account, uint8_t* const dst) { +void hash_open_block(const uint8_t* const source, const uint8_t* const representative, const uint8_t* const account, uint8_t* const dst) { blake2b_state hash; blake2b_init(&hash, BLOCK_HASH_LENGTH); @@ -268,7 +268,7 @@ void compute_open_block_hash(const uint8_t* const source, const uint8_t* const r blake2b_final(&hash, dst, BLOCK_HASH_LENGTH); } -void compute_change_block_hash(const uint8_t* const previous, const uint8_t* const representative, uint8_t* const dst) { +void hash_change_block(const uint8_t* const previous, const uint8_t* const representative, uint8_t* const dst) { blake2b_state hash; blake2b_init(&hash, BLOCK_HASH_LENGTH); @@ -277,7 +277,7 @@ void compute_change_block_hash(const uint8_t* const previous, const uint8_t* con blake2b_final(&hash, dst, BLOCK_HASH_LENGTH); } -void compute_send_block_hash(const uint8_t* const previous, const uint8_t* const destination, const uint8_t* const balance, uint8_t* const dst) { +void hash_send_block(const uint8_t* const previous, const uint8_t* const destination, const uint8_t* const balance, uint8_t* const dst) { blake2b_state hash; blake2b_init(&hash, BLOCK_HASH_LENGTH); @@ -287,7 +287,7 @@ void compute_send_block_hash(const uint8_t* const previous, const uint8_t* const blake2b_final(&hash, dst, BLOCK_HASH_LENGTH); } -void compute_receive_block_hash(const uint8_t* const previous, const uint8_t* const source, uint8_t* const dst) { +void hash_receive_block(const uint8_t* const previous, const uint8_t* const source, uint8_t* const dst) { blake2b_state hash; blake2b_init(&hash, BLOCK_HASH_LENGTH); @@ -296,11 +296,19 @@ void compute_receive_block_hash(const uint8_t* const previous, const uint8_t* co blake2b_final(&hash, dst, BLOCK_HASH_LENGTH); } -void compute_signature(const uint8_t* const block_hash, const uint8_t* const secret_key, const uint8_t* const public_key, uint8_t* const dst) { +void sign_block(const uint8_t* const block_hash, const uint8_t* const secret_key, uint8_t* const dst) { + uint8_t public_key[PUBLIC_KEY_LENGTH]; + compute_public_key(secret_key, public_key); + ed25519_sign(dst, block_hash, BLOCK_HASH_LENGTH, public_key, secret_key); } +bool verify_block(const uint8_t* const block_hash, const uint8_t* const signature, const uint8_t* const public_key) { + return ed25519_verify(signature, block_hash, BLOCK_HASH_LENGTH, public_key); +} + +std::string stack_string; extern "C" { EMSCRIPTEN_KEEPALIVE uint8_t emscripten_validate_work(const char* const block_hash_hex, const char* const work_hex) { @@ -319,60 +327,60 @@ extern "C" { } EMSCRIPTEN_KEEPALIVE - char* emscripten_generate_work(const char* const block_hash_hex, const uint8_t worker_number, const uint8_t worker_count) { + const char* emscripten_work(const char* const block_hash_hex, const uint8_t worker_number, const uint8_t worker_count) { const std::string block_hash_hex_string(block_hash_hex); uint8_t block_hash_bytes[BLOCK_HASH_LENGTH]; hex_to_bytes(block_hash_hex_string, block_hash_bytes); - uint8_t work[WORK_LENGTH]; + uint8_t work_[WORK_LENGTH]; for (unsigned int i = 0; i < WORK_LENGTH; i++) { - work[i] = 0; + work_[i] = 0; } - generate_work(block_hash_bytes, worker_number, worker_count, work); - const std::string work_string = bytes_to_hex(work, WORK_LENGTH); + work(block_hash_bytes, worker_number, worker_count, work_); + stack_string = bytes_to_hex(work_, WORK_LENGTH); - return strdup(work_string.c_str()); + return stack_string.c_str(); } EMSCRIPTEN_KEEPALIVE - char* emscripten_compute_secret_key(const char* const seed_hex, const uint32_t index) { + const char* emscripten_compute_secret_key(const char* const seed_hex, const uint32_t index) { const std::string seed_hex_string(seed_hex); uint8_t seed_bytes[SEED_LENGTH]; hex_to_bytes(seed_hex_string, seed_bytes); uint8_t secret_key[SECRET_KEY_LENGTH]; compute_secret_key(seed_bytes, index, secret_key); - const std::string secret_key_string = bytes_to_hex(secret_key, SECRET_KEY_LENGTH); + stack_string = bytes_to_hex(secret_key, SECRET_KEY_LENGTH); - return strdup(secret_key_string.c_str()); + return stack_string.c_str(); } EMSCRIPTEN_KEEPALIVE - char* emscripten_compute_public_key(const char* const secret_key_hex) { + const char* emscripten_compute_public_key(const char* const secret_key_hex) { const std::string secret_key_hex_string(secret_key_hex); uint8_t secret_key_bytes[SECRET_KEY_LENGTH]; hex_to_bytes(secret_key_hex_string, secret_key_bytes); uint8_t public_key[PUBLIC_KEY_LENGTH]; compute_public_key(secret_key_bytes, public_key); - const std::string public_key_string = bytes_to_hex(public_key, PUBLIC_KEY_LENGTH); + stack_string = bytes_to_hex(public_key, PUBLIC_KEY_LENGTH); - return strdup(public_key_string.c_str()); + return stack_string.c_str(); } EMSCRIPTEN_KEEPALIVE - char* emscripten_compute_address(const char* const public_key_hex) { + const char* emscripten_compute_address(const char* const public_key_hex) { const std::string public_key_hex_string(public_key_hex); uint8_t public_key_bytes[PUBLIC_KEY_LENGTH]; hex_to_bytes(public_key_hex_string, public_key_bytes); - const std::string address_string = compute_address(public_key_bytes); + stack_string = compute_address(public_key_bytes); - return strdup(address_string.c_str()); + return stack_string.c_str(); } EMSCRIPTEN_KEEPALIVE - char* emscripten_compute_receive_block_hash(const char* const previous_hex, const char* const source_hex) { + const char* emscripten_hash_receive_block(const char* const previous_hex, const char* const source_hex) { const std::string previous_hex_string(previous_hex); uint8_t previous_bytes[BLOCK_HASH_LENGTH]; hex_to_bytes(previous_hex_string, previous_bytes); @@ -382,14 +390,14 @@ extern "C" { hex_to_bytes(source_hex_string, source_bytes); uint8_t block_hash[BLOCK_HASH_LENGTH]; - compute_receive_block_hash(previous_bytes, source_bytes, block_hash); - const std::string block_hash_string = bytes_to_hex(block_hash, BLOCK_HASH_LENGTH); + hash_receive_block(previous_bytes, source_bytes, block_hash); + stack_string = bytes_to_hex(block_hash, BLOCK_HASH_LENGTH); - return strdup(block_hash_string.c_str()); + return stack_string.c_str(); } EMSCRIPTEN_KEEPALIVE - char* emscripten_compute_open_block_hash(const char* const source_hex, const char* const representative_address, const char* const account_address) { + const char* emscripten_hash_open_block(const char* const source_hex, const char* const representative_address, const char* const account_address) { const std::string source_hex_string(source_hex); uint8_t source_bytes[BLOCK_HASH_LENGTH]; hex_to_bytes(source_hex_string, source_bytes); @@ -401,14 +409,14 @@ extern "C" { compute_public_key_from_address(account_address, account_public_key); uint8_t block_hash[BLOCK_HASH_LENGTH]; - compute_open_block_hash(source_bytes, representative_public_key, account_public_key, block_hash); - const std::string block_hash_string = bytes_to_hex(block_hash, BLOCK_HASH_LENGTH); + hash_open_block(source_bytes, representative_public_key, account_public_key, block_hash); + stack_string = bytes_to_hex(block_hash, BLOCK_HASH_LENGTH); - return strdup(block_hash_string.c_str()); + return stack_string.c_str(); } EMSCRIPTEN_KEEPALIVE - char* emscripten_compute_change_block_hash(const char* const previous_hex, const char* const representative_address) { + const char* emscripten_hash_change_block(const char* const previous_hex, const char* const representative_address) { const std::string previous_hex_string(previous_hex); uint8_t previous_bytes[BLOCK_HASH_LENGTH]; hex_to_bytes(previous_hex_string, previous_bytes); @@ -417,14 +425,14 @@ extern "C" { compute_public_key_from_address(representative_address, representative_public_key); uint8_t block_hash[BLOCK_HASH_LENGTH]; - compute_change_block_hash(previous_bytes, representative_public_key, block_hash); - const std::string block_hash_string = bytes_to_hex(block_hash, BLOCK_HASH_LENGTH); + hash_change_block(previous_bytes, representative_public_key, block_hash); + stack_string = bytes_to_hex(block_hash, BLOCK_HASH_LENGTH); - return strdup(block_hash_string.c_str()); + return stack_string.c_str(); } EMSCRIPTEN_KEEPALIVE - char* emscripten_compute_send_block_hash(const char* const previous_hex, const char* const destination_address, const char* const balance) { + const char* emscripten_hash_send_block(const char* const previous_hex, const char* const destination_address, const char* const balance) { const std::string previous_hex_string(previous_hex); uint8_t previous_bytes[BLOCK_HASH_LENGTH]; hex_to_bytes(previous_hex_string, previous_bytes); @@ -437,15 +445,14 @@ extern "C" { amount_to_bytes(balance_string, balance_bytes); uint8_t block_hash[BLOCK_HASH_LENGTH]; - compute_send_block_hash(previous_bytes, destination_public_key, balance_bytes, block_hash); - const std::string block_hash_string = bytes_to_hex(block_hash, BLOCK_HASH_LENGTH); + hash_send_block(previous_bytes, destination_public_key, balance_bytes, block_hash); + stack_string = bytes_to_hex(block_hash, BLOCK_HASH_LENGTH); - return strdup(block_hash_string.c_str()); + return stack_string.c_str(); } - // TODO: check EMSCRIPTEN_KEEPALIVE - char* emscripten_compute_signature(const char* const block_hash_hex, const char* const secret_key_hex, const char* const public_key_hex) { + const char* emscripten_sign_block(const char* const block_hash_hex, const char* const secret_key_hex) { const std::string block_hash_hex_string(block_hash_hex); uint8_t block_hash_bytes[BLOCK_HASH_LENGTH]; hex_to_bytes(block_hash_hex_string, block_hash_bytes); @@ -454,14 +461,29 @@ extern "C" { uint8_t secret_key_bytes[SECRET_KEY_LENGTH]; hex_to_bytes(secret_key_hex_string, secret_key_bytes); + uint8_t signature[SIGNATURE_LENGTH]; + sign_block(block_hash_bytes, secret_key_bytes, signature); + stack_string = bytes_to_hex(signature, SIGNATURE_LENGTH); + + return stack_string.c_str(); + } + + EMSCRIPTEN_KEEPALIVE + uint8_t emscripten_verify_block(const char* const block_hash_hex, const char* const signature_hex, const char* const public_key_hex) { + const std::string block_hash_hex_string(block_hash_hex); + uint8_t block_hash_bytes[BLOCK_HASH_LENGTH]; + hex_to_bytes(block_hash_hex_string, block_hash_bytes); + + const std::string signature_hex_string(signature_hex); + uint8_t signature_bytes[SIGNATURE_LENGTH]; + hex_to_bytes(signature_hex_string, signature_bytes); + const std::string public_key_hex_string(public_key_hex); uint8_t public_key_bytes[PUBLIC_KEY_LENGTH]; hex_to_bytes(public_key_hex_string, public_key_bytes); - uint8_t signature[SIGNATURE_LENGTH]; - compute_signature(block_hash_bytes, secret_key_bytes, public_key_bytes, signature); - const std::string signature_string = bytes_to_hex(signature, SIGNATURE_LENGTH); + const bool valid = verify_block(block_hash_bytes, signature_bytes, public_key_bytes); - return strdup(signature_string.c_str()); + return valid ? 1 : 0; } } diff --git a/tmp/.gitkeep b/tmp/.gitkeep new file mode 100644 index 0000000..e69de29