diff --git a/cold-wallet-app/lib/screens/scan_transaction_screen.dart b/cold-wallet-app/lib/screens/scan_transaction_screen.dart index 685386f99..aa5f29e69 100644 --- a/cold-wallet-app/lib/screens/scan_transaction_screen.dart +++ b/cold-wallet-app/lib/screens/scan_transaction_screen.dart @@ -18,7 +18,12 @@ class ScanTransactionScreen extends StatefulWidget { } class _ScanTransactionScreenState extends State { - final MobileScannerController _controller = MobileScannerController(); + // Unrestricted: deliver every decoded camera frame; dedup happens via _parts. + // QR-only: skipping the other symbologies shortens per-frame detection. + final MobileScannerController _controller = MobileScannerController( + detectionSpeed: DetectionSpeed.unrestricted, + formats: const [BarcodeFormat.qrCode], + ); final Set _parts = {}; final Set _seenSeq = {}; final RegExp _seqPattern = RegExp(r'/(\d+)-(\d+)/'); diff --git a/cold-wallet-app/lib/screens/sign_transaction_screen.dart b/cold-wallet-app/lib/screens/sign_transaction_screen.dart index e44efee30..c0abdf1c4 100644 --- a/cold-wallet-app/lib/screens/sign_transaction_screen.dart +++ b/cold-wallet-app/lib/screens/sign_transaction_screen.dart @@ -31,6 +31,11 @@ class SignTransactionScreen extends ConsumerStatefulWidget { } class _SignTransactionScreenState extends ConsumerState { + /// Tuning knobs for the animated signature QR (7 KB payload): bytes per frame + /// and how long each frame is shown. Larger fragments mean fewer, denser QRs. + static const _kUrFragmentLength = 1500; + static const _kUrFrameInterval = Duration(milliseconds: 100); + final ScrollController _scrollController = ScrollController(); ParsedPayload? _parsed; @@ -101,7 +106,7 @@ class _SignTransactionScreenState extends ConsumerState { keypair: keypair, message: QuantusSigningPayload.signablePayload(widget.payload), ); - final parts = encodeUr(data: signed); + final parts = encodeUr(data: signed, maxFragmentLength: _kUrFragmentLength); setState(() { _signing = false; _signatureUr = parts; @@ -400,7 +405,14 @@ class _SignTransactionScreenState extends ConsumerState { textAlign: TextAlign.center, ), const SizedBox(height: 24), - Center(child: AnimatedUrQr(parts: parts)), + Center( + child: AnimatedUrQr( + parts: parts, + interval: _kUrFrameInterval, + // Dense frames need as many screen pixels per QR module as possible. + size: (MediaQuery.sizeOf(context).width - 48).clamp(280.0, 500.0), + ), + ), const SizedBox(height: 16), if (parts.length > 1) Text( diff --git a/mobile-app/assets/v2/keystone_dots_three.svg b/mobile-app/assets/v2/keystone_dots_three.svg new file mode 100644 index 000000000..40929c063 --- /dev/null +++ b/mobile-app/assets/v2/keystone_dots_three.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/mobile-app/assets/v2/keystone_link.svg b/mobile-app/assets/v2/keystone_link.svg new file mode 100644 index 000000000..079d147c9 --- /dev/null +++ b/mobile-app/assets/v2/keystone_link.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/mobile-app/lib/v2/components/animated_qr_scanner.dart b/mobile-app/lib/v2/components/animated_qr_scanner.dart index 13528da2c..797ec285f 100644 --- a/mobile-app/lib/v2/components/animated_qr_scanner.dart +++ b/mobile-app/lib/v2/components/animated_qr_scanner.dart @@ -64,7 +64,12 @@ class AnimatedQrScanner extends StatefulWidget { } class _AnimatedQrScannerState extends State { - final MobileScannerController _controller = MobileScannerController(); + // Unrestricted: deliver every decoded camera frame; dedup happens via _parts. + // QR-only: skipping the other symbologies shortens per-frame detection. + final MobileScannerController _controller = MobileScannerController( + detectionSpeed: DetectionSpeed.unrestricted, + formats: const [BarcodeFormat.qrCode], + ); final Set _parts = {}; final Set _seenSequenceIndexes = {}; diff --git a/mobile-app/lib/v2/components/qr_scanner_page.dart b/mobile-app/lib/v2/components/qr_scanner_page.dart index b6af3996b..f6d8ea545 100644 --- a/mobile-app/lib/v2/components/qr_scanner_page.dart +++ b/mobile-app/lib/v2/components/qr_scanner_page.dart @@ -18,7 +18,7 @@ class QrScannerPage extends ConsumerStatefulWidget { } class _QrScannerPageState extends ConsumerState { - final _controller = MobileScannerController(); + final _controller = MobileScannerController(formats: const [BarcodeFormat.qrCode]); bool _scanned = false; @override diff --git a/mobile-app/lib/v2/components/underlined_text_link.dart b/mobile-app/lib/v2/components/underlined_text_link.dart new file mode 100644 index 000000000..5adadf2a0 --- /dev/null +++ b/mobile-app/lib/v2/components/underlined_text_link.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +/// Centered tappable underlined text, used for secondary actions under a +/// primary button (e.g. "Cancel Transaction"). +class UnderlinedTextLink extends StatelessWidget { + final String label; + final Color color; + final VoidCallback onTap; + + const UnderlinedTextLink({super.key, required this.label, required this.color, required this.onTap}); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + behavior: HitTestBehavior.opaque, + child: Center( + child: Text( + label, + style: context.themeText.smallParagraph?.copyWith( + color: color, + decoration: TextDecoration.underline, + decorationColor: color, + ), + ), + ), + ); + } +} diff --git a/mobile-app/lib/v2/screens/send/keystone_sign_screen.dart b/mobile-app/lib/v2/screens/send/keystone_sign_screen.dart index 065055797..223ec992f 100644 --- a/mobile-app/lib/v2/screens/send/keystone_sign_screen.dart +++ b/mobile-app/lib/v2/screens/send/keystone_sign_screen.dart @@ -35,6 +35,12 @@ class KeystoneSignScreen extends ConsumerStatefulWidget { } class _KeystoneSignScreenState extends ConsumerState { + /// Bytes per QR frame for the animated payload. The Keystone device scans + /// this QR, so frames must stay easy to read. SCALE-encoded transactions are + /// ~300 bytes, so 400-byte fragments put a typical transaction in a single + /// static QR (~version 21, well within Keystone's range). + static const _kUrFragmentLength = 400; + UnsignedTransactionData? _unsignedData; List? _urParts; String? _error; @@ -65,7 +71,7 @@ class _KeystoneSignScreenState extends ConsumerState { widget.session.account, widget.session.buildCall(), ); - final parts = encodeUr(data: unsigned.encodedPayloadRaw); + final parts = encodeUr(data: unsigned.encodedPayloadRaw, maxFragmentLength: _kUrFragmentLength); if (parts.isEmpty) throw Exception('Failed to encode transaction payload as UR'); if (cacheKey != null) { ref.read(keystoneSignCacheProvider.notifier).store(key: cacheKey, unsignedData: unsigned, urParts: parts); diff --git a/quantus_sdk/lib/src/rust/api/ur.dart b/quantus_sdk/lib/src/rust/api/ur.dart index b2fbe43db..332648030 100644 --- a/quantus_sdk/lib/src/rust/api/ur.dart +++ b/quantus_sdk/lib/src/rust/api/ur.dart @@ -8,6 +8,7 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; Uint8List decodeUr({required List urParts}) => RustLib.instance.api.crateApiUrDecodeUr(urParts: urParts); -List encodeUr({required List data}) => RustLib.instance.api.crateApiUrEncodeUr(data: data); +List encodeUr({required List data, int? maxFragmentLength}) => + RustLib.instance.api.crateApiUrEncodeUr(data: data, maxFragmentLength: maxFragmentLength); bool isCompleteUr({required List urParts}) => RustLib.instance.api.crateApiUrIsCompleteUr(urParts: urParts); diff --git a/quantus_sdk/lib/src/rust/frb_generated.dart b/quantus_sdk/lib/src/rust/frb_generated.dart index 2707e1b8c..c0cdf4c45 100644 --- a/quantus_sdk/lib/src/rust/frb_generated.dart +++ b/quantus_sdk/lib/src/rust/frb_generated.dart @@ -106,7 +106,7 @@ abstract class RustLibApi extends BaseApi { WormholeResult crateApiCryptoDeriveWormhole({required String mnemonicStr, required String path}); - List crateApiUrEncodeUr({required List data}); + List crateApiUrEncodeUr({required List data, int? maxFragmentLength}); Future crateApiWormholeEnsureCircuitBinaries({required String binsDir}); @@ -451,23 +451,25 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { const TaskConstMeta(debugName: 'derive_wormhole', argNames: ['mnemonicStr', 'path']); @override - List crateApiUrEncodeUr({required List data}) { + List crateApiUrEncodeUr({required List data, int? maxFragmentLength}) { return handler.executeSync( SyncTask( callFfi: () { 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)!; }, codec: SseCodec(decodeSuccessData: sse_decode_list_String, decodeErrorData: sse_decode_String), constMeta: kCrateApiUrEncodeUrConstMeta, - argValues: [data], + argValues: [data, maxFragmentLength], apiImpl: this, ), ); } - TaskConstMeta get kCrateApiUrEncodeUrConstMeta => const TaskConstMeta(debugName: 'encode_ur', argNames: ['data']); + TaskConstMeta get kCrateApiUrEncodeUrConstMeta => + const TaskConstMeta(debugName: 'encode_ur', argNames: ['data', 'maxFragmentLength']); @override Future crateApiWormholeEnsureCircuitBinaries({required String binsDir}) { @@ -924,6 +926,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return dco_decode_proof_input(raw); } + @protected + int dco_decode_box_autoadd_u_32(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as int; + } + @protected Keypair dco_decode_keypair(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -970,6 +978,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } + @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 U8Array32? dco_decode_opt_u_8_array_32(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -1110,6 +1124,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (sse_decode_proof_input(deserializer)); } + @protected + int sse_decode_box_autoadd_u_32(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return (sse_decode_u_32(deserializer)); + } + @protected Keypair sse_decode_keypair(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1164,6 +1184,17 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return MerkleProcessed(sortedSiblingsFlat: var_sortedSiblingsFlat, positions: var_positions); } + @protected + int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_box_autoadd_u_32(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 @@ -1327,6 +1358,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_proof_input(self, serializer); } + @protected + void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_u_32(self, serializer); + } + @protected void sse_encode_keypair(Keypair self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1373,6 +1410,16 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_list_prim_u_8_strict(self.positions, serializer); } + @protected + void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_box_autoadd_u_32(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 diff --git a/quantus_sdk/lib/src/rust/frb_generated.io.dart b/quantus_sdk/lib/src/rust/frb_generated.io.dart index 25aa4e273..130c3e175 100644 --- a/quantus_sdk/lib/src/rust/frb_generated.io.dart +++ b/quantus_sdk/lib/src/rust/frb_generated.io.dart @@ -44,6 +44,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected ProofInput dco_decode_box_autoadd_proof_input(dynamic raw); + @protected + int dco_decode_box_autoadd_u_32(dynamic raw); + @protected Keypair dco_decode_keypair(dynamic raw); @@ -62,6 +65,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected MerkleProcessed dco_decode_merkle_processed(dynamic raw); + @protected + int? dco_decode_opt_box_autoadd_u_32(dynamic raw); + @protected U8Array32? dco_decode_opt_u_8_array_32(dynamic raw); @@ -117,6 +123,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected ProofInput sse_decode_box_autoadd_proof_input(SseDeserializer deserializer); + @protected + int sse_decode_box_autoadd_u_32(SseDeserializer deserializer); + @protected Keypair sse_decode_keypair(SseDeserializer deserializer); @@ -135,6 +144,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected MerkleProcessed sse_decode_merkle_processed(SseDeserializer deserializer); + @protected + int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer); + @protected U8Array32? sse_decode_opt_u_8_array_32(SseDeserializer deserializer); @@ -195,6 +207,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_box_autoadd_proof_input(ProofInput self, SseSerializer serializer); + @protected + void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer); + @protected void sse_encode_keypair(Keypair self, SseSerializer serializer); @@ -213,6 +228,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_merkle_processed(MerkleProcessed self, SseSerializer serializer); + @protected + void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer); + @protected void sse_encode_opt_u_8_array_32(U8Array32? 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 fcd2a54c3..980032129 100644 --- a/quantus_sdk/lib/src/rust/frb_generated.web.dart +++ b/quantus_sdk/lib/src/rust/frb_generated.web.dart @@ -46,6 +46,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected ProofInput dco_decode_box_autoadd_proof_input(dynamic raw); + @protected + int dco_decode_box_autoadd_u_32(dynamic raw); + @protected Keypair dco_decode_keypair(dynamic raw); @@ -64,6 +67,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected MerkleProcessed dco_decode_merkle_processed(dynamic raw); + @protected + int? dco_decode_opt_box_autoadd_u_32(dynamic raw); + @protected U8Array32? dco_decode_opt_u_8_array_32(dynamic raw); @@ -119,6 +125,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected ProofInput sse_decode_box_autoadd_proof_input(SseDeserializer deserializer); + @protected + int sse_decode_box_autoadd_u_32(SseDeserializer deserializer); + @protected Keypair sse_decode_keypair(SseDeserializer deserializer); @@ -137,6 +146,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected MerkleProcessed sse_decode_merkle_processed(SseDeserializer deserializer); + @protected + int? sse_decode_opt_box_autoadd_u_32(SseDeserializer deserializer); + @protected U8Array32? sse_decode_opt_u_8_array_32(SseDeserializer deserializer); @@ -197,6 +209,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_box_autoadd_proof_input(ProofInput self, SseSerializer serializer); + @protected + void sse_encode_box_autoadd_u_32(int self, SseSerializer serializer); + @protected void sse_encode_keypair(Keypair self, SseSerializer serializer); @@ -215,6 +230,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_merkle_processed(MerkleProcessed self, SseSerializer serializer); + @protected + void sse_encode_opt_box_autoadd_u_32(int? self, SseSerializer serializer); + @protected void sse_encode_opt_u_8_array_32(U8Array32? self, SseSerializer serializer); diff --git a/quantus_sdk/rust/Cargo.lock b/quantus_sdk/rust/Cargo.lock index 9307a4cd0..17376bd25 100644 --- a/quantus_sdk/rust/Cargo.lock +++ b/quantus_sdk/rust/Cargo.lock @@ -3083,7 +3083,7 @@ dependencies = [ [[package]] name = "quantus_ur" version = "0.1.0" -source = "git+https://github.com/Quantus-Network/quantus_ur.git?tag=1.4.0#f3929da023e9c8081b8e9944b5b665871980fa79" +source = "git+https://github.com/Quantus-Network/quantus_ur.git?tag=1.5.0#ddd86aa12941d8a1d67121414a7285f3b2c5cac2" dependencies = [ "hex", "keystone-ur", diff --git a/quantus_sdk/rust/Cargo.toml b/quantus_sdk/rust/Cargo.toml index b30406b61..cb32da4f1 100644 --- a/quantus_sdk/rust/Cargo.toml +++ b/quantus_sdk/rust/Cargo.toml @@ -17,7 +17,7 @@ hex = "0.4.3" sp-core = { version = "43.0.0", default-features = true } parity-scale-codec = { version = "3.7.5", default-features = false, features = ["std"] } sp-runtime = { version = "48.0.0", default-features = false, features = ["std"] } -quantus_ur = { git = "https://github.com/Quantus-Network/quantus_ur.git", tag = "1.4.0" } +quantus_ur = { git = "https://github.com/Quantus-Network/quantus_ur.git", tag = "1.5.0" } # ZK proof generation (aligned with quantus-cli / chain) anyhow = "1.0" diff --git a/quantus_sdk/rust/src/api/ur.rs b/quantus_sdk/rust/src/api/ur.rs index a8dbbc19a..ffa159dfe 100644 --- a/quantus_sdk/rust/src/api/ur.rs +++ b/quantus_sdk/rust/src/api/ur.rs @@ -1,22 +1,21 @@ /// UR API for parsing QR codes in the ur:.. standard /// -use quantus_ur::{decode_bytes, encode_bytes, is_complete}; +use quantus_ur::{decode_bytes, encode_bytes, encode_bytes_with_options, is_complete}; -// Note decode_ur takes the list of QR Codes in any order and assembles them correctly. -// It also deals with the weird elements that are created in the UR standard when we exceed the number -// of segments. -// For example if you have 3 segments, and the scanner scans all 3 but doesn't succeed, subsequent parts -// are sent with strange numbers like /412-3/ which are encoded with pieces of the previous segments so that -// the algorithm recovers faster than just repeating the segments over and over. This is described in the UR -// standard. FYI. +// Note decode_ur takes the list of QR Codes in any order and assembles them correctly. +// Only the simple parts 1..=N are emitted and accepted; mixed fountain parts +// (sequence > sequence_count) are rejected by quantus_ur. #[flutter_rust_bridge::frb(sync)] pub fn decode_ur(ur_parts: Vec) -> Result, String> { decode_bytes(&ur_parts).map_err(|e| e.to_string()) } #[flutter_rust_bridge::frb(sync)] -pub fn encode_ur(data: Vec) -> Result, String> { - encode_bytes(&data).map_err(|e| e.to_string()) +pub fn encode_ur(data: Vec, max_fragment_length: Option) -> Result, String> { + match max_fragment_length { + Some(len) => encode_bytes_with_options(&data, len as usize).map_err(|e| e.to_string()), + None => encode_bytes(&data).map_err(|e| e.to_string()), + } } #[flutter_rust_bridge::frb(sync)] @@ -33,7 +32,7 @@ mod tests { let hex_payload = "0200007416854906f03a9dff66e3270a736c44e15970ac03a638471523a03069f276ca0700e876481755010000007400000002000000"; let payload_bytes = hex::decode(hex_payload).expect("Hex decode failed"); - let encoded_parts = encode_ur(payload_bytes.clone()).expect("Encoding failed"); + let encoded_parts = encode_ur(payload_bytes.clone(), None).expect("Encoding failed"); assert_eq!(encoded_parts.len(), 1, "Should be single part"); let decoded_bytes = decode_ur(encoded_parts).expect("Decoding failed"); @@ -45,7 +44,7 @@ mod tests { let hex_payload = "0200007416854906f03a9dff66e3270a736c44e15970ac03a638471523a03069f276ca0700e876481755010000007400000002000000".repeat(10); let payload_bytes = hex::decode(&hex_payload).expect("Hex decode failed"); - let encoded_parts = encode_ur(payload_bytes.clone()).expect("Encoding failed"); + let encoded_parts = encode_ur(payload_bytes.clone(), None).expect("Encoding failed"); assert!(encoded_parts.len() > 1, "Should be multiple parts"); let decoded_bytes = decode_ur(encoded_parts).expect("Decoding failed"); @@ -56,7 +55,7 @@ mod tests { fn test_is_complete_single_part() { let hex_payload = "0200007416854906f03a9dff66e3270a736c44e15970ac03a638471523a03069f276ca0700e876481755010000007400000002000000"; let payload_bytes = hex::decode(hex_payload).expect("Hex decode failed"); - let encoded_parts = encode_ur(payload_bytes).expect("Encoding failed"); + let encoded_parts = encode_ur(payload_bytes, None).expect("Encoding failed"); assert!(is_complete_ur(encoded_parts), "Single part should be complete"); } @@ -65,7 +64,7 @@ mod tests { fn test_is_complete_multi_part_complete() { let hex_payload = "0200007416854906f03a9dff66e3270a736c44e15970ac03a638471523a03069f276ca0700e876481755010000007400000002000000".repeat(10); let payload_bytes = hex::decode(&hex_payload).expect("Hex decode failed"); - let encoded_parts = encode_ur(payload_bytes).expect("Encoding failed"); + let encoded_parts = encode_ur(payload_bytes, None).expect("Encoding failed"); assert!(is_complete_ur(encoded_parts), "All parts should be complete"); } @@ -74,7 +73,7 @@ mod tests { fn test_is_complete_multi_part_incomplete() { let hex_payload = "0200007416854906f03a9dff66e3270a736c44e15970ac03a638471523a03069f276ca0700e876481755010000007400000002000000".repeat(10); let payload_bytes = hex::decode(&hex_payload).expect("Hex decode failed"); - let encoded_parts = encode_ur(payload_bytes).expect("Encoding failed"); + let encoded_parts = encode_ur(payload_bytes, None).expect("Encoding failed"); assert!(encoded_parts.len() > 1, "Should have multiple parts"); @@ -82,11 +81,21 @@ mod tests { assert!(!is_complete_ur(incomplete_parts), "Incomplete parts should return false"); } + #[test] + fn test_large_fragment_roundtrip() { + let payload_bytes: Vec = (0..7219u32).map(|i| (i % 251) as u8).collect(); + let encoded_parts = + encode_ur(payload_bytes.clone(), Some(1500)).expect("Encoding failed"); + assert_eq!(encoded_parts.len(), 5, "7219 bytes at 1500 per fragment"); + let decoded_bytes = decode_ur(encoded_parts).expect("Decoding failed"); + assert_eq!(decoded_bytes, payload_bytes); + } + #[test] fn test_multi_part_out_of_order() { let hex_payload = "0200007416854906f03a9dff66e3270a736c44e15970ac03a638471523a03069f276ca0700e876481755010000007400000002000000".repeat(10); let payload_bytes = hex::decode(&hex_payload).expect("Hex decode failed"); - let encoded_parts = encode_ur(payload_bytes.clone()).expect("Encoding failed"); + let encoded_parts = encode_ur(payload_bytes.clone(), None).expect("Encoding failed"); assert!(encoded_parts.len() > 1, "Should be multiple parts"); diff --git a/quantus_sdk/rust/src/frb_generated.rs b/quantus_sdk/rust/src/frb_generated.rs index dc1ac8963..7b0f6041f 100644 --- a/quantus_sdk/rust/src/frb_generated.rs +++ b/quantus_sdk/rust/src/frb_generated.rs @@ -470,9 +470,10 @@ fn wire__crate__api__ur__encode_ur_impl( let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); let api_data = >::sse_decode(&mut deserializer); + let api_max_fragment_length = >::sse_decode(&mut deserializer); deserializer.end(); transform_result_sse::<_, String>((move || { - let output_ok = crate::api::ur::encode_ur(api_data)?; + let output_ok = crate::api::ur::encode_ur(api_data, api_max_fragment_length)?; Ok(output_ok) })()) }, @@ -657,15 +658,15 @@ fn wire__crate__api__wormhole__generate_proof_impl( let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); let api_input = ::sse_decode(&mut deserializer); - let api_prover_bin_path = ::sse_decode(&mut deserializer); - let api_common_bin_path = ::sse_decode(&mut deserializer); + let api__prover_bin_path = ::sse_decode(&mut deserializer); + let api__common_bin_path = ::sse_decode(&mut deserializer); deserializer.end(); move |context| { transform_result_sse::<_, String>((move || { let output_ok = crate::api::wormhole::generate_proof( api_input, - api_prover_bin_path, - api_common_bin_path, + api__prover_bin_path, + api__common_bin_path, )?; Ok(output_ok) })()) @@ -1198,6 +1199,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<[u8; 32]> { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -1625,6 +1637,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<[u8; 32]> { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {