-
Notifications
You must be signed in to change notification settings - Fork 208
/
Copy pathaddress.ts
192 lines (176 loc) · 6 KB
/
address.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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
import { Buffer } from 'buffer';
import base32 from 'hi-base32';
import * as nacl from '../nacl/naclWrappers';
import * as utils from '../utils/utils';
import { encodeUint64 } from './uint64';
import { Address } from '../types/address';
import { MultisigMetadata } from '../types/multisig';
const ALGORAND_ADDRESS_BYTE_LENGTH = 36;
const ALGORAND_CHECKSUM_BYTE_LENGTH = 4;
const ALGORAND_ADDRESS_LENGTH = 58;
export const ALGORAND_ZERO_ADDRESS_STRING =
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ';
// Convert "MultisigAddr" UTF-8 to byte array
const MULTISIG_PREIMG2ADDR_PREFIX = new Uint8Array([
77,
117,
108,
116,
105,
115,
105,
103,
65,
100,
100,
114,
]);
const APP_ID_PREFIX = Buffer.from('appID');
export const MALFORMED_ADDRESS_ERROR_MSG = 'address seems to be malformed';
export const CHECKSUM_ADDRESS_ERROR_MSG = 'wrong checksum for address';
export const INVALID_MSIG_VERSION_ERROR_MSG = 'invalid multisig version';
export const INVALID_MSIG_THRESHOLD_ERROR_MSG = 'bad multisig threshold';
export const INVALID_MSIG_PK_ERROR_MSG =
'bad multisig public key - wrong length';
export const UNEXPECTED_PK_LEN_ERROR_MSG =
'nacl public key length is not 32 bytes';
/**
* decodeAddress takes an Algorand address in string form and decodes it into a Uint8Array.
* @param address - an Algorand address with checksum.
* @returns the decoded form of the address's public key and checksum
*/
export function decodeAddress(address: string): Address {
if (typeof address !== 'string' || address.length !== ALGORAND_ADDRESS_LENGTH)
throw new Error(MALFORMED_ADDRESS_ERROR_MSG);
// try to decode
const decoded = base32.decode.asBytes(address.toString());
// Sanity check
if (decoded.length !== ALGORAND_ADDRESS_BYTE_LENGTH)
throw new Error(MALFORMED_ADDRESS_ERROR_MSG);
// Find publickey and checksum
const pk = new Uint8Array(
decoded.slice(
0,
ALGORAND_ADDRESS_BYTE_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH
)
);
const cs = new Uint8Array(
decoded.slice(nacl.PUBLIC_KEY_LENGTH, ALGORAND_ADDRESS_BYTE_LENGTH)
);
// Compute checksum
const checksum = nacl
.genericHash(pk)
.slice(
nacl.HASH_BYTES_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH,
nacl.HASH_BYTES_LENGTH
);
// Check if the checksum and the address are equal
if (!utils.arrayEqual(checksum, cs))
throw new Error(CHECKSUM_ADDRESS_ERROR_MSG);
return { publicKey: pk, checksum: cs };
}
/**
* isValidAddress checks if a string is a valid Algorand address.
* @param address - an Algorand address with checksum.
* @returns true if valid, false otherwise
*/
export function isValidAddress(address: string) {
// Try to decode
try {
decodeAddress(address);
} catch (e) {
return false;
}
return true;
}
/**
* encodeAddress takes an Algorand address as a Uint8Array and encodes it into a string with checksum.
* @param address - a raw Algorand address
* @returns the address and checksum encoded as a string.
*/
export function encodeAddress(address: Uint8Array) {
// compute checksum
const checksum = nacl
.genericHash(address)
.slice(
nacl.PUBLIC_KEY_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH,
nacl.PUBLIC_KEY_LENGTH
);
const addr = base32.encode(utils.concatArrays(address, checksum));
return addr.toString().slice(0, ALGORAND_ADDRESS_LENGTH); // removing the extra '===='
}
/**
* fromMultisigPreImg takes multisig parameters and returns a 32 byte typed array public key,
* representing an address that identifies the "exact group, version, and public keys" that are required for signing.
* Hash("MultisigAddr" || version uint8 || threshold uint8 || PK1 || PK2 || ...)
* Encoding this output yields a human readable address.
* @param version - multisig version
* @param threshold - multisig threshold
* @param pks - array of typed array public keys
*/
export function fromMultisigPreImg({
version,
threshold,
pks,
}: Omit<MultisigMetadata, 'addrs'> & {
pks: Uint8Array[];
}) {
if (version !== 1 || version > 255 || version < 0) {
// ^ a tad redundant, but in case in the future version != 1, still check for uint8
throw new Error(INVALID_MSIG_VERSION_ERROR_MSG);
}
if (
threshold === 0 ||
pks.length === 0 ||
threshold > pks.length ||
threshold > 255
) {
throw new Error(INVALID_MSIG_THRESHOLD_ERROR_MSG);
}
const pkLen = ALGORAND_ADDRESS_BYTE_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH;
if (pkLen !== nacl.PUBLIC_KEY_LENGTH) {
throw new Error(UNEXPECTED_PK_LEN_ERROR_MSG);
}
const merged = new Uint8Array(
MULTISIG_PREIMG2ADDR_PREFIX.length + 2 + pkLen * pks.length
);
merged.set(MULTISIG_PREIMG2ADDR_PREFIX, 0);
merged.set([version], MULTISIG_PREIMG2ADDR_PREFIX.length);
merged.set([threshold], MULTISIG_PREIMG2ADDR_PREFIX.length + 1);
for (let i = 0; i < pks.length; i++) {
if (pks[i].length !== pkLen) {
throw new Error(INVALID_MSIG_PK_ERROR_MSG);
}
merged.set(pks[i], MULTISIG_PREIMG2ADDR_PREFIX.length + 2 + i * pkLen);
}
return new Uint8Array(nacl.genericHash(merged));
}
/**
* fromMultisigPreImgAddrs takes multisig parameters and returns a human readable Algorand address.
* This is equivalent to fromMultisigPreImg, but interfaces with encoded addresses.
* @param version - multisig version
* @param threshold - multisig threshold
* @param addrs - array of encoded addresses
*/
export function fromMultisigPreImgAddrs({
version,
threshold,
addrs,
}: {
version: number;
threshold: number;
addrs: string[];
}) {
const pks = addrs.map((addr) => decodeAddress(addr).publicKey);
return encodeAddress(fromMultisigPreImg({ version, threshold, pks }));
}
/**
* Get the escrow address of an application.
* @param appID - The ID of the application.
* @returns The address corresponding to that application's escrow account.
*/
export function getApplicationAddress(appID: number | bigint): string {
const toBeSigned = utils.concatArrays(APP_ID_PREFIX, encodeUint64(appID));
const hash = nacl.genericHash(toBeSigned);
return encodeAddress(new Uint8Array(hash));
}