]> git.codecow.com Git - nano25519.git/commitdiff
Polish for first release.
authorChris Duncan <chris@zoso.dev>
Sun, 22 Mar 2026 09:48:47 +0000 (02:48 -0700)
committerChris Duncan <chris@zoso.dev>
Sun, 22 Mar 2026 09:48:47 +0000 (02:48 -0700)
Rename project for accuracy. Separate async/sync modules. Add tons of tests. Zero out memory when no longer needed. Reduce global static variables by using WASM linear memory directly.

25 files changed:
AUTHORS.md
CHANGELOG.md
README.md
asconfig.json
esbuild/config.mjs
index.html
package-lock.json
package.json
src/assembly/base.ts [moved from assembly/base.ts with 100% similarity]
src/assembly/blake2b.ts [moved from assembly/blake2b.ts with 97% similarity]
src/assembly/constants.ts [moved from assembly/constants.ts with 100% similarity]
src/assembly/fe.ts [moved from assembly/fe.ts with 100% similarity]
src/assembly/ge.ts [moved from assembly/ge.ts with 100% similarity]
src/assembly/index.ts [moved from assembly/index.ts with 80% similarity]
src/assembly/p.ts [moved from assembly/p.ts with 100% similarity]
src/assembly/sc.ts [moved from assembly/sc.ts with 100% similarity]
src/assembly/tsconfig.json [moved from assembly/tsconfig.json with 100% similarity]
src/assembly/tsconfig.json.license [moved from assembly/tsconfig.json.license with 100% similarity]
src/assembly/utils.ts [moved from assembly/utils.ts with 100% similarity]
src/async.ts [new file with mode: 0644]
src/index.ts [new file with mode: 0644]
src/nano25519.ts [moved from index.ts with 58% similarity]
src/sync.ts [new file with mode: 0644]
test.mjs
tsconfig.json

index f53d78c916b3578929de6298745f83bc8242b0f5..037ceac577018ea11daa76a7a3cfc88613c4a2f3 100644 (file)
@@ -5,7 +5,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
 
 # Authors
 
-[nano-nacl](https://codecow.com/nano-nacl.git)
+[nano25519](https://codecow.com/nano25519.git)
 
 - Chris Duncan <chris@codecow.com> (codecow.com)
 
index e26e7899b98bbf46fe6836f17e5f42403a9b486b..15aeaa64618f7cee935d5e83572ed93d405e953e 100644 (file)
@@ -3,13 +3,21 @@ SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
 SPDX-License-Identifier: GPL-3.0-or-later
 -->
 
+# Changelog
+
 ## v0.0.1
 
 ### Notable Changes
 
-Written in Javascript and AssemblyScript, NanoNaCl leverages WebAssembly to sign
-Nano cryptocurrency transaction blocks and validate block signatures.
+Written in TypeScript and AssemblyScript, nano25519 leverages WebAssembly to
+derive public keys, sign transactions, and validate signatures for Nano
+cryptocurrency.
 
-- Works entirely offline and locally on the device.
+- Fastest available implementation of Ed25519+BLAKE2b required by Nano.
+- Works in both the browser and NodeJS.
+- Optionally import separate modules for fast synchronous functions or
+  non-blocking asynchronous functions.
+- Zero networking. All results are computed entirely offline and locally on the
+  device.
 - Zero external dependencies.
 - Released under the GPLv3 license.
index a7c378fed5d04231b2331b70643ce0ed37ff4baf..f564061034b9c9c82ab0171c783e32d2f9610264 100644 (file)
--- a/README.md
+++ b/README.md
@@ -3,11 +3,11 @@ SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
 SPDX-License-Identifier: GPL-3.0-or-later
 -->
 
-# nano-nacl
+# nano25519
 
 _Block signing and validation for Nano cryptocurrency using WebAssembly._
 
-NanoNaCl is an AssemblyScript implementation of select Ed25519 functions used by
+nano25519 is an AssemblyScript implementation of select Ed25519 functions used by
 the Nano cryptocurrency. It is modified to hash using BLAKE2b instead of SHA-512
 and can derive public keys from private keys, sign blocks, and verify block
 signatures. All processing takes place client-side and offline.
@@ -24,28 +24,28 @@ For more information about the block format defined for hashing and signing, see
 ## Installation
 
 ```console
-npm i nano-nacl
+npm i nano25519
 ```
 
 ### Browser
 
-NanoNaCl can be used directly on a webpage with a script module:
+nano25519 can be used directly on a webpage with a script module:
 
 ```html
 <script type="module">
   (async () => {
-    const NanoNaCl = await import("https://unpkg.com/nano-nacl@latest");
-    const { derive, sign, verify } = NanoNacl;
+    const nano25519 = await import("https://unpkg.com/nano25519@latest");
+    const { derive, sign, verify } = nano25519;
   })();
 </script>
 ```
 
 ### NodeJS
 
-NanoNaCl can be imported into NodeJS projects:
+nano25519 can be imported into NodeJS projects:
 
 ```javascript
-import { derive, sign, verify } from "nano-nacl";
+import { derive, sign, verify } from "nano25519";
 ```
 
 ## Usage
@@ -62,7 +62,7 @@ versions require hex strings and return the same.
 ```javascript
 // `prv` is a 32-byte Uint8Array private key
 const prv = new Uint8Array(32);
-const pub = NanoNaCl.derive(prvBytes);
+const pub = nano25519.derive(prvBytes);
 // `pub` is a 32-byte Uint8Array public key for a Nano account
 ```
 
@@ -71,7 +71,7 @@ const pub = NanoNaCl.derive(prvBytes);
 ```javascript
 // `prv` is a 64-character hex string private key
 const prv = "0000000000000000000000000000000000000000000000000000000000000000";
-const pub = await NanoNaCl.deriveAsync(prv);
+const pub = await nano25519.deriveAsync(prv);
 // `pub` is a 64-character hex string public key for a Nano account
 ```
 
@@ -83,11 +83,11 @@ const msg = new Uint8Array(32);
 // `prv` is a 32-byte Uint8Array private key
 const prv = new Uint8Array(32);
 // `pub` is a 32-byte Uint8Array public key derived from `prv`
-const pub = NanoNaCl.derive(prv);
+const pub = nano25519.derive(prv);
 // `sk` is a 64-byte Uint8Array secret key joining private and public keys
 const sk = new Uint8Array([...prv, ...pub]);
 
-const sig = NanoNaCl.sign(msg, sk);
+const sig = nano25519.sign(msg, sk);
 // `sig` is a 64-byte Uint8Array signature for the block hash
 ```
 
@@ -99,11 +99,11 @@ const msg = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
 // `prv` is a 64-char hex string private key
 const prv = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
 // `pub` is a 64-char hex string public key derived from `prv`
-const pub = await NanoNaCl.deriveAsync(prv);
+const pub = await nano25519.deriveAsync(prv);
 // `sk` is a 128-char hex string secret key joining private and public keys
 const sk = prv + pub;
 
-const sig = await NanoNaCl.signAsync(msg, sk);
+const sig = await nano25519.signAsync(msg, sk);
 // `sig` is a 128-char hex string signature for the block hash
 ```
 
@@ -117,7 +117,7 @@ const msg = new Uint8Array(32);
 // `pub` is a 32-byte Uint8Array public key for a Nano account
 const pub = new Uint8Array(32);
 
-const v = NanoNaCl.verify(sig, msg, pub);
+const v = nano25519.verify(sig, msg, pub);
 // `v` is a boolean 'true' if the same `prv` that derives `pub` was also used to create `sig` by signing `msg`, else 'false'
 ```
 
@@ -132,14 +132,14 @@ const msg = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
 // `pub` is a 64-char hex string public key for a Nano account
 const pub = "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210";
 
-const v = await NanoNaCl.verifyAsync(sig, msg, pub);
+const v = await nano25519.verifyAsync(sig, msg, pub);
 // `v` is a boolean 'true' if the same `prv` that derives `pub` was also used to create `sig` by signing `msg`, else 'false'
 ```
 
 ## Notes
 
 The tweetnacl-js implementation was originally selected as the basis for
-NanoNaCl due to its historical reliability and security. Over time, however, it
+nano25519 due to its historical reliability and security. Over time, however, it
 became clear that tweetnacl was designed to optimize size and not speed. Soon
 after, libsodium became the reference from which functionality was ported.
 
@@ -164,14 +164,14 @@ A few basic tests are availabe in the source repository.
 1. Compile, minify, and bundle
 
 ```console
-git clone https://codecow.com/nano-nacl.git
-cd nano-nacl
+git clone https://codecow.com/nano25519.git
+cd nano25519
 npm i
 ```
 
 ## Reporting Bugs
 
-Email: <bug-nano-nacl@codecow.com>
+Email: <bug-nano25519@codecow.com>
 
 ## Acknowledgements
 
index 2828ae298fb426342bc989a62c9eab26ec031403..1fa175313872905377f7f5fa2bbf2988c9e57ebd 100644 (file)
@@ -1,7 +1,7 @@
 {
        "options": {
-               "outFile": "./build/nano-nacl.wasm",
-               "textFile": "./build/nano-nacl.wat",
+               "outFile": "./build/nano25519.wasm",
+               "textFile": "./build/nano25519.wat",
                "optimizeLevel": 3,
                "shrinkLevel": 0,
                "converge": true,
index 344e640c383ba22d4cfa48d8c95bb08d1f6b605f..96e85027c635de71a022ec65464bc9b2b7a7ea57 100644 (file)
@@ -31,7 +31,7 @@ export const browserOptions = {
        platform: 'browser',
        target: 'esnext',
        entryPoints: [
-               { in: './index.ts', out: 'browser' }
+               { in: './src/index.ts', out: 'browser' }
        ],
        dropLabels: ['NODE']
 }
@@ -44,7 +44,9 @@ export const nodeOptions = {
        platform: 'node',
        target: 'node22',
        entryPoints: [
-               { in: './index.ts', out: 'index' }
+               { in: './src/index.ts', out: 'index' },
+               { in: './src/async.ts', out: 'async' },
+               { in: './src/sync.ts', out: 'sync' }
        ],
        packages: 'external',
        dropLabels: ['BROWSER']
index db7ef495abd1ab7d8dd66a72e70ec8dcfa77c044..b72e2b16f82905af6077b11db693bb79a0c1efe3 100644 (file)
@@ -15,23 +15,23 @@ SPDX-License-Identifier: GPL-3.0-or-later
        <script type="module">
                await libsodium.ready
                await sodium.ready
-               let NanoNaCl, NANO_ORG_VECTOR, PYTHON_ED25519_BLAKE2B_VECTORS
+               let nano25519, NANO_ORG_VECTOR, PYTHON_ED25519_BLAKE2B_VECTORS
                try {
-                       NanoNaCl = await import('./dist/browser.js')
+                       nano25519 = await import('./dist/browser.js')
                } catch (err) {
                        console.warn(err)
                        try {
-                               NanoNaCl = await import('https://unpkg.com/nano-nacl')
+                               nano25519 = await import('https://unpkg.com/nano25519')
                        } catch (err) {
                                console.warn(err)
                                try {
-                                       NanoNaCl = await import('https://cdn.jsdelivr.net/npm/nano-nacl@latest')
+                                       nano25519 = await import('https://cdn.jsdelivr.net/npm/nano25519@latest')
                                } catch (err) {
-                                       throw new Error(`Failed to load NanoNaCl ${err}`)
+                                       throw new Error(`Failed to load nano25519 ${err}`)
                                }
                        }
                }
-               window.NanoNaCl = NanoNaCl
+               window.nano25519 = nano25519
                try {
                        ({ NANO_ORG_VECTOR, PYTHON_ED25519_BLAKE2B_VECTORS } = await import('./vectors.mjs'))
                } catch (err) {
@@ -115,12 +115,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
                const derive = (sk, api) => {
                        const sk8 = new Uint8Array(sk.match(/.{2}/g).map(b => parseInt(b, 16)))
                        switch (api) {
-                               case 'NanoNaCl': {
-                                       const pk8 = NanoNaCl.derive(sk8)
+                               case 'nano25519': {
+                                       const pk8 = nano25519.derive(sk8)
                                        return [...pk8].map(b => b.toString(16).padStart(2, '0')).join('')
                                }
-                               case 'NanoNaCl (async)': {
-                                       return NanoNaCl.deriveAsync(sk)
+                               case 'nano25519 (async)': {
+                                       return nano25519.deriveAsync(sk)
                                }
                                case 'NanocurrencyWeb': {
                                        return NanocurrencyWeb.wallet.legacyAccounts(sk)[0].publicKey
@@ -143,12 +143,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
                        const h8 = new Uint8Array(h.match(/.{2}/g).map(b => parseInt(b, 16)))
                        const sk8 = new Uint8Array((sk + pk).match(/.{2}/g).map(b => parseInt(b, 16)))
                        switch (api) {
-                               case 'NanoNaCl': {
-                                       const s8 = NanoNaCl.sign(h8, sk8)
+                               case 'nano25519': {
+                                       const s8 = nano25519.sign(h8, sk8)
                                        return [...s8].map(b => b.toString(16).padStart(2, '0')).join('')
                                }
-                               case 'NanoNaCl (async)': {
-                                       return NanoNaCl.signAsync(h, sk + pk)
+                               case 'nano25519 (async)': {
+                                       return nano25519.signAsync(h, sk + pk)
                                }
                                case 'NanocurrencyWeb': {
                                        const { privateKey } = NanocurrencyWeb.wallet.legacyAccounts(sk)[0]
@@ -172,11 +172,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
                        const s8 = new Uint8Array(s.match(/.{2}/g).map(b => parseInt(b, 16)))
                        const pk8 = new Uint8Array(pk.match(/.{2}/g).map(b => parseInt(b, 16)))
                        switch (api) {
-                               case 'NanoNaCl': {
-                                       return NanoNaCl.verify(s8, h8, pk8)
+                               case 'nano25519': {
+                                       return nano25519.verify(s8, h8, pk8)
                                }
-                               case 'NanoNaCl (async)': {
-                                       return NanoNaCl.verifyAsync(s, h, pk)
+                               case 'nano25519 (async)': {
+                                       return nano25519.verifyAsync(s, h, pk)
                                }
                                case 'NanocurrencyWeb': {
                                        return NanocurrencyWeb.tools.verify(pk, s, h)
@@ -214,68 +214,87 @@ SPDX-License-Identifier: GPL-3.0-or-later
 
                        // self-check
                        document.getElementById('status').innerHTML = `RUNNING SELF-CHECK`
-                       console.log(`%cNanoNaCl`, 'color:green', 'Checking validation against known values')
+                       console.log(`%cnano25519`, 'color:green', 'Checking validation against known values')
                        const zeroes = '0000000000000000000000000000000000000000000000000000000000000000'
                        const expect = []
                        let result, test, passes = 0, failures = 0
 
                        // PASS
-                       result = await NanoNaCl.deriveAsync(NANO_ORG_VECTOR.privateKey)
+                       result = await nano25519.deriveAsync(NANO_ORG_VECTOR.privateKey)
                        test = result.toLowerCase() === NANO_ORG_VECTOR.publicKey.toLowerCase()
                        check(`async derive from ${NANO_ORG_VECTOR.privateKey}`, test)
                        passes += +test
                        failures += +!test
 
-                       result = await NanoNaCl.signAsync(NANO_ORG_VECTOR.blockHash, NANO_ORG_VECTOR.secretKey)
+                       result = await nano25519.signAsync(NANO_ORG_VECTOR.blockHash, NANO_ORG_VECTOR.secretKey)
                        test = result.toLowerCase() === NANO_ORG_VECTOR.signature.toLowerCase()
                        check(`async sign message ${NANO_ORG_VECTOR.blockHash}`, test)
                        passes += +test
                        failures += +!test
 
-                       result = await NanoNaCl.verifyAsync(NANO_ORG_VECTOR.signature, NANO_ORG_VECTOR.blockHash, NANO_ORG_VECTOR.publicKey)
+                       result = await nano25519.verifyAsync(NANO_ORG_VECTOR.signature, NANO_ORG_VECTOR.blockHash, NANO_ORG_VECTOR.publicKey)
                        test = result === true
                        check(`async verify signature ${NANO_ORG_VECTOR.signature}`, test)
                        passes += +test
                        failures += +!test
 
-                       result = NanoNaCl.derive(NANO_ORG_VECTOR.privateKeyBytes)
+                       result = nano25519.derive(NANO_ORG_VECTOR.privateKeyBytes)
                        test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === NANO_ORG_VECTOR.publicKey.toLowerCase()
                        check(`derive from ${NANO_ORG_VECTOR.privateKey}`, test)
                        passes += +test
                        failures += +!test
 
-                       result = NanoNaCl.sign(NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.secretKeyBytes)
+                       result = nano25519.sign(NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.secretKeyBytes)
                        test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === NANO_ORG_VECTOR.signature.toLowerCase()
                        check(`sign message ${NANO_ORG_VECTOR.blockHash}`, test)
                        passes += +test
                        failures += +!test
 
-                       result = NanoNaCl.verify(NANO_ORG_VECTOR.signatureBytes, NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.publicKeyBytes)
+                       result = nano25519.verify(NANO_ORG_VECTOR.signatureBytes, NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.publicKeyBytes)
                        test = result === true
                        check(`verify signature ${NANO_ORG_VECTOR.signature}`, test)
                        passes += +test
                        failures += +!test
 
+                       // test both strings and bytes as input
                        for (const { privateKey, publicKey, message, signature } of PYTHON_ED25519_BLAKE2B_VECTORS) {
+                               result = nano25519.derive(privateKey)
+                               test = result.toLowerCase() === publicKey
+                               check(`derive from ${privateKey}`, test)
+                               passes += +test
+                               failures += +!test
+
+                               result = nano25519.sign(message, privateKey + publicKey)
+                               test = result.toLowerCase() === signature.slice(0, 128)
+                               check(`sign message ${message}`, test)
+                               passes += +test
+                               failures += +!test
+
+                               result = nano25519.verify(signature.slice(0, 128), message, publicKey)
+                               test = result === true
+                               check(`verify signature ${signature.slice(0, 128)}`, test)
+                               passes += +test
+                               failures += +!test
+
                                const privateKeyBytes = new Uint8Array(privateKey.match(/.{2}/g)?.map(b => parseInt(b, 16)) ?? [])
                                const publicKeyBytes = new Uint8Array(publicKey.match(/.{2}/g)?.map(b => parseInt(b, 16)) ?? [])
                                const secretKeyBytes = new Uint8Array([...privateKeyBytes, ...publicKeyBytes])
                                const messageBytes = new Uint8Array(message.match(/.{2}/g)?.map(b => parseInt(b, 16)) ?? [])
                                const signatureBytes = new Uint8Array(signature.match(/.{2}/g)?.slice(0, 64).map(b => parseInt(b, 16)) ?? [])
 
-                               result = NanoNaCl.derive(privateKeyBytes)
+                               result = nano25519.derive(privateKeyBytes)
                                test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === publicKey
                                check(`derive from ${privateKey}`, test)
                                passes += +test
                                failures += +!test
 
-                               result = NanoNaCl.sign(messageBytes, secretKeyBytes)
+                               result = nano25519.sign(messageBytes, secretKeyBytes)
                                test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === signature.slice(0, 128)
                                check(`sign message ${message}`, test)
                                passes += +test
                                failures += +!test
 
-                               result = NanoNaCl.verify(signatureBytes, messageBytes, publicKeyBytes)
+                               result = nano25519.verify(signatureBytes, messageBytes, publicKeyBytes)
                                test = result === true
                                check(`verify signature ${signature.slice(0, 128)}`, test)
                                passes += +test
@@ -283,35 +302,35 @@ SPDX-License-Identifier: GPL-3.0-or-later
                        }
 
                        // XFAIL
-                       result = await NanoNaCl.verifyAsync(NANO_ORG_VECTOR.badSignature, NANO_ORG_VECTOR.blockHash, NANO_ORG_VECTOR.publicKey)
+                       result = await nano25519.verifyAsync(NANO_ORG_VECTOR.badSignature, NANO_ORG_VECTOR.blockHash, NANO_ORG_VECTOR.publicKey)
                        console.log(result)
                        test = result === false
                        check(`verify() output for non-canonical signature`, test)
                        passes += +test
                        failures += +!test
 
-                       result = await NanoNaCl.verifyAsync(NANO_ORG_VECTOR.signature, random(), NANO_ORG_VECTOR.publicKey)
+                       result = await nano25519.verifyAsync(NANO_ORG_VECTOR.signature, random(), NANO_ORG_VECTOR.publicKey)
                        console.log(result)
                        test = result === false
                        check(`verify() output for random block hash`, test)
                        passes += +test
                        failures += +!test
 
-                       result = await NanoNaCl.verifyAsync(NANO_ORG_VECTOR.signature, zeroes, NANO_ORG_VECTOR.publicKey)
+                       result = await nano25519.verifyAsync(NANO_ORG_VECTOR.signature, zeroes, NANO_ORG_VECTOR.publicKey)
                        console.log(result)
                        test = result === false
                        check(`verify() output for bad block hash`, test)
                        passes += +test
                        failures += +!test
 
-                       result = await NanoNaCl.verifyAsync(`${zeroes}${zeroes}`, NANO_ORG_VECTOR.blockHash, NANO_ORG_VECTOR.publicKey)
+                       result = await nano25519.verifyAsync(`${zeroes}${zeroes}`, NANO_ORG_VECTOR.blockHash, NANO_ORG_VECTOR.publicKey)
                        console.log(result)
                        test = result === false
                        check(`verify() output for bad signature`, test)
                        passes += +test
                        failures += +!test
 
-                       result = await NanoNaCl.verifyAsync(NANO_ORG_VECTOR.signature, NANO_ORG_VECTOR.blockHash, zeroes)
+                       result = await nano25519.verifyAsync(NANO_ORG_VECTOR.signature, NANO_ORG_VECTOR.blockHash, zeroes)
                        console.log(result)
                        test = result === false
                        check(`verify() output for bad public key`, test)
@@ -382,7 +401,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
                        const validation = document.getElementById('validation')
                        validation.innerText = '⏳'
                        if (signature.length === 128 && blockHash.length === 64 && publicKey.length === 64) {
-                               NanoNaCl.verify(signature, blockHash, publicKey)
+                               nano25519.verify(signature, blockHash, publicKey)
                                        .then(result => {
                                                validation.innerText = result
                                                        ? '✔️'
@@ -422,9 +441,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
 </head>
 
 <body>
-       <h1>nano-nacl</h1>
-       <h4><a href="https://www.npmjs.com/package/nano-nacl">https://www.npmjs.com/package/nano-nacl</a></h4>
-       <h2>Speed test for NanoNaCl tool.</h2>
+       <h1>nano25519</h1>
+       <h4><a href="https://www.npmjs.com/package/nano25519">https://www.npmjs.com/package/nano25519</a></h4>
+       <h2>Speed test for nano25519</h2>
        <p>Times below are in milliseconds and are summarized by various averaging methods.</p>
        <hr />
        <label for="signature">Verify Signature</label>
@@ -439,8 +458,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
        <span>
                <label for="api">API</label>
                <select id="api">
-                       <option>NanoNaCl</option>
-                       <option>NanoNaCl (async)</option>
+                       <option>nano25519</option>
+                       <option>nano25519 (async)</option>
                        <option>NanocurrencyWeb</option>
                        <option>Sodium</option>
                        <option>TweetNaCl.js</option>
index 9f21649bf988902db2510bdc22932a01109fa8ba..ea2aff9310e9a82bf175ec674723d8eb87b12da8 100644 (file)
@@ -1,11 +1,11 @@
 {
-       "name": "nano-nacl",
+       "name": "nano25519",
        "version": "0.0.1",
        "lockfileVersion": 3,
        "requires": true,
        "packages": {
                "": {
-                       "name": "nano-nacl",
+                       "name": "nano25519",
                        "version": "0.0.1",
                        "license": "(GPL-3.0-or-later AND MIT)",
                        "devDependencies": {
index 4116c66d0dc93bb8150d931799e8df5974ff6559..2285ed2d71df6659314410b37a46c727b7dc6333 100644 (file)
@@ -1,5 +1,5 @@
 {
-       "name": "nano-nacl",
+       "name": "nano25519",
        "version": "0.0.1",
        "description": "Nano cryptocurrency public key derivation, transaction signing, and signature verification using AssemblyScript.",
        "keywords": [
@@ -13,7 +13,7 @@
                "wasm"
        ],
        "homepage": "https://git.codecow.com",
-       "bugs": "bug-nano-nacl@codecow.com",
+       "bugs": "bug-nano25519@codecow.com",
        "license": "(GPL-3.0-or-later AND MIT)",
        "author": "Chris Duncan <chris@codecow.com>",
        "funding": {
        ],
        "repository": {
                "type": "git",
-               "url": "git+https://codecow.com/nano-nacl.git"
+               "url": "git+https://codecow.com/nano25519.git"
        },
        "scripts": {
                "build": "npm run clean && npm run compile && node ./esbuild/dev.mjs",
                "build:prod": "npm run clean && npm run compile && node ./esbuild/prod.mjs",
                "clean": "rm -rf {build,dist}",
-               "compile": "asc ./assembly/index.ts && tsc",
+               "compile": "asc ./src/assembly/index.ts && tsc",
                "prepublishOnly": "npm run test:prod",
                "test": "npm run build && node ./test.mjs",
                "test:prod": "npm run build:prod && node ./test.mjs"
                        "types": "./dist/index.d.ts",
                        "node": "./dist/index.js",
                        "import": "./dist/browser.js"
+               },
+               "./sync": {
+                       "types": "./dist/sync.d.ts",
+                       "node": "./dist/sync.js"
+               },
+               "./async": {
+                       "types": "./dist/async.d.ts",
+                       "node": "./dist/async.js"
                }
        },
        "browser": {
similarity index 100%
rename from assembly/base.ts
rename to src/assembly/base.ts
similarity index 97%
rename from assembly/blake2b.ts
rename to src/assembly/blake2b.ts
index 272561a6db9a10a7ece8b01be84ca31aec1a82d2..72a268847bbc4d435bec1029780a5a1941bc890f 100644 (file)
@@ -59,16 +59,22 @@ export class Blake2b {
        t: v128 = v128.splat<u64>(0)
        v: StaticArray<u64> = new StaticArray<u64>(16)
 
-       init (): Blake2b {
-               // initialize buffers and counters
+       @inline
+       clear (): void {
                this.b.fill(0)
                this.c = 0
+               this.h.fill(0)
                this.m.fill(0)
+               this.p.fill(0)
                this.t = v128.splat<u64>(0)
                this.v.fill(0)
+       }
+
+       init (): Blake2b {
+               // reset buffers and counters
+               this.clear()
 
                // initialize parameter block
-               this.p.fill(0)
                this.p[0] = 64 // always 64 bytes for this implementation
                this.p[1] = 0 // no key
                this.p[2] = 1 // fanout
@@ -119,6 +125,9 @@ export class Blake2b {
 
                // return byte array of 64-byte chain buffer
                memory.copy(changetype<usize>(output), changetype<usize>(this.h), 64)
+
+               // clear internal buffers and counters
+               this.clear()
        }
 
        // Defined in BLAKE2 section 2.4
similarity index 100%
rename from assembly/fe.ts
rename to src/assembly/fe.ts
similarity index 100%
rename from assembly/ge.ts
rename to src/assembly/ge.ts
similarity index 80%
rename from assembly/index.ts
rename to src/assembly/index.ts
index ff2246d6de8aeb3831012caebc300cbbb01a573e..3a9f7f60a912ebbde04905fb48c8ff717fe73cb8 100644 (file)
@@ -29,8 +29,10 @@ function clamp (k: StaticArray<u8>): void {
        k[31] |= 64\r
 }\r
 \r
-function crypto_derive (pk: StaticArray<u8>, sk: StaticArray<u8>, seed: StaticArray<u8>): void {\r
-       blake2b.init().update(changetype<usize>(seed), seed.length).digest(sk)\r
+const crypto_derive_pk: StaticArray<u8> = new StaticArray<u8>(PUBLICKEY_BYTES)\r
+function crypto_derive (sk: StaticArray<u8>, seed: StaticArray<u8>): void {\r
+       const pk = crypto_derive_pk\r
+       blake2b.init().update(changetype<usize>(seed), 32).digest(sk)\r
        clamp(sk)\r
 \r
        ge_scalarmult_base_tobytes(pk, sk)\r
@@ -41,13 +43,11 @@ function crypto_derive (pk: StaticArray<u8>, sk: StaticArray<u8>, seed: StaticAr
 const crypto_sign_az = new StaticArray<u8>(64)\r
 const crypto_sign_nonce = new StaticArray<u8>(64)\r
 const crypto_sign_hram = new StaticArray<u8>(64)\r
-const crypto_sign_zm = new StaticArray<u8>(SIGNATURE_BYTES)\r
 const crypto_sign_t = new StaticArray<u8>(PRIVATEKEY_BYTES)\r
 function crypto_sign (s: StaticArray<u8>, m: usize, mlen: i32, sk: StaticArray<u8>): void {\r
        const az = crypto_sign_az\r
        const nonce = crypto_sign_nonce\r
        const hram = crypto_sign_hram\r
-       const zm = changetype<usize>(crypto_sign_zm)\r
        const t = changetype<usize>(crypto_sign_t)\r
 \r
        // Hash secret key to private scalar `a` and prefix for nonce derivation `z`\r
@@ -69,11 +69,14 @@ function crypto_sign (s: StaticArray<u8>, m: usize, mlen: i32, sk: StaticArray<u
        sc_reduce(hram)\r
 \r
        // Compute `S = (r + h*a) mod L` and construct final signature `s = (R || S)`\r
-       sc_muladd(changetype<StaticArray<u8>>(changetype<usize>(s) + 32), az, hram, nonce)\r
+       sc_muladd(changetype<StaticArray<u8>>(changetype<usize>(s) + 32), changetype<StaticArray<u8>>(az), hram, nonce)\r
 \r
        // Clean up sensitive data\r
        az.fill(0)\r
+       hram.fill(0)\r
        nonce.fill(0)\r
+       sk.fill(0)\r
+       memory.fill(t, 0, PRIVATEKEY_BYTES)\r
 }\r
 \r
 const crypto_verify_h = new StaticArray<u8>(64)\r
@@ -134,30 +137,20 @@ export function getMessagePointer (): usize {
        return MESSAGE_BUFFER\r
 }\r
 \r
-const derive_pk = new StaticArray<u8>(PUBLICKEY_BYTES)\r
-const derive_sk = new StaticArray<u8>(SECRETKEY_BYTES)\r
-const derive_seed = new StaticArray<u8>(PRIVATEKEY_BYTES)\r
 /**\r
  * Derives a Nano public key from a private key and writes it to the static\r
  * output buffer. This mirrors the functionality of\r
- * `nacl.sign.keyPair.fromSeed()`, returning just the `publicKey` property.\r
+ * `nacl.sign.keyPair.fromSeed()`, returning both private and public keys as a\r
+ * concatenated `secretKey` property.\r
  */\r
 export function derive (): void {\r
-       const pk = derive_pk\r
-       const sk = derive_sk\r
-       const seed = derive_seed\r
-\r
-       for (let i = 0; i < PRIVATEKEY_BYTES; i++) {\r
-               seed[i] = load<u8>(INPUT_BUFFER + i)\r
-       }\r
-\r
-       crypto_derive(pk, sk, seed)\r
-       for (let i = 0; i < PUBLICKEY_BYTES; i++) {\r
-               store<u8>(OUTPUT_BUFFER + i, pk[i])\r
-       }\r
+       const sk = changetype<StaticArray<u8>>(OUTPUT_BUFFER)\r
+       const seed = changetype<StaticArray<u8>>(INPUT_BUFFER)\r
+       memory.fill(OUTPUT_BUFFER, 0, SECRETKEY_BYTES)\r
+       crypto_derive(sk, seed)\r
+       memory.fill(INPUT_BUFFER, 0, PRIVATEKEY_BYTES)\r
 }\r
 \r
-const sign_sk = new StaticArray<u8>(SECRETKEY_BYTES)\r
 const sign_s = new StaticArray<u8>(SIGNEDMESSAGE_BYTES)\r
 /**\r
  * Sign a message using a private key. The signature is written to the static\r
@@ -168,13 +161,15 @@ const sign_s = new StaticArray<u8>(SIGNEDMESSAGE_BYTES)
  * @param {u64} sk - 64-byte secret key (32-byte private key + 32-byte public key)\r
  */\r
 export function sign (mlen: i32): void {\r
+       if (mlen < 0 || mlen > 32768) throw new Error()\r
        const s = sign_s\r
        const m = MESSAGE_BUFFER\r
-       const sk = sign_sk\r
+       const sk = changetype<StaticArray<u8>>(INPUT_BUFFER)\r
 \r
-       memory.copy(changetype<usize>(sk), INPUT_BUFFER, SECRETKEY_BYTES)\r
+       memory.fill(OUTPUT_BUFFER, 0, SIGNATURE_BYTES)\r
        crypto_sign(s, m, mlen, sk)\r
        memory.copy(OUTPUT_BUFFER, changetype<usize>(s), SIGNATURE_BYTES)\r
+       memory.fill(INPUT_BUFFER, 0, SECRETKEY_BYTES)\r
 }\r
 \r
 const verify_s = new StaticArray<u8>(SIGNATURE_BYTES)\r
@@ -188,19 +183,15 @@ const verify_k = new StaticArray<u8>(PUBLICKEY_BYTES)
  * @param {u64} k - 32-byte public key\r
  */\r
 export function verify (mlen: i32): void {\r
+       if (mlen < 0 || mlen > 32768) throw new Error()\r
        const s = verify_s\r
        const m = MESSAGE_BUFFER\r
        const k = verify_k\r
 \r
-       let ptr = INPUT_BUFFER\r
-       for (let i = 0; i < SIGNATURE_BYTES; i++) {\r
-               s[i] = load<u8>(usize(i) + ptr)\r
-       }\r
-       ptr = INPUT_BUFFER + SIGNATURE_BYTES\r
-       for (let i = 0; i < PUBLICKEY_BYTES; i++) {\r
-               k[i] = load<u8>(usize(i) + ptr)\r
-       }\r
-\r
+       memory.fill(OUTPUT_BUFFER, 0, SIGNATURE_BYTES)\r
+       memory.copy(changetype<usize>(s), INPUT_BUFFER, SIGNATURE_BYTES)\r
+       memory.copy(changetype<usize>(k), INPUT_BUFFER + SIGNATURE_BYTES, PUBLICKEY_BYTES)\r
        const v = crypto_verify(s, m, mlen, k)\r
        store<u8>(OUTPUT_BUFFER, v)\r
+       memory.fill(INPUT_BUFFER, 0, SIGNATURE_BYTES)\r
 }\r
similarity index 100%
rename from assembly/p.ts
rename to src/assembly/p.ts
similarity index 100%
rename from assembly/sc.ts
rename to src/assembly/sc.ts
similarity index 100%
rename from assembly/utils.ts
rename to src/assembly/utils.ts
diff --git a/src/async.ts b/src/async.ts
new file mode 100644 (file)
index 0000000..0db6a36
--- /dev/null
@@ -0,0 +1,62 @@
+//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
+//! SPDX-License-Identifier: GPL-3.0-or-later
+
+import { run } from './nano25519'
+
+/**
+ * Asynchronous Nano public key derivation using WebAssembly.
+ * @param {Uint8Array<ArrayBuffer>} k - 64-character private key hex string
+ * @returns Promise for 64-character public key hex string
+ */
+export async function deriveAsync (privateKey: Uint8Array<ArrayBuffer>): Promise<Uint8Array<ArrayBuffer>>
+/**
+ * Asynchronous Nano public key derivation using WebAssembly.
+ * @param {string} k - 64-character private key hex string
+ * @returns Promise for 64-character public key hex string
+ */
+export async function deriveAsync (privateKey: string): Promise<string>
+export async function deriveAsync (privateKey: string | Uint8Array<ArrayBuffer>): Promise<string | Uint8Array<ArrayBuffer>> {
+       return run({ action: 'derive', privateKey })
+}
+
+/**
+ * Asynchronous signing using WebAssembly. To sign Nano blocks, the message
+ * should be a 64-character block hash.
+ * @param {Uint8Array<ArrayBuffer>} m - Variable-length message hex string up to 65536 characters
+ * @param {Uint8Array<ArrayBuffer>} k - 128-character secret key (prv + pub) hex string
+ * @returns Promise for 128-character detached signature hex string
+ */
+export async function signAsync (message: Uint8Array<ArrayBuffer>, secretKey: Uint8Array<ArrayBuffer>): Promise<Uint8Array<ArrayBuffer>>
+/**
+ * Asynchronous signing using WebAssembly. To sign Nano blocks, the message
+ * should be a 64-character block hash.
+ * @param {Uint8Array<ArrayBuffer>} m - Variable-length message hex string up to 65536 characters
+ * @param {Uint8Array<ArrayBuffer>} k - 128-character secret key (prv + pub) hex string
+ * @returns Promise for 128-character detached signature hex string
+ */
+export async function signAsync (message: string, secretKey: string): Promise<string>
+export async function signAsync (message: string | Uint8Array<ArrayBuffer>, secretKey: string | Uint8Array<ArrayBuffer>): Promise<string | Uint8Array<ArrayBuffer>> {
+       return run({ action: 'sign', message, secretKey })
+}
+
+/**
+ * Asynchronous signature verification using WebAssembly. To verify Nano block
+ * signatures, the message should be a 64-character block hash.
+ * @param {Uint8Array<ArrayBuffer>} s - 128-character detached signature hex string
+ * @param {Uint8Array<ArrayBuffer>} m - Variable-length message hex string to up 65536 characters
+ * @param {Uint8Array<ArrayBuffer>} k - 64-character public key hex string
+ * @returns Promise resolving to true if signature matches block hash and public key, else false
+ */
+export async function verifyAsync (signature: Uint8Array<ArrayBuffer>, message: Uint8Array<ArrayBuffer>, publicKey: Uint8Array<ArrayBuffer>): Promise<boolean>
+/**
+ * Asynchronous signature verification using WebAssembly. To verify Nano block
+ * signatures, the message should be a 64-character block hash.
+ * @param {Uint8Array<ArrayBuffer>} s - 128-character detached signature hex string
+ * @param {Uint8Array<ArrayBuffer>} m - Variable-length message hex string to up 65536 characters
+ * @param {Uint8Array<ArrayBuffer>} k - 64-character public key hex string
+ * @returns Promise resolving to true if signature matches block hash and public key, else false
+ */
+export async function verifyAsync (signature: string, message: string, publicKey: string): Promise<boolean>
+export async function verifyAsync (signature: string | Uint8Array<ArrayBuffer>, message: string | Uint8Array<ArrayBuffer>, publicKey: string | Uint8Array<ArrayBuffer>): Promise<boolean> {
+       return !!(await run({ action: 'verify', signature, message, publicKey }))
+}
diff --git a/src/index.ts b/src/index.ts
new file mode 100644 (file)
index 0000000..377ada4
--- /dev/null
@@ -0,0 +1,6 @@
+//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
+//! SPDX-License-Identifier: GPL-3.0-or-later
+
+export { deriveAsync, signAsync, verifyAsync } from './async'
+export { derive, sign, verify } from './sync'
+
similarity index 58%
rename from index.ts
rename to src/nano25519.ts
index 79144d5d03b40d963bf55d7b17e9e31a85b6bd66..b799c42fd431998424bee7e623c0e026dc3c4531 100644 (file)
--- a/index.ts
@@ -3,7 +3,7 @@
 
 import { Worker as NodeWorker } from 'node:worker_threads'
 //@ts-expect-error
-import nacl from './build/nano-nacl.wasm'
+import nano25519_wasm from '../build/nano25519.wasm'
 
 type Data = {
        action: string
@@ -25,15 +25,15 @@ type Exports = {
        }
 }
 
-const startNanoNaCl = (bytes: number[]): { derive: typeof derive, sign: typeof sign, verify: typeof verify } => {
+const nano25519_init = (bytes: number[]): { derive: typeof derive, sign: typeof sign, verify: typeof verify } => {
        const wasm: Uint8Array<ArrayBuffer> = Uint8Array.from(bytes)
        const module = new WebAssembly.Module(wasm)
        const { exports } = new WebAssembly.Instance(module, {
                env: {
                        abort: (msg: any, file: any, row: any, col: any) => {
                                // ~lib/builtins/abort(~lib/string/String | null?, ~lib/string/String | null?, u32?, u32?) => void
-                               // msg = __liftString(msg >>> 0)
-                               // file = __liftString(file >>> 0)
+                               msg = msg >>> 0
+                               file = file >>> 0
                                row = row >>> 0
                                col = col >>> 0
                                console.error('wasm abort:', `msg ${msg}`, `file ${file}`, `row ${row}`, `col ${col}`)
@@ -61,115 +61,125 @@ const startNanoNaCl = (bytes: number[]): { derive: typeof derive, sign: typeof s
                                        console.log(message, ...[a0, a1, a2, a3, a4].slice(0, n))
                                })()
                        },
-                       memory: new WebAssembly.Memory({ initial: 4, maximum: 4 })
+                       memory: new WebAssembly.Memory({ initial: 1, maximum: 1 })
                }
        }) as Exports
 
-       function derive (k: unknown): Uint8Array<ArrayBuffer> {
-               validate('private key', k, 32)
+       function derive (k: unknown): string | Uint8Array<ArrayBuffer> {
+               const privateKey = normalize('private key', 32, 32, k)
                let buffer: DataView | undefined = new DataView(exports.memory.buffer)
                let inPtr = exports.getInputPointer()
                for (let i = 0; i < 32; i++) {
-                       buffer.setUint8(inPtr + i, k[i])
+                       buffer.setUint8(inPtr + i, privateKey[i])
                }
                exports.derive()
                const outPtr = exports.getOutputPointer()
-               const pk = new Uint8Array(32)
+               const sk = new Uint8Array(32)
                buffer = new DataView(exports.memory.buffer)
                for (let i = 0; i < 32; i++) {
-                       pk[i] = buffer.getUint8(outPtr + i)
+                       sk[i] = buffer.getUint8(outPtr + i + 32)
+                       buffer.setUint8(outPtr + i, 0)
+                       buffer.setUint8(outPtr + i + 32, 0)
                }
                buffer = undefined
-               return pk
+               return typeof k === 'string'
+                       ? [...sk].map(b => b.toString(16).padStart(2, '0')).join('')
+                       : sk
        }
 
-       function sign (m: unknown, k: unknown): Uint8Array<ArrayBuffer> {
-               validate('message', m)
-               validate('secret key', k, 64)
+       function sign (m: unknown, k: unknown): string | Uint8Array<ArrayBuffer> {
+               const message = normalize('message', 0, 32768, m)
+               const secretKey = normalize('secret key', 64, 64, k)
                let buffer: DataView | undefined = new DataView(exports.memory.buffer)
                let mPtr = exports.getMessagePointer()
                let inPtr = exports.getInputPointer()
-               for (let i = 0; i < m.byteLength; i++) {
-                       buffer.setUint8(mPtr + i, m[i])
+               for (let i = 0; i < message.byteLength; i++) {
+                       buffer.setUint8(mPtr + i, message[i])
                }
                for (let i = 0; i < 64; i++) {
-                       buffer.setUint8(inPtr + i, k[i])
+                       buffer.setUint8(inPtr + i, secretKey[i])
                }
-               exports.sign(m.byteLength)
+               exports.sign(message.byteLength)
                const outPtr = exports.getOutputPointer()
                const s = new Uint8Array(64)
                buffer = new DataView(exports.memory.buffer)
                for (let i = 0; i < 64; i++) {
                        s[i] = buffer.getUint8(outPtr + i)
+                       buffer.setUint8(outPtr + i, 0)
                }
                buffer = undefined
-               return s
+               return typeof k === 'string'
+                       ? [...s].map(b => b.toString(16).padStart(2, '0')).join('')
+                       : s
        }
 
-       function verify (s: unknown, m: unknown, k: unknown): Uint8Array<ArrayBuffer> {
-               validate('signature', s, 64)
-               validate('message', m)
-               validate('public key', k, 32)
+       function verify (s: unknown, m: unknown, k: unknown): boolean {
+               const signature = normalize('signature', 64, 64, s)
+               const message = normalize('message', 0, 32768, m)
+               const publicKey = normalize('public key', 32, 32, k)
                let buffer: DataView | undefined = new DataView(exports.memory.buffer)
                let mPtr = exports.getMessagePointer()
                let inPtr = exports.getInputPointer()
-               for (let i = 0; i < m.byteLength; i++) {
-                       buffer.setUint8(mPtr + i, m[i])
+               for (let i = 0; i < message.byteLength; i++) {
+                       buffer.setUint8(mPtr + i, message[i])
                }
                for (let i = 0; i < 64; i++) {
-                       buffer.setUint8(inPtr + i, s[i])
+                       buffer.setUint8(inPtr + i, signature[i])
                }
                inPtr += 64
                for (let i = 0; i < 32; i++) {
-                       buffer.setUint8(inPtr + i, k[i])
+                       buffer.setUint8(inPtr + i, publicKey[i])
                }
-               exports.verify(m.byteLength)
+               exports.verify(message.byteLength)
                const outPtr = exports.getOutputPointer()
                const v = new Uint8Array(1)
                buffer = new DataView(exports.memory.buffer)
                v[0] = buffer.getUint8(outPtr)
+               for (let i = 0; i < 64; i++) {
+                       buffer.setUint8(outPtr + i, 0)
+               }
                buffer = undefined
-               return v
+               return v[0] === 0
        }
 
-       function toBytes (type: string, byteLength: number, value?: unknown): Uint8Array<ArrayBuffer> {
-               if (value instanceof ArrayBuffer) {
-                       return new Uint8Array(value)
+       function normalize (name: string, byteLengthMin: number, byteLengthMax: number, value: unknown): Uint8Array<ArrayBuffer> {
+               if (typeof name !== 'string') {
+                       throw new TypeError(`Invalid name ${name}`)
                }
-               if (typeof type !== 'string') {
-                       throw new TypeError(`toBytes(): Invalid type ${type}`)
+               if (typeof byteLengthMin !== 'number' || typeof byteLengthMax !== 'number') {
+                       throw new TypeError(`Invalid byte length for ${name}`)
                }
-               if (typeof byteLength !== 'number') {
-                       throw new TypeError(`toBytes(): Invalid byte length ${byteLength}`)
+               if (typeof value === 'string') {
+                       const regex = RegExp(`[A-Fa-f0-9]{${byteLengthMin << 1},${byteLengthMax << 1}}`)
+                       if (!regex.test(value)) {
+                               throw new TypeError(`Invalid ${name} ${value}`)
+                       }
+                       value = new Uint8Array(value.match(/[A-Fa-f0-9]{2}/g)?.map(b => parseInt(b, 16)) || [])
                }
-               const regex = RegExp(`[a-z0-9]{${byteLength << 1}}`, 'i')
-               if (typeof value !== 'string' || !regex.test(value)) {
-                       throw new TypeError(`toBytes(): Invalid ${type} ${value}`)
+               if (value instanceof ArrayBuffer) {
+                       value = new Uint8Array(value)
                }
-               const bytes = new Uint8Array(value.match(/.{2}/g)?.map(b => parseInt(b, 16)) || [])
-               if (bytes.byteLength !== byteLength) {
-                       throw new TypeError(`toBytes(): Error parsing ${type} ${bytes}`)
+               if (!(value instanceof Uint8Array)) {
+                       throw new TypeError(`${name} must be Uint8Array`)
                }
-               return bytes
-       }
-
-       function validate (type: string, bytes?: unknown, byteLength?: number): asserts bytes is Uint8Array<ArrayBuffer> {
-               if (!(bytes instanceof Uint8Array)) {
-                       throw new TypeError(`${type} must be Uint8Array`)
+               if (!('buffer' in value && value.buffer instanceof ArrayBuffer)) {
+                       throw new TypeError(`${name} must be backed by an ArrayBuffer`)
                }
-               if (!('buffer' in bytes)) {
-                       throw new TypeError(`${type} must be backed by an ArrayBuffer`)
+               if (value.byteLength < byteLengthMin) {
+                       throw new TypeError(`${name} must be at least ${byteLengthMin} bytes`)
                }
-               if (byteLength != null && bytes.buffer.byteLength !== byteLength) {
-                       throw new TypeError(`${type} must be ${byteLength} bytes`)
+               if (value.byteLength > byteLengthMax) {
+                       throw new TypeError(`${name} must be no more than ${byteLengthMax} bytes`)
                }
+               return value as Uint8Array<ArrayBuffer>
        }
 
        let isListening = false
        let host: any = null
 
        /**
-        * Parses inbound data when NanoNaCl is started as a Web Worker.
+        * Parses inbound data when nano25519 is started as a Web Worker. Only called
+        * by functions in `async` module.
         * @param {object} message.data - Worker commands and related data
         */
        function handleMessage (message: unknown): void {
@@ -184,7 +194,7 @@ const startNanoNaCl = (bytes: number[]): { derive: typeof derive, sign: typeof s
                                || !('action' in message.data)
                                || typeof message.data.action !== 'string'
                        ) {
-                               throw new TypeError('Invalid NanoNaClWorker request')
+                               throw new TypeError('Invalid nano25519Worker request')
                        }
                        const data: Data = message.data as object & Record<"action", string>
                        if (data.action === 'start') {
@@ -197,27 +207,21 @@ const startNanoNaCl = (bytes: number[]): { derive: typeof derive, sign: typeof s
                                const { action } = data
                                if (action === 'derive') {
                                        const { privateKey } = data
-                                       const privateKeyBytes = toBytes('private key', 32, privateKey)
-                                       const publicKeyBytes = derive(privateKeyBytes)
-                                       if (publicKeyBytes == null || !(publicKeyBytes instanceof Uint8Array) || publicKeyBytes.byteLength != 32) {
+                                       const publicKeyBytes = derive(privateKey)
+                                       if (publicKeyBytes == null) {
                                                throw new TypeError('Invalid public key from WASM derive()')
                                        }
                                        result = publicKeyBytes
                                } else if (action === 'sign') {
                                        const { message, secretKey } = data
-                                       const messageBytes = toBytes('message', typeof message === 'string' ? message.length >> 1 : 0, message)
-                                       const secretKeyBytes = toBytes('secret key', 64, secretKey)
-                                       const signature = sign(messageBytes, secretKeyBytes)
+                                       const signature = sign(message, secretKey)
                                        if (signature == null) {
                                                throw new TypeError('Invalid signature from WASM sign()')
                                        }
                                        result = signature
                                } else if (action === 'verify') {
                                        const { message, publicKey, signature } = data
-                                       const signatureBytes = toBytes('signature', 64, signature)
-                                       const messageBytes = toBytes('message', typeof message === 'string' ? message.length >> 1 : 0, message)
-                                       const publicKeyBytes = toBytes('public key', 32, publicKey)
-                                       const verification = verify(signatureBytes, messageBytes, publicKeyBytes)
+                                       const verification = verify(signature, message, publicKey)
                                        if (verification == null) {
                                                throw new TypeError('Invalid verification from WASM verify()')
                                        }
@@ -225,6 +229,11 @@ const startNanoNaCl = (bytes: number[]): { derive: typeof derive, sign: typeof s
                                }
                        }
                } catch (err: unknown) {
+                       let buffer: DataView | undefined = new DataView(exports.memory.buffer)
+                       let inPtr = exports.getInputPointer()
+                       for (let i = 0; i < 128; i++) {
+                               buffer.setUint8(inPtr + i, 0)
+                       }
                        if (typeof err === 'object' && err != null) {
                                const e = err as { [k: string]: unknown }
                                if (e.message !== 'divide by zero') {
@@ -252,6 +261,10 @@ const startNanoNaCl = (bytes: number[]): { derive: typeof derive, sign: typeof s
        return { derive, sign, verify }
 }
 
+export const nano25519 = nano25519_init(nano25519_wasm)
+
+const nano25519_worker = `;(${nano25519_init})([${nano25519_wasm}]);`
+
 /**
  * Host code for asynchronous Web Worker
  */
@@ -263,24 +276,24 @@ let url: string
 // Create worker module
 function init (): void {
        try {
-               url = URL.createObjectURL(new Blob([NanoNaClWorker], { type: 'text/javascript' }))
+               url = URL.createObjectURL(new Blob([nano25519_worker], { type: 'text/javascript' }))
                BROWSER: worker = new Worker(url, { type: 'module' })
-               NODE: worker = new NodeWorker(NanoNaClWorker, {
+               NODE: worker = new NodeWorker(nano25519_worker, {
                        eval: true,
                        stderr: false,
                        stdout: false
                })
-               console.log(`NanoNaCl initialized.`)
+               console.log(`nano25519 initialized.`)
                isWorkerReady = true
        } catch (err) {
                isWorkerReady = false
-               throw new Error('NanoNaCl initialization failed.', { cause: err })
+               throw new Error('nano25519 initialization failed.', { cause: err })
        }
 }
 
 // Reconstruct worker when errors occur
 function reset (): void {
-       console.warn(`NanoNaCl encountered an error. Reinitializing...`)
+       console.warn(`nano25519 encountered an error. Reinitializing...`)
        isWorkerReady = false
        worker.terminate()
        init()
@@ -316,10 +329,16 @@ async function start (): Promise<void> {
        }
 }
 
-// Send command and relevant data to NanoNaCl worker
-async function dispatch (data: { [key: string]: string | Uint8Array<ArrayBuffer> }): Promise<Uint8Array<ArrayBuffer>> {
+// Send command and relevant data to nano25519 worker
+async function dispatch (data: { [key: string]: string | ArrayBuffer | Uint8Array<ArrayBuffer> }): Promise<Uint8Array<ArrayBuffer>> {
        return new Promise((resolve, reject) => {
-               const transfer: ArrayBuffer[] = Object.values(data).filter(v => v instanceof Uint8Array).map(({ buffer }) => buffer)
+               const transfer: ArrayBuffer[] = []
+               for (let k of Object.keys(data)) {
+                       if (data[k] instanceof Uint8Array) {
+                               data[k] = data[k].buffer.slice()
+                               transfer.push(data[k])
+                       }
+               }
                const onresult = (msg: any): void => {
                        const result = msg.data
                        console.log(`received result from worker: `, result)
@@ -366,7 +385,7 @@ async function stop (): Promise<void> {
        })
 }
 
-async function run (data: Record<string, string | Uint8Array<ArrayBuffer>>): Promise<string> {
+export async function run (data: Record<string, string | Uint8Array<ArrayBuffer>>): Promise<string | Uint8Array<ArrayBuffer>> {
        try {
                await start()
        } catch (err: any) {
@@ -375,13 +394,14 @@ async function run (data: Record<string, string | Uint8Array<ArrayBuffer>>): Pro
        }
        try {
                const result = await dispatch(data)
-               if (!(result instanceof Uint8Array)) {
+               if (typeof result !== 'boolean' && typeof result !== 'string' && !(result instanceof Uint8Array)) {
                        throw new Error(result)
                }
-               return [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase()
+               return result
        } catch (err: any) {
                try {
                        console.error(err)
+                       console.error(data)
                        await stop()
                } catch (e: any) {
                        console.error('failed to stop worker', err?.message ?? err ?? 'unknown reason')
@@ -392,48 +412,20 @@ async function run (data: Record<string, string | Uint8Array<ArrayBuffer>>): Pro
        }
 }
 
-const NanoNaCl = startNanoNaCl(nacl)
-const NanoNaClWorker = `;(${startNanoNaCl})([${nacl}]);`
-
-/**
- * Nano public key derivation using WebAssembly.
- * @param {Uint8Array<ArrayBuffer>} k - 32-byte private key
- * @returns 32-byte public key
- */
-export function derive (k: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer> {
-       return NanoNaCl.derive(k)
-}
-
-/**
- * Signing using WebAssembly. To sign Nano blocks, the message should be a
- * 32-byte block hash.
- * @param {Uint8Array<ArrayBuffer>} m - Variable-byte-length message up to 32 KiB
- * @param {Uint8Array<ArrayBuffer>} k - 64-byte secret key (prv + pub)
- * @returns 64-byte detached signature
- */
-export function sign (m: Uint8Array<ArrayBuffer>, k: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer> {
-       return NanoNaCl.sign(m, k)
-}
-
 /**
- * Signature verification using WebAssembly. To verify Nano block signatures,
- * the message should be a 64-character block hash.
- * @param {Uint8Array<ArrayBuffer>} s - 64-byte detached signature
- * @param {Uint8Array<ArrayBuffer>} m - Variable-byte-length message up to 32 KiB
- * @param {Uint8Array<ArrayBuffer>} k - 32-byte public key
- * @returns true if signature matches block hash and public key, else false
+ * Asynchronous Nano public key derivation using WebAssembly.
+ * @param {Uint8Array<ArrayBuffer>} k - 64-character private key hex string
+ * @returns Promise for 64-character public key hex string
  */
-export function verify (s: Uint8Array<ArrayBuffer>, m: Uint8Array<ArrayBuffer>, k: Uint8Array<ArrayBuffer>): boolean {
-       return NanoNaCl.verify(s, m, k)[0] === 0
-}
-
+export async function deriveAsync (privateKey: Uint8Array<ArrayBuffer>): Promise<Uint8Array<ArrayBuffer>>
 /**
  * Asynchronous Nano public key derivation using WebAssembly.
- * @param {Uint8Array<ArrayBuffer>} k - 64-character private key hex string
+ * @param {string} k - 64-character private key hex string
  * @returns Promise for 64-character public key hex string
  */
-export async function deriveAsync (privateKey: string): Promise<string> {
-       return await run({ action: 'derive', privateKey })
+export async function deriveAsync (privateKey: string): Promise<string>
+export async function deriveAsync (privateKey: string | Uint8Array<ArrayBuffer>): Promise<string | Uint8Array<ArrayBuffer>> {
+       return run({ action: 'derive', privateKey })
 }
 
 /**
@@ -443,8 +435,17 @@ export async function deriveAsync (privateKey: string): Promise<string> {
  * @param {Uint8Array<ArrayBuffer>} k - 128-character secret key (prv + pub) hex string
  * @returns Promise for 128-character detached signature hex string
  */
-export const signAsync = async function (message: string, secretKey: string): Promise<string> {
-       return await run({ action: 'sign', message, secretKey })
+export async function signAsync (message: Uint8Array<ArrayBuffer>, secretKey: Uint8Array<ArrayBuffer>): Promise<Uint8Array<ArrayBuffer>>
+/**
+ * Asynchronous signing using WebAssembly. To sign Nano blocks, the message
+ * should be a 64-character block hash.
+ * @param {Uint8Array<ArrayBuffer>} m - Variable-length message hex string up to 65536 characters
+ * @param {Uint8Array<ArrayBuffer>} k - 128-character secret key (prv + pub) hex string
+ * @returns Promise for 128-character detached signature hex string
+ */
+export async function signAsync (message: string, secretKey: string): Promise<string>
+export async function signAsync (message: string | Uint8Array<ArrayBuffer>, secretKey: string | Uint8Array<ArrayBuffer>): Promise<string | Uint8Array<ArrayBuffer>> {
+       return run({ action: 'sign', message, secretKey })
 }
 
 /**
@@ -455,6 +456,16 @@ export const signAsync = async function (message: string, secretKey: string): Pr
  * @param {Uint8Array<ArrayBuffer>} k - 64-character public key hex string
  * @returns Promise resolving to true if signature matches block hash and public key, else false
  */
-export const verifyAsync = async function (signature: string, message: string, publicKey: string): Promise<boolean> {
-       return await run({ action: 'verify', signature, message, publicKey }) === '00'
+export async function verifyAsync (signature: Uint8Array<ArrayBuffer>, message: Uint8Array<ArrayBuffer>, publicKey: Uint8Array<ArrayBuffer>): Promise<boolean>
+/**
+ * Asynchronous signature verification using WebAssembly. To verify Nano block
+ * signatures, the message should be a 64-character block hash.
+ * @param {Uint8Array<ArrayBuffer>} s - 128-character detached signature hex string
+ * @param {Uint8Array<ArrayBuffer>} m - Variable-length message hex string to up 65536 characters
+ * @param {Uint8Array<ArrayBuffer>} k - 64-character public key hex string
+ * @returns Promise resolving to true if signature matches block hash and public key, else false
+ */
+export async function verifyAsync (signature: string, message: string, publicKey: string): Promise<boolean>
+export async function verifyAsync (signature: string | Uint8Array<ArrayBuffer>, message: string | Uint8Array<ArrayBuffer>, publicKey: string | Uint8Array<ArrayBuffer>): Promise<boolean> {
+       return !!(await run({ action: 'verify', signature, message, publicKey }))
 }
diff --git a/src/sync.ts b/src/sync.ts
new file mode 100644 (file)
index 0000000..1125afb
--- /dev/null
@@ -0,0 +1,62 @@
+//! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
+//! SPDX-License-Identifier: GPL-3.0-or-later
+
+import { nano25519 } from "./nano25519"
+
+/**
+ * Nano public key derivation using WebAssembly.
+ * @param {string} k - 64-character hexadecimal private key
+ * @returns 64-character hexadecimal public key
+ */
+export function derive (k: string): string
+/**
+ * Nano public key derivation using WebAssembly.
+ * @param {Uint8Array<ArrayBuffer>} k - 32-byte private key
+ * @returns 32-byte public key
+ */
+export function derive (k: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>
+export function derive (k: string | Uint8Array<ArrayBuffer>): string | Uint8Array<ArrayBuffer> {
+       return nano25519.derive(k)
+}
+
+/**
+ * Signing using WebAssembly. To sign Nano blocks, the message should be a
+ * 64-character hexadecimal block hash.
+ * @param {string} m - Variable-length hexadecimal message up to 32 KiB
+ * @param {string} k - 128-character hexadecimal secret key (prv + pub)
+ * @returns 128-character hexadecimal detached signature
+ */
+export function sign (m: string, k: string): string
+/**
+ * Signing using WebAssembly. To sign Nano blocks, the message should be a
+ * 32-byte block hash.
+ * @param {Uint8Array<ArrayBuffer>} m - Variable-byte-length message up to 32 KiB
+ * @param {Uint8Array<ArrayBuffer>} k - 64-byte secret key (prv + pub)
+ * @returns 64-byte detached signature
+ */
+export function sign (m: Uint8Array<ArrayBuffer>, k: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>
+export function sign (m: string | Uint8Array<ArrayBuffer>, k: string | Uint8Array<ArrayBuffer>): string | Uint8Array<ArrayBuffer> {
+       return nano25519.sign(m, k)
+}
+
+/**
+ * Signature verification using WebAssembly. To verify Nano block signatures,
+ * the message should be a 64-character block hash.
+ * @param {string} s - 128-character hexadecimal detached signature
+ * @param {string} m - Variable-length message up to 32 KiB
+ * @param {string} k - 64-character hexadecimal public key
+ * @returns true if signature matches block hash and public key, else false
+ */
+export function verify (s: string, m: string, k: string): boolean
+/**
+ * Signature verification using WebAssembly. To verify Nano block signatures,
+ * the message should be a 64-character block hash.
+ * @param {Uint8Array<ArrayBuffer>} s - 64-byte detached signature
+ * @param {Uint8Array<ArrayBuffer>} m - Variable-byte-length message up to 32 KiB
+ * @param {Uint8Array<ArrayBuffer>} k - 32-byte public key
+ * @returns true if signature matches block hash and public key, else false
+ */
+export function verify (s: Uint8Array<ArrayBuffer>, m: Uint8Array<ArrayBuffer>, k: Uint8Array<ArrayBuffer>): boolean
+export function verify (s: string | Uint8Array<ArrayBuffer>, m: string | Uint8Array<ArrayBuffer>, k: string | Uint8Array<ArrayBuffer>): boolean {
+       return nano25519.verify(s, m, k)
+}
index 85af858b0f35c7b442c6d422d06369ea4b2e981c..23325661352f2cde0f091e3701fa130df98bf0f0 100644 (file)
--- a/test.mjs
+++ b/test.mjs
@@ -1,7 +1,9 @@
 //! SPDX-FileCopyrightText: 2026 Chris Duncan <chris@codecow.com>
 //! SPDX-License-Identifier: GPL-3.0-or-later
 
-import { derive, deriveAsync, sign, signAsync, verify, verifyAsync } from 'nano-nacl'
+import * as Nano25519 from 'nano25519'
+import { derive, sign, verify } from 'nano25519/sync'
+import { deriveAsync, signAsync, verifyAsync } from 'nano25519/async'
 import { NANO_ORG_VECTOR, PYTHON_ED25519_BLAKE2B_VECTORS } from './vectors.mjs'
 
 /**
@@ -17,46 +19,143 @@ function check (name, test) {
 
 let result, test, passes = 0, failures = 0
 
-/**
- * @type {any} result
- */
+// Check combined imports
+result = await Nano25519.deriveAsync(NANO_ORG_VECTOR.privateKeyBytes)
+console.log(`RESULT`, result)
+test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === NANO_ORG_VECTOR.publicKey.toLowerCase()
+check(`async derive from ${NANO_ORG_VECTOR.privateKey}`, test)
+passes += +test
+failures += +!test
+
+result = await Nano25519.signAsync(NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.secretKeyBytes)
+test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === NANO_ORG_VECTOR.signature.toLowerCase()
+check(`async sign message ${NANO_ORG_VECTOR.blockHash}`, test)
+passes += +test
+failures += +!test
+
+result = await Nano25519.verifyAsync(NANO_ORG_VECTOR.signatureBytes, NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.publicKeyBytes)
+test = result === true
+check(`async verify signature ${NANO_ORG_VECTOR.signature}`, test)
+passes += +test
+failures += +!test
+
+result = Nano25519.derive(NANO_ORG_VECTOR.privateKeyBytes)
+test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === NANO_ORG_VECTOR.publicKey.toLowerCase()
+check(`derive from ${NANO_ORG_VECTOR.privateKey}`, test)
+passes += +test
+failures += +!test
+
+result = Nano25519.sign(NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.secretKeyBytes)
+test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === NANO_ORG_VECTOR.signature.toLowerCase()
+check(`sign message ${NANO_ORG_VECTOR.blockHash}`, test)
+passes += +test
+failures += +!test
+
+result = Nano25519.verify(NANO_ORG_VECTOR.signatureBytes, NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.publicKeyBytes)
+test = result === true
+check(`verify signature ${NANO_ORG_VECTOR.signature}`, test)
+passes += +test
+failures += +!test
+
+// Check async imports with string inputs
 result = await deriveAsync(NANO_ORG_VECTOR.privateKey)
 test = result.toLowerCase() === NANO_ORG_VECTOR.publicKey.toLowerCase()
-check(`async derive from ${NANO_ORG_VECTOR.privateKey}`, test)
+console.log(result)
+console.log()
+check(`async derive from private key string ${NANO_ORG_VECTOR.privateKey}`, test)
 passes += +test
 failures += +!test
 
 result = await signAsync(NANO_ORG_VECTOR.blockHash, NANO_ORG_VECTOR.secretKey)
 test = result.toLowerCase() === NANO_ORG_VECTOR.signature.toLowerCase()
-check(`async sign message ${NANO_ORG_VECTOR.blockHash}`, test)
+check(`async sign message string ${NANO_ORG_VECTOR.blockHash}`, test)
 passes += +test
 failures += +!test
 
 result = await verifyAsync(NANO_ORG_VECTOR.signature, NANO_ORG_VECTOR.blockHash, NANO_ORG_VECTOR.publicKey)
 test = result === true
-check(`async verify signature ${NANO_ORG_VECTOR.signature}`, test)
+check(`async verify signature string ${NANO_ORG_VECTOR.signature}`, test)
+passes += +test
+failures += +!test
+
+// Check async imports with byte inputs
+result = await deriveAsync(NANO_ORG_VECTOR.privateKeyBytes)
+console.log(`RESULT`, result)
+test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === NANO_ORG_VECTOR.publicKey.toLowerCase()
+check(`async derive from private key bytes ${NANO_ORG_VECTOR.privateKey}`, test)
+passes += +test
+failures += +!test
+
+result = await signAsync(NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.secretKeyBytes)
+test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === NANO_ORG_VECTOR.signature.toLowerCase()
+check(`async sign message bytes ${NANO_ORG_VECTOR.blockHash}`, test)
+passes += +test
+failures += +!test
+
+result = await verifyAsync(NANO_ORG_VECTOR.signatureBytes, NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.publicKeyBytes)
+test = result === true
+check(`async verify signature bytes ${NANO_ORG_VECTOR.signature}`, test)
+passes += +test
+failures += +!test
+
+// Check sync imports with string inputs
+result = derive(NANO_ORG_VECTOR.privateKey)
+test = result.toLowerCase() === NANO_ORG_VECTOR.publicKey.toLowerCase()
+check(`derive from private key string ${NANO_ORG_VECTOR.privateKey}`, test)
+passes += +test
+failures += +!test
+
+result = sign(NANO_ORG_VECTOR.blockHash, NANO_ORG_VECTOR.secretKey)
+test = result.toLowerCase() === NANO_ORG_VECTOR.signature.toLowerCase()
+check(`sign message string ${NANO_ORG_VECTOR.blockHash}`, test)
+passes += +test
+failures += +!test
+
+result = verify(NANO_ORG_VECTOR.signature, NANO_ORG_VECTOR.blockHash, NANO_ORG_VECTOR.publicKey)
+test = result === true
+check(`verify signature string ${NANO_ORG_VECTOR.signature}`, test)
 passes += +test
 failures += +!test
 
+// Check sync imports with byte inputs
 result = derive(NANO_ORG_VECTOR.privateKeyBytes)
 test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === NANO_ORG_VECTOR.publicKey.toLowerCase()
-check(`derive from ${NANO_ORG_VECTOR.privateKey}`, test)
+check(`derive from private key string ${NANO_ORG_VECTOR.privateKey}`, test)
 passes += +test
 failures += +!test
 
 result = sign(NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.secretKeyBytes)
 test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === NANO_ORG_VECTOR.signature.toLowerCase()
-check(`sign message ${NANO_ORG_VECTOR.blockHash}`, test)
+check(`sign message string ${NANO_ORG_VECTOR.blockHash}`, test)
 passes += +test
 failures += +!test
 
 result = verify(NANO_ORG_VECTOR.signatureBytes, NANO_ORG_VECTOR.blockHashBytes, NANO_ORG_VECTOR.publicKeyBytes)
 test = result === true
-check(`verify signature ${NANO_ORG_VECTOR.signature}`, test)
+check(`verify signature string ${NANO_ORG_VECTOR.signature}`, test)
 passes += +test
 failures += +!test
 
+// test both string inputs and byte inputs
 for (const { privateKey, publicKey, message, signature } of PYTHON_ED25519_BLAKE2B_VECTORS) {
+       result = derive(privateKey)
+       test = result.toLowerCase() === publicKey
+       check(`derive from private key string ${privateKey}`, test)
+       passes += +test
+       failures += +!test
+
+       result = sign(message, privateKey + publicKey)
+       test = result.toLowerCase() === signature.slice(0, 128)
+       check(`sign message string ${message}`, test)
+       passes += +test
+       failures += +!test
+
+       result = verify(signature.slice(0, 128), message, publicKey)
+       test = result === true
+       check(`verify signature string ${signature.slice(0, 128)}`, test)
+       passes += +test
+       failures += +!test
+
        const privateKeyBytes = new Uint8Array(privateKey.match(/.{2}/g)?.map(b => parseInt(b, 16)) ?? [])
        const publicKeyBytes = new Uint8Array(publicKey.match(/.{2}/g)?.map(b => parseInt(b, 16)) ?? [])
        const secretKeyBytes = new Uint8Array([...privateKeyBytes, ...publicKeyBytes])
@@ -65,19 +164,19 @@ for (const { privateKey, publicKey, message, signature } of PYTHON_ED25519_BLAKE
 
        result = derive(privateKeyBytes)
        test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === publicKey
-       check(`derive from ${privateKey}`, test)
+       check(`derive from private key bytes ${privateKey}`, test)
        passes += +test
        failures += +!test
 
        result = sign(messageBytes, secretKeyBytes)
        test = [...result].map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase() === signature.slice(0, 128)
-       check(`sign message ${message}`, test)
+       check(`sign message bytes ${message}`, test)
        passes += +test
        failures += +!test
 
        result = verify(signatureBytes, messageBytes, publicKeyBytes)
        test = result === true
-       check(`verify signature ${signature.slice(0, 128)}`, test)
+       check(`verify signature bytes ${signature.slice(0, 128)}`, test)
        passes += +test
        failures += +!test
 }
index 1bf8ee13bd4f4249adaf5bc585a1edbee7b6b50a..4513fd89a9c2ea9b85883aa784b7ff2fdc3004d1 100644 (file)
                        "ESNext"
                ]
        },
+       "include": [
+               "src/index.ts",
+               "src/async.ts",
+               "src/sync.ts"
+       ],
        "exclude": [
                "assembly",
                "dist"