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
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
@@ -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`
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`.
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",
```
Starting in 3.0 xrpl and its related packages no longer require custom configurations (ex. polyfills) to run.
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
@@ -97,54 +56,7 @@ import './polyfills'
### 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.
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
```
Starting in 3.0 xrpl and all the packages in this repo no longer require custom configurations (ex. polyfills) to run.
### Using xrpl.js with Deno

111
package-lock.json generated
View File

@@ -26,7 +26,6 @@
"@typescript-eslint/parser": "^5.28.0",
"@xrplf/eslint-config": "^1.9.1",
"@xrplf/prettier-config": "^1.9.1",
"buffer": "^6.0.2",
"chai": "^4.3.4",
"copyfiles": "^2.4.1",
"eslint": "^8.18.0",
@@ -4223,33 +4222,6 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
@@ -4420,29 +4392,6 @@
"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": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -7836,25 +7785,6 @@
"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": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@@ -12857,6 +12787,7 @@
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true,
"funding": [
{
"type": "github",
@@ -15365,8 +15296,8 @@
"version": "5.0.0-beta.0",
"license": "ISC",
"dependencies": {
"@xrplf/isomorphic": "^1.0.0-beta.0",
"base-x": "^3.0.9"
"@scure/base": "^1.1.3",
"@xrplf/isomorphic": "^1.0.0-beta.0"
},
"engines": {
"node": ">= 16"
@@ -15378,7 +15309,6 @@
"dependencies": {
"@xrplf/isomorphic": "^1.0.0-beta.0",
"bignumber.js": "^9.0.0",
"buffer": "6.0.3",
"ripple-address-codec": "^5.0.0-beta.0"
},
"engines": {
@@ -18870,19 +18800,6 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
@@ -19010,15 +18927,6 @@
"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": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -21619,11 +21527,6 @@
"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": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@@ -25453,8 +25356,8 @@
"ripple-address-codec": {
"version": "file:packages/ripple-address-codec",
"requires": {
"@xrplf/isomorphic": "^1.0.0-beta.0",
"base-x": "^3.0.9"
"@scure/base": "^1.1.3",
"@xrplf/isomorphic": "^1.0.0-beta.0"
}
},
"ripple-binary-codec": {
@@ -25462,7 +25365,6 @@
"requires": {
"@xrplf/isomorphic": "^1.0.0-beta.0",
"bignumber.js": "^9.0.0",
"buffer": "6.0.3",
"ripple-address-codec": "^5.0.0-beta.0"
}
},
@@ -25519,7 +25421,8 @@
"safe-buffer": {
"version": "5.2.1",
"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": {
"version": "1.0.0",

View File

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

View File

@@ -1,5 +1,11 @@
# @xrplf/isomorphic Release History
## Unreleased
## Added
- hexToString
- stringToHex
## 1.0.0 Beta 1 (2023-10-19)
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]
```
#### 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`
```typescript

View File

@@ -1,11 +1,16 @@
import {
bytesToHex as nobleBytesToHex,
hexToBytes as nobleHexToBytes,
randomBytes as nobleRandomBytes,
} 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) => {
const hex = nobleBytesToHex(
bytes instanceof Uint8Array ? bytes : Uint8Array.from(bytes),
@@ -13,5 +18,33 @@ export const bytesToHex: typeof BytesToHexFn = (bytes) => {
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 * from './shared'

View File

@@ -1,5 +1,6 @@
import { randomBytes as cryptoRandomBytes } from 'crypto'
import type { BytesToHexFn, HexToBytesFn, RandomBytesFn } from './types'
import { HexToStringFn, StringToHexFn } from './types'
const OriginalBuffer = Symbol('OriginalBuffer')
@@ -69,4 +70,17 @@ export const hexToBytes: typeof HexToBytesFn = (hex) => {
export const randomBytes: typeof RandomBytesFn = (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 */
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
*/
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 readyState: number
public constructor(url: string)
public close(code?: number, reason?: Buffer): void
public close(code?: number, reason?: Uint8Array): 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 () {
it('randomBytes', () => {
@@ -28,4 +34,16 @@ describe('utils', function () {
it('bytesToHex - DEADBEEF (Uint8Array)', () => {
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
### 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
### 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`.
### 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.
@@ -79,38 +79,38 @@ Return object type:
```
{
version: number[],
bytes: Buffer,
bytes: UInt8Array,
type: string | null
}
```
### encodeAccountID(bytes: Buffer): string
### encodeAccountID(bytes: UInt8Array): string
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.
### encodeNodePublic(bytes: Buffer): string
### encodeNodePublic(bytes: UInt8Array): string
Encode bytes to the XRP Ledger "node public key" format (base58).
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.
### encodeAccountPublic(bytes: Buffer): string
### encodeAccountPublic(bytes: UInt8Array): string
Encode a public key, as for payment channels.
### decodeAccountPublic(base58string: string): Buffer
### decodeAccountPublic(base58string: string): UInt8Array
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.
@@ -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.
### 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.

View File

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

View File

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

View File

@@ -1,6 +1,4 @@
// Buffer is technically not needed, as a Buffer IS a Uint8Array.
// However, for communication purposes it's listed here
export type ByteArray = number[] | Uint8Array | Buffer
export type ByteArray = number[] | Uint8Array
/**
* 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
* 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]
*
* @param args - Concatenate of these args into a single array.

View File

@@ -2,30 +2,24 @@
* Codec class
*/
import { base58xrp, BytesCoder } from '@scure/base'
import { sha256 } from '@xrplf/isomorphic/sha256'
import baseCodec = require('base-x')
import type { BaseConverter } from 'base-x'
import { arrayEqual, concatArgs, ByteArray } from './utils'
class Codec {
private readonly _sha256: (bytes: ByteArray) => Uint8Array
private readonly _alphabet: string
private readonly _codec: BaseConverter
private readonly _codec: BytesCoder
public constructor(options: {
sha256: (bytes: ByteArray) => Uint8Array
alphabet: string
}) {
public constructor(options: { sha256: (bytes: ByteArray) => Uint8Array }) {
this._sha256 = options.sha256
this._alphabet = options.alphabet
this._codec = baseCodec(this._alphabet)
this._codec = base58xrp
}
/**
* 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.
*/
public encode(
@@ -56,7 +50,7 @@ class Codec {
},
): {
version: number[]
bytes: Buffer
bytes: Uint8Array
type: 'ed25519' | 'secp256k1' | null
} {
const versions = opts.versions
@@ -97,20 +91,20 @@ class Codec {
)
}
public encodeChecked(buffer: ByteArray): string {
const check = this._sha256(this._sha256(buffer)).slice(0, 4)
return this._encodeRaw(Buffer.from(concatArgs(buffer, check)))
public encodeChecked(bytes: ByteArray): string {
const check = this._sha256(this._sha256(bytes)).slice(0, 4)
return this._encodeRaw(Uint8Array.from(concatArgs(bytes, check)))
}
public decodeChecked(base58string: string): Buffer {
const buffer = this._decodeRaw(base58string)
if (buffer.length < 5) {
public decodeChecked(base58string: string): Uint8Array {
const intArray = this._decodeRaw(base58string)
if (intArray.byteLength < 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')
}
return buffer.slice(0, -4)
return intArray.slice(0, -4)
}
private _encodeVersioned(
@@ -118,21 +112,21 @@ class Codec {
versions: number[],
expectedLength: number,
): string {
if (expectedLength && bytes.length !== expectedLength) {
if (!checkByteLength(bytes, expectedLength)) {
throw new Error(
'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))
}
private _encodeRaw(bytes: ByteArray): string {
return this._codec.encode(bytes)
return this._codec.encode(Uint8Array.from(bytes))
}
/* eslint-enable max-lines-per-function */
private _decodeRaw(base58string: string): Buffer {
private _decodeRaw(base58string: string): Uint8Array {
return this._codec.decode(base58string)
}
@@ -162,20 +156,19 @@ const ED25519_SEED = [0x01, 0xe1, 0x4b]
const codecOptions = {
sha256,
alphabet: 'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz',
}
const codecWithXrpAlphabet = new Codec(codecOptions)
export const codec = codecWithXrpAlphabet
// entropy is a Buffer of size 16
// entropy is a Uint8Array of size 16
// type is 'ed25519' or 'secp256k1'
export function encodeSeed(
entropy: ByteArray,
type: 'ed25519' | 'secp256k1',
): string {
if (entropy.length !== 16) {
if (!checkByteLength(entropy, 16)) {
throw new Error('entropy must have length 16')
}
const opts = {
@@ -202,7 +195,7 @@ export function decodeSeed(
},
): {
version: number[]
bytes: Buffer
bytes: Uint8Array
type: 'ed25519' | 'secp256k1' | null
} {
return codecWithXrpAlphabet.decode(seed, opts)
@@ -219,7 +212,7 @@ export function encodeAccountID(bytes: ByteArray): string {
export const encodeAddress = encodeAccountID
/* 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 }
return codecWithXrpAlphabet.decode(accountId, opts).bytes
}
@@ -230,7 +223,7 @@ export function decodeAccountID(accountId: string): Buffer {
export const decodeAddress = decodeAccountID
/* 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 }
return codecWithXrpAlphabet.decode(base58string, opts).bytes
}
@@ -245,7 +238,7 @@ export function encodeAccountPublic(bytes: ByteArray): string {
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 }
return codecWithXrpAlphabet.decode(base58string, opts).bytes
}
@@ -258,3 +251,9 @@ export function isValidClassicAddress(address: string): boolean {
}
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 {
classicAddressToXAddress,
xAddressToClassicAddress,
@@ -199,10 +201,10 @@ const testCases: AddressTestCase[] = [
{
const highAndLowAccounts = [
Buffer.from('00'.repeat(20), 'hex'),
Buffer.from(`${'00'.repeat(19)}01`, 'hex'),
Buffer.from('01'.repeat(20), 'hex'),
Buffer.from('FF'.repeat(20), 'hex'),
hexToBytes('00'.repeat(20)),
hexToBytes(`${'00'.repeat(19)}01`),
hexToBytes('01'.repeat(20)),
hexToBytes('FF'.repeat(20)),
]
highAndLowAccounts.forEach((accountId) => {
@@ -215,7 +217,7 @@ const testCases: AddressTestCase[] = [
tagTestCases.forEach((testCase) => {
const tag = testCase || false
const xAddress = encodeXAddress(accountId, tag, isTestAddress)
it(`Encoding ${accountId.toString('hex')}${
it(`Encoding ${bytesToHex(accountId)}${
tag ? `:${tag}` : ''
} to ${xAddress} has expected length`, () => {
expect(xAddress.length).toBe(47)
@@ -256,8 +258,8 @@ it(`Invalid X-address (64-bit tag) throws`, () => {
it(`Invalid Account ID throws`, () => {
expect(() => {
encodeXAddress(Buffer.from('00'.repeat(19), 'hex'), false, false)
}).toThrow(new Error('Account ID must be 20 bytes'))
encodeXAddress(hexToBytes('00'.repeat(19)), false, false)
}).toThrowError('Account ID must be 20 bytes')
})
it(`isValidXAddress returns false for invalid X-address`, () => {

View File

@@ -1,44 +1,48 @@
import { arrayEqual, concatArgs } from '../src/utils'
it('two sequences are equal', () => {
expect(arrayEqual([1, 2, 3], [1, 2, 3])).toBe(true)
describe('Function: arrayEqual', () => {
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', () => {
expect(arrayEqual([3, 2, 1], [1, 2, 3])).toBe(false)
})
describe('Function: concatArgs', () => {
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', () => {
expect(arrayEqual(Buffer.from([1, 2, 3]), [1, 2, 3])).toBe(true)
expect(arrayEqual(Buffer.from([1, 2, 3]), new Uint8Array([1, 2, 3]))).toBe(
true,
)
})
it('a variety of values are concatenated', () => {
expect(
concatArgs(1, [2, 3], Uint8Array.from([4, 5]), new Uint8Array([6, 7])),
).toEqual([1, 2, 3, 4, 5, 6, 7])
})
it('sequences with a single element', () => {
expect(arrayEqual(Buffer.from([1]), [1])).toBe(true)
expect(arrayEqual(Buffer.from([1]), new Uint8Array([1]))).toBe(true)
})
it('a single value is returned as an array', () => {
expect(concatArgs(Uint8Array.from([7]))).toEqual([7])
})
it('empty sequences', () => {
expect(arrayEqual(Buffer.from([]), [])).toBe(true)
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([])
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 {
codec,
decodeAccountID,
@@ -11,12 +13,8 @@ import {
isValidClassicAddress,
} from '../src'
function toHex(bytes: Buffer): string {
return Buffer.from(bytes).toString('hex').toUpperCase()
}
function toBytes(hex: string): Buffer {
return Buffer.from(hex, 'hex')
function stringToBytes(str: string): Uint8Array {
return hexToBytes(stringToHex(str))
}
/**
@@ -29,18 +27,18 @@ function toBytes(hex: string): Buffer {
*/
// eslint-disable-next-line max-params -- needs them
function makeEncodeDecodeTest(
encoder: (val: Buffer) => string,
decoder: (val: string) => Buffer,
encoder: (val: Uint8Array) => string,
decoder: (val: string) => Uint8Array,
base58: string,
hex: string,
): void {
it(`can translate between ${hex} and ${base58}`, function () {
const actual = encoder(toBytes(hex))
const actual = encoder(hexToBytes(hex))
expect(actual).toBe(base58)
})
it(`can translate between ${base58} and ${hex})`, function () {
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 () {
const decoded = decodeSeed('sEdTM1uX8pu2do5XvTnutH6HsouMaM2')
expect(toHex(decoded.bytes)).toBe('4C3A1D213FBDFB14C7C28D609469B341')
expect(bytesToHex(decoded.bytes)).toBe('4C3A1D213FBDFB14C7C28D609469B341')
expect(decoded.type).toBe('ed25519')
const decoded2 = decodeSeed('sn259rEFXrQrWyx3Q7XneWcwV6dfL')
expect(toHex(decoded2.bytes)).toBe('CF2DE378FBDD7E2EE87D486DFB5A7BFF')
expect(bytesToHex(decoded2.bytes)).toBe('CF2DE378FBDD7E2EE87D486DFB5A7BFF')
expect(decoded2.type).toBe('secp256k1')
})
@@ -79,7 +77,7 @@ it('can pass a type as second arg to encodeSeed', function () {
const edSeed = 'sEdTM1uX8pu2do5XvTnutH6HsouMaM2'
const decoded = decodeSeed(edSeed)
const type = 'ed25519'
expect(toHex(decoded.bytes)).toBe('4C3A1D213FBDFB14C7C28D609469B341')
expect(bytesToHex(decoded.bytes)).toBe('4C3A1D213FBDFB14C7C28D609469B341')
expect(decoded.type).toBe(type)
expect(encodeSeed(decoded.bytes, type)).toBe(edSeed)
})
@@ -105,7 +103,7 @@ it('isValidClassicAddress - empty', function () {
describe('encodeSeed', function () {
it('encodes a secp256k1 seed', function () {
const result = encodeSeed(
Buffer.from('CF2DE378FBDD7E2EE87D486DFB5A7BFF', 'hex'),
hexToBytes('CF2DE378FBDD7E2EE87D486DFB5A7BFF'),
'secp256k1',
)
expect(result).toBe('sn259rEFXrQrWyx3Q7XneWcwV6dfL')
@@ -113,7 +111,7 @@ describe('encodeSeed', function () {
it('encodes low secp256k1 seed', function () {
const result = encodeSeed(
Buffer.from('00000000000000000000000000000000', 'hex'),
hexToBytes('00000000000000000000000000000000'),
'secp256k1',
)
expect(result).toBe('sp6JS7f14BuwFY8Mw6bTtLKWauoUs')
@@ -121,7 +119,7 @@ describe('encodeSeed', function () {
it('encodes high secp256k1 seed', function () {
const result = encodeSeed(
Buffer.from('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 'hex'),
hexToBytes('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'),
'secp256k1',
)
expect(result).toBe('saGwBRReqUNKuWNLpUAq8i8NkXEPN')
@@ -129,7 +127,7 @@ describe('encodeSeed', function () {
it('encodes an ed25519 seed', function () {
const result = encodeSeed(
Buffer.from('4C3A1D213FBDFB14C7C28D609469B341', 'hex'),
hexToBytes('4C3A1D213FBDFB14C7C28D609469B341'),
'ed25519',
)
expect(result).toBe('sEdTM1uX8pu2do5XvTnutH6HsouMaM2')
@@ -137,7 +135,7 @@ describe('encodeSeed', function () {
it('encodes low ed25519 seed', function () {
const result = encodeSeed(
Buffer.from('00000000000000000000000000000000', 'hex'),
hexToBytes('00000000000000000000000000000000'),
'ed25519',
)
expect(result).toBe('sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE')
@@ -145,7 +143,7 @@ describe('encodeSeed', function () {
it('encodes high ed25519 seed', function () {
const result = encodeSeed(
Buffer.from('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 'hex'),
hexToBytes('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'),
'ed25519',
)
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 () {
expect(() => {
encodeSeed(
Buffer.from('CF2DE378FBDD7E2EE87D486DFB5A7B', 'hex'),
'secp256k1',
)
encodeSeed(hexToBytes('CF2DE378FBDD7E2EE87D486DFB5A7B'), 'secp256k1')
}).toThrow(new Error('entropy must have length 16'))
})
it('attempting to encode a seed with more than 16 bytes of entropy throws', function () {
expect(() => {
encodeSeed(
Buffer.from('CF2DE378FBDD7E2EE87D486DFB5A7BFFFF', 'hex'),
'secp256k1',
)
encodeSeed(hexToBytes('CF2DE378FBDD7E2EE87D486DFB5A7BFFFF'), 'secp256k1')
}).toThrow(new Error('entropy must have length 16'))
})
})
@@ -173,13 +165,13 @@ describe('encodeSeed', function () {
describe('decodeSeed', function () {
it('can decode an Ed25519 seed', function () {
const decoded = decodeSeed('sEdTM1uX8pu2do5XvTnutH6HsouMaM2')
expect(toHex(decoded.bytes)).toBe('4C3A1D213FBDFB14C7C28D609469B341')
expect(bytesToHex(decoded.bytes)).toBe('4C3A1D213FBDFB14C7C28D609469B341')
expect(decoded.type).toBe('ed25519')
})
it('can decode a secp256k1 seed', function () {
const decoded = decodeSeed('sn259rEFXrQrWyx3Q7XneWcwV6dfL')
expect(toHex(decoded.bytes)).toBe('CF2DE378FBDD7E2EE87D486DFB5A7BFF')
expect(bytesToHex(decoded.bytes)).toBe('CF2DE378FBDD7E2EE87D486DFB5A7BFF')
expect(decoded.type).toBe('secp256k1')
})
})
@@ -187,17 +179,17 @@ describe('decodeSeed', function () {
describe('encodeAccountID', function () {
it('can encode an AccountID', function () {
const encoded = encodeAccountID(
Buffer.from('BA8E78626EE42C41B46D46C3048DF3A1C3C87072', 'hex'),
hexToBytes('BA8E78626EE42C41B46D46C3048DF3A1C3C87072'),
)
expect(encoded).toBe('rJrRMgiRgrU6hDF4pgu5DXQdWyPbY35ErN')
})
it('unexpected length should throw', function () {
expect(() => {
encodeAccountID(Buffer.from('ABCDEF', 'hex'))
encodeAccountID(hexToBytes('ABCDEF'))
}).toThrow(
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(
'n9MXXueo837zYH36DvMc13BwHcqtfAWNJY5czWVbp7uYTj7x17TH',
)
expect(toHex(decoded)).toBe(
expect(bytesToHex(decoded)).toBe(
'0388E5BA87A000CB807240DF8C848EB0B5FFA5C8E5A521BC8E105C0F0A44217828',
)
})
@@ -216,7 +208,7 @@ describe('decodeNodePublic', function () {
it('encodes 123456789 with version byte of 0', () => {
expect(
codec.encode(Buffer.from('123456789'), {
codec.encode(stringToBytes('123456789'), {
versions: [0],
expectedLength: 9,
}),
@@ -282,7 +274,7 @@ it('decode data', () => {
}),
).toEqual({
version: [0],
bytes: Buffer.from('123456789'),
bytes: stringToBytes('123456789'),
type: null,
})
})
@@ -295,7 +287,7 @@ it('decode data with expected length', function () {
}),
).toEqual({
version: [0],
bytes: Buffer.from('123456789'),
bytes: stringToBytes('123456789'),
type: null,
})
})

View File

@@ -2,6 +2,12 @@
## 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)
### Breaking Changes

View File

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

View File

@@ -1,5 +1,6 @@
/* eslint-disable func-style */
import { bytesToHex } from '@xrplf/isomorphic/utils'
import { coreTypes } from './types'
import { BinaryParser } from './serdes/binary-parser'
import { AccountID } from './types/account-id'
@@ -17,17 +18,17 @@ import { JsonObject } from './types/serialized-type'
/**
* 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.
* Can be customized for sidechains and amendments.
* @returns BinaryParser
*/
const makeParser = (
bytes: string | Buffer,
bytes: string | Uint8Array,
definitions?: XrplDefinitionsBase,
): BinaryParser =>
new BinaryParser(
bytes instanceof Buffer ? bytes.toString('hex') : bytes,
bytes instanceof Uint8Array ? bytesToHex(bytes) : bytes,
definitions,
)
@@ -64,8 +65,8 @@ const binaryToJSON = (
* @field set signingFieldOnly to true if you want to serialize only signing fields
*/
interface OptionObject {
prefix?: Buffer
suffix?: Buffer
prefix?: Uint8Array
suffix?: Uint8Array
signingFieldsOnly?: boolean
definitions?: XrplDefinitionsBase
}
@@ -75,9 +76,12 @@ interface OptionObject {
*
* @param object JSON object to serialize
* @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 bytesList = new BytesList()
@@ -105,13 +109,13 @@ function serializeObject(object: JsonObject, opts: OptionObject = {}): Buffer {
* @param transaction Transaction to serialize
* @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.
* @returns A Buffer with the serialized object
* @returns A Uint8Array with the serialized object
*/
function signingData(
transaction: JsonObject,
prefix: Buffer = HashPrefix.transactionSig,
prefix: Uint8Array = HashPrefix.transactionSig,
opts: { definitions?: XrplDefinitionsBase } = {},
): Buffer {
): Uint8Array {
return serializeObject(transaction, {
prefix,
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.
* @returns the serialized object with appropriate prefix
*/
function signingClaimData(claim: ClaimObject): Buffer {
function signingClaimData(claim: ClaimObject): Uint8Array {
const num = BigInt(String(claim.amount))
const prefix = HashPrefix.paymentChannelClaim
const channel = coreTypes.Hash256.from(claim.channel).toBytes()
@@ -162,7 +166,7 @@ function multiSigningData(
opts: { definitions: XrplDefinitionsBase } = {
definitions: DEFAULT_DEFINITIONS,
},
): Buffer {
): Uint8Array {
const prefix = HashPrefix.transactionMultiSig
const suffix = coreTypes.AccountID.from(signingAccount).toBytes()
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
*/
export class Bytes {
readonly bytes: Buffer
readonly bytes: Uint8Array
constructor(
readonly name: string,
readonly ordinal: number,
readonly ordinalWidth: number,
) {
this.bytes = Buffer.alloc(ordinalWidth)
this.bytes = new Uint8Array(ordinalWidth)
for (let i = 0; i < ordinalWidth; i++) {
this.bytes[ordinalWidth - i - 1] = (ordinal >>> (i * 8)) & 0xff
}

View File

@@ -22,14 +22,14 @@ export interface FieldInstance {
readonly type: Bytes
readonly ordinal: number
readonly name: string
readonly header: Buffer
readonly header: Uint8Array
readonly associatedType: typeof SerializedType
}
/*
* @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> = []
if (type < 16) {
if (nth < 16) {
@@ -42,7 +42,7 @@ function fieldHeader(type: number, nth: number): Buffer {
} else {
header.push(0, type, nth)
}
return Buffer.from(header)
return Uint8Array.from(header)
}
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
* @returns a buffer with the bytes representation of uint32
* @param uint32 32 bit integer to write to Uint8Array
* @returns a Uint8Array with the bytes representation of uint32
*/
function bytes(uint32: number): Buffer {
const result = Buffer.alloc(4)
result.writeUInt32BE(uint32, 0)
function bytes(uint32: number): Uint8Array {
const result = new Uint8Array(4)
writeUInt32BE(result, uint32, 0)
return result
}
/**
* Maps HashPrefix names to their byte representation
*/
const HashPrefix: Record<string, Buffer> = {
const HashPrefix: Record<string, Uint8Array> = {
transactionID: bytes(0x54584e00),
// transaction plus metadata
transaction: bytes(0x534e4400),

View File

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

View File

@@ -9,6 +9,7 @@ import {
} from './enums'
import { XrplDefinitions } from './enums/xrpl-definitions'
import { coreTypes } from './types'
import { bytesToHex } from '@xrplf/isomorphic/utils'
const {
signingData,
@@ -44,9 +45,7 @@ function encode(json: object, definitions?: XrplDefinitionsBase): string {
if (typeof json !== 'object') {
throw new Error()
}
return serializeObject(json as JsonObject, { definitions })
.toString('hex')
.toUpperCase()
return bytesToHex(serializeObject(json as JsonObject, { definitions }))
}
/**
@@ -64,11 +63,11 @@ function encodeForSigning(
if (typeof json !== 'object') {
throw new Error()
}
return signingData(json as JsonObject, HashPrefix.transactionSig, {
definitions,
})
.toString('hex')
.toUpperCase()
return bytesToHex(
signingData(json as JsonObject, HashPrefix.transactionSig, {
definitions,
}),
)
}
/**
@@ -83,9 +82,7 @@ function encodeForSigningClaim(json: object): string {
if (typeof json !== 'object') {
throw new Error()
}
return signingClaimData(json as ClaimObject)
.toString('hex')
.toUpperCase()
return bytesToHex(signingClaimData(json as ClaimObject))
}
/**
@@ -108,9 +105,9 @@ function encodeForMultisigning(
throw new Error()
}
const definitionsOpt = definitions ? { definitions } : undefined
return multiSigningData(json as JsonObject, signer, definitionsOpt)
.toString('hex')
.toUpperCase()
return bytesToHex(
multiSigningData(json as JsonObject, signer, definitionsOpt),
)
}
/**
@@ -123,7 +120,7 @@ function encodeQuality(value: string): string {
if (typeof value !== 'string') {
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 BigNumber from 'bignumber.js'
import { bytesToHex, hexToBytes } from '@xrplf/isomorphic/utils'
/**
* class for encoding and decoding quality
@@ -12,7 +12,7 @@ class quality {
* @param arg string representation of an amount
* @returns Serialized quality
*/
static encode(quality: string): Buffer {
static encode(quality: string): Uint8Array {
const decimal = BigNumber(quality)
const exponent = (decimal?.e || 0) - 15
const qualityString = decimal.times(`1e${-exponent}`).abs().toString()
@@ -28,9 +28,9 @@ class quality {
* @returns deserialized quality
*/
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 mantissa = new BigNumber(`0x${bytes.slice(1).toString('hex')}`)
const mantissa = new BigNumber(`0x${bytesToHex(bytes.slice(1))}`)
return mantissa.times(`1e${exponent}`)
}
}

View File

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

View File

@@ -1,11 +1,12 @@
import { FieldInstance } from '../enums'
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 {
private bytesArray: Array<Buffer> = []
private bytesArray: Array<Uint8Array> = []
/**
* Get the total number of bytes in the BytesList
@@ -13,17 +14,17 @@ class BytesList {
* @return the number of bytes
*/
public getLength(): number {
return Buffer.concat(this.bytesArray).byteLength
return concat(this.bytesArray).byteLength
}
/**
* Put bytes in the BytesList
*
* @param bytesArg A Buffer
* @param bytesArg A Uint8Array
* @return this BytesList
*/
public put(bytesArg: Buffer): BytesList {
const bytes = Buffer.from(bytesArg) // Temporary, to catch instances of Uint8Array being passed in
public put(bytesArg: Uint8Array): BytesList {
const bytes = Uint8Array.from(bytesArg) // Temporary, to catch instances of Uint8Array being passed in
this.bytesArray.push(bytes)
return this
}
@@ -37,17 +38,17 @@ class BytesList {
list.put(this.toBytes())
}
public toBytes(): Buffer {
return Buffer.concat(this.bytesArray)
public toBytes(): Uint8Array {
return concat(this.bytesArray)
}
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 {
private sink: BytesList = new BytesList()
@@ -70,7 +71,7 @@ class BinarySerializer {
*
* @param bytes the bytes to write
*/
put(bytes: Buffer): void {
put(bytes: Uint8Array): void {
this.sink.put(bytes)
}
@@ -98,8 +99,8 @@ class BinarySerializer {
*
* @param length the length of the bytes
*/
private encodeVariableLength(length: number): Buffer {
const lenBytes = Buffer.alloc(3)
private encodeVariableLength(length: number): Uint8Array {
const lenBytes = new Uint8Array(3)
if (length <= 192) {
lenBytes[0] = length
return lenBytes.slice(0, 1)

View File

@@ -8,7 +8,7 @@ import { BytesList } from './serdes/binary-serializer'
* Abstract class describing a SHAMapNode
*/
abstract class ShaMapNode {
abstract hashPrefix(): Buffer
abstract hashPrefix(): Uint8Array
abstract isLeaf(): boolean
abstract isInner(): boolean
abstract toBytesSink(list: BytesList): void
@@ -40,10 +40,10 @@ class ShaMapLeaf extends ShaMapNode {
/**
* 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 {
return this.item === undefined ? Buffer.alloc(0) : this.item.hashPrefix()
hashPrefix(): Uint8Array {
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
*/
hashPrefix(): Buffer {
hashPrefix(): Uint8Array {
return HashPrefix.innerNode
}

View File

@@ -5,6 +5,7 @@ import {
xAddressToClassicAddress,
} from 'ripple-address-codec'
import { Hash160 } from './hash-160'
import { hexToBytes } from '@xrplf/isomorphic/utils'
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 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)
}
@@ -35,7 +38,7 @@ class AccountID extends Hash160 {
}
return HEX_REGEX.test(value)
? new AccountID(Buffer.from(value, 'hex'))
? new AccountID(hexToBytes(value))
: this.fromBase58(value)
}
@@ -58,7 +61,7 @@ class AccountID extends Hash160 {
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
*/
toBase58(): string {
/* eslint-disable @typescript-eslint/no-explicit-any */
return encodeAccountID(this.bytes as any)
/* eslint-enable @typescript-eslint/no-explicit-any */
return encodeAccountID(this.bytes)
}
}

View File

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

View File

@@ -1,11 +1,12 @@
import { SerializedType } from './serialized-type'
import { BinaryParser } from '../serdes/binary-parser'
import { hexToBytes } from '@xrplf/isomorphic/utils'
/**
* Variable length encoded type
*/
class Blob extends SerializedType {
constructor(bytes: Buffer) {
constructor(bytes: Uint8Array) {
super(bytes)
}
@@ -32,7 +33,7 @@ class Blob extends SerializedType {
}
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')

View File

@@ -1,4 +1,5 @@
import { Hash160 } from './hash-160'
import { bytesToHex, hexToBytes, hexToString } from '@xrplf/isomorphic/utils'
const XRP_HEX_REGEX = /^0{40}$/
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
*/
function isoToBytes(iso: string): Buffer {
const bytes = Buffer.alloc(20)
function isoToBytes(iso: string): Uint8Array {
const bytes = new Uint8Array(20)
if (iso !== 'XRP') {
const isoBytes = iso.split('').map((c) => c.charCodeAt(0))
bytes.set(isoBytes, 12)
@@ -25,8 +26,8 @@ function isIsoCode(iso: string): boolean {
return ISO_REGEX.test(iso)
}
function isoCodeFromHex(code: Buffer): string | null {
const iso = code.toString()
function isoCodeFromHex(code: Uint8Array): string | null {
const iso = hexToString(bytesToHex(code))
if (iso === 'XRP') {
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
}
/**
* Ensures that a value is a valid representation of a currency
*/
function isValidRepresentation(input: Buffer | string): boolean {
return input instanceof Buffer
function isValidRepresentation(input: Uint8Array | string): boolean {
return input instanceof Uint8Array
? isBytesArray(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)) {
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 Currency extends Hash160 {
static readonly XRP = new Currency(Buffer.alloc(20))
static readonly XRP = new Currency(new Uint8Array(20))
private readonly _iso: string | null
constructor(byteBuf: Buffer) {
constructor(byteBuf: Uint8Array) {
super(byteBuf ?? Currency.XRP.bytes)
const hex = this.bytes.toString('hex')
const hex = bytesToHex(this.bytes)
if (XRP_HEX_REGEX.test(hex)) {
this._iso = 'XRP'
@@ -132,7 +133,7 @@ class Currency extends Hash160 {
if (iso !== null) {
return iso
}
return this.bytes.toString('hex').toUpperCase()
return bytesToHex(this.bytes)
}
}

View File

@@ -1,13 +1,14 @@
import { Hash } from './hash'
import { bytesToHex } from '@xrplf/isomorphic/utils'
/**
* Hash with a width of 128 bits
*/
class Hash128 extends Hash {
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) {
bytes = Hash128.ZERO_128.bytes
}
@@ -21,7 +22,7 @@ class Hash128 extends Hash {
* @returns hex String of this.bytes
*/
toHex(): string {
const hex = this.toBytes().toString('hex').toUpperCase()
const hex = bytesToHex(this.toBytes())
if (/^0+$/.exec(hex)) {
return ''
}

View File

@@ -5,9 +5,9 @@ import { Hash } from './hash'
*/
class Hash160 extends Hash {
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) {
bytes = Hash160.ZERO_160.bytes
}

View File

@@ -5,9 +5,9 @@ import { Hash } from './hash'
*/
class Hash256 extends Hash {
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)
}
}

View File

@@ -1,5 +1,7 @@
import { Comparable } from './serialized-type'
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
@@ -7,9 +9,9 @@ import { BinaryParser } from '../serdes/binary-parser'
class Hash extends Comparable<Hash | string> {
static readonly width: number
constructor(bytes: Buffer) {
constructor(bytes: Uint8Array) {
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}`)
}
}
@@ -25,7 +27,7 @@ class Hash extends Comparable<Hash | 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')
@@ -47,7 +49,8 @@ class Hash extends Comparable<Hash | string> {
* @param other The Hash to compare this to
*/
compareTo(other: Hash): number {
return this.bytes.compare(
return compare(
this.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 { AccountID } from './account-id'
@@ -27,9 +28,9 @@ function isIssueObject(arg): arg is IssueObject {
* Class for serializing/Deserializing Amounts
*/
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)
}
@@ -51,7 +52,7 @@ class Issue extends SerializedType {
return new Issue(currency)
}
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')
@@ -69,7 +70,7 @@ class Issue extends SerializedType {
return new Issue(currency)
}
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 { BinaryParser } from '../serdes/binary-parser'
import { SerializedType, JsonObject } from './serialized-type'
import { bytesToHex, concat } from '@xrplf/isomorphic/utils'
/**
* Constants for separating Paths in a PathSet
@@ -62,7 +63,7 @@ class Hop extends SerializedType {
return value
}
const bytes: Array<Buffer> = [Buffer.from([0])]
const bytes: Array<Uint8Array> = [Uint8Array.from([0])]
if (value.account) {
bytes.push(AccountID.from(value.account).toBytes())
@@ -79,7 +80,7 @@ class Hop extends SerializedType {
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 {
const type = parser.readUInt8()
const bytes: Array<Buffer> = [Buffer.from([type])]
const bytes: Array<Uint8Array> = [Uint8Array.from([type])]
if (type & TYPE_ACCOUNT) {
bytes.push(parser.read(AccountID.width))
@@ -104,7 +105,7 @@ class Hop extends SerializedType {
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
*/
toJSON(): HopObject {
const hopParser = new BinaryParser(this.bytes.toString('hex'))
const hopParser = new BinaryParser(bytesToHex(this.bytes))
const type = hopParser.readUInt8()
let account, currency, issuer
@@ -170,12 +171,12 @@ class Path extends SerializedType {
return value
}
const bytes: Array<Buffer> = []
const bytes: Array<Uint8Array> = []
value.forEach((hop: HopObject) => {
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
*/
static fromParser(parser: BinaryParser): Path {
const bytes: Array<Buffer> = []
const bytes: Array<Uint8Array> = []
while (!parser.end()) {
bytes.push(Hop.fromParser(parser).toBytes())
@@ -196,7 +197,7 @@ class Path extends SerializedType {
break
}
}
return new Path(Buffer.concat(bytes))
return new Path(concat(bytes))
}
/**
@@ -232,16 +233,16 @@ class PathSet extends SerializedType {
}
if (isPathSet(value)) {
const bytes: Array<Buffer> = []
const bytes: Array<Uint8Array> = []
value.forEach((path: Array<HopObject>) => {
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')
@@ -254,7 +255,7 @@ class PathSet extends SerializedType {
* @returns the PathSet read from parser
*/
static fromParser(parser: BinaryParser): PathSet {
const bytes: Array<Buffer> = []
const bytes: Array<Uint8Array> = []
while (!parser.end()) {
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 { BinaryParser } from '../serdes/binary-parser'
import { XrplDefinitionsBase } from '../enums'
import { bytesToHex } from '@xrplf/isomorphic/utils'
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
*/
class SerializedType {
protected readonly bytes: Buffer = Buffer.alloc(0)
protected readonly bytes: Uint8Array = new Uint8Array(0)
constructor(bytes?: Buffer) {
this.bytes = bytes ?? Buffer.alloc(0)
constructor(bytes?: Uint8Array) {
this.bytes = bytes ?? new Uint8Array(0)
}
static fromParser(parser: BinaryParser, hint?: number): SerializedType {
@@ -42,15 +42,15 @@ class SerializedType {
* @returns hex String of this.bytes
*/
toHex(): string {
return this.toBytes().toString('hex').toUpperCase()
return bytesToHex(this.toBytes())
}
/**
* 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) {
return this.bytes
}

View File

@@ -2,11 +2,12 @@ import { DEFAULT_DEFINITIONS, XrplDefinitionsBase } from '../enums'
import { SerializedType, JsonObject } from './serialized-type'
import { STObject } from './st-object'
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 OBJECT_END_MARKER = Buffer.from([0xe1])
const OBJECT_END_MARKER = Uint8Array.from([0xe1])
/**
* TypeGuard for Array<JsonObject>
@@ -28,7 +29,7 @@ class STArray extends SerializedType {
* @returns An STArray Object
*/
static fromParser(parser: BinaryParser): STArray {
const bytes: Array<Buffer> = []
const bytes: Array<Uint8Array> = []
while (!parser.end()) {
const field = parser.readField()
@@ -44,7 +45,7 @@ class STArray extends SerializedType {
}
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)) {
const bytes: Array<Buffer> = []
const bytes: Array<Uint8Array> = []
value.forEach((obj) => {
bytes.push(STObject.from(obj, undefined, definitions).toBytes())
})
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')

View File

@@ -11,7 +11,7 @@ import { BinarySerializer, BytesList } from '../serdes/binary-serializer'
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 ST_OBJECT = 'STObject'
const DESTINATION = 'Destination'

View File

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

View File

@@ -1,14 +1,17 @@
import { UInt } from './uint'
import { BinaryParser } from '../serdes/binary-parser'
import { readUInt32BE, writeUInt32BE } from '../utils'
/**
* Derived UInt class for serializing/deserializing 32 bit UInt
*/
class UInt32 extends UInt {
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)
}
@@ -26,16 +29,17 @@ class UInt32 extends UInt {
return val
}
const buf = Buffer.alloc(UInt32.width)
const buf = new Uint8Array(UInt32.width)
if (typeof val === 'string') {
const num = Number.parseInt(val)
buf.writeUInt32BE(num, 0)
writeUInt32BE(buf, num, 0)
return new UInt32(buf)
}
if (typeof val === 'number') {
buf.writeUInt32BE(val, 0)
UInt32.checkUintRange(val, 0, 0xffffffff)
writeUInt32BE(buf, val, 0)
return new UInt32(buf)
}
@@ -48,7 +52,7 @@ class UInt32 extends UInt {
* @returns the number represented by this.bytes
*/
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 { 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 mask = BigInt(0x00000000ffffffff)
@@ -9,9 +11,11 @@ const mask = BigInt(0x00000000ffffffff)
*/
class UInt64 extends UInt {
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)
}
@@ -30,7 +34,7 @@ class UInt64 extends UInt {
return val
}
let buf = Buffer.alloc(UInt64.width)
let buf = new Uint8Array(UInt64.width)
if (typeof val === 'number') {
if (val < 0) {
@@ -39,11 +43,11 @@ class UInt64 extends UInt {
const number = BigInt(val)
const intBuf = [Buffer.alloc(4), Buffer.alloc(4)]
intBuf[0].writeUInt32BE(Number(number >> BigInt(32)), 0)
intBuf[1].writeUInt32BE(Number(number & BigInt(mask)), 0)
const intBuf = [new Uint8Array(4), new Uint8Array(4)]
writeUInt32BE(intBuf[0], Number(number >> BigInt(32)), 0)
writeUInt32BE(intBuf[1], Number(number & BigInt(mask)), 0)
return new UInt64(Buffer.concat(intBuf))
return new UInt64(concat(intBuf))
}
if (typeof val === 'string') {
@@ -52,16 +56,16 @@ class UInt64 extends UInt {
}
const strBuf = val.padStart(16, '0')
buf = Buffer.from(strBuf, 'hex')
buf = hexToBytes(strBuf)
return new UInt64(buf)
}
if (typeof val === 'bigint') {
const intBuf = [Buffer.alloc(4), Buffer.alloc(4)]
intBuf[0].writeUInt32BE(Number(val >> BigInt(32)), 0)
intBuf[1].writeUInt32BE(Number(val & BigInt(mask)), 0)
const intBuf = [new Uint8Array(4), new Uint8Array(4)]
writeUInt32BE(intBuf[0], Number(Number(val >> BigInt(32))), 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')
@@ -73,7 +77,7 @@ class UInt64 extends UInt {
* @returns a hex-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
*/
valueOf(): bigint {
const msb = BigInt(this.bytes.slice(0, 4).readUInt32BE(0))
const lsb = BigInt(this.bytes.slice(4).readUInt32BE(0))
const msb = BigInt(readUInt32BE(this.bytes.slice(0, 4), 0))
const lsb = BigInt(readUInt32BE(this.bytes.slice(4), 0))
return (msb << BigInt(32)) | lsb
}
@@ -92,7 +96,7 @@ class UInt64 extends UInt {
*
* @returns 8 bytes representing the UInt64
*/
toBytes(): Buffer {
toBytes(): Uint8Array {
return this.bytes
}
}

View File

@@ -1,14 +1,16 @@
import { UInt } from './uint'
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
*/
class UInt8 extends UInt {
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)
}
@@ -27,8 +29,10 @@ class UInt8 extends UInt {
}
if (typeof val === 'number') {
const buf = Buffer.alloc(UInt8.width)
buf.writeUInt8(val, 0)
UInt8.checkUintRange(val, 0, 0xff)
const buf = new Uint8Array(UInt8.width)
writeUInt8(buf, val, 0)
return new UInt8(buf)
}
@@ -41,7 +45,7 @@ class UInt8 extends UInt {
* @returns the number represented by this.bytes
*/
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> {
protected static width: number
constructor(bytes: Buffer) {
constructor(bytes: Uint8Array) {
super(bytes)
}
@@ -47,6 +47,14 @@ abstract class UInt extends Comparable<UInt | number> {
* @returns the value
*/
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 }

View File

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

View File

@@ -2,8 +2,8 @@ import { BinaryParser } from '../serdes/binary-parser'
import { AccountID } from './account-id'
import { JsonObject, SerializedType } from './serialized-type'
import { Issue, IssueObject } from './issue'
import { concat } from '@xrplf/isomorphic/utils'
/**
* Interface for JSON objects that represent cross-chain bridges
@@ -34,11 +34,11 @@ function isXChainBridgeObject(arg): arg is XChainBridgeObject {
*/
class XChainBridge extends SerializedType {
static readonly ZERO_XCHAIN_BRIDGE: XChainBridge = new XChainBridge(
Buffer.concat([
Buffer.from([0x14]),
Buffer.alloc(40),
Buffer.from([0x14]),
Buffer.alloc(40),
concat([
Uint8Array.from([0x14]),
new Uint8Array(40),
Uint8Array.from([0x14]),
new Uint8Array(40),
]),
)
@@ -50,7 +50,7 @@ class XChainBridge extends SerializedType {
{ name: 'IssuingChainIssue', type: Issue },
]
constructor(bytes: Buffer) {
constructor(bytes: Uint8Array) {
super(bytes ?? XChainBridge.ZERO_XCHAIN_BRIDGE.bytes)
}
@@ -71,16 +71,16 @@ class XChainBridge extends SerializedType {
throw new Error('Invalid type to construct an XChainBridge')
}
const bytes: Array<Buffer> = []
const bytes: Array<Uint8Array> = []
this.TYPE_ORDER.forEach((item) => {
const { name, type } = item
if (type === AccountID) {
bytes.push(Buffer.from([0x14]))
bytes.push(Uint8Array.from([0x14]))
}
const object = type.from(value[name])
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
*/
static fromParser(parser: BinaryParser): XChainBridge {
const bytes: Array<Buffer> = []
const bytes: Array<Uint8Array> = []
this.TYPE_ORDER.forEach((item) => {
const { type } = item
if (type === AccountID) {
parser.skip(1)
bytes.push(Buffer.from([0x14]))
bytes.push(Uint8Array.from([0x14]))
}
const object = type.fromParser(parser)
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 BigNumber from 'bignumber.js'
import { encodeAccountID } from 'ripple-address-codec'
import { Field, TransactionType } from '../src/enums'
import { makeParser, readJSON } from '../src/binary'
import { parseHexOnly, hexOnly } from './utils'
import { BytesList } from '../src/serdes/binary-serializer'
import fixtures from './fixtures/data-driven-tests.json'
const __ = hexOnly
const { bytesToHex } = require('@xrplf/isomorphic/utils')
function toJSON(v) {
return v.toJSON ? v.toJSON() : v
}
@@ -28,16 +29,15 @@ function assertEqualAmountJSON(actual, expected) {
}
function basicApiTests() {
const bytes = parseHexOnly('00,01020304,0506')
it('can read slices of bytes', () => {
const parser = makeParser(bytes)
const parser = makeParser('00010203040506')
// @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)
expect(read1 instanceof Buffer).toBe(true)
expect(read1).toEqual(Buffer.from([0]))
expect(parser.read(4)).toEqual(Buffer.from([1, 2, 3, 4]))
expect(parser.read(2)).toEqual(Buffer.from([5, 6]))
expect(read1 instanceof Uint8Array).toBe(true)
expect(read1).toEqual(Uint8Array.from([0]))
expect(parser.read(4)).toEqual(Uint8Array.from([1, 2, 3, 4]))
expect(parser.read(2)).toEqual(Uint8Array.from([5, 6]))
expect(() => parser.read(1)).toThrow()
})
it('can read a Uint32 at full', () => {
@@ -62,12 +62,12 @@ function transactionParsingTests() {
},
TakerPays: '98957503520',
TransactionType: 'OfferCreate',
TxnSignature: __(`
TxnSignature: hexOnly(`
304502202ABE08D5E78D1E74A4C18F2714F64E87B8BD57444AF
A5733109EB3C077077520022100DB335EE97386E4C0591CAC02
4D50E9230D8F171EEB901B5E5E4BD6D1E0AEF98C`),
},
binary: __(`
binary: hexOnly(`
120007220000000024000195F964400000170A53AC2065D5460561E
C9DE000000000000000000000000000494C53000000000092D70596
8936C419CE614BF264B5EEB1CEA47FF468400000000000000A73210
@@ -102,11 +102,9 @@ function transactionParsingTests() {
expect(parser.read(8)).not.toEqual([])
expect(parser.readField()).toEqual(Field['SigningPubKey'])
expect(parser.readVariableLengthLength()).toBe(33)
expect(parser.read(33).toString('hex').toUpperCase()).toEqual(
tx_json.SigningPubKey,
)
expect(bytesToHex(parser.read(33))).toEqual(tx_json.SigningPubKey)
expect(parser.readField()).toEqual(Field['TxnSignature'])
expect(parser.readVariableLength().toString('hex').toUpperCase()).toEqual(
expect(bytesToHex(parser.readVariableLength())).toEqual(
tx_json.TxnSignature,
)
expect(parser.readField()).toEqual(Field['Account'])
@@ -303,7 +301,7 @@ function nestedObjectTests() {
}
function pathSetBinaryTests() {
const bytes = __(
const bytes = hexOnly(
`1200002200000000240000002E2E00004BF161D4C71AFD498D00000000000000
0000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA0
6594D168400000000000000A69D446F8038585E9400000000000000000000000

View File

@@ -5,6 +5,7 @@ const { encode, decode } = require('../src')
const { makeParser, BytesList, BinarySerializer } = binary
const { coreTypes } = require('../src/types')
const { UInt8, UInt16, UInt32, UInt64, STObject } = coreTypes
const deliverMinTx = require('./fixtures/delivermin-tx.json')
const deliverMinTxBinary = require('./fixtures/delivermin-tx-binary.json')
const SignerListSet = {
@@ -105,12 +106,12 @@ const NegativeUNL = require('./fixtures/negative-unl.json')
function bytesListTest() {
const list = new BytesList()
.put(Buffer.from([0]))
.put(Buffer.from([2, 3]))
.put(Buffer.from([4, 5]))
it('is an Array<Buffer>', function () {
.put(Uint8Array.from([0]))
.put(Uint8Array.from([2, 3]))
.put(Uint8Array.from([4, 5]))
it('is an Array<Uint8Array>', function () {
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 () {
expect(list.getLength()).toBe(5)
@@ -118,7 +119,7 @@ function bytesListTest() {
it('can join all arrays into one via toBytes', function () {
const joined = list.toBytes()
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
}
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(UInt16, 5, [0, 5])
check(UInt32, 5, [0, 0, 0, 5])

View File

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

View File

@@ -1,4 +1,5 @@
const { quality } = require('../src/coretypes')
const { bytesToHex } = require('@xrplf/isomorphic/utils')
describe('Quality encode/decode', function () {
const bookDirectory =
@@ -10,6 +11,6 @@ describe('Quality encode/decode', function () {
})
it('can encode', function () {
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,
): [
Hash256,
{ toBytesSink: (sink: BytesList) => void; hashPrefix: () => Buffer },
{ toBytesSink: (sink: BytesList) => void; hashPrefix: () => Uint8Array },
] {
let str = indexArg
while (str.length < 64) {
@@ -28,7 +28,7 @@ function makeItem(
index.toBytesSink(sink)
},
hashPrefix() {
return Buffer.from([1, 3, 3, 7])
return Uint8Array.from([1, 3, 3, 7])
},
}
return [index, item]

View File

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

View File

@@ -2,6 +2,12 @@
## 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)
### Breaking Changes

View File

@@ -8,6 +8,7 @@ import {
sign,
verify,
} from '../src'
import { stringToHex } from '@xrplf/isomorphic/utils'
const entropy = new Uint8Array([
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', () => {
const privateKey = fixtures.secp256k1.keypair.privateKey
const message = fixtures.secp256k1.message
const messageHex = Buffer.from(message, 'utf8').toString('hex')
const messageHex = stringToHex(message)
const signature = sign(messageHex, privateKey)
expect(signature).toEqual(fixtures.secp256k1.signature)
})
@@ -86,14 +87,14 @@ describe('api', () => {
const signature = fixtures.secp256k1.signature
const publicKey = fixtures.secp256k1.keypair.publicKey
const message = fixtures.secp256k1.message
const messageHex = Buffer.from(message, 'utf8').toString('hex')
const messageHex = stringToHex(message)
expect(verify(messageHex, signature, publicKey)).toBeTruthy()
})
it('sign - ed25519', () => {
const privateKey = fixtures.ed25519.keypair.privateKey
const message = fixtures.ed25519.message
const messageHex = Buffer.from(message, 'utf8').toString('hex')
const messageHex = stringToHex(message)
const signature = sign(messageHex, privateKey)
expect(signature).toEqual(fixtures.ed25519.signature)
})
@@ -102,7 +103,7 @@ describe('api', () => {
const signature = fixtures.ed25519.signature
const publicKey = fixtures.ed25519.keypair.publicKey
const message = fixtures.ed25519.message
const messageHex = Buffer.from(message, 'utf8').toString('hex')
const messageHex = stringToHex(message)
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:
- 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)

View File

@@ -33,12 +33,12 @@ export class Account {
},
}
constructor(secretNumbers?: string[] | string | Buffer) {
constructor(secretNumbers?: string[] | string | Uint8Array) {
if (typeof secretNumbers === 'string') {
this._secret = parseSecretString(secretNumbers)
} else if (Array.isArray(secretNumbers)) {
this._secret = secretNumbers
} else if (Buffer.isBuffer(secretNumbers)) {
} else if (secretNumbers instanceof Uint8Array) {
this._secret = entropyToSecret(secretNumbers)
} else {
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 {
return Buffer.from(randomBytes(16))
function randomEntropy(): Uint8Array {
return randomBytes(16)
}
function calculateChecksum(position: number, value: number): number {
@@ -32,11 +37,11 @@ function checkChecksum(
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 chunks = Array.from(len, (_a, chunk) => {
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)
return fill + String(no) + String(calculateChecksum(chunk, no))
})
@@ -50,8 +55,8 @@ function randomSecret(): string[] {
return entropyToSecret(randomEntropy())
}
function secretToEntropy(secret: string[]): Buffer {
return Buffer.concat(
function secretToEntropy(secret: string[]): Uint8Array {
return concat(
secret.map((chunk, i) => {
const no = Number(chunk.slice(0, 5))
const checksum = Number(chunk.slice(5))
@@ -62,7 +67,7 @@ function secretToEntropy(secret: string[]): Buffer {
throw new Error('Invalid secret part: checksum invalid')
}
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 { Account, secretToEntropy } from '../src'
@@ -17,7 +18,7 @@ describe('API: XRPL Secret Numbers', () => {
})
describe('Account based on entropy', () => {
const entropy = Buffer.from('0123456789ABCDEF0123456789ABCDEF', 'hex')
const entropy = hexToBytes('0123456789ABCDEF0123456789ABCDEF')
const account = new Account(entropy)
it('familySeed as expected', () => {

View File

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

View File

@@ -10,6 +10,8 @@
*is part of the public domain.
*/
import { hexToBytes, concat } from '@xrplf/isomorphic/utils'
import rfc1751Words from './rfc1751Words.json'
const rfc1751WordList: string[] = rfc1751Words
@@ -59,7 +61,7 @@ function extract(key: string, start: number, length: number): number {
*/
function keyToRFC1751Mnemonic(hex_key: string): string {
// 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
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.
* @returns A Buffer containing an encoded secret.
*/
function rfc1751MnemonicToKey(english: string): Buffer {
function rfc1751MnemonicToKey(english: string): Uint8Array {
const words = english.split(' ')
let key: number[] = []
@@ -123,7 +125,7 @@ function rfc1751MnemonicToKey(english: string): Buffer {
}
// This is a step specific to the XRPL's implementation
const bufferKey = swap128(Buffer.from(key))
const bufferKey = swap128(Uint8Array.from(key))
return bufferKey
}
@@ -165,26 +167,53 @@ function getSubKey(
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 */
return Array.prototype.slice.call(buf) as number[]
}
/**
* Swap the byte order of a 128-bit buffer.
*
* @param buf - A 128-bit (16 byte) buffer
* @returns A buffer containing the same data with reversed endianness
*/
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()
function swap(arr: Uint8Array, n: number, m: number): void {
const i = arr[n]
// eslint-disable-next-line no-param-reassign -- we have to swap
arr[n] = arr[m]
// eslint-disable-next-line no-param-reassign -- see above
arr[m] = i
}
// Swap the two 64-bit numbers since our buffer is 128 bits.
return Buffer.concat(
[reversedBytes.slice(8, 16), reversedBytes.slice(0, 8)],
16,
)
/**
* Interprets arr as an array of 64-bit numbers and swaps byte order in 64 bit chunks.
* Example of two 64 bit numbers 0000000100000002 => 1000000020000000
*
* @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 }

View File

@@ -1,3 +1,4 @@
import { bytesToHex } from '@xrplf/isomorphic/utils'
import { BigNumber } from 'bignumber.js'
import { decodeAccountID } from 'ripple-address-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
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)
}

View File

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

View File

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

View File

@@ -3,6 +3,7 @@
/* eslint-disable no-bitwise -- this file mimics behavior in rippled. It uses
bitwise operators for and-ing numbers with a mask and bit shifting. */
import { bytesToHex } from '@xrplf/isomorphic/utils'
import BigNumber from 'bignumber.js'
import { decodeAccountID } from 'ripple-address-codec'
@@ -20,7 +21,7 @@ const HEX = 16
const BYTE_LENGTH = 4
function addressToHex(address: string): string {
return Buffer.from(decodeAccountID(address)).toString('hex')
return bytesToHex(decodeAccountID(address))
}
function ledgerSpaceHex(name: keyof typeof ledgerSpaces): string {
@@ -37,7 +38,7 @@ function currencyToHex(currency: string): string {
bytes[12] = currency.charCodeAt(0) & MASK
bytes[13] = currency.charCodeAt(1) & 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. */
import { hexToBytes } from '@xrplf/isomorphic/utils'
import BigNumber from 'bignumber.js'
import { encodeAccountID } from 'ripple-address-codec'
@@ -85,7 +86,7 @@ export default function parseNFTokenID(nftokenID: string): {
NFTokenID: nftokenID,
Flags: new BigNumber(nftokenID.substring(0, 4), 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),
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.
*
* @param string - The string to convert to Hex.
* @returns The Hex equivalent of the string.
*
* @deprecated use `@xrplf/isomorphic/utils`'s `stringToHex`
*
* @category Utilities
*/
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 encoding - The encoding to use. Defaults to 'utf8' (UTF-8). 'ascii' is also allowed.
* @returns The converted string.
*
* @deprecated use `@xrplf/isomorphic/utils`'s `hexToString`
*
* @category Utilities
*/
function convertHexToString(
hex: string,
encoding: BufferEncoding = 'utf8',
): string {
return Buffer.from(hex, 'hex').toString(encoding)
function convertHexToString(hex: string, encoding = 'utf8'): string {
return hexToString(hex, encoding)
}
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 */
import net from 'net'
@@ -25,22 +24,6 @@ import {
} from './setupClient'
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
const TIMEOUT = 20000

View File

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

View File

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