1
1
import 'dart:typed_data' ;
2
2
3
- import 'package:agent_dart/agent/types.dart' ;
4
3
import 'package:agent_dart/utils/extension.dart' ;
5
4
6
5
import '../agent/errors.dart' ;
@@ -15,8 +14,14 @@ const _suffixAnonymous = 4;
15
14
const _maxLengthInBytes = 29 ;
16
15
const _typeOpaque = 1 ;
17
16
17
+ final _emptySubAccount = Uint8List (32 );
18
+
18
19
class Principal {
19
- const Principal (this ._arr);
20
+ const Principal (
21
+ this ._principal, {
22
+ Uint8List ? subAccount,
23
+ }) : assert (subAccount == null || subAccount.length == 32 ),
24
+ _subAccount = subAccount;
20
25
21
26
factory Principal .selfAuthenticating (Uint8List publicKey) {
22
27
final sha = sha224Hash (publicKey.buffer);
@@ -28,18 +33,18 @@ class Principal {
28
33
return Principal (Uint8List .fromList ([_suffixAnonymous]));
29
34
}
30
35
31
- factory Principal .from (dynamic other) {
36
+ factory Principal .from (Object ? other) {
32
37
if (other is String ) {
33
38
return Principal .fromText (other);
34
39
} else if (other is Map <String , dynamic > && other['_isPrincipal' ] == true ) {
35
- return Principal (other['_arr' ]);
40
+ return Principal (other['_arr' ], subAccount : other[ '_subAccount' ] );
36
41
} else if (other is Principal ) {
37
- return Principal (other._arr );
42
+ return Principal (other._principal, subAccount : other.subAccount );
38
43
}
39
44
throw UnreachableError ();
40
45
}
41
46
42
- factory Principal .create (int uSize, Uint8List data) {
47
+ factory Principal .create (int uSize, Uint8List data, Uint8List ? subAccount ) {
43
48
if (uSize > data.length) {
44
49
throw RangeError .range (
45
50
uSize,
@@ -49,56 +54,112 @@ class Principal {
49
54
'Size must within the data length' ,
50
55
);
51
56
}
52
- return Principal . fromBlob (data.sublist (0 , uSize));
57
+ return Principal (data.sublist (0 , uSize), subAccount : subAccount );
53
58
}
54
59
55
- factory Principal .fromHex (String hex) {
60
+ factory Principal .fromHex (String hex, { String ? subAccountHex} ) {
56
61
if (hex.isEmpty) {
57
62
return Principal (Uint8List (0 ));
58
63
}
59
- return Principal (hex.toU8a ());
64
+ if (subAccountHex == null || subAccountHex.isEmpty) {
65
+ subAccountHex = null ;
66
+ } else if (subAccountHex.startsWith ('0' )) {
67
+ throw ArgumentError .value (
68
+ subAccountHex,
69
+ 'subAccountHex' ,
70
+ 'The representation is not canonical: '
71
+ 'leading zeros are not allowed in subaccounts.' ,
72
+ );
73
+ }
74
+ return Principal (
75
+ hex.toU8a (),
76
+ subAccount: subAccountHex? .padLeft (64 , '0' ).toU8a (),
77
+ );
60
78
}
61
79
62
80
factory Principal .fromText (String text) {
63
- final canisterIdNoDash = text.toLowerCase ().replaceAll ('-' , '' );
81
+ if (text.endsWith ('.' )) {
82
+ throw ArgumentError (
83
+ 'The representation is not canonical: '
84
+ 'default subaccount should be omitted.' ,
85
+ );
86
+ }
87
+ final paths = text.split ('.' );
88
+ final String ? subAccountHex;
89
+ if (paths.length > 1 ) {
90
+ subAccountHex = paths.last;
91
+ } else {
92
+ subAccountHex = null ;
93
+ }
94
+ if (subAccountHex != null && subAccountHex.startsWith ('0' )) {
95
+ throw ArgumentError .value (
96
+ subAccountHex,
97
+ 'subAccount' ,
98
+ 'The representation is not canonical: '
99
+ 'leading zeros are not allowed in subaccounts.' ,
100
+ );
101
+ }
102
+ String prePrincipal = paths.first;
103
+ // Removes the checksum if sub-account is valid.
104
+ if (subAccountHex != null ) {
105
+ final list = prePrincipal.split ('-' );
106
+ final checksum = list.removeLast ();
107
+ // Checksum is 7 digits.
108
+ if (checksum.length != 7 ) {
109
+ throw ArgumentError .value (
110
+ prePrincipal,
111
+ 'principal' ,
112
+ 'Missing checksum' ,
113
+ );
114
+ }
115
+ prePrincipal = list.join ('-' );
116
+ }
117
+ final canisterIdNoDash = prePrincipal.toLowerCase ().replaceAll ('-' , '' );
64
118
Uint8List arr = base32Decode (canisterIdNoDash);
65
119
arr = arr.sublist (4 , arr.length);
66
- final principal = Principal (arr);
120
+ final subAccount = subAccountHex? .padLeft (64 , '0' ).toU8a ();
121
+ final principal = Principal (arr, subAccount: subAccount);
67
122
if (principal.toText () != text) {
68
123
throw ArgumentError .value (
69
124
text,
70
- 'Principal ' ,
71
- 'Principal expected to be ${principal .toText ()} but got' ,
125
+ 'principal ' ,
126
+ 'The principal is expected to be ${principal .toText ()} but got' ,
72
127
);
73
128
}
74
129
return principal;
75
130
}
76
131
77
- factory Principal .fromBlob (BinaryBlob arr) {
78
- return Principal .fromUint8Array (arr);
79
- }
132
+ final Uint8List _principal;
133
+ final Uint8List ? _subAccount;
80
134
81
- factory Principal .fromUint8Array (Uint8List arr) {
82
- return Principal (arr);
135
+ Uint8List ? get subAccount {
136
+ if (_subAccount case final v when v == null || v.eq (_emptySubAccount)) {
137
+ return null ;
138
+ }
139
+ return _subAccount;
83
140
}
84
141
85
- final Uint8List _arr;
142
+ Principal newSubAccount (Uint8List ? subAccount) {
143
+ if (subAccount == null || subAccount.eq (_emptySubAccount)) {
144
+ return this ;
145
+ }
146
+ if (this .subAccount == null || ! this .subAccount! .eq (subAccount)) {
147
+ return Principal (_principal, subAccount: subAccount);
148
+ }
149
+ return this ;
150
+ }
86
151
87
152
bool isAnonymous () {
88
- return _arr .lengthInBytes == 1 && _arr [0 ] == _suffixAnonymous;
153
+ return _principal .lengthInBytes == 1 && _principal [0 ] == _suffixAnonymous;
89
154
}
90
155
91
- Uint8List toUint8List () => _arr;
92
-
93
- Uint8List toBlob () => toUint8List ();
156
+ Uint8List toUint8List () => _principal;
94
157
95
- String toHex () => _toHexString (_arr ).toUpperCase ();
158
+ String toHex () => _toHexString (_principal ).toUpperCase ();
96
159
97
160
String toText () {
98
- final checksumArrayBuf = ByteData (4 );
99
- checksumArrayBuf.setUint32 (0 , getCrc32 (_arr.buffer));
100
- final checksum = checksumArrayBuf.buffer.asUint8List ();
101
- final bytes = Uint8List .fromList (_arr);
161
+ final checksum = _getChecksum (_principal.buffer);
162
+ final bytes = Uint8List .fromList (_principal);
102
163
final array = Uint8List .fromList ([...checksum, ...bytes]);
103
164
final result = base32Encode (array);
104
165
final reg = RegExp (r'.{1,5}' );
@@ -107,21 +168,34 @@ class Principal {
107
168
// This should only happen if there's no character, which is unreachable.
108
169
throw StateError ('No characters found.' );
109
170
}
110
- return matches.map ((e) => e.group (0 )).join ('-' );
171
+ final buffer = StringBuffer (matches.map ((e) => e.group (0 )).join ('-' ));
172
+ if (_subAccount case final subAccount?
173
+ when ! subAccount.eq (_emptySubAccount)) {
174
+ final subAccountHex = subAccount.toHex ();
175
+ int nonZeroStart = 0 ;
176
+ while (nonZeroStart < subAccountHex.length) {
177
+ if (subAccountHex[nonZeroStart] != '0' ) {
178
+ break ;
179
+ }
180
+ nonZeroStart++ ;
181
+ }
182
+ if (nonZeroStart != subAccountHex.length) {
183
+ final checksum = base32Encode (
184
+ _getChecksum (Uint8List .fromList (_principal + subAccount).buffer),
185
+ );
186
+ buffer.write ('-$checksum ' );
187
+ buffer.write ('.' );
188
+ buffer.write (subAccountHex.replaceRange (0 , nonZeroStart, '' ));
189
+ }
190
+ }
191
+ return buffer.toString ();
111
192
}
112
193
113
- Uint8List toAccountId ({Uint8List ? subAccount}) {
114
- if (subAccount != null && subAccount.length != 32 ) {
115
- throw ArgumentError .value (
116
- subAccount,
117
- 'subAccount' ,
118
- 'Sub-account address must be 32-bytes length' ,
119
- );
120
- }
194
+ Uint8List toAccountId () {
121
195
final hash = SHA224 ();
122
196
hash.update ('\x 0Aaccount-id' .plainToU8a ());
123
- hash.update (toBlob ());
124
- hash.update (subAccount ?? Uint8List ( 32 ) );
197
+ hash.update (toUint8List ());
198
+ hash.update (subAccount ?? _emptySubAccount );
125
199
final data = hash.digest ();
126
200
final view = ByteData (4 );
127
201
view.setUint32 (0 , getCrc32 (data.buffer));
@@ -137,14 +211,18 @@ class Principal {
137
211
138
212
@override
139
213
bool operator == (Object other) =>
140
- identical (this , other) || other is Principal && _arr.eq (other._arr);
214
+ identical (this , other) ||
215
+ other is Principal &&
216
+ _principal.eq (other._principal) &&
217
+ (_subAccount? .eq (other._subAccount ?? _emptySubAccount) ??
218
+ _subAccount == null && other._subAccount == null );
141
219
142
220
@override
143
- int get hashCode => _arr.hashCode ;
221
+ int get hashCode => Object . hash (_principal, subAccount) ;
144
222
}
145
223
146
224
class CanisterId extends Principal {
147
- CanisterId (Principal pid) : super (pid.toBlob ());
225
+ CanisterId (Principal pid) : super (pid.toUint8List ());
148
226
149
227
factory CanisterId .fromU64 (int val) {
150
228
// It is important to use big endian here to ensure that the generated
@@ -164,11 +242,18 @@ class CanisterId extends Principal {
164
242
165
243
data[blobLength] = _typeOpaque;
166
244
return CanisterId (
167
- Principal .create (blobLength + 1 , Uint8List .fromList (data)),
245
+ Principal .create (blobLength + 1 , Uint8List .fromList (data), null ),
168
246
);
169
247
}
170
248
}
171
249
250
+ Uint8List _getChecksum (ByteBuffer buffer) {
251
+ final checksumArrayBuf = ByteData (4 );
252
+ checksumArrayBuf.setUint32 (0 , getCrc32 (buffer));
253
+ final checksum = checksumArrayBuf.buffer.asUint8List ();
254
+ return checksum;
255
+ }
256
+
172
257
String _toHexString (Uint8List bytes) {
173
258
return bytes.map ((e) => e.toRadixString (16 ).padLeft (2 , '0' )).join ();
174
259
}
0 commit comments