Skip to content

Commit d893b34

Browse files
committed
V4.1.0
- Added support for Sui addresses using Secp256k1, Secp256r1, Ed25519, and MultiSig. - Implemented support for Sui BIP44 coin derivation.
1 parent cf834bb commit d893b34

74 files changed

Lines changed: 2386 additions & 417 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
## 4.1.0
2+
3+
- Added support for Sui addresses using Secp256k1, Secp256r1, Ed25519, and MultiSig.
4+
- Implemented support for Sui BIP44 coin derivation.
5+
6+
17
## 4.0.1
28

3-
- Fix schnorr vrf verification.
9+
- Fix schnorr vrf verification.
410

511
## 4.0.0
612

lib/bip/address/addr_dec_utils.dart

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,12 @@ class AddrDecUtils {
2222
}
2323

2424
/// Validate and remove prefix from an address.
25-
static String validateAndRemovePrefix(
26-
String addr,
27-
String prefix,
28-
) {
25+
static String validateAndRemovePrefix(String addr, String prefix) {
2926
final prefixGot = addr.substring(0, prefix.length);
3027

3128
if (prefix != prefixGot) {
3229
throw AddressConverterException(
33-
'Invalid prefix (expected $prefix, got $prefixGot)',
34-
);
30+
'Invalid prefix (expected $prefix, got $prefixGot)');
3531
}
3632

3733
return addr.substring(prefix.length);

lib/bip/address/aptos_addr.dart

Lines changed: 265 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,294 @@
1-
import 'package:blockchain_utils/bip/address/addr_dec_utils.dart';
21
import 'package:blockchain_utils/bip/address/decoder.dart';
32
import 'package:blockchain_utils/bip/address/encoder.dart';
4-
import 'package:blockchain_utils/bip/coin_conf/constant/coins_conf.dart';
3+
import 'package:blockchain_utils/bip/address/exception/exception.dart';
4+
import 'package:blockchain_utils/bip/bip.dart';
55
import 'package:blockchain_utils/crypto/quick_crypto.dart';
6+
import 'package:blockchain_utils/layout/layout.dart';
67
import 'package:blockchain_utils/utils/utils.dart';
78

89
import 'addr_key_validator.dart';
910

10-
/// Constants related to Aptos blockchain addresses.
1111
class AptosAddrConst {
12-
/// The suffix byte used for single signature addresses.
13-
static final singleSigSuffixByte = List<int>.from([0x00]);
12+
static const int specialAddressLastBytesMax = 16;
13+
14+
/// aptos address bytes length
15+
static const int addressBytesLength = 32;
16+
17+
/// The Ed25519 signing scheme flag
18+
static const int ed25519AddressFlag = 0;
19+
20+
/// The multi-signature Ed25519 scheme flag
21+
static const int multiEd25519AddressFlag = 1;
22+
23+
/// A single key signing scheme flag (likely used for single-key accounts)
24+
static const int signleKeyAddressFlag = 2;
25+
26+
/// A multi-key signing scheme flag where multiple different types of keys are involved
27+
static const int multikeyAddressFlag = 3;
28+
29+
/// Max number of keys in the multi-signature account
30+
static const int maximumPublicKeys = 32;
31+
32+
/// Minimum number of keys required
33+
static const int minPublicKeys = 2;
34+
35+
/// Minimum threshold of required signatures
36+
static const int minthreshold = 1;
37+
38+
static const int shortAddressLength = 63;
39+
40+
/// Bcs layout for encdoing multikey address
41+
static final multiKeyAddressLayout = LayoutConst.struct([
42+
LayoutConst.bcsVector(
43+
LayoutConst.bcsLazyEnum([
44+
LazyVariantModel(
45+
layout: LayoutConst.bcsBytes,
46+
property: EllipticCurveTypes.ed25519.name,
47+
index: 0),
48+
LazyVariantModel(
49+
layout: LayoutConst.bcsBytes,
50+
property: EllipticCurveTypes.secp256k1.name,
51+
index: 1)
52+
], property: "pubKey"),
53+
property: 'publicKeys'),
54+
LayoutConst.u8(property: "requiredSignature")
55+
]);
56+
57+
/// Bcs layout for encoding single key address
58+
static final singleKeyAddressLayout = LayoutConst.bcsLazyEnum([
59+
LazyVariantModel(
60+
layout: LayoutConst.bcsBytes,
61+
property: EllipticCurveTypes.ed25519.name,
62+
index: 0),
63+
LazyVariantModel(
64+
layout: LayoutConst.bcsBytes,
65+
property: EllipticCurveTypes.secp256k1.name,
66+
index: 1)
67+
]);
68+
}
69+
70+
class AptosAddressUtils {
71+
/// check address bytes and convert special address to 32bytes.
72+
static List<int> praseAddressBytes(List<int> bytes) {
73+
final length = bytes.length;
74+
if (length != AptosAddrConst.addressBytesLength) {
75+
throw AddressConverterException("Invalid aptos address bytes length.",
76+
details: {
77+
"expected": AptosAddrConst.addressBytesLength,
78+
"length": bytes.length
79+
});
80+
}
81+
82+
return bytes;
83+
}
84+
85+
/// convert address string to bytes with padding zero for special addresses.
86+
static List<int> addressToBytes(String address) {
87+
address = StringUtils.strip0x(address);
88+
List<int>? bytes = BytesUtils.tryFromHexString(address,
89+
paddingZero: address.length == 1 ||
90+
address.length == AptosAddrConst.shortAddressLength);
91+
if (bytes == null ||
92+
(bytes.length != AptosAddrConst.addressBytesLength &&
93+
bytes.length != 1)) {
94+
throw AddressConverterException("Invalid aptos address.",
95+
details: {"address": address});
96+
}
97+
if (bytes.length == 1) {
98+
final byte = bytes[0];
99+
if (byte >= AptosAddrConst.specialAddressLastBytesMax) {
100+
throw AddressConverterException("Invalid special address.",
101+
details: {"address": BytesUtils.toHexString(bytes)});
102+
}
103+
bytes = List.filled(AptosAddrConst.addressBytesLength, 0);
104+
bytes.last = byte;
105+
}
106+
return praseAddressBytes(bytes);
107+
}
108+
109+
/// convert bytes (ED25519, Secp256k1 or multisig key data) to address with specify scheme
110+
static List<int> hashKeyBytes(
111+
{required List<int> bytes, required int scheme}) {
112+
bytes = [...bytes, scheme];
113+
bytes = QuickCrypto.sha3256Hash(bytes);
114+
return bytes;
115+
}
116+
117+
/// encode ED25519 public key to address
118+
static List<int> encodeEd25519Key(List<int> bytes) {
119+
try {
120+
final key = AddrKeyValidator.validateAndGetEd25519Key(bytes)
121+
.compressed
122+
.sublist(1);
123+
return hashKeyBytes(
124+
bytes: key, scheme: AptosAddrConst.ed25519AddressFlag);
125+
} catch (e) {
126+
throw AddressConverterException(
127+
"Failed to generate Aptos address: Invalid Ed25519 public key provided.");
128+
}
129+
}
130+
131+
/// encode public key to SignleKey address
132+
static List<int> encodeSingleKey(IPublicKey publicKey) {
133+
try {
134+
final pubkeyBytes = switch (publicKey.curve) {
135+
EllipticCurveTypes.secp256k1 => publicKey.uncompressed,
136+
EllipticCurveTypes.ed25519 => publicKey.compressed.sublist(1),
137+
_ => throw AddressConverterException(
138+
"Unsupported public key: Aptos SingleKey can only be generated from secp256k1 or ed25519 public keys.")
139+
};
140+
final structLayout = {publicKey.curve.name: pubkeyBytes};
141+
final encode =
142+
AptosAddrConst.singleKeyAddressLayout.serialize(structLayout);
143+
return hashKeyBytes(
144+
bytes: encode, scheme: AptosAddrConst.signleKeyAddressFlag);
145+
} on AddressConverterException {
146+
rethrow;
147+
} catch (e) {
148+
throw AddressConverterException("Invalid aptos MultiKey address bytes.",
149+
details: {"error": e.toString()});
150+
}
151+
}
152+
153+
/// encode Multi ED25519 public keys to MultiEd25519 address
154+
static List<int> encodeMultiEd25519Key(
155+
List<Ed25519PublicKey> publicKeys, int threshold) {
156+
try {
157+
if (publicKeys.length < AptosAddrConst.minPublicKeys ||
158+
publicKeys.length > AptosAddrConst.maximumPublicKeys) {
159+
throw AddressConverterException(
160+
"The number of public keys provided is invalid. It must be between ${AptosAddrConst.minPublicKeys} and ${AptosAddrConst.maximumPublicKeys}.");
161+
}
162+
if (threshold < AptosAddrConst.minthreshold ||
163+
threshold > publicKeys.length) {
164+
throw AddressConverterException(
165+
"Invalid threshold. The threshold must be between ${AptosAddrConst.minthreshold} and the number of provided public keys (${publicKeys.length}).");
166+
}
167+
final keyBytes = [
168+
...publicKeys.map((e) => e.compressed.sublist(1)).expand((e) => e),
169+
threshold
170+
];
171+
return hashKeyBytes(
172+
bytes: keyBytes, scheme: AptosAddrConst.multiEd25519AddressFlag);
173+
} on AddressConverterException {
174+
rethrow;
175+
} catch (e) {
176+
throw AddressConverterException(
177+
"Invalid aptos MultiEd25519 address bytes.",
178+
details: {"error": e.toString()});
179+
}
180+
}
181+
182+
/// encode Multi Public keys to MultiKey address
183+
static List<int> encodeMultiKey(
184+
List<IPublicKey> publicKeys, int requiredSignature) {
185+
try {
186+
final pubkeyLayoutStruct = publicKeys.map((e) {
187+
return switch (e.curve) {
188+
EllipticCurveTypes.secp256k1 => {e.curve.name: e.uncompressed},
189+
EllipticCurveTypes.ed25519 => {e.curve.name: e.compressed.sublist(1)},
190+
_ => throw AddressConverterException(
191+
"Unsupported public key: Aptos Multikey address can only be generated from secp256k1 or ed25519 public keys.")
192+
};
193+
}).toList();
194+
if (publicKeys.length < AptosAddrConst.minPublicKeys ||
195+
publicKeys.length > AptosAddrConst.maximumPublicKeys) {
196+
throw AddressConverterException(
197+
"The number of public keys provided is invalid. It must be between ${AptosAddrConst.minPublicKeys} and ${AptosAddrConst.maximumPublicKeys}.");
198+
}
199+
if (requiredSignature < AptosAddrConst.minthreshold ||
200+
requiredSignature > publicKeys.length) {
201+
throw AddressConverterException(
202+
"Invalid threshold. The threshold must be between ${AptosAddrConst.minthreshold} and the number of provided public keys (${publicKeys.length}).");
203+
}
204+
final layoutStruct = {
205+
"requiredSignature": requiredSignature,
206+
"publicKeys": pubkeyLayoutStruct
207+
};
208+
final encode =
209+
AptosAddrConst.multiKeyAddressLayout.serialize(layoutStruct);
210+
return hashKeyBytes(
211+
bytes: encode, scheme: AptosAddrConst.multikeyAddressFlag);
212+
} on AddressConverterException {
213+
rethrow;
214+
} catch (e) {
215+
throw AddressConverterException("Invalid aptos MultiKey address bytes.",
216+
details: {"error": e.toString()});
217+
}
218+
}
14219
}
15220

16221
/// Implementation of the [BlockchainAddressDecoder] for Aptos address.
17222
class AptosAddrDecoder implements BlockchainAddressDecoder {
18-
/// Decode an Aptos blockchain address from its string representation.
19-
///
20-
/// This method decodes the provided `addr` string by removing the prefix,
21-
/// ensuring the address length is valid, and parsing the hexadecimal string
22-
/// to obtain the address bytes.
23-
///
24-
/// Parameters:
25-
/// - `addr`: The Aptos blockchain address in string format.
26-
/// - `kwargs` (optional): Additional arguments, though none are used in this method.
27-
///
28-
/// Returns:
29-
/// - A List containing the decoded address bytes.
30-
///
31-
/// Throws:
32-
/// - ArgumentException if the provided string is not a valid hex encoding.
33-
///
34223
/// This method is used to convert an Aptos blockchain address from its string
35224
/// representation to its binary format for further processing.
36225
@override
37226
List<int> decodeAddr(String addr, [Map<String, dynamic> kwargs = const {}]) {
38-
String addrNoPrefix = AddrDecUtils.validateAndRemovePrefix(
39-
addr,
40-
CoinsConf.aptos.params.addrPrefix!,
41-
);
42-
addrNoPrefix = addrNoPrefix.padLeft(QuickCrypto.sha3256DigestSize * 2, "0");
43-
AddrDecUtils.validateLength(
44-
addrNoPrefix, QuickCrypto.sha3256DigestSize * 2);
45-
46-
return BytesUtils.fromHexString(addrNoPrefix);
227+
return AptosAddressUtils.addressToBytes(addr);
228+
}
229+
}
230+
231+
class AptosSingleKeyEd25519AddrEncoder implements BlockchainAddressEncoder {
232+
/// This method is used to create an Aptos `SingleKey` address from `ED25519` public key.
233+
@override
234+
String encodeKey(List<int> pubKey, [Map<String, dynamic> kwargs = const {}]) {
235+
final publicKey = AddrKeyValidator.validateAndGetEd25519Key(pubKey);
236+
final addressBytes = AptosAddressUtils.encodeSingleKey(publicKey);
237+
238+
/// Concatenate the address prefix and the hash bytes, removing leading zeros
239+
return BytesUtils.toHexString(addressBytes,
240+
prefix: CoinsConf.aptos.params.addrPrefix);
241+
}
242+
}
243+
244+
class AptosSingleKeySecp256k1AddrEncoder implements BlockchainAddressEncoder {
245+
/// This method is used to create an Aptos `SingleKey` address from `Sec256k1` public key.
246+
@override
247+
String encodeKey(List<int> pubKey, [Map<String, dynamic> kwargs = const {}]) {
248+
final publicKey = AddrKeyValidator.validateAndGetSecp256k1Key(pubKey);
249+
final addressBytes = AptosAddressUtils.encodeSingleKey(publicKey);
250+
251+
/// Concatenate the address prefix and the hash bytes, removing leading zeros
252+
return BytesUtils.toHexString(addressBytes,
253+
prefix: CoinsConf.aptos.params.addrPrefix);
47254
}
48255
}
49256

50257
/// Implementation of the [BlockchainAddressEncoder] for Aptos address.
51258
class AptosAddrEncoder implements BlockchainAddressEncoder {
52-
/// Encode an Aptos blockchain address from a public key.
53-
///
54-
/// This method encodes an Aptos blockchain address from the provided `pubKey`
55-
/// by performing the following steps:
56-
/// 1. Validate the public key and extract its raw compressed bytes.
57-
/// 2. Prepare the payload by appending a single-sig suffix byte.
58-
/// 3. Compute the SHA-3-256 hash of the payload.
59-
/// 4. Concatenate the address prefix and the hash bytes.
60-
/// 5. Remove leading zeros from the resulting hex-encoded address.
61-
///
62-
/// Parameters:
63-
/// - `pubKey`: The public key for which to generate the address.
64-
/// - `kwargs` (optional): Additional arguments, though none are used in this method.
65-
///
66-
/// Returns:
67-
/// - A hex-encoded string representing the generated Aptos blockchain address.
68-
///
69-
/// This method is used to create an Aptos blockchain address from a public key.
70-
/// The resulting address is a hexadecimal string without leading zeros.
259+
/// This method is used to create an Aptos `ED25519` address from public key.
71260
@override
72261
String encodeKey(List<int> pubKey, [Map<String, dynamic> kwargs = const {}]) {
73-
final pubKeyBytes = pubKey;
74-
final pubKeyObj = AddrKeyValidator.validateAndGetEd25519Key(pubKeyBytes);
262+
final addressBytes = AptosAddressUtils.encodeEd25519Key(pubKey);
75263

76-
/// Prepare the payload by appending a single-sig suffix byte
77-
final payloadBytes = List<int>.from([
78-
...List<int>.from(pubKeyObj.compressed.sublist(1)),
79-
...AptosAddrConst.singleSigSuffixByte
80-
]);
264+
return BytesUtils.toHexString(addressBytes,
265+
prefix: CoinsConf.aptos.params.addrPrefix);
266+
}
81267

82-
/// Compute the SHA-3-256 hash of the payload
83-
final keyHashBytes = QuickCrypto.sha3256Hash(payloadBytes);
268+
/// This method is used to create an Aptos `SingleKey` address from (ED25519, Sec256k1) public key.
269+
String encodeSingleKey(IPublicKey pubKey) {
270+
final addressBytes = AptosAddressUtils.encodeSingleKey(pubKey);
84271

85272
/// Concatenate the address prefix and the hash bytes, removing leading zeros
86-
return CoinsConf.aptos.params.addrPrefix! +
87-
BytesUtils.toHexString(keyHashBytes).replaceFirst(RegExp('^0+'), '');
273+
return BytesUtils.toHexString(addressBytes,
274+
prefix: CoinsConf.aptos.params.addrPrefix);
275+
}
276+
277+
/// This method is used to create an Aptos `MultiEd25519` address from ED25519 public keys.
278+
String encodeMultiEd25519Key(
279+
{required List<Ed25519PublicKey> publicKeys, required int threshold}) {
280+
final addressBytes =
281+
AptosAddressUtils.encodeMultiEd25519Key(publicKeys, threshold);
282+
return BytesUtils.toHexString(addressBytes,
283+
prefix: CoinsConf.aptos.params.addrPrefix);
284+
}
285+
286+
/// This method is used to create an Aptos `MultiKey` address from (ED25519 or Secp256k1) public keys.
287+
String encodeMultiKey(
288+
{required List<IPublicKey> publicKeys, required int requiredSignature}) {
289+
final addressBytes =
290+
AptosAddressUtils.encodeMultiKey(publicKeys, requiredSignature);
291+
return BytesUtils.toHexString(addressBytes,
292+
prefix: CoinsConf.aptos.params.addrPrefix);
88293
}
89294
}

lib/bip/address/decoders.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ library;
44
/// Export statement for Ada Byron address decoder.
55
export 'ada/ada_byron_addr.dart' show AdaByronAddrDecoder;
66

7+
/// Export statement for Sui address decoder.
8+
export 'sui.dart'
9+
show SuiAddrEncoder, SuiAddressUtils, SuiPublicKeyAndWeight, SuiAddrConst;
10+
711
/// Export statements for Ada Shelley address decoders.
812
export 'ada/ada.dart'
913
show

0 commit comments

Comments
 (0)