ox 0.13.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Base32/package.json +6 -0
- package/Bech32m/package.json +6 -0
- package/CHANGELOG.md +25 -0
- package/CompactSize/package.json +6 -0
- package/_cjs/core/Base32.js +73 -0
- package/_cjs/core/Base32.js.map +1 -0
- package/_cjs/core/Bech32m.js +205 -0
- package/_cjs/core/Bech32m.js.map +1 -0
- package/_cjs/core/CompactSize.js +91 -0
- package/_cjs/core/CompactSize.js.map +1 -0
- package/_cjs/index.js +5 -2
- package/_cjs/index.js.map +1 -1
- package/_cjs/tempo/KeyAuthorization.js +4 -4
- package/_cjs/tempo/KeyAuthorization.js.map +1 -1
- package/_cjs/tempo/SignatureEnvelope.js +18 -3
- package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
- package/_cjs/tempo/TempoAddress.js +117 -0
- package/_cjs/tempo/TempoAddress.js.map +1 -0
- package/_cjs/tempo/TxEnvelopeTempo.js +5 -2
- package/_cjs/tempo/TxEnvelopeTempo.js.map +1 -1
- package/_cjs/tempo/index.js +2 -1
- package/_cjs/tempo/index.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/core/Base32.js +119 -0
- package/_esm/core/Base32.js.map +1 -0
- package/_esm/core/Bech32m.js +238 -0
- package/_esm/core/Bech32m.js.map +1 -0
- package/_esm/core/CompactSize.js +150 -0
- package/_esm/core/CompactSize.js.map +1 -0
- package/_esm/index.js +72 -0
- package/_esm/index.js.map +1 -1
- package/_esm/tempo/KeyAuthorization.js +19 -9
- package/_esm/tempo/KeyAuthorization.js.map +1 -1
- package/_esm/tempo/SignatureEnvelope.js +22 -5
- package/_esm/tempo/SignatureEnvelope.js.map +1 -1
- package/_esm/tempo/TempoAddress.js +182 -0
- package/_esm/tempo/TempoAddress.js.map +1 -0
- package/_esm/tempo/TxEnvelopeTempo.js +42 -2
- package/_esm/tempo/TxEnvelopeTempo.js.map +1 -1
- package/_esm/tempo/index.js +21 -0
- package/_esm/tempo/index.js.map +1 -1
- package/_esm/version.js +1 -1
- package/_types/core/Base32.d.ts +79 -0
- package/_types/core/Base32.d.ts.map +1 -0
- package/_types/core/Bech32m.d.ts +93 -0
- package/_types/core/Bech32m.d.ts.map +1 -0
- package/_types/core/CompactSize.d.ts +107 -0
- package/_types/core/CompactSize.d.ts.map +1 -0
- package/_types/index.d.ts +72 -0
- package/_types/index.d.ts.map +1 -1
- package/_types/tempo/KeyAuthorization.d.ts +17 -7
- package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
- package/_types/tempo/SignatureEnvelope.d.ts +19 -5
- package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/_types/tempo/TempoAddress.d.ts +126 -0
- package/_types/tempo/TempoAddress.d.ts.map +1 -0
- package/_types/tempo/TxEnvelopeTempo.d.ts +47 -1
- package/_types/tempo/TxEnvelopeTempo.d.ts.map +1 -1
- package/_types/tempo/index.d.ts +21 -0
- package/_types/tempo/index.d.ts.map +1 -1
- package/_types/version.d.ts +1 -1
- package/core/Base32.ts +134 -0
- package/core/Bech32m.ts +263 -0
- package/core/CompactSize.ts +169 -0
- package/index.ts +74 -1
- package/package.json +21 -1
- package/tempo/KeyAuthorization.test.ts +70 -23
- package/tempo/KeyAuthorization.ts +21 -18
- package/tempo/SignatureEnvelope.test.ts +15 -8
- package/tempo/SignatureEnvelope.ts +43 -8
- package/tempo/TempoAddress/package.json +6 -0
- package/tempo/TempoAddress.test.ts +237 -0
- package/tempo/TempoAddress.ts +222 -0
- package/tempo/Transaction.test.ts +4 -2
- package/tempo/TxEnvelopeTempo.test.ts +7 -3
- package/tempo/TxEnvelopeTempo.ts +52 -1
- package/tempo/e2e.test.ts +45 -10
- package/tempo/index.ts +22 -0
- package/version.ts +1 -1
package/core/Base32.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import * as Bytes from './Bytes.js'
|
|
2
|
+
import * as Errors from './Errors.js'
|
|
3
|
+
import * as Hex from './Hex.js'
|
|
4
|
+
|
|
5
|
+
/** Bech32 base32 alphabet (BIP-173). */
|
|
6
|
+
const alphabet = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'
|
|
7
|
+
const alphabetMap = /*#__PURE__*/ (() => {
|
|
8
|
+
const map: Record<string, number> = {}
|
|
9
|
+
for (let i = 0; i < alphabet.length; i++) map[alphabet[i]!] = i
|
|
10
|
+
return map
|
|
11
|
+
})()
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Encodes a {@link ox#Bytes.Bytes} value to a Base32-encoded string (using the BIP-173 bech32 alphabet).
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts twoslash
|
|
18
|
+
* import { Base32, Bytes } from 'ox'
|
|
19
|
+
*
|
|
20
|
+
* const value = Base32.fromBytes(new Uint8Array([0x00, 0xff, 0x00]))
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @param value - The byte array to encode.
|
|
24
|
+
* @returns The Base32 encoded string.
|
|
25
|
+
*/
|
|
26
|
+
export function fromBytes(value: Bytes.Bytes): string {
|
|
27
|
+
let bits = 0
|
|
28
|
+
let acc = 0
|
|
29
|
+
let result = ''
|
|
30
|
+
for (const byte of value) {
|
|
31
|
+
acc = (acc << 8) | byte
|
|
32
|
+
bits += 8
|
|
33
|
+
while (bits >= 5) {
|
|
34
|
+
bits -= 5
|
|
35
|
+
result += alphabet[(acc >>> bits) & 0x1f]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (bits > 0) result += alphabet[(acc << (5 - bits)) & 0x1f]
|
|
39
|
+
return result
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export declare namespace fromBytes {
|
|
43
|
+
type ErrorType = Errors.GlobalErrorType
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Encodes a {@link ox#Hex.Hex} value to a Base32-encoded string (using the BIP-173 bech32 alphabet).
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts twoslash
|
|
51
|
+
* import { Base32 } from 'ox'
|
|
52
|
+
*
|
|
53
|
+
* const value = Base32.fromHex('0x00ff00')
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @param value - The hex value to encode.
|
|
57
|
+
* @returns The Base32 encoded string.
|
|
58
|
+
*/
|
|
59
|
+
export function fromHex(value: Hex.Hex): string {
|
|
60
|
+
return fromBytes(Bytes.fromHex(value))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export declare namespace fromHex {
|
|
64
|
+
type ErrorType = fromBytes.ErrorType | Errors.GlobalErrorType
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Decodes a Base32-encoded string (using the BIP-173 bech32 alphabet) to {@link ox#Bytes.Bytes}.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts twoslash
|
|
72
|
+
* import { Base32 } from 'ox'
|
|
73
|
+
*
|
|
74
|
+
* const value = Base32.toBytes('qqsa0')
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* @param value - The Base32 encoded string.
|
|
78
|
+
* @returns The decoded byte array.
|
|
79
|
+
*/
|
|
80
|
+
export function toBytes(value: string): Bytes.Bytes {
|
|
81
|
+
const values: number[] = []
|
|
82
|
+
for (const char of value) {
|
|
83
|
+
const v = alphabetMap[char]
|
|
84
|
+
if (v === undefined) throw new InvalidCharacterError({ character: char })
|
|
85
|
+
values.push(v)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let bits = 0
|
|
89
|
+
let acc = 0
|
|
90
|
+
const bytes: number[] = []
|
|
91
|
+
for (const v of values) {
|
|
92
|
+
acc = (acc << 5) | v
|
|
93
|
+
bits += 5
|
|
94
|
+
if (bits >= 8) {
|
|
95
|
+
bits -= 8
|
|
96
|
+
bytes.push((acc >>> bits) & 0xff)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return new Uint8Array(bytes)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export declare namespace toBytes {
|
|
103
|
+
type ErrorType = InvalidCharacterError | Errors.GlobalErrorType
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Decodes a Base32-encoded string (using the BIP-173 bech32 alphabet) to {@link ox#Hex.Hex}.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts twoslash
|
|
111
|
+
* import { Base32 } from 'ox'
|
|
112
|
+
*
|
|
113
|
+
* const value = Base32.toHex('qqsa0')
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* @param value - The Base32 encoded string.
|
|
117
|
+
* @returns The decoded hex string.
|
|
118
|
+
*/
|
|
119
|
+
export function toHex(value: string): Hex.Hex {
|
|
120
|
+
return Hex.fromBytes(toBytes(value))
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export declare namespace toHex {
|
|
124
|
+
type ErrorType = toBytes.ErrorType | Errors.GlobalErrorType
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Thrown when a Base32 string contains an invalid character. */
|
|
128
|
+
export class InvalidCharacterError extends Errors.BaseError {
|
|
129
|
+
override readonly name = 'Base32.InvalidCharacterError'
|
|
130
|
+
|
|
131
|
+
constructor({ character }: { character: string }) {
|
|
132
|
+
super(`Invalid bech32 base32 character: "${character}".`)
|
|
133
|
+
}
|
|
134
|
+
}
|
package/core/Bech32m.ts
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import type * as Errors from './Errors.js'
|
|
2
|
+
import { BaseError } from './Errors.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Encodes data bytes with a human-readable part (HRP) into a bech32m string (BIP-350).
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts twoslash
|
|
9
|
+
* import { Bech32m } from 'ox'
|
|
10
|
+
*
|
|
11
|
+
* const encoded = Bech32m.encode('tempo', new Uint8Array(20))
|
|
12
|
+
* // @log: 'tempo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqwa7xtm'
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @param hrp - The human-readable part (e.g. `"tempo"`, `"tempoz"`).
|
|
16
|
+
* @param data - The data bytes to encode.
|
|
17
|
+
* @returns The bech32m-encoded string.
|
|
18
|
+
*/
|
|
19
|
+
export function encode(
|
|
20
|
+
hrp: string,
|
|
21
|
+
data: Uint8Array,
|
|
22
|
+
options: encode.Options = {},
|
|
23
|
+
): string {
|
|
24
|
+
const { limit = 90 } = options
|
|
25
|
+
|
|
26
|
+
hrp = hrp.toLowerCase()
|
|
27
|
+
|
|
28
|
+
if (hrp.length === 0) throw new InvalidHrpError()
|
|
29
|
+
for (let i = 0; i < hrp.length; i++) {
|
|
30
|
+
const c = hrp.charCodeAt(i)
|
|
31
|
+
if (c < 33 || c > 126) throw new InvalidHrpError()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const data5 = convertBits(data, 8, 5, true)
|
|
35
|
+
|
|
36
|
+
if (hrp.length + 1 + data5.length + 6 > limit)
|
|
37
|
+
throw new ExceedsLengthError({ limit })
|
|
38
|
+
|
|
39
|
+
const checksum = createChecksum(hrp, data5)
|
|
40
|
+
let result = hrp + '1'
|
|
41
|
+
for (const d of data5.concat(checksum)) result += alphabet[d]
|
|
42
|
+
return result
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export declare namespace encode {
|
|
46
|
+
type Options = {
|
|
47
|
+
/** Maximum length of the encoded string. @default 90 */
|
|
48
|
+
limit?: number | undefined
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type ErrorType = InvalidHrpError | ExceedsLengthError | Errors.GlobalErrorType
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Decodes a bech32m string (BIP-350) into a human-readable part and data bytes.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts twoslash
|
|
59
|
+
* import { Bech32m } from 'ox'
|
|
60
|
+
*
|
|
61
|
+
* const { hrp, data } = Bech32m.decode('tempo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqwa7xtm')
|
|
62
|
+
* // @log: { hrp: 'tempo', data: Uint8Array(20) }
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* @param str - The bech32m-encoded string to decode.
|
|
66
|
+
* @returns The decoded HRP and data bytes.
|
|
67
|
+
*/
|
|
68
|
+
export function decode(
|
|
69
|
+
str: string,
|
|
70
|
+
options: decode.Options = {},
|
|
71
|
+
): decode.ReturnType {
|
|
72
|
+
const { limit = 90 } = options
|
|
73
|
+
|
|
74
|
+
if (str.length > limit) throw new ExceedsLengthError({ limit })
|
|
75
|
+
|
|
76
|
+
if (str !== str.toLowerCase() && str !== str.toUpperCase())
|
|
77
|
+
throw new MixedCaseError()
|
|
78
|
+
|
|
79
|
+
const lower = str.toLowerCase()
|
|
80
|
+
const pos = lower.lastIndexOf('1')
|
|
81
|
+
if (pos === -1) throw new NoSeparatorError()
|
|
82
|
+
if (pos === 0) throw new InvalidHrpError()
|
|
83
|
+
if (pos + 7 > lower.length) throw new InvalidChecksumError()
|
|
84
|
+
|
|
85
|
+
const hrp = lower.slice(0, pos)
|
|
86
|
+
for (let i = 0; i < hrp.length; i++) {
|
|
87
|
+
const c = hrp.charCodeAt(i)
|
|
88
|
+
if (c < 33 || c > 126) throw new InvalidHrpError()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const dataChars = lower.slice(pos + 1)
|
|
92
|
+
|
|
93
|
+
const data5: number[] = []
|
|
94
|
+
for (const c of dataChars) {
|
|
95
|
+
const v = alphabetMap[c]
|
|
96
|
+
if (v === undefined) throw new InvalidCharacterError({ character: c })
|
|
97
|
+
data5.push(v)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!verifyChecksum(hrp, data5)) throw new InvalidChecksumError()
|
|
101
|
+
|
|
102
|
+
const data8 = convertBits(data5.slice(0, -6), 5, 8, false)
|
|
103
|
+
return { hrp, data: new Uint8Array(data8) }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export declare namespace decode {
|
|
107
|
+
type Options = {
|
|
108
|
+
/** Maximum length of the encoded string. @default 90 */
|
|
109
|
+
limit?: number | undefined
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
type ReturnType = {
|
|
113
|
+
/** The human-readable part. */
|
|
114
|
+
hrp: string
|
|
115
|
+
/** The decoded data bytes. */
|
|
116
|
+
data: Uint8Array
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
type ErrorType =
|
|
120
|
+
| NoSeparatorError
|
|
121
|
+
| InvalidChecksumError
|
|
122
|
+
| InvalidCharacterError
|
|
123
|
+
| InvalidPaddingError
|
|
124
|
+
| MixedCaseError
|
|
125
|
+
| InvalidHrpError
|
|
126
|
+
| ExceedsLengthError
|
|
127
|
+
| Errors.GlobalErrorType
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Thrown when a bech32m string has no separator. */
|
|
131
|
+
export class NoSeparatorError extends BaseError {
|
|
132
|
+
override readonly name = 'Bech32m.NoSeparatorError'
|
|
133
|
+
constructor() {
|
|
134
|
+
super('Bech32m string has no separator.')
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Thrown when a bech32m string has an invalid checksum. */
|
|
139
|
+
export class InvalidChecksumError extends BaseError {
|
|
140
|
+
override readonly name = 'Bech32m.InvalidChecksumError'
|
|
141
|
+
constructor() {
|
|
142
|
+
super('Invalid bech32m checksum.')
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Thrown when a bech32m string contains an invalid character. */
|
|
147
|
+
export class InvalidCharacterError extends BaseError {
|
|
148
|
+
override readonly name = 'Bech32m.InvalidCharacterError'
|
|
149
|
+
constructor({ character }: { character: string }) {
|
|
150
|
+
super(`Invalid bech32m character: "${character}".`)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Thrown when the padding bits are invalid during base32 conversion. */
|
|
155
|
+
export class InvalidPaddingError extends BaseError {
|
|
156
|
+
override readonly name = 'Bech32m.InvalidPaddingError'
|
|
157
|
+
constructor() {
|
|
158
|
+
super('Invalid padding in bech32m data.')
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Thrown when a bech32m string contains mixed case. */
|
|
163
|
+
export class MixedCaseError extends BaseError {
|
|
164
|
+
override readonly name = 'Bech32m.MixedCaseError'
|
|
165
|
+
constructor() {
|
|
166
|
+
super('Bech32m string must not contain mixed case.')
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Thrown when the HRP is invalid (empty or contains non-ASCII characters). */
|
|
171
|
+
export class InvalidHrpError extends BaseError {
|
|
172
|
+
override readonly name = 'Bech32m.InvalidHrpError'
|
|
173
|
+
constructor() {
|
|
174
|
+
super(
|
|
175
|
+
'Invalid bech32m human-readable part (HRP). Must be 1+ characters in ASCII range 33-126.',
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Thrown when the encoded string exceeds the length limit. */
|
|
181
|
+
export class ExceedsLengthError extends BaseError {
|
|
182
|
+
override readonly name = 'Bech32m.ExceedsLengthError'
|
|
183
|
+
constructor({ limit }: { limit: number }) {
|
|
184
|
+
super(`Bech32m string exceeds length limit of ${limit}.`)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** @internal */
|
|
189
|
+
const alphabet = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'
|
|
190
|
+
|
|
191
|
+
/** @internal */
|
|
192
|
+
const alphabetMap = /*#__PURE__*/ (() => {
|
|
193
|
+
const map: Record<string, number> = {}
|
|
194
|
+
for (let i = 0; i < alphabet.length; i++) map[alphabet[i]!] = i
|
|
195
|
+
return map
|
|
196
|
+
})()
|
|
197
|
+
|
|
198
|
+
/** @internal */
|
|
199
|
+
const BECH32M_CONST = 0x2bc830a3
|
|
200
|
+
|
|
201
|
+
/** @internal */
|
|
202
|
+
const GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
|
|
203
|
+
|
|
204
|
+
/** @internal */
|
|
205
|
+
function polymod(values: number[]): number {
|
|
206
|
+
let chk = 1
|
|
207
|
+
for (const v of values) {
|
|
208
|
+
const b = chk >> 25
|
|
209
|
+
chk = ((chk & 0x1ffffff) << 5) ^ v
|
|
210
|
+
for (let i = 0; i < 5; i++) if ((b >> i) & 1) chk ^= GEN[i]!
|
|
211
|
+
}
|
|
212
|
+
return chk
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** @internal */
|
|
216
|
+
function hrpExpand(hrp: string): number[] {
|
|
217
|
+
const ret: number[] = []
|
|
218
|
+
for (let i = 0; i < hrp.length; i++) ret.push(hrp.charCodeAt(i) >> 5)
|
|
219
|
+
ret.push(0)
|
|
220
|
+
for (let i = 0; i < hrp.length; i++) ret.push(hrp.charCodeAt(i) & 31)
|
|
221
|
+
return ret
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** @internal */
|
|
225
|
+
function createChecksum(hrp: string, data: number[]): number[] {
|
|
226
|
+
const values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0])
|
|
227
|
+
const mod = polymod(values) ^ BECH32M_CONST
|
|
228
|
+
const ret: number[] = []
|
|
229
|
+
for (let i = 0; i < 6; i++) ret.push((mod >> (5 * (5 - i))) & 31)
|
|
230
|
+
return ret
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** @internal */
|
|
234
|
+
function verifyChecksum(hrp: string, data: number[]): boolean {
|
|
235
|
+
return polymod(hrpExpand(hrp).concat(data)) === BECH32M_CONST
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** @internal */
|
|
239
|
+
function convertBits(
|
|
240
|
+
data: Iterable<number>,
|
|
241
|
+
fromBits: number,
|
|
242
|
+
toBits: number,
|
|
243
|
+
pad: boolean,
|
|
244
|
+
): number[] {
|
|
245
|
+
let acc = 0
|
|
246
|
+
let bits = 0
|
|
247
|
+
const maxv = (1 << toBits) - 1
|
|
248
|
+
const ret: number[] = []
|
|
249
|
+
for (const value of data) {
|
|
250
|
+
acc = (acc << fromBits) | value
|
|
251
|
+
bits += fromBits
|
|
252
|
+
while (bits >= toBits) {
|
|
253
|
+
bits -= toBits
|
|
254
|
+
ret.push((acc >> bits) & maxv)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (pad) {
|
|
258
|
+
if (bits > 0) ret.push((acc << (toBits - bits)) & maxv)
|
|
259
|
+
} else if (bits >= fromBits || (acc << (toBits - bits)) & maxv) {
|
|
260
|
+
throw new InvalidPaddingError()
|
|
261
|
+
}
|
|
262
|
+
return ret
|
|
263
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import * as Bytes from './Bytes.js'
|
|
2
|
+
import * as Errors from './Errors.js'
|
|
3
|
+
import * as Hex from './Hex.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Encodes an integer using Bitcoin's CompactSize variable-length encoding.
|
|
7
|
+
*
|
|
8
|
+
* | Range | Encoding | Bytes |
|
|
9
|
+
* |---|---|---|
|
|
10
|
+
* | 0–252 | Direct value | 1 |
|
|
11
|
+
* | 253–65,535 | `0xFD` + 2 bytes LE | 3 |
|
|
12
|
+
* | 65,536–4,294,967,295 | `0xFE` + 4 bytes LE | 5 |
|
|
13
|
+
* | \> 4,294,967,295 | `0xFF` + 8 bytes LE | 9 |
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts twoslash
|
|
17
|
+
* import { CompactSize } from 'ox'
|
|
18
|
+
*
|
|
19
|
+
* const bytes = CompactSize.toBytes(252)
|
|
20
|
+
* // Uint8Array [252]
|
|
21
|
+
*
|
|
22
|
+
* const bytes2 = CompactSize.toBytes(253)
|
|
23
|
+
* // Uint8Array [253, 253, 0]
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @param value - The integer to encode.
|
|
27
|
+
* @returns The CompactSize-encoded bytes.
|
|
28
|
+
*/
|
|
29
|
+
export function toBytes(value: bigint | number): Bytes.Bytes {
|
|
30
|
+
const n = BigInt(value)
|
|
31
|
+
if (n < 0n) throw new NegativeValueError({ value: n })
|
|
32
|
+
if (n <= 252n) return new Uint8Array([Number(n)])
|
|
33
|
+
if (n <= 0xffffn) {
|
|
34
|
+
const buf = new Uint8Array(3)
|
|
35
|
+
buf[0] = 0xfd
|
|
36
|
+
const view = new DataView(buf.buffer)
|
|
37
|
+
view.setUint16(1, Number(n), true)
|
|
38
|
+
return buf
|
|
39
|
+
}
|
|
40
|
+
if (n <= 0xffffffffn) {
|
|
41
|
+
const buf = new Uint8Array(5)
|
|
42
|
+
buf[0] = 0xfe
|
|
43
|
+
const view = new DataView(buf.buffer)
|
|
44
|
+
view.setUint32(1, Number(n), true)
|
|
45
|
+
return buf
|
|
46
|
+
}
|
|
47
|
+
const buf = new Uint8Array(9)
|
|
48
|
+
buf[0] = 0xff
|
|
49
|
+
const view = new DataView(buf.buffer)
|
|
50
|
+
view.setBigUint64(1, n, true)
|
|
51
|
+
return buf
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export declare namespace toBytes {
|
|
55
|
+
type ErrorType = NegativeValueError | Errors.GlobalErrorType
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Encodes an integer using Bitcoin's CompactSize variable-length encoding and returns it as {@link ox#Hex.Hex}.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts twoslash
|
|
63
|
+
* import { CompactSize } from 'ox'
|
|
64
|
+
*
|
|
65
|
+
* const hex = CompactSize.toHex(252)
|
|
66
|
+
* // '0xfc'
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* @param value - The integer to encode.
|
|
70
|
+
* @returns The CompactSize-encoded hex string.
|
|
71
|
+
*/
|
|
72
|
+
export function toHex(value: bigint | number): Hex.Hex {
|
|
73
|
+
return Hex.fromBytes(toBytes(value))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export declare namespace toHex {
|
|
77
|
+
type ErrorType = toBytes.ErrorType | Errors.GlobalErrorType
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Decodes a CompactSize-encoded value from {@link ox#Bytes.Bytes}.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts twoslash
|
|
85
|
+
* import { CompactSize } from 'ox'
|
|
86
|
+
*
|
|
87
|
+
* const result = CompactSize.fromBytes(new Uint8Array([0xfd, 0x00, 0x01]))
|
|
88
|
+
* // { value: 256, size: 3 }
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* @param data - The bytes to decode from.
|
|
92
|
+
* @returns The decoded value and number of bytes consumed.
|
|
93
|
+
*/
|
|
94
|
+
export function fromBytes(data: Bytes.Bytes): fromBytes.ReturnType {
|
|
95
|
+
if (data.length === 0)
|
|
96
|
+
throw new InsufficientBytesError({ expected: 1, actual: 0 })
|
|
97
|
+
const first = data[0]!
|
|
98
|
+
if (first < 0xfd) return { value: BigInt(first), size: 1 }
|
|
99
|
+
const view = new DataView(data.buffer, data.byteOffset)
|
|
100
|
+
if (first === 0xfd) {
|
|
101
|
+
if (data.length < 3)
|
|
102
|
+
throw new InsufficientBytesError({ expected: 3, actual: data.length })
|
|
103
|
+
return { value: BigInt(view.getUint16(1, true)), size: 3 }
|
|
104
|
+
}
|
|
105
|
+
if (first === 0xfe) {
|
|
106
|
+
if (data.length < 5)
|
|
107
|
+
throw new InsufficientBytesError({ expected: 5, actual: data.length })
|
|
108
|
+
return { value: BigInt(view.getUint32(1, true)), size: 5 }
|
|
109
|
+
}
|
|
110
|
+
if (data.length < 9)
|
|
111
|
+
throw new InsufficientBytesError({ expected: 9, actual: data.length })
|
|
112
|
+
return {
|
|
113
|
+
value: view.getBigUint64(1, true),
|
|
114
|
+
size: 9,
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export declare namespace fromBytes {
|
|
119
|
+
type ReturnType = {
|
|
120
|
+
/** The decoded integer value. */
|
|
121
|
+
value: bigint
|
|
122
|
+
/** The number of bytes consumed. */
|
|
123
|
+
size: number
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
type ErrorType = InsufficientBytesError | Errors.GlobalErrorType
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Decodes a CompactSize-encoded value from {@link ox#Hex.Hex}.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```ts twoslash
|
|
134
|
+
* import { CompactSize } from 'ox'
|
|
135
|
+
*
|
|
136
|
+
* const result = CompactSize.fromHex('0xfd0001')
|
|
137
|
+
* // { value: 256, size: 3 }
|
|
138
|
+
* ```
|
|
139
|
+
*
|
|
140
|
+
* @param data - The hex string to decode from.
|
|
141
|
+
* @returns The decoded value and number of bytes consumed.
|
|
142
|
+
*/
|
|
143
|
+
export function fromHex(data: Hex.Hex): fromBytes.ReturnType {
|
|
144
|
+
return fromBytes(Bytes.fromHex(data))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export declare namespace fromHex {
|
|
148
|
+
type ErrorType = fromBytes.ErrorType | Errors.GlobalErrorType
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Thrown when a CompactSize value is negative. */
|
|
152
|
+
export class NegativeValueError extends Errors.BaseError {
|
|
153
|
+
override readonly name = 'CompactSize.NegativeValueError'
|
|
154
|
+
|
|
155
|
+
constructor({ value }: { value: bigint }) {
|
|
156
|
+
super(`CompactSize value must be non-negative, got ${value}.`)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Thrown when there are insufficient bytes to decode a CompactSize value. */
|
|
161
|
+
export class InsufficientBytesError extends Errors.BaseError {
|
|
162
|
+
override readonly name = 'CompactSize.InsufficientBytesError'
|
|
163
|
+
|
|
164
|
+
constructor({ expected, actual }: { expected: number; actual: number }) {
|
|
165
|
+
super(
|
|
166
|
+
`Insufficient bytes for CompactSize decoding. Expected at least ${expected}, got ${actual}.`,
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
}
|
package/index.ts
CHANGED
|
@@ -838,6 +838,30 @@ export * as AesGcm from './core/AesGcm.js'
|
|
|
838
838
|
*/
|
|
839
839
|
export * as Authorization from './core/Authorization.js'
|
|
840
840
|
|
|
841
|
+
/**
|
|
842
|
+
* Utility functions for working with Base32 values using the [BIP-173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) bech32 alphabet.
|
|
843
|
+
*
|
|
844
|
+
* @example
|
|
845
|
+
* ### Encoding to Base32
|
|
846
|
+
*
|
|
847
|
+
* ```ts twoslash
|
|
848
|
+
* import { Base32 } from 'ox'
|
|
849
|
+
*
|
|
850
|
+
* const value = Base32.fromHex('0x00ff00')
|
|
851
|
+
* ```
|
|
852
|
+
*
|
|
853
|
+
* @example
|
|
854
|
+
* ### Decoding Base32
|
|
855
|
+
*
|
|
856
|
+
* ```ts twoslash
|
|
857
|
+
* import { Base32 } from 'ox'
|
|
858
|
+
*
|
|
859
|
+
* const value = Base32.toBytes('qrlsq')
|
|
860
|
+
* ```
|
|
861
|
+
*
|
|
862
|
+
* @category Data
|
|
863
|
+
*/
|
|
864
|
+
export * as Base32 from './core/Base32.js'
|
|
841
865
|
/**
|
|
842
866
|
* Utility functions for working with [Base58](https://digitalbazaar.github.io/base58-spec/) values.
|
|
843
867
|
*
|
|
@@ -892,7 +916,6 @@ export * as Authorization from './core/Authorization.js'
|
|
|
892
916
|
* @category Data
|
|
893
917
|
*/
|
|
894
918
|
export * as Base58 from './core/Base58.js'
|
|
895
|
-
|
|
896
919
|
/**
|
|
897
920
|
* Utility functions for working with [RFC-4648](https://datatracker.ietf.org/doc/html/rfc4648) Base64.
|
|
898
921
|
*
|
|
@@ -946,6 +969,30 @@ export * as Base58 from './core/Base58.js'
|
|
|
946
969
|
* @category Data
|
|
947
970
|
*/
|
|
948
971
|
export * as Base64 from './core/Base64.js'
|
|
972
|
+
/**
|
|
973
|
+
* Utility functions for [BIP-350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki) bech32m encoding and decoding.
|
|
974
|
+
*
|
|
975
|
+
* @example
|
|
976
|
+
* ### Encoding
|
|
977
|
+
*
|
|
978
|
+
* ```ts twoslash
|
|
979
|
+
* import { Bech32m } from 'ox'
|
|
980
|
+
*
|
|
981
|
+
* const encoded = Bech32m.encode('tempo', new Uint8Array(20))
|
|
982
|
+
* ```
|
|
983
|
+
*
|
|
984
|
+
* @example
|
|
985
|
+
* ### Decoding
|
|
986
|
+
*
|
|
987
|
+
* ```ts twoslash
|
|
988
|
+
* import { Bech32m } from 'ox'
|
|
989
|
+
*
|
|
990
|
+
* const { hrp, data } = Bech32m.decode('tempo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq7w9gdx')
|
|
991
|
+
* ```
|
|
992
|
+
*
|
|
993
|
+
* @category Data
|
|
994
|
+
*/
|
|
995
|
+
export * as Bech32m from './core/Bech32m.js'
|
|
949
996
|
|
|
950
997
|
/**
|
|
951
998
|
* Utility functions for working with [EIP-7864](https://eips.ethereum.org/EIPS/eip-7864) Binary State Trees.
|
|
@@ -1330,6 +1377,32 @@ export * as Caches from './core/Caches.js'
|
|
|
1330
1377
|
* @category Data
|
|
1331
1378
|
*/
|
|
1332
1379
|
export * as Cbor from './core/Cbor.js'
|
|
1380
|
+
|
|
1381
|
+
/**
|
|
1382
|
+
* Utility functions for [Bitcoin's CompactSize](https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer) variable-length integer encoding.
|
|
1383
|
+
*
|
|
1384
|
+
* @example
|
|
1385
|
+
* ### Encoding
|
|
1386
|
+
*
|
|
1387
|
+
* ```ts twoslash
|
|
1388
|
+
* import { CompactSize } from 'ox'
|
|
1389
|
+
*
|
|
1390
|
+
* const bytes = CompactSize.toBytes(65535)
|
|
1391
|
+
* ```
|
|
1392
|
+
*
|
|
1393
|
+
* @example
|
|
1394
|
+
* ### Decoding
|
|
1395
|
+
*
|
|
1396
|
+
* ```ts twoslash
|
|
1397
|
+
* import { CompactSize } from 'ox'
|
|
1398
|
+
*
|
|
1399
|
+
* const { value, size } = CompactSize.fromBytes(new Uint8Array([0xfd, 0xff, 0xff]))
|
|
1400
|
+
* ```
|
|
1401
|
+
*
|
|
1402
|
+
* @category Data
|
|
1403
|
+
*/
|
|
1404
|
+
export * as CompactSize from './core/CompactSize.js'
|
|
1405
|
+
|
|
1333
1406
|
/**
|
|
1334
1407
|
* Utility functions for computing Contract Addresses.
|
|
1335
1408
|
*
|