feat: remove Buffer support and bundle polyfill (#2526)

- Removes need for bundlers to polyfill the `Buffer` class. `UInt8Array` are used instead which are native to the browser and node.
- Reduces bundle size 7.1kb gzipped and eliminates 4 runtime dependencies: `base-x`, `base64-js`, `buffer`, and `ieee754`.

BREAKING CHANGE: All methods that previously took a `Buffer` now accept a `UInt8Array`.

---------

Co-authored-by: Jackson Mills <jmills@ripple.com>
This commit is contained in:
Caleb Kniffen
2023-11-30 09:32:28 -06:00
parent eefb52a9cb
commit 38b385969b
77 changed files with 853 additions and 666 deletions

View File

@@ -1,6 +1,6 @@
# Unique Setup Steps for Xrpl.js # Unique Setup Steps for Xrpl.js
For when you need to do more than just install `xrpl.js` for it to work (especially for React projects in the browser). Starting in 3.0 xrpl and all the packages in this repo no longer require custom configurations (ex. polyfills) to run.
### Using xrpl.js from a CDN ### Using xrpl.js from a CDN
@@ -13,48 +13,7 @@ Ensure that the full path is provided so the browser can find the sourcemaps.
### Using xrpl.js with `create-react-app` ### Using xrpl.js with `create-react-app`
To use `xrpl.js` with React, you need to install shims for core NodeJS modules. Starting with version 5, Webpack stopped including shims by default, so you must modify your Webpack configuration to add the shims you need. Either you can eject your config and modify it, or you can use a library such as `react-app-rewired`. The example below uses `react-app-rewired`. Starting in 3.0 xrpl and its related packages no longer require custom configurations (ex. polyfills) to run.
1. Install shims (you can use `yarn` as well):
```shell
npm install --save-dev \
buffer \
process
```
2. Modify your webpack configuration
1. Install `react-app-rewired`
```shell
npm install --save-dev react-app-rewired
```
2. At the project root, add a file named `config-overrides.js` with the following content:
```javascript
const webpack = require("webpack");
module.exports = function override(config) {
config.plugins = (config.plugins || []).concat([
new webpack.ProvidePlugin({
process: "process/browser",
Buffer: ["buffer", "Buffer"],
}),
]);
return config;
};
```
3. Update package.json scripts section with
```
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
```
This online template uses these steps to run xrpl.js with React in the browser: This online template uses these steps to run xrpl.js with React in the browser:
https://codesandbox.io/s/xrpl-intro-pxgdjr?file=/src/App.js https://codesandbox.io/s/xrpl-intro-pxgdjr?file=/src/App.js
@@ -97,54 +56,7 @@ import './polyfills'
### Using xrpl.js with Vite React ### Using xrpl.js with Vite React
Similar to above, to get xrpl.js to work with Vite you need to set up a couple aliases in the vite.config.ts file. Starting in 3.0 xrpl and all the packages in this repo no longer require custom configurations (ex. polyfills) to run.
1. If it's a fresh project you can use `npm create vite@latest` then choose the React and TypeScript options.
2. Copy these settings into your `vite.config.ts` file.
```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { NodeGlobalsPolyfillPlugin } from "@esbuild-plugins/node-globals-polyfill";
import polyfillNode from 'rollup-plugin-polyfill-node'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
define: {
'process.env': {}
},
optimizeDeps: {
esbuildOptions: {
define: {
global: 'globalThis',
},
plugins: [
NodeGlobalsPolyfillPlugin({
process: true,
buffer: true,
}),
],
},
},
build: {
rollupOptions: {
plugins: [
polyfillNode(),
]
}
},
})
```
3. Install the config dependencies and xrpl (e.g. using this command)
```shell
npm install --save-dev @esbuild-plugins/node-globals-polyfill \
rollup-plugin-polyfill-node \
&& npm install xrpl
```
### Using xrpl.js with Deno ### Using xrpl.js with Deno

111
package-lock.json generated
View File

@@ -26,7 +26,6 @@
"@typescript-eslint/parser": "^5.28.0", "@typescript-eslint/parser": "^5.28.0",
"@xrplf/eslint-config": "^1.9.1", "@xrplf/eslint-config": "^1.9.1",
"@xrplf/prettier-config": "^1.9.1", "@xrplf/prettier-config": "^1.9.1",
"buffer": "^6.0.2",
"chai": "^4.3.4", "chai": "^4.3.4",
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"eslint": "^8.18.0", "eslint": "^8.18.0",
@@ -4223,33 +4222,6 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true "dev": true
}, },
"node_modules/base-x": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz",
"integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/base64id": { "node_modules/base64id": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
@@ -4420,29 +4392,6 @@
"node-int64": "^0.4.0" "node-int64": "^0.4.0"
} }
}, },
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/buffer-from": { "node_modules/buffer-from": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -7836,25 +7785,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/ignore": { "node_modules/ignore": {
"version": "5.2.4", "version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@@ -12857,6 +12787,7 @@
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -15365,8 +15296,8 @@
"version": "5.0.0-beta.0", "version": "5.0.0-beta.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@xrplf/isomorphic": "^1.0.0-beta.0", "@scure/base": "^1.1.3",
"base-x": "^3.0.9" "@xrplf/isomorphic": "^1.0.0-beta.0"
}, },
"engines": { "engines": {
"node": ">= 16" "node": ">= 16"
@@ -15378,7 +15309,6 @@
"dependencies": { "dependencies": {
"@xrplf/isomorphic": "^1.0.0-beta.0", "@xrplf/isomorphic": "^1.0.0-beta.0",
"bignumber.js": "^9.0.0", "bignumber.js": "^9.0.0",
"buffer": "6.0.3",
"ripple-address-codec": "^5.0.0-beta.0" "ripple-address-codec": "^5.0.0-beta.0"
}, },
"engines": { "engines": {
@@ -18870,19 +18800,6 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true "dev": true
}, },
"base-x": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz",
"integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==",
"requires": {
"safe-buffer": "^5.0.1"
}
},
"base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"base64id": { "base64id": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
@@ -19010,15 +18927,6 @@
"node-int64": "^0.4.0" "node-int64": "^0.4.0"
} }
}, },
"buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"requires": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"buffer-from": { "buffer-from": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -21619,11 +21527,6 @@
"safer-buffer": ">= 2.1.2 < 3" "safer-buffer": ">= 2.1.2 < 3"
} }
}, },
"ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
},
"ignore": { "ignore": {
"version": "5.2.4", "version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@@ -25453,8 +25356,8 @@
"ripple-address-codec": { "ripple-address-codec": {
"version": "file:packages/ripple-address-codec", "version": "file:packages/ripple-address-codec",
"requires": { "requires": {
"@xrplf/isomorphic": "^1.0.0-beta.0", "@scure/base": "^1.1.3",
"base-x": "^3.0.9" "@xrplf/isomorphic": "^1.0.0-beta.0"
} }
}, },
"ripple-binary-codec": { "ripple-binary-codec": {
@@ -25462,7 +25365,6 @@
"requires": { "requires": {
"@xrplf/isomorphic": "^1.0.0-beta.0", "@xrplf/isomorphic": "^1.0.0-beta.0",
"bignumber.js": "^9.0.0", "bignumber.js": "^9.0.0",
"buffer": "6.0.3",
"ripple-address-codec": "^5.0.0-beta.0" "ripple-address-codec": "^5.0.0-beta.0"
} }
}, },
@@ -25519,7 +25421,8 @@
"safe-buffer": { "safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true
}, },
"safe-regex-test": { "safe-regex-test": {
"version": "1.0.0", "version": "1.0.0",

View File

@@ -31,7 +31,6 @@
"@typescript-eslint/parser": "^5.28.0", "@typescript-eslint/parser": "^5.28.0",
"@xrplf/eslint-config": "^1.9.1", "@xrplf/eslint-config": "^1.9.1",
"@xrplf/prettier-config": "^1.9.1", "@xrplf/prettier-config": "^1.9.1",
"buffer": "^6.0.2",
"chai": "^4.3.4", "chai": "^4.3.4",
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"eslint": "^8.18.0", "eslint": "^8.18.0",

View File

@@ -1,5 +1,11 @@
# @xrplf/isomorphic Release History # @xrplf/isomorphic Release History
## Unreleased
## Added
- hexToString
- stringToHex
## 1.0.0 Beta 1 (2023-10-19) ## 1.0.0 Beta 1 (2023-10-19)
Initial release providing isomorphic and tree-shakable implementations of: Initial release providing isomorphic and tree-shakable implementations of:

View File

@@ -95,6 +95,26 @@ import { hexToBytes } from @xrplf/isomorphic/utils
console.log(hexToBytes('DEADBEEF')) // [222, 173, 190, 239] console.log(hexToBytes('DEADBEEF')) // [222, 173, 190, 239]
``` ```
#### hexToString
Converts hex to its string equivalent. Useful to read the Domain field and some Memos.
```typescript
import { hexToString } from @xrplf/isomorphic/utils
console.log(hexToString('6465616462656566D68D')) // "deadbeef֍"
```
#### stringToHex
Converts a utf-8 to its hex equivalent. Useful for Memos.
```typescript
import { stringToHex } from @xrplf/isomorphic/utils
console.log(stringToHex('deadbeef֍')) // "6465616462656566D68D"
```
### `@xrplf/isomorphic/ws` ### `@xrplf/isomorphic/ws`
```typescript ```typescript

View File

@@ -1,11 +1,16 @@
import { import {
bytesToHex as nobleBytesToHex, bytesToHex as nobleBytesToHex,
hexToBytes as nobleHexToBytes,
randomBytes as nobleRandomBytes, randomBytes as nobleRandomBytes,
} from '@noble/hashes/utils' } from '@noble/hashes/utils'
import type { BytesToHexFn, HexToBytesFn, RandomBytesFn } from './types' import type {
BytesToHexFn,
HexToBytesFn,
HexToStringFn,
RandomBytesFn,
StringToHexFn,
} from './types'
/* eslint-disable-next-line func-style -- Typed to ensure uniformity between node and browser implementations and docs */ /* eslint-disable func-style -- Typed to ensure uniformity between node and browser implementations and docs */
export const bytesToHex: typeof BytesToHexFn = (bytes) => { export const bytesToHex: typeof BytesToHexFn = (bytes) => {
const hex = nobleBytesToHex( const hex = nobleBytesToHex(
bytes instanceof Uint8Array ? bytes : Uint8Array.from(bytes), bytes instanceof Uint8Array ? bytes : Uint8Array.from(bytes),
@@ -13,5 +18,33 @@ export const bytesToHex: typeof BytesToHexFn = (bytes) => {
return hex.toUpperCase() return hex.toUpperCase()
} }
export const hexToBytes: typeof HexToBytesFn = nobleHexToBytes // A clone of hexToBytes from @noble/hashes without the length checks. This allows us to do our own checks.
export const hexToBytes: typeof HexToBytesFn = (hex): Uint8Array => {
const len = hex.length
const array = new Uint8Array(len / 2)
for (let i = 0; i < array.length; i++) {
const j = i * 2
const hexByte = hex.slice(j, j + 2)
const byte = Number.parseInt(hexByte, 16)
if (Number.isNaN(byte) || byte < 0) {
throw new Error('Invalid byte sequence')
}
array[i] = byte
}
return array
}
export const hexToString: typeof HexToStringFn = (
hex: string,
encoding = 'utf8',
): string => {
return new TextDecoder(encoding).decode(hexToBytes(hex))
}
export const stringToHex: typeof StringToHexFn = (string: string): string => {
return bytesToHex(new TextEncoder().encode(string))
}
/* eslint-enable func-style */
export const randomBytes: typeof RandomBytesFn = nobleRandomBytes export const randomBytes: typeof RandomBytesFn = nobleRandomBytes
export * from './shared'

View File

@@ -1,5 +1,6 @@
import { randomBytes as cryptoRandomBytes } from 'crypto' import { randomBytes as cryptoRandomBytes } from 'crypto'
import type { BytesToHexFn, HexToBytesFn, RandomBytesFn } from './types' import type { BytesToHexFn, HexToBytesFn, RandomBytesFn } from './types'
import { HexToStringFn, StringToHexFn } from './types'
const OriginalBuffer = Symbol('OriginalBuffer') const OriginalBuffer = Symbol('OriginalBuffer')
@@ -69,4 +70,17 @@ export const hexToBytes: typeof HexToBytesFn = (hex) => {
export const randomBytes: typeof RandomBytesFn = (size) => { export const randomBytes: typeof RandomBytesFn = (size) => {
return toUint8Array(cryptoRandomBytes(size)) return toUint8Array(cryptoRandomBytes(size))
} }
export const hexToString: typeof HexToStringFn = (
hex: string,
encoding = 'utf8',
): string => {
return new TextDecoder(encoding).decode(hexToBytes(hex))
}
export const stringToHex: typeof StringToHexFn = (string: string): string => {
return bytesToHex(new TextEncoder().encode(string))
}
/* eslint-enable func-style */ /* eslint-enable func-style */
export * from './shared'

View File

@@ -0,0 +1,19 @@
import { concatBytes } from '@noble/hashes/utils'
export function concat(views: Uint8Array[]): Uint8Array {
return concatBytes(...views)
}
export function equal(buf1: Uint8Array, buf2: Uint8Array): boolean {
if (buf1.byteLength !== buf2.byteLength) {
return false
}
const dv1 = new Int8Array(buf1)
const dv2 = new Int8Array(buf2)
for (let i = 0; i !== buf1.byteLength; i++) {
if (dv1[i] !== dv2[i]) {
return false
}
}
return true
}

View File

@@ -18,3 +18,20 @@ export declare function HexToBytesFn(hex: string): Uint8Array
* @param size - number of bytes to generate * @param size - number of bytes to generate
*/ */
export declare function RandomBytesFn(size: number): Uint8Array export declare function RandomBytesFn(size: number): Uint8Array
/**
* Converts hex to its string equivalent. Useful to read the Domain field and some Memos.
*
* @param hex - The hex to convert to a string.
* @param encoding - The encoding to use. Defaults to 'utf8' (UTF-8). 'ascii' is also allowed.
* @returns The converted string.
*/
export declare function HexToStringFn(hex: string, encoding?: string): string
/**
* Converts a utf-8 to its hex equivalent. Useful for Memos.
*
* @param string - The string to convert to Hex.
* @returns The Hex equivalent of the string.
*/
export declare function StringToHexFn(string: string): string

View File

@@ -9,7 +9,7 @@ declare class WebSocket {
public onmessage?: (message: MessageEvent) => void public onmessage?: (message: MessageEvent) => void
public readyState: number public readyState: number
public constructor(url: string) public constructor(url: string)
public close(code?: number, reason?: Buffer): void public close(code?: number, reason?: Uint8Array): void
public send(message: string): void public send(message: string): void
} }

View File

@@ -1,4 +1,10 @@
import { bytesToHex, hexToBytes, randomBytes } from '../utils' import {
bytesToHex,
hexToBytes,
hexToString,
randomBytes,
stringToHex,
} from '../utils'
describe('utils', function () { describe('utils', function () {
it('randomBytes', () => { it('randomBytes', () => {
@@ -28,4 +34,16 @@ describe('utils', function () {
it('bytesToHex - DEADBEEF (Uint8Array)', () => { it('bytesToHex - DEADBEEF (Uint8Array)', () => {
expect(bytesToHex(new Uint8Array([222, 173, 190, 239]))).toEqual('DEADBEEF') expect(bytesToHex(new Uint8Array([222, 173, 190, 239]))).toEqual('DEADBEEF')
}) })
it('hexToString - deadbeef+infinity symbol (HEX ASCII)', () => {
expect(hexToString('646561646265656658D', 'ascii')).toEqual('deadbeefX')
})
it('hexToString - deadbeef+infinity symbol (HEX)', () => {
expect(hexToString('6465616462656566D68D')).toEqual('deadbeef֍')
})
it('stringToHex - deadbeef+infinity symbol (utf8)', () => {
expect(stringToHex('deadbeef֍')).toEqual('6465616462656566D68D')
})
}) })

View File

@@ -2,6 +2,12 @@
## Unreleased ## Unreleased
### Breaking Changes
* `Buffer` has been replaced with `UInt8Array` for both params and return values. `Buffer` may continue to work with params since they extend `UInt8Arrays`.
### Changes
* Eliminates 4 runtime dependencies: `base-x`, `base64-js`, `buffer`, and `ieee754`.
## 5.0.0 Beta 1 ## 5.0.0 Beta 1
### Breaking Changes ### Breaking Changes

View File

@@ -67,7 +67,7 @@ Check whether a classic address (starting with `r`...) is valid.
Returns `false` for X-addresses (extended addresses). To validate an X-address, use `isValidXAddress`. Returns `false` for X-addresses (extended addresses). To validate an X-address, use `isValidXAddress`.
### encodeSeed(entropy: Buffer, type: 'ed25519' | 'secp256k1'): string ### encodeSeed(entropy: UInt8Array, type: 'ed25519' | 'secp256k1'): string
Encode the given entropy as an XRP Ledger seed (secret). The entropy must be exactly 16 bytes (128 bits). The encoding includes which elliptic curve digital signature algorithm (ECDSA) the seed is intended to be used with. The seed is used to produce the private key. Encode the given entropy as an XRP Ledger seed (secret). The entropy must be exactly 16 bytes (128 bits). The encoding includes which elliptic curve digital signature algorithm (ECDSA) the seed is intended to be used with. The seed is used to produce the private key.
@@ -79,38 +79,38 @@ Return object type:
``` ```
{ {
version: number[], version: number[],
bytes: Buffer, bytes: UInt8Array,
type: string | null type: string | null
} }
``` ```
### encodeAccountID(bytes: Buffer): string ### encodeAccountID(bytes: UInt8Array): string
Encode bytes as a classic address (starting with `r`...). Encode bytes as a classic address (starting with `r`...).
### decodeAccountID(accountId: string): Buffer ### decodeAccountID(accountId: string): UInt8Array
Decode a classic address (starting with `r`...) to its raw bytes. Decode a classic address (starting with `r`...) to its raw bytes.
### encodeNodePublic(bytes: Buffer): string ### encodeNodePublic(bytes: UInt8Array): string
Encode bytes to the XRP Ledger "node public key" format (base58). Encode bytes to the XRP Ledger "node public key" format (base58).
This is useful for rippled validators. This is useful for rippled validators.
### decodeNodePublic(base58string: string): Buffer ### decodeNodePublic(base58string: string): UInt8Array
Decode an XRP Ledger "node public key" (in base58 format) into its raw bytes. Decode an XRP Ledger "node public key" (in base58 format) into its raw bytes.
### encodeAccountPublic(bytes: Buffer): string ### encodeAccountPublic(bytes: UInt8Array): string
Encode a public key, as for payment channels. Encode a public key, as for payment channels.
### decodeAccountPublic(base58string: string): Buffer ### decodeAccountPublic(base58string: string): UInt8Array
Decode a public key, as for payment channels. Decode a public key, as for payment channels.
### encodeXAddress(accountId: Buffer, tag: number | false, test: boolean): string ### encodeXAddress(accountId: UInt8Array, tag: number | false, test: boolean): string
Encode account ID, tag, and network ID to X-address. Encode account ID, tag, and network ID to X-address.
@@ -120,7 +120,7 @@ At this time, `tag` must be <= MAX_32_BIT_UNSIGNED_INT (4294967295) as the XRP L
If `test` is `true`, this address is intended for use with a test network such as Testnet or Devnet. If `test` is `true`, this address is intended for use with a test network such as Testnet or Devnet.
### decodeXAddress(xAddress: string): {accountId: Buffer, tag: number | false, test: boolean} ### decodeXAddress(xAddress: string): {accountId: UInt8Array, tag: number | false, test: boolean}
Convert an X-address to its classic address, tag, and network ID. Convert an X-address to its classic address, tag, and network ID.

View File

@@ -11,7 +11,7 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@xrplf/isomorphic": "^1.0.0-beta.0", "@xrplf/isomorphic": "^1.0.0-beta.0",
"base-x": "^3.0.9" "@scure/base": "^1.1.3"
}, },
"keywords": [ "keywords": [
"ripple", "ripple",

View File

@@ -1,3 +1,5 @@
import { concat, equal, hexToBytes } from '@xrplf/isomorphic/utils'
import { import {
codec, codec,
encodeSeed, encodeSeed,
@@ -13,9 +15,9 @@ import {
const PREFIX_BYTES = { const PREFIX_BYTES = {
// 5, 68 // 5, 68
main: Buffer.from([0x05, 0x44]), main: Uint8Array.from([0x05, 0x44]),
// 4, 147 // 4, 147
test: Buffer.from([0x04, 0x93]), test: Uint8Array.from([0x04, 0x93]),
} }
const MAX_32_BIT_UNSIGNED_INT = 4294967295 const MAX_32_BIT_UNSIGNED_INT = 4294967295
@@ -30,7 +32,7 @@ function classicAddressToXAddress(
} }
function encodeXAddress( function encodeXAddress(
accountId: Buffer, accountId: Uint8Array,
tag: number | false, tag: number | false,
test: boolean, test: boolean,
): string { ): string {
@@ -46,10 +48,10 @@ function encodeXAddress(
const flag = tag === false || tag == null ? 0 : 1 const flag = tag === false || tag == null ? 0 : 1
/* eslint-disable no-bitwise --- /* eslint-disable no-bitwise ---
* need to use bitwise operations here */ * need to use bitwise operations here */
const bytes = Buffer.concat([ const bytes = concat([
test ? PREFIX_BYTES.test : PREFIX_BYTES.main, test ? PREFIX_BYTES.test : PREFIX_BYTES.main,
accountId, accountId,
Buffer.from([ Uint8Array.from([
// 0x00 if no tag, 0x01 if 32-bit tag // 0x00 if no tag, 0x01 if 32-bit tag
flag, flag,
// first byte // first byte
@@ -90,7 +92,7 @@ function xAddressToClassicAddress(xAddress: string): {
} }
function decodeXAddress(xAddress: string): { function decodeXAddress(xAddress: string): {
accountId: Buffer accountId: Uint8Array
tag: number | false tag: number | false
test: boolean test: boolean
} { } {
@@ -98,10 +100,10 @@ function decodeXAddress(xAddress: string): {
/* eslint-disable @typescript-eslint/naming-convention -- /* eslint-disable @typescript-eslint/naming-convention --
* TODO 'test' should be something like 'isTest', do this later * TODO 'test' should be something like 'isTest', do this later
*/ */
const test = isBufferForTestAddress(decoded) const test = isUint8ArrayForTestAddress(decoded)
/* eslint-enable @typescript-eslint/naming-convention */ /* eslint-enable @typescript-eslint/naming-convention */
const accountId = decoded.slice(2, 22) const accountId = decoded.slice(2, 22)
const tag = tagFromBuffer(decoded) const tag = tagFromUint8Array(decoded)
return { return {
accountId, accountId,
tag, tag,
@@ -109,19 +111,19 @@ function decodeXAddress(xAddress: string): {
} }
} }
function isBufferForTestAddress(buf: Buffer): boolean { function isUint8ArrayForTestAddress(buf: Uint8Array): boolean {
const decodedPrefix = buf.slice(0, 2) const decodedPrefix = buf.slice(0, 2)
if (PREFIX_BYTES.main.equals(decodedPrefix)) { if (equal(PREFIX_BYTES.main, decodedPrefix)) {
return false return false
} }
if (PREFIX_BYTES.test.equals(decodedPrefix)) { if (equal(PREFIX_BYTES.test, decodedPrefix)) {
return true return true
} }
throw new Error('Invalid X-address: bad prefix') throw new Error('Invalid X-address: bad prefix')
} }
function tagFromBuffer(buf: Buffer): number | false { function tagFromUint8Array(buf: Uint8Array): number | false {
const flag = buf[22] const flag = buf[22]
if (flag >= 2) { if (flag >= 2) {
// No support for 64-bit tags at this time // No support for 64-bit tags at this time
@@ -134,7 +136,7 @@ function tagFromBuffer(buf: Buffer): number | false {
if (flag !== 0) { if (flag !== 0) {
throw new Error('flag must be zero to indicate no tag') throw new Error('flag must be zero to indicate no tag')
} }
if (!Buffer.from('0000000000000000', 'hex').equals(buf.slice(23, 23 + 8))) { if (!equal(hexToBytes('0000000000000000'), buf.slice(23, 23 + 8))) {
throw new Error('remaining bytes must be zero') throw new Error('remaining bytes must be zero')
} }
return false return false

View File

@@ -1,6 +1,4 @@
// Buffer is technically not needed, as a Buffer IS a Uint8Array. export type ByteArray = number[] | Uint8Array
// However, for communication purposes it's listed here
export type ByteArray = number[] | Uint8Array | Buffer
/** /**
* Check whether two sequences (e.g. Arrays of numbers) are equal. * Check whether two sequences (e.g. Arrays of numbers) are equal.
@@ -29,7 +27,7 @@ function isScalar(val: ByteArray | number): val is number {
* a single element or a sequence, which has a `length` property and supports * a single element or a sequence, which has a `length` property and supports
* element retrieval via sequence[ix]. * element retrieval via sequence[ix].
* *
* > concatArgs(1, [2, 3], Buffer.from([4,5]), new Uint8Array([6, 7])); * > concatArgs(1, [2, 3], Uint8Array.from([4,5]), new Uint8Array([6, 7]));
* [1,2,3,4,5,6,7] * [1,2,3,4,5,6,7]
* *
* @param args - Concatenate of these args into a single array. * @param args - Concatenate of these args into a single array.

View File

@@ -2,30 +2,24 @@
* Codec class * Codec class
*/ */
import { base58xrp, BytesCoder } from '@scure/base'
import { sha256 } from '@xrplf/isomorphic/sha256' import { sha256 } from '@xrplf/isomorphic/sha256'
import baseCodec = require('base-x')
import type { BaseConverter } from 'base-x'
import { arrayEqual, concatArgs, ByteArray } from './utils' import { arrayEqual, concatArgs, ByteArray } from './utils'
class Codec { class Codec {
private readonly _sha256: (bytes: ByteArray) => Uint8Array private readonly _sha256: (bytes: ByteArray) => Uint8Array
private readonly _alphabet: string private readonly _codec: BytesCoder
private readonly _codec: BaseConverter
public constructor(options: { public constructor(options: { sha256: (bytes: ByteArray) => Uint8Array }) {
sha256: (bytes: ByteArray) => Uint8Array
alphabet: string
}) {
this._sha256 = options.sha256 this._sha256 = options.sha256
this._alphabet = options.alphabet this._codec = base58xrp
this._codec = baseCodec(this._alphabet)
} }
/** /**
* Encoder. * Encoder.
* *
* @param bytes - Buffer of data to encode. * @param bytes - Uint8Array of data to encode.
* @param opts - Options object including the version bytes and the expected length of the data to encode. * @param opts - Options object including the version bytes and the expected length of the data to encode.
*/ */
public encode( public encode(
@@ -56,7 +50,7 @@ class Codec {
}, },
): { ): {
version: number[] version: number[]
bytes: Buffer bytes: Uint8Array
type: 'ed25519' | 'secp256k1' | null type: 'ed25519' | 'secp256k1' | null
} { } {
const versions = opts.versions const versions = opts.versions
@@ -97,20 +91,20 @@ class Codec {
) )
} }
public encodeChecked(buffer: ByteArray): string { public encodeChecked(bytes: ByteArray): string {
const check = this._sha256(this._sha256(buffer)).slice(0, 4) const check = this._sha256(this._sha256(bytes)).slice(0, 4)
return this._encodeRaw(Buffer.from(concatArgs(buffer, check))) return this._encodeRaw(Uint8Array.from(concatArgs(bytes, check)))
} }
public decodeChecked(base58string: string): Buffer { public decodeChecked(base58string: string): Uint8Array {
const buffer = this._decodeRaw(base58string) const intArray = this._decodeRaw(base58string)
if (buffer.length < 5) { if (intArray.byteLength < 5) {
throw new Error('invalid_input_size: decoded data must have length >= 5') throw new Error('invalid_input_size: decoded data must have length >= 5')
} }
if (!this._verifyCheckSum(buffer)) { if (!this._verifyCheckSum(intArray)) {
throw new Error('checksum_invalid') throw new Error('checksum_invalid')
} }
return buffer.slice(0, -4) return intArray.slice(0, -4)
} }
private _encodeVersioned( private _encodeVersioned(
@@ -118,21 +112,21 @@ class Codec {
versions: number[], versions: number[],
expectedLength: number, expectedLength: number,
): string { ): string {
if (expectedLength && bytes.length !== expectedLength) { if (!checkByteLength(bytes, expectedLength)) {
throw new Error( throw new Error(
'unexpected_payload_length: bytes.length does not match expectedLength.' + 'unexpected_payload_length: bytes.length does not match expectedLength.' +
' Ensure that the bytes are a Buffer.', ' Ensure that the bytes are a Uint8Array.',
) )
} }
return this.encodeChecked(concatArgs(versions, bytes)) return this.encodeChecked(concatArgs(versions, bytes))
} }
private _encodeRaw(bytes: ByteArray): string { private _encodeRaw(bytes: ByteArray): string {
return this._codec.encode(bytes) return this._codec.encode(Uint8Array.from(bytes))
} }
/* eslint-enable max-lines-per-function */ /* eslint-enable max-lines-per-function */
private _decodeRaw(base58string: string): Buffer { private _decodeRaw(base58string: string): Uint8Array {
return this._codec.decode(base58string) return this._codec.decode(base58string)
} }
@@ -162,20 +156,19 @@ const ED25519_SEED = [0x01, 0xe1, 0x4b]
const codecOptions = { const codecOptions = {
sha256, sha256,
alphabet: 'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz',
} }
const codecWithXrpAlphabet = new Codec(codecOptions) const codecWithXrpAlphabet = new Codec(codecOptions)
export const codec = codecWithXrpAlphabet export const codec = codecWithXrpAlphabet
// entropy is a Buffer of size 16 // entropy is a Uint8Array of size 16
// type is 'ed25519' or 'secp256k1' // type is 'ed25519' or 'secp256k1'
export function encodeSeed( export function encodeSeed(
entropy: ByteArray, entropy: ByteArray,
type: 'ed25519' | 'secp256k1', type: 'ed25519' | 'secp256k1',
): string { ): string {
if (entropy.length !== 16) { if (!checkByteLength(entropy, 16)) {
throw new Error('entropy must have length 16') throw new Error('entropy must have length 16')
} }
const opts = { const opts = {
@@ -202,7 +195,7 @@ export function decodeSeed(
}, },
): { ): {
version: number[] version: number[]
bytes: Buffer bytes: Uint8Array
type: 'ed25519' | 'secp256k1' | null type: 'ed25519' | 'secp256k1' | null
} { } {
return codecWithXrpAlphabet.decode(seed, opts) return codecWithXrpAlphabet.decode(seed, opts)
@@ -219,7 +212,7 @@ export function encodeAccountID(bytes: ByteArray): string {
export const encodeAddress = encodeAccountID export const encodeAddress = encodeAccountID
/* eslint-enable import/no-unused-modules */ /* eslint-enable import/no-unused-modules */
export function decodeAccountID(accountId: string): Buffer { export function decodeAccountID(accountId: string): Uint8Array {
const opts = { versions: [ACCOUNT_ID], expectedLength: 20 } const opts = { versions: [ACCOUNT_ID], expectedLength: 20 }
return codecWithXrpAlphabet.decode(accountId, opts).bytes return codecWithXrpAlphabet.decode(accountId, opts).bytes
} }
@@ -230,7 +223,7 @@ export function decodeAccountID(accountId: string): Buffer {
export const decodeAddress = decodeAccountID export const decodeAddress = decodeAccountID
/* eslint-enable import/no-unused-modules */ /* eslint-enable import/no-unused-modules */
export function decodeNodePublic(base58string: string): Buffer { export function decodeNodePublic(base58string: string): Uint8Array {
const opts = { versions: [NODE_PUBLIC], expectedLength: 33 } const opts = { versions: [NODE_PUBLIC], expectedLength: 33 }
return codecWithXrpAlphabet.decode(base58string, opts).bytes return codecWithXrpAlphabet.decode(base58string, opts).bytes
} }
@@ -245,7 +238,7 @@ export function encodeAccountPublic(bytes: ByteArray): string {
return codecWithXrpAlphabet.encode(bytes, opts) return codecWithXrpAlphabet.encode(bytes, opts)
} }
export function decodeAccountPublic(base58string: string): Buffer { export function decodeAccountPublic(base58string: string): Uint8Array {
const opts = { versions: [ACCOUNT_PUBLIC_KEY], expectedLength: 33 } const opts = { versions: [ACCOUNT_PUBLIC_KEY], expectedLength: 33 }
return codecWithXrpAlphabet.decode(base58string, opts).bytes return codecWithXrpAlphabet.decode(base58string, opts).bytes
} }
@@ -258,3 +251,9 @@ export function isValidClassicAddress(address: string): boolean {
} }
return true return true
} }
function checkByteLength(bytes: ByteArray, expectedLength: number): boolean {
return 'byteLength' in bytes
? bytes.byteLength === expectedLength
: bytes.length === expectedLength
}

View File

@@ -1,3 +1,5 @@
import { bytesToHex, hexToBytes } from '@xrplf/isomorphic/utils'
import { import {
classicAddressToXAddress, classicAddressToXAddress,
xAddressToClassicAddress, xAddressToClassicAddress,
@@ -199,10 +201,10 @@ const testCases: AddressTestCase[] = [
{ {
const highAndLowAccounts = [ const highAndLowAccounts = [
Buffer.from('00'.repeat(20), 'hex'), hexToBytes('00'.repeat(20)),
Buffer.from(`${'00'.repeat(19)}01`, 'hex'), hexToBytes(`${'00'.repeat(19)}01`),
Buffer.from('01'.repeat(20), 'hex'), hexToBytes('01'.repeat(20)),
Buffer.from('FF'.repeat(20), 'hex'), hexToBytes('FF'.repeat(20)),
] ]
highAndLowAccounts.forEach((accountId) => { highAndLowAccounts.forEach((accountId) => {
@@ -215,7 +217,7 @@ const testCases: AddressTestCase[] = [
tagTestCases.forEach((testCase) => { tagTestCases.forEach((testCase) => {
const tag = testCase || false const tag = testCase || false
const xAddress = encodeXAddress(accountId, tag, isTestAddress) const xAddress = encodeXAddress(accountId, tag, isTestAddress)
it(`Encoding ${accountId.toString('hex')}${ it(`Encoding ${bytesToHex(accountId)}${
tag ? `:${tag}` : '' tag ? `:${tag}` : ''
} to ${xAddress} has expected length`, () => { } to ${xAddress} has expected length`, () => {
expect(xAddress.length).toBe(47) expect(xAddress.length).toBe(47)
@@ -256,8 +258,8 @@ it(`Invalid X-address (64-bit tag) throws`, () => {
it(`Invalid Account ID throws`, () => { it(`Invalid Account ID throws`, () => {
expect(() => { expect(() => {
encodeXAddress(Buffer.from('00'.repeat(19), 'hex'), false, false) encodeXAddress(hexToBytes('00'.repeat(19)), false, false)
}).toThrow(new Error('Account ID must be 20 bytes')) }).toThrowError('Account ID must be 20 bytes')
}) })
it(`isValidXAddress returns false for invalid X-address`, () => { it(`isValidXAddress returns false for invalid X-address`, () => {

View File

@@ -1,44 +1,48 @@
import { arrayEqual, concatArgs } from '../src/utils' import { arrayEqual, concatArgs } from '../src/utils'
it('two sequences are equal', () => { describe('Function: arrayEqual', () => {
expect(arrayEqual([1, 2, 3], [1, 2, 3])).toBe(true) it('two sequences are equal', () => {
expect(arrayEqual([1, 2, 3], [1, 2, 3])).toBe(true)
})
it('elements must be in the same order', () => {
expect(arrayEqual([3, 2, 1], [1, 2, 3])).toBe(false)
})
it('sequences do not need to be the same type', () => {
expect(arrayEqual(Uint8Array.from([1, 2, 3]), [1, 2, 3])).toBe(true)
expect(
arrayEqual(Uint8Array.from([1, 2, 3]), new Uint8Array([1, 2, 3])),
).toBe(true)
})
it('single element sequences do not need to be the same type', () => {
expect(arrayEqual(Uint8Array.from([1]), [1])).toBe(true)
expect(arrayEqual(Uint8Array.from([1]), new Uint8Array([1]))).toBe(true)
})
it('empty sequences do not need to be the same type', () => {
expect(arrayEqual(Uint8Array.from([]), [])).toBe(true)
expect(arrayEqual(Uint8Array.from([]), new Uint8Array([]))).toBe(true)
})
}) })
it('elements must be in the same order', () => { describe('Function: concatArgs', () => {
expect(arrayEqual([3, 2, 1], [1, 2, 3])).toBe(false) it('plain numbers are concatenated', () => {
}) expect(concatArgs(10, 20, 30, 40)).toEqual([10, 20, 30, 40])
})
it('sequences do not need to be the same type', () => { it('a variety of values are concatenated', () => {
expect(arrayEqual(Buffer.from([1, 2, 3]), [1, 2, 3])).toBe(true) expect(
expect(arrayEqual(Buffer.from([1, 2, 3]), new Uint8Array([1, 2, 3]))).toBe( concatArgs(1, [2, 3], Uint8Array.from([4, 5]), new Uint8Array([6, 7])),
true, ).toEqual([1, 2, 3, 4, 5, 6, 7])
) })
})
it('sequences with a single element', () => { it('a single value is returned as an array', () => {
expect(arrayEqual(Buffer.from([1]), [1])).toBe(true) expect(concatArgs(Uint8Array.from([7]))).toEqual([7])
expect(arrayEqual(Buffer.from([1]), new Uint8Array([1]))).toBe(true) })
})
it('empty sequences', () => { it('no arguments returns an empty array', () => {
expect(arrayEqual(Buffer.from([]), [])).toBe(true) expect(concatArgs()).toEqual([])
expect(arrayEqual(Buffer.from([]), new Uint8Array([]))).toBe(true) })
})
it('plain numbers are concatenated', () => {
expect(concatArgs(10, 20, 30, 40)).toEqual([10, 20, 30, 40])
})
it('a variety of values are concatenated', () => {
expect(
concatArgs(1, [2, 3], Buffer.from([4, 5]), new Uint8Array([6, 7])),
).toEqual([1, 2, 3, 4, 5, 6, 7])
})
it('a single value is returned as an array', () => {
expect(concatArgs(Buffer.from([7]))).toEqual([7])
})
it('no arguments returns an empty array', () => {
expect(concatArgs()).toEqual([])
}) })

View File

@@ -1,3 +1,5 @@
import { bytesToHex, hexToBytes, stringToHex } from '@xrplf/isomorphic/utils'
import { import {
codec, codec,
decodeAccountID, decodeAccountID,
@@ -11,12 +13,8 @@ import {
isValidClassicAddress, isValidClassicAddress,
} from '../src' } from '../src'
function toHex(bytes: Buffer): string { function stringToBytes(str: string): Uint8Array {
return Buffer.from(bytes).toString('hex').toUpperCase() return hexToBytes(stringToHex(str))
}
function toBytes(hex: string): Buffer {
return Buffer.from(hex, 'hex')
} }
/** /**
@@ -29,18 +27,18 @@ function toBytes(hex: string): Buffer {
*/ */
// eslint-disable-next-line max-params -- needs them // eslint-disable-next-line max-params -- needs them
function makeEncodeDecodeTest( function makeEncodeDecodeTest(
encoder: (val: Buffer) => string, encoder: (val: Uint8Array) => string,
decoder: (val: string) => Buffer, decoder: (val: string) => Uint8Array,
base58: string, base58: string,
hex: string, hex: string,
): void { ): void {
it(`can translate between ${hex} and ${base58}`, function () { it(`can translate between ${hex} and ${base58}`, function () {
const actual = encoder(toBytes(hex)) const actual = encoder(hexToBytes(hex))
expect(actual).toBe(base58) expect(actual).toBe(base58)
}) })
it(`can translate between ${base58} and ${hex})`, function () { it(`can translate between ${base58} and ${hex})`, function () {
const buf = decoder(base58) const buf = decoder(base58)
expect(toHex(buf)).toBe(hex) expect(bytesToHex(buf)).toBe(hex)
}) })
} }
@@ -67,11 +65,11 @@ makeEncodeDecodeTest(
it('can decode arbitrary seeds', function () { it('can decode arbitrary seeds', function () {
const decoded = decodeSeed('sEdTM1uX8pu2do5XvTnutH6HsouMaM2') const decoded = decodeSeed('sEdTM1uX8pu2do5XvTnutH6HsouMaM2')
expect(toHex(decoded.bytes)).toBe('4C3A1D213FBDFB14C7C28D609469B341') expect(bytesToHex(decoded.bytes)).toBe('4C3A1D213FBDFB14C7C28D609469B341')
expect(decoded.type).toBe('ed25519') expect(decoded.type).toBe('ed25519')
const decoded2 = decodeSeed('sn259rEFXrQrWyx3Q7XneWcwV6dfL') const decoded2 = decodeSeed('sn259rEFXrQrWyx3Q7XneWcwV6dfL')
expect(toHex(decoded2.bytes)).toBe('CF2DE378FBDD7E2EE87D486DFB5A7BFF') expect(bytesToHex(decoded2.bytes)).toBe('CF2DE378FBDD7E2EE87D486DFB5A7BFF')
expect(decoded2.type).toBe('secp256k1') expect(decoded2.type).toBe('secp256k1')
}) })
@@ -79,7 +77,7 @@ it('can pass a type as second arg to encodeSeed', function () {
const edSeed = 'sEdTM1uX8pu2do5XvTnutH6HsouMaM2' const edSeed = 'sEdTM1uX8pu2do5XvTnutH6HsouMaM2'
const decoded = decodeSeed(edSeed) const decoded = decodeSeed(edSeed)
const type = 'ed25519' const type = 'ed25519'
expect(toHex(decoded.bytes)).toBe('4C3A1D213FBDFB14C7C28D609469B341') expect(bytesToHex(decoded.bytes)).toBe('4C3A1D213FBDFB14C7C28D609469B341')
expect(decoded.type).toBe(type) expect(decoded.type).toBe(type)
expect(encodeSeed(decoded.bytes, type)).toBe(edSeed) expect(encodeSeed(decoded.bytes, type)).toBe(edSeed)
}) })
@@ -105,7 +103,7 @@ it('isValidClassicAddress - empty', function () {
describe('encodeSeed', function () { describe('encodeSeed', function () {
it('encodes a secp256k1 seed', function () { it('encodes a secp256k1 seed', function () {
const result = encodeSeed( const result = encodeSeed(
Buffer.from('CF2DE378FBDD7E2EE87D486DFB5A7BFF', 'hex'), hexToBytes('CF2DE378FBDD7E2EE87D486DFB5A7BFF'),
'secp256k1', 'secp256k1',
) )
expect(result).toBe('sn259rEFXrQrWyx3Q7XneWcwV6dfL') expect(result).toBe('sn259rEFXrQrWyx3Q7XneWcwV6dfL')
@@ -113,7 +111,7 @@ describe('encodeSeed', function () {
it('encodes low secp256k1 seed', function () { it('encodes low secp256k1 seed', function () {
const result = encodeSeed( const result = encodeSeed(
Buffer.from('00000000000000000000000000000000', 'hex'), hexToBytes('00000000000000000000000000000000'),
'secp256k1', 'secp256k1',
) )
expect(result).toBe('sp6JS7f14BuwFY8Mw6bTtLKWauoUs') expect(result).toBe('sp6JS7f14BuwFY8Mw6bTtLKWauoUs')
@@ -121,7 +119,7 @@ describe('encodeSeed', function () {
it('encodes high secp256k1 seed', function () { it('encodes high secp256k1 seed', function () {
const result = encodeSeed( const result = encodeSeed(
Buffer.from('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 'hex'), hexToBytes('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'),
'secp256k1', 'secp256k1',
) )
expect(result).toBe('saGwBRReqUNKuWNLpUAq8i8NkXEPN') expect(result).toBe('saGwBRReqUNKuWNLpUAq8i8NkXEPN')
@@ -129,7 +127,7 @@ describe('encodeSeed', function () {
it('encodes an ed25519 seed', function () { it('encodes an ed25519 seed', function () {
const result = encodeSeed( const result = encodeSeed(
Buffer.from('4C3A1D213FBDFB14C7C28D609469B341', 'hex'), hexToBytes('4C3A1D213FBDFB14C7C28D609469B341'),
'ed25519', 'ed25519',
) )
expect(result).toBe('sEdTM1uX8pu2do5XvTnutH6HsouMaM2') expect(result).toBe('sEdTM1uX8pu2do5XvTnutH6HsouMaM2')
@@ -137,7 +135,7 @@ describe('encodeSeed', function () {
it('encodes low ed25519 seed', function () { it('encodes low ed25519 seed', function () {
const result = encodeSeed( const result = encodeSeed(
Buffer.from('00000000000000000000000000000000', 'hex'), hexToBytes('00000000000000000000000000000000'),
'ed25519', 'ed25519',
) )
expect(result).toBe('sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE') expect(result).toBe('sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE')
@@ -145,7 +143,7 @@ describe('encodeSeed', function () {
it('encodes high ed25519 seed', function () { it('encodes high ed25519 seed', function () {
const result = encodeSeed( const result = encodeSeed(
Buffer.from('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 'hex'), hexToBytes('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'),
'ed25519', 'ed25519',
) )
expect(result).toBe('sEdV19BLfeQeKdEXyYA4NhjPJe6XBfG') expect(result).toBe('sEdV19BLfeQeKdEXyYA4NhjPJe6XBfG')
@@ -153,19 +151,13 @@ describe('encodeSeed', function () {
it('attempting to encode a seed with less than 16 bytes of entropy throws', function () { it('attempting to encode a seed with less than 16 bytes of entropy throws', function () {
expect(() => { expect(() => {
encodeSeed( encodeSeed(hexToBytes('CF2DE378FBDD7E2EE87D486DFB5A7B'), 'secp256k1')
Buffer.from('CF2DE378FBDD7E2EE87D486DFB5A7B', 'hex'),
'secp256k1',
)
}).toThrow(new Error('entropy must have length 16')) }).toThrow(new Error('entropy must have length 16'))
}) })
it('attempting to encode a seed with more than 16 bytes of entropy throws', function () { it('attempting to encode a seed with more than 16 bytes of entropy throws', function () {
expect(() => { expect(() => {
encodeSeed( encodeSeed(hexToBytes('CF2DE378FBDD7E2EE87D486DFB5A7BFFFF'), 'secp256k1')
Buffer.from('CF2DE378FBDD7E2EE87D486DFB5A7BFFFF', 'hex'),
'secp256k1',
)
}).toThrow(new Error('entropy must have length 16')) }).toThrow(new Error('entropy must have length 16'))
}) })
}) })
@@ -173,13 +165,13 @@ describe('encodeSeed', function () {
describe('decodeSeed', function () { describe('decodeSeed', function () {
it('can decode an Ed25519 seed', function () { it('can decode an Ed25519 seed', function () {
const decoded = decodeSeed('sEdTM1uX8pu2do5XvTnutH6HsouMaM2') const decoded = decodeSeed('sEdTM1uX8pu2do5XvTnutH6HsouMaM2')
expect(toHex(decoded.bytes)).toBe('4C3A1D213FBDFB14C7C28D609469B341') expect(bytesToHex(decoded.bytes)).toBe('4C3A1D213FBDFB14C7C28D609469B341')
expect(decoded.type).toBe('ed25519') expect(decoded.type).toBe('ed25519')
}) })
it('can decode a secp256k1 seed', function () { it('can decode a secp256k1 seed', function () {
const decoded = decodeSeed('sn259rEFXrQrWyx3Q7XneWcwV6dfL') const decoded = decodeSeed('sn259rEFXrQrWyx3Q7XneWcwV6dfL')
expect(toHex(decoded.bytes)).toBe('CF2DE378FBDD7E2EE87D486DFB5A7BFF') expect(bytesToHex(decoded.bytes)).toBe('CF2DE378FBDD7E2EE87D486DFB5A7BFF')
expect(decoded.type).toBe('secp256k1') expect(decoded.type).toBe('secp256k1')
}) })
}) })
@@ -187,17 +179,17 @@ describe('decodeSeed', function () {
describe('encodeAccountID', function () { describe('encodeAccountID', function () {
it('can encode an AccountID', function () { it('can encode an AccountID', function () {
const encoded = encodeAccountID( const encoded = encodeAccountID(
Buffer.from('BA8E78626EE42C41B46D46C3048DF3A1C3C87072', 'hex'), hexToBytes('BA8E78626EE42C41B46D46C3048DF3A1C3C87072'),
) )
expect(encoded).toBe('rJrRMgiRgrU6hDF4pgu5DXQdWyPbY35ErN') expect(encoded).toBe('rJrRMgiRgrU6hDF4pgu5DXQdWyPbY35ErN')
}) })
it('unexpected length should throw', function () { it('unexpected length should throw', function () {
expect(() => { expect(() => {
encodeAccountID(Buffer.from('ABCDEF', 'hex')) encodeAccountID(hexToBytes('ABCDEF'))
}).toThrow( }).toThrow(
new Error( new Error(
'unexpected_payload_length: bytes.length does not match expectedLength. Ensure that the bytes are a Buffer.', 'unexpected_payload_length: bytes.length does not match expectedLength. Ensure that the bytes are a Uint8Array.',
), ),
) )
}) })
@@ -208,7 +200,7 @@ describe('decodeNodePublic', function () {
const decoded = decodeNodePublic( const decoded = decodeNodePublic(
'n9MXXueo837zYH36DvMc13BwHcqtfAWNJY5czWVbp7uYTj7x17TH', 'n9MXXueo837zYH36DvMc13BwHcqtfAWNJY5czWVbp7uYTj7x17TH',
) )
expect(toHex(decoded)).toBe( expect(bytesToHex(decoded)).toBe(
'0388E5BA87A000CB807240DF8C848EB0B5FFA5C8E5A521BC8E105C0F0A44217828', '0388E5BA87A000CB807240DF8C848EB0B5FFA5C8E5A521BC8E105C0F0A44217828',
) )
}) })
@@ -216,7 +208,7 @@ describe('decodeNodePublic', function () {
it('encodes 123456789 with version byte of 0', () => { it('encodes 123456789 with version byte of 0', () => {
expect( expect(
codec.encode(Buffer.from('123456789'), { codec.encode(stringToBytes('123456789'), {
versions: [0], versions: [0],
expectedLength: 9, expectedLength: 9,
}), }),
@@ -282,7 +274,7 @@ it('decode data', () => {
}), }),
).toEqual({ ).toEqual({
version: [0], version: [0],
bytes: Buffer.from('123456789'), bytes: stringToBytes('123456789'),
type: null, type: null,
}) })
}) })
@@ -295,7 +287,7 @@ it('decode data with expected length', function () {
}), }),
).toEqual({ ).toEqual({
version: [0], version: [0],
bytes: Buffer.from('123456789'), bytes: stringToBytes('123456789'),
type: null, type: null,
}) })
}) })

View File

@@ -2,6 +2,12 @@
## Unreleased ## Unreleased
### Breaking Changes
* `Buffer` has been replaced with `UInt8Array` for both params and return values. `Buffer` may continue to work with params since they extend `UInt8Arrays`.
### Changes
* Eliminates 4 runtime dependencies: `base-x`, `base64-js`, `buffer`, and `ieee754`.
## 2.0.0 Beta 1 (2023-10-19) ## 2.0.0 Beta 1 (2023-10-19)
### Breaking Changes ### Breaking Changes

View File

@@ -13,7 +13,6 @@
"dependencies": { "dependencies": {
"@xrplf/isomorphic": "^1.0.0-beta.0", "@xrplf/isomorphic": "^1.0.0-beta.0",
"bignumber.js": "^9.0.0", "bignumber.js": "^9.0.0",
"buffer": "6.0.3",
"ripple-address-codec": "^5.0.0-beta.0" "ripple-address-codec": "^5.0.0-beta.0"
}, },
"scripts": { "scripts": {

View File

@@ -1,5 +1,6 @@
/* eslint-disable func-style */ /* eslint-disable func-style */
import { bytesToHex } from '@xrplf/isomorphic/utils'
import { coreTypes } from './types' import { coreTypes } from './types'
import { BinaryParser } from './serdes/binary-parser' import { BinaryParser } from './serdes/binary-parser'
import { AccountID } from './types/account-id' import { AccountID } from './types/account-id'
@@ -17,17 +18,17 @@ import { JsonObject } from './types/serialized-type'
/** /**
* Construct a BinaryParser * Construct a BinaryParser
* *
* @param bytes hex-string or Buffer to construct BinaryParser from * @param bytes hex-string or Uint8Array to construct BinaryParser from
* @param definitions rippled definitions used to parse the values of transaction types and such. * @param definitions rippled definitions used to parse the values of transaction types and such.
* Can be customized for sidechains and amendments. * Can be customized for sidechains and amendments.
* @returns BinaryParser * @returns BinaryParser
*/ */
const makeParser = ( const makeParser = (
bytes: string | Buffer, bytes: string | Uint8Array,
definitions?: XrplDefinitionsBase, definitions?: XrplDefinitionsBase,
): BinaryParser => ): BinaryParser =>
new BinaryParser( new BinaryParser(
bytes instanceof Buffer ? bytes.toString('hex') : bytes, bytes instanceof Uint8Array ? bytesToHex(bytes) : bytes,
definitions, definitions,
) )
@@ -64,8 +65,8 @@ const binaryToJSON = (
* @field set signingFieldOnly to true if you want to serialize only signing fields * @field set signingFieldOnly to true if you want to serialize only signing fields
*/ */
interface OptionObject { interface OptionObject {
prefix?: Buffer prefix?: Uint8Array
suffix?: Buffer suffix?: Uint8Array
signingFieldsOnly?: boolean signingFieldsOnly?: boolean
definitions?: XrplDefinitionsBase definitions?: XrplDefinitionsBase
} }
@@ -75,9 +76,12 @@ interface OptionObject {
* *
* @param object JSON object to serialize * @param object JSON object to serialize
* @param opts options for serializing, including optional prefix, suffix, signingFieldOnly, and definitions * @param opts options for serializing, including optional prefix, suffix, signingFieldOnly, and definitions
* @returns A Buffer containing the serialized object * @returns A Uint8Array containing the serialized object
*/ */
function serializeObject(object: JsonObject, opts: OptionObject = {}): Buffer { function serializeObject(
object: JsonObject,
opts: OptionObject = {},
): Uint8Array {
const { prefix, suffix, signingFieldsOnly = false, definitions } = opts const { prefix, suffix, signingFieldsOnly = false, definitions } = opts
const bytesList = new BytesList() const bytesList = new BytesList()
@@ -105,13 +109,13 @@ function serializeObject(object: JsonObject, opts: OptionObject = {}): Buffer {
* @param transaction Transaction to serialize * @param transaction Transaction to serialize
* @param prefix Prefix bytes to put before the serialized object * @param prefix Prefix bytes to put before the serialized object
* @param opts.definitions Custom rippled types to use instead of the default. Used for sidechains and amendments. * @param opts.definitions Custom rippled types to use instead of the default. Used for sidechains and amendments.
* @returns A Buffer with the serialized object * @returns A Uint8Array with the serialized object
*/ */
function signingData( function signingData(
transaction: JsonObject, transaction: JsonObject,
prefix: Buffer = HashPrefix.transactionSig, prefix: Uint8Array = HashPrefix.transactionSig,
opts: { definitions?: XrplDefinitionsBase } = {}, opts: { definitions?: XrplDefinitionsBase } = {},
): Buffer { ): Uint8Array {
return serializeObject(transaction, { return serializeObject(transaction, {
prefix, prefix,
signingFieldsOnly: true, signingFieldsOnly: true,
@@ -134,7 +138,7 @@ interface ClaimObject extends JsonObject {
* @param opts.definitions Custom rippled types to use instead of the default. Used for sidechains and amendments. * @param opts.definitions Custom rippled types to use instead of the default. Used for sidechains and amendments.
* @returns the serialized object with appropriate prefix * @returns the serialized object with appropriate prefix
*/ */
function signingClaimData(claim: ClaimObject): Buffer { function signingClaimData(claim: ClaimObject): Uint8Array {
const num = BigInt(String(claim.amount)) const num = BigInt(String(claim.amount))
const prefix = HashPrefix.paymentChannelClaim const prefix = HashPrefix.paymentChannelClaim
const channel = coreTypes.Hash256.from(claim.channel).toBytes() const channel = coreTypes.Hash256.from(claim.channel).toBytes()
@@ -162,7 +166,7 @@ function multiSigningData(
opts: { definitions: XrplDefinitionsBase } = { opts: { definitions: XrplDefinitionsBase } = {
definitions: DEFAULT_DEFINITIONS, definitions: DEFAULT_DEFINITIONS,
}, },
): Buffer { ): Uint8Array {
const prefix = HashPrefix.transactionMultiSig const prefix = HashPrefix.transactionMultiSig
const suffix = coreTypes.AccountID.from(signingAccount).toBytes() const suffix = coreTypes.AccountID.from(signingAccount).toBytes()
return serializeObject(transaction, { return serializeObject(transaction, {

View File

@@ -4,14 +4,14 @@ import { BytesList, BinaryParser } from '../binary'
* @brief: Bytes, name, and ordinal representing one type, ledger_type, transaction type, or result * @brief: Bytes, name, and ordinal representing one type, ledger_type, transaction type, or result
*/ */
export class Bytes { export class Bytes {
readonly bytes: Buffer readonly bytes: Uint8Array
constructor( constructor(
readonly name: string, readonly name: string,
readonly ordinal: number, readonly ordinal: number,
readonly ordinalWidth: number, readonly ordinalWidth: number,
) { ) {
this.bytes = Buffer.alloc(ordinalWidth) this.bytes = new Uint8Array(ordinalWidth)
for (let i = 0; i < ordinalWidth; i++) { for (let i = 0; i < ordinalWidth; i++) {
this.bytes[ordinalWidth - i - 1] = (ordinal >>> (i * 8)) & 0xff this.bytes[ordinalWidth - i - 1] = (ordinal >>> (i * 8)) & 0xff
} }

View File

@@ -22,14 +22,14 @@ export interface FieldInstance {
readonly type: Bytes readonly type: Bytes
readonly ordinal: number readonly ordinal: number
readonly name: string readonly name: string
readonly header: Buffer readonly header: Uint8Array
readonly associatedType: typeof SerializedType readonly associatedType: typeof SerializedType
} }
/* /*
* @brief: Serialize a field based on type_code and Field.nth * @brief: Serialize a field based on type_code and Field.nth
*/ */
function fieldHeader(type: number, nth: number): Buffer { function fieldHeader(type: number, nth: number): Uint8Array {
const header: Array<number> = [] const header: Array<number> = []
if (type < 16) { if (type < 16) {
if (nth < 16) { if (nth < 16) {
@@ -42,7 +42,7 @@ function fieldHeader(type: number, nth: number): Buffer {
} else { } else {
header.push(0, type, nth) header.push(0, type, nth)
} }
return Buffer.from(header) return Uint8Array.from(header)
} }
function buildField( function buildField(

View File

@@ -1,19 +1,21 @@
import { writeUInt32BE } from './utils'
/** /**
* Write a 32 bit integer to a Buffer * Write a 32 bit integer to a Uint8Array
* *
* @param uint32 32 bit integer to write to buffer * @param uint32 32 bit integer to write to Uint8Array
* @returns a buffer with the bytes representation of uint32 * @returns a Uint8Array with the bytes representation of uint32
*/ */
function bytes(uint32: number): Buffer { function bytes(uint32: number): Uint8Array {
const result = Buffer.alloc(4) const result = new Uint8Array(4)
result.writeUInt32BE(uint32, 0) writeUInt32BE(result, uint32, 0)
return result return result
} }
/** /**
* Maps HashPrefix names to their byte representation * Maps HashPrefix names to their byte representation
*/ */
const HashPrefix: Record<string, Buffer> = { const HashPrefix: Record<string, Uint8Array> = {
transactionID: bytes(0x54584e00), transactionID: bytes(0x54584e00),
// transaction plus metadata // transaction plus metadata
transaction: bytes(0x534e4400), transaction: bytes(0x534e4400),

View File

@@ -1,7 +1,6 @@
import { HashPrefix } from './hash-prefixes' import { HashPrefix } from './hash-prefixes'
import { Hash256 } from './types' import { Hash256 } from './types'
import { BytesList } from './serdes/binary-serializer' import { BytesList } from './serdes/binary-serializer'
import { sha512 } from '@xrplf/isomorphic/sha512' import { sha512 } from '@xrplf/isomorphic/sha512'
/** /**
@@ -17,7 +16,7 @@ class Sha512Half extends BytesList {
* @param bytes bytes to write to this.hash * @param bytes bytes to write to this.hash
* @returns the new Sha512Hash object * @returns the new Sha512Hash object
*/ */
static put(bytes: Buffer): Sha512Half { static put(bytes: Uint8Array): Sha512Half {
return new Sha512Half().put(bytes) return new Sha512Half().put(bytes)
} }
@@ -27,7 +26,7 @@ class Sha512Half extends BytesList {
* @param bytes bytes to write to object * @param bytes bytes to write to object
* @returns the Sha512 object * @returns the Sha512 object
*/ */
put(bytes: Buffer): Sha512Half { put(bytes: Uint8Array): Sha512Half {
this.hash.update(bytes) this.hash.update(bytes)
return this return this
} }
@@ -37,8 +36,8 @@ class Sha512Half extends BytesList {
* *
* @returns half of a SHA512 hash * @returns half of a SHA512 hash
*/ */
finish256(): Buffer { finish256(): Uint8Array {
return Buffer.from(this.hash.digest().slice(0, 32)) return Uint8Array.from(this.hash.digest().slice(0, 32))
} }
/** /**
@@ -57,7 +56,7 @@ class Sha512Half extends BytesList {
* @param args zero or more arguments to hash * @param args zero or more arguments to hash
* @returns the sha512half hash of the arguments. * @returns the sha512half hash of the arguments.
*/ */
function sha512Half(...args: Buffer[]): Buffer { function sha512Half(...args: Uint8Array[]): Uint8Array {
const hash = new Sha512Half() const hash = new Sha512Half()
args.forEach((a) => hash.put(a)) args.forEach((a) => hash.put(a))
return hash.finish256() return hash.finish256()
@@ -69,7 +68,7 @@ function sha512Half(...args: Buffer[]): Buffer {
* @param serialized bytes to hash * @param serialized bytes to hash
* @returns a Hash256 object * @returns a Hash256 object
*/ */
function transactionID(serialized: Buffer): Hash256 { function transactionID(serialized: Uint8Array): Hash256 {
return new Hash256(sha512Half(HashPrefix.transactionID, serialized)) return new Hash256(sha512Half(HashPrefix.transactionID, serialized))
} }

View File

@@ -9,6 +9,7 @@ import {
} from './enums' } from './enums'
import { XrplDefinitions } from './enums/xrpl-definitions' import { XrplDefinitions } from './enums/xrpl-definitions'
import { coreTypes } from './types' import { coreTypes } from './types'
import { bytesToHex } from '@xrplf/isomorphic/utils'
const { const {
signingData, signingData,
@@ -44,9 +45,7 @@ function encode(json: object, definitions?: XrplDefinitionsBase): string {
if (typeof json !== 'object') { if (typeof json !== 'object') {
throw new Error() throw new Error()
} }
return serializeObject(json as JsonObject, { definitions }) return bytesToHex(serializeObject(json as JsonObject, { definitions }))
.toString('hex')
.toUpperCase()
} }
/** /**
@@ -64,11 +63,11 @@ function encodeForSigning(
if (typeof json !== 'object') { if (typeof json !== 'object') {
throw new Error() throw new Error()
} }
return signingData(json as JsonObject, HashPrefix.transactionSig, { return bytesToHex(
definitions, signingData(json as JsonObject, HashPrefix.transactionSig, {
}) definitions,
.toString('hex') }),
.toUpperCase() )
} }
/** /**
@@ -83,9 +82,7 @@ function encodeForSigningClaim(json: object): string {
if (typeof json !== 'object') { if (typeof json !== 'object') {
throw new Error() throw new Error()
} }
return signingClaimData(json as ClaimObject) return bytesToHex(signingClaimData(json as ClaimObject))
.toString('hex')
.toUpperCase()
} }
/** /**
@@ -108,9 +105,9 @@ function encodeForMultisigning(
throw new Error() throw new Error()
} }
const definitionsOpt = definitions ? { definitions } : undefined const definitionsOpt = definitions ? { definitions } : undefined
return multiSigningData(json as JsonObject, signer, definitionsOpt) return bytesToHex(
.toString('hex') multiSigningData(json as JsonObject, signer, definitionsOpt),
.toUpperCase() )
} }
/** /**
@@ -123,7 +120,7 @@ function encodeQuality(value: string): string {
if (typeof value !== 'string') { if (typeof value !== 'string') {
throw new Error() throw new Error()
} }
return quality.encode(value).toString('hex').toUpperCase() return bytesToHex(quality.encode(value))
} }
/** /**

View File

@@ -1,6 +1,6 @@
import { coreTypes } from './types' import { coreTypes } from './types'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { bytesToHex, hexToBytes } from '@xrplf/isomorphic/utils'
/** /**
* class for encoding and decoding quality * class for encoding and decoding quality
@@ -12,7 +12,7 @@ class quality {
* @param arg string representation of an amount * @param arg string representation of an amount
* @returns Serialized quality * @returns Serialized quality
*/ */
static encode(quality: string): Buffer { static encode(quality: string): Uint8Array {
const decimal = BigNumber(quality) const decimal = BigNumber(quality)
const exponent = (decimal?.e || 0) - 15 const exponent = (decimal?.e || 0) - 15
const qualityString = decimal.times(`1e${-exponent}`).abs().toString() const qualityString = decimal.times(`1e${-exponent}`).abs().toString()
@@ -28,9 +28,9 @@ class quality {
* @returns deserialized quality * @returns deserialized quality
*/ */
static decode(quality: string): BigNumber { static decode(quality: string): BigNumber {
const bytes = Buffer.from(quality, 'hex').slice(-8) const bytes = hexToBytes(quality).slice(-8)
const exponent = bytes[0] - 100 const exponent = bytes[0] - 100
const mantissa = new BigNumber(`0x${bytes.slice(1).toString('hex')}`) const mantissa = new BigNumber(`0x${bytesToHex(bytes.slice(1))}`)
return mantissa.times(`1e${exponent}`) return mantissa.times(`1e${exponent}`)
} }
} }

View File

@@ -4,12 +4,13 @@ import {
FieldInstance, FieldInstance,
} from '../enums' } from '../enums'
import { type SerializedType } from '../types/serialized-type' import { type SerializedType } from '../types/serialized-type'
import { hexToBytes } from '@xrplf/isomorphic/utils'
/** /**
* BinaryParser is used to compute fields and values from a HexString * BinaryParser is used to compute fields and values from a HexString
*/ */
class BinaryParser { class BinaryParser {
private bytes: Buffer private bytes: Uint8Array
definitions: XrplDefinitionsBase definitions: XrplDefinitionsBase
/** /**
@@ -23,7 +24,7 @@ class BinaryParser {
hexBytes: string, hexBytes: string,
definitions: XrplDefinitionsBase = DEFAULT_DEFINITIONS, definitions: XrplDefinitionsBase = DEFAULT_DEFINITIONS,
) { ) {
this.bytes = Buffer.from(hexBytes, 'hex') this.bytes = hexToBytes(hexBytes)
this.definitions = definitions this.definitions = definitions
} }
@@ -57,7 +58,7 @@ class BinaryParser {
* @param n The number of bytes to read * @param n The number of bytes to read
* @return The bytes * @return The bytes
*/ */
read(n: number): Buffer { read(n: number): Uint8Array {
if (n > this.bytes.byteLength) { if (n > this.bytes.byteLength) {
throw new Error() throw new Error()
} }
@@ -106,7 +107,7 @@ class BinaryParser {
* *
* @return The variable length bytes * @return The variable length bytes
*/ */
readVariableLength(): Buffer { readVariableLength(): Uint8Array {
return this.read(this.readVariableLengthLength()) return this.read(this.readVariableLengthLength())
} }

View File

@@ -1,11 +1,12 @@
import { FieldInstance } from '../enums' import { FieldInstance } from '../enums'
import { type SerializedType } from '../types/serialized-type' import { type SerializedType } from '../types/serialized-type'
import { bytesToHex, concat } from '@xrplf/isomorphic/utils'
/** /**
* Bytes list is a collection of buffer objects * Bytes list is a collection of Uint8Array objects
*/ */
class BytesList { class BytesList {
private bytesArray: Array<Buffer> = [] private bytesArray: Array<Uint8Array> = []
/** /**
* Get the total number of bytes in the BytesList * Get the total number of bytes in the BytesList
@@ -13,17 +14,17 @@ class BytesList {
* @return the number of bytes * @return the number of bytes
*/ */
public getLength(): number { public getLength(): number {
return Buffer.concat(this.bytesArray).byteLength return concat(this.bytesArray).byteLength
} }
/** /**
* Put bytes in the BytesList * Put bytes in the BytesList
* *
* @param bytesArg A Buffer * @param bytesArg A Uint8Array
* @return this BytesList * @return this BytesList
*/ */
public put(bytesArg: Buffer): BytesList { public put(bytesArg: Uint8Array): BytesList {
const bytes = Buffer.from(bytesArg) // Temporary, to catch instances of Uint8Array being passed in const bytes = Uint8Array.from(bytesArg) // Temporary, to catch instances of Uint8Array being passed in
this.bytesArray.push(bytes) this.bytesArray.push(bytes)
return this return this
} }
@@ -37,17 +38,17 @@ class BytesList {
list.put(this.toBytes()) list.put(this.toBytes())
} }
public toBytes(): Buffer { public toBytes(): Uint8Array {
return Buffer.concat(this.bytesArray) return concat(this.bytesArray)
} }
toHex(): string { toHex(): string {
return this.toBytes().toString('hex').toUpperCase() return bytesToHex(this.toBytes())
} }
} }
/** /**
* BinarySerializer is used to write fields and values to buffers * BinarySerializer is used to write fields and values to Uint8Arrays
*/ */
class BinarySerializer { class BinarySerializer {
private sink: BytesList = new BytesList() private sink: BytesList = new BytesList()
@@ -70,7 +71,7 @@ class BinarySerializer {
* *
* @param bytes the bytes to write * @param bytes the bytes to write
*/ */
put(bytes: Buffer): void { put(bytes: Uint8Array): void {
this.sink.put(bytes) this.sink.put(bytes)
} }
@@ -98,8 +99,8 @@ class BinarySerializer {
* *
* @param length the length of the bytes * @param length the length of the bytes
*/ */
private encodeVariableLength(length: number): Buffer { private encodeVariableLength(length: number): Uint8Array {
const lenBytes = Buffer.alloc(3) const lenBytes = new Uint8Array(3)
if (length <= 192) { if (length <= 192) {
lenBytes[0] = length lenBytes[0] = length
return lenBytes.slice(0, 1) return lenBytes.slice(0, 1)

View File

@@ -8,7 +8,7 @@ import { BytesList } from './serdes/binary-serializer'
* Abstract class describing a SHAMapNode * Abstract class describing a SHAMapNode
*/ */
abstract class ShaMapNode { abstract class ShaMapNode {
abstract hashPrefix(): Buffer abstract hashPrefix(): Uint8Array
abstract isLeaf(): boolean abstract isLeaf(): boolean
abstract isInner(): boolean abstract isInner(): boolean
abstract toBytesSink(list: BytesList): void abstract toBytesSink(list: BytesList): void
@@ -40,10 +40,10 @@ class ShaMapLeaf extends ShaMapNode {
/** /**
* Get the prefix of the this.item * Get the prefix of the this.item
* *
* @returns The hash prefix, unless this.item is undefined, then it returns an empty Buffer * @returns The hash prefix, unless this.item is undefined, then it returns an empty Uint8Array
*/ */
hashPrefix(): Buffer { hashPrefix(): Uint8Array {
return this.item === undefined ? Buffer.alloc(0) : this.item.hashPrefix() return this.item === undefined ? new Uint8Array(0) : this.item.hashPrefix()
} }
/** /**
@@ -99,7 +99,7 @@ class ShaMapInner extends ShaMapNode {
* *
* @returns hash prefix describing an inner node * @returns hash prefix describing an inner node
*/ */
hashPrefix(): Buffer { hashPrefix(): Uint8Array {
return HashPrefix.innerNode return HashPrefix.innerNode
} }

View File

@@ -5,6 +5,7 @@ import {
xAddressToClassicAddress, xAddressToClassicAddress,
} from 'ripple-address-codec' } from 'ripple-address-codec'
import { Hash160 } from './hash-160' import { Hash160 } from './hash-160'
import { hexToBytes } from '@xrplf/isomorphic/utils'
const HEX_REGEX = /^[A-F0-9]{40}$/ const HEX_REGEX = /^[A-F0-9]{40}$/
@@ -12,9 +13,11 @@ const HEX_REGEX = /^[A-F0-9]{40}$/
* Class defining how to encode and decode an AccountID * Class defining how to encode and decode an AccountID
*/ */
class AccountID extends Hash160 { class AccountID extends Hash160 {
static readonly defaultAccountID: AccountID = new AccountID(Buffer.alloc(20)) static readonly defaultAccountID: AccountID = new AccountID(
new Uint8Array(20),
)
constructor(bytes?: Buffer) { constructor(bytes?: Uint8Array) {
super(bytes ?? AccountID.defaultAccountID.bytes) super(bytes ?? AccountID.defaultAccountID.bytes)
} }
@@ -35,7 +38,7 @@ class AccountID extends Hash160 {
} }
return HEX_REGEX.test(value) return HEX_REGEX.test(value)
? new AccountID(Buffer.from(value, 'hex')) ? new AccountID(hexToBytes(value))
: this.fromBase58(value) : this.fromBase58(value)
} }
@@ -58,7 +61,7 @@ class AccountID extends Hash160 {
value = classic.classicAddress value = classic.classicAddress
} }
return new AccountID(Buffer.from(decodeAccountID(value))) return new AccountID(Uint8Array.from(decodeAccountID(value)))
} }
/** /**
@@ -76,9 +79,7 @@ class AccountID extends Hash160 {
* @returns the base58 string defined by this.bytes * @returns the base58 string defined by this.bytes
*/ */
toBase58(): string { toBase58(): string {
/* eslint-disable @typescript-eslint/no-explicit-any */ return encodeAccountID(this.bytes)
return encodeAccountID(this.bytes as any)
/* eslint-enable @typescript-eslint/no-explicit-any */
} }
} }

View File

@@ -3,8 +3,9 @@ import { BinaryParser } from '../serdes/binary-parser'
import { AccountID } from './account-id' import { AccountID } from './account-id'
import { Currency } from './currency' import { Currency } from './currency'
import { JsonObject, SerializedType } from './serialized-type' import { JsonObject, SerializedType } from './serialized-type'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { bytesToHex, concat, hexToBytes } from '@xrplf/isomorphic/utils'
import { readUInt32BE, writeUInt32BE } from '../utils'
/** /**
* Constants for validating amounts * Constants for validating amounts
@@ -52,11 +53,9 @@ function isAmountObject(arg): arg is AmountObject {
* Class for serializing/Deserializing Amounts * Class for serializing/Deserializing Amounts
*/ */
class Amount extends SerializedType { class Amount extends SerializedType {
static defaultAmount: Amount = new Amount( static defaultAmount: Amount = new Amount(hexToBytes('4000000000000000'))
Buffer.from('4000000000000000', 'hex'),
)
constructor(bytes: Buffer) { constructor(bytes: Uint8Array) {
super(bytes ?? Amount.defaultAmount.bytes) super(bytes ?? Amount.defaultAmount.bytes)
} }
@@ -72,17 +71,17 @@ class Amount extends SerializedType {
return value return value
} }
let amount = Buffer.alloc(8) let amount = new Uint8Array(8)
if (typeof value === 'string') { if (typeof value === 'string') {
Amount.assertXrpIsValid(value) Amount.assertXrpIsValid(value)
const number = BigInt(value) const number = BigInt(value)
const intBuf = [Buffer.alloc(4), Buffer.alloc(4)] const intBuf = [new Uint8Array(4), new Uint8Array(4)]
intBuf[0].writeUInt32BE(Number(number >> BigInt(32)), 0) writeUInt32BE(intBuf[0], Number(number >> BigInt(32)), 0)
intBuf[1].writeUInt32BE(Number(number & BigInt(mask)), 0) writeUInt32BE(intBuf[1], Number(number & BigInt(mask)), 0)
amount = Buffer.concat(intBuf) amount = concat(intBuf)
amount[0] |= 0x40 amount[0] |= 0x40
@@ -102,11 +101,11 @@ class Amount extends SerializedType {
.toString() .toString()
const num = BigInt(integerNumberString) const num = BigInt(integerNumberString)
const intBuf = [Buffer.alloc(4), Buffer.alloc(4)] const intBuf = [new Uint8Array(4), new Uint8Array(4)]
intBuf[0].writeUInt32BE(Number(num >> BigInt(32)), 0) writeUInt32BE(intBuf[0], Number(num >> BigInt(32)), 0)
intBuf[1].writeUInt32BE(Number(num & BigInt(mask)), 0) writeUInt32BE(intBuf[1], Number(num & BigInt(mask)), 0)
amount = Buffer.concat(intBuf) amount = concat(intBuf)
amount[0] |= 0x80 amount[0] |= 0x80
@@ -122,7 +121,7 @@ class Amount extends SerializedType {
const currency = Currency.from(value.currency).toBytes() const currency = Currency.from(value.currency).toBytes()
const issuer = AccountID.from(value.issuer).toBytes() const issuer = AccountID.from(value.issuer).toBytes()
return new Amount(Buffer.concat([amount, currency, issuer])) return new Amount(concat([amount, currency, issuer]))
} }
throw new Error('Invalid type to construct an Amount') throw new Error('Invalid type to construct an Amount')
@@ -152,8 +151,8 @@ class Amount extends SerializedType {
const sign = isPositive ? '' : '-' const sign = isPositive ? '' : '-'
bytes[0] &= 0x3f bytes[0] &= 0x3f
const msb = BigInt(bytes.slice(0, 4).readUInt32BE(0)) const msb = BigInt(readUInt32BE(bytes.slice(0, 4), 0))
const lsb = BigInt(bytes.slice(4).readUInt32BE(0)) const lsb = BigInt(readUInt32BE(bytes.slice(4), 0))
const num = (msb << BigInt(32)) | lsb const num = (msb << BigInt(32)) | lsb
return `${sign}${num.toString()}` return `${sign}${num.toString()}`
@@ -172,7 +171,7 @@ class Amount extends SerializedType {
mantissa[0] = 0 mantissa[0] = 0
mantissa[1] &= 0x3f mantissa[1] &= 0x3f
const value = new BigNumber(`${sign}0x${mantissa.toString('hex')}`).times( const value = new BigNumber(`${sign}0x${bytesToHex(mantissa)}`).times(
`1e${exponent}`, `1e${exponent}`,
) )
Amount.assertIouIsValid(value) Amount.assertIouIsValid(value)

View File

@@ -1,11 +1,12 @@
import { SerializedType } from './serialized-type' import { SerializedType } from './serialized-type'
import { BinaryParser } from '../serdes/binary-parser' import { BinaryParser } from '../serdes/binary-parser'
import { hexToBytes } from '@xrplf/isomorphic/utils'
/** /**
* Variable length encoded type * Variable length encoded type
*/ */
class Blob extends SerializedType { class Blob extends SerializedType {
constructor(bytes: Buffer) { constructor(bytes: Uint8Array) {
super(bytes) super(bytes)
} }
@@ -32,7 +33,7 @@ class Blob extends SerializedType {
} }
if (typeof value === 'string') { if (typeof value === 'string') {
return new Blob(Buffer.from(value, 'hex')) return new Blob(hexToBytes(value))
} }
throw new Error('Cannot construct Blob from value given') throw new Error('Cannot construct Blob from value given')

View File

@@ -1,4 +1,5 @@
import { Hash160 } from './hash-160' import { Hash160 } from './hash-160'
import { bytesToHex, hexToBytes, hexToString } from '@xrplf/isomorphic/utils'
const XRP_HEX_REGEX = /^0{40}$/ const XRP_HEX_REGEX = /^0{40}$/
const ISO_REGEX = /^[A-Z0-9a-z?!@#$%^&*(){}[\]|]{3}$/ const ISO_REGEX = /^[A-Z0-9a-z?!@#$%^&*(){}[\]|]{3}$/
@@ -9,8 +10,8 @@ const STANDARD_FORMAT_HEX_REGEX = /^0{24}[\x00-\x7F]{6}0{10}$/
/** /**
* Convert an ISO code to a currency bytes representation * Convert an ISO code to a currency bytes representation
*/ */
function isoToBytes(iso: string): Buffer { function isoToBytes(iso: string): Uint8Array {
const bytes = Buffer.alloc(20) const bytes = new Uint8Array(20)
if (iso !== 'XRP') { if (iso !== 'XRP') {
const isoBytes = iso.split('').map((c) => c.charCodeAt(0)) const isoBytes = iso.split('').map((c) => c.charCodeAt(0))
bytes.set(isoBytes, 12) bytes.set(isoBytes, 12)
@@ -25,8 +26,8 @@ function isIsoCode(iso: string): boolean {
return ISO_REGEX.test(iso) return ISO_REGEX.test(iso)
} }
function isoCodeFromHex(code: Buffer): string | null { function isoCodeFromHex(code: Uint8Array): string | null {
const iso = code.toString() const iso = hexToString(bytesToHex(code))
if (iso === 'XRP') { if (iso === 'XRP') {
return null return null
} }
@@ -51,41 +52,41 @@ function isStringRepresentation(input: string): boolean {
} }
/** /**
* Tests if a Buffer is a valid representation of a currency * Tests if a Uint8Array is a valid representation of a currency
*/ */
function isBytesArray(bytes: Buffer): boolean { function isBytesArray(bytes: Uint8Array): boolean {
return bytes.byteLength === 20 return bytes.byteLength === 20
} }
/** /**
* Ensures that a value is a valid representation of a currency * Ensures that a value is a valid representation of a currency
*/ */
function isValidRepresentation(input: Buffer | string): boolean { function isValidRepresentation(input: Uint8Array | string): boolean {
return input instanceof Buffer return input instanceof Uint8Array
? isBytesArray(input) ? isBytesArray(input)
: isStringRepresentation(input) : isStringRepresentation(input)
} }
/** /**
* Generate bytes from a string or buffer representation of a currency * Generate bytes from a string or UInt8Array representation of a currency
*/ */
function bytesFromRepresentation(input: string): Buffer { function bytesFromRepresentation(input: string): Uint8Array {
if (!isValidRepresentation(input)) { if (!isValidRepresentation(input)) {
throw new Error(`Unsupported Currency representation: ${input}`) throw new Error(`Unsupported Currency representation: ${input}`)
} }
return input.length === 3 ? isoToBytes(input) : Buffer.from(input, 'hex') return input.length === 3 ? isoToBytes(input) : hexToBytes(input)
} }
/** /**
* Class defining how to encode and decode Currencies * Class defining how to encode and decode Currencies
*/ */
class Currency extends Hash160 { class Currency extends Hash160 {
static readonly XRP = new Currency(Buffer.alloc(20)) static readonly XRP = new Currency(new Uint8Array(20))
private readonly _iso: string | null private readonly _iso: string | null
constructor(byteBuf: Buffer) { constructor(byteBuf: Uint8Array) {
super(byteBuf ?? Currency.XRP.bytes) super(byteBuf ?? Currency.XRP.bytes)
const hex = this.bytes.toString('hex') const hex = bytesToHex(this.bytes)
if (XRP_HEX_REGEX.test(hex)) { if (XRP_HEX_REGEX.test(hex)) {
this._iso = 'XRP' this._iso = 'XRP'
@@ -132,7 +133,7 @@ class Currency extends Hash160 {
if (iso !== null) { if (iso !== null) {
return iso return iso
} }
return this.bytes.toString('hex').toUpperCase() return bytesToHex(this.bytes)
} }
} }

View File

@@ -1,13 +1,14 @@
import { Hash } from './hash' import { Hash } from './hash'
import { bytesToHex } from '@xrplf/isomorphic/utils'
/** /**
* Hash with a width of 128 bits * Hash with a width of 128 bits
*/ */
class Hash128 extends Hash { class Hash128 extends Hash {
static readonly width = 16 static readonly width = 16
static readonly ZERO_128: Hash128 = new Hash128(Buffer.alloc(Hash128.width)) static readonly ZERO_128: Hash128 = new Hash128(new Uint8Array(Hash128.width))
constructor(bytes: Buffer) { constructor(bytes: Uint8Array) {
if (bytes && bytes.byteLength === 0) { if (bytes && bytes.byteLength === 0) {
bytes = Hash128.ZERO_128.bytes bytes = Hash128.ZERO_128.bytes
} }
@@ -21,7 +22,7 @@ class Hash128 extends Hash {
* @returns hex String of this.bytes * @returns hex String of this.bytes
*/ */
toHex(): string { toHex(): string {
const hex = this.toBytes().toString('hex').toUpperCase() const hex = bytesToHex(this.toBytes())
if (/^0+$/.exec(hex)) { if (/^0+$/.exec(hex)) {
return '' return ''
} }

View File

@@ -5,9 +5,9 @@ import { Hash } from './hash'
*/ */
class Hash160 extends Hash { class Hash160 extends Hash {
static readonly width = 20 static readonly width = 20
static readonly ZERO_160: Hash160 = new Hash160(Buffer.alloc(Hash160.width)) static readonly ZERO_160: Hash160 = new Hash160(new Uint8Array(Hash160.width))
constructor(bytes?: Buffer) { constructor(bytes?: Uint8Array) {
if (bytes && bytes.byteLength === 0) { if (bytes && bytes.byteLength === 0) {
bytes = Hash160.ZERO_160.bytes bytes = Hash160.ZERO_160.bytes
} }

View File

@@ -5,9 +5,9 @@ import { Hash } from './hash'
*/ */
class Hash256 extends Hash { class Hash256 extends Hash {
static readonly width = 32 static readonly width = 32
static readonly ZERO_256 = new Hash256(Buffer.alloc(Hash256.width)) static readonly ZERO_256 = new Hash256(new Uint8Array(Hash256.width))
constructor(bytes: Buffer) { constructor(bytes: Uint8Array) {
super(bytes ?? Hash256.ZERO_256.bytes) super(bytes ?? Hash256.ZERO_256.bytes)
} }
} }

View File

@@ -1,5 +1,7 @@
import { Comparable } from './serialized-type' import { Comparable } from './serialized-type'
import { BinaryParser } from '../serdes/binary-parser' import { BinaryParser } from '../serdes/binary-parser'
import { hexToBytes } from '@xrplf/isomorphic/utils'
import { compare } from '../utils'
/** /**
* Base class defining how to encode and decode hashes * Base class defining how to encode and decode hashes
@@ -7,9 +9,9 @@ import { BinaryParser } from '../serdes/binary-parser'
class Hash extends Comparable<Hash | string> { class Hash extends Comparable<Hash | string> {
static readonly width: number static readonly width: number
constructor(bytes: Buffer) { constructor(bytes: Uint8Array) {
super(bytes) super(bytes)
if (this.bytes.byteLength !== (this.constructor as typeof Hash).width) { if (this.bytes.length !== (this.constructor as typeof Hash).width) {
throw new Error(`Invalid Hash length ${this.bytes.byteLength}`) throw new Error(`Invalid Hash length ${this.bytes.byteLength}`)
} }
} }
@@ -25,7 +27,7 @@ class Hash extends Comparable<Hash | string> {
} }
if (typeof value === 'string') { if (typeof value === 'string') {
return new this(Buffer.from(value, 'hex')) return new this(hexToBytes(value))
} }
throw new Error('Cannot construct Hash from given value') throw new Error('Cannot construct Hash from given value')
@@ -47,7 +49,8 @@ class Hash extends Comparable<Hash | string> {
* @param other The Hash to compare this to * @param other The Hash to compare this to
*/ */
compareTo(other: Hash): number { compareTo(other: Hash): number {
return this.bytes.compare( return compare(
this.bytes,
(this.constructor as typeof Hash).from(other).bytes, (this.constructor as typeof Hash).from(other).bytes,
) )
} }

View File

@@ -1,3 +1,4 @@
import { concat } from '@xrplf/isomorphic/utils'
import { BinaryParser } from '../serdes/binary-parser' import { BinaryParser } from '../serdes/binary-parser'
import { AccountID } from './account-id' import { AccountID } from './account-id'
@@ -27,9 +28,9 @@ function isIssueObject(arg): arg is IssueObject {
* Class for serializing/Deserializing Amounts * Class for serializing/Deserializing Amounts
*/ */
class Issue extends SerializedType { class Issue extends SerializedType {
static readonly ZERO_ISSUED_CURRENCY: Issue = new Issue(Buffer.alloc(20)) static readonly ZERO_ISSUED_CURRENCY: Issue = new Issue(new Uint8Array(20))
constructor(bytes: Buffer) { constructor(bytes: Uint8Array) {
super(bytes ?? Issue.ZERO_ISSUED_CURRENCY.bytes) super(bytes ?? Issue.ZERO_ISSUED_CURRENCY.bytes)
} }
@@ -51,7 +52,7 @@ class Issue extends SerializedType {
return new Issue(currency) return new Issue(currency)
} }
const issuer = AccountID.from(value.issuer).toBytes() const issuer = AccountID.from(value.issuer).toBytes()
return new Issue(Buffer.concat([currency, issuer])) return new Issue(concat([currency, issuer]))
} }
throw new Error('Invalid type to construct an Amount') throw new Error('Invalid type to construct an Amount')
@@ -69,7 +70,7 @@ class Issue extends SerializedType {
return new Issue(currency) return new Issue(currency)
} }
const currencyAndIssuer = [currency, parser.read(20)] const currencyAndIssuer = [currency, parser.read(20)]
return new Issue(Buffer.concat(currencyAndIssuer)) return new Issue(concat(currencyAndIssuer))
} }
/** /**

View File

@@ -2,6 +2,7 @@ import { AccountID } from './account-id'
import { Currency } from './currency' import { Currency } from './currency'
import { BinaryParser } from '../serdes/binary-parser' import { BinaryParser } from '../serdes/binary-parser'
import { SerializedType, JsonObject } from './serialized-type' import { SerializedType, JsonObject } from './serialized-type'
import { bytesToHex, concat } from '@xrplf/isomorphic/utils'
/** /**
* Constants for separating Paths in a PathSet * Constants for separating Paths in a PathSet
@@ -62,7 +63,7 @@ class Hop extends SerializedType {
return value return value
} }
const bytes: Array<Buffer> = [Buffer.from([0])] const bytes: Array<Uint8Array> = [Uint8Array.from([0])]
if (value.account) { if (value.account) {
bytes.push(AccountID.from(value.account).toBytes()) bytes.push(AccountID.from(value.account).toBytes())
@@ -79,7 +80,7 @@ class Hop extends SerializedType {
bytes[0][0] |= TYPE_ISSUER bytes[0][0] |= TYPE_ISSUER
} }
return new Hop(Buffer.concat(bytes)) return new Hop(concat(bytes))
} }
/** /**
@@ -90,7 +91,7 @@ class Hop extends SerializedType {
*/ */
static fromParser(parser: BinaryParser): Hop { static fromParser(parser: BinaryParser): Hop {
const type = parser.readUInt8() const type = parser.readUInt8()
const bytes: Array<Buffer> = [Buffer.from([type])] const bytes: Array<Uint8Array> = [Uint8Array.from([type])]
if (type & TYPE_ACCOUNT) { if (type & TYPE_ACCOUNT) {
bytes.push(parser.read(AccountID.width)) bytes.push(parser.read(AccountID.width))
@@ -104,7 +105,7 @@ class Hop extends SerializedType {
bytes.push(parser.read(AccountID.width)) bytes.push(parser.read(AccountID.width))
} }
return new Hop(Buffer.concat(bytes)) return new Hop(concat(bytes))
} }
/** /**
@@ -113,7 +114,7 @@ class Hop extends SerializedType {
* @returns a HopObject, an JS object with optional account, issuer, and currency * @returns a HopObject, an JS object with optional account, issuer, and currency
*/ */
toJSON(): HopObject { toJSON(): HopObject {
const hopParser = new BinaryParser(this.bytes.toString('hex')) const hopParser = new BinaryParser(bytesToHex(this.bytes))
const type = hopParser.readUInt8() const type = hopParser.readUInt8()
let account, currency, issuer let account, currency, issuer
@@ -170,12 +171,12 @@ class Path extends SerializedType {
return value return value
} }
const bytes: Array<Buffer> = [] const bytes: Array<Uint8Array> = []
value.forEach((hop: HopObject) => { value.forEach((hop: HopObject) => {
bytes.push(Hop.from(hop).toBytes()) bytes.push(Hop.from(hop).toBytes())
}) })
return new Path(Buffer.concat(bytes)) return new Path(concat(bytes))
} }
/** /**
@@ -185,7 +186,7 @@ class Path extends SerializedType {
* @returns the Path represented by the bytes read from the BinaryParser * @returns the Path represented by the bytes read from the BinaryParser
*/ */
static fromParser(parser: BinaryParser): Path { static fromParser(parser: BinaryParser): Path {
const bytes: Array<Buffer> = [] const bytes: Array<Uint8Array> = []
while (!parser.end()) { while (!parser.end()) {
bytes.push(Hop.fromParser(parser).toBytes()) bytes.push(Hop.fromParser(parser).toBytes())
@@ -196,7 +197,7 @@ class Path extends SerializedType {
break break
} }
} }
return new Path(Buffer.concat(bytes)) return new Path(concat(bytes))
} }
/** /**
@@ -232,16 +233,16 @@ class PathSet extends SerializedType {
} }
if (isPathSet(value)) { if (isPathSet(value)) {
const bytes: Array<Buffer> = [] const bytes: Array<Uint8Array> = []
value.forEach((path: Array<HopObject>) => { value.forEach((path: Array<HopObject>) => {
bytes.push(Path.from(path).toBytes()) bytes.push(Path.from(path).toBytes())
bytes.push(Buffer.from([PATH_SEPARATOR_BYTE])) bytes.push(Uint8Array.from([PATH_SEPARATOR_BYTE]))
}) })
bytes[bytes.length - 1] = Buffer.from([PATHSET_END_BYTE]) bytes[bytes.length - 1] = Uint8Array.from([PATHSET_END_BYTE])
return new PathSet(Buffer.concat(bytes)) return new PathSet(concat(bytes))
} }
throw new Error('Cannot construct PathSet from given value') throw new Error('Cannot construct PathSet from given value')
@@ -254,7 +255,7 @@ class PathSet extends SerializedType {
* @returns the PathSet read from parser * @returns the PathSet read from parser
*/ */
static fromParser(parser: BinaryParser): PathSet { static fromParser(parser: BinaryParser): PathSet {
const bytes: Array<Buffer> = [] const bytes: Array<Uint8Array> = []
while (!parser.end()) { while (!parser.end()) {
bytes.push(Path.fromParser(parser).toBytes()) bytes.push(Path.fromParser(parser).toBytes())
@@ -265,7 +266,7 @@ class PathSet extends SerializedType {
} }
} }
return new PathSet(Buffer.concat(bytes)) return new PathSet(concat(bytes))
} }
/** /**

View File

@@ -1,7 +1,7 @@
import { BytesList } from '../serdes/binary-serializer' import { BytesList } from '../serdes/binary-serializer'
import { BinaryParser } from '../serdes/binary-parser' import { BinaryParser } from '../serdes/binary-parser'
import { XrplDefinitionsBase } from '../enums' import { XrplDefinitionsBase } from '../enums'
import { bytesToHex } from '@xrplf/isomorphic/utils'
type JSON = string | number | boolean | null | undefined | JSON[] | JsonObject type JSON = string | number | boolean | null | undefined | JSON[] | JsonObject
@@ -11,10 +11,10 @@ type JsonObject = { [key: string]: JSON }
* The base class for all binary-codec types * The base class for all binary-codec types
*/ */
class SerializedType { class SerializedType {
protected readonly bytes: Buffer = Buffer.alloc(0) protected readonly bytes: Uint8Array = new Uint8Array(0)
constructor(bytes?: Buffer) { constructor(bytes?: Uint8Array) {
this.bytes = bytes ?? Buffer.alloc(0) this.bytes = bytes ?? new Uint8Array(0)
} }
static fromParser(parser: BinaryParser, hint?: number): SerializedType { static fromParser(parser: BinaryParser, hint?: number): SerializedType {
@@ -42,15 +42,15 @@ class SerializedType {
* @returns hex String of this.bytes * @returns hex String of this.bytes
*/ */
toHex(): string { toHex(): string {
return this.toBytes().toString('hex').toUpperCase() return bytesToHex(this.toBytes())
} }
/** /**
* Get the bytes representation of a SerializedType * Get the bytes representation of a SerializedType
* *
* @returns A buffer of the bytes * @returns A Uint8Array of the bytes
*/ */
toBytes(): Buffer { toBytes(): Uint8Array {
if (this.bytes) { if (this.bytes) {
return this.bytes return this.bytes
} }

View File

@@ -2,11 +2,12 @@ import { DEFAULT_DEFINITIONS, XrplDefinitionsBase } from '../enums'
import { SerializedType, JsonObject } from './serialized-type' import { SerializedType, JsonObject } from './serialized-type'
import { STObject } from './st-object' import { STObject } from './st-object'
import { BinaryParser } from '../serdes/binary-parser' import { BinaryParser } from '../serdes/binary-parser'
import { concat } from '@xrplf/isomorphic/utils'
const ARRAY_END_MARKER = Buffer.from([0xf1]) const ARRAY_END_MARKER = Uint8Array.from([0xf1])
const ARRAY_END_MARKER_NAME = 'ArrayEndMarker' const ARRAY_END_MARKER_NAME = 'ArrayEndMarker'
const OBJECT_END_MARKER = Buffer.from([0xe1]) const OBJECT_END_MARKER = Uint8Array.from([0xe1])
/** /**
* TypeGuard for Array<JsonObject> * TypeGuard for Array<JsonObject>
@@ -28,7 +29,7 @@ class STArray extends SerializedType {
* @returns An STArray Object * @returns An STArray Object
*/ */
static fromParser(parser: BinaryParser): STArray { static fromParser(parser: BinaryParser): STArray {
const bytes: Array<Buffer> = [] const bytes: Array<Uint8Array> = []
while (!parser.end()) { while (!parser.end()) {
const field = parser.readField() const field = parser.readField()
@@ -44,7 +45,7 @@ class STArray extends SerializedType {
} }
bytes.push(ARRAY_END_MARKER) bytes.push(ARRAY_END_MARKER)
return new STArray(Buffer.concat(bytes)) return new STArray(concat(bytes))
} }
/** /**
@@ -63,13 +64,13 @@ class STArray extends SerializedType {
} }
if (isObjects(value)) { if (isObjects(value)) {
const bytes: Array<Buffer> = [] const bytes: Array<Uint8Array> = []
value.forEach((obj) => { value.forEach((obj) => {
bytes.push(STObject.from(obj, undefined, definitions).toBytes()) bytes.push(STObject.from(obj, undefined, definitions).toBytes())
}) })
bytes.push(ARRAY_END_MARKER) bytes.push(ARRAY_END_MARKER)
return new STArray(Buffer.concat(bytes)) return new STArray(concat(bytes))
} }
throw new Error('Cannot construct STArray from value given') throw new Error('Cannot construct STArray from value given')

View File

@@ -11,7 +11,7 @@ import { BinarySerializer, BytesList } from '../serdes/binary-serializer'
import { STArray } from './st-array' import { STArray } from './st-array'
const OBJECT_END_MARKER_BYTE = Buffer.from([0xe1]) const OBJECT_END_MARKER_BYTE = Uint8Array.from([0xe1])
const OBJECT_END_MARKER = 'ObjectEndMarker' const OBJECT_END_MARKER = 'ObjectEndMarker'
const ST_OBJECT = 'STObject' const ST_OBJECT = 'STObject'
const DESTINATION = 'Destination' const DESTINATION = 'Destination'

View File

@@ -1,14 +1,17 @@
import { UInt } from './uint' import { UInt } from './uint'
import { BinaryParser } from '../serdes/binary-parser' import { BinaryParser } from '../serdes/binary-parser'
import { readUInt16BE, writeUInt16BE } from '../utils'
/** /**
* Derived UInt class for serializing/deserializing 16 bit UInt * Derived UInt class for serializing/deserializing 16 bit UInt
*/ */
class UInt16 extends UInt { class UInt16 extends UInt {
protected static readonly width: number = 16 / 8 // 2 protected static readonly width: number = 16 / 8 // 2
static readonly defaultUInt16: UInt16 = new UInt16(Buffer.alloc(UInt16.width)) static readonly defaultUInt16: UInt16 = new UInt16(
new Uint8Array(UInt16.width),
)
constructor(bytes: Buffer) { constructor(bytes: Uint8Array) {
super(bytes ?? UInt16.defaultUInt16.bytes) super(bytes ?? UInt16.defaultUInt16.bytes)
} }
@@ -27,8 +30,10 @@ class UInt16 extends UInt {
} }
if (typeof val === 'number') { if (typeof val === 'number') {
const buf = Buffer.alloc(UInt16.width) UInt16.checkUintRange(val, 0, 0xffff)
buf.writeUInt16BE(val, 0)
const buf = new Uint8Array(UInt16.width)
writeUInt16BE(buf, val, 0)
return new UInt16(buf) return new UInt16(buf)
} }
@@ -41,7 +46,7 @@ class UInt16 extends UInt {
* @returns the number represented by this.bytes * @returns the number represented by this.bytes
*/ */
valueOf(): number { valueOf(): number {
return this.bytes.readUInt16BE(0) return parseInt(readUInt16BE(this.bytes, 0))
} }
} }

View File

@@ -1,14 +1,17 @@
import { UInt } from './uint' import { UInt } from './uint'
import { BinaryParser } from '../serdes/binary-parser' import { BinaryParser } from '../serdes/binary-parser'
import { readUInt32BE, writeUInt32BE } from '../utils'
/** /**
* Derived UInt class for serializing/deserializing 32 bit UInt * Derived UInt class for serializing/deserializing 32 bit UInt
*/ */
class UInt32 extends UInt { class UInt32 extends UInt {
protected static readonly width: number = 32 / 8 // 4 protected static readonly width: number = 32 / 8 // 4
static readonly defaultUInt32: UInt32 = new UInt32(Buffer.alloc(UInt32.width)) static readonly defaultUInt32: UInt32 = new UInt32(
new Uint8Array(UInt32.width),
)
constructor(bytes: Buffer) { constructor(bytes: Uint8Array) {
super(bytes ?? UInt32.defaultUInt32.bytes) super(bytes ?? UInt32.defaultUInt32.bytes)
} }
@@ -26,16 +29,17 @@ class UInt32 extends UInt {
return val return val
} }
const buf = Buffer.alloc(UInt32.width) const buf = new Uint8Array(UInt32.width)
if (typeof val === 'string') { if (typeof val === 'string') {
const num = Number.parseInt(val) const num = Number.parseInt(val)
buf.writeUInt32BE(num, 0) writeUInt32BE(buf, num, 0)
return new UInt32(buf) return new UInt32(buf)
} }
if (typeof val === 'number') { if (typeof val === 'number') {
buf.writeUInt32BE(val, 0) UInt32.checkUintRange(val, 0, 0xffffffff)
writeUInt32BE(buf, val, 0)
return new UInt32(buf) return new UInt32(buf)
} }
@@ -48,7 +52,7 @@ class UInt32 extends UInt {
* @returns the number represented by this.bytes * @returns the number represented by this.bytes
*/ */
valueOf(): number { valueOf(): number {
return this.bytes.readUInt32BE(0) return parseInt(readUInt32BE(this.bytes, 0), 10)
} }
} }

View File

@@ -1,5 +1,7 @@
import { UInt } from './uint' import { UInt } from './uint'
import { BinaryParser } from '../serdes/binary-parser' import { BinaryParser } from '../serdes/binary-parser'
import { bytesToHex, concat, hexToBytes } from '@xrplf/isomorphic/utils'
import { readUInt32BE, writeUInt32BE } from '../utils'
const HEX_REGEX = /^[a-fA-F0-9]{1,16}$/ const HEX_REGEX = /^[a-fA-F0-9]{1,16}$/
const mask = BigInt(0x00000000ffffffff) const mask = BigInt(0x00000000ffffffff)
@@ -9,9 +11,11 @@ const mask = BigInt(0x00000000ffffffff)
*/ */
class UInt64 extends UInt { class UInt64 extends UInt {
protected static readonly width: number = 64 / 8 // 8 protected static readonly width: number = 64 / 8 // 8
static readonly defaultUInt64: UInt64 = new UInt64(Buffer.alloc(UInt64.width)) static readonly defaultUInt64: UInt64 = new UInt64(
new Uint8Array(UInt64.width),
)
constructor(bytes: Buffer) { constructor(bytes: Uint8Array) {
super(bytes ?? UInt64.defaultUInt64.bytes) super(bytes ?? UInt64.defaultUInt64.bytes)
} }
@@ -30,7 +34,7 @@ class UInt64 extends UInt {
return val return val
} }
let buf = Buffer.alloc(UInt64.width) let buf = new Uint8Array(UInt64.width)
if (typeof val === 'number') { if (typeof val === 'number') {
if (val < 0) { if (val < 0) {
@@ -39,11 +43,11 @@ class UInt64 extends UInt {
const number = BigInt(val) const number = BigInt(val)
const intBuf = [Buffer.alloc(4), Buffer.alloc(4)] const intBuf = [new Uint8Array(4), new Uint8Array(4)]
intBuf[0].writeUInt32BE(Number(number >> BigInt(32)), 0) writeUInt32BE(intBuf[0], Number(number >> BigInt(32)), 0)
intBuf[1].writeUInt32BE(Number(number & BigInt(mask)), 0) writeUInt32BE(intBuf[1], Number(number & BigInt(mask)), 0)
return new UInt64(Buffer.concat(intBuf)) return new UInt64(concat(intBuf))
} }
if (typeof val === 'string') { if (typeof val === 'string') {
@@ -52,16 +56,16 @@ class UInt64 extends UInt {
} }
const strBuf = val.padStart(16, '0') const strBuf = val.padStart(16, '0')
buf = Buffer.from(strBuf, 'hex') buf = hexToBytes(strBuf)
return new UInt64(buf) return new UInt64(buf)
} }
if (typeof val === 'bigint') { if (typeof val === 'bigint') {
const intBuf = [Buffer.alloc(4), Buffer.alloc(4)] const intBuf = [new Uint8Array(4), new Uint8Array(4)]
intBuf[0].writeUInt32BE(Number(val >> BigInt(32)), 0) writeUInt32BE(intBuf[0], Number(Number(val >> BigInt(32))), 0)
intBuf[1].writeUInt32BE(Number(val & BigInt(mask)), 0) writeUInt32BE(intBuf[1], Number(val & BigInt(mask)), 0)
return new UInt64(Buffer.concat(intBuf)) return new UInt64(concat(intBuf))
} }
throw new Error('Cannot construct UInt64 from given value') throw new Error('Cannot construct UInt64 from given value')
@@ -73,7 +77,7 @@ class UInt64 extends UInt {
* @returns a hex-string * @returns a hex-string
*/ */
toJSON(): string { toJSON(): string {
return this.bytes.toString('hex').toUpperCase() return bytesToHex(this.bytes)
} }
/** /**
@@ -82,8 +86,8 @@ class UInt64 extends UInt {
* @returns the number represented buy this.bytes * @returns the number represented buy this.bytes
*/ */
valueOf(): bigint { valueOf(): bigint {
const msb = BigInt(this.bytes.slice(0, 4).readUInt32BE(0)) const msb = BigInt(readUInt32BE(this.bytes.slice(0, 4), 0))
const lsb = BigInt(this.bytes.slice(4).readUInt32BE(0)) const lsb = BigInt(readUInt32BE(this.bytes.slice(4), 0))
return (msb << BigInt(32)) | lsb return (msb << BigInt(32)) | lsb
} }
@@ -92,7 +96,7 @@ class UInt64 extends UInt {
* *
* @returns 8 bytes representing the UInt64 * @returns 8 bytes representing the UInt64
*/ */
toBytes(): Buffer { toBytes(): Uint8Array {
return this.bytes return this.bytes
} }
} }

View File

@@ -1,14 +1,16 @@
import { UInt } from './uint' import { UInt } from './uint'
import { BinaryParser } from '../serdes/binary-parser' import { BinaryParser } from '../serdes/binary-parser'
import { bytesToHex } from '@xrplf/isomorphic/utils'
import { writeUInt8 } from '../utils'
/** /**
* Derived UInt class for serializing/deserializing 8 bit UInt * Derived UInt class for serializing/deserializing 8 bit UInt
*/ */
class UInt8 extends UInt { class UInt8 extends UInt {
protected static readonly width: number = 8 / 8 // 1 protected static readonly width: number = 8 / 8 // 1
static readonly defaultUInt8: UInt8 = new UInt8(Buffer.alloc(UInt8.width)) static readonly defaultUInt8: UInt8 = new UInt8(new Uint8Array(UInt8.width))
constructor(bytes: Buffer) { constructor(bytes: Uint8Array) {
super(bytes ?? UInt8.defaultUInt8.bytes) super(bytes ?? UInt8.defaultUInt8.bytes)
} }
@@ -27,8 +29,10 @@ class UInt8 extends UInt {
} }
if (typeof val === 'number') { if (typeof val === 'number') {
const buf = Buffer.alloc(UInt8.width) UInt8.checkUintRange(val, 0, 0xff)
buf.writeUInt8(val, 0)
const buf = new Uint8Array(UInt8.width)
writeUInt8(buf, val, 0)
return new UInt8(buf) return new UInt8(buf)
} }
@@ -41,7 +45,7 @@ class UInt8 extends UInt {
* @returns the number represented by this.bytes * @returns the number represented by this.bytes
*/ */
valueOf(): number { valueOf(): number {
return this.bytes.readUInt8(0) return parseInt(bytesToHex(this.bytes), 16)
} }
} }

View File

@@ -17,7 +17,7 @@ function compare(n1: number | bigint, n2: number | bigint): number {
abstract class UInt extends Comparable<UInt | number> { abstract class UInt extends Comparable<UInt | number> {
protected static width: number protected static width: number
constructor(bytes: Buffer) { constructor(bytes: Uint8Array) {
super(bytes) super(bytes)
} }
@@ -47,6 +47,14 @@ abstract class UInt extends Comparable<UInt | number> {
* @returns the value * @returns the value
*/ */
abstract valueOf(): number | bigint abstract valueOf(): number | bigint
static checkUintRange(val: number, min: number, max: number): void {
if (val < min || val > max) {
throw new Error(
`Invalid ${this.constructor.name}: ${val} must be >= ${min} and <= ${max}`,
)
}
}
} }
export { UInt } export { UInt }

View File

@@ -2,6 +2,7 @@ import { SerializedType } from './serialized-type'
import { BinaryParser } from '../serdes/binary-parser' import { BinaryParser } from '../serdes/binary-parser'
import { Hash256 } from './hash-256' import { Hash256 } from './hash-256'
import { BytesList } from '../serdes/binary-serializer' import { BytesList } from '../serdes/binary-serializer'
import { bytesToHex } from '@xrplf/isomorphic/utils'
/** /**
* TypeGuard for Array<string> * TypeGuard for Array<string>
@@ -14,7 +15,7 @@ function isStrings(arg): arg is Array<string> {
* Class for serializing and deserializing vectors of Hash256 * Class for serializing and deserializing vectors of Hash256
*/ */
class Vector256 extends SerializedType { class Vector256 extends SerializedType {
constructor(bytes: Buffer) { constructor(bytes: Uint8Array) {
super(bytes) super(bytes)
} }
@@ -69,12 +70,7 @@ class Vector256 extends SerializedType {
const result: Array<string> = [] const result: Array<string> = []
for (let i = 0; i < this.bytes.byteLength; i += 32) { for (let i = 0; i < this.bytes.byteLength; i += 32) {
result.push( result.push(bytesToHex(this.bytes.slice(i, i + 32)))
this.bytes
.slice(i, i + 32)
.toString('hex')
.toUpperCase(),
)
} }
return result return result
} }

View File

@@ -2,8 +2,8 @@ import { BinaryParser } from '../serdes/binary-parser'
import { AccountID } from './account-id' import { AccountID } from './account-id'
import { JsonObject, SerializedType } from './serialized-type' import { JsonObject, SerializedType } from './serialized-type'
import { Issue, IssueObject } from './issue' import { Issue, IssueObject } from './issue'
import { concat } from '@xrplf/isomorphic/utils'
/** /**
* Interface for JSON objects that represent cross-chain bridges * Interface for JSON objects that represent cross-chain bridges
@@ -34,11 +34,11 @@ function isXChainBridgeObject(arg): arg is XChainBridgeObject {
*/ */
class XChainBridge extends SerializedType { class XChainBridge extends SerializedType {
static readonly ZERO_XCHAIN_BRIDGE: XChainBridge = new XChainBridge( static readonly ZERO_XCHAIN_BRIDGE: XChainBridge = new XChainBridge(
Buffer.concat([ concat([
Buffer.from([0x14]), Uint8Array.from([0x14]),
Buffer.alloc(40), new Uint8Array(40),
Buffer.from([0x14]), Uint8Array.from([0x14]),
Buffer.alloc(40), new Uint8Array(40),
]), ]),
) )
@@ -50,7 +50,7 @@ class XChainBridge extends SerializedType {
{ name: 'IssuingChainIssue', type: Issue }, { name: 'IssuingChainIssue', type: Issue },
] ]
constructor(bytes: Buffer) { constructor(bytes: Uint8Array) {
super(bytes ?? XChainBridge.ZERO_XCHAIN_BRIDGE.bytes) super(bytes ?? XChainBridge.ZERO_XCHAIN_BRIDGE.bytes)
} }
@@ -71,16 +71,16 @@ class XChainBridge extends SerializedType {
throw new Error('Invalid type to construct an XChainBridge') throw new Error('Invalid type to construct an XChainBridge')
} }
const bytes: Array<Buffer> = [] const bytes: Array<Uint8Array> = []
this.TYPE_ORDER.forEach((item) => { this.TYPE_ORDER.forEach((item) => {
const { name, type } = item const { name, type } = item
if (type === AccountID) { if (type === AccountID) {
bytes.push(Buffer.from([0x14])) bytes.push(Uint8Array.from([0x14]))
} }
const object = type.from(value[name]) const object = type.from(value[name])
bytes.push(object.toBytes()) bytes.push(object.toBytes())
}) })
return new XChainBridge(Buffer.concat(bytes)) return new XChainBridge(concat(bytes))
} }
/** /**
@@ -90,19 +90,19 @@ class XChainBridge extends SerializedType {
* @returns An XChainBridge object * @returns An XChainBridge object
*/ */
static fromParser(parser: BinaryParser): XChainBridge { static fromParser(parser: BinaryParser): XChainBridge {
const bytes: Array<Buffer> = [] const bytes: Array<Uint8Array> = []
this.TYPE_ORDER.forEach((item) => { this.TYPE_ORDER.forEach((item) => {
const { type } = item const { type } = item
if (type === AccountID) { if (type === AccountID) {
parser.skip(1) parser.skip(1)
bytes.push(Buffer.from([0x14])) bytes.push(Uint8Array.from([0x14]))
} }
const object = type.fromParser(parser) const object = type.fromParser(parser)
bytes.push(object.toBytes()) bytes.push(object.toBytes())
}) })
return new XChainBridge(Buffer.concat(bytes)) return new XChainBridge(concat(bytes))
} }
/** /**

View File

@@ -0,0 +1,152 @@
// Even though this comes from NodeJS it is valid in the browser
import TypedArray = NodeJS.TypedArray
/**
* Writes value to array at the specified offset. The value must be a valid unsigned 8-bit integer.
* @param array Uint8Array to be written to
* @param value Number to be written to array.
* @param offset plus the number of bytes written.
*/
export function writeUInt8(
array: Uint8Array,
value: number,
offset: number,
): void {
value = Number(value)
array[offset] = value
}
/**
* Writes value to array at the specified offset as big-endian. The value must be a valid unsigned 16-bit integer.
* @param array Uint8Array to be written to
* @param value Number to be written to array.
* @param offset plus the number of bytes written.
*/
export function writeUInt16BE(
array: Uint8Array,
value: number,
offset: number,
): void {
value = Number(value)
array[offset] = value >>> 8
array[offset + 1] = value
}
/**
* Writes value to array at the specified offset as big-endian. The value must be a valid unsigned 32-bit integer.
* @param array Uint8Array to be written to
* @param value Number to be written to array.
* @param offset plus the number of bytes written.
*/
export function writeUInt32BE(
array: Uint8Array,
value: number,
offset: number,
): void {
array[offset] = (value >>> 24) & 0xff
array[offset + 1] = (value >>> 16) & 0xff
array[offset + 2] = (value >>> 8) & 0xff
array[offset + 3] = value & 0xff
}
/**
* Reads an unsigned, big-endian 16-bit integer from the array at the specified offset.
* @param array Uint8Array to read
* @param offset Number of bytes to skip before starting to read. Must satisfy 0 <= offset <= buf.length - 2
*/
export function readUInt16BE(array: Uint8Array, offset: number): string {
return new DataView(array.buffer).getUint16(offset, false).toString(10)
}
/**
* Reads an unsigned, big-endian 16-bit integer from the array at the specified offset.
* @param array Uint8Array to read
* @param offset Number of bytes to skip before starting to read. Must satisfy 0 <= offset <= buf.length - 4
*/
export function readUInt32BE(array: Uint8Array, offset: number): string {
return new DataView(array.buffer).getUint32(offset, false).toString(10)
}
/**
* Compares two Uint8Array or ArrayBuffers
* @param a first array to compare
* @param b second array to compare
*/
export function equal(
a: Uint8Array | ArrayBuffer,
b: Uint8Array | ArrayBuffer,
): boolean {
const aUInt = a instanceof ArrayBuffer ? new Uint8Array(a, 0) : a
const bUInt = b instanceof ArrayBuffer ? new Uint8Array(b, 0) : b
if (aUInt.byteLength != bUInt.byteLength) return false
if (aligned32(aUInt) && aligned32(bUInt)) return compare32(aUInt, bUInt) === 0
if (aligned16(aUInt) && aligned16(bUInt)) return compare16(aUInt, bUInt) === 0
return compare8(aUInt, bUInt) === 0
}
/**
* Compares two 8 bit aligned arrays
* @param a first array to compare
* @param b second array to compare
*/
function compare8(a, b) {
const ua = new Uint8Array(a.buffer, a.byteOffset, a.byteLength)
const ub = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)
return compare(ua, ub)
}
/**
* Compares two 16 bit aligned arrays
* @param a first array to compare
* @param b second array to compare
*/
function compare16(a: Uint8Array, b: Uint8Array) {
const ua = new Uint16Array(a.buffer, a.byteOffset, a.byteLength / 2)
const ub = new Uint16Array(b.buffer, b.byteOffset, b.byteLength / 2)
return compare(ua, ub)
}
/**
* Compares two 32 bit aligned arrays
* @param a first array to compare
* @param b second array to compare
*/
function compare32(a: Uint8Array, b: Uint8Array) {
const ua = new Uint32Array(a.buffer, a.byteOffset, a.byteLength / 4)
const ub = new Uint32Array(b.buffer, b.byteOffset, b.byteLength / 4)
return compare(ua, ub)
}
/**
* Compare two TypedArrays
* @param a first array to compare
* @param b second array to compare
*/
export function compare(a: TypedArray, b: TypedArray): 1 | -1 | 0 {
if (a.byteLength !== b.byteLength) {
throw new Error('Cannot compare arrays of different length')
}
for (let i = 0; i < a.length - 1; i += 1) {
if (a[i] > b[i]) return 1
if (a[i] < b[i]) return -1
}
return 0
}
/**
* Determine if TypedArray is 16 bit aligned
* @param array The array to check
*/
function aligned16(array: TypedArray) {
return array.byteOffset % 2 === 0 && array.byteLength % 2 === 0
}
/**
* Determine if TypedArray is 32 bit aligned
* @param array The array to check
*/
function aligned32(array: TypedArray) {
return array.byteOffset % 4 === 0 && array.byteLength % 4 === 0
}

View File

@@ -1,14 +1,15 @@
import { hexOnly } from './utils'
import { coreTypes, Amount, Hash160 } from '../src/types' import { coreTypes, Amount, Hash160 } from '../src/types'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { encodeAccountID } from 'ripple-address-codec' import { encodeAccountID } from 'ripple-address-codec'
import { Field, TransactionType } from '../src/enums' import { Field, TransactionType } from '../src/enums'
import { makeParser, readJSON } from '../src/binary' import { makeParser, readJSON } from '../src/binary'
import { parseHexOnly, hexOnly } from './utils'
import { BytesList } from '../src/serdes/binary-serializer' import { BytesList } from '../src/serdes/binary-serializer'
import fixtures from './fixtures/data-driven-tests.json' import fixtures from './fixtures/data-driven-tests.json'
const __ = hexOnly const { bytesToHex } = require('@xrplf/isomorphic/utils')
function toJSON(v) { function toJSON(v) {
return v.toJSON ? v.toJSON() : v return v.toJSON ? v.toJSON() : v
} }
@@ -28,16 +29,15 @@ function assertEqualAmountJSON(actual, expected) {
} }
function basicApiTests() { function basicApiTests() {
const bytes = parseHexOnly('00,01020304,0506')
it('can read slices of bytes', () => { it('can read slices of bytes', () => {
const parser = makeParser(bytes) const parser = makeParser('00010203040506')
// @ts-expect-error -- checking private variable type // @ts-expect-error -- checking private variable type
expect(parser.bytes instanceof Buffer).toBe(true) expect(parser.bytes instanceof Uint8Array).toBe(true)
const read1 = parser.read(1) const read1 = parser.read(1)
expect(read1 instanceof Buffer).toBe(true) expect(read1 instanceof Uint8Array).toBe(true)
expect(read1).toEqual(Buffer.from([0])) expect(read1).toEqual(Uint8Array.from([0]))
expect(parser.read(4)).toEqual(Buffer.from([1, 2, 3, 4])) expect(parser.read(4)).toEqual(Uint8Array.from([1, 2, 3, 4]))
expect(parser.read(2)).toEqual(Buffer.from([5, 6])) expect(parser.read(2)).toEqual(Uint8Array.from([5, 6]))
expect(() => parser.read(1)).toThrow() expect(() => parser.read(1)).toThrow()
}) })
it('can read a Uint32 at full', () => { it('can read a Uint32 at full', () => {
@@ -62,12 +62,12 @@ function transactionParsingTests() {
}, },
TakerPays: '98957503520', TakerPays: '98957503520',
TransactionType: 'OfferCreate', TransactionType: 'OfferCreate',
TxnSignature: __(` TxnSignature: hexOnly(`
304502202ABE08D5E78D1E74A4C18F2714F64E87B8BD57444AF 304502202ABE08D5E78D1E74A4C18F2714F64E87B8BD57444AF
A5733109EB3C077077520022100DB335EE97386E4C0591CAC02 A5733109EB3C077077520022100DB335EE97386E4C0591CAC02
4D50E9230D8F171EEB901B5E5E4BD6D1E0AEF98C`), 4D50E9230D8F171EEB901B5E5E4BD6D1E0AEF98C`),
}, },
binary: __(` binary: hexOnly(`
120007220000000024000195F964400000170A53AC2065D5460561E 120007220000000024000195F964400000170A53AC2065D5460561E
C9DE000000000000000000000000000494C53000000000092D70596 C9DE000000000000000000000000000494C53000000000092D70596
8936C419CE614BF264B5EEB1CEA47FF468400000000000000A73210 8936C419CE614BF264B5EEB1CEA47FF468400000000000000A73210
@@ -102,11 +102,9 @@ function transactionParsingTests() {
expect(parser.read(8)).not.toEqual([]) expect(parser.read(8)).not.toEqual([])
expect(parser.readField()).toEqual(Field['SigningPubKey']) expect(parser.readField()).toEqual(Field['SigningPubKey'])
expect(parser.readVariableLengthLength()).toBe(33) expect(parser.readVariableLengthLength()).toBe(33)
expect(parser.read(33).toString('hex').toUpperCase()).toEqual( expect(bytesToHex(parser.read(33))).toEqual(tx_json.SigningPubKey)
tx_json.SigningPubKey,
)
expect(parser.readField()).toEqual(Field['TxnSignature']) expect(parser.readField()).toEqual(Field['TxnSignature'])
expect(parser.readVariableLength().toString('hex').toUpperCase()).toEqual( expect(bytesToHex(parser.readVariableLength())).toEqual(
tx_json.TxnSignature, tx_json.TxnSignature,
) )
expect(parser.readField()).toEqual(Field['Account']) expect(parser.readField()).toEqual(Field['Account'])
@@ -303,7 +301,7 @@ function nestedObjectTests() {
} }
function pathSetBinaryTests() { function pathSetBinaryTests() {
const bytes = __( const bytes = hexOnly(
`1200002200000000240000002E2E00004BF161D4C71AFD498D00000000000000 `1200002200000000240000002E2E00004BF161D4C71AFD498D00000000000000
0000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA0 0000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA0
6594D168400000000000000A69D446F8038585E9400000000000000000000000 6594D168400000000000000A69D446F8038585E9400000000000000000000000

View File

@@ -5,6 +5,7 @@ const { encode, decode } = require('../src')
const { makeParser, BytesList, BinarySerializer } = binary const { makeParser, BytesList, BinarySerializer } = binary
const { coreTypes } = require('../src/types') const { coreTypes } = require('../src/types')
const { UInt8, UInt16, UInt32, UInt64, STObject } = coreTypes const { UInt8, UInt16, UInt32, UInt64, STObject } = coreTypes
const deliverMinTx = require('./fixtures/delivermin-tx.json') const deliverMinTx = require('./fixtures/delivermin-tx.json')
const deliverMinTxBinary = require('./fixtures/delivermin-tx-binary.json') const deliverMinTxBinary = require('./fixtures/delivermin-tx-binary.json')
const SignerListSet = { const SignerListSet = {
@@ -105,12 +106,12 @@ const NegativeUNL = require('./fixtures/negative-unl.json')
function bytesListTest() { function bytesListTest() {
const list = new BytesList() const list = new BytesList()
.put(Buffer.from([0])) .put(Uint8Array.from([0]))
.put(Buffer.from([2, 3])) .put(Uint8Array.from([2, 3]))
.put(Buffer.from([4, 5])) .put(Uint8Array.from([4, 5]))
it('is an Array<Buffer>', function () { it('is an Array<Uint8Array>', function () {
expect(Array.isArray(list.bytesArray)).toBe(true) expect(Array.isArray(list.bytesArray)).toBe(true)
expect(list.bytesArray[0] instanceof Buffer).toBe(true) expect(list.bytesArray[0] instanceof Uint8Array).toBe(true)
}) })
it('keeps track of the length itself', function () { it('keeps track of the length itself', function () {
expect(list.getLength()).toBe(5) expect(list.getLength()).toBe(5)
@@ -118,7 +119,7 @@ function bytesListTest() {
it('can join all arrays into one via toBytes', function () { it('can join all arrays into one via toBytes', function () {
const joined = list.toBytes() const joined = list.toBytes()
expect(joined.length).toEqual(5) expect(joined.length).toEqual(5)
expect(joined).toEqual(Buffer.from([0, 2, 3, 4, 5])) expect(joined).toEqual(Uint8Array.from([0, 2, 3, 4, 5]))
}) })
} }
@@ -149,10 +150,18 @@ function check(type, n, expected) {
return return
} }
serializer.writeType(type, n) serializer.writeType(type, n)
expect(bl.toBytes()).toEqual(Buffer.from(expected)) expect(bl.toBytes()).toEqual(Uint8Array.from(expected))
}) })
} }
it(`Uint16 serializes 5 as 0,5`, function () {
const bl = new BytesList()
const serializer = new BinarySerializer(bl)
const expected = [0, 5]
serializer.writeType(UInt16, 5)
expect(bl.toBytes()).toEqual(Uint8Array.from(expected))
})
check(UInt8, 5, [5]) check(UInt8, 5, [5])
check(UInt16, 5, [0, 5]) check(UInt16, 5, [0, 5])
check(UInt32, 5, [0, 0, 0, 5]) check(UInt32, 5, [0, 0, 0, 5])

View File

@@ -1,5 +1,4 @@
const { coreTypes } = require('../src/types') import { Hash128, Hash160, Hash256, AccountID, Currency } from '../src/types'
const { Hash128, Hash160, Hash256, AccountID, Currency } = coreTypes
describe('Hash128', function () { describe('Hash128', function () {
it('has a static width member', function () { it('has a static width member', function () {
@@ -110,8 +109,8 @@ describe('Currency', function () {
).toBe(null) ).toBe(null)
}) })
it('can be constructed from a Buffer', function () { it('can be constructed from a Uint8Array', function () {
const xrp = new Currency(Buffer.alloc(20)) const xrp = new Currency(new Uint8Array(20))
expect(xrp.iso()).toBe('XRP') expect(xrp.iso()).toBe('XRP')
}) })
it('Can handle non-standard currency codes', () => { it('Can handle non-standard currency codes', () => {
@@ -125,7 +124,9 @@ describe('Currency', function () {
}) })
it('throws on invalid reprs', function () { it('throws on invalid reprs', function () {
expect(() => Currency.from(Buffer.alloc(19))).toThrow() // @ts-expect-error -- invalid type check
expect(() => Currency.from(new Uint8Array(19))).toThrow()
// @ts-expect-error -- invalid type check
expect(() => Currency.from(1)).toThrow() expect(() => Currency.from(1)).toThrow()
expect(() => expect(() =>
Currency.from('00000000000000000000000000000000000000m'), Currency.from('00000000000000000000000000000000000000m'),

View File

@@ -1,4 +1,5 @@
const { quality } = require('../src/coretypes') const { quality } = require('../src/coretypes')
const { bytesToHex } = require('@xrplf/isomorphic/utils')
describe('Quality encode/decode', function () { describe('Quality encode/decode', function () {
const bookDirectory = const bookDirectory =
@@ -10,6 +11,6 @@ describe('Quality encode/decode', function () {
}) })
it('can encode', function () { it('can encode', function () {
const bytes = quality.encode(expectedQuality) const bytes = quality.encode(expectedQuality)
expect(bytes.toString('hex').toUpperCase()).toBe(bookDirectory.slice(-16)) expect(bytesToHex(bytes)).toBe(bookDirectory.slice(-16))
}) })
}) })

View File

@@ -16,7 +16,7 @@ function makeItem(
indexArg: string, indexArg: string,
): [ ): [
Hash256, Hash256,
{ toBytesSink: (sink: BytesList) => void; hashPrefix: () => Buffer }, { toBytesSink: (sink: BytesList) => void; hashPrefix: () => Uint8Array },
] { ] {
let str = indexArg let str = indexArg
while (str.length < 64) { while (str.length < 64) {
@@ -28,7 +28,7 @@ function makeItem(
index.toBytesSink(sink) index.toBytesSink(sink)
}, },
hashPrefix() { hashPrefix() {
return Buffer.from([1, 3, 3, 7]) return Uint8Array.from([1, 3, 3, 7])
}, },
} }
return [index, item] return [index, item]

View File

@@ -1,7 +1,9 @@
import { hexToBytes } from '@xrplf/isomorphic/utils'
export function hexOnly(hex: string): string { export function hexOnly(hex: string): string {
return hex.replace(/[^a-fA-F0-9]/g, '') return hex.replace(/[^a-fA-F0-9]/g, '')
} }
export function parseHexOnly(hex: string): Buffer { export function parseHexOnly(hex: string): Uint8Array {
return Buffer.from(hexOnly(hex), 'hex') return hexToBytes(hexOnly(hex))
} }

View File

@@ -2,6 +2,12 @@
## Unreleased ## Unreleased
### Breaking Changes
* `Buffer` has been replaced with `UInt8Array` for both params and return values. `Buffer` may continue to work with params since they extend `UInt8Arrays`.
### Changes
* Eliminates 4 runtime dependencies: `base-x`, `base64-js`, `buffer`, and `ieee754`.
## 2.0.0 Beta 1 (2023-10-19) ## 2.0.0 Beta 1 (2023-10-19)
### Breaking Changes ### Breaking Changes

View File

@@ -8,6 +8,7 @@ import {
sign, sign,
verify, verify,
} from '../src' } from '../src'
import { stringToHex } from '@xrplf/isomorphic/utils'
const entropy = new Uint8Array([ const entropy = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
@@ -77,7 +78,7 @@ describe('api', () => {
it('sign - secp256k1', () => { it('sign - secp256k1', () => {
const privateKey = fixtures.secp256k1.keypair.privateKey const privateKey = fixtures.secp256k1.keypair.privateKey
const message = fixtures.secp256k1.message const message = fixtures.secp256k1.message
const messageHex = Buffer.from(message, 'utf8').toString('hex') const messageHex = stringToHex(message)
const signature = sign(messageHex, privateKey) const signature = sign(messageHex, privateKey)
expect(signature).toEqual(fixtures.secp256k1.signature) expect(signature).toEqual(fixtures.secp256k1.signature)
}) })
@@ -86,14 +87,14 @@ describe('api', () => {
const signature = fixtures.secp256k1.signature const signature = fixtures.secp256k1.signature
const publicKey = fixtures.secp256k1.keypair.publicKey const publicKey = fixtures.secp256k1.keypair.publicKey
const message = fixtures.secp256k1.message const message = fixtures.secp256k1.message
const messageHex = Buffer.from(message, 'utf8').toString('hex') const messageHex = stringToHex(message)
expect(verify(messageHex, signature, publicKey)).toBeTruthy() expect(verify(messageHex, signature, publicKey)).toBeTruthy()
}) })
it('sign - ed25519', () => { it('sign - ed25519', () => {
const privateKey = fixtures.ed25519.keypair.privateKey const privateKey = fixtures.ed25519.keypair.privateKey
const message = fixtures.ed25519.message const message = fixtures.ed25519.message
const messageHex = Buffer.from(message, 'utf8').toString('hex') const messageHex = stringToHex(message)
const signature = sign(messageHex, privateKey) const signature = sign(messageHex, privateKey)
expect(signature).toEqual(fixtures.ed25519.signature) expect(signature).toEqual(fixtures.ed25519.signature)
}) })
@@ -102,7 +103,7 @@ describe('api', () => {
const signature = fixtures.ed25519.signature const signature = fixtures.ed25519.signature
const publicKey = fixtures.ed25519.keypair.publicKey const publicKey = fixtures.ed25519.keypair.publicKey
const message = fixtures.ed25519.message const message = fixtures.ed25519.message
const messageHex = Buffer.from(message, 'utf8').toString('hex') const messageHex = stringToHex(message)
expect(verify(messageHex, signature, publicKey)).toBeTruthy() expect(verify(messageHex, signature, publicKey)).toBeTruthy()
}) })

View File

@@ -6,6 +6,10 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
### BREAKING CHANGES: ### BREAKING CHANGES:
- Moved all methods that were on `Utils` are now individually exported. - Moved all methods that were on `Utils` are now individually exported.
* `Buffer` has been replaced with `UInt8Array` for both params and return values. `Buffer` may continue to work with params since they extend `UInt8Arrays`.
### Changes
* Eliminates 4 runtime dependencies: `base-x`, `base64-js`, `buffer`, and `ieee754`.
## 1.0.0 Beta 1 (2023-10-19) ## 1.0.0 Beta 1 (2023-10-19)

View File

@@ -33,12 +33,12 @@ export class Account {
}, },
} }
constructor(secretNumbers?: string[] | string | Buffer) { constructor(secretNumbers?: string[] | string | Uint8Array) {
if (typeof secretNumbers === 'string') { if (typeof secretNumbers === 'string') {
this._secret = parseSecretString(secretNumbers) this._secret = parseSecretString(secretNumbers)
} else if (Array.isArray(secretNumbers)) { } else if (Array.isArray(secretNumbers)) {
this._secret = secretNumbers this._secret = secretNumbers
} else if (Buffer.isBuffer(secretNumbers)) { } else if (secretNumbers instanceof Uint8Array) {
this._secret = entropyToSecret(secretNumbers) this._secret = entropyToSecret(secretNumbers)
} else { } else {
this._secret = randomSecret() this._secret = randomSecret()

View File

@@ -1,7 +1,12 @@
import { randomBytes } from '@xrplf/isomorphic/utils' import {
bytesToHex,
concat,
hexToBytes,
randomBytes,
} from '@xrplf/isomorphic/utils'
function randomEntropy(): Buffer { function randomEntropy(): Uint8Array {
return Buffer.from(randomBytes(16)) return randomBytes(16)
} }
function calculateChecksum(position: number, value: number): number { function calculateChecksum(position: number, value: number): number {
@@ -32,11 +37,11 @@ function checkChecksum(
return (normalizedValue * (position * 2 + 1)) % 9 === normalizedChecksum return (normalizedValue * (position * 2 + 1)) % 9 === normalizedChecksum
} }
function entropyToSecret(entropy: Buffer): string[] { function entropyToSecret(entropy: Uint8Array): string[] {
const len = new Array(Math.ceil(entropy.length / 2)) const len = new Array(Math.ceil(entropy.length / 2))
const chunks = Array.from(len, (_a, chunk) => { const chunks = Array.from(len, (_a, chunk) => {
const buffChunk = entropy.slice(chunk * 2, (chunk + 1) * 2) const buffChunk = entropy.slice(chunk * 2, (chunk + 1) * 2)
const no = parseInt(buffChunk.toString('hex'), 16) const no = parseInt(bytesToHex(buffChunk), 16)
const fill = '0'.repeat(5 - String(no).length) const fill = '0'.repeat(5 - String(no).length)
return fill + String(no) + String(calculateChecksum(chunk, no)) return fill + String(no) + String(calculateChecksum(chunk, no))
}) })
@@ -50,8 +55,8 @@ function randomSecret(): string[] {
return entropyToSecret(randomEntropy()) return entropyToSecret(randomEntropy())
} }
function secretToEntropy(secret: string[]): Buffer { function secretToEntropy(secret: string[]): Uint8Array {
return Buffer.concat( return concat(
secret.map((chunk, i) => { secret.map((chunk, i) => {
const no = Number(chunk.slice(0, 5)) const no = Number(chunk.slice(0, 5))
const checksum = Number(chunk.slice(5)) const checksum = Number(chunk.slice(5))
@@ -62,7 +67,7 @@ function secretToEntropy(secret: string[]): Buffer {
throw new Error('Invalid secret part: checksum invalid') throw new Error('Invalid secret part: checksum invalid')
} }
const hex = `0000${no.toString(16)}`.slice(-4) const hex = `0000${no.toString(16)}`.slice(-4)
return Buffer.from(hex, 'hex') return hexToBytes(hex)
}), }),
) )
} }

View File

@@ -1,3 +1,4 @@
import { hexToBytes } from '@xrplf/isomorphic/utils'
import { deriveAddress, deriveKeypair, generateSeed } from 'ripple-keypairs' import { deriveAddress, deriveKeypair, generateSeed } from 'ripple-keypairs'
import { Account, secretToEntropy } from '../src' import { Account, secretToEntropy } from '../src'
@@ -17,7 +18,7 @@ describe('API: XRPL Secret Numbers', () => {
}) })
describe('Account based on entropy', () => { describe('Account based on entropy', () => {
const entropy = Buffer.from('0123456789ABCDEF0123456789ABCDEF', 'hex') const entropy = hexToBytes('0123456789ABCDEF0123456789ABCDEF')
const account = new Account(entropy) const account = new Account(entropy)
it('familySeed as expected', () => { it('familySeed as expected', () => {

View File

@@ -1,3 +1,5 @@
import { bytesToHex, hexToBytes } from '@xrplf/isomorphic/utils'
import { import {
calculateChecksum, calculateChecksum,
checkChecksum, checkChecksum,
@@ -12,10 +14,10 @@ describe('Utils', () => {
it('randomEntropy: valid output', () => { it('randomEntropy: valid output', () => {
const data = randomEntropy() const data = randomEntropy()
expect(typeof data).toEqual('object') expect(typeof data).toEqual('object')
expect(data instanceof Buffer).toBeTruthy() expect(data instanceof Uint8Array).toBeTruthy()
expect(data.length).toEqual(16) expect(data.length).toEqual(16)
expect(data.toString('hex').length).toEqual(32) expect(bytesToHex(data).length).toEqual(32)
expect(data.toString('hex')).toMatch(/^[a-f0-9]+$/u) expect(bytesToHex(data)).toMatch(/^[A-F0-9]+$/u)
}) })
it('calculateChecksum: 1st position', () => { it('calculateChecksum: 1st position', () => {
@@ -53,7 +55,7 @@ describe('Utils', () => {
}) })
it('entropyToSecret', () => { it('entropyToSecret', () => {
const entropy = Buffer.from('76ebb2d06879b45b7568fb9c1ded097c', 'hex') const entropy = hexToBytes('76ebb2d06879b45b7568fb9c1ded097c')
const secret = [ const secret = [
'304435', '304435',
'457766', '457766',
@@ -78,7 +80,7 @@ describe('Utils', () => {
'076618', '076618',
'024286', '024286',
] ]
const entropy = Buffer.from('76ebb2d06879b45b7568fb9c1ded097c', 'hex') const entropy = hexToBytes('76ebb2d06879b45b7568fb9c1ded097c')
expect(secretToEntropy(secret)).toEqual(entropy) expect(secretToEntropy(secret)).toEqual(entropy)
}) })

View File

@@ -13,6 +13,15 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
* `getSignedTx` * `getSignedTx`
* `isAccountDelete` * `isAccountDelete`
* `dropsToXRP` and `Client.getXrpBalance` now return a `number` instead of a `string` * `dropsToXRP` and `Client.getXrpBalance` now return a `number` instead of a `string`
* `Buffer` has been replaced with `UInt8Array` for both params and return values. `Buffer` may continue to work with params since they extend `UInt8Arrays`.
### Bundling Changes
* `Buffer` and `process` polyfills are no longer required.
### Changes
* Deprecated:
* `convertHexToString` in favor of `@xrplf/isomorphic/utils`'s `hexToString`
* `convertStringToHex` in favor of `@xrplf/isomorphic/utils`'s `stringToHex`
## 3.0.0 Beta 1 (2023-10-19) ## 3.0.0 Beta 1 (2023-10-19)

View File

@@ -10,6 +10,8 @@
*is part of the public domain. *is part of the public domain.
*/ */
import { hexToBytes, concat } from '@xrplf/isomorphic/utils'
import rfc1751Words from './rfc1751Words.json' import rfc1751Words from './rfc1751Words.json'
const rfc1751WordList: string[] = rfc1751Words const rfc1751WordList: string[] = rfc1751Words
@@ -59,7 +61,7 @@ function extract(key: string, start: number, length: number): number {
*/ */
function keyToRFC1751Mnemonic(hex_key: string): string { function keyToRFC1751Mnemonic(hex_key: string): string {
// Remove whitespace and interpret hex // Remove whitespace and interpret hex
const buf = Buffer.from(hex_key.replace(/\s+/gu, ''), 'hex') const buf = hexToBytes(hex_key.replace(/\s+/gu, ''))
// Swap byte order and use rfc1751 // Swap byte order and use rfc1751
let key: number[] = bufferToArray(swap128(buf)) let key: number[] = bufferToArray(swap128(buf))
@@ -97,7 +99,7 @@ function keyToRFC1751Mnemonic(hex_key: string): string {
* @throws Error if the parity after decoding does not match. * @throws Error if the parity after decoding does not match.
* @returns A Buffer containing an encoded secret. * @returns A Buffer containing an encoded secret.
*/ */
function rfc1751MnemonicToKey(english: string): Buffer { function rfc1751MnemonicToKey(english: string): Uint8Array {
const words = english.split(' ') const words = english.split(' ')
let key: number[] = [] let key: number[] = []
@@ -123,7 +125,7 @@ function rfc1751MnemonicToKey(english: string): Buffer {
} }
// This is a step specific to the XRPL's implementation // This is a step specific to the XRPL's implementation
const bufferKey = swap128(Buffer.from(key)) const bufferKey = swap128(Uint8Array.from(key))
return bufferKey return bufferKey
} }
@@ -165,26 +167,53 @@ function getSubKey(
return { subKey, word } return { subKey, word }
} }
function bufferToArray(buf: Buffer): number[] { function bufferToArray(buf: Uint8Array): number[] {
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- We know the end type */ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- We know the end type */
return Array.prototype.slice.call(buf) as number[] return Array.prototype.slice.call(buf) as number[]
} }
/** function swap(arr: Uint8Array, n: number, m: number): void {
* Swap the byte order of a 128-bit buffer. const i = arr[n]
* // eslint-disable-next-line no-param-reassign -- we have to swap
* @param buf - A 128-bit (16 byte) buffer arr[n] = arr[m]
* @returns A buffer containing the same data with reversed endianness // eslint-disable-next-line no-param-reassign -- see above
*/ arr[m] = i
function swap128(buf: Buffer): Buffer { }
// Interprets buffer as an array of (two, in this case) 64-bit numbers and swaps byte order in-place.
const reversedBytes = buf.swap64()
// Swap the two 64-bit numbers since our buffer is 128 bits. /**
return Buffer.concat( * Interprets arr as an array of 64-bit numbers and swaps byte order in 64 bit chunks.
[reversedBytes.slice(8, 16), reversedBytes.slice(0, 8)], * Example of two 64 bit numbers 0000000100000002 => 1000000020000000
16, *
) * @param arr A Uint8Array representation of one or more 64 bit numbers
* @returns Uint8Array An array containing the bytes of 64 bit numbers each with reversed endianness
*/
function swap64(arr: Uint8Array): Uint8Array {
const len = arr.length
for (let i = 0; i < len; i += 8) {
swap(arr, i, i + 7)
swap(arr, i + 1, i + 6)
swap(arr, i + 2, i + 5)
swap(arr, i + 3, i + 4)
}
return arr
}
/**
* Swap the byte order of a 128-bit array.
* Ex. 0000000100000002 => 2000000010000000
*
* @param arr - A 128-bit (16 byte) array
* @returns An array containing the same data with reversed endianness
*/
function swap128(arr: Uint8Array): Uint8Array {
// Interprets arr as an array of (two, in this case) 64-bit numbers and swaps byte order in 64 bit chunks.
// Ex. 0000000100000002 => 1000000020000000
const reversedBytes = swap64(arr)
// Further swap the two 64-bit numbers since our buffer is 128 bits.
// Ex. 1000000020000000 => 2000000010000000
return concat([reversedBytes.slice(8, 16), reversedBytes.slice(0, 8)])
} }
export { rfc1751MnemonicToKey, keyToRFC1751Mnemonic } export { rfc1751MnemonicToKey, keyToRFC1751Mnemonic }

View File

@@ -1,3 +1,4 @@
import { bytesToHex } from '@xrplf/isomorphic/utils'
import { BigNumber } from 'bignumber.js' import { BigNumber } from 'bignumber.js'
import { decodeAccountID } from 'ripple-address-codec' import { decodeAccountID } from 'ripple-address-codec'
import { decode, encode, encodeForSigning } from 'ripple-binary-codec' import { decode, encode, encodeForSigning } from 'ripple-binary-codec'
@@ -144,7 +145,7 @@ function compareSigners(left: Signer, right: Signer): number {
const NUM_BITS_IN_HEX = 16 const NUM_BITS_IN_HEX = 16
function addressToBigNumber(address: string): BigNumber { function addressToBigNumber(address: string): BigNumber {
const hex = Buffer.from(decodeAccountID(address)).toString('hex') const hex = bytesToHex(decodeAccountID(address))
return new BigNumber(hex, NUM_BITS_IN_HEX) return new BigNumber(hex, NUM_BITS_IN_HEX)
} }

View File

@@ -1,6 +1,7 @@
/* eslint-disable max-lines -- Connection is a large file w/ lots of imports/exports */ /* eslint-disable max-lines -- Connection is a large file w/ lots of imports/exports */
import type { Agent } from 'http' import type { Agent } from 'http'
import { bytesToHex, hexToString } from '@xrplf/isomorphic/utils'
import WebSocket, { ClientOptions } from '@xrplf/isomorphic/ws' import WebSocket, { ClientOptions } from '@xrplf/isomorphic/ws'
import { EventEmitter } from 'eventemitter3' import { EventEmitter } from 'eventemitter3'
@@ -68,10 +69,9 @@ function createWebSocket(
options.headers = config.headers options.headers = config.headers
} }
if (config.authorization != null) { if (config.authorization != null) {
const base64 = Buffer.from(config.authorization).toString('base64')
options.headers = { options.headers = {
...options.headers, ...options.headers,
Authorization: `Basic ${base64}`, Authorization: `Basic ${btoa(config.authorization)}`,
} }
} }
const websocketOptions = { ...options } const websocketOptions = { ...options }
@@ -382,7 +382,7 @@ export class Connection extends EventEmitter {
this.emit('error', 'websocket', error.message, error), this.emit('error', 'websocket', error.message, error),
) )
// Handle a closed connection: reconnect if it was unexpected // Handle a closed connection: reconnect if it was unexpected
this.ws.once('close', (code?: number, reason?: Buffer) => { this.ws.once('close', (code?: number, reason?: Uint8Array) => {
if (this.ws == null) { if (this.ws == null) {
throw new XrplError('onceClose: ws is null') throw new XrplError('onceClose: ws is null')
} }
@@ -390,7 +390,9 @@ export class Connection extends EventEmitter {
this.clearHeartbeatInterval() this.clearHeartbeatInterval()
this.requestManager.rejectAll( this.requestManager.rejectAll(
new DisconnectedError( new DisconnectedError(
`websocket was closed, ${new TextDecoder('utf-8').decode(reason)}`, `websocket was closed, ${
reason ? hexToString(bytesToHex(reason)) : ''
}`,
), ),
) )
this.ws.removeAllListeners() this.ws.removeAllListeners()

View File

@@ -3,6 +3,7 @@
/* eslint-disable no-bitwise -- this file mimics behavior in rippled. It uses /* eslint-disable no-bitwise -- this file mimics behavior in rippled. It uses
bitwise operators for and-ing numbers with a mask and bit shifting. */ bitwise operators for and-ing numbers with a mask and bit shifting. */
import { bytesToHex } from '@xrplf/isomorphic/utils'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { decode, encode } from 'ripple-binary-codec' import { decode, encode } from 'ripple-binary-codec'
@@ -29,10 +30,6 @@ function intToHex(integer: number, byteLength: number): string {
return foo return foo
} }
function bytesToHex(bytes: number[]): string {
return Buffer.from(bytes).toString('hex')
}
function bigintToHex( function bigintToHex(
integerString: string | number | BigNumber, integerString: string | number | BigNumber,
byteLength: number, byteLength: number,

View File

@@ -3,6 +3,7 @@
/* eslint-disable no-bitwise -- this file mimics behavior in rippled. It uses /* eslint-disable no-bitwise -- this file mimics behavior in rippled. It uses
bitwise operators for and-ing numbers with a mask and bit shifting. */ bitwise operators for and-ing numbers with a mask and bit shifting. */
import { bytesToHex } from '@xrplf/isomorphic/utils'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { decodeAccountID } from 'ripple-address-codec' import { decodeAccountID } from 'ripple-address-codec'
@@ -20,7 +21,7 @@ const HEX = 16
const BYTE_LENGTH = 4 const BYTE_LENGTH = 4
function addressToHex(address: string): string { function addressToHex(address: string): string {
return Buffer.from(decodeAccountID(address)).toString('hex') return bytesToHex(decodeAccountID(address))
} }
function ledgerSpaceHex(name: keyof typeof ledgerSpaces): string { function ledgerSpaceHex(name: keyof typeof ledgerSpaces): string {
@@ -37,7 +38,7 @@ function currencyToHex(currency: string): string {
bytes[12] = currency.charCodeAt(0) & MASK bytes[12] = currency.charCodeAt(0) & MASK
bytes[13] = currency.charCodeAt(1) & MASK bytes[13] = currency.charCodeAt(1) & MASK
bytes[14] = currency.charCodeAt(2) & MASK bytes[14] = currency.charCodeAt(2) & MASK
return Buffer.from(bytes).toString('hex') return bytesToHex(Uint8Array.from(bytes))
} }
/** /**

View File

@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-magic-numbers -- Doing hex string parsing. */ /* eslint-disable @typescript-eslint/no-magic-numbers -- Doing hex string parsing. */
import { hexToBytes } from '@xrplf/isomorphic/utils'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { encodeAccountID } from 'ripple-address-codec' import { encodeAccountID } from 'ripple-address-codec'
@@ -85,7 +86,7 @@ export default function parseNFTokenID(nftokenID: string): {
NFTokenID: nftokenID, NFTokenID: nftokenID,
Flags: new BigNumber(nftokenID.substring(0, 4), 16).toNumber(), Flags: new BigNumber(nftokenID.substring(0, 4), 16).toNumber(),
TransferFee: new BigNumber(nftokenID.substring(4, 8), 16).toNumber(), TransferFee: new BigNumber(nftokenID.substring(4, 8), 16).toNumber(),
Issuer: encodeAccountID(Buffer.from(nftokenID.substring(8, 48), 'hex')), Issuer: encodeAccountID(hexToBytes(nftokenID.substring(8, 48))),
Taxon: unscrambleTaxon(scrambledTaxon, sequence), Taxon: unscrambleTaxon(scrambledTaxon, sequence),
Sequence: sequence, Sequence: sequence,
} }

View File

@@ -1,12 +1,17 @@
import { stringToHex, hexToString } from '@xrplf/isomorphic/utils'
/** /**
* Converts a string to its hex equivalent. Useful for Memos. * Converts a string to its hex equivalent. Useful for Memos.
* *
* @param string - The string to convert to Hex. * @param string - The string to convert to Hex.
* @returns The Hex equivalent of the string. * @returns The Hex equivalent of the string.
*
* @deprecated use `@xrplf/isomorphic/utils`'s `stringToHex`
*
* @category Utilities * @category Utilities
*/ */
function convertStringToHex(string: string): string { function convertStringToHex(string: string): string {
return Buffer.from(string, 'utf8').toString('hex').toUpperCase() return stringToHex(string)
} }
/** /**
@@ -15,13 +20,13 @@ function convertStringToHex(string: string): string {
* @param hex - The hex to convert to a string. * @param hex - The hex to convert to a string.
* @param encoding - The encoding to use. Defaults to 'utf8' (UTF-8). 'ascii' is also allowed. * @param encoding - The encoding to use. Defaults to 'utf8' (UTF-8). 'ascii' is also allowed.
* @returns The converted string. * @returns The converted string.
*
* @deprecated use `@xrplf/isomorphic/utils`'s `hexToString`
*
* @category Utilities * @category Utilities
*/ */
function convertHexToString( function convertHexToString(hex: string, encoding = 'utf8'): string {
hex: string, return hexToString(hex, encoding)
encoding: BufferEncoding = 'utf8',
): string {
return Buffer.from(hex, 'hex').toString(encoding)
} }
export { convertHexToString, convertStringToHex } export { convertHexToString, convertStringToHex }

View File

@@ -1,4 +1,3 @@
/* eslint-disable max-len -- Some large lines necessary */
/* eslint-disable max-statements -- test has a lot of statements */ /* eslint-disable max-statements -- test has a lot of statements */
import net from 'net' import net from 'net'
@@ -25,22 +24,6 @@ import {
} from './setupClient' } from './setupClient'
import { assertRejects, ignoreWebSocketDisconnect } from './testUtils' import { assertRejects, ignoreWebSocketDisconnect } from './testUtils'
type GlobalThis = typeof globalThis
type Global = GlobalThis & {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Necessary for Jest in browser
TextEncoder: any
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Necessary for Jest in browser
TextDecoder: any
}
declare const global: Global
if (typeof TextDecoder === 'undefined') {
// eslint-disable-next-line node/global-require, @typescript-eslint/no-require-imports, node/prefer-global/text-encoder, global-require, @typescript-eslint/no-var-requires -- Needed for Jest
global.TextEncoder = require('util').TextEncoder
// eslint-disable-next-line node/global-require, @typescript-eslint/no-require-imports, node/prefer-global/text-decoder, global-require, @typescript-eslint/no-var-requires -- Needed for Jest
global.TextDecoder = require('util').TextDecoder
}
// how long before each test case times out // how long before each test case times out
const TIMEOUT = 20000 const TIMEOUT = 20000

View File

@@ -22,6 +22,7 @@ function webpackForTest(testFileName, basePath) {
new webpack.DefinePlugin({ new webpack.DefinePlugin({
"process.stdout": {}, "process.stdout": {},
}), }),
new webpack.ProvidePlugin({ process: "process/browser" }),
], ],
module: { module: {
rules: [ rules: [

View File

@@ -13,10 +13,6 @@ function getDefaultConfiguration() {
}, },
stats: "errors-only", stats: "errors-only",
devtool: "source-map", devtool: "source-map",
plugins: [
new webpack.ProvidePlugin({ process: "process/browser" }),
new webpack.ProvidePlugin({ Buffer: ["buffer", "Buffer"] }),
],
module: { module: {
rules: [ rules: [
{ {
@@ -31,9 +27,6 @@ function getDefaultConfiguration() {
// ripple-address-codec, ripple-binary-codec, ripple-keypairs, which are // ripple-address-codec, ripple-binary-codec, ripple-keypairs, which are
// symlinked together via lerna // symlinked together via lerna
symlinks: false, symlinks: false,
fallback: {
buffer: require.resolve("buffer"),
},
}, },
}; };
} }
@@ -64,7 +57,7 @@ module.exports = {
new BundleAnalyzerPlugin({ new BundleAnalyzerPlugin({
analyzerPort: `auto`, analyzerPort: `auto`,
analyzerMode: "static", analyzerMode: "static",
}) }),
); );
} }
return localConfig; return localConfig;