-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Added support for Sui addresses using Secp256k1, Secp256r1, Ed25519, and MultiSig. - Implemented support for Sui BIP44 coin derivation.
- Loading branch information
1 parent
cf834bb
commit d893b34
Showing
74 changed files
with
2,386 additions
and
417 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,89 +1,294 @@ | ||
import 'package:blockchain_utils/bip/address/addr_dec_utils.dart'; | ||
import 'package:blockchain_utils/bip/address/decoder.dart'; | ||
import 'package:blockchain_utils/bip/address/encoder.dart'; | ||
import 'package:blockchain_utils/bip/coin_conf/constant/coins_conf.dart'; | ||
import 'package:blockchain_utils/bip/address/exception/exception.dart'; | ||
import 'package:blockchain_utils/bip/bip.dart'; | ||
import 'package:blockchain_utils/crypto/quick_crypto.dart'; | ||
import 'package:blockchain_utils/layout/layout.dart'; | ||
import 'package:blockchain_utils/utils/utils.dart'; | ||
|
||
import 'addr_key_validator.dart'; | ||
|
||
/// Constants related to Aptos blockchain addresses. | ||
class AptosAddrConst { | ||
/// The suffix byte used for single signature addresses. | ||
static final singleSigSuffixByte = List<int>.from([0x00]); | ||
static const int specialAddressLastBytesMax = 16; | ||
|
||
/// aptos address bytes length | ||
static const int addressBytesLength = 32; | ||
|
||
/// The Ed25519 signing scheme flag | ||
static const int ed25519AddressFlag = 0; | ||
|
||
/// The multi-signature Ed25519 scheme flag | ||
static const int multiEd25519AddressFlag = 1; | ||
|
||
/// A single key signing scheme flag (likely used for single-key accounts) | ||
static const int signleKeyAddressFlag = 2; | ||
|
||
/// A multi-key signing scheme flag where multiple different types of keys are involved | ||
static const int multikeyAddressFlag = 3; | ||
|
||
/// Max number of keys in the multi-signature account | ||
static const int maximumPublicKeys = 32; | ||
|
||
/// Minimum number of keys required | ||
static const int minPublicKeys = 2; | ||
|
||
/// Minimum threshold of required signatures | ||
static const int minthreshold = 1; | ||
|
||
static const int shortAddressLength = 63; | ||
|
||
/// Bcs layout for encdoing multikey address | ||
static final multiKeyAddressLayout = LayoutConst.struct([ | ||
LayoutConst.bcsVector( | ||
LayoutConst.bcsLazyEnum([ | ||
LazyVariantModel( | ||
layout: LayoutConst.bcsBytes, | ||
property: EllipticCurveTypes.ed25519.name, | ||
index: 0), | ||
LazyVariantModel( | ||
layout: LayoutConst.bcsBytes, | ||
property: EllipticCurveTypes.secp256k1.name, | ||
index: 1) | ||
], property: "pubKey"), | ||
property: 'publicKeys'), | ||
LayoutConst.u8(property: "requiredSignature") | ||
]); | ||
|
||
/// Bcs layout for encoding single key address | ||
static final singleKeyAddressLayout = LayoutConst.bcsLazyEnum([ | ||
LazyVariantModel( | ||
layout: LayoutConst.bcsBytes, | ||
property: EllipticCurveTypes.ed25519.name, | ||
index: 0), | ||
LazyVariantModel( | ||
layout: LayoutConst.bcsBytes, | ||
property: EllipticCurveTypes.secp256k1.name, | ||
index: 1) | ||
]); | ||
} | ||
|
||
class AptosAddressUtils { | ||
/// check address bytes and convert special address to 32bytes. | ||
static List<int> praseAddressBytes(List<int> bytes) { | ||
final length = bytes.length; | ||
if (length != AptosAddrConst.addressBytesLength) { | ||
throw AddressConverterException("Invalid aptos address bytes length.", | ||
details: { | ||
"expected": AptosAddrConst.addressBytesLength, | ||
"length": bytes.length | ||
}); | ||
} | ||
|
||
return bytes; | ||
} | ||
|
||
/// convert address string to bytes with padding zero for special addresses. | ||
static List<int> addressToBytes(String address) { | ||
address = StringUtils.strip0x(address); | ||
List<int>? bytes = BytesUtils.tryFromHexString(address, | ||
paddingZero: address.length == 1 || | ||
address.length == AptosAddrConst.shortAddressLength); | ||
if (bytes == null || | ||
(bytes.length != AptosAddrConst.addressBytesLength && | ||
bytes.length != 1)) { | ||
throw AddressConverterException("Invalid aptos address.", | ||
details: {"address": address}); | ||
} | ||
if (bytes.length == 1) { | ||
final byte = bytes[0]; | ||
if (byte >= AptosAddrConst.specialAddressLastBytesMax) { | ||
throw AddressConverterException("Invalid special address.", | ||
details: {"address": BytesUtils.toHexString(bytes)}); | ||
} | ||
bytes = List.filled(AptosAddrConst.addressBytesLength, 0); | ||
bytes.last = byte; | ||
} | ||
return praseAddressBytes(bytes); | ||
} | ||
|
||
/// convert bytes (ED25519, Secp256k1 or multisig key data) to address with specify scheme | ||
static List<int> hashKeyBytes( | ||
{required List<int> bytes, required int scheme}) { | ||
bytes = [...bytes, scheme]; | ||
bytes = QuickCrypto.sha3256Hash(bytes); | ||
return bytes; | ||
} | ||
|
||
/// encode ED25519 public key to address | ||
static List<int> encodeEd25519Key(List<int> bytes) { | ||
try { | ||
final key = AddrKeyValidator.validateAndGetEd25519Key(bytes) | ||
.compressed | ||
.sublist(1); | ||
return hashKeyBytes( | ||
bytes: key, scheme: AptosAddrConst.ed25519AddressFlag); | ||
} catch (e) { | ||
throw AddressConverterException( | ||
"Failed to generate Aptos address: Invalid Ed25519 public key provided."); | ||
} | ||
} | ||
|
||
/// encode public key to SignleKey address | ||
static List<int> encodeSingleKey(IPublicKey publicKey) { | ||
try { | ||
final pubkeyBytes = switch (publicKey.curve) { | ||
EllipticCurveTypes.secp256k1 => publicKey.uncompressed, | ||
EllipticCurveTypes.ed25519 => publicKey.compressed.sublist(1), | ||
_ => throw AddressConverterException( | ||
"Unsupported public key: Aptos SingleKey can only be generated from secp256k1 or ed25519 public keys.") | ||
}; | ||
final structLayout = {publicKey.curve.name: pubkeyBytes}; | ||
final encode = | ||
AptosAddrConst.singleKeyAddressLayout.serialize(structLayout); | ||
return hashKeyBytes( | ||
bytes: encode, scheme: AptosAddrConst.signleKeyAddressFlag); | ||
} on AddressConverterException { | ||
rethrow; | ||
} catch (e) { | ||
throw AddressConverterException("Invalid aptos MultiKey address bytes.", | ||
details: {"error": e.toString()}); | ||
} | ||
} | ||
|
||
/// encode Multi ED25519 public keys to MultiEd25519 address | ||
static List<int> encodeMultiEd25519Key( | ||
List<Ed25519PublicKey> publicKeys, int threshold) { | ||
try { | ||
if (publicKeys.length < AptosAddrConst.minPublicKeys || | ||
publicKeys.length > AptosAddrConst.maximumPublicKeys) { | ||
throw AddressConverterException( | ||
"The number of public keys provided is invalid. It must be between ${AptosAddrConst.minPublicKeys} and ${AptosAddrConst.maximumPublicKeys}."); | ||
} | ||
if (threshold < AptosAddrConst.minthreshold || | ||
threshold > publicKeys.length) { | ||
throw AddressConverterException( | ||
"Invalid threshold. The threshold must be between ${AptosAddrConst.minthreshold} and the number of provided public keys (${publicKeys.length})."); | ||
} | ||
final keyBytes = [ | ||
...publicKeys.map((e) => e.compressed.sublist(1)).expand((e) => e), | ||
threshold | ||
]; | ||
return hashKeyBytes( | ||
bytes: keyBytes, scheme: AptosAddrConst.multiEd25519AddressFlag); | ||
} on AddressConverterException { | ||
rethrow; | ||
} catch (e) { | ||
throw AddressConverterException( | ||
"Invalid aptos MultiEd25519 address bytes.", | ||
details: {"error": e.toString()}); | ||
} | ||
} | ||
|
||
/// encode Multi Public keys to MultiKey address | ||
static List<int> encodeMultiKey( | ||
List<IPublicKey> publicKeys, int requiredSignature) { | ||
try { | ||
final pubkeyLayoutStruct = publicKeys.map((e) { | ||
return switch (e.curve) { | ||
EllipticCurveTypes.secp256k1 => {e.curve.name: e.uncompressed}, | ||
EllipticCurveTypes.ed25519 => {e.curve.name: e.compressed.sublist(1)}, | ||
_ => throw AddressConverterException( | ||
"Unsupported public key: Aptos Multikey address can only be generated from secp256k1 or ed25519 public keys.") | ||
}; | ||
}).toList(); | ||
if (publicKeys.length < AptosAddrConst.minPublicKeys || | ||
publicKeys.length > AptosAddrConst.maximumPublicKeys) { | ||
throw AddressConverterException( | ||
"The number of public keys provided is invalid. It must be between ${AptosAddrConst.minPublicKeys} and ${AptosAddrConst.maximumPublicKeys}."); | ||
} | ||
if (requiredSignature < AptosAddrConst.minthreshold || | ||
requiredSignature > publicKeys.length) { | ||
throw AddressConverterException( | ||
"Invalid threshold. The threshold must be between ${AptosAddrConst.minthreshold} and the number of provided public keys (${publicKeys.length})."); | ||
} | ||
final layoutStruct = { | ||
"requiredSignature": requiredSignature, | ||
"publicKeys": pubkeyLayoutStruct | ||
}; | ||
final encode = | ||
AptosAddrConst.multiKeyAddressLayout.serialize(layoutStruct); | ||
return hashKeyBytes( | ||
bytes: encode, scheme: AptosAddrConst.multikeyAddressFlag); | ||
} on AddressConverterException { | ||
rethrow; | ||
} catch (e) { | ||
throw AddressConverterException("Invalid aptos MultiKey address bytes.", | ||
details: {"error": e.toString()}); | ||
} | ||
} | ||
} | ||
|
||
/// Implementation of the [BlockchainAddressDecoder] for Aptos address. | ||
class AptosAddrDecoder implements BlockchainAddressDecoder { | ||
/// Decode an Aptos blockchain address from its string representation. | ||
/// | ||
/// This method decodes the provided `addr` string by removing the prefix, | ||
/// ensuring the address length is valid, and parsing the hexadecimal string | ||
/// to obtain the address bytes. | ||
/// | ||
/// Parameters: | ||
/// - `addr`: The Aptos blockchain address in string format. | ||
/// - `kwargs` (optional): Additional arguments, though none are used in this method. | ||
/// | ||
/// Returns: | ||
/// - A List containing the decoded address bytes. | ||
/// | ||
/// Throws: | ||
/// - ArgumentException if the provided string is not a valid hex encoding. | ||
/// | ||
/// This method is used to convert an Aptos blockchain address from its string | ||
/// representation to its binary format for further processing. | ||
@override | ||
List<int> decodeAddr(String addr, [Map<String, dynamic> kwargs = const {}]) { | ||
String addrNoPrefix = AddrDecUtils.validateAndRemovePrefix( | ||
addr, | ||
CoinsConf.aptos.params.addrPrefix!, | ||
); | ||
addrNoPrefix = addrNoPrefix.padLeft(QuickCrypto.sha3256DigestSize * 2, "0"); | ||
AddrDecUtils.validateLength( | ||
addrNoPrefix, QuickCrypto.sha3256DigestSize * 2); | ||
|
||
return BytesUtils.fromHexString(addrNoPrefix); | ||
return AptosAddressUtils.addressToBytes(addr); | ||
} | ||
} | ||
|
||
class AptosSingleKeyEd25519AddrEncoder implements BlockchainAddressEncoder { | ||
/// This method is used to create an Aptos `SingleKey` address from `ED25519` public key. | ||
@override | ||
String encodeKey(List<int> pubKey, [Map<String, dynamic> kwargs = const {}]) { | ||
final publicKey = AddrKeyValidator.validateAndGetEd25519Key(pubKey); | ||
final addressBytes = AptosAddressUtils.encodeSingleKey(publicKey); | ||
|
||
/// Concatenate the address prefix and the hash bytes, removing leading zeros | ||
return BytesUtils.toHexString(addressBytes, | ||
prefix: CoinsConf.aptos.params.addrPrefix); | ||
} | ||
} | ||
|
||
class AptosSingleKeySecp256k1AddrEncoder implements BlockchainAddressEncoder { | ||
/// This method is used to create an Aptos `SingleKey` address from `Sec256k1` public key. | ||
@override | ||
String encodeKey(List<int> pubKey, [Map<String, dynamic> kwargs = const {}]) { | ||
final publicKey = AddrKeyValidator.validateAndGetSecp256k1Key(pubKey); | ||
final addressBytes = AptosAddressUtils.encodeSingleKey(publicKey); | ||
|
||
/// Concatenate the address prefix and the hash bytes, removing leading zeros | ||
return BytesUtils.toHexString(addressBytes, | ||
prefix: CoinsConf.aptos.params.addrPrefix); | ||
} | ||
} | ||
|
||
/// Implementation of the [BlockchainAddressEncoder] for Aptos address. | ||
class AptosAddrEncoder implements BlockchainAddressEncoder { | ||
/// Encode an Aptos blockchain address from a public key. | ||
/// | ||
/// This method encodes an Aptos blockchain address from the provided `pubKey` | ||
/// by performing the following steps: | ||
/// 1. Validate the public key and extract its raw compressed bytes. | ||
/// 2. Prepare the payload by appending a single-sig suffix byte. | ||
/// 3. Compute the SHA-3-256 hash of the payload. | ||
/// 4. Concatenate the address prefix and the hash bytes. | ||
/// 5. Remove leading zeros from the resulting hex-encoded address. | ||
/// | ||
/// Parameters: | ||
/// - `pubKey`: The public key for which to generate the address. | ||
/// - `kwargs` (optional): Additional arguments, though none are used in this method. | ||
/// | ||
/// Returns: | ||
/// - A hex-encoded string representing the generated Aptos blockchain address. | ||
/// | ||
/// This method is used to create an Aptos blockchain address from a public key. | ||
/// The resulting address is a hexadecimal string without leading zeros. | ||
/// This method is used to create an Aptos `ED25519` address from public key. | ||
@override | ||
String encodeKey(List<int> pubKey, [Map<String, dynamic> kwargs = const {}]) { | ||
final pubKeyBytes = pubKey; | ||
final pubKeyObj = AddrKeyValidator.validateAndGetEd25519Key(pubKeyBytes); | ||
final addressBytes = AptosAddressUtils.encodeEd25519Key(pubKey); | ||
|
||
/// Prepare the payload by appending a single-sig suffix byte | ||
final payloadBytes = List<int>.from([ | ||
...List<int>.from(pubKeyObj.compressed.sublist(1)), | ||
...AptosAddrConst.singleSigSuffixByte | ||
]); | ||
return BytesUtils.toHexString(addressBytes, | ||
prefix: CoinsConf.aptos.params.addrPrefix); | ||
} | ||
|
||
/// Compute the SHA-3-256 hash of the payload | ||
final keyHashBytes = QuickCrypto.sha3256Hash(payloadBytes); | ||
/// This method is used to create an Aptos `SingleKey` address from (ED25519, Sec256k1) public key. | ||
String encodeSingleKey(IPublicKey pubKey) { | ||
final addressBytes = AptosAddressUtils.encodeSingleKey(pubKey); | ||
|
||
/// Concatenate the address prefix and the hash bytes, removing leading zeros | ||
return CoinsConf.aptos.params.addrPrefix! + | ||
BytesUtils.toHexString(keyHashBytes).replaceFirst(RegExp('^0+'), ''); | ||
return BytesUtils.toHexString(addressBytes, | ||
prefix: CoinsConf.aptos.params.addrPrefix); | ||
} | ||
|
||
/// This method is used to create an Aptos `MultiEd25519` address from ED25519 public keys. | ||
String encodeMultiEd25519Key( | ||
{required List<Ed25519PublicKey> publicKeys, required int threshold}) { | ||
final addressBytes = | ||
AptosAddressUtils.encodeMultiEd25519Key(publicKeys, threshold); | ||
return BytesUtils.toHexString(addressBytes, | ||
prefix: CoinsConf.aptos.params.addrPrefix); | ||
} | ||
|
||
/// This method is used to create an Aptos `MultiKey` address from (ED25519 or Secp256k1) public keys. | ||
String encodeMultiKey( | ||
{required List<IPublicKey> publicKeys, required int requiredSignature}) { | ||
final addressBytes = | ||
AptosAddressUtils.encodeMultiKey(publicKeys, requiredSignature); | ||
return BytesUtils.toHexString(addressBytes, | ||
prefix: CoinsConf.aptos.params.addrPrefix); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.