use alloc::string::{String, ToString};
use core::fmt;
use ur_registry::error::{URError, URResult};
pub fn cyclic_encode(
message: &[u8],
max_fragment_length: usize,
ur_type: String,
) -> URResult<UREncodeResult> {
let mut encoder = ur::Encoder::new(message, max_fragment_length, ur_type.clone())
.map_err(|e| URError::CborEncodeError(e.to_string()))?;
if encoder.fragment_count() > 1 {
Ok(UREncodeResult {
is_multi_part: true,
data: encoder
.next_part()
.map_err(|e| URError::UrEncodeError(e.to_string()))?,
encoder: Some(KeystoneUREncoder::new(encoder)),
})
} else {
let ur = ur::encode(message, ur_type);
Ok(UREncodeResult {
is_multi_part: false,
data: ur,
encoder: None,
})
}
}
pub fn probe_encode(
message: &[u8],
max_fragment_length: usize,
ur_type: String,
) -> URResult<UREncodeResult> {
let mut encoder = ur::Encoder::new(message, max_fragment_length, ur_type.clone())
.map_err(|e| URError::CborEncodeError(e.to_string()))?;
if encoder.fragment_count() > 1 {
Ok(UREncodeResult {
is_multi_part: true,
data: encoder
.next_part()
.map_err(|e| URError::UrEncodeError(e.to_string()))?,
encoder: Some(KeystoneUREncoder::new(encoder)),
})
} else {
let ur = ur::encode(message, ur_type);
Ok(UREncodeResult {
is_multi_part: false,
data: ur,
encoder: None,
})
}
}
pub struct UREncodeResult {
pub is_multi_part: bool,
pub data: String,
pub encoder: Option<KeystoneUREncoder>,
}
pub struct KeystoneUREncoder {
encoder: ur::Encoder,
}
impl fmt::Debug for UREncodeResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("")
.field(&self.is_multi_part)
.field(&self.data)
.finish()
}
}
impl KeystoneUREncoder {
pub fn new(encoder: ur::Encoder) -> Self {
KeystoneUREncoder { encoder }
}
pub fn next_cyclic_part(&mut self) -> URResult<String> {
self.encoder
.next_cyclic_part()
.map_err(|e| URError::CborEncodeError(e.to_string()))
}
pub fn next_part(&mut self) -> URResult<String> {
self.encoder
.next_part()
.map_err(|e| URError::CborEncodeError(e.to_string()))
}
pub fn current_index(&self) -> usize {
self.encoder.current_index()
}
pub fn fragment_count(&self) -> usize {
self.encoder.fragment_count()
}
}
#[cfg(test)]
mod tests {
use crate::keystone_ur_decoder::{MultiURParseResult, URParseResult, probe_decode};
use crate::keystone_ur_encoder::{cyclic_encode, probe_encode};
use alloc::string::ToString;
use alloc::vec;
use alloc::vec::Vec;
use hex::FromHex;
use ur_registry::crypto_psbt::CryptoPSBT;
use ur_registry::extend::qr_hardware_call::QRHardwareCall;
use ur_registry::traits::{RegistryItem, UR};
use ur_registry::zcash::zcash_batch_sig_result::ZcashBatchSigResult;
use ur_registry::zcash::zcash_sign_batch::ZcashSignBatch;
use ur_registry::zcash::zcash_sign_result::{
ZCASH_SIGN_RESULT_KIND_PCZT_V1, ZCASH_SIGN_RESULT_VERSION, ZCASH_SIGN_STATUS_SIGNED,
ZcashSignMessageResult, ZcashSignResult,
};
#[test]
fn test_encode_decode_zcash_sign_batch_ur() {
// Serialization of an empty PCZT BatchSignRequest:
// "PCZB" || batch_version_le || pczt_version_le || Postcard body.
let request = vec![b'P', b'C', b'Z', b'B', 1, 0, 0, 0, 2, 0, 0, 0, 0];
let request_id = vec![0xaa, 0xbb];
let batch = ZcashSignBatch::new(request_id.clone(), request.clone());
let cbor: Vec<u8> = batch.try_into().unwrap();
let encoded =
probe_encode(&cbor, 400, ZcashSignBatch::get_registry_type().get_type()).unwrap();
assert!(!encoded.is_multi_part);
let decoded: URParseResult<ZcashSignBatch> = probe_decode(encoded.data).unwrap();
let decoded_batch = decoded.data.unwrap();
assert_eq!(decoded.ur_type.unwrap().get_type_str(), "zcash-sign-batch");
assert_eq!(decoded_batch.get_data(), request);
assert_eq!(decoded_batch.get_request_id(), request_id);
}
#[test]
fn test_registry_ur_encoder_type_is_compatible() {
let crypto = CryptoPSBT::new(vec![0xaa, 0xbb, 0xcc]);
let mut encoder = super::KeystoneUREncoder::new(crypto.to_ur_encoder(400));
assert_eq!(encoder.fragment_count(), 1);
let part = encoder.next_part().unwrap();
assert!(part.starts_with("ur:crypto-psbt/"));
}
#[test]
fn test_encode_decode_zcash_sign_result_ur() {
let payload_digest =
Vec::from_hex("f2dbc955d1edad3014bc907efc15e93adb4412cdee847d261cd942998693e590")
.unwrap();
let result = ZcashSignResult::new(
ZCASH_SIGN_RESULT_VERSION,
vec![0xaa, 0xbb],
vec![ZcashSignMessageResult::signed(
vec![0x01],
ZCASH_SIGN_RESULT_KIND_PCZT_V1,
b"signed-pczt-result".to_vec(),
payload_digest.clone(),
)],
);
let cbor: Vec<u8> = result.try_into().unwrap();
let encoded =
probe_encode(&cbor, 400, ZcashSignResult::get_registry_type().get_type()).unwrap();
let literal_ur = "ur:zcash-sign-result/otadadaofwpkrkaxlyonadfpadaoaeaxadaagmjkiniojtihiedpjoiaknjydpjpihjkkpjzjyamhdcxwzuysogottwepmdybbrfmhkbztbzwlftuyfybgsnwylrkidscetafwnllnmuvwmhuorkvwgr";
assert!(!encoded.is_multi_part);
assert_eq!(encoded.data, literal_ur);
let decoded: URParseResult<ZcashSignResult> = probe_decode(literal_ur.to_string()).unwrap();
let decoded_result = decoded.data.unwrap();
assert_eq!(decoded.ur_type.unwrap().get_type_str(), "zcash-sign-result");
assert_eq!(decoded_result.get_version(), ZCASH_SIGN_RESULT_VERSION);
assert_eq!(decoded_result.get_request_id(), &vec![0xaa, 0xbb]);
assert_eq!(decoded_result.get_results().len(), 1);
assert_eq!(decoded_result.get_results()[0].get_id(), &vec![0x01]);
assert_eq!(
decoded_result.get_results()[0].get_status(),
ZCASH_SIGN_STATUS_SIGNED
);
assert_eq!(
decoded_result.get_results()[0].get_kind(),
ZCASH_SIGN_RESULT_KIND_PCZT_V1
);
assert_eq!(
decoded_result.get_results()[0].get_payload(),
&b"signed-pczt-result".to_vec()
);
assert_eq!(
decoded_result.get_results()[0].get_payload_digest(),
&payload_digest
);
}
#[test]
fn test_encode_decode_zcash_batch_sig_result_ur() {
// Serialization of an empty PCZT BatchSignResponse:
// "PCZS" || batch_version_le || Postcard body.
let response = vec![b'P', b'C', b'Z', b'S', 1, 0, 0, 0, 0];
let request_id = vec![0xaa, 0xbb];
let firmware_version = [1, 2, 3];
let result = ZcashBatchSigResult::new(
request_id.clone(),
response.clone(),
firmware_version,
);
let cbor: Vec<u8> = result.try_into().unwrap();
let encoded = probe_encode(
&cbor,
400,
ZcashBatchSigResult::get_registry_type().get_type(),
)
.unwrap();
assert!(!encoded.is_multi_part);
let decoded: URParseResult<ZcashBatchSigResult> = probe_decode(encoded.data).unwrap();
let decoded_result = decoded.data.unwrap();
assert_eq!(
decoded.ur_type.unwrap().get_type_str(),
"zcash-batch-sig-result"
);
assert_eq!(decoded_result.get_data(), response);
assert_eq!(decoded_result.get_request_id(), request_id);
assert_eq!(decoded_result.get_firmware_version(), &firmware_version);
}
#[test]
fn test_encode_decode_multipart_zcash_sign_batch_ur() {
let payload = vec![0x42; 1024];
let request_id = vec![0xaa, 0xbb];
let batch = ZcashSignBatch::new(request_id.clone(), payload.clone());
let cbor: Vec<u8> = batch.try_into().unwrap();
let encoded =
probe_encode(&cbor, 100, ZcashSignBatch::get_registry_type().get_type()).unwrap();
assert!(encoded.is_multi_part);
let first: URParseResult<ZcashSignBatch> = probe_decode(encoded.data).unwrap();
assert!(first.is_multi_part);
assert!(first.data.is_none());
let mut decoder = first.decoder.unwrap();
let mut encoder = encoded.encoder.unwrap();
let fragment_count = encoder.fragment_count();
let mut decoded_batch = None;
for _ in 1..fragment_count {
let part = encoder.next_part().unwrap();
let parsed: MultiURParseResult<ZcashSignBatch> = decoder.parse_ur(part).unwrap();
if parsed.is_complete {
decoded_batch = parsed.data;
break;
}
}
let decoded_batch = decoded_batch.unwrap();
assert_eq!(decoded_batch.get_data(), payload);
assert_eq!(decoded_batch.get_request_id(), request_id);
}
#[test]
fn test_encode_ada_hardware_call() {
let data = "a3010002d90515a10182d90516a101d90130a10186182cf500f500f5d90516a201d90130a1018a182cf51901f5f500f500f500f502010400";
let data = Vec::from_hex(data).unwrap();
// hardware call
let res = probe_encode(&data, 400, QRHardwareCall::get_registry_type().get_type()).unwrap();
assert_eq!(
"ur:qr-hardware-call/otadaeaotaahbzoyadlftaahcmoyadtaaddyoyadlncsdwykaeykaeyktaahcmoeadtaaddyoyadlecsdwykcfadykykaeykaeykaeykaoadaaaeyteyldre",
res.data
)
}
//
#[test]
fn test_encode_sol_hardware_call() {
let data = "a4010002d90515a10184d90516a301d90130a10186182cf5183cf500f502000463455448d90516a301d90130a10186182cf51901f5f500f502010463534f4cd90516a301d90130a10186182cf51901f5f501f502000463534f4cd90516a301d90130a1018a182cf51901f5f500f500f400f402010463534f4c036b4c6561702057616c6c65740401";
let data = Vec::from_hex(data).unwrap();
// hardware call
let res = probe_encode(&data, 400, QRHardwareCall::get_registry_type().get_type()).unwrap();
assert_eq!(
"ur:qr-hardware-call/oxadaeaotaahbzoyadlrtaahcmotadtaaddyoyadlncsdwykcsfnykaeykaoaeaaiafeghfdtaahcmotadtaaddyoyadlncsdwykcfadykykaeykaoadaaiagugwgstaahcmotadtaaddyoyadlncsdwykcfadykykadykaoaeaaiagugwgstaahcmotadtaaddyoyadlecsdwykcfadykykaeykaewkaewkaoadaaiagugwgsaxjegsihhsjocxhghsjzjzihjyaaadfnfxcmfy",
res.data
)
}
#[test]
fn test_encode_cosmos_hardware_call() {
let data = "a3010002d90515a10182d90516a101d90130a10186182cf500f500f5d90516a201d90130a1018a182cf51901f5f500f500f500f502010400";
let data = Vec::from_hex(data).unwrap();
// hardware call
let res = probe_encode(&data, 400, QRHardwareCall::get_registry_type().get_type()).unwrap();
assert_eq!(
"ur:qr-hardware-call/otadaeaotaahbzoyadlftaahcmoyadtaaddyoyadlncsdwykaeykaeyktaahcmoeadtaaddyoyadlecsdwykcfadykykaeykaeykaeykaoadaaaeyteyldre",
res.data
)
}
#[test]
fn test_encode() {
let crypto = CryptoPSBT::new(
Vec::from_hex("8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa")
.unwrap());
let result: Vec<u8> = crypto.try_into().unwrap();
let result =
probe_encode(&result, 400, CryptoPSBT::get_registry_type().get_type()).unwrap();
assert_eq!("ur:crypto-psbt/1-3/lpadaxcfaxiacyvwhdfhndhkadclhkaxhnlkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbnychpmiy",
result.data);
if result.is_multi_part {
let mut encoder = result.encoder.unwrap();
let next = encoder.next_part().unwrap();
assert_eq!("ur:crypto-psbt/2-3/lpaoaxcfaxiacyvwhdfhndhkadclaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaylbntahvo",
next);
let next = encoder.next_part().unwrap();
assert_eq!("ur:crypto-psbt/3-3/lpaxaxcfaxiacyvwhdfhndhkadclpklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypknseoskve",
next
);
let next = encoder.next_part().unwrap();
assert_eq!("ur:crypto-psbt/4-3/lpaaaxcfaxiacyvwhdfhndhkadclaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbayneieyksn",
next);
}
}
#[test]
fn test_cyclic_encode() {
let crypto = CryptoPSBT::new(
Vec::from_hex("8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa8c05c4b4f3e88840a4f4b5f155cfd69473ea169f3d0431b7a6787a23777f08aa")
.unwrap());
let result: Vec<u8> = crypto.try_into().unwrap();
let result =
cyclic_encode(&result, 400, CryptoPSBT::get_registry_type().get_type()).unwrap();
assert_eq!("ur:crypto-psbt/1-3/lpadaxcfaxiacyvwhdfhndhkadclhkaxhnlkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbnychpmiy",
result.data);
if result.is_multi_part {
let mut encoder = result.encoder.unwrap();
let next = encoder.next_cyclic_part().unwrap();
assert_eq!("ur:crypto-psbt/2-3/lpaoaxcfaxiacyvwhdfhndhkadclaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaylbntahvo",
next);
let next = encoder.next_cyclic_part().unwrap();
assert_eq!("ur:crypto-psbt/3-3/lpaxaxcfaxiacyvwhdfhndhkadclpklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypknseoskve",
next
);
let next = encoder.next_cyclic_part().unwrap();
assert_eq!("ur:crypto-psbt/1-3/lpadaxcfaxiacyvwhdfhndhkadclhkaxhnlkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbaypklkahssqzwfvslofzoxwkrewngotktbmwjkwdcmnefsaaehrlolkskncnktlbnychpmiy",
next);
}
}
}