]> git.codecow.com Git - nano25519.git/commitdiff
Revert to array-based word copy with endianness check since hardened getUint32 is...
authorChris Duncan <chris@zoso.dev>
Sun, 27 Sep 2026 03:00:36 +0000 (20:00 -0700)
committerChris Duncan <chris@zoso.dev>
Sun, 27 Sep 2026 03:00:36 +0000 (20:00 -0700)
src/lib/primordials.ts
src/lib/wasm.ts
test/node.mjs

index 2d44108e756f50df2bbbfd84102ad7a51b99e830..101ff485aa165c43a24187d8f6a8690230ed9f98 100644 (file)
 
 /** Type alias for legibility. */
 export type Bytes = Uint8Array<ArrayBuffer>
-export type View = DataView<ArrayBuffer>
-export const isArray = Array.isArray
+export type Words = Uint32Array<ArrayBuffer>
 
 /** `TypedArray` constructors, bound before anything can replace the globals. */
 const Bytes = Uint8Array
+const Words = Uint32Array
 const TypedArray = Object.getPrototypeOf(Bytes.prototype)
-const View = DataView
 
 const bytesSet = Function.prototype.call.bind(Bytes.prototype.set)
 const bytesFill = Function.prototype.call.bind(Bytes.prototype.fill)
@@ -112,6 +111,9 @@ export function fill (target: Uint8Array, value: number, start?: number, end?: n
        bytesFill(target, value, start, end)
 }
 
+/** Captured version of `Array.isArray(value)`. */
+export const isArray = Array.isArray
+
 /**
  * True only for a genuine `Uint8Array` backed by a non-shared `ArrayBuffer`.
  * Reads internal slot `[[TypedArrayName]]`, so a subclass overriding
@@ -121,24 +123,27 @@ export function isBytes (a: unknown): a is Bytes {
        return getTag(a) === 'Uint8Array' && isArrayBuffer(getBuffer(a))
 }
 
+/** Checks system endianness. Returns true if little-endian, else false. */
+export const isLE = new Bytes(getBuffer(new Words([1])))[0] === 1
+
 /**
- * Captured version of `DataView` reusing input offset and length.
- * Enables reading multiple bytes in a single
- * load which is more efficient than reading and combining individual bytes
- * with bitshifts. The caller is responsible for managing endianness.
+ * Captured version of `Uint32Array` reusing input offset and length.
+ * Enables reading multiple bytes in a single load which is more efficient than
+ * reading and combining individual bytes with bitshifts. The caller is
+ * responsible for managing endianness.
  *
  * Every boundary-crossing buffer comes from here or `normalize()`, so the
  * offset is always 0 in practice; the offset check is a guard against mishap.
  */
-export function view (bytes: Bytes): View {
+export function words (bytes: Bytes): Words {
        if (!isBytes(bytes)) {
-               throw new TypeError('Error creating DataView', { cause: bytes })
+               throw new TypeError('Error creating Uint32Array', { cause: bytes })
        }
        const offset = getByteOffset(bytes)
        if (offset !== 0) {
                throw new RangeError('Unexpected byte offset', { cause: offset })
        }
-       return new View(getBuffer(bytes) as ArrayBuffer, offset, getByteLength(bytes))
+       return new Words(getBuffer(bytes) as ArrayBuffer, offset, getByteLength(bytes) >> 2)
 }
 
 const LOWER = '0123456789abcdef'
index 15d4ee365a21f5852e097b40298c87c57ebc7932..595540ea6cdc4c7b20928f8d8ce2a94f9e8ccf61 100644 (file)
@@ -4,7 +4,7 @@
 //@ts-expect-error
 import nano25519_wasm from '../../build/nano25519.wasm'
 import { Nano25519MutexError, Nano25519TypeError, abort, errorCodes, } from './errors'
-import { Bytes, View, byteLength, copy, view, } from './primordials'
+import { Bytes, Words, byteLength, copy, isLE, words } from './primordials'
 
 type Exports = WebAssembly.Instance['exports'] & {
        clearMemory: () => void
@@ -92,7 +92,7 @@ const MAX_MESSAGE_BYTELENGTH = exports.MAX_MESSAGE_BYTELENGTH.value
  * This is the path for partial words. It runs for the short final chunk of any
  * message and ensures little-endian byte order and zero-padded filler.
  */
-function setInputMsgEnd (m: Bytes, i: number): void {
+function setInputMsgShift (m: Bytes, i: number): void {
        exports.setInputMsg(i,
                (m[i] ?? 0) | ((m[i + 1] ?? 0) << 8) | ((m[i + 2] ?? 0) << 16) | ((m[i + 3] ?? 0) << 24),
                (m[i + 4] ?? 0) | ((m[i + 5] ?? 0) << 8) | ((m[i + 6] ?? 0) << 16) | ((m[i + 7] ?? 0) << 24),
@@ -105,6 +105,22 @@ function setInputMsgEnd (m: Bytes, i: number): void {
        )
 }
 
+function setInputMsgWords (m: Words, i: number): void {
+       const p = i >> 2
+       exports.setInputMsg(i,
+               m[p],
+               m[p + 1],
+               m[p + 2],
+               m[p + 3],
+               m[p + 4],
+               m[p + 5],
+               m[p + 6],
+               m[p + 7]
+       )
+}
+
+const setInputMsgEndian = isLE ? setInputMsgWords : setInputMsgShift
+
 /**
  * Copies the bytes of a message for signing, or signature verification, into
  * the static message input buffer of the WASM module. The entire input is
@@ -122,21 +138,13 @@ function setInputMsgEnd (m: Bytes, i: number): void {
 function setInputMsg (message: Bytes): void {
        const mlen = byteLength(message)
        const mlentrunc = mlen & ~31
-       const m = view(message)
+       const m = words(message)
        for (let i = 0; i < mlentrunc; i += 32) {
-               exports.setInputMsg(i,
-                       m.getUint32(i, true),
-                       m.getUint32(i + 4, true),
-                       m.getUint32(i + 8, true),
-                       m.getUint32(i + 12, true),
-                       m.getUint32(i + 16, true),
-                       m.getUint32(i + 20, true),
-                       m.getUint32(i + 24, true),
-                       m.getUint32(i + 28, true)
-               )
+               //@ts-expect-error
+               setInputMsgEndian(m, i)
        }
        if (mlentrunc < mlen) {
-               setInputMsgEnd(message, mlentrunc)
+               setInputMsgShift(message, mlentrunc)
        }
 }
 
index 519d936ee42e62bcd0bd869f5a12351631210835..b37e4597473b791e8fb3cafdceddd10db29bc21b 100644 (file)
@@ -656,6 +656,11 @@ const patchedBlocks = [{ hash: NANO_ORG_VECTOR.blockHashBytes, signature: NANO_O
 const TypedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype)
 /** @type {[string, (() => () => void)][]} */
 const patches = [
+       ['Function.prototype.call', () => {
+               const real = Function.prototype.call
+               Function.prototype.call = () => { throw new Error('patched') }
+               return () => { Function.prototype.call = real }
+       }],
        ['Uint8Array.prototype.slice', () => {
                const real = Uint8Array.prototype.slice
                Uint8Array.prototype.slice = () => { throw new Error('patched') }