diff --git a/CHANGELOG.md b/CHANGELOG.md index c7d6ca3..1284a19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.4.0 + +- Stellar Address Support: Add support for stellar Contract address. + ## 3.3.0 - Fix substrate key generation from seed (ECDSA, EDDSA). diff --git a/example/lib/main.dart b/example/lib/main.dart index efb7094..7030fcb 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -80,15 +80,18 @@ import 'package:example/test/elctrum/mnemonic/mnemonic.dart'; import 'package:example/test/elctrum/v1/v1.dart'; import 'package:example/test/elctrum/v2/v2.dart'; import 'package:example/test/monero/mnemonic/monero_mnemonic.dart'; -import 'package:example/test/monero/monero.dart'; import 'package:example/test/schnorrkel/derive.dart'; import 'package:example/test/schnorrkel/schnorrkel_key.dart'; import 'package:example/test/schnorrkel/sign.dart'; import 'package:example/test/schnorrkel/vrf.dart'; import 'package:example/test/secure_storage.dart'; +import 'package:example/test/signer/ethereum_test.dart'; +import 'package:example/test/signer/tron_test.dart'; +import 'package:example/test/signer/xrp_test.dart'; import 'package:example/test/ss58/ss58.dart'; import 'package:example/test/substrate/scale.dart'; import 'package:example/test/substrate/substrate.dart'; +import 'package:example/test/ton_mnemonic/ton_mnemonic.dart'; import 'package:example/test/uuid.dart'; import 'package:example/test/wif/wif.dart'; import 'package:flutter/material.dart'; @@ -157,7 +160,12 @@ void _testAll() async { _test("algorandMnemonic and derive address", algorandMnemonicAndAddressTest); _test("substrate derive", substrateDeriveTest); _test("monero mnemonic", moneroMnemonucTest); - _test("monero", moneroTest); + _test("ton mnemonic", tonMnemonic); + _test("sign ethereum", signEthereum); + _test("sign tron", signTron); + _test("sign xrp", signXrp); + + // _test("monero", moneroTest); _test("electrum v2", electrumV2Test); _test("electrum v1", electrumV1Test); _test("electrum mnemonic", electrumMnemonicTest); diff --git a/example/lib/test/signer/ethereum_test.dart b/example/lib/test/signer/ethereum_test.dart new file mode 100644 index 0000000..63b0fd5 --- /dev/null +++ b/example/lib/test/signer/ethereum_test.dart @@ -0,0 +1,18 @@ +import 'package:blockchain_utils/blockchain_utils.dart'; + +void signEthereum() { + final signer = ETHSigner.fromKeyBytes(BytesUtils.fromHexString( + "cd23c9f2e2c096ee3be3c4e0e58199800c0036ea27b7cd4e838bbde8b21788b3")); + final message = + BytesUtils.fromHexString("0x84df2267aa318f451199223385516162"); + final sign = signer.signProsonalMessage(message); + final verify = signer.toVerifyKey().verifyPersonalMessage(message, sign); + assert(BytesUtils.toHexString(sign) == + "4b57a6ca5e2f5da5ae9667d69bb61285808b54ed08dacc76d77b02a8e6f6be905bf4f6fce63ff4142af25458c3bb8ecbda4990b76783a35561382096e30082321b"); + assert(verify, true); + final publicKey = ETHVerifier.getPublicKey(message, sign); + assert( + BytesUtils.bytesEqual(publicKey?.toBytes(), + signer.toVerifyKey().edsaVerifyKey.publicKey.toBytes()), + true); +} diff --git a/example/lib/test/signer/tron_test.dart b/example/lib/test/signer/tron_test.dart new file mode 100644 index 0000000..05de6e7 --- /dev/null +++ b/example/lib/test/signer/tron_test.dart @@ -0,0 +1,19 @@ +import 'dart:convert'; + +import 'package:blockchain_utils/blockchain_utils.dart'; + +void signTron() { + final signer = TronSigner.fromKeyBytes(BytesUtils.fromHexString( + "43985273a3d94eb753fe6acfd7003e88254effce1eb53e2e97b8522558a98038")); + final message = utf8.encode("message"); + final sign = signer.signProsonalMessage(message); + final verify = signer.toVerifyKey().verifyPersonalMessage(message, sign); + assert(BytesUtils.toHexString(sign) == + "fde00bc33d78109bc61de314c1c0526a047e22a2aaae473ca84b32d8aa35ed3e03720e05d614087e3d8c6fae63879755b32aa08818a2d4de66fee1a617a971671b"); + assert(verify, true); + final publicKey = TronVerifier.getPublicKey(message, sign); + assert( + BytesUtils.bytesEqual(publicKey?.toBytes(), + signer.toVerifyKey().edsaVerifyKey.publicKey.toBytes()), + true); +} diff --git a/example/lib/test/signer/xrp_test.dart b/example/lib/test/signer/xrp_test.dart new file mode 100644 index 0000000..6060c7a --- /dev/null +++ b/example/lib/test/signer/xrp_test.dart @@ -0,0 +1,26 @@ +import 'package:blockchain_utils/blockchain_utils.dart'; + +void signXrp() { + const String blob = + "535458001200052200000000240272eb8c201b0272ec4a68400000000000000a732103ef451404c8753525f760059c5f4431181f29d09448856cc8fd867a7346698aae8114440bee336b72b7f4ad58b1b2fd2ba0ce9ba7625288140000000000000000000000000000000000000001f9ea7c04546578747d0e4d52544e4554574f524b2e636f6d7e0a746578742f706c61696ee1f1"; + const String sig = + "3044022043b52acacfe98066ade99274ac96d5dab65738b344fed8d78acca1c33894d8a5022021f3dd6caba9c9904cf1347a3440652ba47af844cfb1bfcd7ad878a6dd9a3909"; + final signer = XrpSigner.fromKeyBytes( + BytesUtils.fromHexString( + "C63383DAC6B5B043A66B8E1BBBC3CF48E6B170862B7AE36217F516237211E39B"), + EllipticCurveTypes.secp256k1); + + assert(BytesUtils.toHexString(signer.sign(BytesUtils.fromHexString(blob))) == + sig); + const String blob2 = + "535458001200032200000000240272eb8d201b0272eedc20210000000468400000000000000a732103ef451404c8753525f760059c5f4431181f29d09448856cc8fd867a7346698aae8114440bee336b72b7f4ad58b1b2fd2ba0ce9ba76252f9ea7c04546578747d0e4d52544e4554574f524b2e636f6d7e0a746578742f706c61696ee1f1"; + const String sig2 = + "3045022100d37aac7c901baa32a5fc1759d41740216264193f85662e186c419e7481f0f7a102202948dfd2f13b724bdc16e71335117b9a644a27f409c4ec905f5568bad96ec680"; + final signer2 = XrpSigner.fromKeyBytes( + BytesUtils.fromHexString( + "C63383DAC6B5B043A66B8E1BBBC3CF48E6B170862B7AE36217F516237211E39B"), + EllipticCurveTypes.secp256k1); + assert( + BytesUtils.toHexString(signer2.sign(BytesUtils.fromHexString(blob2))) == + sig2); +} diff --git a/example/lib/test/substrate/substrate.dart b/example/lib/test/substrate/substrate.dart index 93617ae..c1c4595 100644 --- a/example/lib/test/substrate/substrate.dart +++ b/example/lib/test/substrate/substrate.dart @@ -11,7 +11,7 @@ void substrateDeriveTest() { final seed = BytesUtils.fromHexString(i["private_key"]); final coin = SubstrateCoins.values.firstWhere((element) => element.name.toLowerCase() == - (i["coin"] as String).toLowerCase().replaceAll("_", "")); + "${(i["coin"] as String).toLowerCase().replaceAll("_", "")}sr25519"); Substrate w = Substrate.fromPrivateKey(seed, coin); assert(w.publicKey.compressed.toHex() == i["public_key"]); assert(w.priveKey.raw.toHex() == i["private_key"]); @@ -31,7 +31,7 @@ void substrateDeriveTest() { final secret = (w.priveKey.privKey as Sr25519PrivateKey).secretKey; final testPrive = BytesUtils.fromHexString(childInfo["private_key"]).sublist(0, 32); - assert(BytesUtils.bytesEqual(testPrive, secret.key())); + assert(BytesUtils.bytesEqual(testPrive, secret.key()), true); } } } @@ -41,7 +41,7 @@ void substrateDeriveTest() { final seed = BytesUtils.fromHexString(i["seed"]); final coin = SubstrateCoins.values.firstWhere((element) => element.name.toLowerCase() == - (i["coin"] as String).toLowerCase().replaceAll("_", "")); + "${(i["coin"] as String).toLowerCase().replaceAll("_", "")}sr25519"); Substrate w = Substrate.fromSeed(seed, coin); assert(w.publicKey.compressed.toHex() == i["public_key"]); assert(w.priveKey.raw.toHex() == i["private_key"]); @@ -61,7 +61,7 @@ void substrateDeriveTest() { final secret = (w.priveKey.privKey as Sr25519PrivateKey).secretKey; final testPrive = BytesUtils.fromHexString(childInfo["private_key"]).sublist(0, 32); - assert(BytesUtils.bytesEqual(testPrive, secret.key())); + assert(BytesUtils.bytesEqual(testPrive, secret.key()) == true); } } } diff --git a/example/lib/test/ton_mnemonic/ton_mnemonic.dart b/example/lib/test/ton_mnemonic/ton_mnemonic.dart new file mode 100644 index 0000000..4decbb0 --- /dev/null +++ b/example/lib/test/ton_mnemonic/ton_mnemonic.dart @@ -0,0 +1,122 @@ +import 'package:blockchain_utils/blockchain_utils.dart'; + +void tonMnemonic() { + _test(); + _test2(); + _test3(); + _test4(); +} + +void _test() { + final mnemonic = [ + "current", + "phrase", + "now", + "sea", + "verify", + "chapter", + "rain", + "below", + "office", + "voice", + "trade", + "share", + "inject", + "impulse", + "empower", + "bitter", + "fee", + "half", + "excess", + "oval", + "genuine", + "happy", + "wrong", + "trust" + ]; + final seed = + TonSeedGenerator(Mnemonic.fromList(mnemonic)).generate(password: ""); + final privateKey = Ed25519PrivateKey.fromBytes( + seed.sublist(0, Ed25519KeysConst.privKeyByteLen)); + assert(privateKey.publicKey.toHex(withPrefix: false) == + "cd1fea46e4a59115211ed483161bb315a8e0028ae190b24c1838351dc0bdf040"); +} + +void _test2() { + final mnemonic = [ + "smoke", + "area", + "audit", + "artist", + "tennis", + "owner", + "salute", + "donate", + "hole", + "victory", + "such", + "boost", + "ahead", + "jeans", + "protect", + "decade", + "report", + "float", + "rather", + "sheriff", + "salad", + "supreme", + "acquire", + "bulb" + ]; + final seed = + TonSeedGenerator(Mnemonic.fromList(mnemonic)).generate(password: ""); + final privateKey = Ed25519PrivateKey.fromBytes( + seed.sublist(0, Ed25519KeysConst.privKeyByteLen)); + assert(privateKey.toHex() == + "ab460eb3462747a1e57e75e2c6a3afab420a7a297f53d5258b9b7fe25113cebe"); +} + +void _test3() { + final mnemonic = [ + "woman", + "harvest", + "crawl", + "blind", + "piece", + "portion", + "draft", + "write", + "win", + "coil", + "lawsuit", + "illegal" + ]; + final seed = TonSeedGenerator(Mnemonic.fromList(mnemonic)) + .generate(password: "MRTNETWORK"); + final privateKey = Ed25519PrivateKey.fromBytes( + seed.sublist(0, Ed25519KeysConst.privKeyByteLen)); + assert(privateKey.toHex() == + "b91ad008bdf851289acaa77401612674ea3906eba0ca044374ff38f2a170ba85"); +} + +void _test4() { + final mnemonic = [ + "woman", + "harvest", + "crawl", + "blind", + "piece", + "portion", + "draft", + "write", + "win", + "coil", + "lawsuit", + "illegal" + ]; + final validator = TomMnemonicValidator(); + assert(validator.isValid(Mnemonic.fromList(mnemonic)), false); + assert(validator.isValid(Mnemonic.fromList(mnemonic), password: "MRTNETWORK"), + true); +} diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 27e0f50..7d9ca67 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -227,7 +227,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 331C80D4294CF70F00263BE5 = { diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 397f3d3..15368ec 100644 --- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ dataBytes, [Base58Alphabets base58alphabets = Base58Alphabets.bitcoin]) { + dataBytes = dataBytes.asImmutableBytes; final checksum = Base58Utils.computeChecksum(dataBytes); final dataWithChecksum = List.from([...dataBytes, ...checksum]); return encode(dataWithChecksum, base58alphabets); @@ -101,7 +103,7 @@ class Base58Encoder { /// A utility class for decoding Base58 encoded strings into List data using a specified alphabet. class Base58Decoder { - /// Decode the provided Base58 encoded [dataStr] into a List of data bytes using the specified [base58alphabets]. + /// Decode the provided Base58 encoded [data] into a List of data bytes using the specified [base58alphabets]. /// /// Parameters: /// - data: The Base58 encoded string to be decoded. @@ -114,7 +116,7 @@ class Base58Decoder { final alphabet = Base58Const.alphabets[base58alphabets]!; var val = BigInt.zero; - for (var i = 0; i < data.length; i++) { + for (int i = 0; i < data.length; i++) { final c = data[data.length - 1 - i]; final charIndex = alphabet.indexOf(c); if (charIndex == -1) { diff --git a/lib/base58/base58_ex.dart b/lib/base58/base58_ex.dart index dfe6603..1e4231f 100644 --- a/lib/base58/base58_ex.dart +++ b/lib/base58/base58_ex.dart @@ -1,19 +1,8 @@ import 'package:blockchain_utils/exception/exception.dart'; /// An exception class representing an error related to Base58 checksum validation. -class Base58ChecksumError implements BlockchainUtilsException { - @override - final String message; - - @override - final Map? details; - +class Base58ChecksumError extends BlockchainUtilsException { /// Constructor for creating a Base58ChecksumError with an optional error message. - const Base58ChecksumError(this.message, {this.details}); - - @override - String toString() { - /// Provide a custom string representation of the error. - return message; - } + const Base58ChecksumError(String message, {Map? details}) + : super(message, details: details); } diff --git a/lib/base58/base58_xmr.dart b/lib/base58/base58_xmr.dart index 203ae60..170341b 100644 --- a/lib/base58/base58_xmr.dart +++ b/lib/base58/base58_xmr.dart @@ -53,6 +53,7 @@ */ import 'package:blockchain_utils/base58/base58.dart'; +import 'package:blockchain_utils/helper/helper.dart'; /// Constants and data related to Base58 encoding used in Monero (XMR). class Base58XmrConst { @@ -73,6 +74,7 @@ class Base58XmrConst { class Base58XmrEncoder { /// Encode the provided BytesData of dataBytes into a Monero Base58 encoded string. static String encode(List dataBytes) { + dataBytes = dataBytes.asImmutableBytes; String enc = ''; /// Get lengths @@ -109,7 +111,6 @@ class Base58XmrEncoder { /// A utility class for decoding Monero Base58 encoded strings into BytesData data. class Base58XmrDecoder { /// Decode the provided Monero Base58 encoded [dataStr] into a BytesData of dataBytes. - /// Throws a [Base58ChecksumError] if the checksum is invalid. static List decode(String dataStr) { List dec = List.empty(); diff --git a/lib/bech32/bch_bech32.dart b/lib/bech32/bch_bech32.dart index dc1213a..ad8a9a0 100644 --- a/lib/bech32/bch_bech32.dart +++ b/lib/bech32/bch_bech32.dart @@ -54,6 +54,7 @@ import 'dart:typed_data'; +import 'package:blockchain_utils/helper/helper.dart'; import 'package:blockchain_utils/utils/utils.dart'; import 'bech32_utils.dart'; @@ -131,9 +132,10 @@ class _BchBech32Utils { class BchBech32Encoder extends Bech32EncoderBase { /// Combine the network version bytes and data. static String encode(String hrp, List netVar, List data) { + final concatBytes = [...netVar, ...data].asImmutableBytes; return Bech32EncoderBase.encodeBech32( hrp, - Bech32BaseUtils.convertToBase32(List.from([...netVar, ...data])), + Bech32BaseUtils.convertToBase32(concatBytes), BchBech32Const.separator, _BchBech32Utils.computeChecksum); } diff --git a/lib/bech32/bech32_ex.dart b/lib/bech32/bech32_ex.dart index 71fd1d7..766f0fc 100644 --- a/lib/bech32/bech32_ex.dart +++ b/lib/bech32/bech32_ex.dart @@ -1,21 +1,8 @@ import 'package:blockchain_utils/exception/exception.dart'; /// An exception class representing errors related to Bech32 checksum validation. -class Bech32ChecksumError implements BlockchainUtilsException { +class Bech32ChecksumError extends BlockchainUtilsException { /// The error message associated with this checksum error. - @override - final String message; - @override - final Map? details; - - /// Creates a new instance of [Bech32ChecksumError]. - /// - /// Parameters: - /// - message: An optional error message describing the checksum error. - const Bech32ChecksumError(this.message, {this.details}); - - @override - String toString() { - return message; - } + const Bech32ChecksumError(String message, {Map? details}) + : super(message, details: details); } diff --git a/lib/bip/address/exception/exception.dart b/lib/bip/address/exception/exception.dart index 79613c4..fe44b11 100644 --- a/lib/bip/address/exception/exception.dart +++ b/lib/bip/address/exception/exception.dart @@ -1,11 +1,7 @@ import 'package:blockchain_utils/exception/exceptions.dart'; -class AddressConverterException implements BlockchainUtilsException { - @override - final String message; - - @override - final Map? details; - - const AddressConverterException(this.message, {this.details}); +class AddressConverterException extends BlockchainUtilsException { + const AddressConverterException(String message, + {Map? details}) + : super(message, details: details); } diff --git a/lib/bip/address/xlm_addr.dart b/lib/bip/address/xlm_addr.dart index d4e401d..9c015af 100644 --- a/lib/bip/address/xlm_addr.dart +++ b/lib/bip/address/xlm_addr.dart @@ -2,12 +2,12 @@ import 'package:blockchain_utils/base32/base32.dart'; import 'package:blockchain_utils/bip/address/addr_dec_utils.dart'; import 'package:blockchain_utils/bip/address/addr_key_validator.dart'; import 'package:blockchain_utils/bip/address/decoder.dart'; -import 'package:blockchain_utils/bip/ecc/keys/i_keys.dart'; import 'package:blockchain_utils/bip/ecc/keys/ed25519_keys.dart'; - import 'package:blockchain_utils/crypto/crypto/x_modem_crc/x_modem_crc.dart'; +import 'package:blockchain_utils/utils/binary/binary_operation.dart'; +import 'package:blockchain_utils/utils/binary/utils.dart'; +import 'package:blockchain_utils/utils/numbers/numbers.dart'; import 'exception/exception.dart'; - import 'encoder.dart'; /// Enum representing different types of Stellar (XLM) addresses. @@ -25,18 +25,44 @@ import 'encoder.dart'; /// final addressValue = addressType.value; // Returns 48 (6 << 3) /// ``` class XlmAddrTypes { - /// Public key address type. - static const XlmAddrTypes pubKey = XlmAddrTypes._(6 << 3); + final int value; + final String name; - /// Private key address type. - static const XlmAddrTypes privKey = XlmAddrTypes._(18 << 3); + /// Contract key address type. + static const XlmAddrTypes contract = + XlmAddrTypes._(value: 2 << 3, name: "Contract"); - final int value; + /// Public key address type. (keyBytes must be a valid Ed25519 public key) + static const XlmAddrTypes pubKey = + XlmAddrTypes._(name: "PublicKey", value: 6 << 3); + + /// Private key address type. (keyBytes must be a valid Ed25519 private key) + static const XlmAddrTypes privKey = + XlmAddrTypes._(name: "SecretKey", value: 18 << 3); + + /// Muxed + static const XlmAddrTypes muxed = + XlmAddrTypes._(value: 12 << 3, name: "Muxed"); - static const List values = [pubKey, privKey]; + static const List values = [pubKey, privKey, contract, muxed]; /// Constructor for XlmAddrTypes enum values. - const XlmAddrTypes._(this.value); + const XlmAddrTypes._({required this.value, required this.name}); + + static XlmAddrTypes fromTag(int? tag) { + return values.firstWhere((e) => e.value == tag, + orElse: () => throw AddressConverterException( + "Invalid or unsuported xlm address type.", + details: { + "excepted": values.map((e) => e.value).join(", "), + "got": tag + })); + } + + @override + String toString() { + return "XlmAddrTypes.$name"; + } } /// Constants related to Stellar (XLM) addresses. @@ -44,7 +70,13 @@ class XlmAddrTypes { /// This class contains constants used for handling Stellar addresses, including the checksum byte length. class XlmAddrConst { /// The length in bytes of the checksum used in Stellar addresses. - static const checksumByteLen = 2; + static const int checksumByteLen = 2; + static const int versionBytesLength = 1; + static const int muxedAddrLen = pubkeyAddrLength + muxedIdLength; + static const int pubkeyAddrLength = Ed25519KeysConst.pubKeyByteLen + + XlmAddrConst.versionBytesLength + + XlmAddrConst.checksumByteLen; + static const int muxedIdLength = 8; } class _XlmAddrUtils { @@ -56,57 +88,93 @@ class _XlmAddrUtils { } } -/// Implementation of the [BlockchainAddressDecoder] for Stellar (XLM) blockchain addresses. -class XlmAddrDecoder implements BlockchainAddressDecoder { +class XlmAddrDecoderResult { + final XlmAddrTypes type; + final List pubKeyBytes; + final String baseAddress; + final BigInt? accountId; + XlmAddrDecoderResult( + {required this.type, + required List pubKeyBytes, + required this.baseAddress, + required this.accountId}) + : pubKeyBytes = BytesUtils.toBytes(pubKeyBytes, unmodifiable: true); @override - List decodeAddr(String addr, [Map kwargs = const {}]) { - /// Decode a Stellar (XLM) address and return the public key. - /// - /// This method decodes a Stellar address and extracts the public key part, returning it as a List. - /// - /// - [addr]: The Stellar address to decode. - /// - [kwargs]: A map of optional keyword arguments. - /// - [addr_type]: The address type, either XlmAddrTypes.pubKey or XlmAddrTypes.privKey. - /// - /// Throws an [ArgumentException] if the address type is not valid or if there's a validation error. - /// - /// Example usage: - /// ```dart - /// final decoder = XlmAddrDecoder(); - /// final addr = 'GC2Z66U3A3I5VGM3S5INUT4FVC3VGCUJ7ALDCTF6WLYBMXNO5KNOHWZL'; - /// final publicKey = decoder.decodeAddr(addr, {'addr_type': XlmAddrTypes.pubKey}); - /// ``` - final addrType = kwargs['addr_type'] ?? XlmAddrTypes.pubKey; - if (addrType is! XlmAddrTypes) { - throw const AddressConverterException( - 'Address type is not an enumerative of XlmAddrTypes'); - } + String toString() { + return baseAddress; + } +} +/// Implementation of the [BlockchainAddressDecoder] for Stellar (XLM) blockchain addresses. +class XlmAddrDecoder implements BlockchainAddressDecoder { + XlmAddrDecoderResult decode(String addr, + [Map kwargs = const {}]) { + final addrType = AddrKeyValidator.nullOrValidateAddressArgs( + kwargs, "addr_type"); final addrDecBytes = Base32Decoder.decode(addr); - AddrDecUtils.validateBytesLength( - addrDecBytes, - Ed25519KeysConst.pubKeyByteLen + - Ed25519KeysConst.pubKeyPrefix.length + - XlmAddrConst.checksumByteLen); - final payloadBytes = AddrDecUtils.splitPartsByChecksum( addrDecBytes, XlmAddrConst.checksumByteLen) .item1; final addrTypeGot = payloadBytes[0]; - if (addrType.value != addrTypeGot) { + + final type = XlmAddrTypes.fromTag(addrTypeGot); + if (addrType != null && addrType != type) { throw AddressConverterException( 'Invalid address type (expected ${addrType.value}, got $addrTypeGot)'); } + AddrDecUtils.validateBytesLength( + addrDecBytes, + type == XlmAddrTypes.muxed + ? XlmAddrConst.muxedAddrLen + : XlmAddrConst.pubkeyAddrLength); AddrDecUtils.validateChecksum( payloadBytes, addrDecBytes .sublist(addrDecBytes.length - XlmAddrConst.checksumByteLen), _XlmAddrUtils.computeChecksum); - final pubKeyBytes = payloadBytes.sublist(1); - return pubKeyBytes; + List pubKeyBytes = payloadBytes.sublist(1); + BigInt? accountId; + if (type == XlmAddrTypes.muxed) { + accountId = BigintUtils.fromBytes( + pubKeyBytes.sublist(pubKeyBytes.length - XlmAddrConst.muxedIdLength)); + if (accountId > maxU64 || accountId < BigInt.zero) { + throw const AddressConverterException( + "Invalid muxed address account id."); + } + pubKeyBytes = List.unmodifiable(pubKeyBytes.sublist( + 0, pubKeyBytes.length - XlmAddrConst.muxedIdLength)); + addr = XlmAddrEncoder().encodeKey(pubKeyBytes); + } + return XlmAddrDecoderResult( + type: type, + pubKeyBytes: pubKeyBytes, + baseAddress: addr, + accountId: accountId); + } + + /// Decode a Stellar (XLM) address and return the public key. + /// + /// This method decodes a Stellar address and extracts the public key part, returning it as a List. + /// + /// - [addr]: The Stellar address to decode. + /// - [kwargs]: A map of optional keyword arguments. + /// - [addr_type]: The address type, either XlmAddrTypes.pubKey or XlmAddrTypes.privKey. + /// + /// Throws an [AddressConverterException] if the address type is not valid or if there's a validation error. + /// + /// Example usage: + /// ```dart + /// final decoder = XlmAddrDecoder(); + /// final addr = 'GC2Z66U3A3I5VGM3S5INUT4FVC3VGCUJ7ALDCTF6WLYBMXNO5KNOHWZL'; + /// final publicKey = decoder.decodeAddr(addr, {'addr_type': XlmAddrTypes.pubKey}); + /// ``` + @override + List decodeAddr(String addr, [Map kwargs = const {}]) { + final decodeAddress = decode(addr, kwargs); + return decodeAddress.pubKeyBytes; } } @@ -120,7 +188,7 @@ class XlmAddrEncoder implements BlockchainAddressEncoder { /// - [kwargs]: A map of optional keyword arguments. /// - [addr_type]: The address type, either XlmAddrTypes.pubKey or XlmAddrTypes.privKey. /// - /// Throws an [ArgumentException] if the address type is not valid or if there's a validation error. + /// Throws an [AddressConverterException] if the address type is not valid or if there's a validation error. /// /// Example usage: /// ```dart @@ -130,15 +198,33 @@ class XlmAddrEncoder implements BlockchainAddressEncoder { /// ``` @override String encodeKey(List pubKey, [Map kwargs = const {}]) { - final addrType = kwargs['addr_type'] ?? XlmAddrTypes.pubKey; - if (addrType is! XlmAddrTypes) { - throw const AddressConverterException( - 'Address type is not an enumerative of XlmAddrTypes'); + if (pubKey.length == + Ed25519KeysConst.pubKeyByteLen + Ed25519KeysConst.pubKeyPrefix.length) { + pubKey = pubKey.sublist(1); + } + final addrType = AddrKeyValidator.nullOrValidateAddressArgs( + kwargs, "addr_type") ?? + XlmAddrTypes.pubKey; + AddrDecUtils.validateBytesLength(pubKey, Ed25519KeysConst.pubKeyByteLen); + if (addrType == XlmAddrTypes.pubKey) { + AddrKeyValidator.validateAndGetEd25519Key(pubKey); + } else if (addrType == XlmAddrTypes.privKey) { + Ed25519PrivateKey.fromBytes(pubKey); + } + + if (addrType == XlmAddrTypes.muxed) { + final BigInt? muxedId = BigintUtils.tryParse(kwargs["account_id"]); + if (muxedId == null || muxedId > maxU64 || muxedId < BigInt.zero) { + throw AddressConverterException( + "Missing or invalid 'account_id'. An accountId is required for a muxed address.", + details: {"accounts_id": kwargs["account_id"]}); + } + final idBytes = + BigintUtils.toBytes(muxedId, length: XlmAddrConst.muxedIdLength); + pubKey = [...pubKey, ...idBytes]; } - IPublicKey pubKeyObj = AddrKeyValidator.validateAndGetEd25519Key(pubKey); - List payloadBytes = - List.from([addrType.value, ...pubKeyObj.compressed.sublist(1)]); + List payloadBytes = List.from([addrType.value, ...pubKey]); List checksumBytes = _XlmAddrUtils.computeChecksum(payloadBytes); return Base32Encoder.encodeNoPaddingBytes( diff --git a/lib/bip/algorand/mnemonic/algorand_mnemonic_encoder.dart b/lib/bip/algorand/mnemonic/algorand_mnemonic_encoder.dart index 2a3dedb..50406b3 100644 --- a/lib/bip/algorand/mnemonic/algorand_mnemonic_encoder.dart +++ b/lib/bip/algorand/mnemonic/algorand_mnemonic_encoder.dart @@ -5,6 +5,7 @@ import 'package:blockchain_utils/bip/bip/bip39/bip39_mnemonic_utils.dart'; import 'package:blockchain_utils/bip/mnemonic/mnemonic.dart'; import 'package:blockchain_utils/bip/mnemonic/mnemonic_encoder_base.dart'; import 'package:blockchain_utils/exception/exception.dart'; +import 'package:blockchain_utils/helper/helper.dart'; /// Algorand mnemonic encoder class. /// @@ -21,6 +22,7 @@ class AlgorandMnemonicEncoder extends MnemonicEncoderBase { /// Encode bytes to a mnemonic phrase following the Algorand standard. @override Mnemonic encode(List entropyBytes) { + entropyBytes = entropyBytes.asImmutableBytes; final entropyByteLen = entropyBytes.length; if (!AlgorandEntropyGenerator.isValidEntropyByteLen(entropyByteLen)) { throw ArgumentException( diff --git a/lib/bip/algorand/mnemonic/algorand_mnemonic_generator.dart b/lib/bip/algorand/mnemonic/algorand_mnemonic_generator.dart index 859a36c..594fc7a 100644 --- a/lib/bip/algorand/mnemonic/algorand_mnemonic_generator.dart +++ b/lib/bip/algorand/mnemonic/algorand_mnemonic_generator.dart @@ -11,7 +11,7 @@ import 'package:blockchain_utils/exception/exception.dart'; class AlgorandMnemonicGeneratorConst { static final Map wordsNumToEntropyLen = { - AlgorandWordsNum.wordsNum25: AlgorandEntropyBitLen.bitLen256, + AlgorandWordsNum.wordsNum25: AlgorandEntropyBitLen.bitLen256 }; } @@ -47,16 +47,12 @@ class AlgorandMnemonicGenerator { /// /// [wordsNum] can be either the number of words or an [AlgorandWordsNum] enum value. /// It ensures that the number of words is valid and consistent with the entropy bit length. - Mnemonic fromWordsNumber(dynamic wordsNum) { + Mnemonic fromWordsNumber(AlgorandWordsNum wordsNum) { if (!AlgorandMnemonicConst.mnemonicWordNum.contains(wordsNum)) { throw ArgumentException( 'Words number for mnemonic ($wordsNum) is not valid'); } - if (wordsNum is int) { - wordsNum = AlgorandWordsNum.values[wordsNum]; - } - final entropyBitLen = AlgorandMnemonicGeneratorConst.wordsNumToEntropyLen[wordsNum]!; final entropyBytes = AlgorandEntropyGenerator(entropyBitLen).generate(); diff --git a/lib/bip/bip/bip32/base/bip32_base.dart b/lib/bip/bip/bip32/base/bip32_base.dart index b229c1f..ae83cb7 100644 --- a/lib/bip/bip/bip32/base/bip32_base.dart +++ b/lib/bip/bip/bip32/base/bip32_base.dart @@ -8,6 +8,7 @@ import 'package:blockchain_utils/bip/bip/bip32/bip32_path.dart'; import 'package:blockchain_utils/bip/ecc/curve/elliptic_curve_types.dart'; import 'package:blockchain_utils/exception/exception.dart'; +import 'package:blockchain_utils/helper/helper.dart'; import 'ibip32_mst_key_generator.dart'; @@ -67,6 +68,7 @@ abstract class Bip32Base { /// The [seedBytes] parameter is used to generate a master key, and the /// optional [keyNetVer] specifies the key network version. Bip32Base.fromSeed(List seedBytes, [Bip32KeyNetVersions? keyNetVer]) { + seedBytes = seedBytes.asImmutableBytes; keyNetVer ??= defaultKeyNetVersion; final result = masterKeyGenerator.generateFromSeed(seedBytes); final keyData = Bip32KeyData(chainCode: Bip32ChainCode(result.item2)); @@ -82,6 +84,7 @@ abstract class Bip32Base { /// [keyData] and [keyNetVer] parameters specify key data and network versions. Bip32Base.fromPrivateKey(List privKey, [Bip32KeyData? keyData, Bip32KeyNetVersions? keyNetVer]) { + privKey = privKey.asImmutableBytes; keyNetVer ??= defaultKeyNetVersion; keyData ??= Bip32KeyData(); _privKey = @@ -96,6 +99,7 @@ abstract class Bip32Base { /// [keyData] and [keyNetVer] parameters specify key data and network versions. Bip32Base.fromPublicKey(List pubKey, [Bip32KeyData? keyData, Bip32KeyNetVersions? keyNetVer]) { + pubKey = pubKey.asImmutableBytes; keyNetVer ??= defaultKeyNetVersion; keyData ??= Bip32KeyData(); _privKey = diff --git a/lib/bip/bip/bip32/bip32_ex.dart b/lib/bip/bip/bip32/bip32_ex.dart index ab241f4..5768838 100644 --- a/lib/bip/bip/bip32/bip32_ex.dart +++ b/lib/bip/bip/bip32/bip32_ex.dart @@ -5,20 +5,9 @@ import 'package:blockchain_utils/exception/exception.dart'; /// an optional error message to describe the specific issue. When caught, you /// can access the error message using the `toString()` method or the `message` /// property, if provided. -class Bip32KeyError implements BlockchainUtilsException { - @override - final String message; - - @override - final Map? details; - - /// Creates a `Bip32KeyError` with an optional error message. - const Bip32KeyError(this.message, {this.details}); - - @override - String toString() { - return message; - } +class Bip32KeyError extends BlockchainUtilsException { + const Bip32KeyError(String message, {Map? details}) + : super(message, details: details); } /// The `Bip32PathError` class represents an exception that can be thrown in case @@ -26,18 +15,7 @@ class Bip32KeyError implements BlockchainUtilsException { /// errors associated with hierarchical deterministic paths. You can include /// an optional error message to describe the specific issue. To access the error /// message, use the `toString()` method or the `message` property, if provided. -class Bip32PathError implements BlockchainUtilsException { - @override - final String message; - - @override - final Map? details; - - /// Creates a `Bip32PathError` with an optional error message. - const Bip32PathError(this.message, {this.details}); - - @override - String toString() { - return message; - } +class Bip32PathError extends BlockchainUtilsException { + const Bip32PathError(String message, {Map? details}) + : super(message, details: details); } diff --git a/lib/bip/bip/bip32/bip32_key_net_ver.dart b/lib/bip/bip/bip32/bip32_key_net_ver.dart index 3293f5c..f3696ff 100644 --- a/lib/bip/bip/bip32/bip32_key_net_ver.dart +++ b/lib/bip/bip/bip32/bip32_key_net_ver.dart @@ -1,4 +1,5 @@ import 'package:blockchain_utils/exception/exception.dart'; +import 'package:blockchain_utils/helper/helper.dart'; /// Contains constants related to BIP32 key network versions. class Bip32KeyNetVersionsConst { @@ -9,15 +10,15 @@ class Bip32KeyNetVersionsConst { class Bip32KeyNetVersions { late final List _pubNetVer; late final List _privNetVer; + Bip32KeyNetVersions._(this._pubNetVer, this._privNetVer); /// constractur for Bip32KeyNetVersions - Bip32KeyNetVersions(List pubNetVer, List privNetVer) { + factory Bip32KeyNetVersions(List pubNetVer, List privNetVer) { if (pubNetVer.length != length || privNetVer.length != length) { throw const ArgumentException("Invalid key net version length"); } - - _pubNetVer = pubNetVer; - _privNetVer = privNetVer; + return Bip32KeyNetVersions._( + pubNetVer.asImmutableBytes, privNetVer.asImmutableBytes); } /// Get the key net version length. diff --git a/lib/bip/bip/bip32/bip32_key_ser.dart b/lib/bip/bip/bip32/bip32_key_ser.dart index 9c916b1..1fca414 100644 --- a/lib/bip/bip/bip32/bip32_key_ser.dart +++ b/lib/bip/bip/bip32/bip32_key_ser.dart @@ -4,6 +4,7 @@ import 'package:blockchain_utils/bip/bip/bip32/bip32_ex.dart'; import 'package:blockchain_utils/bip/bip/bip32/bip32_key_data.dart'; import 'package:blockchain_utils/bip/bip/bip32/bip32_key_net_ver.dart'; import 'package:blockchain_utils/bip/ecc/keys/i_keys.dart'; +import 'package:blockchain_utils/helper/helper.dart'; import 'package:blockchain_utils/utils/utils.dart'; /// The `Bip32KeySerConst` class contains constants related to the serialization @@ -72,7 +73,7 @@ class Bip32DeserializedKey { final bool isPublic; Bip32DeserializedKey(List keyBytes, this.keyData, this.isPublic) - : _keyBytes = keyBytes; + : _keyBytes = keyBytes.asImmutableBytes; List get keyBytes { return List.from(_keyBytes); diff --git a/lib/bip/bip/bip32/bip32_path.dart b/lib/bip/bip/bip32/bip32_path.dart index fbcebdc..5364ace 100644 --- a/lib/bip/bip/bip32/bip32_path.dart +++ b/lib/bip/bip/bip32/bip32_path.dart @@ -53,6 +53,7 @@ */ import 'package:blockchain_utils/bip/bip/bip32/bip32_key_data.dart'; +import 'package:blockchain_utils/helper/helper.dart'; import 'bip32_ex.dart'; @@ -77,8 +78,8 @@ class Bip32Path { /// /// [elems] is an optional list of key indices in the path. /// [isAbsolute] specifies if the path is absolute (default: true). - Bip32Path({List? elems, this.isAbsolute = true}) - : elems = elems ?? List.empty(growable: true); + Bip32Path({List elems = const [], this.isAbsolute = true}) + : elems = elems.immutable; /// Adds a key index element to the path and returns a new Bip32Path. Bip32Path addElem(Bip32KeyIndex elem) { diff --git a/lib/bip/bip/bip39/bip39_mnemonic_decoder.dart b/lib/bip/bip/bip39/bip39_mnemonic_decoder.dart index c8d8dcf..594c6fc 100644 --- a/lib/bip/bip/bip39/bip39_mnemonic_decoder.dart +++ b/lib/bip/bip/bip39/bip39_mnemonic_decoder.dart @@ -33,7 +33,6 @@ class Bip39MnemonicDecoder extends MnemonicDecoderBase { /// If no [language] is provided, it defaults to [Bip39Languages.english]. /// /// The constructor initializes the decoder with the appropriate [language], - /// [wordsListFinder], and [wordsListGetter] for BIP39 mnemonics. /// /// Example usage: /// diff --git a/lib/bip/bip/bip39/bip39_mnemonic_encoder.dart b/lib/bip/bip/bip39/bip39_mnemonic_encoder.dart index 559c0a6..65e2be1 100644 --- a/lib/bip/bip/bip39/bip39_mnemonic_encoder.dart +++ b/lib/bip/bip/bip39/bip39_mnemonic_encoder.dart @@ -2,6 +2,7 @@ import 'package:blockchain_utils/bip/bip/bip39/bip39_entropy_generator.dart'; import 'package:blockchain_utils/bip/bip/bip39/bip39_mnemonic.dart'; import 'package:blockchain_utils/bip/bip/bip39/bip39_mnemonic_utils.dart'; import 'package:blockchain_utils/crypto/quick_crypto.dart'; +import 'package:blockchain_utils/helper/helper.dart'; import 'package:blockchain_utils/utils/utils.dart'; import 'package:blockchain_utils/bip/mnemonic/mnemonic_encoder_base.dart'; import 'package:blockchain_utils/exception/exception.dart'; @@ -36,6 +37,7 @@ class Bip39MnemonicEncoder extends MnemonicEncoderBase { /// A BIP39 mnemonic phrase representing the given entropy. @override Bip39Mnemonic encode(List entropyBytes) { + entropyBytes = entropyBytes.asImmutableBytes; final entropyByteLen = entropyBytes.length; if (!Bip39EntropyGenerator.isValidEntropyByteLen(entropyByteLen)) { throw ArgumentException( diff --git a/lib/bip/bip/bip39/bip39_seed_generator.dart b/lib/bip/bip/bip39/bip39_seed_generator.dart index c47031c..ee0b1a7 100644 --- a/lib/bip/bip/bip39/bip39_seed_generator.dart +++ b/lib/bip/bip/bip39/bip39_seed_generator.dart @@ -1,5 +1,6 @@ import 'package:blockchain_utils/crypto/quick_crypto.dart'; import 'package:blockchain_utils/bip/mnemonic/mnemonic.dart'; +import 'package:blockchain_utils/helper/helper.dart'; import 'package:blockchain_utils/utils/utils.dart'; import 'bip39_mnemonic_decoder.dart'; @@ -22,7 +23,7 @@ class Bip39SeedGenerator { final List _entropy; final Mnemonic mnemonic; Bip39SeedGenerator._(this.mnemonic, List entropy) - : _entropy = BytesUtils.toBytes(entropy, unmodifiable: true); + : _entropy = entropy.asImmutableBytes; /// Initializes a new instance of the Bip39SeedGenerator. /// diff --git a/lib/bip/bip/bip39/word_list/portuguese.dart b/lib/bip/bip/bip39/word_list/portuguese.dart index ed75f60..5f6a04e 100644 --- a/lib/bip/bip/bip39/word_list/portuguese.dart +++ b/lib/bip/bip/bip39/word_list/portuguese.dart @@ -1,7 +1,7 @@ part of 'package:blockchain_utils/bip/bip/bip39/word_list/languages.dart'; /// Portuguese -final List _portuguese = [ +const List _portuguese = [ "abacate", "abaixo", "abalar", diff --git a/lib/bip/bip/bip39/word_list/spanish.dart b/lib/bip/bip/bip39/word_list/spanish.dart index 6d47879..d32a9e0 100644 --- a/lib/bip/bip/bip39/word_list/spanish.dart +++ b/lib/bip/bip/bip39/word_list/spanish.dart @@ -1,7 +1,7 @@ part of 'package:blockchain_utils/bip/bip/bip39/word_list/languages.dart'; /// Spanish -final List _spanish = [ +const List _spanish = [ "ábaco", "abdomen", "abeja", diff --git a/lib/bip/bip/bip44/base/bip44_base.dart b/lib/bip/bip/bip44/base/bip44_base.dart index 8f00969..7e37d47 100644 --- a/lib/bip/bip/bip44/base/bip44_base.dart +++ b/lib/bip/bip/bip44/base/bip44_base.dart @@ -112,7 +112,7 @@ abstract class Bip44Base { bip = Bip32Slip10Nist256p1.fromSeed(seedBytes, coin.keyNetVer); break; default: - throw const ArgumentException("invaid type"); + throw ArgumentException("Bip44 does not supported ${coin.type}"); } final validate = _validate(bip, coin); bip32 = validate.item1; diff --git a/lib/bip/bip/bip44/base/bip44_base_ex.dart b/lib/bip/bip/bip44/base/bip44_base_ex.dart index 19f1cf0..6a6f3ce 100644 --- a/lib/bip/bip/bip44/base/bip44_base_ex.dart +++ b/lib/bip/bip/bip44/base/bip44_base_ex.dart @@ -5,20 +5,7 @@ import 'package:blockchain_utils/exception/exception.dart'; /// This class, `Bip44DepthError`, represents a custom exception for handling /// errors related to the BIP-44 hierarchical deterministic wallet structure's /// depth. It can be thrown to indicate issues with depth levels in BIP-44 paths. -class Bip44DepthError implements BlockchainUtilsException { - @override - final String message; - - @override - final Map? details; - - /// Create a `Bip44DepthError` with an optional error message. - /// - /// - [message]: An optional error message to provide more context. - const Bip44DepthError(this.message, {this.details}); - - @override - String toString() { - return message; - } +class Bip44DepthError extends BlockchainUtilsException { + const Bip44DepthError(String message, {Map? details}) + : super(message, details: details); } diff --git a/lib/bip/bip/conf/bip/bip_coins.dart b/lib/bip/bip/conf/bip/bip_coins.dart index 0c5eafa..fd50499 100644 --- a/lib/bip/bip/conf/bip/bip_coins.dart +++ b/lib/bip/bip/conf/bip/bip_coins.dart @@ -50,10 +50,6 @@ abstract class BipCoins implements CryptoCoins { } } -// abstract class BipProposals implements CoinProposal { -// abstract final Bip32KeyIndex purpose; -// } - /// Enum representing different BIP proposals. class BipProposal implements CoinProposal { static const BipProposal bip44 = BipProposal._('bip44'); diff --git a/lib/bip/bip/conf/bip44/bip44_coins.dart b/lib/bip/bip/conf/bip44/bip44_coins.dart index ce3638f..402f226 100644 --- a/lib/bip/bip/conf/bip44/bip44_coins.dart +++ b/lib/bip/bip/conf/bip44/bip44_coins.dart @@ -284,6 +284,9 @@ class Bip44Coins extends BipCoins { /// Stellar static const stellar = Bip44Coins._('stellar'); + /// Stellar testnet + static const stellarTestnet = Bip44Coins._('stellarTestnet'); + /// Terra static const terra = Bip44Coins._('terra'); @@ -462,6 +465,7 @@ class Bip44Coins extends BipCoins { Bip44Coins.solana: Bip44Conf.solana, Bip44Coins.solanaTestnet: Bip44Conf.solanaTestnet, Bip44Coins.stellar: Bip44Conf.stellar, + Bip44Coins.stellarTestnet: Bip44Conf.stellarTestnet, Bip44Coins.terra: Bip44Conf.terra, Bip44Coins.tezos: Bip44Conf.tezos, Bip44Coins.theta: Bip44Conf.theta, diff --git a/lib/bip/bip/conf/bip44/bip44_conf.dart b/lib/bip/bip/conf/bip44/bip44_conf.dart index 40907d7..b5e1a8f 100644 --- a/lib/bip/bip/conf/bip44/bip44_conf.dart +++ b/lib/bip/bip/conf/bip44/bip44_conf.dart @@ -1246,6 +1246,19 @@ class Bip44Conf { addrParams: {"addr_type": XlmAddrTypes.pubKey}, ); + /// Configuration for Stellar testnet + static final BipCoinConfig stellarTestnet = BipCoinConfig( + coinNames: CoinsConf.stellar.coinName, + coinIdx: Slip44.testnet, + isTestnet: true, + defPath: derPathHardenedShort, + keyNetVer: bip44BtcKeyNetVerMain, + wifNetVer: null, + type: EllipticCurveTypes.ed25519, + addressEncoder: ([dynamic kwargs]) => XlmAddrEncoder(), + addrParams: {"addr_type": XlmAddrTypes.pubKey}, + ); + /// Configuration for Terra static final BipCoinConfig terra = BipCoinConfig( coinNames: CoinsConf.terra.coinName, diff --git a/lib/bip/ecc/curve/elliptic_curve_types.dart b/lib/bip/ecc/curve/elliptic_curve_types.dart index 159ac31..a36dfdc 100644 --- a/lib/bip/ecc/curve/elliptic_curve_types.dart +++ b/lib/bip/ecc/curve/elliptic_curve_types.dart @@ -1,3 +1,5 @@ +import 'package:blockchain_utils/blockchain_utils.dart'; + /// An enumeration of common elliptic curve types used in cryptographic operations. class EllipticCurveTypes { /// Edwards-curve Digital Signature Algorithm (EdDSA) using ed25519 curve @@ -38,8 +40,9 @@ class EllipticCurveTypes { ]; static EllipticCurveTypes fromName(String name) { - return EllipticCurveTypes.values - .firstWhere((element) => element.name == name); + return EllipticCurveTypes.values.firstWhere( + (element) => element.name == name, + orElse: () => throw MessageException("Invalid curve type name. $name")); } @override diff --git a/lib/bip/ecc/keys/ed25519_blake2b_keys.dart b/lib/bip/ecc/keys/ed25519_blake2b_keys.dart index 1305d28..3b9e908 100644 --- a/lib/bip/ecc/keys/ed25519_blake2b_keys.dart +++ b/lib/bip/ecc/keys/ed25519_blake2b_keys.dart @@ -140,6 +140,15 @@ class Ed25519Blake2bPublicKey implements IPublicKey { } return BytesUtils.toHexString(key, prefix: prefix, lowerCase: lowerCase); } + + @override + operator ==(other) { + if (other is! Ed25519Blake2bPublicKey) return false; + return _publicKey == other._publicKey && curve == other.curve; + } + + @override + int get hashCode => _publicKey.hashCode ^ curve.hashCode; } /// Represents an Ed25519 private key with Blake2b hashing, implementing the IPrivateKey interface. @@ -200,4 +209,13 @@ class Ed25519Blake2bPrivateKey implements IPrivateKey { String toHex({bool lowerCase = true, String? prefix = ""}) { return BytesUtils.toHexString(raw, lowerCase: lowerCase, prefix: prefix); } + + @override + operator ==(other) { + if (other is! Ed25519Blake2bPrivateKey) return false; + return _privateKey == other._privateKey && curveType == other.curveType; + } + + @override + int get hashCode => _privateKey.hashCode ^ curveType.hashCode; } diff --git a/lib/bip/ecc/keys/ed25519_keys.dart b/lib/bip/ecc/keys/ed25519_keys.dart index 255ab46..c4725ce 100644 --- a/lib/bip/ecc/keys/ed25519_keys.dart +++ b/lib/bip/ecc/keys/ed25519_keys.dart @@ -104,6 +104,15 @@ class Ed25519PublicKey implements IPublicKey { } return BytesUtils.toHexString(key, prefix: prefix, lowerCase: lowerCase); } + + @override + operator ==(other) { + if (other is! Ed25519PublicKey) return false; + return _publicKey == other._publicKey && curve == other.curve; + } + + @override + int get hashCode => _publicKey.hashCode ^ curve.hashCode; } /// A class representing an Ed25519 private key that implements the IPrivateKey interface. @@ -164,4 +173,13 @@ class Ed25519PrivateKey implements IPrivateKey { String toHex({bool lowerCase = true, String? prefix = ""}) { return BytesUtils.toHexString(raw, lowerCase: lowerCase, prefix: prefix); } + + @override + operator ==(other) { + if (other is! Ed25519PrivateKey) return false; + return _privateKey == other._privateKey && curveType == other.curveType; + } + + @override + int get hashCode => _privateKey.hashCode ^ curveType.hashCode; } diff --git a/lib/bip/ecc/keys/ed25519_kholaw_keys.dart b/lib/bip/ecc/keys/ed25519_kholaw_keys.dart index ff80a00..2115249 100644 --- a/lib/bip/ecc/keys/ed25519_kholaw_keys.dart +++ b/lib/bip/ecc/keys/ed25519_kholaw_keys.dart @@ -41,8 +41,7 @@ class Ed25519KholawPublicKey implements IPublicKey { Ed25519KholawPublicKey.fromBytes(keyBytes); return true; - // ignore: empty_catches - } catch (e) {} + } catch (_) {} return false; } @@ -93,6 +92,15 @@ class Ed25519KholawPublicKey implements IPublicKey { } return BytesUtils.toHexString(key, prefix: prefix, lowerCase: lowerCase); } + + @override + operator ==(other) { + if (other is! Ed25519KholawPublicKey) return false; + return _publicKey == other._publicKey && curve == other.curve; + } + + @override + int get hashCode => _publicKey.hashCode ^ curve.hashCode; } /// A class representing an Ed25519-Kholaw private key that implements the IPrivateKey interface. @@ -124,8 +132,7 @@ class Ed25519KholawPrivateKey implements IPrivateKey { Ed25519KholawPrivateKey.fromBytes(keyBytes); return true; - // ignore: empty_catches - } catch (e) {} + } catch (_) {} return false; } @@ -157,4 +164,13 @@ class Ed25519KholawPrivateKey implements IPrivateKey { String toHex({bool lowerCase = true, String? prefix = ""}) { return BytesUtils.toHexString(raw, lowerCase: lowerCase, prefix: prefix); } + + @override + operator ==(other) { + if (other is! Ed25519KholawPrivateKey) return false; + return _privateKey == other._privateKey && curveType == other.curveType; + } + + @override + int get hashCode => _privateKey.hashCode ^ curveType.hashCode; } diff --git a/lib/bip/ecc/keys/ed25519_monero_keys.dart b/lib/bip/ecc/keys/ed25519_monero_keys.dart index dacb726..8b461f3 100644 --- a/lib/bip/ecc/keys/ed25519_monero_keys.dart +++ b/lib/bip/ecc/keys/ed25519_monero_keys.dart @@ -132,6 +132,15 @@ class Ed25519MoneroPublicKey implements IPublicKey { } return BytesUtils.toHexString(key, prefix: prefix, lowerCase: lowerCase); } + + @override + operator ==(other) { + if (other is! Ed25519MoneroPublicKey) return false; + return _publicKey == other._publicKey && curve == other.curve; + } + + @override + int get hashCode => _publicKey.hashCode ^ curve.hashCode; } /// A class representing an Ed25519 Monero-compatible private key that implements the IPrivateKey interface. @@ -192,4 +201,13 @@ class Ed25519MoneroPrivateKey implements IPrivateKey { String toHex({bool lowerCase = true, String? prefix = ""}) { return BytesUtils.toHexString(raw, lowerCase: lowerCase, prefix: prefix); } + + @override + operator ==(other) { + if (other is! Ed25519MoneroPrivateKey) return false; + return _privateKey == other._privateKey && curveType == other.curveType; + } + + @override + int get hashCode => _privateKey.hashCode ^ curveType.hashCode; } diff --git a/lib/bip/ecc/keys/nist256p1_keys.dart b/lib/bip/ecc/keys/nist256p1_keys.dart index 2856248..839a180 100644 --- a/lib/bip/ecc/keys/nist256p1_keys.dart +++ b/lib/bip/ecc/keys/nist256p1_keys.dart @@ -128,6 +128,15 @@ class Nist256p1PublicKey implements IPublicKey { return BytesUtils.toHexString(compressed, prefix: prefix, lowerCase: lowerCase); } + + @override + operator ==(other) { + if (other is! Nist256p1PublicKey) return false; + return publicKey == other.publicKey && curve == other.curve; + } + + @override + int get hashCode => publicKey.hashCode ^ curve.hashCode; } /// A class representing a NIST P-256 private key that implements the IPrivateKey interface. @@ -181,4 +190,13 @@ class Nist256p1PrivateKey implements IPrivateKey { String toHex({bool lowerCase = true, String? prefix = ""}) { return BytesUtils.toHexString(raw, lowerCase: lowerCase, prefix: prefix); } + + @override + operator ==(other) { + if (other is! Nist256p1PrivateKey) return false; + return privateKey == other.privateKey && curveType == other.curveType; + } + + @override + int get hashCode => privateKey.hashCode ^ curveType.hashCode; } diff --git a/lib/bip/ecc/keys/secp256k1_keys_ecdsa.dart b/lib/bip/ecc/keys/secp256k1_keys_ecdsa.dart index 8d6db50..7317630 100644 --- a/lib/bip/ecc/keys/secp256k1_keys_ecdsa.dart +++ b/lib/bip/ecc/keys/secp256k1_keys_ecdsa.dart @@ -75,6 +75,15 @@ class Secp256k1PublicKeyEcdsa implements IPublicKey { return BytesUtils.toHexString(compressed, prefix: prefix, lowerCase: lowerCase); } + + @override + operator ==(other) { + if (other is! Secp256k1PublicKeyEcdsa) return false; + return publicKey == other.publicKey && curve == other.curve; + } + + @override + int get hashCode => publicKey.hashCode ^ curve.hashCode; } /// A class representing a Secp256k1 private key using the ECDSA algorithm that implements the IPrivateKey interface. @@ -128,4 +137,13 @@ class Secp256k1PrivateKeyEcdsa implements IPrivateKey { String toHex({bool lowerCase = true, String? prefix = ""}) { return BytesUtils.toHexString(raw, lowerCase: lowerCase, prefix: prefix); } + + @override + operator ==(other) { + if (other is! Secp256k1PrivateKeyEcdsa) return false; + return privateKey == other.privateKey && curveType == other.curveType; + } + + @override + int get hashCode => privateKey.hashCode ^ curveType.hashCode; } diff --git a/lib/bip/ecc/keys/sr25519_keys.dart b/lib/bip/ecc/keys/sr25519_keys.dart index 59d65f3..3e18ebb 100644 --- a/lib/bip/ecc/keys/sr25519_keys.dart +++ b/lib/bip/ecc/keys/sr25519_keys.dart @@ -77,6 +77,15 @@ class Sr25519PublicKey implements IPublicKey { return BytesUtils.toHexString(compressed, prefix: prefix, lowerCase: lowerCase); } + + @override + operator ==(other) { + if (other is! Sr25519PublicKey) return false; + return publicKey == other.publicKey && curve == other.curve; + } + + @override + int get hashCode => publicKey.hashCode ^ curve.hashCode; } /// A class representing an Sr25519 private key that implements the IPrivateKey interface. @@ -129,4 +138,13 @@ class Sr25519PrivateKey implements IPrivateKey { String toHex({bool lowerCase = true, String? prefix = ""}) { return BytesUtils.toHexString(raw, lowerCase: lowerCase, prefix: prefix); } + + @override + operator ==(other) { + if (other is! Sr25519PrivateKey) return false; + return secretKey == other.secretKey && curveType == other.curveType; + } + + @override + int get hashCode => secretKey.hashCode ^ curveType.hashCode; } diff --git a/lib/bip/mnemonic/mnemonic_ex.dart b/lib/bip/mnemonic/mnemonic_ex.dart index c8fc45b..5b723e9 100644 --- a/lib/bip/mnemonic/mnemonic_ex.dart +++ b/lib/bip/mnemonic/mnemonic_ex.dart @@ -1,17 +1,7 @@ import 'package:blockchain_utils/exception/exception.dart'; /// An exception representing an error related to mnemonic. -class MnemonicException implements BlockchainUtilsException { - @override - final String message; - - @override - final Map? details; - - const MnemonicException(this.message, {this.details}); - - @override - String toString() { - return message; - } +class MnemonicException extends BlockchainUtilsException { + const MnemonicException(String message, {Map? details}) + : super(message, details: details); } diff --git a/lib/bip/monero/monero_exc.dart b/lib/bip/monero/monero_exc.dart index 8f5821e..8ff20cc 100644 --- a/lib/bip/monero/monero_exc.dart +++ b/lib/bip/monero/monero_exc.dart @@ -3,21 +3,7 @@ import 'package:blockchain_utils/exception/exception.dart'; /// An exception class representing an error related to Monero keys. /// /// This exception class is used to represent errors and exceptions related to Monero keys. -class MoneroKeyError implements BlockchainUtilsException { - @override - final String message; - - @override - final Map? details; - - /// Constructs a MoneroKeyError with an optional error message. - /// - /// [message]: An optional error message describing the key-related issue. - const MoneroKeyError(this.message, {this.details}); - - /// Returns a string representation of the exception. - @override - String toString() { - return message; - } +class MoneroKeyError extends BlockchainUtilsException { + const MoneroKeyError(String message, {Map? details}) + : super(message, details: details); } diff --git a/lib/bip/substrate/exception/substrate_ex.dart b/lib/bip/substrate/exception/substrate_ex.dart index 2b52f92..c2e460b 100644 --- a/lib/bip/substrate/exception/substrate_ex.dart +++ b/lib/bip/substrate/exception/substrate_ex.dart @@ -1,37 +1,13 @@ import 'package:blockchain_utils/exception/exception.dart'; /// An exception class representing an error related to Substrate keys. -class SubstrateKeyError implements BlockchainUtilsException { - /// The error message associated with this exception. - @override - final String message; - - @override - final Map? details; - - /// Creates a new instance of [SubstrateKeyError] with an optional [message]. - const SubstrateKeyError(this.message, {this.details}); - - @override - String toString() { - return message; - } +class SubstrateKeyError extends BlockchainUtilsException { + const SubstrateKeyError(String message, {Map? details}) + : super(message, details: details); } /// An exception class representing an error related to Substrate paths. -class SubstratePathError implements BlockchainUtilsException { - /// The error message associated with this exception. - @override - final String message; - - @override - final Map? details; - - /// Creates a new instance of [SubstratePathError] with an optional [message]. - const SubstratePathError(this.message, {this.details}); - - @override - String toString() { - return message; - } +class SubstratePathError extends BlockchainUtilsException { + const SubstratePathError(String message, {Map? details}) + : super(message, details: details); } diff --git a/lib/blockchain_utils.dart b/lib/blockchain_utils.dart index cd8b1be..03392d9 100644 --- a/lib/blockchain_utils.dart +++ b/lib/blockchain_utils.dart @@ -83,3 +83,5 @@ export 'exception/exceptions.dart'; export 'utils/compare/compare.dart'; export 'layout/layout.dart'; + +export 'helper/helper.dart'; diff --git a/lib/cbor/types/bytes.dart b/lib/cbor/types/bytes.dart index 9a4653b..72b9e76 100644 --- a/lib/cbor/types/bytes.dart +++ b/lib/cbor/types/bytes.dart @@ -1,3 +1,4 @@ +import 'package:blockchain_utils/helper/helper.dart'; import 'package:blockchain_utils/utils/utils.dart'; import 'package:blockchain_utils/cbor/utils/dynamic_bytes.dart'; import 'package:blockchain_utils/cbor/core/tags.dart'; @@ -7,7 +8,7 @@ import 'package:blockchain_utils/cbor/core/cbor.dart'; class CborBytesValue implements CborObject { /// Constructor for creating a CborBytesValue instance with the provided parameters. /// It accepts the bytes value. - const CborBytesValue(this.value); + CborBytesValue(List value) : value = value.asImmutableBytes; /// The value as a List. @override @@ -45,9 +46,9 @@ class CborBytesValue implements CborObject { class CborDynamicBytesValue implements CborObject { /// Constructor for creating a CborDynamicBytesValue instance with the provided parameters. /// It accepts the bytes value. - const CborDynamicBytesValue(this.value); + CborDynamicBytesValue(List> value) + : value = value.map((e) => e.asImmutableBytes).toList().immutable; - /// The value as a List>. @override final List> value; diff --git a/lib/cbor/types/cbor_tag.dart b/lib/cbor/types/cbor_tag.dart index 5172be0..3789e5a 100644 --- a/lib/cbor/types/cbor_tag.dart +++ b/lib/cbor/types/cbor_tag.dart @@ -1,3 +1,4 @@ +import 'package:blockchain_utils/helper/helper.dart'; import 'package:blockchain_utils/utils/utils.dart'; import 'package:blockchain_utils/cbor/utils/dynamic_bytes.dart'; import 'package:blockchain_utils/cbor/core/cbor.dart'; @@ -6,14 +7,15 @@ import 'package:blockchain_utils/cbor/core/cbor.dart'; class CborTagValue implements CborObject { /// Constructor for creating a CborBoleanValue instance with the provided parameters. /// It accepts the all encodable cbor value. - CborTagValue(this.value, List tags) - : tags = List.unmodifiable(tags); + CborTagValue(T value, List tags) + : _value = value, + tags = tags.immutable; final List tags; - /// The value as a T. + final T _value; @override - final T value; + T get value => _value; /// Encode the value into CBOR bytes @override diff --git a/lib/cbor/types/list.dart b/lib/cbor/types/list.dart index 21a1e6f..f43157e 100644 --- a/lib/cbor/types/list.dart +++ b/lib/cbor/types/list.dart @@ -1,3 +1,4 @@ +import 'package:blockchain_utils/helper/helper.dart'; import 'package:blockchain_utils/utils/utils.dart'; import 'package:blockchain_utils/cbor/utils/dynamic_bytes.dart'; import 'package:blockchain_utils/cbor/core/tags.dart'; @@ -8,12 +9,16 @@ class CborListValue implements CborObject { /// Constructor for creating a CborListValue instance with the provided parameters. /// It accepts the List of all cbor encodable value. /// - CborListValue.fixedLength(this.value) : _isFixedLength = true; + CborListValue.fixedLength(List value) + : value = value.immutable, + _isFixedLength = true; /// Constructor for creating a CborListValue instance with the provided parameters. /// It accepts the List of all cbor encodable value. /// this method encode values with indefinite tag. - CborListValue.dynamicLength(this.value) : _isFixedLength = false; + CborListValue.dynamicLength(List value) + : value = value.immutable, + _isFixedLength = false; /// value as List @override diff --git a/lib/cbor/types/map.dart b/lib/cbor/types/map.dart index 2a60389..19aa0db 100644 --- a/lib/cbor/types/map.dart +++ b/lib/cbor/types/map.dart @@ -1,3 +1,4 @@ +import 'package:blockchain_utils/helper/helper.dart'; import 'package:blockchain_utils/utils/utils.dart'; import 'package:blockchain_utils/cbor/utils/dynamic_bytes.dart'; import 'package:blockchain_utils/cbor/core/tags.dart'; @@ -7,12 +8,16 @@ import 'package:blockchain_utils/cbor/core/cbor.dart'; class CborMapValue implements CborObject { /// Constructor for creating a CborMapValue instance with the provided parameters. /// It accepts the Map with all cbor encodable key and value. - CborMapValue.fixedLength(this.value) : _isFixedLength = true; + CborMapValue.fixedLength(Map value) + : value = value.immutable, + _isFixedLength = true; /// Constructor for creating a CborMapValue instance with the provided parameters. /// It accepts the Map with all cbor encodable key and value. /// this method encode values with indefinite tag. - CborMapValue.dynamicLength(this.value) : _isFixedLength = false; + CborMapValue.dynamicLength(Map value) + : value = value.immutable, + _isFixedLength = false; /// value as Map @override diff --git a/lib/cbor/types/set.dart b/lib/cbor/types/set.dart index 18fbf5b..f11655b 100644 --- a/lib/cbor/types/set.dart +++ b/lib/cbor/types/set.dart @@ -7,7 +7,7 @@ import 'package:blockchain_utils/cbor/core/cbor.dart'; class CborSetValue implements CborObject { /// Constructor for creating a CborSetValue instance with the provided parameters. /// It accepts a set of all encodable cbor object. - CborSetValue(this.value); + CborSetValue(Set value) : value = Set.unmodifiable(value); /// value as set @override diff --git a/lib/cbor/types/string.dart b/lib/cbor/types/string.dart index e88ba26..b022d96 100644 --- a/lib/cbor/types/string.dart +++ b/lib/cbor/types/string.dart @@ -1,3 +1,4 @@ +import 'package:blockchain_utils/helper/helper.dart'; import 'package:blockchain_utils/utils/utils.dart'; import 'package:blockchain_utils/cbor/utils/dynamic_bytes.dart'; import 'package:blockchain_utils/cbor/core/tags.dart'; @@ -58,7 +59,7 @@ class CborStringValue extends CborString { class CborIndefiniteStringValue extends CborString { /// Constructor for creating a CborStringValue instance with the provided parameters. /// It accepts a List value. - CborIndefiniteStringValue(this.value); + CborIndefiniteStringValue(List value) : value = value.immutable; /// value as List @override diff --git a/lib/crypto/crypto/cdsa/ecdsa/private_key.dart b/lib/crypto/crypto/cdsa/ecdsa/private_key.dart index f093023..db2d65d 100644 --- a/lib/crypto/crypto/cdsa/ecdsa/private_key.dart +++ b/lib/crypto/crypto/cdsa/ecdsa/private_key.dart @@ -1,5 +1,4 @@ import 'dart:typed_data'; - import 'package:blockchain_utils/exception/exception.dart'; import 'package:blockchain_utils/utils/utils.dart'; import 'package:blockchain_utils/crypto/crypto/cdsa/ecdsa/signature.dart'; diff --git a/lib/crypto/crypto/cdsa/eddsa/privatekey.dart b/lib/crypto/crypto/cdsa/eddsa/privatekey.dart index b3f45de..8d31c19 100644 --- a/lib/crypto/crypto/cdsa/eddsa/privatekey.dart +++ b/lib/crypto/crypto/cdsa/eddsa/privatekey.dart @@ -1,4 +1,5 @@ import 'dart:typed_data'; +import 'package:blockchain_utils/helper/helper.dart'; import 'package:blockchain_utils/utils/utils.dart'; import 'package:blockchain_utils/crypto/crypto/cdsa/curve/curves.dart'; import 'package:blockchain_utils/crypto/crypto/cdsa/eddsa/publickey.dart'; @@ -16,8 +17,8 @@ class EDDSAPrivateKey { final EDDSAPublicKey publicKey; EDDSAPrivateKey._(this.generator, this.baselen, List privateKey, this._secret, List? extendedKey) - : _privateKey = BytesUtils.toBytes(privateKey, unmodifiable: true), - _extendedKey = BytesUtils.tryToBytes(extendedKey, unmodifiable: true), + : _privateKey = privateKey.asImmutableBytes, + _extendedKey = extendedKey?.asImmutableBytes, publicKey = EDDSAPublicKey(generator, (generator * _secret).toBytes()); /// Creates an EdDSA private key from a random value using a provided hash method. @@ -99,7 +100,7 @@ class EDDSAPrivateKey { hLog = 3; } else { throw const ArgumentException( - 'Only cofactor 4 and 8 curves are supported'); + 'Invalid private key. Only cofactor 4 and 8 curves are supported'); } key[0] &= ~((1 << hLog) - 1); diff --git a/lib/crypto/crypto/cdsa/eddsa/publickey.dart b/lib/crypto/crypto/cdsa/eddsa/publickey.dart index c9896cc..fbdb343 100644 --- a/lib/crypto/crypto/cdsa/eddsa/publickey.dart +++ b/lib/crypto/crypto/cdsa/eddsa/publickey.dart @@ -1,4 +1,5 @@ import 'dart:typed_data'; +import 'package:blockchain_utils/helper/helper.dart'; import 'package:blockchain_utils/utils/utils.dart'; import 'package:blockchain_utils/crypto/crypto/cdsa/curve/curves.dart'; import 'package:blockchain_utils/crypto/crypto/cdsa/point/edwards.dart'; @@ -21,7 +22,7 @@ class EDDSAPublicKey { EDDSAPublicKey._( this.generator, List _encoded, this.baselen, this._point) - : _encoded = BytesUtils.toBytes(_encoded, unmodifiable: true); + : _encoded = _encoded.asImmutableBytes; /// Creates an EdDSA public key from a generator, encoded public key bytes, and an optional public point. /// diff --git a/lib/crypto/crypto/cdsa/point/ec_projective_point.dart b/lib/crypto/crypto/cdsa/point/ec_projective_point.dart index 357b3fb..f66476f 100644 --- a/lib/crypto/crypto/cdsa/point/ec_projective_point.dart +++ b/lib/crypto/crypto/cdsa/point/ec_projective_point.dart @@ -278,17 +278,6 @@ class ProjectiveECCPoint extends AbstractPoint { return normalizedY; } - /// Scales the projective point's coordinates. - /// - /// If the current z-coordinate is already one, there's no need to scale the point. - /// - /// [currentX], [currentY], and [currentZ] represent the current coordinates. - /// [primeField] is the prime field value of the curve. - /// [zInverse] is the modular inverse of the current z-coordinate. - /// [zInverseSquared] is the square of the z-inverse. - /// - /// The x and y coordinates are scaled using [zInverseSquared]. - /// /// The coordinates are updated to the scaled values, and the scaled point is returned. ProjectiveECCPoint scale() { final currentZ = _coords[2]; diff --git a/lib/crypto/crypto/cdsa/utils/exp.dart b/lib/crypto/crypto/cdsa/utils/exp.dart index 02164b1..f2352b5 100644 --- a/lib/crypto/crypto/cdsa/utils/exp.dart +++ b/lib/crypto/crypto/cdsa/utils/exp.dart @@ -5,19 +5,9 @@ import 'package:blockchain_utils/exception/exception.dart'; /// This exception is thrown when there is an issue with computing the square root /// of a BigInt value within the defined constraints. /// -class SquareRootError implements BlockchainUtilsException { - @override - final String message; - - @override - final Map? details; - - const SquareRootError(this.message, {this.details}); - - @override - String toString() { - return message; - } +class SquareRootError extends BlockchainUtilsException { + const SquareRootError(String message, {Map? details}) + : super(message, details: details); } /// An exception class for errors related to Jacobi symbol calculations. @@ -25,17 +15,7 @@ class SquareRootError implements BlockchainUtilsException { /// This exception is thrown when there is an issue with computing the Jacobi symbol /// for a pair of BigInt values within the defined constraints. /// -class JacobiError implements BlockchainUtilsException { - @override - final String message; - - @override - final Map? details; - - const JacobiError(this.message, {this.details}); - - @override - String toString() { - return message; - } +class JacobiError extends BlockchainUtilsException { + const JacobiError(String message, {Map? details}) + : super(message, details: details); } diff --git a/lib/crypto/crypto/schnorrkel/keys/keys.dart b/lib/crypto/crypto/schnorrkel/keys/keys.dart index 334f976..817d92e 100644 --- a/lib/crypto/crypto/schnorrkel/keys/keys.dart +++ b/lib/crypto/crypto/schnorrkel/keys/keys.dart @@ -675,6 +675,15 @@ class SchnorrkelSecretKey { return VRFProof._(c, s); } + + @override + operator ==(other) { + if (other is! SchnorrkelSecretKey) return false; + return BytesUtils.bytesEqual(_key, other._key); + } + + @override + int get hashCode => _key.fold(0, (c, p) => p ^ c).hashCode; } /// Represents a Schnorrkel public key used for verifying signatures. @@ -949,6 +958,15 @@ class SchnorrkelPublicKey { RistrettoPoint.fromUniform(script.toBytes("VRFHash".codeUnits, 64)); return hashPoint; } + + @override + operator ==(other) { + if (other is! SchnorrkelPublicKey) return false; + return BytesUtils.bytesEqual(_publicKey, other._publicKey); + } + + @override + int get hashCode => _publicKey.fold(0, (c, p) => p ^ c).hashCode; } /// Represents a Schnorrkel key pair, consisting of a secret key and a public key. diff --git a/lib/exception/exception.dart b/lib/exception/exception.dart index 572085d..41878c8 100644 --- a/lib/exception/exception.dart +++ b/lib/exception/exception.dart @@ -2,55 +2,35 @@ /// This class serves as a base for custom exceptions related to blockchain utility operations. abstract class BlockchainUtilsException implements Exception { /// Abstract field to hold the exception message. - abstract final String message; + final String message; - abstract final Map? details; + final Map? details; - const BlockchainUtilsException(); + const BlockchainUtilsException(this.message, {this.details}); - /// Override the 'toString' method to provide a custom string representation of the exception. @override String toString() { - return message; + final infos = Map.fromEntries( + details?.entries.where((element) => element.value != null) ?? []); + if (infos.isEmpty) return "$runtimeType($message)"; + final String msg = + "$message ${infos.entries.map((e) => "${e.key}: ${e.value}").join(", ")}"; + return "$runtimeType($msg)"; } } /// A specific exception class 'ArgumentException' that extends 'BlockchainUtilsException'. /// This exception is used to represent errors related to invalid arguments in blockchain utility operations. -class ArgumentException implements BlockchainUtilsException { +class ArgumentException extends BlockchainUtilsException { /// Constructor to initialize the exception with a specific message. - const ArgumentException(this.message, {this.details}); - - @override - final Map? details; - - /// Final field to store the exception message. - @override - final String message; - - /// Override the 'toString' method to provide a custom string representation of the exception. - @override - String toString() { - return message; - } + const ArgumentException(String message, {Map? details}) + : super(message, details: details); } /// Another specific exception class 'MessageException' that extends 'BlockchainUtilsException'. /// This exception is used to represent errors related to messages in blockchain utility operations. -class MessageException implements BlockchainUtilsException { +class MessageException extends BlockchainUtilsException { /// Constructor to initialize the exception with a specific message. - const MessageException(this.message, {this.details}); - - /// Final field to store the exception message. - @override - final String message; - - /// Override the 'toString' method to provide a custom string representation of the exception. - @override - String toString() { - return "$message${details == null ? '' : " $details"}"; - } - - @override - final Map? details; + const MessageException(String message, {Map? details}) + : super(message, details: details); } diff --git a/lib/exception/rpc_error.dart b/lib/exception/rpc_error.dart index f16d09e..038dd7e 100644 --- a/lib/exception/rpc_error.dart +++ b/lib/exception/rpc_error.dart @@ -1,41 +1,40 @@ import 'exception.dart'; /// An exception class representing errors that occur during RPC (Remote Procedure Call) communication. -class RPCError implements BlockchainUtilsException { +class RPCError extends BlockchainUtilsException { /// Creates an instance of [RPCError]. /// /// The [errorCode] parameter represents the error code associated with the RPC error. /// The [message] parameter provides a human-readable description of the error. - /// The [data] parameter contains additional data associated with the error. /// The optional [request] parameter holds the details of the RPC request that resulted in the error. const RPCError( - {required this.message, + {required String message, required this.errorCode, - required this.data, - required this.request, - this.details}); + this.request, + Map? details}) + : super(message, details: details); /// The error code associated with the RPC error. - final int errorCode; - - /// A human-readable description of the RPC error. - @override - final String message; - - /// Additional data associated with the RPC error. - final dynamic data; + final int? errorCode; /// Details of the RPC request that resulted in the error. - final Map request; + final Map? request; - @override - final Map? details; - - /// Returns a string representation of the RPCError. - /// - /// The string includes the error code, error message, and the request details if available. @override String toString() { - return 'RPCError: got code $errorCode with msg "$message". ${data ?? ''}'; + final infos = Map.fromEntries( + details?.entries.where((element) => element.value != null) ?? []); + if (infos.isEmpty) { + if (errorCode == null) { + return 'RPCError: $message'; + } + return 'RPCError: got code $errorCode with message "$message".'; + } + final String msg = + "$message ${infos.entries.map((e) => "${e.key}: ${e.value}").join(", ")}"; + if (errorCode == null) { + return 'RPCError: $msg'; + } + return 'RPCError: got code $errorCode with message "$msg".'; } } diff --git a/lib/helper/extensions/extensions.dart b/lib/helper/extensions/extensions.dart new file mode 100644 index 0000000..6ac90b0 --- /dev/null +++ b/lib/helper/extensions/extensions.dart @@ -0,0 +1,88 @@ +import 'package:blockchain_utils/exception/exception.dart'; +import 'package:blockchain_utils/utils/utils.dart'; + +extension ListHelper on List { + List get immutable => List.unmodifiable(this); + List clone({bool immutable = false}) { + if (immutable) return this.immutable; + return List.from(this); + } + + List? get emptyAsNull => isEmpty ? null : this; +} + +extension ListIntHelper on List { + List get asBytes { + BytesUtils.validateBytes(this); + return this; + } + + List get asImmutableBytes { + BytesUtils.validateBytes(this); + return immutable; + } + + List get toImutableBytes { + return BytesUtils.toBytes(this, unmodifiable: true); + } + + List get toBytes { + return BytesUtils.toBytes(this); + } +} + +extension MapHelper on Map { + Map get immutable => Map.unmodifiable(this); + Map clone({bool immutable = false}) { + if (immutable) return this.immutable; + return Map.from(this); + } +} + +extension BigIntHelper on BigInt { + BigInt get asUint64 { + if (isNegative || this > maxU64) { + throw ArgumentException("Invalid Unsigned BigInt 64.", details: { + "excepted": maxU64.bitLength, + "bitLength": bitLength, + "value": toString() + }); + } + return this; + } + + BigInt get asInt64 { + if (this > maxInt64 || this < minInt64) { + throw ArgumentException("Invalid Signed BigInt 64.", details: { + "excepted": maxU64.bitLength, + "bitLength": bitLength, + "value": toString() + }); + } + return this; + } +} + +extension IntHelper on int { + int get asInt32 { + if (this > maxInt32 || this < minInt32) { + throw ArgumentException("Invalid Signed int 32.", details: { + "excepted": mask32.bitLength, + "bitLength": bitLength, + "value": toString() + }); + } + return this; + } + + int get asUint32 { + if (isNegative || this > maxUint32) { + throw ArgumentException("Invalid Unsigned int 32.", details: { + "excepted": mask32.bitLength, + "bitLength": bitLength, + "value": toString() + }); + } + return this; + } +} diff --git a/lib/helper/helper.dart b/lib/helper/helper.dart new file mode 100644 index 0000000..6ddc72e --- /dev/null +++ b/lib/helper/helper.dart @@ -0,0 +1 @@ +export 'extensions/extensions.dart'; diff --git a/lib/layout/byte/reader.dart b/lib/layout/byte/reader.dart index 8154ef5..6711236 100644 --- a/lib/layout/byte/reader.dart +++ b/lib/layout/byte/reader.dart @@ -18,7 +18,7 @@ class LayoutByteReader { final length = LayoutSerializationUtils.decodeLength(_maxOffset(offset, 12), sign: sign); if (!length.item2.isValidInt) { - throw LayoutException("compact value is too large for length."); + throw const LayoutException("compact value is too large for length."); } return Tuple(length.item1, length.item2.toInt()); } diff --git a/lib/layout/constant/constant.dart b/lib/layout/constant/constant.dart index 239a05c..49c8f9e 100644 --- a/lib/layout/constant/constant.dart +++ b/lib/layout/constant/constant.dart @@ -86,7 +86,7 @@ class LayoutConst { IntegerLayout(6, property: property, order: Endian.big); /// [BigIntLayout] (big-endian unsigned int layouts) interpreted as Numbers. - static BigIntLayout nu64be({String? property}) => + static BigIntLayout u64be({String? property}) => BigIntLayout(8, property: property, order: Endian.big); /// [IntegerLayout] (signed int layouts) spanning one byte. @@ -138,7 +138,7 @@ class LayoutConst { IntegerLayout(6, property: property, sign: true, order: Endian.big); /// [BigIntLayout] (big-endian signed int layouts) interpreted as Numbers. - static BigIntLayout ns64be({String? property}) => + static BigIntLayout s64be({String? property}) => BigIntLayout(8, property: property, sign: true, order: Endian.big); /// [DoubleLayout] (little-endian 32-bit floating point) values. @@ -162,6 +162,12 @@ class LayoutConst { {String? property, bool decodePrefixes = false}) => StructLayout(fields, property: property, decodePrefixes: decodePrefixes); + // /// [StructLayout] values. + static LazyStructLayout lazyStruct(List fields, + {String? property, bool decodePrefixes = false}) => + LazyStructLayout(fields, + property: property, decodePrefixes: decodePrefixes); + /// [SequenceLayout] values. static SequenceLayout seq(Layout elementLayout, Layout count, {String? property}) => @@ -188,15 +194,6 @@ class LayoutConst { static RawBytesLayout fixedBlobN(int len, {String? property}) => RawBytesLayout(len, property: property); - // /// [CString] values. - // static CStringLayout cstr({String? property}) => - // CStringLayout(property: property); - - // /// [UTF8Layout] values. - // static UTF8Layout utf8({int? maxSpan, String? property}) => - // UTF8Layout(maxSpan: maxSpan ?? -1, property: property); - - /// [Constant] values. static ConstantLayout constant(dynamic value, {String? property}) => ConstantLayout(value, property: property); static String nameWithProperty(String name, Layout? lo) { @@ -240,9 +237,15 @@ class LayoutConst { return OptionalLayout(u64(), property: property); } + static OptionalLayout optionalU32Be(Layout layout, + {String? property, bool keepSize = false}) { + return optional(layout, + keepSize: keepSize, discriminator: u32be(), property: property); + } + /// [OptionalLayout] static OptionalLayout optional(Layout layout, - {String? property, bool keepSize = false, Layout? discriminator}) { + {String? property, bool keepSize = false, BaseIntiger? discriminator}) { return OptionalLayout(layout, property: property, discriminator: discriminator, @@ -266,6 +269,10 @@ class LayoutConst { property: property); } + static CustomLayout boolean32Be({String? property}) { + return boolean(property: property, layout: u32be()); + } + /// [bool] 4 bytes values static CustomLayout boolean32({String? property}) { return boolean(property: property, layout: u32()); @@ -306,6 +313,86 @@ class LayoutConst { ); } + static Layout xdrString({String? property}) { + return CustomLayout, String>( + layout: xdrVecBytes(), + decoder: (bytes) => StringUtils.decode(bytes), + encoder: (src) => StringUtils.encode(src), + property: property); + } + + static Layout> xdrVecBytes({String? property}) { + final length = padding(u32be(property: "length"), propery: "length"); + final layout = struct([ + length, + XDRBytesLayout(offset(length, -length.span), property: 'data'), + ]); + return CustomLayout, List>( + layout: layout, + encoder: (data) { + return {"data": data}; + }, + decoder: (data) => data["data"], + property: property); + } + + static CustomLayout, Map> enum32Be( + List variants, { + String? property, + bool useKeyAndValue = true, + }) { + return rustEnum(variants, + discriminant: u32be(), + property: property, + useKeyAndValue: useKeyAndValue); + } + + static CustomLayout, Map> lazyEnumU32Be( + List variants, { + required String? property, + bool useKeyAndValue = true, + }) { + return lazyEnum(variants, + discriminant: u32be(), + property: property, + useKeyAndValue: useKeyAndValue); + } + + static CustomLayout, Map> lazyEnumS32Be( + List variants, { + required String? property, + bool useKeyAndValue = true, + }) { + return lazyEnum(variants, + discriminant: s32be(), + property: property, + useKeyAndValue: useKeyAndValue); + } + + static CustomLayout, Map> lazyEnum( + List variants, { + IntegerLayout? discriminant, + String? property, + bool useKeyAndValue = true, + }) { + final unionLayout = LazyUnion(discriminant ?? u8()); + variants + .asMap() + .forEach((index, variant) => unionLayout.addVariant(variant)); + return CustomLayout, Map>( + layout: unionLayout, + decoder: (value) { + if (useKeyAndValue) { + return {"key": value.keys.first, "value": value.values.first}; + } + return value; + }, + encoder: (src) { + return src; + }, + property: property); + } + /// enum values static CustomLayout, Map> rustEnum( List variants, { @@ -356,6 +443,19 @@ class LayoutConst { property: property); } + static CustomLayout xdrVec(Layout elementLayout, {String? property}) { + final length = padding(u32be(property: "length"), propery: "length"); + final layout = struct([ + length, + seq(elementLayout, offset(length, -length.span), property: 'values'), + ]); + return CustomLayout, dynamic>( + layout: layout, + encoder: (data) => {"values": data}, + decoder: (data) => data["values"], + property: property); + } + /// vectors static CustomLayout vec(Layout elementLayout, {String? property}) { final length = padding(u32(property: "length"), propery: "length"); @@ -377,7 +477,6 @@ class LayoutConst { /// factory for Rust vectors static CustomLayout rustVec(Layout elementLayout, {String? property}) { - // final length = u32(property: "length"); final length = padding(u32(property: "length"), propery: "length"); final paddingLayout = padding(u32(), propery: "padding_length"); @@ -397,7 +496,6 @@ class LayoutConst { /// map values static CustomLayout map(Layout keyLayout, Layout valueLayout, {String? property}) { - // final length = u32(property: "length"); final length = padding(u32(property: "length"), propery: "length"); final layout = struct([ length, @@ -440,7 +538,6 @@ class LayoutConst { ); } - /// arrays static CustomLayout array(Layout elementLayout, int length, {String? property}) { final layout = struct([ @@ -454,7 +551,6 @@ class LayoutConst { ); } - /// arrays with greedy size layout. static CustomLayout greedyArray(Layout elementLayout, {String? property}) { final layout = struct([ seq(elementLayout, greedy(elementSpan: elementLayout.span), diff --git a/lib/layout/core/core.dart b/lib/layout/core/core.dart index 1f498e6..4a023d9 100644 --- a/lib/layout/core/core.dart +++ b/lib/layout/core/core.dart @@ -11,9 +11,12 @@ export 'types/compact_bytes.dart'; export 'types/struct.dart'; export 'types/union.dart'; export 'types/raw.dart'; -export 'types/string.dart'; export 'types/map.dart'; export 'types/constant.dart'; export 'types/compact_layout.dart'; export 'types/padding_layout.dart'; export 'types/bit_sequence.dart'; +export 'types/xdr_bytes.dart'; +export 'types/lazy_union.dart'; +export 'types/lazy_struct.dart'; +// export 'types/'; diff --git a/lib/layout/core/core/core.dart b/lib/layout/core/core/core.dart index 68ecf0d..e3b36e5 100644 --- a/lib/layout/core/core/core.dart +++ b/lib/layout/core/core/core.dart @@ -49,6 +49,14 @@ The 3-Clause BSD License import 'package:blockchain_utils/layout/byte/byte_handler.dart'; import 'package:blockchain_utils/layout/exception/exception.dart'; +typedef LayoutFunc = Layout Function({String? property}); + +class LazyLayout { + final LayoutFunc layout; + final String? property; + const LazyLayout({required this.layout, required this.property}); +} + /// Base class for layout objects. /// /// **NOTE:** This is an abstract base class; you can create instances if it amuses you, diff --git a/lib/layout/core/types/lazy_struct.dart b/lib/layout/core/types/lazy_struct.dart new file mode 100644 index 0000000..d4ce8cf --- /dev/null +++ b/lib/layout/core/types/lazy_struct.dart @@ -0,0 +1,127 @@ +import 'package:blockchain_utils/layout/byte/byte_handler.dart'; +import 'package:blockchain_utils/layout/core/core/core.dart'; +import 'package:blockchain_utils/layout/core/types/padding_layout.dart'; +import 'package:blockchain_utils/layout/exception/exception.dart'; + +/// - [fields] : Initializer for [fields]. An error is raised if this contains a variable-length field for which a [property] is not defined. +/// - [property] (optional): Initializer for [property]. +/// - [decodePrefixes] (optional): Initializer for [decodePrefixes]. +/// +/// Throws [LayoutException] if [fields] contains an unnamed variable-length layout. +/// +class LazyStructLayout extends Layout> { + final List fields; + final bool decodePrefixes; + factory LazyStructLayout(List fields, + {String? property, bool decodePrefixes = false}) { + for (final field in fields) { + if (field.property == null) { + throw LayoutException("fields cannot contain unnamed layout", details: { + "property": property, + "fields": + fields.map((e) => "${e.runtimeType}: ${e.property}").join(", ") + }); + } + } + return LazyStructLayout._( + fields: fields, + span: -1, + decodePrefixes: decodePrefixes, + property: property); + } + + LazyStructLayout._({ + required List fields, + required int span, + required this.decodePrefixes, + String? property, + }) : fields = List.unmodifiable(fields), + super(span, property: property); + + @override + LazyStructLayout clone({String? newProperty}) { + return LazyStructLayout._( + span: span, + fields: fields, + property: newProperty, + decodePrefixes: decodePrefixes); + } + + @override + int getSpan(LayoutByteReader? bytes, {int offset = 0}) { + if (this.span >= 0) { + return this.span; + } + + int span = 0; + + try { + span = fields.fold(0, (span, field) { + final layout = field.layout(property: field.property); + final fsp = layout.getSpan(bytes, offset: offset); + offset += fsp; + + return span + fsp; + }); + } catch (_, s) { + throw LayoutException("indeterminate span", + details: {"property": property, "stack": s}); + } + + return span; + } + + @override + LayoutDecodeResult> decode(LayoutByteReader bytes, + {int offset = 0}) { + final Map result = {}; + int consumed = 0; + for (final field in fields) { + final layout = field.layout(property: field.property); + if (field.property != null) { + final decode = layout.decode(bytes, offset: offset); + consumed += decode.consumed; + result[field.property!] = decode.value; + } + offset += layout.getSpan(bytes, offset: offset); + if (decodePrefixes && bytes.length == offset) { + break; + } + } + + return LayoutDecodeResult(consumed: consumed, value: result); + } + + @override + int encode(Map source, LayoutByteWriter writer, + {int offset = 0}) { + final firstOffset = offset; + int lastOffset = 0; + int lastWrote = 0; + + for (final field in fields) { + final layout = field.layout(property: field.property); + int span = layout.span; + lastWrote = (span > 0) ? span : 0; + if (source.containsKey(field.property)) { + final value = source[field.property]; + lastWrote = layout.encode(value, writer, offset: offset); + if (span < 0) { + span = layout.getSpan(writer.reader, offset: offset); + } + } else { + if (span < 0 || field is! PaddingLayout) { + throw LayoutException("Struct Source not found.", details: { + "key": field.property, + "source": source, + "property": property + }); + } + } + lastOffset = offset; + offset += span; + } + + return (lastOffset + lastWrote) - firstOffset; + } +} diff --git a/lib/layout/core/types/lazy_union.dart b/lib/layout/core/types/lazy_union.dart new file mode 100644 index 0000000..df9e90d --- /dev/null +++ b/lib/layout/core/types/lazy_union.dart @@ -0,0 +1,199 @@ +import 'package:blockchain_utils/layout/byte/byte_handler.dart'; +import 'package:blockchain_utils/layout/core/core/core.dart'; +import 'package:blockchain_utils/layout/core/types/padding_layout.dart'; +import 'package:blockchain_utils/layout/exception/exception.dart'; +import 'numeric.dart'; + +class LazyVariantModel { + final LayoutFunc layout; + final String? property; + final int index; + const LazyVariantModel( + {required this.layout, required this.property, required this.index}); +} + +class LazyUnion extends Layout> { + final UnionLayoutDiscriminatorLayout discriminator; + final Map _registry = {}; + LazyUnion._( + {required this.discriminator, + required int span, + required String? property}) + : super(span, property: property); + factory LazyUnion(IntegerLayout discr, {String? property}) { + return LazyUnion._( + discriminator: + UnionLayoutDiscriminatorLayout(OffsetLayout(PaddingLayout(discr))), + span: -1, + property: property); + } + + @override + int getSpan(LayoutByteReader? bytes, {int offset = 0}) { + if (span >= 0) { + return span; + } + + final vlo = getVariant(bytes!, offset: offset); + if (vlo == null) { + throw LayoutException("unable to determine span for unrecognized variant", + details: {"property": property}); + } + + return vlo.getSpan(bytes, offset: offset); + } + + LazyVariantLayout? defaultGetSourceVariant(Map source) { + if (source.containsKey(discriminator.property)) { + final vlo = _registry[source[discriminator.property]]; + if (vlo != null && (source.containsKey(vlo.property))) { + return vlo; + } + } else { + for (final tag in _registry.keys) { + final vlo = _registry[tag]; + if (source.containsKey(vlo?.property)) { + return vlo; + } + } + } + throw LayoutException("unable to infer source variant", details: { + "property": property, + "discriminator": discriminator.property, + "sources": source.keys.map((e) => e.toString()).join(", ") + }); + } + + @override + LayoutDecodeResult> decode(LayoutByteReader bytes, + {int offset = 0}) { + final discr = discriminator.decode(bytes, offset: offset); + final clo = _registry[discr.value]; + if (clo == null) { + throw LayoutException("unable to determine layout.", + details: {"property": property, "layout": discr.value}); + } + Map result = {}; + int consumed = discr.consumed; + final decode = clo.decode(bytes, offset: offset); + result = decode.value; + consumed += decode.consumed; + return LayoutDecodeResult(consumed: consumed, value: result); + } + + @override + int encode(Map source, LayoutByteWriter writer, + {int offset = 0}) { + final vlo = defaultGetSourceVariant(source); + if (vlo == null) { + throw LayoutException("unable to determine source layout.", + details: {"property": property, "source": source}); + } + + return vlo.encode(source, writer, offset: offset); + } + + LazyVariantLayout addVariant(LazyVariantModel layout) { + final rv = LazyVariantLayout(union: this, layout: layout); + _registry[layout.index] = rv; + return rv; + } + + LazyVariantLayout? getVariant(LayoutByteReader variantBytes, + {int offset = 0}) { + int variant = discriminator.decode(variantBytes, offset: offset).value; + return _registry[variant]; + } + + @override + LazyUnion clone({String? newProperty}) { + final layout = LazyUnion._( + discriminator: discriminator, property: newProperty, span: span); + layout._registry.addAll(Map.from(_registry)); + return layout; + } +} + +class LazyVariantLayout extends Layout> { + final LazyUnion union; + final LazyVariantModel layout; + const LazyVariantLayout._( + {required this.union, + required this.layout, + required int span, + String? property}) + : super(span, property: property); + + factory LazyVariantLayout( + {required LazyUnion union, required LazyVariantModel layout}) { + return LazyVariantLayout._( + union: union, + span: union.span, + layout: layout, + property: layout.property); + } + + @override + int getSpan(LayoutByteReader? bytes, {int offset = 0}) { + if (!this.span.isNegative) { + return this.span; + } + int contentOffset = union.discriminator.layout.span; + + int span = 0; + span = layout + .layout(property: layout.property) + .getSpan(bytes, offset: offset + contentOffset); + return contentOffset + span; + } + + @override + LayoutDecodeResult> decode(LayoutByteReader bytes, + {int offset = 0}) { + if (this != union.getVariant(bytes, offset: offset)) { + throw LayoutException("variant mismatch", + details: {"property": property}); + } + + int contentOffset = union.discriminator.layout.span; + + final Map dest = {}; + int consumed = 0; + final result = layout + .layout(property: layout.property) + .decode(bytes, offset: offset + contentOffset); + dest[property!] = result.value; + consumed += result.consumed; + + return LayoutDecodeResult(consumed: consumed, value: dest); + } + + @override + int encode(Map source, LayoutByteWriter writer, + {int offset = 0}) { + int contentOffset = union.discriminator.layout.span; + if (!source.containsKey(property)) { + throw LayoutException("variant lacks property", + details: {"property": property}); + } + union.discriminator.encode(this.layout.index, writer, offset: offset); + int span = contentOffset; + + final layout = this.layout.layout(property: this.layout.property); + layout.encode(source[property], writer, offset: offset + contentOffset); + span += layout.getSpan(writer.reader, offset: offset + contentOffset); + + if (union.span >= 0 && span > union.span) { + throw LayoutException("encoded variant overruns containing union", + details: {"property": property}); + } + + return span; + } + + @override + LazyVariantLayout clone({String? newProperty}) { + return LazyVariantLayout._( + union: union, layout: layout, property: newProperty, span: span); + } +} diff --git a/lib/layout/core/types/optional_layout.dart b/lib/layout/core/types/optional_layout.dart index 135f9d6..04a4af8 100644 --- a/lib/layout/core/types/optional_layout.dart +++ b/lib/layout/core/types/optional_layout.dart @@ -5,21 +5,34 @@ import 'numeric.dart'; /// Represents a layout for optional values. class OptionalLayout extends Layout { + OptionalLayout._(this.layout, + {required this.discriminator, String? property, this.size}) + : super(-1, property: property); + /// Constructs an [OptionalLayout] with the specified layout and optional discriminator. /// /// - [layout] : The layout for the optional value. /// - [property] (optional): The property identifier. /// - [keepLayoutSize] (optional): Whether to keep the layout size. /// - [discriminator] (optional): The discriminator layout. - OptionalLayout(this.layout, - {Layout? discriminator, String? property, this.keepLayoutSize = false}) - : discriminator = discriminator ?? IntegerLayout(1), - super(-1, property: property); + factory OptionalLayout(Layout layout, + {BaseIntiger? discriminator, + String? property, + bool keepLayoutSize = false}) { + final BaseIntiger disc = discriminator ??= IntegerLayout(1); + if (keepLayoutSize && layout.span.isNegative) { + throw const LayoutException( + "keepLayoutSize works only with layouts that have a fixed size."); + } + late final int? size = keepLayoutSize ? layout.span + disc.span : null; + return OptionalLayout._(layout, + discriminator: disc, size: size, property: property); + } + final Layout layout; - final Layout discriminator; - final bool keepLayoutSize; - late final int? size = - keepLayoutSize ? layout.span + discriminator.span : null; + final BaseIntiger discriminator; + // final bool keepLayoutSize; + final int? size; static void _validateOption({int? value, String? property}) { if (value != 0 && value != 1) { throw LayoutException("Invalid option bytes.", @@ -34,7 +47,7 @@ class OptionalLayout extends Layout { return LayoutDecodeResult(consumed: size ?? decode.consumed, value: null); } _validateOption(property: property, value: decode.value); - final result = layout.decode(bytes, offset: offset + 1); + final result = layout.decode(bytes, offset: offset + decode.consumed); return LayoutDecodeResult( consumed: size ?? (decode.consumed + result.consumed), value: result.value as T?); @@ -46,8 +59,9 @@ class OptionalLayout extends Layout { return size ?? discriminator.encode(0, writer, offset: offset); } discriminator.encode(1, writer, offset: offset); - final encode = layout.encode(source, writer, offset: offset + 1); - return size ?? encode + 1; + final encode = + layout.encode(source, writer, offset: offset + discriminator.span); + return size ?? encode + discriminator.span; } @override @@ -55,16 +69,19 @@ class OptionalLayout extends Layout { if (size != null) return size!; final decode = discriminator.decode(bytes!, offset: offset); - if (decode.value == 0) return 1; + if (decode.value == 0) return discriminator.span; _validateOption(property: property, value: decode.value); - return layout.getSpan(bytes, offset: offset + 1) + 1; + return layout.getSpan(bytes, offset: offset + discriminator.span) + + discriminator.span; } @override OptionalLayout clone({String? newProperty}) { - return OptionalLayout(layout, - property: newProperty, - keepLayoutSize: keepLayoutSize, - discriminator: discriminator); + return OptionalLayout._( + layout, + property: newProperty, + discriminator: discriminator, + size: size, + ); } } diff --git a/lib/layout/core/types/string.dart b/lib/layout/core/types/string.dart deleted file mode 100644 index d8e199f..0000000 --- a/lib/layout/core/types/string.dart +++ /dev/null @@ -1,47 +0,0 @@ -// import 'package:blockchain_utils/layout/byte/byte_handler.dart'; -// import 'package:blockchain_utils/layout/core/core/core.dart'; -// import 'package:blockchain_utils/layout/exception/exception.dart'; -// import 'package:blockchain_utils/utils/utils.dart'; - -// /// A class representing a C-style string layout within a buffer. -// class CStringLayout extends Layout { -// /// Constructs a [CStringLayout] with the given [property]. -// const CStringLayout({String? property}) : super(-1, property: property); - -// @override -// int getSpan(LayoutByteReader? bytes, {int offset = 0}) { -// int idx = offset; -// while (idx < bytes!.length && bytes.at(idx) != 0) { -// idx += 1; -// } -// return 1 + idx - offset; -// } - -// @override -// LayoutDecodeResult decode(LayoutByteReader bytes, {int offset = 0}) { -// int span = getSpan(bytes, offset: offset); -// final totalLength = span - 1; -// final result = -// String.fromCharCodes(bytes.sublist(offset, offset + totalLength)); -// return LayoutDecodeResult(consumed: totalLength, value: result); -// } - -// @override -// int encode(String source, LayoutByteWriter writer, {int offset = 0}) { -// List srcBytes = StringUtils.encode(source); -// int span = srcBytes.length; -// if (!writer.growable && (offset + span) > writer.length) { -// throw LayoutException("Encoding overruns bytes", -// details: {"property": property}); -// } - -// writer.setRange(offset, offset + span, srcBytes); -// writer.set(offset + span, 0); -// return span + 1; -// } - -// @override -// CStringLayout clone({String? newProperty}) { -// return CStringLayout(property: newProperty); -// } -// } diff --git a/lib/layout/core/types/struct.dart b/lib/layout/core/types/struct.dart index 6f2b8dd..2cc7207 100644 --- a/lib/layout/core/types/struct.dart +++ b/lib/layout/core/types/struct.dart @@ -16,7 +16,7 @@ import 'package:blockchain_utils/layout/exception/exception.dart'; /// - [property] (optional): Initializer for [property]. /// - [decodePrefixes] (optional): Initializer for [decodePrefixes]. /// -/// Throws [MessageException] if [fields] contains an unnamed variable-length layout. +/// Throws [LayoutException] if [fields] contains an unnamed variable-length layout. /// class StructLayout extends Layout> { final List fields; @@ -80,9 +80,9 @@ class StructLayout extends Layout> { return span + fsp; }); - } catch (e, s) { + } catch (_, s) { throw LayoutException("indeterminate span", - details: {"property": property}, trace: s); + details: {"property": property, "stack": s}); } return span; diff --git a/lib/layout/core/types/xdr_bytes.dart b/lib/layout/core/types/xdr_bytes.dart new file mode 100644 index 0000000..e7d9b36 --- /dev/null +++ b/lib/layout/core/types/xdr_bytes.dart @@ -0,0 +1,58 @@ +import 'package:blockchain_utils/layout/byte/byte_handler.dart'; +import 'package:blockchain_utils/layout/core/core.dart'; +import 'package:blockchain_utils/layout/exception/exception.dart'; + +/// A class representing a blob layout within a buffer. +class XDRBytesLayout extends Layout> { + XDRBytesLayout._(this.layout, {String? property}) + : super(layout.span, property: property); + + final RawBytesLayout layout; + + /// Constructs a [XDRBytesLayout] with the given [length] and [property]. + /// + /// The [length] can be a positive integer or an unsigned integer [ExternalLayout]. + factory XDRBytesLayout(dynamic length, {String? property}) { + if (length is int) { + if (length.isNegative) { + throw LayoutException("The length must be a positive integer.", + details: {"property": property, "length": length}); + } + } else if (length is! ExternalLayout) { + throw LayoutException( + "The length can be a positive integer or an unsigned integer ExternalLayout", + details: {"property": property, "length": length}); + } + return XDRBytesLayout._(RawBytesLayout(length), property: property); + } + static int _reminder(int dividend) { + int remainder = dividend % 4; + return remainder == 0 ? 0 : 4 - remainder; + } + + @override + int getSpan(LayoutByteReader? bytes, {int offset = 0}) { + int span = layout.getSpan(bytes, offset: offset); + span += _reminder(span); + return span; + } + + @override + LayoutDecodeResult> decode(LayoutByteReader bytes, + {int offset = 0}) { + final result = layout.decode(bytes, offset: offset); + final span = result.consumed + _reminder(result.consumed); + return LayoutDecodeResult(consumed: span, value: result.value); + } + + @override + int encode(List source, LayoutByteWriter writer, {int offset = 0}) { + final span = layout.encode(source, writer, offset: offset); + return span + _reminder(span); + } + + @override + XDRBytesLayout clone({String? newProperty}) { + return XDRBytesLayout._(layout, property: newProperty); + } +} diff --git a/lib/layout/exception/exception.dart b/lib/layout/exception/exception.dart index a0c68b4..248969d 100644 --- a/lib/layout/exception/exception.dart +++ b/lib/layout/exception/exception.dart @@ -1,21 +1,6 @@ import 'package:blockchain_utils/exception/exceptions.dart'; -class LayoutException implements BlockchainUtilsException { - LayoutException(this.message, {this.trace, Map? details}) - : details = details == null - ? null - : Map.unmodifiable( - details..removeWhere((key, value) => value == null)); - @override - final String message; - final StackTrace? trace; - @override - final Map? details; - - @override - String toString() { - final detaillsValues = - details?.keys.map((e) => "$e: ${details![e]}").join(", ") ?? ""; - return "LayoutException: $message${detaillsValues.isEmpty ? '' : ' $detaillsValues'}"; - } +class LayoutException extends BlockchainUtilsException { + const LayoutException(String message, {Map? details}) + : super(message, details: details); } diff --git a/lib/secret_wallet/exception.dart b/lib/secret_wallet/exception.dart index e3a11a3..4aef257 100644 --- a/lib/secret_wallet/exception.dart +++ b/lib/secret_wallet/exception.dart @@ -1,13 +1,7 @@ import 'package:blockchain_utils/blockchain_utils.dart'; class Web3SecretStorageDefinationV3Exception extends BlockchainUtilsException { - /// The error message associated with this exception. - @override - final String message; - - @override - final Map? details; - - /// Creates a new instance of [Web3SecretStorageDefinationV3Exception] with an optional [message]. - const Web3SecretStorageDefinationV3Exception(this.message, {this.details}); + const Web3SecretStorageDefinationV3Exception(String message, + {Map? details}) + : super(message, details: details); } diff --git a/lib/ss58/ss58_ex.dart b/lib/ss58/ss58_ex.dart index 9f48670..0210619 100644 --- a/lib/ss58/ss58_ex.dart +++ b/lib/ss58/ss58_ex.dart @@ -3,18 +3,7 @@ import 'package:blockchain_utils/exception/exception.dart'; /// An exception class for errors related to SS58 address checksum validation. /// /// The [message] field can contain additional information about the error. -class SS58ChecksumError implements BlockchainUtilsException { - @override - final String message; - - @override - final Map? details; - - /// Creates a new [SS58ChecksumError] with an optional [message]. - const SS58ChecksumError(this.message, {this.details}); - - @override - String toString() { - return message; - } +class SS58ChecksumError extends BlockchainUtilsException { + const SS58ChecksumError(String message, {Map? details}) + : super(message, details: details); } diff --git a/lib/utils/binary/binary_operation.dart b/lib/utils/binary/binary_operation.dart index 672416d..e2b2e1b 100644 --- a/lib/utils/binary/binary_operation.dart +++ b/lib/utils/binary/binary_operation.dart @@ -118,3 +118,12 @@ final BigInt maskBig8 = BigInt.from(mask8); final BigInt maskBig16 = BigInt.from(mask16); final BigInt maskBig32 = BigInt.from(mask32); +final BigInt maskBig64 = BigInt.parse("FFFFFFFFFFFFFFFF", radix: 16); + +final BigInt minInt64 = BigInt.parse("-9223372036854775808"); +final BigInt maxInt64 = BigInt.parse("9223372036854775807"); + +const int maxInt32 = 2147483647; +const int minInt32 = -2147483648; + +const int maxUint32 = 4294967295; diff --git a/lib/utils/numbers/rational/big_rational.dart b/lib/utils/numbers/rational/big_rational.dart index f6907ed..6709203 100644 --- a/lib/utils/numbers/rational/big_rational.dart +++ b/lib/utils/numbers/rational/big_rational.dart @@ -1,6 +1,10 @@ import 'package:blockchain_utils/exception/exception.dart'; import 'package:blockchain_utils/utils/numbers/utils/bigint_utils.dart'; +class _BigRationalConst { + static const int maxScale = 20; +} + /// Represents a rational number with arbitrary precision using BigInt for the numerator and denominator. class BigRational { static final BigRational zero = BigRational.from(0); @@ -247,6 +251,26 @@ class BigRational { return BigRational._(floor, _one); } + /// Returns the floor of the BigRational as a BigRational with denominator 1 + BigRational floor() { + BigInt flooredNumerator; + + if (numerator.isNegative && denominator.isNegative) { + // Both numerator and denominator are negative: treat as positive + flooredNumerator = numerator.abs() ~/ denominator.abs(); + } else if (numerator.isNegative || denominator.isNegative) { + // One of them is negative: result will be negative + flooredNumerator = + (numerator.abs() ~/ denominator.abs()) * BigInt.from(-1) - BigInt.one; + } else { + // Both are positive: simple integer division + flooredNumerator = numerator ~/ denominator; + } + + // Return the floored value as a BigRational with denominator 1 + return BigRational(flooredNumerator, denominator: BigInt.one); + } + /// Rounds this BigRational towards zero and returns the result as a BigRational. BigRational operator ~() { if (denominator.isNegative) { @@ -326,12 +350,11 @@ class BigRational { static BigRational _reduce(BigInt n, BigInt d) { final BigInt divisor = _gcd(n, d); - final BigInt num = n ~/ divisor; - final BigInt denom = d ~/ divisor; + BigInt num = n ~/ divisor; + BigInt denom = d ~/ divisor; if (denom.isNegative) { return BigRational._(-num, -denom); } - return BigRational._(num, denom); } @@ -362,7 +385,7 @@ class BigRational { /// [digits] The number of digits after the decimal point (default is the scale of the BigRational). /// Returns a string representing the decimal value, with the specified number of digits after the decimal point. String toDecimal({int? digits}) { - if (_inDecimal != null) { + if (digits == null && _inDecimal != null) { return _inDecimal!; } digits ??= scale; @@ -423,6 +446,7 @@ class BigRational { while (r.denominator != BigInt.one) { scale++; r *= ten; + if (scale >= _BigRationalConst.maxScale) break; } return scale; } diff --git a/lib/utils/numbers/utils/bigint_utils.dart b/lib/utils/numbers/utils/bigint_utils.dart index c96722c..fd208be 100644 --- a/lib/utils/numbers/utils/bigint_utils.dart +++ b/lib/utils/numbers/utils/bigint_utils.dart @@ -8,6 +8,10 @@ import 'package:blockchain_utils/exception/exception.dart'; import 'int_utils.dart'; class BigintUtils { + static BigInt max(BigInt a, BigInt b) { + return a.compareTo(b) > 0 ? a : b; + } + /// Converts a BigInt 'num' into a List of bytes with a specified 'order'. /// /// This method converts 'num' into a hexadecimal string, ensuring it's at least @@ -185,16 +189,7 @@ class BigintUtils { /// Returns: /// A binary string representation of the `value`, possibly zero-padded. /// - /// Example: - /// ```dart - /// BigInt number = BigInt.parse('10'); - /// int bitLength = 8; // Desired bit length - /// String binaryString = toBinary(number, bitLength); - /// print('Binary representation: $binaryString'); - /// ``` - /// /// This method is useful for converting BigInt values to binary strings for various applications. - /// static String toBinary(BigInt value, int zeroPadBitLen) { String binaryStr = value.toRadixString(2); if (zeroPadBitLen > 0) { @@ -336,9 +331,8 @@ class BigintUtils { encodedIntegers.fold>([], (prev, e) => [...prev, ...e]); _encodeLength(200); var derBytes = [ - 0x30, ...lengthBytes, - - /// DER SEQUENCE tag and length + 0x30, + ...lengthBytes, ...contentBytes, ]; @@ -375,8 +369,8 @@ class BigintUtils { /// can't support negative numbers yet assert(r >= BigInt.zero); - List s = BigintUtils.toBytes(r, length: BigintUtils.orderLen(r)); - + final len = BigintUtils.orderLen(r); + List s = BigintUtils.toBytes(r, length: len); int num = s[0]; if (num <= 0x7F) { return [0x02, ..._encodeLength(s.length), ...s]; diff --git a/lib/utils/numbers/utils/int_utils.dart b/lib/utils/numbers/utils/int_utils.dart index f8ad3c1..137e0ae 100644 --- a/lib/utils/numbers/utils/int_utils.dart +++ b/lib/utils/numbers/utils/int_utils.dart @@ -99,11 +99,8 @@ class IntUtils { /// whether the most significant bytes are at the beginning (big-endian) or end /// (little-endian) of the resulting byte list. static List toBytes(int val, - {required int length, - Endian byteOrder = Endian.big, - int maxBytesLength = 6}) { - assert(maxBytesLength > 0 && maxBytesLength <= 8); - assert(length <= maxBytesLength); + {required int length, Endian byteOrder = Endian.big}) { + assert(length <= 8); if (length > 4) { int lowerPart = val & mask32; int upperPart = (val >> 32) & mask32; @@ -137,9 +134,8 @@ class IntUtils { /// [byteOrder] The byte order, defaults to Endian.big. /// Returns the corresponding integer value. static int fromBytes(List bytes, - {Endian byteOrder = Endian.big, bool sign = false, int maxBytes = 6}) { - assert(maxBytes > 0 && maxBytes <= 8); - assert(bytes.length <= maxBytes); + {Endian byteOrder = Endian.big, bool sign = false}) { + assert(bytes.length <= 8); if (byteOrder == Endian.little) { bytes = List.from(bytes.reversed.toList()); } @@ -188,8 +184,7 @@ class IntUtils { } return parse!; } - // ignore: empty_catches - } catch (e) {} + } catch (_) {} throw const ArgumentException("invalid input for parse int"); } diff --git a/lib/utils/string/string.dart b/lib/utils/string/string.dart index ae7ac91..6ac3f2a 100644 --- a/lib/utils/string/string.dart +++ b/lib/utils/string/string.dart @@ -24,6 +24,7 @@ enum StringEncoding { class StringUtils { static final RegExp _hexBytesRegex = RegExp(r'^(0x|0X)?([0-9A-Fa-f]{2})+$'); static final RegExp _hexaDecimalRegex = RegExp(r'^(0x|0X)?[0-9A-Fa-f]+$'); + static bool isHexBytes(String v) { return _hexBytesRegex.hasMatch(v); } @@ -43,12 +44,8 @@ class StringUtils { static List? tryToBytes(String? v) { if (v == null) return null; try { - if (isHexBytes(v)) { - return BytesUtils.fromHexString(v); - } else { - return encode(v); - } - } catch (e) { + return toBytes(v); + } catch (_) { return null; } } @@ -134,8 +131,15 @@ class StringUtils { /// Converts a Dart object represented as a Map to a JSON-encoded string. /// /// The input [data] is a Map representing the Dart object. - static String fromJson(Object data) { - return jsonEncode(data); + static String fromJson(Object data, + {String? indent, bool toStringEncodable = false}) { + if (indent != null) { + return JsonEncoder.withIndent( + indent, toStringEncodable ? (c) => c.toString() : null) + .convert(data); + } + return jsonEncode(data, + toEncodable: toStringEncodable ? (c) => c.toString() : null); } /// Converts a JSON-encoded string to a Dart object represented as a Map. @@ -154,9 +158,11 @@ class StringUtils { /// Converts a Dart object represented as a Map to a JSON-encoded string if possible. /// /// The input [data] is a Map representing the Dart object. - static String? tryFromJson(Object? data) { + static String? tryFromJson(Object? data, + {String? indent, bool toStringEncodable = false}) { try { - return fromJson(data!); + return fromJson(data!, + indent: indent, toStringEncodable: toStringEncodable); } catch (e) { return null; } @@ -166,10 +172,10 @@ class StringUtils { /// /// The input [data] is a JSON-encoded string. /// Returns a Map representing the Dart object. - static T? tryToJson(String data) { + static T? tryToJson(String? data) { try { - return toJson(data); - } catch (e) { + return toJson(data!); + } catch (_) { return null; } } diff --git a/lib/uuid/uuid.dart b/lib/uuid/uuid.dart index c8b1af2..8b87815 100644 --- a/lib/uuid/uuid.dart +++ b/lib/uuid/uuid.dart @@ -23,12 +23,6 @@ class UUID { /// /// Returns: /// A random UUIDv4 as a string in the format "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx". - /// - /// Example: - /// ```dart - /// final uuid = generateUUIDv4(); - /// print(uuid); /// Output: "550e8400-e29b-41d4-a716-446655440000" - /// ``` static String generateUUIDv4() { final random = math.Random.secure(); @@ -70,13 +64,6 @@ class UUID { /// /// Returns: /// A binary buffer (List) representing the UUID. - /// - /// Example: - /// ```dart - /// final uuid = '550e8400-e29b-41d4-a716-446655440000'; - /// final buffer = toBuffer(uuid); - /// print(buffer); /// Output: [85, 14, 132, 0, 226, 155, 65, 212, 167, 22, 68, 102, 85, 68, 0, 0] - /// ``` static List toBuffer(String uuidString, {bool validate = true}) { if (validate && !isValidUUIDv4(uuidString)) { throw ArgumentException("invalid uuid string.", @@ -108,15 +95,8 @@ class UUID { /// Returns: /// A UUIDv4 string. /// - /// Example: - /// ```dart - /// final buffer = List.from([85, 14, 132, 0, 226, 155, 65, 212, 167, 22, 68, 102, 85, 68, 0, 0]); - /// final uuid = fromBuffer(buffer); - /// print(uuid); /// Output: '550e8400-e29b-41d4-a716-446655440000' - /// ``` - /// /// Throws: - /// - [Exception] if the input buffer's length is not 16 bytes, as UUIDv4 + /// - [ArgumentException] if the input buffer's length is not 16 bytes, as UUIDv4 /// buffers must be exactly 16 bytes long. /// /// Note: diff --git a/pubspec.yaml b/pubspec.yaml index 8542f2f..abfc819 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: blockchain_utils description: Comprehensive Crypto & Blockchain Toolkit, Pure Dart, Cross-Platform, Encoding, Cryptography, Addresses, Mnemonics, & More. -version: 3.3.0 +version: 3.4.0 homepage: "https://github.com/mrtnetwork/blockchain_utils" repository: "https://github.com/mrtnetwork/blockchain_utils" Author: mrhaydari.t@gmail.com @@ -21,7 +21,7 @@ dependencies: dev_dependencies: lints: ^4.0.0 - test: ^1.25.2 + test: ^1.25.8 flutter_lints: ^4.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/test/test_test.dart b/test/test_test.dart new file mode 100644 index 0000000..ab73b3a --- /dev/null +++ b/test/test_test.dart @@ -0,0 +1 @@ +void main() {} diff --git a/test/uuid_test.dart b/test/uuid_test.dart index 976c762..5c3c8cc 100644 --- a/test/uuid_test.dart +++ b/test/uuid_test.dart @@ -4,6 +4,7 @@ import 'package:test/test.dart'; void main() { List buffer1 = List.from( [174, 91, 168, 91, 107, 15, 78, 26, 181, 132, 151, 91, 160, 7, 157, 152]); + List buffer2 = List.from([ 42, 118,