diff --git a/quantus_sdk/lib/quantus_sdk.dart b/quantus_sdk/lib/quantus_sdk.dart index 6659a8219..8e11c11f4 100644 --- a/quantus_sdk/lib/quantus_sdk.dart +++ b/quantus_sdk/lib/quantus_sdk.dart @@ -54,6 +54,7 @@ export 'src/models/raid_stats.dart'; export 'src/rust/api/crypto.dart' hide crystalAlice, crystalCharlie, crystalBob; export 'src/rust/api/ur.dart'; export 'src/utils/ur_qr.dart'; +export 'src/rust/api/airdrop.dart'; export 'src/rust/api/wormhole.dart'; export 'src/services/account_discovery_service.dart'; export 'src/services/accounts_service.dart'; diff --git a/quantus_sdk/lib/src/rust/api/airdrop.dart b/quantus_sdk/lib/src/rust/api/airdrop.dart new file mode 100644 index 000000000..187942698 --- /dev/null +++ b/quantus_sdk/lib/src/rust/api/airdrop.dart @@ -0,0 +1,195 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.12.0. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import '../frb_generated.dart'; +import 'crypto.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +// These functions are ignored because they are not marked as `pub`: `compact8_decode`, `decode_account`, `derive_dilithium`, `derive_historical_dilithium`, `derive`, `dilithium_keygen_ids`, `hash_felts_rate4_pad10`, `hash_felts`, `hash_padded_legacy`, `hash_padded_v10`, `injective4_secret_words`, `injective4`, `mldsa87_keypair`, `now_unix`, `rehash`, `sign_dilithium_claim`, `v09_hash_no_pad`, `v09_permutation`, `wormhole_scan_paths` +// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `HistoricalKeypair`, `SecretEncoding`, `Sponge`, `WormholeSchemeDef` +// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `assert_receiver_is_total_eq`, `clone`, `clone`, `eq`, `fmt`, `fmt` + +/// Determine which snapshot addresses belong to this wallet. +/// +/// `snapshot_addresses` are the SS58 addresses from `GET /snapshot` (or the +/// miner-rewards CSV). Dilithium matches are checked against +/// `dilithium_public_key` under every historical hash, and — when `mnemonic` +/// is given — against keypairs re-derived under every historical keygen era +/// (see `dilithium_keygen_ids`). Wormhole matches are checked for HD-derived +/// secrets (`m/44'/189189189'/0'/{0..=8}'/{0..=99}'` under both the current +/// "Dilithium seed" and the pre-2.1.0 "Bitcoin seed" BIP32 masters, plus the +/// legacy master-node secret) when `mnemonic` is given, plus any +/// `extra_wormhole_secrets` (32 bytes each). +Future> findAirdropMatches({ + required List snapshotAddresses, + Uint8List? dilithiumPublicKey, + String? mnemonic, + required List extraWormholeSecrets, +}) => RustLib.instance.api.crateApiAirdropFindAirdropMatches( + snapshotAddresses: snapshotAddresses, + dilithiumPublicKey: dilithiumPublicKey, + mnemonic: mnemonic, + extraWormholeSecrets: extraWormholeSecrets, +); + +/// Sign an airdrop claim for a Dilithium-era snapshot `address`, paying out to +/// `claim_account`. The wallet must be ML-DSA-87 (miner keys are). Submit the +/// result as `{"kind": "dilithium", ...fields}` within ~10 minutes. +Future buildAirdropDilithiumClaim({ + required Keypair keypair, + required String address, + required String claimAccount, +}) => RustLib.instance.api.crateApiAirdropBuildAirdropDilithiumClaim( + keypair: keypair, + address: address, + claimAccount: claimAccount, +); + +/// Sign an airdrop claim with a key from a historical keygen era: pass the +/// `dilithium_keygen` id from the `AirdropMatch`. Re-derives the era's +/// keypair from the mnemonic, so the wallet does not need to store it. +/// Submit like `build_airdrop_dilithium_claim`'s result. +Future buildAirdropDilithiumClaimFromMnemonic({ + required String mnemonic, + required String dilithiumKeygen, + required String address, + required String claimAccount, +}) => RustLib.instance.api.crateApiAirdropBuildAirdropDilithiumClaimFromMnemonic( + mnemonic: mnemonic, + dilithiumKeygen: dilithiumKeygen, + address: address, + claimAccount: claimAccount, +); + +/// Prove ownership of the wormhole address derived from `wormhole_secret` +/// (current rate-8 scheme), binding the payout to `claim_account`. Builds the +/// circuit in-process: expect tens of seconds of CPU on first call. Submit as +/// `{"kind": "wormhole", ...fields}`. +Future proveAirdropWormhole({required List wormholeSecret, required String claimAccount}) => + RustLib.instance.api.crateApiAirdropProveAirdropWormhole( + wormholeSecret: wormholeSecret, + claimAccount: claimAccount, + ); + +/// A snapshot address this wallet can prove ownership of. +class AirdropMatch { + /// The address exactly as it appeared in `snapshot_addresses`. + final String address; + + /// "dilithium" or "wormhole". + final String kind; + + /// Derivation scheme id, e.g. "dilithium-v10-padded". + final String scheme; + + /// Whether the claim server currently accepts proofs for this scheme. + final bool claimable; + + /// Where the key came from: "dilithium public key", an HD path, + /// "provided secret #N", or a historical keygen description. + final String source; + + /// The 32-byte wormhole secret that produced the match (needed to build + /// the ownership proof). None for Dilithium matches. + final Uint8List? wormholeSecret; + + /// For Dilithium matches found through a historical keygen scheme: the + /// keygen id to pass to `build_airdrop_dilithium_claim_from_mnemonic`. + /// None when the match came from the provided current public key (claim + /// with `build_airdrop_dilithium_claim`). + final String? dilithiumKeygen; + + const AirdropMatch({ + required this.address, + required this.kind, + required this.scheme, + required this.claimable, + required this.source, + this.wormholeSecret, + this.dilithiumKeygen, + }); + + @override + int get hashCode => + address.hashCode ^ + kind.hashCode ^ + scheme.hashCode ^ + claimable.hashCode ^ + source.hashCode ^ + wormholeSecret.hashCode ^ + dilithiumKeygen.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is AirdropMatch && + runtimeType == other.runtimeType && + address == other.address && + kind == other.kind && + scheme == other.scheme && + claimable == other.claimable && + source == other.source && + wormholeSecret == other.wormholeSecret && + dilithiumKeygen == other.dilithiumKeygen; +} + +/// The `POST /claim` body fields for a Dilithium claim. +class DilithiumClaimBody { + final String scheme; + final String address; + final String claimAccount; + final String publicKeyHex; + final String signatureHex; + final PlatformInt64 expiryUnix; + + const DilithiumClaimBody({ + required this.scheme, + required this.address, + required this.claimAccount, + required this.publicKeyHex, + required this.signatureHex, + required this.expiryUnix, + }); + + @override + int get hashCode => + scheme.hashCode ^ + address.hashCode ^ + claimAccount.hashCode ^ + publicKeyHex.hashCode ^ + signatureHex.hashCode ^ + expiryUnix.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is DilithiumClaimBody && + runtimeType == other.runtimeType && + scheme == other.scheme && + address == other.address && + claimAccount == other.claimAccount && + publicKeyHex == other.publicKeyHex && + signatureHex == other.signatureHex && + expiryUnix == other.expiryUnix; +} + +/// The `POST /claim` body fields for a wormhole claim. +class WormholeClaimBody { + final String proofKind; + final String proofHex; + + const WormholeClaimBody({required this.proofKind, required this.proofHex}); + + @override + int get hashCode => proofKind.hashCode ^ proofHex.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is WormholeClaimBody && + runtimeType == other.runtimeType && + proofKind == other.proofKind && + proofHex == other.proofHex; +} diff --git a/quantus_sdk/lib/src/rust/frb_generated.dart b/quantus_sdk/lib/src/rust/frb_generated.dart index d8c26a019..60c08e8dc 100644 --- a/quantus_sdk/lib/src/rust/frb_generated.dart +++ b/quantus_sdk/lib/src/rust/frb_generated.dart @@ -3,6 +3,7 @@ // ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field +import 'api/airdrop.dart'; import 'api/crypto.dart'; import 'api/multisig.dart'; import 'api/ur.dart'; @@ -65,7 +66,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.12.0'; @override - int get rustContentHash => 1684949517; + int get rustContentHash => -1962506674; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( stem: 'rust_lib_quantus_wallet', @@ -78,6 +79,19 @@ class RustLib extends BaseEntrypoint { abstract class RustLibApi extends BaseApi { Future crateApiWormholeAggregateProofs({required List proofBytesList, required String binsDir}); + Future crateApiAirdropBuildAirdropDilithiumClaim({ + required Keypair keypair, + required String address, + required String claimAccount, + }); + + Future crateApiAirdropBuildAirdropDilithiumClaimFromMnemonic({ + required String mnemonic, + required String dilithiumKeygen, + required String address, + required String claimAccount, + }); + String crateApiWormholeComputeAddressHashHex({required List rawAddress}); MerkleProcessed crateApiWormholeComputeMerklePositions({ @@ -110,6 +124,13 @@ abstract class RustLibApi extends BaseApi { Future crateApiWormholeEnsureCircuitBinaries({required String binsDir}); + Future> crateApiAirdropFindAirdropMatches({ + required List snapshotAddresses, + Uint8List? dilithiumPublicKey, + String? mnemonic, + required List extraWormholeSecrets, + }); + String crateApiCryptoFirstHashToAddress({required String firstHashHex}); Keypair crateApiCryptoGenerateDerivedKeypair({ @@ -142,6 +163,11 @@ abstract class RustLibApi extends BaseApi { required BigInt nonce, }); + Future crateApiAirdropProveAirdropWormhole({ + required List wormholeSecret, + required String claimAccount, + }); + int crateApiCryptoPublicKeyBytes({required DilithiumScheme scheme}); int crateApiCryptoSecretKeyBytes({required DilithiumScheme scheme}); @@ -218,6 +244,62 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiWormholeAggregateProofsConstMeta => const TaskConstMeta(debugName: 'aggregate_proofs', argNames: ['proofBytesList', 'binsDir']); + @override + Future crateApiAirdropBuildAirdropDilithiumClaim({ + required Keypair keypair, + required String address, + required String claimAccount, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_box_autoadd_keypair(keypair, serializer); + sse_encode_String(address, serializer); + sse_encode_String(claimAccount, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 2, port: port_); + }, + codec: SseCodec(decodeSuccessData: sse_decode_dilithium_claim_body, decodeErrorData: sse_decode_String), + constMeta: kCrateApiAirdropBuildAirdropDilithiumClaimConstMeta, + argValues: [keypair, address, claimAccount], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiAirdropBuildAirdropDilithiumClaimConstMeta => + const TaskConstMeta(debugName: 'build_airdrop_dilithium_claim', argNames: ['keypair', 'address', 'claimAccount']); + + @override + Future crateApiAirdropBuildAirdropDilithiumClaimFromMnemonic({ + required String mnemonic, + required String dilithiumKeygen, + required String address, + required String claimAccount, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(mnemonic, serializer); + sse_encode_String(dilithiumKeygen, serializer); + sse_encode_String(address, serializer); + sse_encode_String(claimAccount, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 3, port: port_); + }, + codec: SseCodec(decodeSuccessData: sse_decode_dilithium_claim_body, decodeErrorData: sse_decode_String), + constMeta: kCrateApiAirdropBuildAirdropDilithiumClaimFromMnemonicConstMeta, + argValues: [mnemonic, dilithiumKeygen, address, claimAccount], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiAirdropBuildAirdropDilithiumClaimFromMnemonicConstMeta => const TaskConstMeta( + debugName: 'build_airdrop_dilithium_claim_from_mnemonic', + argNames: ['mnemonic', 'dilithiumKeygen', 'address', 'claimAccount'], + ); + @override String crateApiWormholeComputeAddressHashHex({required List rawAddress}) { return handler.executeSync( @@ -225,7 +307,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(rawAddress, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 2)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 4)!; }, codec: SseCodec(decodeSuccessData: sse_decode_String, decodeErrorData: sse_decode_String), constMeta: kCrateApiWormholeComputeAddressHashHexConstMeta, @@ -251,7 +333,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_list_prim_u_8_loose(unsortedSiblingsFlat, serializer); sse_encode_list_prim_u_8_loose(leafHash, serializer); sse_encode_u_32(depth, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 3)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 5)!; }, codec: SseCodec(decodeSuccessData: sse_decode_merkle_processed, decodeErrorData: sse_decode_String), constMeta: kCrateApiWormholeComputeMerklePositionsConstMeta, @@ -274,7 +356,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(secret, serializer); sse_encode_u_64(transferCount, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 4)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 6)!; }, codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: sse_decode_String), constMeta: kCrateApiWormholeComputeNullifierConstMeta, @@ -294,7 +376,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(secret, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 5)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 7)!; }, codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: sse_decode_String), constMeta: kCrateApiWormholeComputeWormholeAddressConstMeta, @@ -313,7 +395,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { SyncTask( callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 6)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 8)!; }, codec: SseCodec(decodeSuccessData: sse_decode_keypair, decodeErrorData: null), constMeta: kCrateApiCryptoCrystalAliceConstMeta, @@ -332,7 +414,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { SyncTask( callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 7)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 9)!; }, codec: SseCodec(decodeSuccessData: sse_decode_keypair, decodeErrorData: null), constMeta: kCrateApiCryptoCrystalBobConstMeta, @@ -350,7 +432,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { SyncTask( callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 8)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 10)!; }, codec: SseCodec(decodeSuccessData: sse_decode_keypair, decodeErrorData: null), constMeta: kCrateApiCryptoCrystalCharlieConstMeta, @@ -370,7 +452,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(leafData, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 9)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 11)!; }, codec: SseCodec(decodeSuccessData: sse_decode_u_32, decodeErrorData: sse_decode_String), constMeta: kCrateApiWormholeDecodeLeafAmountConstMeta, @@ -390,7 +472,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(leafData, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 10)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 12)!; }, codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: sse_decode_String), constMeta: kCrateApiWormholeDecodeLeafToAccountConstMeta, @@ -410,7 +492,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(leafData, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 11)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 13)!; }, codec: SseCodec(decodeSuccessData: sse_decode_u_64, decodeErrorData: sse_decode_String), constMeta: kCrateApiWormholeDecodeLeafTransferCountConstMeta, @@ -430,7 +512,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_String(urParts, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 12)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 14)!; }, codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: sse_decode_String), constMeta: kCrateApiUrDecodeUrConstMeta, @@ -450,7 +532,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(mnemonicStr, serializer); sse_encode_String(path, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 13)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 15)!; }, codec: SseCodec( decodeSuccessData: sse_decode_wormhole_result, @@ -475,7 +557,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(data, serializer); sse_encode_opt_box_autoadd_u_32(maxFragmentLength, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 14)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 16)!; }, codec: SseCodec(decodeSuccessData: sse_decode_list_String, decodeErrorData: sse_decode_String), constMeta: kCrateApiUrEncodeUrConstMeta, @@ -495,7 +577,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(binsDir, serializer); - pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 15, port: port_); + pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 17, port: port_); }, codec: SseCodec(decodeSuccessData: sse_decode_String, decodeErrorData: sse_decode_String), constMeta: kCrateApiWormholeEnsureCircuitBinariesConstMeta, @@ -508,6 +590,36 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiWormholeEnsureCircuitBinariesConstMeta => const TaskConstMeta(debugName: 'ensure_circuit_binaries', argNames: ['binsDir']); + @override + Future> crateApiAirdropFindAirdropMatches({ + required List snapshotAddresses, + Uint8List? dilithiumPublicKey, + String? mnemonic, + required List extraWormholeSecrets, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_list_String(snapshotAddresses, serializer); + sse_encode_opt_list_prim_u_8_strict(dilithiumPublicKey, serializer); + sse_encode_opt_String(mnemonic, serializer); + sse_encode_list_list_prim_u_8_strict(extraWormholeSecrets, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 18, port: port_); + }, + codec: SseCodec(decodeSuccessData: sse_decode_list_airdrop_match, decodeErrorData: sse_decode_String), + constMeta: kCrateApiAirdropFindAirdropMatchesConstMeta, + argValues: [snapshotAddresses, dilithiumPublicKey, mnemonic, extraWormholeSecrets], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiAirdropFindAirdropMatchesConstMeta => const TaskConstMeta( + debugName: 'find_airdrop_matches', + argNames: ['snapshotAddresses', 'dilithiumPublicKey', 'mnemonic', 'extraWormholeSecrets'], + ); + @override String crateApiCryptoFirstHashToAddress({required String firstHashHex}) { return handler.executeSync( @@ -515,7 +627,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(firstHashHex, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 16)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 19)!; }, codec: SseCodec(decodeSuccessData: sse_decode_String, decodeErrorData: sse_decode_String), constMeta: kCrateApiCryptoFirstHashToAddressConstMeta, @@ -541,7 +653,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_String(mnemonicStr, serializer); sse_encode_String(path, serializer); sse_encode_dilithium_scheme(scheme, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 17)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 20)!; }, codec: SseCodec( decodeSuccessData: sse_decode_keypair, @@ -565,7 +677,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(mnemonicStr, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 18)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 21)!; }, codec: SseCodec( decodeSuccessData: sse_decode_keypair, @@ -589,7 +701,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(seed, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 19)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 22)!; }, codec: SseCodec(decodeSuccessData: sse_decode_keypair, decodeErrorData: null), constMeta: kCrateApiCryptoGenerateKeypairFromSeedConstMeta, @@ -615,7 +727,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_box_autoadd_proof_input(input, serializer); sse_encode_String(proverBinPath, serializer); sse_encode_String(commonBinPath, serializer); - pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 20, port: port_); + pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 23, port: port_); }, codec: SseCodec(decodeSuccessData: sse_decode_proof_output, decodeErrorData: sse_decode_String), constMeta: kCrateApiWormholeGenerateProofConstMeta, @@ -634,7 +746,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { NormalTask( callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); - pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 21, port: port_); + pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 24, port: port_); }, codec: SseCodec(decodeSuccessData: sse_decode_unit, decodeErrorData: null), constMeta: kCrateApiCryptoInitAppConstMeta, @@ -653,7 +765,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_String(urParts, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 22)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 25)!; }, codec: SseCodec(decodeSuccessData: sse_decode_bool, decodeErrorData: null), constMeta: kCrateApiUrIsCompleteUrConstMeta, @@ -672,7 +784,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { SyncTask( callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 23)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 26)!; }, codec: SseCodec(decodeSuccessData: sse_decode_u_32, decodeErrorData: null), constMeta: kCrateApiUrMaxUrPartCharsConstMeta, @@ -691,7 +803,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { SyncTask( callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 24)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 27)!; }, codec: SseCodec(decodeSuccessData: sse_decode_u_32, decodeErrorData: null), constMeta: kCrateApiUrMaxUrPartsConstMeta, @@ -716,7 +828,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_list_String(signers, serializer); sse_encode_u_32(threshold, serializer); sse_encode_u_64(nonce, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 25)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 28)!; }, codec: SseCodec(decodeSuccessData: sse_decode_String, decodeErrorData: sse_decode_String), constMeta: kCrateApiMultisigPredictMultisigAddressConstMeta, @@ -729,6 +841,30 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiMultisigPredictMultisigAddressConstMeta => const TaskConstMeta(debugName: 'predict_multisig_address', argNames: ['signers', 'threshold', 'nonce']); + @override + Future crateApiAirdropProveAirdropWormhole({ + required List wormholeSecret, + required String claimAccount, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_list_prim_u_8_loose(wormholeSecret, serializer); + sse_encode_String(claimAccount, serializer); + pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 29, port: port_); + }, + codec: SseCodec(decodeSuccessData: sse_decode_wormhole_claim_body, decodeErrorData: sse_decode_String), + constMeta: kCrateApiAirdropProveAirdropWormholeConstMeta, + argValues: [wormholeSecret, claimAccount], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiAirdropProveAirdropWormholeConstMeta => + const TaskConstMeta(debugName: 'prove_airdrop_wormhole', argNames: ['wormholeSecret', 'claimAccount']); + @override int crateApiCryptoPublicKeyBytes({required DilithiumScheme scheme}) { return handler.executeSync( @@ -736,7 +872,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_dilithium_scheme(scheme, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 26)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 30)!; }, codec: SseCodec(decodeSuccessData: sse_decode_u_32, decodeErrorData: null), constMeta: kCrateApiCryptoPublicKeyBytesConstMeta, @@ -756,7 +892,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_dilithium_scheme(scheme, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 27)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 31)!; }, codec: SseCodec(decodeSuccessData: sse_decode_u_32, decodeErrorData: null), constMeta: kCrateApiCryptoSecretKeyBytesConstMeta, @@ -776,7 +912,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_u_16(prefix, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 28)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 32)!; }, codec: SseCodec(decodeSuccessData: sse_decode_unit, decodeErrorData: null), constMeta: kCrateApiCryptoSetDefaultSs58PrefixConstMeta, @@ -804,7 +940,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_list_prim_u_8_loose(message, serializer); sse_encode_opt_u_8_array_32(entropy, serializer); sse_encode_u_32(specVersion, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 29)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 33)!; }, codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: null), constMeta: kCrateApiCryptoSignMessageConstMeta, @@ -832,7 +968,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_list_prim_u_8_loose(message, serializer); sse_encode_opt_u_8_array_32(entropy, serializer); sse_encode_u_32(specVersion, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 30)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 34)!; }, codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: null), constMeta: kCrateApiCryptoSignMessageWithPubkeyConstMeta, @@ -854,7 +990,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_dilithium_scheme(scheme, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 31)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 35)!; }, codec: SseCodec(decodeSuccessData: sse_decode_u_32, decodeErrorData: null), constMeta: kCrateApiCryptoSignatureBytesConstMeta, @@ -874,7 +1010,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(s, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 32)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 36)!; }, codec: SseCodec(decodeSuccessData: sse_decode_list_prim_u_8_strict, decodeErrorData: sse_decode_String), constMeta: kCrateApiCryptoSs58ToAccountIdConstMeta, @@ -894,7 +1030,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_box_autoadd_keypair(obj, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 33)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 37)!; }, codec: SseCodec(decodeSuccessData: sse_decode_String, decodeErrorData: null), constMeta: kCrateApiCryptoToAccountIdConstMeta, @@ -922,7 +1058,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_list_prim_u_8_loose(message, serializer); sse_encode_list_prim_u_8_loose(signature, serializer); sse_encode_u_32(specVersion, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 34)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 38)!; }, codec: SseCodec(decodeSuccessData: sse_decode_bool, decodeErrorData: null), constMeta: kCrateApiCryptoVerifyMessageConstMeta, @@ -943,7 +1079,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_u_32(inputAmount, serializer); sse_encode_u_32(feeBps, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 35)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 39)!; }, codec: SseCodec(decodeSuccessData: sse_decode_u_32, decodeErrorData: sse_decode_String), constMeta: kCrateApiWormholeWormholeComputeOutputAmountConstMeta, @@ -962,7 +1098,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { SyncTask( callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 36)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 40)!; }, codec: SseCodec(decodeSuccessData: sse_decode_String, decodeErrorData: null), constMeta: kCrateApiWormholeZkCircuitsVersionConstMeta, @@ -1001,6 +1137,22 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw as String; } + @protected + AirdropMatch dco_decode_airdrop_match(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 7) throw Exception('unexpected arr length: expect 7 but see ${arr.length}'); + return AirdropMatch( + address: dco_decode_String(arr[0]), + kind: dco_decode_String(arr[1]), + scheme: dco_decode_String(arr[2]), + claimable: dco_decode_bool(arr[3]), + source: dco_decode_String(arr[4]), + wormholeSecret: dco_decode_opt_list_prim_u_8_strict(arr[5]), + dilithiumKeygen: dco_decode_opt_String(arr[6]), + ); + } + @protected bool dco_decode_bool(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1025,6 +1177,21 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw as int; } + @protected + DilithiumClaimBody dco_decode_dilithium_claim_body(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 6) throw Exception('unexpected arr length: expect 6 but see ${arr.length}'); + return DilithiumClaimBody( + scheme: dco_decode_String(arr[0]), + address: dco_decode_String(arr[1]), + claimAccount: dco_decode_String(arr[2]), + publicKeyHex: dco_decode_String(arr[3]), + signatureHex: dco_decode_String(arr[4]), + expiryUnix: dco_decode_i_64(arr[5]), + ); + } + @protected DilithiumScheme dco_decode_dilithium_scheme(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1037,6 +1204,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw as int; } + @protected + PlatformInt64 dco_decode_i_64(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dcoDecodeI64(raw); + } + @protected Keypair dco_decode_keypair(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1055,6 +1228,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (raw as List).map(dco_decode_String).toList(); } + @protected + List dco_decode_list_airdrop_match(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return (raw as List).map(dco_decode_airdrop_match).toList(); + } + @protected List dco_decode_list_list_prim_u_8_strict(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1084,12 +1263,24 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @protected + String? dco_decode_opt_String(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null ? null : dco_decode_String(raw); + } + @protected int? dco_decode_opt_box_autoadd_u_32(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs return raw == null ? null : dco_decode_box_autoadd_u_32(raw); } + @protected + Uint8List? dco_decode_opt_list_prim_u_8_strict(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null ? null : dco_decode_list_prim_u_8_strict(raw); + } + @protected U8Array32? dco_decode_opt_u_8_array_32(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1177,6 +1368,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return dcoDecodeU64(raw); } + @protected + WormholeClaimBody dco_decode_wormhole_claim_body(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) throw Exception('unexpected arr length: expect 2 but see ${arr.length}'); + return WormholeClaimBody(proofKind: dco_decode_String(arr[0]), proofHex: dco_decode_String(arr[1])); + } + @protected WormholeResult dco_decode_wormhole_result(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1212,6 +1411,27 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return utf8.decoder.convert(inner); } + @protected + AirdropMatch sse_decode_airdrop_match(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_address = sse_decode_String(deserializer); + var var_kind = sse_decode_String(deserializer); + var var_scheme = sse_decode_String(deserializer); + var var_claimable = sse_decode_bool(deserializer); + var var_source = sse_decode_String(deserializer); + var var_wormholeSecret = sse_decode_opt_list_prim_u_8_strict(deserializer); + var var_dilithiumKeygen = sse_decode_opt_String(deserializer); + return AirdropMatch( + address: var_address, + kind: var_kind, + scheme: var_scheme, + claimable: var_claimable, + source: var_source, + wormholeSecret: var_wormholeSecret, + dilithiumKeygen: var_dilithiumKeygen, + ); + } + @protected bool sse_decode_bool(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1236,6 +1456,25 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (sse_decode_u_32(deserializer)); } + @protected + DilithiumClaimBody sse_decode_dilithium_claim_body(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_scheme = sse_decode_String(deserializer); + var var_address = sse_decode_String(deserializer); + var var_claimAccount = sse_decode_String(deserializer); + var var_publicKeyHex = sse_decode_String(deserializer); + var var_signatureHex = sse_decode_String(deserializer); + var var_expiryUnix = sse_decode_i_64(deserializer); + return DilithiumClaimBody( + scheme: var_scheme, + address: var_address, + claimAccount: var_claimAccount, + publicKeyHex: var_publicKeyHex, + signatureHex: var_signatureHex, + expiryUnix: var_expiryUnix, + ); + } + @protected DilithiumScheme sse_decode_dilithium_scheme(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1249,6 +1488,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return deserializer.buffer.getInt32(); } + @protected + PlatformInt64 sse_decode_i_64(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getPlatformInt64(); + } + @protected Keypair sse_decode_keypair(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1270,6 +1515,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return ans_; } + @protected + List sse_decode_list_airdrop_match(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var len_ = sse_decode_i_32(deserializer); + var ans_ = []; + for (var idx_ = 0; idx_ < len_; ++idx_) { + ans_.add(sse_decode_airdrop_match(deserializer)); + } + return ans_; + } + @protected List sse_decode_list_list_prim_u_8_strict(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1304,6 +1561,17 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return MerkleProcessed(sortedSiblingsFlat: var_sortedSiblingsFlat, positions: var_positions); } + @protected + String? sse_decode_opt_String(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_String(deserializer)); + } else { + return null; + } + } + @protected int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1315,6 +1583,17 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + Uint8List? sse_decode_opt_list_prim_u_8_strict(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_list_prim_u_8_strict(deserializer)); + } else { + return null; + } + } + @protected U8Array32? sse_decode_opt_u_8_array_32(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1421,6 +1700,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return deserializer.buffer.getBigUint64(); } + @protected + WormholeClaimBody sse_decode_wormhole_claim_body(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_proofKind = sse_decode_String(deserializer); + var var_proofHex = sse_decode_String(deserializer); + return WormholeClaimBody(proofKind: var_proofKind, proofHex: var_proofHex); + } + @protected WormholeResult sse_decode_wormhole_result(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1454,6 +1741,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_list_prim_u_8_strict(utf8.encoder.convert(self), serializer); } + @protected + void sse_encode_airdrop_match(AirdropMatch self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.address, serializer); + sse_encode_String(self.kind, serializer); + sse_encode_String(self.scheme, serializer); + sse_encode_bool(self.claimable, serializer); + sse_encode_String(self.source, serializer); + sse_encode_opt_list_prim_u_8_strict(self.wormholeSecret, serializer); + sse_encode_opt_String(self.dilithiumKeygen, serializer); + } + @protected void sse_encode_bool(bool self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1478,6 +1777,17 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_u_32(self, serializer); } + @protected + void sse_encode_dilithium_claim_body(DilithiumClaimBody self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.scheme, serializer); + sse_encode_String(self.address, serializer); + sse_encode_String(self.claimAccount, serializer); + sse_encode_String(self.publicKeyHex, serializer); + sse_encode_String(self.signatureHex, serializer); + sse_encode_i_64(self.expiryUnix, serializer); + } + @protected void sse_encode_dilithium_scheme(DilithiumScheme self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1490,6 +1800,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { serializer.buffer.putInt32(self); } + @protected + void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putPlatformInt64(self); + } + @protected void sse_encode_keypair(Keypair self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1507,6 +1823,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + void sse_encode_list_airdrop_match(List self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + for (final item in self) { + sse_encode_airdrop_match(item, serializer); + } + } + @protected void sse_encode_list_list_prim_u_8_strict(List self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1537,6 +1862,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_list_prim_u_8_strict(self.positions, serializer); } + @protected + void sse_encode_opt_String(String? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_String(self, serializer); + } + } + @protected void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1547,6 +1882,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + void sse_encode_opt_list_prim_u_8_strict(Uint8List? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_list_prim_u_8_strict(self, serializer); + } + } + @protected void sse_encode_opt_u_8_array_32(U8Array32? self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1629,6 +1974,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { serializer.buffer.putBigUint64(self); } + @protected + void sse_encode_wormhole_claim_body(WormholeClaimBody self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.proofKind, serializer); + sse_encode_String(self.proofHex, serializer); + } + @protected void sse_encode_wormhole_result(WormholeResult self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs diff --git a/quantus_sdk/lib/src/rust/frb_generated.io.dart b/quantus_sdk/lib/src/rust/frb_generated.io.dart index c8cd57473..b115cd87f 100644 --- a/quantus_sdk/lib/src/rust/frb_generated.io.dart +++ b/quantus_sdk/lib/src/rust/frb_generated.io.dart @@ -3,6 +3,7 @@ // ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field +import 'api/airdrop.dart'; import 'api/crypto.dart'; import 'api/multisig.dart'; import 'api/ur.dart'; @@ -35,6 +36,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected String dco_decode_String(dynamic raw); + @protected + AirdropMatch dco_decode_airdrop_match(dynamic raw); + @protected bool dco_decode_bool(dynamic raw); @@ -47,18 +51,27 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected int dco_decode_box_autoadd_u_32(dynamic raw); + @protected + DilithiumClaimBody dco_decode_dilithium_claim_body(dynamic raw); + @protected DilithiumScheme dco_decode_dilithium_scheme(dynamic raw); @protected int dco_decode_i_32(dynamic raw); + @protected + PlatformInt64 dco_decode_i_64(dynamic raw); + @protected Keypair dco_decode_keypair(dynamic raw); @protected List dco_decode_list_String(dynamic raw); + @protected + List dco_decode_list_airdrop_match(dynamic raw); + @protected List dco_decode_list_list_prim_u_8_strict(dynamic raw); @@ -71,9 +84,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected MerkleProcessed dco_decode_merkle_processed(dynamic raw); + @protected + String? dco_decode_opt_String(dynamic raw); + @protected int? dco_decode_opt_box_autoadd_u_32(dynamic raw); + @protected + Uint8List? dco_decode_opt_list_prim_u_8_strict(dynamic raw); + @protected U8Array32? dco_decode_opt_u_8_array_32(dynamic raw); @@ -104,6 +123,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected BigInt dco_decode_usize(dynamic raw); + @protected + WormholeClaimBody dco_decode_wormhole_claim_body(dynamic raw); + @protected WormholeResult dco_decode_wormhole_result(dynamic raw); @@ -120,6 +142,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected String sse_decode_String(SseDeserializer deserializer); + @protected + AirdropMatch sse_decode_airdrop_match(SseDeserializer deserializer); + @protected bool sse_decode_bool(SseDeserializer deserializer); @@ -132,18 +157,27 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected int sse_decode_box_autoadd_u_32(SseDeserializer deserializer); + @protected + DilithiumClaimBody sse_decode_dilithium_claim_body(SseDeserializer deserializer); + @protected DilithiumScheme sse_decode_dilithium_scheme(SseDeserializer deserializer); @protected int sse_decode_i_32(SseDeserializer deserializer); + @protected + PlatformInt64 sse_decode_i_64(SseDeserializer deserializer); + @protected Keypair sse_decode_keypair(SseDeserializer deserializer); @protected List sse_decode_list_String(SseDeserializer deserializer); + @protected + List sse_decode_list_airdrop_match(SseDeserializer deserializer); + @protected List sse_decode_list_list_prim_u_8_strict(SseDeserializer deserializer); @@ -156,9 +190,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected MerkleProcessed sse_decode_merkle_processed(SseDeserializer deserializer); + @protected + String? sse_decode_opt_String(SseDeserializer deserializer); + @protected int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer); + @protected + Uint8List? sse_decode_opt_list_prim_u_8_strict(SseDeserializer deserializer); + @protected U8Array32? sse_decode_opt_u_8_array_32(SseDeserializer deserializer); @@ -189,6 +229,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected BigInt sse_decode_usize(SseDeserializer deserializer); + @protected + WormholeClaimBody sse_decode_wormhole_claim_body(SseDeserializer deserializer); + @protected WormholeResult sse_decode_wormhole_result(SseDeserializer deserializer); @@ -207,6 +250,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_String(String self, SseSerializer serializer); + @protected + void sse_encode_airdrop_match(AirdropMatch self, SseSerializer serializer); + @protected void sse_encode_bool(bool self, SseSerializer serializer); @@ -219,18 +265,27 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer); + @protected + void sse_encode_dilithium_claim_body(DilithiumClaimBody self, SseSerializer serializer); + @protected void sse_encode_dilithium_scheme(DilithiumScheme self, SseSerializer serializer); @protected void sse_encode_i_32(int self, SseSerializer serializer); + @protected + void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer); + @protected void sse_encode_keypair(Keypair self, SseSerializer serializer); @protected void sse_encode_list_String(List self, SseSerializer serializer); + @protected + void sse_encode_list_airdrop_match(List self, SseSerializer serializer); + @protected void sse_encode_list_list_prim_u_8_strict(List self, SseSerializer serializer); @@ -243,9 +298,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_merkle_processed(MerkleProcessed self, SseSerializer serializer); + @protected + void sse_encode_opt_String(String? self, SseSerializer serializer); + @protected void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer); + @protected + void sse_encode_opt_list_prim_u_8_strict(Uint8List? self, SseSerializer serializer); + @protected void sse_encode_opt_u_8_array_32(U8Array32? self, SseSerializer serializer); @@ -276,6 +337,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_usize(BigInt self, SseSerializer serializer); + @protected + void sse_encode_wormhole_claim_body(WormholeClaimBody self, SseSerializer serializer); + @protected void sse_encode_wormhole_result(WormholeResult self, SseSerializer serializer); } diff --git a/quantus_sdk/lib/src/rust/frb_generated.web.dart b/quantus_sdk/lib/src/rust/frb_generated.web.dart index 9dbc5b342..f8299322b 100644 --- a/quantus_sdk/lib/src/rust/frb_generated.web.dart +++ b/quantus_sdk/lib/src/rust/frb_generated.web.dart @@ -6,6 +6,7 @@ // Static analysis wrongly picks the IO variant, thus ignore this // ignore_for_file: argument_type_not_assignable +import 'api/airdrop.dart'; import 'api/crypto.dart'; import 'api/multisig.dart'; import 'api/ur.dart'; @@ -37,6 +38,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected String dco_decode_String(dynamic raw); + @protected + AirdropMatch dco_decode_airdrop_match(dynamic raw); + @protected bool dco_decode_bool(dynamic raw); @@ -49,18 +53,27 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected int dco_decode_box_autoadd_u_32(dynamic raw); + @protected + DilithiumClaimBody dco_decode_dilithium_claim_body(dynamic raw); + @protected DilithiumScheme dco_decode_dilithium_scheme(dynamic raw); @protected int dco_decode_i_32(dynamic raw); + @protected + PlatformInt64 dco_decode_i_64(dynamic raw); + @protected Keypair dco_decode_keypair(dynamic raw); @protected List dco_decode_list_String(dynamic raw); + @protected + List dco_decode_list_airdrop_match(dynamic raw); + @protected List dco_decode_list_list_prim_u_8_strict(dynamic raw); @@ -73,9 +86,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected MerkleProcessed dco_decode_merkle_processed(dynamic raw); + @protected + String? dco_decode_opt_String(dynamic raw); + @protected int? dco_decode_opt_box_autoadd_u_32(dynamic raw); + @protected + Uint8List? dco_decode_opt_list_prim_u_8_strict(dynamic raw); + @protected U8Array32? dco_decode_opt_u_8_array_32(dynamic raw); @@ -106,6 +125,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected BigInt dco_decode_usize(dynamic raw); + @protected + WormholeClaimBody dco_decode_wormhole_claim_body(dynamic raw); + @protected WormholeResult dco_decode_wormhole_result(dynamic raw); @@ -122,6 +144,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected String sse_decode_String(SseDeserializer deserializer); + @protected + AirdropMatch sse_decode_airdrop_match(SseDeserializer deserializer); + @protected bool sse_decode_bool(SseDeserializer deserializer); @@ -134,18 +159,27 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected int sse_decode_box_autoadd_u_32(SseDeserializer deserializer); + @protected + DilithiumClaimBody sse_decode_dilithium_claim_body(SseDeserializer deserializer); + @protected DilithiumScheme sse_decode_dilithium_scheme(SseDeserializer deserializer); @protected int sse_decode_i_32(SseDeserializer deserializer); + @protected + PlatformInt64 sse_decode_i_64(SseDeserializer deserializer); + @protected Keypair sse_decode_keypair(SseDeserializer deserializer); @protected List sse_decode_list_String(SseDeserializer deserializer); + @protected + List sse_decode_list_airdrop_match(SseDeserializer deserializer); + @protected List sse_decode_list_list_prim_u_8_strict(SseDeserializer deserializer); @@ -158,9 +192,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected MerkleProcessed sse_decode_merkle_processed(SseDeserializer deserializer); + @protected + String? sse_decode_opt_String(SseDeserializer deserializer); + @protected int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer); + @protected + Uint8List? sse_decode_opt_list_prim_u_8_strict(SseDeserializer deserializer); + @protected U8Array32? sse_decode_opt_u_8_array_32(SseDeserializer deserializer); @@ -191,6 +231,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected BigInt sse_decode_usize(SseDeserializer deserializer); + @protected + WormholeClaimBody sse_decode_wormhole_claim_body(SseDeserializer deserializer); + @protected WormholeResult sse_decode_wormhole_result(SseDeserializer deserializer); @@ -209,6 +252,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_String(String self, SseSerializer serializer); + @protected + void sse_encode_airdrop_match(AirdropMatch self, SseSerializer serializer); + @protected void sse_encode_bool(bool self, SseSerializer serializer); @@ -221,18 +267,27 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer); + @protected + void sse_encode_dilithium_claim_body(DilithiumClaimBody self, SseSerializer serializer); + @protected void sse_encode_dilithium_scheme(DilithiumScheme self, SseSerializer serializer); @protected void sse_encode_i_32(int self, SseSerializer serializer); + @protected + void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer); + @protected void sse_encode_keypair(Keypair self, SseSerializer serializer); @protected void sse_encode_list_String(List self, SseSerializer serializer); + @protected + void sse_encode_list_airdrop_match(List self, SseSerializer serializer); + @protected void sse_encode_list_list_prim_u_8_strict(List self, SseSerializer serializer); @@ -245,9 +300,15 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_merkle_processed(MerkleProcessed self, SseSerializer serializer); + @protected + void sse_encode_opt_String(String? self, SseSerializer serializer); + @protected void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer); + @protected + void sse_encode_opt_list_prim_u_8_strict(Uint8List? self, SseSerializer serializer); + @protected void sse_encode_opt_u_8_array_32(U8Array32? self, SseSerializer serializer); @@ -278,6 +339,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_usize(BigInt self, SseSerializer serializer); + @protected + void sse_encode_wormhole_claim_body(WormholeClaimBody self, SseSerializer serializer); + @protected void sse_encode_wormhole_result(WormholeResult self, SseSerializer serializer); } diff --git a/quantus_sdk/rust/Cargo.lock b/quantus_sdk/rust/Cargo.lock index 7f893213c..5a1daca99 100644 --- a/quantus_sdk/rust/Cargo.lock +++ b/quantus_sdk/rust/Cargo.lock @@ -511,6 +511,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base58" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" + [[package]] name = "base64" version = "0.22.1" @@ -1884,6 +1890,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -1941,6 +1956,7 @@ dependencies = [ "once_cell", "serdect", "sha2 0.10.9", + "signature", ] [[package]] @@ -2193,6 +2209,19 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "nam-tiny-hderive" +version = "0.3.1-nam.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2cd44792ed5cd84dc9dedc3d572242ac00e76c244e85eb4bf34da2c6239ce30" +dependencies = [ + "base58", + "hmac", + "k256", + "sha2 0.10.9", + "zeroize", +] + [[package]] name = "no_std_io2" version = "0.9.4" @@ -2357,6 +2386,122 @@ dependencies = [ "log", ] +[[package]] +name = "p3-dft" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b2764a3982d22d62aa933c8de6f9d71d8a474c9110b69e675dea1887bdeffc" +dependencies = [ + "itertools 0.14.0", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", + "tracing", +] + +[[package]] +name = "p3-field" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc13a73509fe09c67b339951ca8d4cc6e61c9bf08c130dbc90dda52452918cc2" +dependencies = [ + "itertools 0.14.0", + "num-bigint", + "p3-maybe-rayon", + "p3-util", + "paste", + "rand 0.9.5", + "serde", + "tracing", +] + +[[package]] +name = "p3-goldilocks" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552849f6309ffde34af0d31aa9a2d0a549cb0ec138d9792bfbf4a17800742362" +dependencies = [ + "num-bigint", + "p3-dft", + "p3-field", + "p3-mds", + "p3-poseidon2", + "p3-symmetric", + "p3-util", + "paste", + "rand 0.9.5", + "serde", +] + +[[package]] +name = "p3-matrix" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e1e9f69c2fe15768b3ceb2915edb88c47398aa22c485d8163deab2a47fe194" +dependencies = [ + "itertools 0.14.0", + "p3-field", + "p3-maybe-rayon", + "p3-util", + "rand 0.9.5", + "serde", + "tracing", + "transpose", +] + +[[package]] +name = "p3-maybe-rayon" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33f765046b763d046728b3246b690f81dfa7ccd7523b7a1582c74f616fbce6a0" + +[[package]] +name = "p3-mds" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c90541c6056712daf2ee69ec328db8b5605ae8dbafe60226c8eb75eaac0e1f9" +dependencies = [ + "p3-dft", + "p3-field", + "p3-symmetric", + "p3-util", + "rand 0.9.5", +] + +[[package]] +name = "p3-poseidon2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88e9f053f120a78ad27e9c1991a0ea547777328ca24025c42364d6ee2667d59a" +dependencies = [ + "p3-field", + "p3-mds", + "p3-symmetric", + "p3-util", + "rand 0.9.5", +] + +[[package]] +name = "p3-symmetric" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d5db8f05a26d706dfd8aaf7aa4272ca4f3e7a075db897ec7108f24fad78759" +dependencies = [ + "itertools 0.14.0", + "p3-field", + "serde", +] + +[[package]] +name = "p3-util" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dfee67245d9ce78a15176728da2280032f0a84b5819a39a953e7ec03cfd9bd7" +dependencies = [ + "serde", +] + [[package]] name = "parity-scale-codec" version = "3.7.5" @@ -2736,6 +2881,42 @@ dependencies = [ "prost", ] +[[package]] +name = "qp-ownership-circuit" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6dfd3d8f8fba2a5ebc966bb7d53f8eaf0e4582eb58663a549f9a8beb745dbb4" +dependencies = [ + "anyhow", + "qp-ownership-inputs", + "qp-plonky2", + "qp-wormhole-circuit", + "qp-zk-circuits-common", +] + +[[package]] +name = "qp-ownership-inputs" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d8b25b2a264b401240b011f6748306191478122b7b1fc15e45aad735ed05510" +dependencies = [ + "anyhow", + "qp-wormhole-inputs", +] + +[[package]] +name = "qp-ownership-prover" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04ea262e2d923cb1314cfe32b6289da3b3afe3f284e8a749a6ea5eafded3b50b" +dependencies = [ + "anyhow", + "qp-ownership-circuit", + "qp-plonky2", + "qp-wormhole-circuit", + "qp-zk-circuits-common", +] + [[package]] name = "qp-plonky2" version = "1.5.5" @@ -2756,7 +2937,7 @@ dependencies = [ "qp-plonky2-core", "qp-plonky2-field", "qp-plonky2-verifier", - "qp-poseidon-core", + "qp-poseidon-core 3.1.0", "rand 0.10.1", "serde", "static_assertions", @@ -2820,12 +3001,26 @@ dependencies = [ "plonky2_util", "qp-plonky2-core", "qp-plonky2-field", - "qp-poseidon-core", + "qp-poseidon-core 3.1.0", "serde", "static_assertions", "unroll", ] +[[package]] +name = "qp-poseidon-core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec326fc2631a929de09a38af2613a3db5230882c12a2f68205693ec632751e8b" +dependencies = [ + "p3-field", + "p3-goldilocks", + "p3-poseidon2", + "p3-symmetric", + "rand 0.9.5", + "rand_chacha 0.9.0", +] + [[package]] name = "qp-poseidon-core" version = "3.1.0" @@ -2851,7 +3046,7 @@ dependencies = [ "getrandom 0.2.17", "hex", "hex-literal", - "qp-poseidon-core", + "qp-poseidon-core 3.1.0", "qp-rusty-crystals-dilithium", "serde", "serde_json", @@ -2863,9 +3058,9 @@ dependencies = [ [[package]] name = "qp-wormhole-aggregator" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6604a9d9248e3270d330155fc27a4a84b382849b2aecb1f92a110511314fe07b" +checksum = "9f32f9fbbd49a0655b267a151afe4e6fd38edcad13bce79e487a949073672d46" dependencies = [ "anyhow", "hex", @@ -2881,9 +3076,9 @@ dependencies = [ [[package]] name = "qp-wormhole-circuit" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55167daf965a3616b74184171148b94c64b3a48771d6433a1a43377a6e46c6e8" +checksum = "997bce0db5f628ff973b9983b167f8499ab5e0aa2b07848d97d6be5e7b0e921a" dependencies = [ "anyhow", "hex", @@ -2895,9 +3090,9 @@ dependencies = [ [[package]] name = "qp-wormhole-circuit-builder" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fe35e0d8ad019e37e447e092da348fe4c52ae691d84dfd919fc9b58e7effe5" +checksum = "372d30af09b9b9fcde24549d40749219ecf4098a48ef7c0cae2cc414141b4435" dependencies = [ "anyhow", "clap", @@ -2910,18 +3105,18 @@ dependencies = [ [[package]] name = "qp-wormhole-inputs" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c07863a2211a17b46319289c5ef22e5670a3fb8653386bb366c418193254793" +checksum = "536c7358f87bb209d3113e89763a1bc7a45a2cd4fc82968869f1be3cdd59a21a" dependencies = [ "anyhow", ] [[package]] name = "qp-wormhole-prover" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7203e454113812e1ca85aee7687dc5f270f7c297d113a647a3a00b6d43c5a4f6" +checksum = "ee1fd5f90cfca2e5f01d7e36fe35d417b541443d7e6548d99b7e73b0c3ce75c4" dependencies = [ "anyhow", "qp-plonky2", @@ -2931,13 +3126,13 @@ dependencies = [ [[package]] name = "qp-zk-circuits-common" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec45510701f160fdb730bdb622d6cdd0a62547e607c9b1ecb19558202797f81" +checksum = "10135f3f253f33423341f97c71de9eeaf39fe7f1d791e1cfab026c14fdf172bd" dependencies = [ "anyhow", "qp-plonky2", - "qp-poseidon-core", + "qp-poseidon-core 3.1.0", "qp-wormhole-inputs", "rand 0.8.6", "serde", @@ -2989,10 +3184,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", - "rand_chacha", + "rand_chacha 0.3.1", "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_core 0.9.5", +] + [[package]] name = "rand" version = "0.10.1" @@ -3014,6 +3218,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + [[package]] name = "rand_core" version = "0.6.4" @@ -3023,6 +3237,12 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" + [[package]] name = "rand_core" version = "0.10.1" @@ -3140,9 +3360,16 @@ dependencies = [ "blake3", "flutter_rust_bridge", "hex", + "nam-tiny-hderive", + "p3-field", + "p3-goldilocks", + "p3-symmetric", "parity-scale-codec", + "qp-ownership-circuit", + "qp-ownership-prover", "qp-plonky2", - "qp-poseidon-core", + "qp-poseidon-core 0.9.5", + "qp-poseidon-core 3.1.0", "qp-rusty-crystals-dilithium", "qp-rusty-crystals-hdwallet", "qp-wormhole-aggregator", @@ -3152,6 +3379,8 @@ dependencies = [ "qp-wormhole-prover", "qp-zk-circuits-common", "quantus_ur", + "rand_chacha 0.9.0", + "sha2 0.10.9", "sp-core", "sp-runtime", "tempfile", @@ -3858,6 +4087,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + [[package]] name = "strsim" version = "0.11.1" @@ -4276,6 +4511,16 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "transpose" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +dependencies = [ + "num-integer", + "strength_reduce", +] + [[package]] name = "trie-db" version = "0.31.0" @@ -4441,7 +4686,7 @@ dependencies = [ "arrayref", "digest 0.10.7", "rand 0.8.6", - "rand_chacha", + "rand_chacha 0.3.1", "rand_core 0.6.4", "sha2 0.10.9", "sha3", diff --git a/quantus_sdk/rust/Cargo.toml b/quantus_sdk/rust/Cargo.toml index d661db037..882f2e64f 100644 --- a/quantus_sdk/rust/Cargo.toml +++ b/quantus_sdk/rust/Cargo.toml @@ -9,8 +9,16 @@ crate-type = ["cdylib", "staticlib", "rlib"] [dependencies] # NOTE: Quantus chain dependencies. qp-poseidon-core = "3.1.0" +# The Resonance-era (v0.9.x) Poseidon2 permutation used different round +# constants than the current one, so v09 airdrop addresses need the real crate. +qp-poseidon-core-v09 = { package = "qp-poseidon-core", version = "=0.9.5" } qp-rusty-crystals-dilithium = { version = "4.1.1", default-features = false, features = ["ml-dsa-65", "ml-dsa-87"] } qp-rusty-crystals-hdwallet = { version = "4.1.1", features = ["ml-dsa-65"] } +# The pre-March-2026 BIP32 tree ("Bitcoin seed" master HMAC key, soft child +# support): historical Dilithium accounts and wormhole entropies in +# api/airdrop.rs derive through it. Same crate qp-rusty-crystals-hdwallet +# < 2.1.0 used internally. +nam-tiny-hderive = { version = "=0.3.1-nam.1", default-features = false } flutter_rust_bridge = "=2.12.0" hex = "0.4.3" @@ -22,15 +30,28 @@ quantus_ur = { git = "https://github.com/Quantus-Network/quantus_ur.git", tag = # ZK proof generation (aligned with quantus-cli / chain) anyhow = "1.0" blake3 = "1.8" +# Airdrop ownership proofs (published on crates.io as of the v4.4.0 release). +# The wormhole circuit sources are identical between 4.3.0 and 4.4.0, so the +# family-wide bump keeps wormhole proofs chain-compatible. +qp-ownership-circuit = { version = "4.4.0", default-features = false, features = ["std"] } +qp-ownership-prover = { version = "4.4.0", default-features = false, features = ["std"] } +# Building blocks of qp-poseidon-core 0.9.5's hasher. The local v09 sponge in +# api/airdrop.rs hashes secret preimages from borrowed slices because the +# 0.9.5 crate's hash_no_pad consumes its Vec and frees it unscrubbed. +p3-field = { version = "0.3.0", default-features = false } +p3-goldilocks = { version = "0.3.0", default-features = false } +p3-symmetric = { version = "0.3.0", default-features = false } +rand_chacha = { version = "0.9", default-features = false } qp-plonky2 = { version = "1.5.5", default-features = false, features = ["rand", "std"] } -qp-wormhole-circuit = { version = "4.3.0", default-features = false, features = ["std"] } -qp-wormhole-prover = { version = "4.3.0", default-features = false, features = ["std"] } -qp-wormhole-aggregator = { version = "4.3.0", default-features = false, features = ["std"] } -qp-wormhole-inputs = { version = "4.3.0", default-features = false, features = ["std"] } -qp-zk-circuits-common = { version = "4.3.0", default-features = false, features = ["std"] } -qp-wormhole-circuit-builder = { version = "4.3.0" } +qp-wormhole-circuit = { version = "4.4.0", default-features = false, features = ["std"] } +qp-wormhole-prover = { version = "4.4.0", default-features = false, features = ["std"] } +qp-wormhole-aggregator = { version = "4.4.0", default-features = false, features = ["std"] } +qp-wormhole-inputs = { version = "4.4.0", default-features = false, features = ["std"] } +qp-zk-circuits-common = { version = "4.4.0", default-features = false, features = ["std"] } +qp-wormhole-circuit-builder = { version = "4.4.0" } [dev-dependencies] +sha2 = "0.10" tempfile = "3" # Circuit generation is CPU-intensive; without this it takes ~10min instead of ~30s. diff --git a/quantus_sdk/rust/src/api/airdrop.rs b/quantus_sdk/rust/src/api/airdrop.rs new file mode 100644 index 000000000..778223a20 --- /dev/null +++ b/quantus_sdk/rust/src/api/airdrop.rs @@ -0,0 +1,1509 @@ +//! Airdrop snapshot matching. Given the claim-server snapshot address list, +//! derive every historical address format from the wallet's keys locally and +//! return which snapshot rows belong to this wallet. Nothing here talks to a +//! server; secrets never leave the process. +//! +//! Testnet address history: +//! - early Resonance (poseidon-resonance 0.8.0): legacy plonky2 Poseidon, +//! 8-byte limbs zero-padded to 73 felts +//! - mid Resonance (qp-poseidon 0.9.1): legacy plonky2 Poseidon, 4-byte limbs +//! zero-padded to 188 felts +//! - Resonance / early Schrödinger (qp-poseidon 0.9.5): different permutation +//! constants (ChaCha8, seed 0x189189189189189) AND a different sponge +//! (rate 4, pad10 + domain block). Handled by the real 0.9.5 crate. +//! - late Schrödinger / Dirac (1.0.x–1.1.x): current permutation, rate 4, pad10 +//! - late Planck / current (1.2.x+): current permutation, rate 8, pad10 + +use nam_tiny_hderive::bip32::ExtendedPrivKey; +use qp_ownership_circuit::{BytesDigest, CircuitInputs, Secret}; +use qp_poseidon_core::{ + serialization::digest_to_bytes, Goldilocks, Poseidon2, POSEIDON2_OUTPUT, SPONGE_WIDTH, +}; +use qp_poseidon_core_v09 as v09; +use qp_rusty_crystals_dilithium::{fips202, ml_dsa_87, packing, params, poly, polyvec}; +use qp_rusty_crystals_hdwallet::{ + generate_wormhole_from_seed, mnemonic_to_seed, SensitiveBytes64, QUANTUS_WORMHOLE_CHAIN_ID, +}; +use std::collections::HashMap; + +use crate::sensitive::{wipe, SensitiveVec}; + +const RATE_4: usize = 4; +const WORMHOLE_SALT: &[u8] = b"wormhole"; +/// Only proofs for the current wormhole scheme are accepted by the claim server. +const CLAIMABLE_WORMHOLE_SCHEME: &str = "wormhole-rate8-compact"; +/// Address indexes scanned per wormhole HD branch. The shipped wallets never +/// capped the index, so scan well past any realistic count. +const HD_SCAN_INDEXES: u32 = 100; +/// Middle HD path component. The app uses 0 (external) and 1 (dedicated +/// change branch since July 2026); the CLI's wormhole multiround flow uses it +/// as a round counter (default 2 rounds), so scan several rounds beyond that. +const HD_SCAN_BRANCHES: u32 = 9; +/// FIPS 204 context for airdrop claim signatures; must match the claim server. +const CLAIM_CONTEXT: &[u8] = b"qp-airdrop-claim-v1"; +/// Claim expiry horizon. The server rejects expiries more than 15 min out. +const CLAIM_TTL_SECS: i64 = 10 * 60; + +// --------------------------------------------------------------------------- +// Historical Poseidon2 sponges +// --------------------------------------------------------------------------- + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Sponge { + /// qp-poseidon 0.9.x `hash_no_pad`: distinct permutation constants and a + /// rate-4 pad10 sponge with a mandatory `[0,0,0,1]` block. Uses the real + /// 0.9.5 crate; see `WormholeSchemeDef::derive`. + V09, + /// qp-poseidon 1.0.x–1.1.x: rate 4, pad10 only. + Rate4Pad10, + /// qp-poseidon 1.2.x+ (current): rate 8, pad10. + Rate8Pad10, +} + +impl Sponge { + fn hash_felts(self, input: &[Goldilocks]) -> [u8; 32] { + match self { + Self::V09 => unreachable!("v09 uses its own field type; see derive()"), + Self::Rate4Pad10 => hash_felts_rate4_pad10(input), + Self::Rate8Pad10 => qp_poseidon_core::hash_to_bytes(input), + } + } + + fn rehash(self, digest: &[u8; 32]) -> [u8; 32] { + self.hash_felts(&compact8_decode(digest)) + } +} + +/// 8-byte little-endian limbs, reduced into Goldilocks. +fn compact8_decode(bytes: &[u8; 32]) -> [Goldilocks; POSEIDON2_OUTPUT] { + qp_poseidon_core::serialization::bytes_to_digest_lossy(bytes) +} + +/// Field element of qp-poseidon-core 0.9.5 (plonky3 Goldilocks 0.3.0). +type V09Felt = p3_goldilocks::Goldilocks; + +/// The injective 4-bytes-per-felt encoding of a 32-byte secret, as canonical +/// limb values: eight little-endian u32 words plus the `1` terminator +/// (32 % 4 == 0, so the terminator is always appended). Matches both +/// `injective4` and v0.9.5's `injective_bytes_to_felts` without materializing +/// an intermediate felt buffer. +fn injective4_secret_words(secret: &[u8; 32]) -> impl Iterator + '_ { + secret + .chunks(4) + .map(|chunk| u32::from_le_bytes(chunk.try_into().expect("4-byte chunk")) as u64) + .chain([1u64]) +} + +/// The v0.9.5 Poseidon2 permutation, rebuilt from the same public crates the +/// historical qp-poseidon-core used: ChaCha8-derived constants with seed +/// 0x189189189189189 over `Poseidon2Goldilocks<12>`. Equality with +/// `Poseidon2Core::new()` is pinned by the wormhole-v09-injective golden +/// vector test. +fn v09_permutation() -> &'static p3_goldilocks::Poseidon2Goldilocks<12> { + use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; + static PERMUTATION: std::sync::OnceLock> = + std::sync::OnceLock::new(); + PERMUTATION.get_or_init(|| { + const V09_POSEIDON2_SEED: u64 = 0x189189189189189; + let mut rng = ChaCha8Rng::seed_from_u64(V09_POSEIDON2_SEED); + p3_goldilocks::Poseidon2Goldilocks::<12>::new_from_rng_128(&mut rng) + }) +} + +/// qp-poseidon-core 0.9.5's `hash_no_pad` sponge (rate 4, terminator felt in +/// the last short block, `[1,0,0,0]` block after a full final chunk, then an +/// unconditional `[0,0,0,1]` domain block), reimplemented over a borrowed +/// slice with stack state. The 0.9.5 crate's own `hash_no_pad` takes its +/// preimage `Vec` by value and frees it unscrubbed, so it must never see a +/// secret. +fn v09_hash_no_pad(input: &[V09Felt]) -> [u8; 32] { + use p3_field::{PrimeCharacteristicRing, PrimeField64}; + use p3_symmetric::Permutation; + const WIDTH: usize = 12; + const RATE: usize = 4; + + let poseidon2 = v09_permutation(); + let mut state = [V09Felt::ZERO; WIDTH]; + let mut block = [V09Felt::ZERO; RATE]; + let num_chunks = input.chunks(RATE).len(); + let mut unpadded = false; + for (j, chunk) in input.chunks(RATE).enumerate() { + block.fill(V09Felt::ZERO); + if j == num_chunks - 1 { + if chunk.len() < RATE { + block[chunk.len()] = V09Felt::ONE; + } else { + unpadded = true; + } + } + block[..chunk.len()].copy_from_slice(chunk); + for i in 0..RATE { + state[i] += block[i]; + } + poseidon2.permute_mut(&mut state); + } + if unpadded { + state[0] += V09Felt::ONE; + poseidon2.permute_mut(&mut state); + } + state[RATE - 1] += V09Felt::ONE; + poseidon2.permute_mut(&mut state); + + let mut out = [0u8; 32]; + for (i, felt) in state[..RATE].iter().enumerate() { + out[i * 8..(i + 1) * 8].copy_from_slice(&felt.as_canonical_u64().to_le_bytes()); + } + wipe(&mut state); + wipe(&mut block); + out +} + +/// v0.9.5 injective 4-bytes/felt encoding (terminator in the last word or as an extra felt). +fn injective4(bytes: &[u8]) -> Vec { + if bytes.is_empty() { + return Vec::new(); + } + const N: usize = 4; + let mut out = Vec::new(); + let num_chunks = bytes.len().div_ceil(N); + let mut unpadded = false; + for (i, chunk) in bytes.chunks(N).enumerate() { + let mut word = [0u8; N]; + if i == num_chunks - 1 { + if chunk.len() < N { + word[chunk.len()] = 1; + } else { + unpadded = true; + } + } + word[..chunk.len()].copy_from_slice(chunk); + out.push(Goldilocks::from_u64(u32::from_le_bytes(word) as u64)); + } + if unpadded { + out.push(Goldilocks::from_u64(1)); + } + out +} + +fn hash_felts_rate4_pad10(x: &[Goldilocks]) -> [u8; 32] { + let poseidon = Poseidon2::new(); + let mut state = [Goldilocks::ZERO; SPONGE_WIDTH]; + let mut buf = [Goldilocks::ZERO; RATE_4]; + let mut buf_len = 0usize; + + let absorb = |felt: Goldilocks, + state: &mut [Goldilocks; SPONGE_WIDTH], + buf: &mut [Goldilocks; RATE_4], + buf_len: &mut usize| { + buf[*buf_len] = felt; + *buf_len += 1; + if *buf_len == RATE_4 { + for i in 0..RATE_4 { + state[i] += buf[i]; + } + poseidon.permute_mut(state); + *buf = [Goldilocks::ZERO; RATE_4]; + *buf_len = 0; + } + }; + + for &felt in x { + absorb(felt, &mut state, &mut buf, &mut buf_len); + } + absorb(Goldilocks::ONE, &mut state, &mut buf, &mut buf_len); + while buf_len != 0 { + absorb(Goldilocks::ZERO, &mut state, &mut buf, &mut buf_len); + } + + let digest: [Goldilocks; POSEIDON2_OUTPUT] = state[..POSEIDON2_OUTPUT] + .try_into() + .expect("width > output"); + // The absorb buffer holds raw preimage felts and the state is the + // permuted secret; wipe both before returning. + wipe(&mut state); + wipe(&mut buf); + digest_to_bytes(&digest) +} + +/// Pre-0.9.5 Resonance AccountId hash: legacy plonky2 Poseidon (unchanged in +/// today's qp-plonky2) over little-endian limbs, zero-padded to a fixed +/// preimage length. poseidon-resonance 0.8.0 used 8-byte limbs and 73 felts; +/// qp-poseidon 0.9.1 used 4-byte limbs and 188 felts. +fn hash_padded_legacy(bytes: &[u8], bytes_per_felt: usize, pad_to: usize) -> [u8; 32] { + use plonky2::field::goldilocks_field::GoldilocksField; + use plonky2::field::types::Field; + use plonky2::plonk::config::{GenericHashOut, Hasher}; + + let mut felts: Vec = bytes + .chunks(bytes_per_felt) + .map(|chunk| { + let mut word = [0u8; 8]; + word[..chunk.len()].copy_from_slice(chunk); + GoldilocksField::from_noncanonical_u64(u64::from_le_bytes(word)) + }) + .collect(); + if felts.len() < pad_to { + felts.resize(pad_to, GoldilocksField::ZERO); + } + plonky2::hash::poseidon::PoseidonHash::hash_no_pad(&felts) + .to_bytes() + .try_into() + .expect("poseidon output is 32 bytes") +} + +/// qp-poseidon 1.0.x Dilithium AccountId: injective bytes, zero-pad to 189, rate-4 pad10. +fn hash_padded_v10(bytes: &[u8]) -> [u8; 32] { + const PAD: usize = 189; + let mut felts = injective4(bytes); + if felts.len() < PAD { + felts.resize(PAD, Goldilocks::ZERO); + } + hash_felts_rate4_pad10(&felts) +} + +// --------------------------------------------------------------------------- +// Derivation schemes +// --------------------------------------------------------------------------- + +#[derive(Clone, Copy)] +enum SecretEncoding { + Injective4, + Compact8, +} + +struct WormholeSchemeDef { + id: &'static str, + sponge: Sponge, + secret_encoding: SecretEncoding, +} + +const WORMHOLE_SCHEMES: &[WormholeSchemeDef] = &[ + WormholeSchemeDef { + id: "wormhole-v09-injective", + sponge: Sponge::V09, + secret_encoding: SecretEncoding::Injective4, + }, + WormholeSchemeDef { + id: "wormhole-rate4-compact", + sponge: Sponge::Rate4Pad10, + secret_encoding: SecretEncoding::Compact8, + }, + WormholeSchemeDef { + id: "wormhole-rate8-compact", + sponge: Sponge::Rate8Pad10, + secret_encoding: SecretEncoding::Compact8, + }, + WormholeSchemeDef { + id: "wormhole-rate4-injective", + sponge: Sponge::Rate4Pad10, + secret_encoding: SecretEncoding::Injective4, + }, + WormholeSchemeDef { + id: "wormhole-rate8-injective", + sponge: Sponge::Rate8Pad10, + secret_encoding: SecretEncoding::Injective4, + }, +]; + +impl WormholeSchemeDef { + /// All preimages holding the secret are built in pre-sized + /// zeroize-on-drop buffers and hashed from borrowed slices, so no heap + /// allocation containing the secret is ever freed unscrubbed (verified by + /// the allocator test in `heap_zeroization_tests`). + fn derive(&self, secret: &[u8; 32]) -> [u8; 32] { + use p3_field::integers::QuotientMap; + if self.sponge == Sponge::V09 { + let salt = v09::injective_bytes_to_felts(WORMHOLE_SALT); + let mut preimage = SensitiveVec::with_capacity(salt.len() + 9); + for felt in salt { + preimage.push(felt); + } + match self.secret_encoding { + SecretEncoding::Injective4 => { + for word in injective4_secret_words(secret) { + preimage.push(V09Felt::from_int(word)); + } + } + SecretEncoding::Compact8 => { + for chunk in secret.chunks(8) { + let word = u64::from_le_bytes(chunk.try_into().expect("8-byte chunk")); + preimage.push(V09Felt::from_int(word)); + } + } + } + let first_hash = v09_hash_no_pad(&preimage); + return v09_hash_no_pad(&v09::digest_bytes_to_felts(&first_hash)); + } + let salt = injective4(WORMHOLE_SALT); + // injective4 of a 32-byte secret is exactly 9 felts; compact8 is 4. + let mut preimage = SensitiveVec::with_capacity(salt.len() + 9); + for felt in salt { + preimage.push(felt); + } + match self.secret_encoding { + SecretEncoding::Injective4 => { + for word in injective4_secret_words(secret) { + preimage.push(Goldilocks::from_u64(word)); + } + } + SecretEncoding::Compact8 => { + let mut digest = compact8_decode(secret); + for felt in digest { + preimage.push(felt); + } + wipe(&mut digest); + } + } + let first_hash = self.sponge.hash_felts(&preimage); + self.sponge.rehash(&first_hash) + } +} + +const DILITHIUM_SCHEMES: &[&str] = &[ + "dilithium-v08-padded", + "dilithium-v091-padded", + "dilithium-v09-padded", + "dilithium-v10-padded", + "dilithium-rate8-hash-bytes", +]; + +fn derive_dilithium(scheme: &str, public_key: &[u8]) -> [u8; 32] { + match scheme { + "dilithium-v08-padded" => hash_padded_legacy(public_key, 8, 73), + "dilithium-v091-padded" => hash_padded_legacy(public_key, 4, 188), + "dilithium-v09-padded" => v09::Poseidon2Core::new().hash_padded(public_key), + "dilithium-v10-padded" => hash_padded_v10(public_key), + "dilithium-rate8-hash-bytes" => qp_poseidon_core::hash_bytes(public_key), + _ => unreachable!(), + } +} + +// --------------------------------------------------------------------------- +// Historical ML-DSA-87 keygens +// --------------------------------------------------------------------------- +// +// The address-hash schemes above cover how a *public key* became an +// AccountId. The public key produced from the same mnemonic changed too: +// +// 1. pre Nov 2025 (qp-rusty-crystals-dilithium < 2.0.0): keygen expanded +// SHAKE256(seed[..32]) with no domain separation. Wallets were non-HD +// (`Keypair::generate(seed64)`, which absorbed only the first 32 bytes) +// or, after the multiple-accounts feature, HD at m/44'/189189'/N'/0/0 +// under the BIP32 master HMAC key "Bitcoin seed" with a soft tail. +// 2. Nov 2025 (dilithium 2.0.0): FIPS 204 keygen, SHAKE256(seed ‖ K ‖ L) +// absorbing the whole input. Same "Bitcoin seed" paths; the non-HD +// account went from an effective 32-byte input to the full 64-byte seed. +// 3. Feb 2026 (CLI only): default path hardened to m/44'/189189'/0'/0'/0'; +// --no-derivation wallets became the BIP32 child at m/44'/189189'/0'. +// 4. Mar 2026 (hdwallet 2.1.0): the BIP32 master HMAC key became +// "Dilithium seed" (hardened-only), changing every HD key again; the +// non-HD legacy account became FIPS keygen over seed64[..32]. This is +// the current scheme. +// +// Matching therefore derives candidate keypairs for every era and hashes +// each candidate public key under every address scheme. + +/// BIP44 coin type for Dilithium keys (the wormhole coin type is 189189189'). +const DILITHIUM_CHAIN_ID: &str = "189189'"; +/// Account indexes scanned per HD keygen family. The multiple-accounts +/// wallet never capped the account index, so scan well past any realistic +/// count. +const DILITHIUM_SCAN_ACCOUNTS: u32 = 100; + +struct HistoricalKeypair { + public: Vec, + secret: SensitiveVec, +} + +/// Every mnemonic→ML-DSA-87-keypair scheme a snapshot key may have used. +/// Ids are opaque to callers ([`derive_historical_dilithium`] parses them): +/// `v1`/`fips` selects the seed expansion, and the source is `seed` (the +/// 64-byte BIP39 seed), `seed32` (its first half, the chain's `from_seed`), +/// `bip32:` ("Bitcoin seed" BIP32 entropy), or `hd:` (current +/// "Dilithium seed" derivation). +fn dilithium_keygen_ids() -> Vec { + let mut ids = vec![ + // Era 1 non-HD (absorbs seed64[..32]); era 2 non-HD; era 4 legacy. + "v1:seed".to_string(), + "fips:seed".to_string(), + "fips:seed32".to_string(), + ]; + for account in 0..DILITHIUM_SCAN_ACCOUNTS { + // Era 1 and era 2 app/CLI accounts (soft tail), era 3 CLI hardened + // default and --no-derivation account child, era 4 current HD. + ids.push(format!( + "v1:bip32:m/44'/{DILITHIUM_CHAIN_ID}/{account}'/0/0" + )); + ids.push(format!( + "fips:bip32:m/44'/{DILITHIUM_CHAIN_ID}/{account}'/0/0" + )); + ids.push(format!( + "fips:bip32:m/44'/{DILITHIUM_CHAIN_ID}/{account}'/0'/0'" + )); + ids.push(format!("fips:bip32:m/44'/{DILITHIUM_CHAIN_ID}/{account}'")); + ids.push(format!( + "fips:hd:m/44'/{DILITHIUM_CHAIN_ID}/{account}'/0'/0'" + )); + } + ids +} + +/// Derive the ML-DSA-87 keypair for a historical keygen id from the BIP39 +/// seed. The secret key comes back in a self-wiping buffer; intermediate +/// entropy buffers are wiped before returning. +fn derive_historical_dilithium( + seed: &SensitiveBytes64, + id: &str, +) -> Result { + let (expansion, source) = id + .split_once(':') + .ok_or_else(|| format!("malformed keygen id {id:?}"))?; + let v1 = match expansion { + "v1" => true, + "fips" => false, + _ => return Err(format!("unknown keygen era in {id:?}")), + }; + if source == "seed" { + return Ok(mldsa87_keypair(seed.as_bytes(), v1)); + } + if source == "seed32" { + return Ok(mldsa87_keypair(&seed.as_bytes()[..32], v1)); + } + if let Some(path) = source.strip_prefix("bip32:") { + let mut entropy = ExtendedPrivKey::derive(seed.as_bytes(), path) + .map_err(|e| format!("BIP32 derivation failed at {path}: {e:?}"))? + .secret(); + let keypair = mldsa87_keypair(&entropy, v1); + wipe(&mut entropy); + return Ok(keypair); + } + if let Some(path) = source.strip_prefix("hd:") { + if v1 { + return Err(format!( + "keygen id {id:?} is inconsistent: no v1-era wallet used the Dilithium-seed tree" + )); + } + let keypair = qp_rusty_crystals_hdwallet::ml_dsa_87::derive_key_from_seed(seed, path) + .map_err(|e| format!("HD derivation failed at {path}: {e:?}"))?; + // `to_bytes` returns a `Zeroizing` buffer, wiped when it drops here. + let secret = SensitiveVec(keypair.secret().to_bytes().to_vec()); + return Ok(HistoricalKeypair { + public: keypair.public().to_bytes().to_vec(), + secret, + }); + } + Err(format!("unknown keygen source in {id:?}")) +} + +/// ML-DSA-87 key generation with a selectable seed expansion, built from the +/// current crate's public primitives (its `keypair_var` is not public, and +/// the historical crates' own keygens copy the seed into heap buffers they +/// free unscrubbed). `v1` selects the pre-FIPS expansion — SHAKE256 over the +/// first 32 seed bytes with no `K ‖ L` domain suffix; otherwise the FIPS 204 +/// expansion absorbs the whole seed plus the suffix. Byte-equality of both +/// keypairs with the shipped dilithium 1.0.3 / 2.0.0 keygens is pinned by +/// golden vectors in the tests. +fn mldsa87_keypair(seed: &[u8], v1: bool) -> HistoricalKeypair { + use params::ml_dsa_87::{ETA, K, L, PUBLICKEYBYTES, SECRETKEYBYTES}; + use params::{CRHBYTES, SEEDBYTES, TR_BYTES}; + use polyvec::Polyvec; + + debug_assert!(seed.len() == 32 || seed.len() == 64); + let mut seedbuf = [0u8; 2 * SEEDBYTES + CRHBYTES]; + if v1 { + // The v1 expansion reads exactly SEEDBYTES from its input. + fips202::shake256(&mut seedbuf, &seed[..SEEDBYTES]); + } else { + let mut preimage = [0u8; 64 + 2]; + preimage[..seed.len()].copy_from_slice(seed); + preimage[seed.len()] = K as u8; + preimage[seed.len() + 1] = L as u8; + fips202::shake256(&mut seedbuf, &preimage[..seed.len() + 2]); + wipe(&mut preimage); + } + + let mut rho = [0u8; SEEDBYTES]; + rho.copy_from_slice(&seedbuf[..SEEDBYTES]); + let mut rhoprime = [0u8; CRHBYTES]; + rhoprime.copy_from_slice(&seedbuf[SEEDBYTES..SEEDBYTES + CRHBYTES]); + let mut key = [0u8; SEEDBYTES]; + key.copy_from_slice(&seedbuf[SEEDBYTES + CRHBYTES..]); + wipe(&mut seedbuf); + + let mut s1 = Polyvec::::default(); + for (i, p) in s1.vec.iter_mut().enumerate() { + poly::uniform_eta::(p, &rhoprime, i as u16); + } + let mut s2 = Polyvec::::default(); + for (i, p) in s2.vec.iter_mut().enumerate() { + poly::uniform_eta::(p, &rhoprime, (L + i) as u16); + } + wipe(&mut rhoprime); + + let mut s1hat = s1.clone(); + polyvec::ntt(&mut s1hat); + let mut t1 = Polyvec::::default(); + polyvec::matrix_pointwise_montgomery_streamed(&mut t1, &rho, &s1hat); + polyvec::reduce(&mut t1); + polyvec::invntt_tomont(&mut t1); + polyvec::add(&mut t1, &s2); + polyvec::caddq(&mut t1); + let mut t0 = Polyvec::::default(); + polyvec::power2round(&mut t1, &mut t0); + + let mut pk = [0u8; PUBLICKEYBYTES]; + packing::pack_pk::(&mut pk, &rho, &t1); + let mut tr = [0u8; TR_BYTES]; + fips202::shake256(&mut tr, &pk); + let mut sk = [0u8; SECRETKEYBYTES]; + packing::pack_sk::(&mut sk, &rho, &tr, &key, &t0, &s1, &s2); + wipe(&mut key); + + // s1, s2, t0, and s1hat wipe themselves on drop (Polyvec is + // ZeroizeOnDrop); the packed sk moves into a self-wiping buffer and its + // stack copy is scrubbed here. + let secret = SensitiveVec(sk.to_vec()); + wipe(&mut sk); + HistoricalKeypair { + public: pk.to_vec(), + secret, + } +} + +/// Wormhole HD paths the app and CLI may have used: account 0', every +/// scanned branch and address index. +fn wormhole_scan_paths() -> impl Iterator { + (0..HD_SCAN_BRANCHES).flat_map(|branch| { + (0..HD_SCAN_INDEXES) + .map(move |index| format!("m/44'/{QUANTUS_WORMHOLE_CHAIN_ID}/0'/{branch}'/{index}'")) + }) +} + +/// Scan-buffer entries one mnemonic contributes: the path grid under both +/// BIP32 masters plus the legacy master-node secret. +const WORMHOLE_SCAN_SECRETS: usize = 2 * (HD_SCAN_BRANCHES * HD_SCAN_INDEXES) as usize + 1; + +// --------------------------------------------------------------------------- +// FFI surface +// --------------------------------------------------------------------------- + +/// A snapshot address this wallet can prove ownership of. +#[flutter_rust_bridge::frb(sync)] +pub struct AirdropMatch { + /// The address exactly as it appeared in `snapshot_addresses`. + pub address: String, + /// "dilithium" or "wormhole". + pub kind: String, + /// Derivation scheme id, e.g. "dilithium-v10-padded". + pub scheme: String, + /// Whether the claim server currently accepts proofs for this scheme. + pub claimable: bool, + /// Where the key came from: "dilithium public key", an HD path, + /// "provided secret #N", or a historical keygen description. + pub source: String, + /// The 32-byte wormhole secret that produced the match (needed to build + /// the ownership proof). None for Dilithium matches. + pub wormhole_secret: Option>, + /// For Dilithium matches found through a historical keygen scheme: the + /// keygen id to pass to `build_airdrop_dilithium_claim_from_mnemonic`. + /// None when the match came from the provided current public key (claim + /// with `build_airdrop_dilithium_claim`). + pub dilithium_keygen: Option, +} + +/// Determine which snapshot addresses belong to this wallet. +/// +/// `snapshot_addresses` are the SS58 addresses from `GET /snapshot` (or the +/// miner-rewards CSV). Dilithium matches are checked against +/// `dilithium_public_key` under every historical hash, and — when `mnemonic` +/// is given — against keypairs re-derived under every historical keygen era +/// (see `dilithium_keygen_ids`). Wormhole matches are checked for HD-derived +/// secrets (`m/44'/189189189'/0'/{0..=8}'/{0..=99}'` under both the current +/// "Dilithium seed" and the pre-2.1.0 "Bitcoin seed" BIP32 masters, plus the +/// legacy master-node secret) when `mnemonic` is given, plus any +/// `extra_wormhole_secrets` (32 bytes each). +pub fn find_airdrop_matches( + snapshot_addresses: Vec, + dilithium_public_key: Option>, + mnemonic: Option, + extra_wormhole_secrets: Vec>, +) -> Result, String> { + let mut by_account: HashMap<[u8; 32], String> = HashMap::new(); + for address in snapshot_addresses { + let account: [u8; 32] = super::crypto::ss58_to_account_id(&address)? + .try_into() + .map_err(|_| format!("address {address} did not decode to 32 bytes"))?; + by_account.insert(account, address); + } + + let mut matches = Vec::new(); + let mut seen_dilithium: Vec<([u8; 32], &str)> = Vec::new(); + + if let Some(public_key) = dilithium_public_key { + if !public_key.is_empty() { + for scheme in DILITHIUM_SCHEMES { + let account = derive_dilithium(scheme, &public_key); + if let Some(address) = by_account.get(&account) { + seen_dilithium.push((account, scheme)); + matches.push(AirdropMatch { + address: address.clone(), + kind: "dilithium".into(), + scheme: (*scheme).into(), + claimable: true, + source: "dilithium public key".into(), + wormhole_secret: None, + dilithium_keygen: None, + }); + } + } + } + } + + let extra_wormhole_secrets = SensitiveVec(extra_wormhole_secrets); + // Every candidate spend secret goes into one pre-sized self-wiping + // buffer, so neither growth nor an early return frees it unscrubbed. + let mut secrets = SensitiveVec::with_capacity( + mnemonic.as_ref().map_or(0, |_| WORMHOLE_SCAN_SECRETS) + extra_wormhole_secrets.len(), + ); + if let Some(mnemonic) = mnemonic { + // Stretch the BIP39 seed once, then walk the HD tree per path. + // `mnemonic_to_seed` consumes and zeroizes the mnemonic string. + let mut seed = SensitiveBytes64::zeroed(); + mnemonic_to_seed(mnemonic, None, &mut seed) + .map_err(|e| format!("invalid mnemonic: {e:?}"))?; + + // Dilithium keys under every historical keygen era. The current-era + // candidates can duplicate a match already found through the provided + // public key; the (account, scheme) dedupe keeps the pubkey-based + // match, whose claim path needs no mnemonic. + for id in dilithium_keygen_ids() { + let keypair = derive_historical_dilithium(&seed, &id)?; + for scheme in DILITHIUM_SCHEMES { + let account = derive_dilithium(scheme, &keypair.public); + if let Some(address) = by_account.get(&account) { + if seen_dilithium.contains(&(account, scheme)) { + continue; + } + seen_dilithium.push((account, scheme)); + matches.push(AirdropMatch { + address: address.clone(), + kind: "dilithium".into(), + scheme: (*scheme).into(), + claimable: true, + source: format!("mnemonic keygen {id}"), + wormhole_secret: None, + dilithium_keygen: Some(id.clone()), + }); + } + } + } + + // Wormhole secrets under the current "Dilithium seed" tree. + for path in wormhole_scan_paths() { + let pair = generate_wormhole_from_seed(&seed, &path) + .map_err(|e| format!("HD derivation failed at {path}: {e:?}"))?; + secrets.push((*pair.secret().as_bytes(), path)); + } + + // Wormhole secrets under the pre-March-2026 "Bitcoin seed" tree: + // same paths, different master HMAC key, so every entropy differs. + // The app's first wormhole address also used the master node's own + // key (hdwallet 1.0.0 `generate_wormhole_pair`), hence path "m". + for path in std::iter::once("m".to_string()).chain(wormhole_scan_paths()) { + let entropy = ExtendedPrivKey::derive(seed.as_bytes(), path.as_str()) + .map_err(|e| format!("BIP32 derivation failed at {path}: {e:?}"))? + .secret(); + secrets.push((entropy, format!("bitcoin-seed {path}"))); + } + } + for (i, secret) in extra_wormhole_secrets.iter().enumerate() { + let copy = <[u8; 32]>::try_from(secret.as_slice()) + .map_err(|_| format!("wormhole secret #{i} must be 32 bytes"))?; + secrets.push((copy, format!("provided secret #{i}"))); + } + + let mut seen: Vec<([u8; 32], &'static str)> = Vec::new(); + for (secret, source) in secrets.iter() { + for scheme in WORMHOLE_SCHEMES { + let account = scheme.derive(secret); + if let Some(address) = by_account.get(&account) { + if seen.contains(&(account, scheme.id)) { + continue; + } + seen.push((account, scheme.id)); + matches.push(AirdropMatch { + address: address.clone(), + kind: "wormhole".into(), + scheme: scheme.id.into(), + claimable: scheme.id == CLAIMABLE_WORMHOLE_SCHEME, + source: source.clone(), + wormhole_secret: Some(secret.to_vec()), + dilithium_keygen: None, + }); + } + } + } + + // `secrets` wipes itself on drop. Matched secrets intentionally survive + // in the returned `AirdropMatch`es — the caller needs them to prove. + Ok(matches) +} + +fn decode_account(label: &str, address: &str) -> Result<[u8; 32], String> { + super::crypto::ss58_to_account_id(address)? + .try_into() + .map_err(|_| format!("{label} {address} did not decode to 32 bytes")) +} + +fn now_unix() -> Result { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_secs() as i64) + .map_err(|e| format!("system clock before epoch: {e}")) +} + +/// The `POST /claim` body fields for a Dilithium claim. +#[derive(Debug)] +#[flutter_rust_bridge::frb(sync)] +pub struct DilithiumClaimBody { + pub scheme: String, + pub address: String, + pub claim_account: String, + pub public_key_hex: String, + pub signature_hex: String, + pub expiry_unix: i64, +} + +/// Sign an airdrop claim for a Dilithium-era snapshot `address`, paying out to +/// `claim_account`. The wallet must be ML-DSA-87 (miner keys are). Submit the +/// result as `{"kind": "dilithium", ...fields}` within ~10 minutes. +pub fn build_airdrop_dilithium_claim( + keypair: &super::crypto::Keypair, + address: String, + claim_account: String, +) -> Result { + if keypair.scheme != super::crypto::DilithiumScheme::MlDsa87 { + return Err("airdrop claims require an ML-DSA-87 keypair".into()); + } + sign_dilithium_claim( + &keypair.public_key, + &keypair.secret_key, + address, + claim_account, + ) +} + +/// Sign an airdrop claim with a key from a historical keygen era: pass the +/// `dilithium_keygen` id from the `AirdropMatch`. Re-derives the era's +/// keypair from the mnemonic, so the wallet does not need to store it. +/// Submit like `build_airdrop_dilithium_claim`'s result. +pub fn build_airdrop_dilithium_claim_from_mnemonic( + mnemonic: String, + dilithium_keygen: String, + address: String, + claim_account: String, +) -> Result { + // `mnemonic_to_seed` consumes and zeroizes the mnemonic string. + let mut seed = SensitiveBytes64::zeroed(); + mnemonic_to_seed(mnemonic, None, &mut seed).map_err(|e| format!("invalid mnemonic: {e:?}"))?; + let keypair = derive_historical_dilithium(&seed, &dilithium_keygen)?; + sign_dilithium_claim(&keypair.public, &keypair.secret, address, claim_account) +} + +fn sign_dilithium_claim( + public_key: &[u8], + secret_key: &[u8], + address: String, + claim_account: String, +) -> Result { + let address_bytes = decode_account("address", &address)?; + let claim_bytes = decode_account("claim account", &claim_account)?; + + let scheme = DILITHIUM_SCHEMES + .iter() + .find(|s| derive_dilithium(s, public_key) == address_bytes) + .ok_or("public key does not derive this address under any known Dilithium scheme")?; + + let expiry_unix = now_unix()?.saturating_add(CLAIM_TTL_SECS); + let mut msg = [0u8; 72]; + msg[..32].copy_from_slice(&address_bytes); + msg[32..64].copy_from_slice(&claim_bytes); + msg[64..].copy_from_slice(&expiry_unix.to_be_bytes()); + + let secret = + ml_dsa_87::SecretKey::from_bytes(secret_key).map_err(|_| "invalid ML-DSA-87 secret key")?; + let signature = secret + .sign(&msg, Some(CLAIM_CONTEXT), None) + .map_err(|e| format!("ML-DSA sign failed: {e}"))?; + + Ok(DilithiumClaimBody { + scheme: (*scheme).into(), + address, + claim_account, + public_key_hex: hex::encode(public_key), + signature_hex: hex::encode(signature), + expiry_unix, + }) +} + +/// The `POST /claim` body fields for a wormhole claim. +#[flutter_rust_bridge::frb(sync)] +pub struct WormholeClaimBody { + pub proof_kind: String, + pub proof_hex: String, +} + +/// Prove ownership of the wormhole address derived from `wormhole_secret` +/// (current rate-8 scheme), binding the payout to `claim_account`. Builds the +/// circuit in-process: expect tens of seconds of CPU on first call. Submit as +/// `{"kind": "wormhole", ...fields}`. +pub fn prove_airdrop_wormhole( + mut wormhole_secret: Vec, + claim_account: String, +) -> Result { + // Copy into a fixed buffer and scrub both the FFI-owned Vec and (via + // `Secret::new`, which zeroizes its source) the copy, so no unscrubbed + // Rust-side copy of the secret outlives this call. + let valid_len = wormhole_secret.len() == 32; + let mut secret_bytes = [0u8; 32]; + if valid_len { + secret_bytes.copy_from_slice(&wormhole_secret); + } + wipe(&mut wormhole_secret); + if !valid_len { + return Err("wormhole secret must be 32 bytes".into()); + } + let secret = + Secret::new(&mut secret_bytes).map_err(|e| format!("invalid wormhole secret: {e:?}"))?; + let claim_bytes = decode_account("claim account", &claim_account)?; + let claim = BytesDigest::try_from(claim_bytes.as_slice()) + .map_err(|e| format!("invalid claim account: {e:?}"))?; + + let inputs = CircuitInputs::from_secret(secret, claim); + let proof = qp_ownership_prover::build_fresh() + .commit(&inputs) + .map_err(|e| format!("ownership prover commit failed: {e}"))? + .prove() + .map_err(|e| format!("ownership proof failed: {e}"))?; + + Ok(WormholeClaimBody { + proof_kind: "wormhole_rate8".into(), + proof_hex: hex::encode(proof.to_bytes()), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use sha2::Digest; + use sp_core::crypto::{AccountId32, Ss58Codec}; + + fn to_ss58(account: &[u8; 32]) -> String { + AccountId32::new(*account) + .to_ss58check_with_version(sp_core::crypto::Ss58AddressFormat::custom(189)) + } + + fn hex32(s: &str) -> [u8; 32] { + hex::decode(s).unwrap().try_into().unwrap() + } + + #[test] + fn current_wormhole_scheme_matches_hdwallet_golden() { + let secret = hex32("30051cfa3abd462d3bc26da2d660e90ba8af6080b7fe95d9fd3f3b37c7d9ce4b"); + let scheme = WORMHOLE_SCHEMES + .iter() + .find(|s| s.id == CLAIMABLE_WORMHOLE_SCHEME) + .unwrap(); + assert_eq!( + scheme.derive(&secret), + hex32("6a2f0d3abe4390e0b05f6dea4ba10670676cda7c00d49526ddde59f16c85269f") + ); + } + + /// Vectors computed with the exact crates the shipped Resonance chains + /// pinned: poseidon-resonance 0.8.0 (rev fcb49a7, plonky2 fork rev + /// 80a1000, per chain tag v0.0.12-resonance-alpha) and crates.io + /// qp-poseidon 0.9.1 (per chain rev e9fc9b9). The 2592-byte inputs are + /// ML-DSA-87 public key sized. + #[test] + fn pre_v095_dilithium_matches_original_crate_vectors() { + let square_pattern: Vec = (0..2592u32).map(|i| (i * i % 251) as u8).collect(); + let vectors: &[(&str, &[u8], &str)] = &[ + ( + "dilithium-v08-padded", + &[0u8], + "fdf0715f178bfb2381d3804961bda8c679990d6318ff53f7a6475e1bef1982ca", + ), + ( + "dilithium-v08-padded", + &[42u8; 32], + "832c0ecb43187d773d7e54865c5148be7a2f9c9b86d8ad630196b0ff9a8037b5", + ), + ( + "dilithium-v08-padded", + &[5u8; 2592], + "9c69917b10f0228a0beed1d78ce34026b4778dc04a49a401dd5e692a51f44207", + ), + ( + "dilithium-v08-padded", + &square_pattern, + "3222344f6b35748d59d5983410b1baaba0bb680af7fecfdd7cd54de1a848977e", + ), + ( + "dilithium-v091-padded", + &[0u8], + "c4f1020767625056e669e3653f190b7763c6c398a45f1dc20db0d7ed32b14ff7", + ), + ( + "dilithium-v091-padded", + &[42u8; 32], + "19be0e79d925f42481cb5b30fb703903c866395373671728bb1594b01d850f6e", + ), + ( + "dilithium-v091-padded", + &[5u8; 2592], + "8ba4f919664c796aa811f552eaff5975570d56ed6812728944f60fe7d28d3c74", + ), + ( + "dilithium-v091-padded", + &square_pattern, + "61d1490430a295ad2d33ed55948ece058cce09b43994f35c7528f164a482a5e5", + ), + ]; + for (scheme, input, expected) in vectors { + assert_eq!( + derive_dilithium(scheme, input), + hex32(expected), + "{scheme} input len {}", + input.len() + ); + } + } + + #[test] + fn all_dilithium_schemes_disagree_on_same_public_key() { + let pk = [5u8; 2592]; + let addrs: Vec<_> = DILITHIUM_SCHEMES + .iter() + .map(|s| derive_dilithium(s, &pk)) + .collect(); + for i in 0..addrs.len() { + for j in (i + 1)..addrs.len() { + assert_ne!( + addrs[i], addrs[j], + "{} and {} collide", + DILITHIUM_SCHEMES[i], DILITHIUM_SCHEMES[j] + ); + } + } + } + + #[test] + fn matches_v08_dilithium_address_against_snapshot() { + let pk = vec![5u8; 2592]; + let resonance_era = derive_dilithium("dilithium-v08-padded", &pk); + let snapshot = vec![to_ss58(&resonance_era)]; + + let matches = find_airdrop_matches(snapshot.clone(), Some(pk), None, vec![]).unwrap(); + assert_eq!(matches.len(), 1); + assert_eq!(matches[0].scheme, "dilithium-v08-padded"); + assert!(matches[0].claimable); + } + + /// Vectors computed with qp-poseidon-core 0.9.5 (git tag v0.9.5); the + /// `[0]` digest was also independently reproduced in review. These pin the + /// legacy permutation constants, not just the sponge shape. + #[test] + fn v09_dilithium_matches_original_crate_vectors() { + assert_eq!( + derive_dilithium("dilithium-v09-padded", &[0u8]), + hex32("b17b423096da9ebd57af5038b490257d9c492e64059c0ccff23f44e6293213d4") + ); + assert_eq!( + derive_dilithium("dilithium-v09-padded", &[5u8; 2592]), + hex32("90d32b1ed817ec85479f70af5db84a07fb8b6c314d12d3470fc046cf873a54ac") + ); + } + + #[test] + fn v09_wormhole_matches_original_crate_vector() { + let scheme = WORMHOLE_SCHEMES + .iter() + .find(|s| s.id == "wormhole-v09-injective") + .unwrap(); + assert_eq!( + scheme.derive(&[42u8; 32]), + hex32("f4e231ede747e9ca2da9528add147ee488651a0df72b00313b0a8e6b76388fea") + ); + } + + #[test] + fn historical_wormhole_schemes_disagree() { + let secret = [9u8; 32]; + let addrs: Vec<_> = WORMHOLE_SCHEMES.iter().map(|s| s.derive(&secret)).collect(); + assert!(addrs.iter().any(|a| *a != addrs[0])); + } + + #[test] + fn dilithium_rate8_matches_current_hash_bytes() { + let pk = [7u8; 2592]; + assert_eq!( + derive_dilithium("dilithium-rate8-hash-bytes", &pk), + qp_poseidon_core::hash_bytes(&pk) + ); + } + + #[test] + fn matches_wormhole_secret_against_snapshot() { + let secret = [42u8; 32]; + let current = WORMHOLE_SCHEMES + .iter() + .find(|s| s.id == CLAIMABLE_WORMHOLE_SCHEME) + .unwrap() + .derive(&secret); + let snapshot = vec![to_ss58(¤t), to_ss58(&[1u8; 32])]; + + let matches = + find_airdrop_matches(snapshot.clone(), None, None, vec![secret.to_vec()]).unwrap(); + assert_eq!(matches.len(), 1); + assert_eq!(matches[0].address, snapshot[0]); + assert_eq!(matches[0].kind, "wormhole"); + assert_eq!(matches[0].scheme, CLAIMABLE_WORMHOLE_SCHEME); + assert!(matches[0].claimable); + assert_eq!(matches[0].wormhole_secret.as_deref(), Some(&secret[..])); + } + + #[test] + fn matches_dilithium_public_key_against_snapshot() { + let pk = vec![5u8; 2592]; + let dirac_era = derive_dilithium("dilithium-v10-padded", &pk); + let snapshot = vec![to_ss58(&dirac_era)]; + + let matches = find_airdrop_matches(snapshot.clone(), Some(pk), None, vec![]).unwrap(); + assert_eq!(matches.len(), 1); + assert_eq!(matches[0].address, snapshot[0]); + assert_eq!(matches[0].scheme, "dilithium-v10-padded"); + assert!(matches[0].claimable); + assert!(matches[0].wormhole_secret.is_none()); + } + + #[test] + fn rejects_non_quantus_addresses() { + // Prefix 42 (generic Substrate) must be rejected. + let generic = AccountId32::new([1u8; 32]) + .to_ss58check_with_version(sp_core::crypto::Ss58AddressFormat::custom(42)); + assert!(find_airdrop_matches(vec![generic], None, None, vec![]).is_err()); + } + + #[test] + fn dilithium_claim_signature_verifies_with_context() { + let mut seed = [7u8; 32]; + let keys = ml_dsa_87::Keypair::generate( + &mut qp_rusty_crystals_dilithium::SensitiveBytes32::new(&mut seed), + ); + let keypair = super::super::crypto::Keypair { + public_key: keys.public().to_bytes().to_vec(), + secret_key: keys.secret().to_bytes().to_vec(), + scheme: super::super::crypto::DilithiumScheme::MlDsa87, + }; + let address_bytes = derive_dilithium("dilithium-v10-padded", &keypair.public_key); + let claim_bytes = [9u8; 32]; + + let body = + build_airdrop_dilithium_claim(&keypair, to_ss58(&address_bytes), to_ss58(&claim_bytes)) + .unwrap(); + assert_eq!(body.scheme, "dilithium-v10-padded"); + + let mut msg = [0u8; 72]; + msg[..32].copy_from_slice(&address_bytes); + msg[32..64].copy_from_slice(&claim_bytes); + msg[64..].copy_from_slice(&body.expiry_unix.to_be_bytes()); + let sig = hex::decode(&body.signature_hex).unwrap(); + assert!(keys.public().verify(&msg, &sig, Some(CLAIM_CONTEXT))); + // Context is part of the signature: verification without it must fail. + assert!(!keys.public().verify(&msg, &sig, None)); + } + + #[test] + fn dilithium_claim_rejects_foreign_address() { + let mut seed = [8u8; 32]; + let keys = ml_dsa_87::Keypair::generate( + &mut qp_rusty_crystals_dilithium::SensitiveBytes32::new(&mut seed), + ); + let keypair = super::super::crypto::Keypair { + public_key: keys.public().to_bytes().to_vec(), + secret_key: keys.secret().to_bytes().to_vec(), + scheme: super::super::crypto::DilithiumScheme::MlDsa87, + }; + let err = build_airdrop_dilithium_claim(&keypair, to_ss58(&[3u8; 32]), to_ss58(&[9u8; 32])) + .unwrap_err(); + assert!(err.contains("does not derive")); + } + + #[test] + #[ignore = "builds the ownership circuit; run with --release"] + fn wormhole_proof_generates() { + let body = prove_airdrop_wormhole([42u8; 32].to_vec(), to_ss58(&[9u8; 32])).unwrap(); + assert_eq!(body.proof_kind, "wormhole_rate8"); + assert!(!body.proof_hex.is_empty()); + } + + const TEST_MNEMONIC: &str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; + + fn test_seed() -> SensitiveBytes64 { + let mut seed = SensitiveBytes64::zeroed(); + mnemonic_to_seed(TEST_MNEMONIC.into(), None, &mut seed).unwrap(); + seed + } + + /// sha256(pk) vectors computed with the exact shipped crates + /// (`.probe-keygen`): qp-rusty-crystals-dilithium 1.0.3 (pre-FIPS + /// expansion), 2.0.0 (FIPS expansion), qp-rusty-crystals-hdwallet 1.0.0 + /// ("Bitcoin seed" BIP32), and the current 4.1.1. These pin the local + /// keygen reimplementation and the BIP32 tree to the historical bytes. + #[test] + fn historical_dilithium_keygens_match_shipped_crate_vectors() { + let seed = test_seed(); + let ids = dilithium_keygen_ids(); + let vectors = [ + ( + "v1:seed", + "77993f1dafc02c9162925807f825f611bab071d121d6a42250bc4957c9149562", + ), + ( + "v1:bip32:m/44'/189189'/0'/0/0", + "57eafbd7c902c02686aff7f39c692beb9f3c057383dc6c954defc381e2c59f7d", + ), + ( + "fips:seed", + "1819feeaba63629813f1266de3d135de22ec505f1e014e669011cd9399bacb6f", + ), + ( + "fips:bip32:m/44'/189189'/0'/0/0", + "08fffe331b888d215c335e82712aa41ef680edd57e4634a89cca81b87e021e24", + ), + ( + "fips:bip32:m/44'/189189'/0'/0'/0'", + "7aeb9126559a7f750bf90941f632cf1f2835a57500cb1be74d9d3d15007d7e8d", + ), + ( + "fips:bip32:m/44'/189189'/0'", + "a49dac7c3537f61476626d491016abb2ca0e355be017c8cbbabc5a705d1cb5d7", + ), + ( + "fips:seed32", + "2af97815f11fb93d64d0e93fecff2b0e7af88882ed9fda813b5cd6f421799ab2", + ), + ( + "fips:hd:m/44'/189189'/0'/0'/0'", + "aa46cca1014fa42d40388298b33a7537d6a313b401f5be259cc59797cf4307e7", + ), + ]; + for (id, expected_pk_sha) in vectors { + assert!(ids.iter().any(|i| i == id), "{id} missing from scan list"); + let keypair = derive_historical_dilithium(&seed, id).unwrap(); + assert_eq!( + hex::encode(sha2::Sha256::digest(&keypair.public)), + expected_pk_sha, + "{id}" + ); + } + // Secret keys too, for the two locally reimplemented expansions: the + // packed sk must be byte-identical to the historical crates' output. + let v1 = derive_historical_dilithium(&seed, "v1:seed").unwrap(); + assert_eq!( + hex::encode(sha2::Sha256::digest(&v1.secret[..])), + "fbcb8f8db649111054baddc24f3eaab314c120950d76fdb8df5c1cd6a4102aa9" + ); + let fips = derive_historical_dilithium(&seed, "fips:seed").unwrap(); + assert_eq!( + hex::encode(sha2::Sha256::digest(&fips.secret[..])), + "fbe63db6ccf71badbb5a4aea59bead046637065fca38270b1a6361644ecf20d3" + ); + } + + /// An address minted by an era-1 wallet (pre-FIPS keygen, soft HD path, + /// v0.8 address hash) is found from the mnemonic alone, and the claim + /// built from its keygen id signs with the era's key. + #[test] + fn hd_scan_finds_historical_dilithium_address_and_claims() { + let keygen = "v1:bip32:m/44'/189189'/1'/0/0"; + let keypair = derive_historical_dilithium(&test_seed(), keygen).unwrap(); + let address_bytes = derive_dilithium("dilithium-v08-padded", &keypair.public); + let snapshot = vec![to_ss58(&address_bytes)]; + + let matches = + find_airdrop_matches(snapshot.clone(), None, Some(TEST_MNEMONIC.into()), vec![]) + .unwrap(); + assert_eq!(matches.len(), 1); + assert_eq!(matches[0].kind, "dilithium"); + assert_eq!(matches[0].scheme, "dilithium-v08-padded"); + assert_eq!(matches[0].dilithium_keygen.as_deref(), Some(keygen)); + assert!(matches[0].claimable); + + let claim_bytes = [9u8; 32]; + let body = build_airdrop_dilithium_claim_from_mnemonic( + TEST_MNEMONIC.into(), + keygen.into(), + matches[0].address.clone(), + to_ss58(&claim_bytes), + ) + .unwrap(); + assert_eq!(body.scheme, "dilithium-v08-padded"); + assert_eq!(body.public_key_hex, hex::encode(&keypair.public)); + + // The signature must verify under the current crate — that is what + // the claim server runs. + let mut msg = [0u8; 72]; + msg[..32].copy_from_slice(&address_bytes); + msg[32..64].copy_from_slice(&claim_bytes); + msg[64..].copy_from_slice(&body.expiry_unix.to_be_bytes()); + let public = ml_dsa_87::PublicKey::from_bytes(&keypair.public).unwrap(); + let sig = hex::decode(&body.signature_hex).unwrap(); + assert!(public.verify(&msg, &sig, Some(CLAIM_CONTEXT))); + } + + /// The current wallet's own key must not be reported twice when both the + /// provided public key and the era-4 HD keygen derive it. + #[test] + fn current_key_is_not_double_reported() { + let keygen = "fips:hd:m/44'/189189'/0'/0'/0'"; + let keypair = derive_historical_dilithium(&test_seed(), keygen).unwrap(); + let address = derive_dilithium("dilithium-rate8-hash-bytes", &keypair.public); + let snapshot = vec![to_ss58(&address)]; + + let matches = find_airdrop_matches( + snapshot, + Some(keypair.public.clone()), + Some(TEST_MNEMONIC.into()), + vec![], + ) + .unwrap(); + assert_eq!(matches.len(), 1); + // The pubkey-based match wins: its claim path needs no mnemonic. + assert_eq!(matches[0].source, "dilithium public key"); + assert!(matches[0].dilithium_keygen.is_none()); + } + + /// Wormhole entropies derived before hdwallet 2.1.0 used the "Bitcoin + /// seed" BIP32 master. Secrets pinned by the `.probe-keygen` run of + /// hdwallet 1.0.0: the master-node secret (`generate_wormhole_pair`) and + /// a path-derived one (`generate_wormhole_pair_from_path`). + #[test] + fn hd_scan_finds_bitcoin_seed_wormhole_addresses() { + let master_secret = + hex32("1837c1be8e2995ec11cda2b066151be2cfb48adf9e47b151d46adab3a21cdf67"); + let path_secret = hex32("87b3000325d7058a64b01b93f199b3d54ba5d9b2e036cee672199e7da326538c"); + let rate4 = WORMHOLE_SCHEMES + .iter() + .find(|s| s.id == "wormhole-rate4-compact") + .unwrap(); + let snapshot = vec![ + to_ss58(&rate4.derive(&master_secret)), + to_ss58(&rate4.derive(&path_secret)), + ]; + + let matches = + find_airdrop_matches(snapshot.clone(), None, Some(TEST_MNEMONIC.into()), vec![]) + .unwrap(); + assert_eq!(matches.len(), 2); + let sources: Vec<_> = matches.iter().map(|m| m.source.as_str()).collect(); + assert!(sources.contains(&"bitcoin-seed m")); + assert!(sources.contains( + &format!("bitcoin-seed m/44'/{QUANTUS_WORMHOLE_CHAIN_ID}/0'/0'/0'").as_str() + )); + for m in &matches { + assert_eq!(m.kind, "wormhole"); + assert!(m.wormhole_secret.is_some()); + } + } + + /// The shipped wallets never capped the HD account or address index, so + /// the scan must reach past the old bounds (accounts 0..9, indexes 0..17) + /// and cover the whole configured range. + #[test] + fn hd_scan_reaches_high_account_and_address_indexes() { + let seed = test_seed(); + let keygens: Vec = [9, DILITHIUM_SCAN_ACCOUNTS - 1] + .iter() + .map(|account| format!("fips:hd:m/44'/{DILITHIUM_CHAIN_ID}/{account}'/0'/0'")) + .collect(); + let paths: Vec = [17, HD_SCAN_INDEXES - 1] + .iter() + .map(|index| format!("m/44'/{QUANTUS_WORMHOLE_CHAIN_ID}/0'/1'/{index}'")) + .collect(); + let wormhole = WORMHOLE_SCHEMES + .iter() + .find(|s| s.id == CLAIMABLE_WORMHOLE_SCHEME) + .unwrap(); + let mut snapshot = Vec::new(); + for keygen in &keygens { + let keypair = derive_historical_dilithium(&seed, keygen).unwrap(); + snapshot.push(to_ss58(&derive_dilithium( + "dilithium-rate8-hash-bytes", + &keypair.public, + ))); + } + for path in &paths { + let pair = generate_wormhole_from_seed(&seed, path).unwrap(); + snapshot.push(to_ss58(&wormhole.derive(pair.secret().as_bytes()))); + } + + let matches = + find_airdrop_matches(snapshot, None, Some(TEST_MNEMONIC.into()), vec![]).unwrap(); + let mut sources: Vec = matches.iter().map(|m| m.source.clone()).collect(); + sources.sort(); + let mut expected: Vec = keygens + .iter() + .map(|k| format!("mnemonic keygen {k}")) + .chain(paths.iter().cloned()) + .collect(); + expected.sort(); + assert_eq!(sources, expected); + } + + #[test] + fn hd_scan_finds_wallet_wormhole_address() { + let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; + // App external branch, plus a CLI multiround round-2 address: the + // middle component is a round counter there, not a change branch. + let paths = [ + format!("m/44'/{}/0'/0'/3'", QUANTUS_WORMHOLE_CHAIN_ID), + format!("m/44'/{}/0'/2'/1'", QUANTUS_WORMHOLE_CHAIN_ID), + ]; + let scheme = WORMHOLE_SCHEMES + .iter() + .find(|s| s.id == CLAIMABLE_WORMHOLE_SCHEME) + .unwrap(); + let snapshot: Vec = paths + .iter() + .map(|path| { + let pair = + qp_rusty_crystals_hdwallet::derive_wormhole_from_mnemonic(mnemonic, None, path) + .unwrap(); + to_ss58(&scheme.derive(pair.secret().as_bytes())) + }) + .collect(); + + let matches = + find_airdrop_matches(snapshot.clone(), None, Some(mnemonic.into()), vec![]).unwrap(); + assert_eq!(matches.len(), 2); + let sources: Vec<_> = matches.iter().map(|m| m.source.as_str()).collect(); + assert!(sources.contains(&paths[0].as_str())); + assert!(sources.contains(&paths[1].as_str())); + assert!(matches.iter().all(|m| m.claimable)); + } +} + +/// Regression test (security review): wormhole address matching must never +/// free heap memory that still contains the spend secret. +/// +/// Mirroring `heap_zeroization.rs` in qp-zk-circuits, a global allocator +/// scans every freed block for the secret at `dealloc` time (the block is +/// still valid inside the hook). Two byte images are searched, because the +/// secret appears in two encodings: the raw 32 bytes (also the in-memory +/// image of the compact8 felt encoding — every 8-byte limb of the ASCII +/// pattern is canonical), and the injective4 felt image (4 secret bytes then +/// 4 zero bytes per limb). No exemptions: the v09 scheme hashes through the +/// local borrowed-slice sponge precisely so that no allocation holding the +/// secret is ever freed, by anyone. +/// +/// The scanner only reacts to blocks containing the distinctive pattern, so +/// unrelated tests running in the same binary cannot trip it. +#[cfg(test)] +mod heap_zeroization_tests { + use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + use std::{ + alloc::{GlobalAlloc, Layout, System}, + sync::OnceLock, + }; + + use super::{find_airdrop_matches, injective4_secret_words, WORMHOLE_SCHEMES}; + + /// Distinctive all-ASCII 32-byte pattern; see module docs for why ASCII + /// makes the compact8 felt image identical to the raw bytes. + const SECRET_PATTERN: [u8; 32] = *b"quantus-sdk-airdrop-zeroize-pat!"; + + static SCANNING: AtomicBool = AtomicBool::new(false); + static LEAKED_BLOCK_SIZE: AtomicUsize = AtomicUsize::new(0); + /// Injective4 felt image of the pattern (precomputed: the dealloc hook + /// should not allocate). + static INJECTIVE4_IMAGE: OnceLock> = OnceLock::new(); + + fn contains(haystack: &[u8], needle: &[u8]) -> bool { + haystack.windows(needle.len()).any(|w| w == needle) + } + + struct SecretScanningAllocator; + + unsafe impl GlobalAlloc for SecretScanningAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + unsafe { System.alloc(layout) } + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + if SCANNING.load(Ordering::SeqCst) && layout.size() >= SECRET_PATTERN.len() { + let block = unsafe { core::slice::from_raw_parts(ptr, layout.size()) }; + let hit = contains(block, &SECRET_PATTERN) + || INJECTIVE4_IMAGE + .get() + .is_some_and(|img| contains(block, img)); + if hit { + LEAKED_BLOCK_SIZE.store(layout.size(), Ordering::SeqCst); + } + } + unsafe { System.dealloc(ptr, layout) } + } + } + + #[global_allocator] + static ALLOCATOR: SecretScanningAllocator = SecretScanningAllocator; + + fn injective4_image() -> Vec { + injective4_secret_words(&SECRET_PATTERN) + .take(8) // the terminator limb is not secret material + .flat_map(u64::to_le_bytes) + .collect() + } + + fn assert_no_secret_freed(what: &str, run: impl FnOnce()) { + LEAKED_BLOCK_SIZE.store(0, Ordering::SeqCst); + SCANNING.store(true, Ordering::SeqCst); + run(); + SCANNING.store(false, Ordering::SeqCst); + let leaked = LEAKED_BLOCK_SIZE.load(Ordering::SeqCst); + assert_eq!( + leaked, 0, + "{what}: a heap block of {leaked} bytes still containing the spend secret was \ + freed unscrubbed; see SensitiveVec pre-sizing (api/airdrop.rs) and Drop (sensitive.rs)" + ); + } + + #[test] + fn matching_never_frees_heap_memory_containing_the_secret() { + INJECTIVE4_IMAGE.set(injective4_image()).expect("set once"); + + assert_no_secret_freed("scheme derivation", || { + for scheme in WORMHOLE_SCHEMES { + core::hint::black_box(scheme.derive(&SECRET_PATTERN)); + } + }); + + // The public entry point: the scan buffer must not leak through Vec + // growth (nine secrets force several reallocations), an early error + // return (a bad length after a good secret, with an unvisited secret + // left in the input), or the final drop. + let nine = vec![SECRET_PATTERN.to_vec(); 9]; + assert_no_secret_freed("find_airdrop_matches", || { + assert!(find_airdrop_matches(vec![], None, None, nine).is_ok()); + }); + let bad_length = vec![ + SECRET_PATTERN.to_vec(), + vec![0u8; 31], + SECRET_PATTERN.to_vec(), + ]; + assert_no_secret_freed("find_airdrop_matches error path", || { + assert!(find_airdrop_matches(vec![], None, None, bad_length).is_err()); + }); + } +} diff --git a/quantus_sdk/rust/src/api/mod.rs b/quantus_sdk/rust/src/api/mod.rs index a03eae7f8..47a4a587e 100644 --- a/quantus_sdk/rust/src/api/mod.rs +++ b/quantus_sdk/rust/src/api/mod.rs @@ -1,3 +1,4 @@ +pub mod airdrop; pub mod crypto; pub mod multisig; pub mod ur; diff --git a/quantus_sdk/rust/src/frb_generated.rs b/quantus_sdk/rust/src/frb_generated.rs index b09af1211..10d93ed92 100644 --- a/quantus_sdk/rust/src/frb_generated.rs +++ b/quantus_sdk/rust/src/frb_generated.rs @@ -39,7 +39,7 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_auto_opaque = RustAutoOpaqueMoi, ); pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0"; -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1684949517; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1962506674; // Section: executor @@ -82,6 +82,87 @@ fn wire__crate__api__wormhole__aggregate_proofs_impl( }, ) } +fn wire__crate__api__airdrop__build_airdrop_dilithium_claim_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "build_airdrop_dilithium_claim", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_keypair = ::sse_decode(&mut deserializer); + let api_address = ::sse_decode(&mut deserializer); + let api_claim_account = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, String>((move || { + let output_ok = crate::api::airdrop::build_airdrop_dilithium_claim( + &api_keypair, + api_address, + api_claim_account, + )?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__api__airdrop__build_airdrop_dilithium_claim_from_mnemonic_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "build_airdrop_dilithium_claim_from_mnemonic", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_mnemonic = ::sse_decode(&mut deserializer); + let api_dilithium_keygen = ::sse_decode(&mut deserializer); + let api_address = ::sse_decode(&mut deserializer); + let api_claim_account = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, String>((move || { + let output_ok = + crate::api::airdrop::build_airdrop_dilithium_claim_from_mnemonic( + api_mnemonic, + api_dilithium_keygen, + api_address, + api_claim_account, + )?; + Ok(output_ok) + })()) + } + }, + ) +} fn wire__crate__api__wormhole__compute_address_hash_hex_impl( ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len_: i32, @@ -512,6 +593,47 @@ fn wire__crate__api__wormhole__ensure_circuit_binaries_impl( }, ) } +fn wire__crate__api__airdrop__find_airdrop_matches_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "find_airdrop_matches", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_snapshot_addresses = >::sse_decode(&mut deserializer); + let api_dilithium_public_key = >>::sse_decode(&mut deserializer); + let api_mnemonic = >::sse_decode(&mut deserializer); + let api_extra_wormhole_secrets = >>::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, String>((move || { + let output_ok = crate::api::airdrop::find_airdrop_matches( + api_snapshot_addresses, + api_dilithium_public_key, + api_mnemonic, + api_extra_wormhole_secrets, + )?; + Ok(output_ok) + })()) + } + }, + ) +} fn wire__crate__api__crypto__first_hash_to_address_impl( ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len_: i32, @@ -836,6 +958,43 @@ fn wire__crate__api__multisig__predict_multisig_address_impl( }, ) } +fn wire__crate__api__airdrop__prove_airdrop_wormhole_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "prove_airdrop_wormhole", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_wormhole_secret = >::sse_decode(&mut deserializer); + let api_claim_account = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, String>((move || { + let output_ok = crate::api::airdrop::prove_airdrop_wormhole( + api_wormhole_secret, + api_claim_account, + )?; + Ok(output_ok) + })()) + } + }, + ) +} fn wire__crate__api__crypto__public_key_bytes_impl( ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len_: i32, @@ -1235,6 +1394,28 @@ impl SseDecode for String { } } +impl SseDecode for crate::api::airdrop::AirdropMatch { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_address = ::sse_decode(deserializer); + let mut var_kind = ::sse_decode(deserializer); + let mut var_scheme = ::sse_decode(deserializer); + let mut var_claimable = ::sse_decode(deserializer); + let mut var_source = ::sse_decode(deserializer); + let mut var_wormholeSecret = >>::sse_decode(deserializer); + let mut var_dilithiumKeygen = >::sse_decode(deserializer); + return crate::api::airdrop::AirdropMatch { + address: var_address, + kind: var_kind, + scheme: var_scheme, + claimable: var_claimable, + source: var_source, + wormhole_secret: var_wormholeSecret, + dilithium_keygen: var_dilithiumKeygen, + }; + } +} + impl SseDecode for bool { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1242,6 +1423,26 @@ impl SseDecode for bool { } } +impl SseDecode for crate::api::airdrop::DilithiumClaimBody { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_scheme = ::sse_decode(deserializer); + let mut var_address = ::sse_decode(deserializer); + let mut var_claimAccount = ::sse_decode(deserializer); + let mut var_publicKeyHex = ::sse_decode(deserializer); + let mut var_signatureHex = ::sse_decode(deserializer); + let mut var_expiryUnix = ::sse_decode(deserializer); + return crate::api::airdrop::DilithiumClaimBody { + scheme: var_scheme, + address: var_address, + claim_account: var_claimAccount, + public_key_hex: var_publicKeyHex, + signature_hex: var_signatureHex, + expiry_unix: var_expiryUnix, + }; + } +} + impl SseDecode for crate::api::crypto::DilithiumScheme { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1261,6 +1462,13 @@ impl SseDecode for i32 { } } +impl SseDecode for i64 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_i64::().unwrap() + } +} + impl SseDecode for crate::api::crypto::Keypair { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1287,6 +1495,20 @@ impl SseDecode for Vec { } } +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = Vec::with_capacity(len_ as usize); + for idx_ in 0..len_ { + ans_.push(::sse_decode( + deserializer, + )); + } + return ans_; + } +} + impl SseDecode for Vec> { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1323,6 +1545,17 @@ impl SseDecode for crate::api::wormhole::MerkleProcessed { } } +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode(deserializer)); + } else { + return None; + } + } +} + impl SseDecode for Option { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1334,6 +1567,17 @@ impl SseDecode for Option { } } +impl SseDecode for Option> { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(>::sse_decode(deserializer)); + } else { + return None; + } + } +} + impl SseDecode for Option<[u8; 32]> { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1451,6 +1695,18 @@ impl SseDecode for usize { } } +impl SseDecode for crate::api::airdrop::WormholeClaimBody { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_proofKind = ::sse_decode(deserializer); + let mut var_proofHex = ::sse_decode(deserializer); + return crate::api::airdrop::WormholeClaimBody { + proof_kind: var_proofKind, + proof_hex: var_proofHex, + }; + } +} + impl SseDecode for crate::api::crypto::WormholeResult { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1475,14 +1731,35 @@ fn pde_ffi_dispatcher_primary_impl( // Codec=Pde (Serialization + dispatch), see doc to use other codecs match func_id { 1 => wire__crate__api__wormhole__aggregate_proofs_impl(port, ptr, rust_vec_len, data_len), - 15 => wire__crate__api__wormhole__ensure_circuit_binaries_impl( + 2 => wire__crate__api__airdrop__build_airdrop_dilithium_claim_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 3 => wire__crate__api__airdrop__build_airdrop_dilithium_claim_from_mnemonic_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 17 => wire__crate__api__wormhole__ensure_circuit_binaries_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 18 => { + wire__crate__api__airdrop__find_airdrop_matches_impl(port, ptr, rust_vec_len, data_len) + } + 23 => wire__crate__api__wormhole__generate_proof_impl(port, ptr, rust_vec_len, data_len), + 24 => wire__crate__api__crypto__init_app_impl(port, ptr, rust_vec_len, data_len), + 29 => wire__crate__api__airdrop__prove_airdrop_wormhole_impl( port, ptr, rust_vec_len, data_len, ), - 20 => wire__crate__api__wormhole__generate_proof_impl(port, ptr, rust_vec_len, data_len), - 21 => wire__crate__api__crypto__init_app_impl(port, ptr, rust_vec_len, data_len), _ => unreachable!(), } } @@ -1495,48 +1772,48 @@ fn pde_ffi_dispatcher_sync_impl( ) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { // Codec=Pde (Serialization + dispatch), see doc to use other codecs match func_id { - 2 => wire__crate__api__wormhole__compute_address_hash_hex_impl(ptr, rust_vec_len, data_len), - 3 => wire__crate__api__wormhole__compute_merkle_positions_impl(ptr, rust_vec_len, data_len), - 4 => wire__crate__api__wormhole__compute_nullifier_impl(ptr, rust_vec_len, data_len), - 5 => wire__crate__api__wormhole__compute_wormhole_address_impl(ptr, rust_vec_len, data_len), - 6 => wire__crate__api__crypto__crystal_alice_impl(ptr, rust_vec_len, data_len), - 7 => wire__crate__api__crypto__crystal_bob_impl(ptr, rust_vec_len, data_len), - 8 => wire__crate__api__crypto__crystal_charlie_impl(ptr, rust_vec_len, data_len), - 9 => wire__crate__api__wormhole__decode_leaf_amount_impl(ptr, rust_vec_len, data_len), - 10 => wire__crate__api__wormhole__decode_leaf_to_account_impl(ptr, rust_vec_len, data_len), - 11 => { + 4 => wire__crate__api__wormhole__compute_address_hash_hex_impl(ptr, rust_vec_len, data_len), + 5 => wire__crate__api__wormhole__compute_merkle_positions_impl(ptr, rust_vec_len, data_len), + 6 => wire__crate__api__wormhole__compute_nullifier_impl(ptr, rust_vec_len, data_len), + 7 => wire__crate__api__wormhole__compute_wormhole_address_impl(ptr, rust_vec_len, data_len), + 8 => wire__crate__api__crypto__crystal_alice_impl(ptr, rust_vec_len, data_len), + 9 => wire__crate__api__crypto__crystal_bob_impl(ptr, rust_vec_len, data_len), + 10 => wire__crate__api__crypto__crystal_charlie_impl(ptr, rust_vec_len, data_len), + 11 => wire__crate__api__wormhole__decode_leaf_amount_impl(ptr, rust_vec_len, data_len), + 12 => wire__crate__api__wormhole__decode_leaf_to_account_impl(ptr, rust_vec_len, data_len), + 13 => { wire__crate__api__wormhole__decode_leaf_transfer_count_impl(ptr, rust_vec_len, data_len) } - 12 => wire__crate__api__ur__decode_ur_impl(ptr, rust_vec_len, data_len), - 13 => wire__crate__api__crypto__derive_wormhole_impl(ptr, rust_vec_len, data_len), - 14 => wire__crate__api__ur__encode_ur_impl(ptr, rust_vec_len, data_len), - 16 => wire__crate__api__crypto__first_hash_to_address_impl(ptr, rust_vec_len, data_len), - 17 => wire__crate__api__crypto__generate_derived_keypair_impl(ptr, rust_vec_len, data_len), - 18 => wire__crate__api__crypto__generate_keypair_impl(ptr, rust_vec_len, data_len), - 19 => { + 14 => wire__crate__api__ur__decode_ur_impl(ptr, rust_vec_len, data_len), + 15 => wire__crate__api__crypto__derive_wormhole_impl(ptr, rust_vec_len, data_len), + 16 => wire__crate__api__ur__encode_ur_impl(ptr, rust_vec_len, data_len), + 19 => wire__crate__api__crypto__first_hash_to_address_impl(ptr, rust_vec_len, data_len), + 20 => wire__crate__api__crypto__generate_derived_keypair_impl(ptr, rust_vec_len, data_len), + 21 => wire__crate__api__crypto__generate_keypair_impl(ptr, rust_vec_len, data_len), + 22 => { wire__crate__api__crypto__generate_keypair_from_seed_impl(ptr, rust_vec_len, data_len) } - 22 => wire__crate__api__ur__is_complete_ur_impl(ptr, rust_vec_len, data_len), - 23 => wire__crate__api__ur__max_ur_part_chars_impl(ptr, rust_vec_len, data_len), - 24 => wire__crate__api__ur__max_ur_parts_impl(ptr, rust_vec_len, data_len), - 25 => { + 25 => wire__crate__api__ur__is_complete_ur_impl(ptr, rust_vec_len, data_len), + 26 => wire__crate__api__ur__max_ur_part_chars_impl(ptr, rust_vec_len, data_len), + 27 => wire__crate__api__ur__max_ur_parts_impl(ptr, rust_vec_len, data_len), + 28 => { wire__crate__api__multisig__predict_multisig_address_impl(ptr, rust_vec_len, data_len) } - 26 => wire__crate__api__crypto__public_key_bytes_impl(ptr, rust_vec_len, data_len), - 27 => wire__crate__api__crypto__secret_key_bytes_impl(ptr, rust_vec_len, data_len), - 28 => wire__crate__api__crypto__set_default_ss58_prefix_impl(ptr, rust_vec_len, data_len), - 29 => wire__crate__api__crypto__sign_message_impl(ptr, rust_vec_len, data_len), - 30 => wire__crate__api__crypto__sign_message_with_pubkey_impl(ptr, rust_vec_len, data_len), - 31 => wire__crate__api__crypto__signature_bytes_impl(ptr, rust_vec_len, data_len), - 32 => wire__crate__api__crypto__ss58_to_account_id_impl(ptr, rust_vec_len, data_len), - 33 => wire__crate__api__crypto__to_account_id_impl(ptr, rust_vec_len, data_len), - 34 => wire__crate__api__crypto__verify_message_impl(ptr, rust_vec_len, data_len), - 35 => wire__crate__api__wormhole__wormhole_compute_output_amount_impl( + 30 => wire__crate__api__crypto__public_key_bytes_impl(ptr, rust_vec_len, data_len), + 31 => wire__crate__api__crypto__secret_key_bytes_impl(ptr, rust_vec_len, data_len), + 32 => wire__crate__api__crypto__set_default_ss58_prefix_impl(ptr, rust_vec_len, data_len), + 33 => wire__crate__api__crypto__sign_message_impl(ptr, rust_vec_len, data_len), + 34 => wire__crate__api__crypto__sign_message_with_pubkey_impl(ptr, rust_vec_len, data_len), + 35 => wire__crate__api__crypto__signature_bytes_impl(ptr, rust_vec_len, data_len), + 36 => wire__crate__api__crypto__ss58_to_account_id_impl(ptr, rust_vec_len, data_len), + 37 => wire__crate__api__crypto__to_account_id_impl(ptr, rust_vec_len, data_len), + 38 => wire__crate__api__crypto__verify_message_impl(ptr, rust_vec_len, data_len), + 39 => wire__crate__api__wormhole__wormhole_compute_output_amount_impl( ptr, rust_vec_len, data_len, ), - 36 => wire__crate__api__wormhole__zk_circuits_version_impl(ptr, rust_vec_len, data_len), + 40 => wire__crate__api__wormhole__zk_circuits_version_impl(ptr, rust_vec_len, data_len), _ => unreachable!(), } } @@ -1558,6 +1835,57 @@ impl flutter_rust_bridge::IntoIntoDart> for HDLattice } } +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::airdrop::AirdropMatch { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.address.into_into_dart().into_dart(), + self.kind.into_into_dart().into_dart(), + self.scheme.into_into_dart().into_dart(), + self.claimable.into_into_dart().into_dart(), + self.source.into_into_dart().into_dart(), + self.wormhole_secret.into_into_dart().into_dart(), + self.dilithium_keygen.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::airdrop::AirdropMatch +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::airdrop::AirdropMatch +{ + fn into_into_dart(self) -> crate::api::airdrop::AirdropMatch { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::airdrop::DilithiumClaimBody { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.scheme.into_into_dart().into_dart(), + self.address.into_into_dart().into_dart(), + self.claim_account.into_into_dart().into_dart(), + self.public_key_hex.into_into_dart().into_dart(), + self.signature_hex.into_into_dart().into_dart(), + self.expiry_unix.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::airdrop::DilithiumClaimBody +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::airdrop::DilithiumClaimBody +{ + fn into_into_dart(self) -> crate::api::airdrop::DilithiumClaimBody { + self + } +} // Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for crate::api::crypto::DilithiumScheme { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { @@ -1679,6 +2007,27 @@ impl flutter_rust_bridge::IntoIntoDart } } // Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::airdrop::WormholeClaimBody { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.proof_kind.into_into_dart().into_dart(), + self.proof_hex.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::airdrop::WormholeClaimBody +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::airdrop::WormholeClaimBody +{ + fn into_into_dart(self) -> crate::api::airdrop::WormholeClaimBody { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for crate::api::crypto::WormholeResult { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { [ @@ -1726,6 +2075,19 @@ impl SseEncode for String { } } +impl SseEncode for crate::api::airdrop::AirdropMatch { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.address, serializer); + ::sse_encode(self.kind, serializer); + ::sse_encode(self.scheme, serializer); + ::sse_encode(self.claimable, serializer); + ::sse_encode(self.source, serializer); + >>::sse_encode(self.wormhole_secret, serializer); + >::sse_encode(self.dilithium_keygen, serializer); + } +} + impl SseEncode for bool { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -1733,6 +2095,18 @@ impl SseEncode for bool { } } +impl SseEncode for crate::api::airdrop::DilithiumClaimBody { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.scheme, serializer); + ::sse_encode(self.address, serializer); + ::sse_encode(self.claim_account, serializer); + ::sse_encode(self.public_key_hex, serializer); + ::sse_encode(self.signature_hex, serializer); + ::sse_encode(self.expiry_unix, serializer); + } +} + impl SseEncode for crate::api::crypto::DilithiumScheme { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -1756,6 +2130,13 @@ impl SseEncode for i32 { } } +impl SseEncode for i64 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_i64::(self).unwrap(); + } +} + impl SseEncode for crate::api::crypto::Keypair { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -1775,6 +2156,16 @@ impl SseEncode for Vec { } } +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + impl SseEncode for Vec> { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -1803,6 +2194,16 @@ impl SseEncode for crate::api::wormhole::MerkleProcessed { } } +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + impl SseEncode for Option { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -1813,6 +2214,16 @@ impl SseEncode for Option { } } +impl SseEncode for Option> { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + >::sse_encode(value, serializer); + } + } +} + impl SseEncode for Option<[u8; 32]> { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -1912,6 +2323,14 @@ impl SseEncode for usize { } } +impl SseEncode for crate::api::airdrop::WormholeClaimBody { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.proof_kind, serializer); + ::sse_encode(self.proof_hex, serializer); + } +} + impl SseEncode for crate::api::crypto::WormholeResult { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { diff --git a/quantus_sdk/rust/src/lib.rs b/quantus_sdk/rust/src/lib.rs index fd84100b5..48631bed0 100644 --- a/quantus_sdk/rust/src/lib.rs +++ b/quantus_sdk/rust/src/lib.rs @@ -1,3 +1,4 @@ pub mod api; mod frb_generated; +mod sensitive; pub mod signing_context; diff --git a/quantus_sdk/rust/src/sensitive.rs b/quantus_sdk/rust/src/sensitive.rs new file mode 100644 index 000000000..a197e4f41 --- /dev/null +++ b/quantus_sdk/rust/src/sensitive.rs @@ -0,0 +1,85 @@ +//! Self-wiping buffers for secret material. Every wipe ends in `black_box`, +//! so the compiler must assume the zeros are observed and cannot elide the +//! stores (same construction as qp-poseidon-core's internal state wipe). + +/// Element types that can hold secret material. +pub(crate) trait Wipe { + fn wipe(&mut self); +} + +impl Wipe for u8 { + fn wipe(&mut self) { + *self = 0; + } +} + +impl Wipe for qp_poseidon_core::Goldilocks { + fn wipe(&mut self) { + *self = qp_poseidon_core::Goldilocks::ZERO; + } +} + +impl Wipe for p3_goldilocks::Goldilocks { + fn wipe(&mut self) { + *self = ::ZERO; + } +} + +impl Wipe for Vec { + fn wipe(&mut self) { + wipe(self.as_mut_slice()); + } +} + +impl Wipe for [T; N] { + fn wipe(&mut self) { + wipe(self.as_mut_slice()); + } +} + +/// A secret paired with its public source label. +impl Wipe for (T, String) { + fn wipe(&mut self) { + self.0.wipe(); + } +} + +/// Zero every element, resistant to dead-store elimination. +pub(crate) fn wipe(items: &mut [T]) { + for item in items.iter_mut() { + item.wipe(); + } + core::hint::black_box(items); +} + +/// Heap buffer of secret-bearing elements, wiped on drop (so success, error, +/// and panic paths all scrub). Wrap an existing `Vec` or reserve the full +/// capacity before pushing: a growing `Vec` frees its old block unscrubbed. +pub(crate) struct SensitiveVec(pub(crate) Vec); + +impl SensitiveVec { + pub(crate) fn with_capacity(capacity: usize) -> Self { + Self(Vec::with_capacity(capacity)) + } + + pub(crate) fn push(&mut self, item: T) { + debug_assert!( + self.0.len() < self.0.capacity(), + "SensitiveVec must be pre-sized" + ); + self.0.push(item); + } +} + +impl std::ops::Deref for SensitiveVec { + type Target = [T]; + fn deref(&self) -> &[T] { + &self.0 + } +} + +impl Drop for SensitiveVec { + fn drop(&mut self) { + wipe(&mut self.0); + } +} diff --git a/quantus_sdk/test/public_exports_test.dart b/quantus_sdk/test/public_exports_test.dart new file mode 100644 index 000000000..bd368762f --- /dev/null +++ b/quantus_sdk/test/public_exports_test.dart @@ -0,0 +1,13 @@ +// Compile-time check that the airdrop API is reachable through the public +// library, without needing the native library loaded. +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + test('airdrop API is exported from quantus_sdk.dart', () { + expect(findAirdropMatches, isA()); + expect(buildAirdropDilithiumClaim, isA()); + expect(buildAirdropDilithiumClaimFromMnemonic, isA()); + expect(proveAirdropWormhole, isA()); + }); +}