import 'dart:convert'; import 'dart:ffi'; import 'dart:typed_data'; import 'package:frostdart/frostdart.dart'; import 'package:frostdart/frostdart_bindings_generated.dart'; import 'package:frostdart/output.dart'; import 'package:frostdart/util.dart'; import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart'; import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/extensions/extensions.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart'; abstract class Frost { //==================== utility =============================================== static List getParticipants({ required String multisigConfig, }) { try { final numberOfParticipants = multisigParticipants( multisigConfig: multisigConfig, ); final List participants = []; for (int i = 0; i < numberOfParticipants; i++) { participants.add( multisigParticipant( multisigConfig: multisigConfig, index: i, ), ); } return participants; } catch (e, s) { Logging.instance.log( "getParticipants failed: $e\n$s", level: LogLevel.Fatal, ); rethrow; } } static bool validateEncodedMultisigConfig({required String encodedConfig}) { try { decodeMultisigConfig(multisigConfig: encodedConfig); return true; } catch (e, s) { Logging.instance.log( "validateEncodedMultisigConfig failed: $e\n$s", level: LogLevel.Fatal, ); return false; } } static int getThreshold({ required String multisigConfig, }) { try { final threshold = multisigThreshold( multisigConfig: multisigConfig, ); return threshold; } catch (e, s) { Logging.instance.log( "getThreshold failed: $e\n$s", level: LogLevel.Fatal, ); rethrow; } } static ({ List<({String address, Amount amount})> recipients, String changeAddress, int feePerWeight, List inputs, }) extractDataFromSignConfig({ required String signConfig, required CryptoCurrency coin, }) { try { final network = coin.network == CryptoCurrencyNetwork.test ? Network.Testnet : Network.Mainnet; final signConfigPointer = decodedSignConfig( encodedConfig: signConfig, network: network, ); // get various data from config final feePerWeight = signFeePerWeight(signConfigPointer: signConfigPointer); final changeAddress = signChange(signConfigPointer: signConfigPointer); final recipientsCount = signPayments( signConfigPointer: signConfigPointer, ); // get tx recipient info final List<({String address, Amount amount})> recipients = []; for (int i = 0; i < recipientsCount; i++) { final String address = signPaymentAddress( signConfigPointer: signConfigPointer, index: i, ); final int amount = signPaymentAmount( signConfigPointer: signConfigPointer, index: i, ); recipients.add( ( address: address, amount: Amount( rawValue: BigInt.from(amount), fractionDigits: coin.fractionDigits, ), ), ); } // get utxos final count = signInputs(signConfigPointer: signConfigPointer); final List outputs = []; for (int i = 0; i < count; i++) { final output = signInput( signConfig: signConfig, index: i, network: network, ); outputs.add(output); } return ( recipients: recipients, changeAddress: changeAddress, feePerWeight: feePerWeight, inputs: outputs, ); } catch (e, s) { Logging.instance.log( "extractDataFromSignConfig failed: $e\n$s", level: LogLevel.Fatal, ); rethrow; } } //==================== wallet creation ======================================= static String createMultisigConfig({ required String name, required int threshold, required List participants, }) { try { final config = newMultisigConfig( name: name, threshold: threshold, participants: participants, ); return config; } catch (e, s) { Logging.instance.log( "createMultisigConfig failed: $e\n$s", level: LogLevel.Fatal, ); rethrow; } } static ({ String seed, String commitments, Pointer multisigConfigWithNamePtr, Pointer secretShareMachineWrapperPtr, }) startKeyGeneration({ required String multisigConfig, required String myName, }) { try { final startKeyGenResPtr = startKeyGen( multisigConfig: multisigConfig, myName: myName, language: Language.english, ); final seed = startKeyGenResPtr.ref.seed.toDartString(); final commitments = startKeyGenResPtr.ref.commitments.toDartString(); final configWithNamePtr = startKeyGenResPtr.ref.config; final machinePtr = startKeyGenResPtr.ref.machine; return ( seed: seed, commitments: commitments, multisigConfigWithNamePtr: configWithNamePtr, secretShareMachineWrapperPtr: machinePtr, ); } catch (e, s) { Logging.instance.log( "startKeyGeneration failed: $e\n$s", level: LogLevel.Fatal, ); rethrow; } } static ({ String share, Pointer secretSharesResPtr, }) generateSecretShares({ required Pointer multisigConfigWithNamePtr, required String mySeed, required Pointer secretShareMachineWrapperPtr, required List commitments, }) { try { final secretSharesResPtr = getSecretShares( multisigConfigWithName: multisigConfigWithNamePtr, seed: mySeed, language: Language.english, machine: secretShareMachineWrapperPtr, commitments: commitments, ); final share = secretSharesResPtr.ref.shares.toDartString(); return (share: share, secretSharesResPtr: secretSharesResPtr); } catch (e, s) { Logging.instance.log( "generateSecretShares failed: $e\n$s", level: LogLevel.Fatal, ); rethrow; } } static ({ Uint8List multisigId, String recoveryString, String serializedKeys, }) completeKeyGeneration({ required Pointer multisigConfigWithNamePtr, required Pointer secretSharesResPtr, required List shares, }) { try { final keyGenResPtr = completeKeyGen( multisigConfigWithName: multisigConfigWithNamePtr, machineAndCommitments: secretSharesResPtr, shares: shares, ); final id = Uint8List.fromList( List.generate( MULTISIG_ID_LENGTH, (index) => keyGenResPtr.ref.multisig_id[index], ), ); final recoveryString = keyGenResPtr.ref.recovery.toDartString(); final serializedKeys = serializeKeys(keys: keyGenResPtr.ref.keys); return ( multisigId: id, recoveryString: recoveryString, serializedKeys: serializedKeys, ); } catch (e, s) { Logging.instance.log( "completeKeyGeneration failed: $e\n$s", level: LogLevel.Fatal, ); rethrow; } } //=================== transaction creation =================================== static String createSignConfig({ required int network, required List<({UTXO utxo, Uint8List scriptPubKey})> inputs, required List<({String address, Amount amount, bool isChange})> outputs, required String changeAddress, required int feePerWeight, }) { try { final signConfig = newSignConfig( network: network, outputs: inputs .map( (e) => Output( hash: e.utxo.txid.toUint8ListFromHex, vout: e.utxo.vout, value: e.utxo.value, scriptPubKey: e.scriptPubKey, ), ) .toList(), paymentAddresses: outputs.map((e) => e.address).toList(), paymentAmounts: outputs.map((e) => e.amount.raw.toInt()).toList(), change: changeAddress, feePerWeight: feePerWeight, ); return signConfig; } catch (e, s) { Logging.instance.log( "createSignConfig failed: $e\n$s", level: LogLevel.Fatal, ); rethrow; } } static ({ Pointer machinePtr, String preprocess, }) attemptSignConfig({ required int network, required String config, required String serializedKeys, }) { try { final keys = deserializeKeys(keys: serializedKeys); final attemptSignRes = attemptSign( thresholdKeysWrapperPointer: keys, network: network, signConfig: config, ); return ( preprocess: attemptSignRes.ref.preprocess.toDartString(), machinePtr: attemptSignRes.ref.machine, ); } catch (e, s) { Logging.instance.log( "attemptSignConfig failed: $e\n$s", level: LogLevel.Fatal, ); rethrow; } } static ({ Pointer machinePtr, String share, }) continueSigning({ required Pointer machinePtr, required List preprocesses, }) { try { final continueSignRes = continueSign( machine: machinePtr, preprocesses: preprocesses, ); return ( share: continueSignRes.ref.preprocess.toDartString(), machinePtr: continueSignRes.ref.machine, ); } catch (e, s) { Logging.instance.log( "continueSigning failed: $e\n$s", level: LogLevel.Fatal, ); rethrow; } } static String completeSigning({ required Pointer machinePtr, required List shares, }) { try { final rawTransaction = completeSign( machine: machinePtr, shares: shares, ); return rawTransaction; } catch (e, s) { Logging.instance.log( "completeSigning failed: $e\n$s", level: LogLevel.Fatal, ); rethrow; } } static Pointer decodedSignConfig({ required String encodedConfig, required int network, }) { try { final configPtr = decodeSignConfig(encodedSignConfig: encodedConfig, network: network); return configPtr; } catch (e, s) { Logging.instance.log( "decodedSignConfig failed: $e\n$s", level: LogLevel.Fatal, ); rethrow; } } //========================== resharing ======================================= static String createResharerConfig({ required int newThreshold, required List resharers, required List newParticipants, }) { try { final config = newResharerConfig( newThreshold: newThreshold, newParticipants: newParticipants, resharers: resharers, ); return config; } catch (e, s) { Logging.instance.log( "createResharerConfig failed: $e\n$s", level: LogLevel.Fatal, ); rethrow; } } static ({ String resharerStart, Pointer machine, }) beginResharer({ required String serializedKeys, required String config, }) { try { final result = startResharer( serializedKeys: serializedKeys, config: config, ); return ( resharerStart: result.encoded, machine: result.machine, ); } catch (e, s) { Logging.instance.log( "beginResharer failed: $e\n$s", level: LogLevel.Fatal, ); rethrow; } } /// expects [resharerStarts] of length equal to resharers. static ({ String resharedStart, Pointer prior, }) beginReshared({ required String myName, required String resharerConfig, required List resharerStarts, }) { try { final result = startReshared( newMultisigName: 'unused_property', myName: myName, resharerConfig: resharerConfig, resharerStarts: resharerStarts, ); return ( resharedStart: result.encoded, prior: result.machine, ); } catch (e, s) { Logging.instance.log( "beginReshared failed: $e\n$s", level: LogLevel.Fatal, ); rethrow; } } /// expects [encryptionKeysOfResharedTo] of length equal to new participants static String finishResharer({ required StartResharerRes machine, required List encryptionKeysOfResharedTo, }) { try { final result = completeResharer( machine: machine, encryptionKeysOfResharedTo: encryptionKeysOfResharedTo, ); return result; } catch (e, s) { Logging.instance.log( "finishResharer failed: $e\n$s", level: LogLevel.Fatal, ); rethrow; } } /// expects [resharerCompletes] of length equal to resharers static ({ String multisigConfig, String serializedKeys, String resharedId, }) finishReshared({ required StartResharedRes prior, required List resharerCompletes, }) { try { final result = completeReshared( prior: prior, resharerCompletes: resharerCompletes, ); return result; } catch (e, s) { Logging.instance.log( "finishReshared failed: $e\n$s", level: LogLevel.Fatal, ); rethrow; } } static Pointer decodedResharerConfig({ required String resharerConfig, }) { try { final config = decodeResharerConfig(resharerConfig: resharerConfig); return config; } catch (e, s) { Logging.instance.log( "decodedResharerConfig failed: $e\n$s", level: LogLevel.Fatal, ); rethrow; } } static ({ int newThreshold, Map resharers, List newParticipants, }) extractResharerConfigData({ required String rConfig, }) { final decoded = _decodeRConfigWithResharers(rConfig); final resharerConfig = decoded.config; try { final newThreshold = resharerNewThreshold( resharerConfigPointer: decodedResharerConfig( resharerConfig: resharerConfig, ), ); final resharersCount = resharerResharers( resharerConfigPointer: decodedResharerConfig( resharerConfig: resharerConfig, ), ); final List resharers = []; for (int i = 0; i < resharersCount; i++) { resharers.add( resharerResharer( resharerConfigPointer: decodedResharerConfig( resharerConfig: resharerConfig, ), index: i, ), ); } final newParticipantsCount = resharerNewParticipants( resharerConfigPointer: decodedResharerConfig( resharerConfig: resharerConfig, ), ); final List newParticipants = []; for (int i = 0; i < newParticipantsCount; i++) { newParticipants.add( resharerNewParticipant( resharerConfigPointer: decodedResharerConfig( resharerConfig: resharerConfig, ), index: i, ), ); } final Map resharersMap = {}; for (final resharer in resharers) { resharersMap[decoded.resharers.entries .firstWhere((e) => e.value == resharer) .key] = resharer; } return ( newThreshold: newThreshold, resharers: resharersMap, newParticipants: newParticipants, ); } catch (e, s) { Logging.instance.log( "extractResharerConfigData failed: $e\n$s", level: LogLevel.Fatal, ); rethrow; } } static String encodeRConfig( String config, Map resharers, ) { return base64Encode("$config@${jsonEncode(resharers)}".toUint8ListFromUtf8); } static String decodeRConfig( String rConfig, ) { return base64Decode(rConfig).toUtf8String.split("@").first; } static ({Map resharers, String config}) _decodeRConfigWithResharers( String rConfig, ) { final parts = base64Decode(rConfig).toUtf8String.split("@"); final config = parts[0]; final resharers = Map.from(jsonDecode(parts[1]) as Map); return (resharers: resharers, config: config); } }