forked from stacks-network/c32check
-
Notifications
You must be signed in to change notification settings - Fork 0
/
encoding.ts
154 lines (132 loc) · 4.18 KB
/
encoding.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import { hexToBytes } from '@noble/hashes/utils';
export const c32 = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
const hex = '0123456789abcdef';
/**
* Encode a hex string as a c32 string. Note that the hex string is assumed
* to be big-endian (and the resulting c32 string will be as well).
* @param {string} inputHex - the input to encode
* @param {number} minLength - the minimum length of the c32 string
* @returns {string} the c32check-encoded representation of the data, as a string
*/
export function c32encode(inputHex: string, minLength?: number): string {
// must be hex
if (!inputHex.match(/^[0-9a-fA-F]*$/)) {
throw new Error('Not a hex-encoded string');
}
if (inputHex.length % 2 !== 0) {
inputHex = `0${inputHex}`;
}
inputHex = inputHex.toLowerCase();
let res = [];
let carry = 0;
for (let i = inputHex.length - 1; i >= 0; i--) {
if (carry < 4) {
const currentCode = hex.indexOf(inputHex[i]) >> carry;
let nextCode = 0;
if (i !== 0) {
nextCode = hex.indexOf(inputHex[i - 1]);
}
// carry = 0, nextBits is 1, carry = 1, nextBits is 2
const nextBits = 1 + carry;
const nextLowBits = nextCode % (1 << nextBits) << (5 - nextBits);
const curC32Digit = c32[currentCode + nextLowBits];
carry = nextBits;
res.unshift(curC32Digit);
} else {
carry = 0;
}
}
let C32leadingZeros = 0;
for (let i = 0; i < res.length; i++) {
if (res[i] !== '0') {
break;
} else {
C32leadingZeros++;
}
}
res = res.slice(C32leadingZeros);
const zeroPrefix = new TextDecoder().decode(hexToBytes(inputHex)).match(/^\u0000*/);
const numLeadingZeroBytesInHex = zeroPrefix ? zeroPrefix[0].length : 0;
for (let i = 0; i < numLeadingZeroBytesInHex; i++) {
res.unshift(c32[0]);
}
if (minLength) {
const count = minLength - res.length;
for (let i = 0; i < count; i++) {
res.unshift(c32[0]);
}
}
return res.join('');
}
/*
* Normalize a c32 string
* @param {string} c32input - the c32-encoded input string
* @returns {string} the canonical representation of the c32 input string
*/
export function c32normalize(c32input: string): string {
// must be upper-case
// replace all O's with 0's
// replace all I's and L's with 1's
return c32input.toUpperCase().replace(/O/g, '0').replace(/L|I/g, '1');
}
/*
* Decode a c32 string back into a hex string. Note that the c32 input
* string is assumed to be big-endian (and the resulting hex string will
* be as well).
* @param {string} c32input - the c32-encoded input to decode
* @param {number} minLength - the minimum length of the output hex string (in bytes)
* @returns {string} the hex-encoded representation of the data, as a string
*/
export function c32decode(c32input: string, minLength?: number): string {
c32input = c32normalize(c32input);
// must result in a c32 string
if (!c32input.match(`^[${c32}]*$`)) {
throw new Error('Not a c32-encoded string');
}
const zeroPrefix = c32input.match(`^${c32[0]}*`);
const numLeadingZeroBytes = zeroPrefix ? zeroPrefix[0].length : 0;
let res = [];
let carry = 0;
let carryBits = 0;
for (let i = c32input.length - 1; i >= 0; i--) {
if (carryBits === 4) {
res.unshift(hex[carry]);
carryBits = 0;
carry = 0;
}
const currentCode = c32.indexOf(c32input[i]) << carryBits;
const currentValue = currentCode + carry;
const currentHexDigit = hex[currentValue % 16];
carryBits += 1;
carry = currentValue >> 4;
if (carry > 1 << carryBits) {
throw new Error('Panic error in decoding.');
}
res.unshift(currentHexDigit);
}
// one last carry
res.unshift(hex[carry]);
if (res.length % 2 === 1) {
res.unshift('0');
}
let hexLeadingZeros = 0;
for (let i = 0; i < res.length; i++) {
if (res[i] !== '0') {
break;
} else {
hexLeadingZeros++;
}
}
res = res.slice(hexLeadingZeros - (hexLeadingZeros % 2));
let hexStr = res.join('');
for (let i = 0; i < numLeadingZeroBytes; i++) {
hexStr = `00${hexStr}`;
}
if (minLength) {
const count = minLength * 2 - hexStr.length;
for (let i = 0; i < count; i += 2) {
hexStr = `00${hexStr}`;
}
}
return hexStr;
}