diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index b8d6bb34c8a..2a7d48f978c 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -48,7 +48,7 @@ jobs: uses: actions/cache@v3 with: path: build/local - key: ${{ runner.os }}-${{ runner.arch }}-internal-${{ hashFiles('tools/install-dependencies') }} + key: ${{ runner.os }}-${{ runner.arch }}-internal-${{ hashFiles('tools/install-dependencies') }}-${{ hashFiles('tools/dependencies-version') }} - name: Install internal dependencies run: tools/install-dependencies diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 649b15e84a2..ac5f55c258b 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -36,7 +36,7 @@ jobs: uses: actions/cache@v3 with: path: build/local - key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + key: ${{ runner.os }}-${{ runner.arch }}-internal-${{ hashFiles('tools/install-dependencies') }}-${{ hashFiles('tools/dependencies-version') }} - name: Install internal dependencies run: | diff --git a/.github/workflows/kotlin-ci.yml b/.github/workflows/kotlin-ci.yml index 1198c20ae3c..fb60149fa30 100644 --- a/.github/workflows/kotlin-ci.yml +++ b/.github/workflows/kotlin-ci.yml @@ -56,7 +56,7 @@ jobs: uses: actions/cache@v3 with: path: build/local - key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + key: ${{ runner.os }}-${{ runner.arch }}-internal-${{ hashFiles('tools/install-dependencies') }}-${{ hashFiles('tools/dependencies-version') }} - name: Install internal dependencies run: tools/install-dependencies diff --git a/.github/workflows/linux-ci-sonarcloud.yml b/.github/workflows/linux-ci-sonarcloud.yml index 83fa3b4f216..32522f7b7c6 100644 --- a/.github/workflows/linux-ci-sonarcloud.yml +++ b/.github/workflows/linux-ci-sonarcloud.yml @@ -33,7 +33,7 @@ jobs: uses: actions/cache@v3 with: path: build/local - key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + key: ${{ runner.os }}-${{ runner.arch }}-internal-${{ hashFiles('tools/install-dependencies') }}-${{ hashFiles('tools/dependencies-version') }} - name: Install internal dependencies run: | diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 1062e02dacb..acda9dcfe10 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -25,7 +25,7 @@ jobs: uses: actions/cache@v3 with: path: build/local - key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-sys-dependencies-linux') }}-${{ hashFiles('tools/install-dependencies') }} + key: ${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('tools/install-sys-dependencies-linux') }}-internal-${{ hashFiles('tools/install-dependencies') }}-${{ hashFiles('tools/dependencies-version') }} - name: Install internal dependencies run: | tools/install-dependencies diff --git a/.github/workflows/linux-sampleapp-ci.yml b/.github/workflows/linux-sampleapp-ci.yml index a8c0dbe15da..66a7943858c 100644 --- a/.github/workflows/linux-sampleapp-ci.yml +++ b/.github/workflows/linux-sampleapp-ci.yml @@ -25,7 +25,7 @@ jobs: uses: actions/cache@v3 with: path: build/local - key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-sys-dependencies-linux') }}-${{ hashFiles('tools/install-dependencies') }} + key: ${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('tools/install-sys-dependencies-linux') }}-internal-${{ hashFiles('tools/install-dependencies') }}-${{ hashFiles('tools/dependencies-version') }} - name: Install internal dependencies run: | tools/install-dependencies diff --git a/.github/workflows/wasm-ci.yml b/.github/workflows/wasm-ci.yml index d68b0dcb39d..3422e664796 100644 --- a/.github/workflows/wasm-ci.yml +++ b/.github/workflows/wasm-ci.yml @@ -29,7 +29,7 @@ jobs: uses: actions/cache@v3 with: path: build/local - key: ${{ runner.os }}-internal-${{ hashFiles('tools/install-dependencies') }} + key: ${{ runner.os }}-${{ runner.arch }}-internal-${{ hashFiles('tools/install-dependencies') }}-${{ hashFiles('tools/dependencies-version') }} - name: Install internal dependencies run: tools/install-dependencies diff --git a/android/app/build.gradle b/android/app/build.gradle index 1e0093c6494..2314ed0e075 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -3,8 +3,8 @@ apply plugin: 'kotlin-android' android { namespace 'com.trustwallet.core.app' - compileSdkVersion 32 - ndkVersion '23.1.7779620' + compileSdk 35 + ndkVersion '28.0.12674087' defaultConfig { applicationId "com.trustwallet.core.app" minSdkVersion 23 @@ -25,6 +25,10 @@ android { } } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } } dependencies { diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aptos/TestAptosSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aptos/TestAptosSigner.kt index e536821658b..d4b0bc016d4 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aptos/TestAptosSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aptos/TestAptosSigner.kt @@ -69,6 +69,59 @@ class TestAptosSigner { ) } + @Test + fun AptosTransactionBlindSigningWithABI() { + // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x1ee2aa55382bf6b5a9f7a7f2b2066e16979489c6b2868704a2cf2c482f12b5ca/payload?network=mainnet + val key = + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec".toHexBytesInByteString() + + val payloadJson = """ + { + "function": "0x9770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da3::controller::deposit", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin" + ], + "arguments": [ + "0x4d61696e204163636f756e74", + "10000000", + false + ], + "type": "entry_function_payload" + } + """.trimIndent() + val signingInput = Aptos.SigningInput.newBuilder() + .setChainId(1) + .setExpirationTimestampSecs(1735902711) + .setGasUnitPrice(100) + .setMaxGasAmount(50000) + .setSender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30") + .setSequenceNumber(69) + .setAnyEncoded(payloadJson) + .setPrivateKey(key) + .setAbi(""" + [ + "vector", + "u64", + "bool" + ] + """.trimIndent()) + .build() + + val result = AnySigner.sign(signingInput, CoinType.APTOS, Aptos.SigningOutput.parser()) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.rawTxn.toByteArray())), + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304500000000000000029770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da30a636f6e74726f6c6c6572076465706f736974010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00030d0c4d61696e204163636f756e74088096980000000000010050c30000000000006400000000000000f7c577670000000001" + ) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.authenticator.signature.toByteArray())), + "13dcf1636abd31996729ded4d3bf56e9c7869a7188df4f185cbcce42f0dc74b6e1b54d31703ee3babbea2ef72b3338b8c2866cec68cbd761ccc7f80910124304" + ) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.encoded.toByteArray())), + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304500000000000000029770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da30a636f6e74726f6c6c6572076465706f736974010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00030d0c4d61696e204163636f756e74088096980000000000010050c30000000000006400000000000000f7c5776700000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c4013dcf1636abd31996729ded4d3bf56e9c7869a7188df4f185cbcce42f0dc74b6e1b54d31703ee3babbea2ef72b3338b8c2866cec68cbd761ccc7f80910124304" + ) + } + @Test fun AptosTransactionSigning() { // Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb4d62afd3862116e060dd6ad9848ccb50c2bc177799819f1d29c059ae2042467?network=devnet @@ -144,4 +197,41 @@ class TestAptosSigner { "1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf2502000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e7472616e736665725f636f696e730107e9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9086d65655f636f696e074d6565436f696e000220b7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c081027000000000000d0070000000000006400000000000000c2276ada0000000001002062e7a6a486553b56a53e89dfae3f780693e537e5b0a7ed33290780e581ca83694030ebd7e95cb464677f411868e2cbfcb22bc01cc63cded36c459dff45e6d2f1354ae4e090e7dfbb509851c0368b343e0e5ecaf6b08e7c1b94c186530b0f7dee0d" ) } + + @Test + fun AptosFungibleAssetTransfer() { + // Successfully broadcasted https://explorer.aptoslabs.com/txn/0x475fc97bcba87907166a720676e1b2f5320e613fd13014df37dcf17b09ff0e98/balanceChange?network=mainnet + val key = + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec".toHexBytesInByteString() + + val fungibleAssetTransferMessage = Aptos.FungibleAssetTransferMessage.newBuilder() + .setAmount(100000000) + .setTo("0x2d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e52") + .setMetadataAddress("0x2ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12") + .build() + val signingInput = Aptos.SigningInput.newBuilder() + .setChainId(1) + .setSender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30") + .setSequenceNumber(74) + .setGasUnitPrice(100) + .setMaxGasAmount(20) + .setExpirationTimestampSecs(1736060099) + .setFungibleAssetTransfer(fungibleAssetTransferMessage) + .setPrivateKey(key) + .build() + + val result = AnySigner.sign(signingInput, CoinType.APTOS, Aptos.SigningOutput.parser()) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.rawTxn.toByteArray())), + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304a00000000000000020000000000000000000000000000000000000000000000000000000000000001167072696d6172795f66756e6769626c655f73746f7265087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010e66756e6769626c655f6173736574084d657461646174610003202ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12202d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e520800e1f5050000000014000000000000006400000000000000c32c7a670000000001" + ) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.authenticator.signature.toByteArray())), + "2d4c5cbb710b6ef92813597054dbf8d3014529a7d85f6393f01e2a3e978c461c6aa656475b98b453ed3faebf7aa1fdd912bfc59a0c1b6fc44330793994b2e40c" + ) + assertEquals( + Numeric.cleanHexPrefix(Numeric.toHexString(result.encoded.toByteArray())), + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304a00000000000000020000000000000000000000000000000000000000000000000000000000000001167072696d6172795f66756e6769626c655f73746f7265087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010e66756e6769626c655f6173736574084d657461646174610003202ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12202d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e520800e1f5050000000014000000000000006400000000000000c32c7a6700000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c402d4c5cbb710b6ef92813597054dbf8d3014529a7d85f6393f01e2a3e978c461c6aa656475b98b453ed3faebf7aa1fdd912bfc59a0c1b6fc44330793994b2e40c" + ) + } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbi.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbi.kt index b6ad3654115..bd192a97070 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbi.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumAbi.kt @@ -209,4 +209,31 @@ class TestEthereumAbiDecoder { assertEquals(decodingOutput.getTokens(0).name, "name") assertEquals(decodingOutput.getTokens(0).stringValue, "deadbeef") } + + @Test + fun testEthereumAbiGetFunctionSignature() { + val abiJson = """ + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + """.trimIndent() + + val functionSignature = wallet.core.jni.EthereumAbi.getFunctionSignature(abiJson) + assertEquals(functionSignature, "transfer(address,uint256)") + } } diff --git a/android/build.gradle b/android/build.gradle index ae4f70d4299..9e475a4589b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,11 +1,11 @@ buildscript { - ext.kotlin_version = '1.6.10' + ext.kotlin_version = '2.1.0' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.0.0' + classpath 'com.android.tools.build:gradle:8.8.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index a21c6ebe28b..36074adc5de 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/android/wallet-core/build.gradle b/android/wallet-core/build.gradle index fcdf15afbbc..fe07d5c85bd 100644 --- a/android/wallet-core/build.gradle +++ b/android/wallet-core/build.gradle @@ -5,12 +5,10 @@ group = 'com.trustwallet' android { namespace 'wallet.core' - compileSdkVersion 32 - ndkVersion '23.1.7779620' + compileSdk 35 + ndkVersion '28.0.12674087' defaultConfig { minSdkVersion 23 - versionCode 1 - versionName "1.0" externalNativeBuild { cmake { arguments "-DCMAKE_BUILD_TYPE=Release", "-DTW_UNITY_BUILD=ON" @@ -18,10 +16,6 @@ android { } } - lintOptions { - abortOnError false - disable 'InvalidPackage' - } buildTypes { release { @@ -52,6 +46,15 @@ android { withSourcesJar() } } + lint { + abortOnError false + disable 'InvalidPackage' + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } } dependencies { diff --git a/codegen-v2/manifest/TWEthereumAbi.yaml b/codegen-v2/manifest/TWEthereumAbi.yaml index fda88c4c4c1..8db92277e5b 100644 --- a/codegen-v2/manifest/TWEthereumAbi.yaml +++ b/codegen-v2/manifest/TWEthereumAbi.yaml @@ -81,3 +81,18 @@ functions: is_constant: true is_nullable: false is_pointer: true +- name: TWEthereumAbiGetFunctionSignature + is_public: true + is_static: true + params: + - name: abi + type: + variant: string + is_constant: true + is_nullable: false + is_pointer: true + return_type: + variant: string + is_constant: true + is_nullable: true + is_pointer: true diff --git a/codegen/lib/templates/jni/compare_to.erb b/codegen/lib/templates/jni/compare_to.erb index fa3515d00bc..b2b5a4e21a1 100644 --- a/codegen/lib/templates/jni/compare_to.erb +++ b/codegen/lib/templates/jni/compare_to.erb @@ -1,7 +1,7 @@ <% less = locals[:less] -%> <% equal = locals[:equal] -%> <% compareMethod = JNIHelper.compareMethod(entity) -%> -<%= render('jni/method_prototype.erb', { method: compareMethod }) %> { +<%= render('jni/method_prototype.erb', { method: compareMethod, maybe_unused: true }) %> { <%= render('jni/instance_access.erb', { entity: entity }) %> <%= render('jni/parameter_access.erb', { method: compareMethod }) -%> <% if entity.struct? -%> diff --git a/codegen/lib/templates/jni/method.erb b/codegen/lib/templates/jni/method.erb index 5b33da3a226..dfd3962c74c 100644 --- a/codegen/lib/templates/jni/method.erb +++ b/codegen/lib/templates/jni/method.erb @@ -1,5 +1,5 @@ <% method = locals[:method] -%> -<%= render('jni/method_prototype.erb', { method: method }) %> { +<%= render('jni/method_prototype.erb', { method: method, maybe_unused: true }) %> { <% if !method.static -%> <%= render('jni/instance_access.erb', { entity: entity }) %> <% end -%> diff --git a/codegen/lib/templates/jni/method_prototype.erb b/codegen/lib/templates/jni/method_prototype.erb index 6aa64528c88..a8cda92705f 100644 --- a/codegen/lib/templates/jni/method_prototype.erb +++ b/codegen/lib/templates/jni/method_prototype.erb @@ -1,9 +1,16 @@ <% method = locals[:method] + maybe_unused = locals[:maybe_unused] + + maybe_unused_str = '' + if maybe_unused + maybe_unused_str = '[[maybe_unused]] ' + end + if method.static - parameters = 'jclass thisClass' + JNIHelper.parameters(method.parameters) + parameters = maybe_unused_str + 'jclass thisClass' + JNIHelper.parameters(method.parameters) else - parameters = 'jobject thisObject' + JNIHelper.parameters(method.parameters.drop(1)) + parameters = maybe_unused_str + 'jobject thisObject' + JNIHelper.parameters(method.parameters.drop(1)) end -%> -<%= JNIHelper.type(method.return_type) %> JNICALL <%= JNIHelper.function_name(entity: entity, function: method) %>(JNIEnv *env, <%= parameters %>)<% -%> +<%= JNIHelper.type(method.return_type) %> JNICALL <%= JNIHelper.function_name(entity: entity, function: method) %>(<%= maybe_unused_str %>JNIEnv *env, <%= parameters %>)<% -%> diff --git a/codegen/lib/templates/jni_c.erb b/codegen/lib/templates/jni_c.erb index d7785723aac..5f7f56125bc 100644 --- a/codegen/lib/templates/jni_c.erb +++ b/codegen/lib/templates/jni_c.erb @@ -20,7 +20,7 @@ <%# Constructors -%> <% entity.static_methods.each do |method| -%> <% next unless method.name.start_with?('Create') -%> -jlong JNICALL <%= JNIHelper.function_name(entity: entity, function: method, native_prefix: true) %>(JNIEnv *env, jclass thisClass<%= JNIHelper.parameters(method.parameters) %>) { +jlong JNICALL <%= JNIHelper.function_name(entity: entity, function: method, native_prefix: true) %>([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass thisClass<%= JNIHelper.parameters(method.parameters) %>) { <%= render('jni/parameter_access.erb', { method: method }) -%> struct TW<%= entity.name %> *instance = TW<%= entity.name %><%= method.name %>(<%= JNIHelper.arguments(method.parameters).join(', ') %>); <%= render('jni/parameter_release.erb', { method: method }) -%> @@ -31,7 +31,7 @@ jlong JNICALL <%= JNIHelper.function_name(entity: entity, function: method, nati <%# Destructors -%> <% entity.methods.each do |method| -%> <% next unless method.name.start_with?('Delete') -%> -void JNICALL <%= JNIHelper.function_name(entity: entity, function: method, native_prefix: true) %>(JNIEnv *env, jclass thisClass, jlong handle) { +void JNICALL <%= JNIHelper.function_name(entity: entity, function: method, native_prefix: true) %>([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass thisClass, jlong handle) { TW<%= entity.name %>Delete((struct TW<%= entity.name %> *) handle); } @@ -39,7 +39,7 @@ void JNICALL <%= JNIHelper.function_name(entity: entity, function: method, nativ <%# Initializers -%> <% entity.static_methods.each do |method| -%> <% next unless method.name.start_with?('Init') -%> -jbyteArray JNICALL <%= JNIHelper.function_name(entity: entity, function: method) %>(JNIEnv *env, jclass thisClass<%= JNIHelper.parameters(method.parameters.drop(1)) %>) { +jbyteArray JNICALL <%= JNIHelper.function_name(entity: entity, function: method) %>(JNIEnv *env, [[maybe_unused]] jclass thisClass<%= JNIHelper.parameters(method.parameters.drop(1)) %>) { jbyteArray array = (*env)->NewByteArray(env, sizeof(struct TW<%= entity.name %>)); jbyte* bytesBuffer = (*env)->GetByteArrayElements(env, array, NULL); struct TW<%= entity.name %> *instance = (struct TW<%= entity.name %> *) bytesBuffer; diff --git a/codegen/lib/templates/jni_h.erb b/codegen/lib/templates/jni_h.erb index 64ae676d449..0769b406880 100644 --- a/codegen/lib/templates/jni_h.erb +++ b/codegen/lib/templates/jni_h.erb @@ -30,34 +30,34 @@ jbyteArray JNICALL <%= JNIHelper.function_name(entity: entity, function: method) <%# Static property declarations -%> <% entity.static_properties.each do |property| -%> JNIEXPORT -<%= render('jni/method_prototype.erb', { method: property }) %>; +<%= render('jni/method_prototype.erb', { method: property, maybe_unused: false }) %>; <% end -%> <%# Static method declarations -%> <% entity.static_methods.each do |method| -%> <% next if method.name.start_with?('Create') || method.name.start_with?('Init') -%> JNIEXPORT -<%= render('jni/method_prototype.erb', { method: method }) %>; +<%= render('jni/method_prototype.erb', { method: method, maybe_unused: false }) %>; <% end -%> <%# Property declarations -%> <% entity.properties.each do |property| -%> JNIEXPORT -<%= render('jni/method_prototype.erb', { method: property }) %>; +<%= render('jni/method_prototype.erb', { method: property, maybe_unused: false }) %>; <% end -%> <%# Method declarations -%> <% entity.methods.each do |method| -%> <% next if method.name.start_with?('Delete') -%> JNIEXPORT -<%= render('jni/method_prototype.erb', { method: method }) %>; +<%= render('jni/method_prototype.erb', { method: method, maybe_unused: false }) %>; <% end -%> <% less = entity.static_methods.detect{ |i| i.name == 'Less' } -%> <% equal = entity.static_methods.detect{ |i| i.name == 'Equal' } -%> <% if !less.nil? && !equal.nil? -%> JNIEXPORT -<%= render('jni/method_prototype.erb', { method: JNIHelper.compareMethod(entity) }) %>; +<%= render('jni/method_prototype.erb', { method: JNIHelper.compareMethod(entity), maybe_unused: false }) %>; <% end -%> diff --git a/codegen/lib/templates/kotlin/ios_class.erb b/codegen/lib/templates/kotlin/ios_class.erb index 94d7a42f24b..f782f3f8dc8 100644 --- a/codegen/lib/templates/kotlin/ios_class.erb +++ b/codegen/lib/templates/kotlin/ios_class.erb @@ -7,12 +7,13 @@ import kotlinx.cinterop.toCValues <% constructors = entity.static_methods.select { |method| method.name.start_with?('Create') } -%> <% methods = entity.methods.select { |method| not method.name.start_with?('Delete') } -%> <% static_methods = entity.static_methods.select { |method| not method.name.start_with?('Create') } -%> +@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) actual class <%= entity.name %> constructor( val pointer: CPointer>, ) { <% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%> - @OptIn(ExperimentalStdlibApi::class) - private val cleaner = kotlin.native.internal.createCleaner(pointer) { ptr -> + @OptIn(kotlin.experimental.ExperimentalNativeApi::class) + private val cleaner = kotlin.native.ref.createCleaner(pointer) { ptr -> TW<%= entity.name %>Delete(ptr) } <% end -%> diff --git a/codegen/lib/templates/kotlin/ios_enum.erb b/codegen/lib/templates/kotlin/ios_enum.erb index 16d26ba89b4..28e0efe0ef7 100644 --- a/codegen/lib/templates/kotlin/ios_enum.erb +++ b/codegen/lib/templates/kotlin/ios_enum.erb @@ -6,6 +6,7 @@ import com.trustwallet.core.<%= "TW#{entity.name}" %>.* <% end -%> <% type = ": TW#{entity.name}" -%> +@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) actual enum class <%= entity.name %>( <% if has_string -%> val nativeValue<%= type %>, diff --git a/codegen/lib/templates/kotlin/ios_struct.erb b/codegen/lib/templates/kotlin/ios_struct.erb index 08fbcc34ef8..7092e86757a 100644 --- a/codegen/lib/templates/kotlin/ios_struct.erb +++ b/codegen/lib/templates/kotlin/ios_struct.erb @@ -2,6 +2,7 @@ import kotlinx.cinterop.toCValues +@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) actual object <%= entity.name %> { <%# Static property declarations -%> <% entity.static_properties.each do |property| -%> diff --git a/codegen/lib/templates/kotlin_jni/compare_to.erb b/codegen/lib/templates/kotlin_jni/compare_to.erb index 253a8584b6e..cd2d3dc5ab5 100644 --- a/codegen/lib/templates/kotlin_jni/compare_to.erb +++ b/codegen/lib/templates/kotlin_jni/compare_to.erb @@ -1,7 +1,7 @@ <% less = locals[:less] -%> <% equal = locals[:equal] -%> <% compareMethod = KotlinJniHelper.compareMethod(entity) -%> -<%= render('kotlin_jni/method_prototype.erb', { method: compareMethod }) %> { +<%= render('kotlin_jni/method_prototype.erb', { method: compareMethod, maybe_unused: false }) %> { <%= render('kotlin_jni/instance_access.erb', { entity: entity }) %> <%= render('kotlin_jni/parameter_access.erb', { method: compareMethod }) -%> <% if entity.struct? -%> diff --git a/codegen/lib/templates/kotlin_jni/method.erb b/codegen/lib/templates/kotlin_jni/method.erb index f41b05435a8..646e6c56523 100644 --- a/codegen/lib/templates/kotlin_jni/method.erb +++ b/codegen/lib/templates/kotlin_jni/method.erb @@ -1,5 +1,5 @@ <% method = locals[:method] -%> -<%= render('kotlin_jni/method_prototype.erb', { method: method }) %> { +<%= render('kotlin_jni/method_prototype.erb', { method: method, maybe_unused: false }) %> { <% if !method.static -%> <%= render('kotlin_jni/instance_access.erb', { entity: entity }) %> <% end -%> diff --git a/codegen/lib/templates/kotlin_jni/method_prototype.erb b/codegen/lib/templates/kotlin_jni/method_prototype.erb index a971ce95bd5..b46d90ef285 100644 --- a/codegen/lib/templates/kotlin_jni/method_prototype.erb +++ b/codegen/lib/templates/kotlin_jni/method_prototype.erb @@ -1,9 +1,16 @@ <% method = locals[:method] + maybe_unused = locals[:maybe_unused] + + maybe_unused_str = '' + if maybe_unused + maybe_unused_str = '[[maybe_unused]] ' + end + if method.static - parameters = 'jclass thisClass' + KotlinJniHelper.parameters(method.parameters) + parameters = maybe_unused_str + 'jclass thisClass' + KotlinJniHelper.parameters(method.parameters) else - parameters = 'jobject thisObject' + KotlinJniHelper.parameters(method.parameters.drop(1)) + parameters = maybe_unused_str + 'jobject thisObject' + KotlinJniHelper.parameters(method.parameters.drop(1)) end -%> -<%= KotlinJniHelper.type(method.return_type) %> JNICALL <%= KotlinJniHelper.function_name(entity: entity, function: method) %>(JNIEnv *env, <%= parameters %>)<% -%> +<%= KotlinJniHelper.type(method.return_type) %> JNICALL <%= KotlinJniHelper.function_name(entity: entity, function: method) %>(<%= maybe_unused_str %>JNIEnv *env, <%= parameters %>)<% -%> diff --git a/codegen/lib/templates/kotlin_jni_h.erb b/codegen/lib/templates/kotlin_jni_h.erb index 44e3bec9cf8..3e875035193 100644 --- a/codegen/lib/templates/kotlin_jni_h.erb +++ b/codegen/lib/templates/kotlin_jni_h.erb @@ -30,34 +30,34 @@ jbyteArray JNICALL <%= KotlinJniHelper.function_name(entity: entity, function: m <%# Static property declarations -%> <% entity.static_properties.each do |property| -%> JNIEXPORT -<%= render('kotlin_jni/method_prototype.erb', { method: property }) %>; +<%= render('kotlin_jni/method_prototype.erb', { method: property, maybe_unused: false }) %>; <% end -%> <%# Static method declarations -%> <% entity.static_methods.each do |method| -%> <% next if method.name.start_with?('Create') || method.name.start_with?('Init') -%> JNIEXPORT -<%= render('kotlin_jni/method_prototype.erb', { method: method }) %>; +<%= render('kotlin_jni/method_prototype.erb', { method: method, maybe_unused: false }) %>; <% end -%> <%# Property declarations -%> <% entity.properties.each do |property| -%> JNIEXPORT -<%= render('kotlin_jni/method_prototype.erb', { method: property }) %>; +<%= render('kotlin_jni/method_prototype.erb', { method: property, maybe_unused: false }) %>; <% end -%> <%# Method declarations -%> <% entity.methods.each do |method| -%> <% next if method.name.start_with?('Delete') -%> JNIEXPORT -<%= render('kotlin_jni/method_prototype.erb', { method: method }) %>; +<%= render('kotlin_jni/method_prototype.erb', { method: method, maybe_unused: false }) %>; <% end -%> <% less = entity.static_methods.detect{ |i| i.name == 'Less' } -%> <% equal = entity.static_methods.detect{ |i| i.name == 'Equal' } -%> <% if !less.nil? && !equal.nil? -%> JNIEXPORT -<%= render('kotlin_jni/method_prototype.erb', { method: KotlinJniHelper.compareMethod(entity) }) %>; +<%= render('kotlin_jni/method_prototype.erb', { method: KotlinJniHelper.compareMethod(entity), maybe_unused: false }) %>; <% end -%> diff --git a/include/TrustWalletCore/TWCardano.h b/include/TrustWalletCore/TWCardano.h index 17bd9ebb61f..2fd283e68d4 100644 --- a/include/TrustWalletCore/TWCardano.h +++ b/include/TrustWalletCore/TWCardano.h @@ -7,6 +7,7 @@ #include "TWBase.h" #include "TWData.h" #include "TWString.h" +#include "TWPublicKey.h" TW_EXTERN_C_BEGIN diff --git a/include/TrustWalletCore/TWEthereumAbi.h b/include/TrustWalletCore/TWEthereumAbi.h index f2d23a4dec5..638acde0367 100644 --- a/include/TrustWalletCore/TWEthereumAbi.h +++ b/include/TrustWalletCore/TWEthereumAbi.h @@ -113,4 +113,11 @@ TWString* _Nullable TWEthereumAbiDecodeCall(TWData* _Nonnull data, TWString* _No TW_EXPORT_STATIC_METHOD TWData* _Nonnull TWEthereumAbiEncodeTyped(TWString* _Nonnull messageJson); +/// Get function signature from Ethereum ABI json +/// +/// \param abi The function ABI json string, for example: {"inputs":[{"internalType":"bool","name":"arg1","type":"bool"}],"name":"fun1","outputs":[],"stateMutability":"nonpayable","type":"function"} +/// \return the function type signature, of the form "baz(int32,uint256)", null if the abi is invalid. +TW_EXPORT_STATIC_METHOD +TWString* _Nullable TWEthereumAbiGetFunctionSignature(TWString* _Nonnull abi); + TW_EXTERN_C_END diff --git a/jni/cpp/Random.cpp b/jni/cpp/Random.cpp index 216c61d46cc..8b46fbf6959 100644 --- a/jni/cpp/Random.cpp +++ b/jni/cpp/Random.cpp @@ -14,7 +14,7 @@ extern "C" { void random_buffer(uint8_t *buf, size_t len); } -JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) { +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, [[maybe_unused]] void *reserved) { cachedJVM = jvm; return JNI_VERSION_1_2; } diff --git a/kotlin/build.gradle.kts b/kotlin/build.gradle.kts index 387b99278c9..900b4bd953c 100644 --- a/kotlin/build.gradle.kts +++ b/kotlin/build.gradle.kts @@ -16,6 +16,7 @@ allprojects { compilerOptions { allWarningsAsErrors.set(true) jvmTarget.set(JvmTarget.JVM_17) + freeCompilerArgs.add("-Xexpect-actual-classes") } } } diff --git a/kotlin/gradle/libs.versions.toml b/kotlin/gradle/libs.versions.toml index 3d233c16d10..66e19c38b8b 100644 --- a/kotlin/gradle/libs.versions.toml +++ b/kotlin/gradle/libs.versions.toml @@ -1,12 +1,12 @@ [versions] -android-sdk-tools = "33.0.2" +android-sdk-tools = "35.0.0" android-sdk-min = "24" -android-sdk-compile = "33" +android-sdk-compile = "35" android-cmake = "3.22.1" -android-ndk = "25.2.9519653" +android-ndk = "28.0.12674087" -kotlin = "1.8.21" -agp = "8.0.0" +kotlin = "2.1.0" +agp = "8.8.0" wire = "4.5.6" androidx-test-runner = "1.5.2" diff --git a/kotlin/gradle/wrapper/gradle-wrapper.jar b/kotlin/gradle/wrapper/gradle-wrapper.jar index c1962a79e29..2c3521197d7 100644 Binary files a/kotlin/gradle/wrapper/gradle-wrapper.jar and b/kotlin/gradle/wrapper/gradle-wrapper.jar differ diff --git a/kotlin/gradle/wrapper/gradle-wrapper.properties b/kotlin/gradle/wrapper/gradle-wrapper.properties index 8707e8b5067..df97d72b8b9 100644 --- a/kotlin/gradle/wrapper/gradle-wrapper.properties +++ b/kotlin/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/kotlin/gradlew b/kotlin/gradlew index aeb74cbb43e..f5feea6d6b1 100755 --- a/kotlin/gradlew +++ b/kotlin/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,7 +85,9 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -130,10 +134,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -141,7 +148,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -149,7 +156,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -198,11 +205,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/kotlin/gradlew.bat b/kotlin/gradlew.bat index 6689b85beec..9b42019c791 100755 --- a/kotlin/gradlew.bat +++ b/kotlin/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/kotlin/settings.gradle.kts b/kotlin/settings.gradle.kts index 481134aa1f2..b943d1869cb 100644 --- a/kotlin/settings.gradle.kts +++ b/kotlin/settings.gradle.kts @@ -6,6 +6,7 @@ pluginManagement { repositories { google() mavenCentral() + gradlePluginPortal() } } diff --git a/kotlin/wallet-core-kotlin/build.gradle.kts b/kotlin/wallet-core-kotlin/build.gradle.kts index 22782599f26..8fc8be0a32f 100644 --- a/kotlin/wallet-core-kotlin/build.gradle.kts +++ b/kotlin/wallet-core-kotlin/build.gradle.kts @@ -12,7 +12,7 @@ plugins { kotlin { targetHierarchy.default() - android { + androidTarget { publishLibraryVariants = listOf("release") } @@ -23,6 +23,7 @@ kotlin { } } } + jvmToolchain(17) val nativeTargets = listOf( @@ -103,6 +104,10 @@ kotlin { } } } + + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } } android { diff --git a/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/AnySigner.kt b/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/AnySigner.kt index 480d260002f..a5a55bc2eb3 100644 --- a/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/AnySigner.kt +++ b/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/AnySigner.kt @@ -6,6 +6,7 @@ package com.trustwallet.core import kotlinx.cinterop.toCValues +@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) actual object AnySigner { actual fun sign(input: ByteArray, coin: CoinType): ByteArray { diff --git a/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/ByteArrayExt.kt b/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/ByteArrayExt.kt index bc537de453d..b6e500d75bd 100644 --- a/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/ByteArrayExt.kt +++ b/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/ByteArrayExt.kt @@ -9,6 +9,7 @@ import kotlinx.cinterop.readBytes import kotlinx.cinterop.toCValues // Build ByteArray from TWData, and then delete TWData +@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) internal fun COpaquePointer?.readTwBytes(): ByteArray? = this?.let { val result = TWDataBytes(it)?.readBytes(TWDataSize(it).toInt()) diff --git a/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/StringExt.kt b/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/StringExt.kt index 79cd8125d84..708228d9f63 100644 --- a/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/StringExt.kt +++ b/kotlin/wallet-core-kotlin/src/iosMain/kotlin/com/trustwallet/core/StringExt.kt @@ -8,6 +8,7 @@ import kotlinx.cinterop.CValuesRef import kotlinx.cinterop.toKString // Build String from TWString, and then delete TWString +@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) internal fun CValuesRef<*>?.fromTwString(): String? = this?.let { val result = TWStringUTF8Bytes(it)?.toKString() diff --git a/registry.json b/registry.json index c5edff27ed7..72a5e13ce3a 100644 --- a/registry.json +++ b/registry.json @@ -709,11 +709,11 @@ "curve": "ed25519", "publicKeyType": "ed25519", "explorer": { - "url": "https://explorer.sui.io/", - "txPath": "/txblock/", - "accountPath": "/address/", - "sampleTx": "5i8fbSL6r8yw2xcKmXxwkzHu3wpiyMLsyf2htCvDH8Ao", - "sampleAccount": "0x259ff8074ab425cbb489f236e18e08f03f1a7856bdf7c7a2877bd64f738b5015" + "url": "https://suiscan.xyz/mainnet", + "txPath": "/tx/", + "accountPath": "/account/", + "sampleTx": "68wBKsZyYXmCUydDmabQ71kTcFWTfDG7tFmTLk1HgNdN", + "sampleAccount": "0x54e80d76d790c277f5a44f3ce92f53d26f5894892bf395dee6375988876be6b2" }, "info": { "url": "https://sui.io/", diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 0c16a96d581..3b6ce25bf99 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -198,6 +198,7 @@ dependencies = [ "bitcoin_hashes", "hex_lit", "secp256k1", + "serde", ] [[package]] @@ -213,6 +214,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d7066118b13d4b20b23645932dfb3a81ce7e29f95726c2036fa33cd7b092501" dependencies = [ "bitcoin-private", + "serde", ] [[package]] @@ -1389,6 +1391,7 @@ dependencies = [ "bitcoin_hashes", "rand", "secp256k1-sys", + "serde", ] [[package]] @@ -1785,6 +1788,8 @@ name = "tw_bitcoin" version = "0.1.0" dependencies = [ "bitcoin", + "itertools", + "lazy_static", "secp256k1", "serde", "serde_json", diff --git a/rust/chains/tw_aptos/src/aptos_move_packages.rs b/rust/chains/tw_aptos/src/aptos_move_packages.rs index e90e6bf2a4b..67b471c2640 100644 --- a/rust/chains/tw_aptos/src/aptos_move_packages.rs +++ b/rust/chains/tw_aptos/src/aptos_move_packages.rs @@ -2,6 +2,8 @@ // // Copyright © 2017 Trust Wallet. +use std::str::FromStr; + use crate::transaction_payload::{EntryFunction, TransactionPayload}; use move_core_types::account_address::AccountAddress; use move_core_types::ident_str; @@ -205,3 +207,32 @@ pub fn managed_coin_register(coin_type: TypeTag) -> TransactionPayload { json!([]), )) } + +pub fn fungible_asset_transfer( + metadata_address: AccountAddress, + to: AccountAddress, + amount: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("primary_fungible_store").to_owned(), + ), + ident_str!("transfer").to_owned(), + vec![TypeTag::from_str("0x1::fungible_asset::Metadata") + .tw_err(|_| SigningErrorType::Error_internal)?], + vec![ + bcs::encode(&metadata_address)?, + bcs::encode(&to)?, + bcs::encode(&amount)?, + ], + json!([ + metadata_address.to_hex_literal(), + to.to_hex_literal(), + amount.to_string() + ]), + ))) +} diff --git a/rust/chains/tw_aptos/src/aptos_move_types.rs b/rust/chains/tw_aptos/src/aptos_move_types.rs new file mode 100644 index 00000000000..f364b7f6e42 --- /dev/null +++ b/rust/chains/tw_aptos/src/aptos_move_types.rs @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use move_core_types::{ + account_address::AccountAddress, + identifier::Identifier, + language_storage::{StructTag, TypeTag}, + parser::parse_type_tag, +}; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; +use tw_encoding::EncodingError; + +/// The address of an account +/// +/// This is represented in a string as a 64 character hex string, sometimes +/// shortened by stripping leading 0s, and adding a 0x. +#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Address(AccountAddress); + +impl From for Address { + fn from(address: AccountAddress) -> Self { + Self(address) + } +} + +impl From
for AccountAddress { + fn from(address: Address) -> Self { + address.0 + } +} + +impl From<&Address> for AccountAddress { + fn from(address: &Address) -> Self { + address.0 + } +} + +/// A wrapper of a Move identifier +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)] +pub struct IdentifierWrapper(pub Identifier); + +impl From for Identifier { + fn from(value: IdentifierWrapper) -> Identifier { + value.0 + } +} + +impl From for IdentifierWrapper { + fn from(value: Identifier) -> IdentifierWrapper { + Self(value) + } +} + +impl From<&Identifier> for IdentifierWrapper { + fn from(value: &Identifier) -> IdentifierWrapper { + Self(value.clone()) + } +} + +/// A Move struct tag for referencing an onchain struct type +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MoveStructTag { + pub address: Address, + pub module: IdentifierWrapper, + pub name: IdentifierWrapper, + /// Generic type parameters associated with the struct + pub generic_type_params: Vec, +} + +impl From for MoveStructTag { + fn from(tag: StructTag) -> Self { + Self { + address: tag.address.into(), + module: tag.module.into(), + name: tag.name.into(), + generic_type_params: tag.type_params.into_iter().map(MoveType::from).collect(), + } + } +} + +impl From<&StructTag> for MoveStructTag { + fn from(tag: &StructTag) -> Self { + Self { + address: tag.address.into(), + module: IdentifierWrapper::from(&tag.module), + name: IdentifierWrapper::from(&tag.name), + generic_type_params: tag.type_params.iter().map(MoveType::from).collect(), + } + } +} + +impl TryFrom for StructTag { + type Error = EncodingError; + + fn try_from(tag: MoveStructTag) -> Result { + Ok(Self { + address: tag.address.into(), + module: tag.module.into(), + name: tag.name.into(), + type_params: tag + .generic_type_params + .into_iter() + .map(|p| p.try_into()) + .collect::, Self::Error>>()?, + }) + } +} + +/// An enum of Move's possible types on-chain +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum MoveType { + /// A bool type + Bool, + /// An 8-bit unsigned int + U8, + /// A 16-bit unsigned int + U16, + /// A 32-bit unsigned int + U32, + /// A 64-bit unsigned int + U64, + /// A 128-bit unsigned int + U128, + /// A 256-bit unsigned int + U256, + /// A 32-byte account address + Address, + /// An account signer + Signer, + /// A Vector of [`MoveType`] + Vector { items: Box }, + /// A struct of [`MoveStructTag`] + Struct(MoveStructTag), + /// A generic type param with index + GenericTypeParam { index: u16 }, + /// A reference + Reference { mutable: bool, to: Box }, + /// A move type that couldn't be parsed + /// + /// This prevents the parser from just throwing an error because one field + /// was unparsable, and gives the value in it. + Unparsable(String), +} + +impl FromStr for MoveType { + type Err = EncodingError; + + // Taken from: https://github.com/aptos-labs/aptos-core/blob/aaa3514c8ee4e5d38b89d916eadff7286a42e040/api/types/src/move_types.rs#L612-L639 + fn from_str(mut s: &str) -> Result { + let mut is_ref = false; + let mut is_mut = false; + if s.starts_with('&') { + s = &s[1..]; + is_ref = true; + } + if is_ref && s.starts_with("mut ") { + s = &s[4..]; + is_mut = true; + } + // Previously this would just crap out, but this meant the API could + // return a serialized version of an object and not be able to + // deserialize it using that same object. + let inner = match parse_type_tag(s) { + Ok(inner) => inner.into(), + Err(_e) => MoveType::Unparsable(s.to_string()), + }; + if is_ref { + Ok(MoveType::Reference { + mutable: is_mut, + to: Box::new(inner), + }) + } else { + Ok(inner) + } + } +} + +impl From for MoveType { + fn from(tag: TypeTag) -> Self { + match tag { + TypeTag::Bool => MoveType::Bool, + TypeTag::U8 => MoveType::U8, + TypeTag::U16 => MoveType::U16, + TypeTag::U32 => MoveType::U32, + TypeTag::U64 => MoveType::U64, + TypeTag::U256 => MoveType::U256, + TypeTag::U128 => MoveType::U128, + TypeTag::Address => MoveType::Address, + TypeTag::Signer => MoveType::Signer, + TypeTag::Vector(v) => MoveType::Vector { + items: Box::new(MoveType::from(*v)), + }, + TypeTag::Struct(v) => MoveType::Struct((*v).into()), + } + } +} + +impl From<&TypeTag> for MoveType { + fn from(tag: &TypeTag) -> Self { + match tag { + TypeTag::Bool => MoveType::Bool, + TypeTag::U8 => MoveType::U8, + TypeTag::U16 => MoveType::U16, + TypeTag::U32 => MoveType::U32, + TypeTag::U64 => MoveType::U64, + TypeTag::U128 => MoveType::U128, + TypeTag::U256 => MoveType::U256, + TypeTag::Address => MoveType::Address, + TypeTag::Signer => MoveType::Signer, + TypeTag::Vector(v) => MoveType::Vector { + items: Box::new(MoveType::from(v.as_ref())), + }, + TypeTag::Struct(v) => MoveType::Struct((&**v).into()), + } + } +} + +impl TryFrom for TypeTag { + type Error = EncodingError; + + fn try_from(tag: MoveType) -> Result { + let ret = match tag { + MoveType::Bool => TypeTag::Bool, + MoveType::U8 => TypeTag::U8, + MoveType::U16 => TypeTag::U16, + MoveType::U32 => TypeTag::U32, + MoveType::U64 => TypeTag::U64, + MoveType::U128 => TypeTag::U128, + MoveType::U256 => TypeTag::U256, + MoveType::Address => TypeTag::Address, + MoveType::Signer => TypeTag::Signer, + MoveType::Vector { items } => TypeTag::Vector(Box::new((*items).try_into()?)), + MoveType::Struct(v) => TypeTag::Struct(Box::new(v.try_into()?)), + MoveType::GenericTypeParam { index: _ } => TypeTag::Address, // Dummy type, allows for Object + _ => { + return Err(EncodingError::InvalidInput); + }, + }; + Ok(ret) + } +} diff --git a/rust/chains/tw_aptos/src/constants.rs b/rust/chains/tw_aptos/src/constants.rs index 714c1a2f782..2f2996ccceb 100644 --- a/rust/chains/tw_aptos/src/constants.rs +++ b/rust/chains/tw_aptos/src/constants.rs @@ -2,6 +2,11 @@ // // Copyright © 2017 Trust Wallet. +use move_core_types::{ident_str, identifier::IdentStr}; + pub const GAS_UNIT_PRICE: u64 = 100; pub const MAX_GAS_AMOUNT: u64 = 100_000_000; pub const APTOS_SALT: &[u8] = b"APTOS::RawTransaction"; + +pub const OBJECT_MODULE: &IdentStr = ident_str!("object"); +pub const OBJECT_STRUCT: &IdentStr = ident_str!("Object"); diff --git a/rust/chains/tw_aptos/src/lib.rs b/rust/chains/tw_aptos/src/lib.rs index 5388e03f4e7..1407bdd73c0 100644 --- a/rust/chains/tw_aptos/src/lib.rs +++ b/rust/chains/tw_aptos/src/lib.rs @@ -4,6 +4,7 @@ pub mod address; pub mod aptos_move_packages; +pub mod aptos_move_types; pub mod constants; pub mod entry; mod serde_helper; diff --git a/rust/chains/tw_aptos/src/transaction_builder.rs b/rust/chains/tw_aptos/src/transaction_builder.rs index deae84c8008..5449f86608d 100644 --- a/rust/chains/tw_aptos/src/transaction_builder.rs +++ b/rust/chains/tw_aptos/src/transaction_builder.rs @@ -5,8 +5,9 @@ use crate::address::from_account_error; use crate::aptos_move_packages::{ aptos_account_create_account, aptos_account_transfer, aptos_account_transfer_coins, - coin_transfer, managed_coin_register, token_transfers_cancel_offer_script, - token_transfers_claim_script, token_transfers_offer_script, + coin_transfer, fungible_asset_transfer, managed_coin_register, + token_transfers_cancel_offer_script, token_transfers_claim_script, + token_transfers_offer_script, }; use crate::constants::{GAS_UNIT_PRICE, MAX_GAS_AMOUNT}; use crate::liquid_staking::{ @@ -147,13 +148,27 @@ impl TransactionFactory { convert_proto_struct_tag_to_type_tag(func)?, ) }, + OneOftransaction_payload::fungible_asset_transfer(fungible_asset_transfer) => factory + .fungible_asset_transfer( + AccountAddress::from_str(&fungible_asset_transfer.metadata_address) + .map_err(from_account_error) + .into_tw() + .context("Invalid metadata address")?, + AccountAddress::from_str(&fungible_asset_transfer.to) + .map_err(from_account_error) + .into_tw() + .context("Invalid destination address")?, + fungible_asset_transfer.amount, + ), OneOftransaction_payload::None => { let is_blind_sign = !input.any_encoded.is_empty(); let v = serde_json::from_str::(&input.any_encoded) .into_tw() .context("Error decoding 'SigningInput::any_encoded' as JSON")?; + let abi = + serde_json::from_str::(&input.abi).unwrap_or(serde_json::json!([])); if is_blind_sign { - let entry_function = EntryFunction::try_from(v)?; + let entry_function = EntryFunction::parse_with_abi(v, abi)?; Ok(factory.payload(TransactionPayload::EntryFunction(entry_function))) } else { SigningError::err(SigningErrorType::Error_input_parse) @@ -251,6 +266,15 @@ impl TransactionFactory { Ok(self.payload(coin_transfer(coin_type, to, amount)?)) } + pub fn fungible_asset_transfer( + &self, + metadata_address: AccountAddress, + to: AccountAddress, + amount: u64, + ) -> SigningResult { + Ok(self.payload(fungible_asset_transfer(metadata_address, to, amount)?)) + } + pub fn implicitly_create_user_and_coins_transfer( &self, to: AccountAddress, diff --git a/rust/chains/tw_aptos/src/transaction_payload.rs b/rust/chains/tw_aptos/src/transaction_payload.rs index d0105bc949d..6b1c78e4cd3 100644 --- a/rust/chains/tw_aptos/src/transaction_payload.rs +++ b/rust/chains/tw_aptos/src/transaction_payload.rs @@ -2,16 +2,22 @@ // // Copyright © 2017 Trust Wallet. +use crate::aptos_move_types::MoveType; +use crate::constants::{OBJECT_MODULE, OBJECT_STRUCT}; use crate::serde_helper::vec_bytes; +use move_core_types::account_address::AccountAddress; use move_core_types::identifier::Identifier; -use move_core_types::language_storage::{ModuleId, StructTag, TypeTag}; +use move_core_types::language_storage::{ModuleId, StructTag, TypeTag, CORE_CODE_ADDRESS}; use move_core_types::parser::parse_transaction_argument; use move_core_types::transaction_argument::TransactionArgument; +use move_core_types::u256; +use move_core_types::value::{MoveStruct, MoveStructLayout, MoveTypeLayout, MoveValue}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use std::default::Default; use std::str::FromStr; use tw_coin_entry::error::prelude::*; +use tw_encoding::hex::DecodeHex; use tw_encoding::{bcs, EncodingError, EncodingResult}; use tw_memory::Data; use tw_proto::Aptos; @@ -57,23 +63,41 @@ impl TryFrom for EntryFunction { type Error = EntryFunctionError; fn try_from(value: Value) -> EntryFunctionResult { + Self::parse_with_abi(value, json!([])) + } +} + +impl EntryFunction { + pub fn parse_with_abi(value: Value, abi: Value) -> EntryFunctionResult { let function_str = value["function"] .as_str() .ok_or(EntryFunctionError::MissingFunctionName)?; let tag = StructTag::from_str(function_str) .map_err(|_| EntryFunctionError::InvalidFunctionName)?; + let abi = abi + .as_array() + .ok_or(EntryFunctionError::MissingTypeArguments)?; + let get_abi_str = + |index: usize| -> Option { abi.get(index)?.as_str().map(|s| s.to_string()) }; + let args = value["arguments"] .as_array() .ok_or(EntryFunctionError::MissingArguments)? .iter() - .map(|element| { + .enumerate() + .map(|(index, element)| { let arg_str = element.to_string(); - let arg = parse_transaction_argument( - arg_str.trim_start_matches('"').trim_end_matches('"'), - ) - .map_err(|_| EntryFunctionError::InvalidArguments)?; - serialize_argument(&arg).map_err(EntryFunctionError::from) + let arg_str = arg_str.trim_start_matches('"').trim_end_matches('"'); + + if let Some(abi_str) = get_abi_str(index) { + let arg = convert_to_move_value(&abi_str, element.clone())?; + bcs::encode(&arg).map_err(EntryFunctionError::from) + } else { + let arg = parse_transaction_argument(arg_str) + .map_err(|_| EntryFunctionError::InvalidArguments)?; + serialize_argument(&arg).map_err(EntryFunctionError::from) + } }) .collect::>>()?; @@ -99,6 +123,215 @@ impl TryFrom for EntryFunction { } } +fn convert_to_move_value(abi_str: &str, element: Value) -> EntryFunctionResult { + let move_type: MoveType = abi_str + .parse() + .map_err(|_| EntryFunctionError::InvalidTypeArguments)?; + let type_tag: TypeTag = move_type + .try_into() + .map_err(|_| EntryFunctionError::InvalidTypeArguments)?; + // Taken from: https://github.com/aptos-labs/aptos-core/blob/aaa3514c8ee4e5d38b89d916eadff7286a42e040/api/types/src/convert.rs#L845-L872 + let layout = match type_tag { + TypeTag::Struct(ref boxed_struct) => { + // The current framework can't handle generics, so we handle this here + if boxed_struct.address == AccountAddress::ONE + && boxed_struct.module.as_ident_str() == OBJECT_MODULE + && boxed_struct.name.as_ident_str() == OBJECT_STRUCT + { + // Objects are just laid out as an address + MoveTypeLayout::Address + } else { + // For all other structs, use their set layout + build_type_layout(&type_tag)? + } + }, + _ => build_type_layout(&type_tag)?, + }; + parse_argument(&layout, element).map_err(|_| EntryFunctionError::InvalidArguments) +} + +fn build_type_layout(t: &TypeTag) -> EncodingResult { + use TypeTag::*; + Ok(match t { + Bool => MoveTypeLayout::Bool, + U8 => MoveTypeLayout::U8, + U64 => MoveTypeLayout::U64, + U128 => MoveTypeLayout::U128, + Address => MoveTypeLayout::Address, + Vector(elem_t) => MoveTypeLayout::Vector(Box::new(build_type_layout(elem_t)?)), + Struct(s) => MoveTypeLayout::Struct(build_struct_layout(s)?), + U16 => MoveTypeLayout::U16, + U32 => MoveTypeLayout::U32, + U256 => MoveTypeLayout::U256, + Signer => Err(EncodingError::InvalidInput)?, + }) +} + +fn build_struct_layout(s: &StructTag) -> EncodingResult { + let type_arguments = s + .type_params + .iter() + .map(build_type_layout) + .collect::>>()?; + if type_arguments.is_empty() { + Ok(MoveStructLayout::WithTypes { + type_: s.clone(), + fields: vec![], + }) + } else { + Ok(MoveStructLayout::Runtime(type_arguments)) + } +} + +fn parse_argument(layout: &MoveTypeLayout, val: Value) -> EncodingResult { + let val_str = val + .to_string() + .trim_start_matches('"') + .trim_end_matches('"') + .to_string(); + Ok(match layout { + MoveTypeLayout::Bool => MoveValue::Bool( + val_str + .parse::() + .map_err(|_| EncodingError::InvalidInput)?, + ), + MoveTypeLayout::U8 => MoveValue::U8( + val_str + .parse::() + .map_err(|_| EncodingError::InvalidInput)?, + ), + MoveTypeLayout::U16 => MoveValue::U16( + val_str + .parse::() + .map_err(|_| EncodingError::InvalidInput)?, + ), + MoveTypeLayout::U32 => MoveValue::U32( + val_str + .parse::() + .map_err(|_| EncodingError::InvalidInput)?, + ), + MoveTypeLayout::U64 => MoveValue::U64( + val_str + .parse::() + .map_err(|_| EncodingError::InvalidInput)?, + ), + MoveTypeLayout::U128 => MoveValue::U128( + val_str + .parse::() + .map_err(|_| EncodingError::InvalidInput)?, + ), + MoveTypeLayout::U256 => MoveValue::U256( + val_str + .parse::() + .map_err(|_| EncodingError::InvalidInput)?, + ), + MoveTypeLayout::Address => MoveValue::Address( + val_str + .parse::() + .map_err(|_| EncodingError::InvalidInput)?, + ), + MoveTypeLayout::Vector(item_layout) => parse_vector_argument(item_layout.as_ref(), val)?, + MoveTypeLayout::Struct(struct_layout) => parse_struct_argument(struct_layout, val)?, + // Some values, e.g., signer or ones with custom serialization + // (native), are not stored to storage and so we do not expect + // to see them here. + MoveTypeLayout::Signer => { + return Err(EncodingError::InvalidInput); + }, + }) +} + +fn parse_vector_argument(layout: &MoveTypeLayout, val: Value) -> EncodingResult { + if matches!(layout, MoveTypeLayout::U8) { + Ok(MoveValue::Vector( + val.as_str() + .ok_or(EncodingError::InvalidInput)? + .decode_hex() + .map_err(|_| EncodingError::InvalidInput)? + .into_iter() + .map(MoveValue::U8) + .collect::>(), + )) + } else { + let val = trim_if_needed(val)?; + if let Value::Array(list) = val { + let vals = list + .into_iter() + .map(|v| parse_argument(layout, v).map_err(|_| EncodingError::InvalidInput)) + .collect::>()?; + Ok(MoveValue::Vector(vals)) + } else { + Err(EncodingError::InvalidInput) + } + } +} + +// Inspired from: https://github.com/aptos-labs/aptos-core/blob/aaa3514c8ee4e5d38b89d916eadff7286a42e040/api/types/src/convert.rs#L924 +// However, we expect struct with strings and unnamed fields while the original code expects struct with named fields. +// This is because the original code uses a module resolver internally to obtain the struct types and we don't have that here. +// In order to be able to accept that as an API, we need to change the code to accept struct with unnamed fields. +fn parse_struct_argument(layout: &MoveStructLayout, val: Value) -> EncodingResult { + let field_layouts = match layout { + MoveStructLayout::Runtime(fields) => fields, + MoveStructLayout::WithTypes { type_, .. } => { + if is_utf8_string(type_) { + let string = val.as_str().ok_or(EncodingError::InvalidInput)?; + return Ok(new_vm_utf8_string(string)); + } else { + return Err(EncodingError::InvalidInput); + } + }, + _ => return Err(EncodingError::InvalidInput), + }; + let val = trim_if_needed(val)?; + let field_values = if let Value::Array(fields) = val { + fields + } else { + return Err(EncodingError::InvalidInput); + }; + let fields = field_layouts + .iter() + .zip(field_values.into_iter()) + .map(|(field_layout, value)| { + let move_value = parse_argument(field_layout, value)?; + Ok(move_value) + }) + .collect::>()?; + + Ok(MoveValue::Struct(MoveStruct::Runtime(fields))) +} + +fn trim_if_needed(val: Value) -> EncodingResult { + if val.is_string() { + let val_str = val.as_str().ok_or(EncodingError::InvalidInput)?; + let val_str = val_str + .trim_start_matches('"') + .trim_end_matches('"') + .to_string(); + let val: Value = serde_json::from_str(&val_str).map_err(|_| EncodingError::InvalidInput)?; + Ok(val) + } else { + Ok(val) + } +} + +fn is_utf8_string(st: &StructTag) -> bool { + st.address == CORE_CODE_ADDRESS + && st.name.to_string() == "String" + && st.module.to_string() == "string" +} + +fn new_vm_utf8_string(string: &str) -> MoveValue { + let byte_vector = MoveValue::Vector( + string + .as_bytes() + .iter() + .map(|byte| MoveValue::U8(*byte)) + .collect(), + ); + MoveValue::Struct(MoveStruct::Runtime(vec![byte_vector])) +} + fn serialize_argument(arg: &TransactionArgument) -> EncodingResult { match arg { TransactionArgument::U8(v) => bcs::encode(v), @@ -273,4 +506,141 @@ mod tests { let serialized = bcs::encode(&tp).unwrap(); assert_eq!(hex::encode(serialized, false), expected_serialized); } + + #[test] + fn test_payload_with_vector_of_u8() { + let payload_value: Value = json!({ + "type":"entry_function_payload", + "function":"0x9770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da3::controller::deposit", + "type_arguments":["0x1::aptos_coin::AptosCoin"], + "arguments":[ + "0x010302" + ] + }); + let abi = r#"[ + "vector" + ]"#; + let abi_value: Value = serde_json::from_str(abi).unwrap(); + let v = EntryFunction::parse_with_abi(payload_value.clone(), abi_value).unwrap(); + let v = bcs::decode::>(&v.args[0]).unwrap(); + assert_eq!(v, vec![1u8, 3u8, 2u8]); + } + + #[test] + fn test_payload_with_vector_of_u64() { + let payload_value: Value = json!({ + "type":"entry_function_payload", + "function":"0x9770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da3::controller::deposit", + "type_arguments":["0x1::aptos_coin::AptosCoin"], + "arguments":[ + "[\"1\", \"2\", \"3\"]" + ] + }); + let abi = r#"[ + "vector" + ]"#; + let abi_value: Value = serde_json::from_str(abi).unwrap(); + let v = EntryFunction::parse_with_abi(payload_value.clone(), abi_value).unwrap(); + let v = bcs::decode::>(&v.args[0]).unwrap(); + assert_eq!(v, vec![1u64, 2u64, 3u64]); + } + + #[test] + fn test_payload_with_vector_of_vector() { + let payload_value: Value = json!({ + "type":"entry_function_payload", + "function":"0x9770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da3::controller::deposit", + "type_arguments":["0x1::aptos_coin::AptosCoin"], + "arguments":[ + "[\"0x4d61696e204163636f756e74\",\"0x6112\"]" + ] + }); + let abi = r#"[ + "vector>" + ]"#; + let abi_value: Value = serde_json::from_str(abi).unwrap(); + let v = EntryFunction::parse_with_abi(payload_value.clone(), abi_value).unwrap(); + let v = bcs::decode::>>(&v.args[0]).unwrap(); + assert_eq!( + hex::encode(v[0].clone(), true), + "0x4d61696e204163636f756e74" + ); + assert_eq!(hex::encode(v[1].clone(), true), "0x6112"); + } + + #[test] + fn test_payload_with_struct_string() { + let payload_value: Value = json!({ + "type":"entry_function_payload", + "function":"0x9770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da3::controller::deposit", + "type_arguments":["0x1::aptos_coin::AptosCoin"], + "arguments":[ + "123" + ] + }); + let abi = r#"[ + "0x1::string::String" + ]"#; + let abi_value: Value = serde_json::from_str(abi).unwrap(); + let v = EntryFunction::parse_with_abi(payload_value.clone(), abi_value).unwrap(); + let v = bcs::decode::(&v.args[0]).unwrap(); + assert_eq!(v, "123"); + } + + #[test] + fn test_payload_with_struct() { + let payload_value: Value = json!({ + "type":"entry_function_payload", + "function":"0x9770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da3::controller::deposit", + "type_arguments":["0x1::aptos_coin::AptosCoin"], + "arguments":[ + "[10]" + ] + }); + let abi = r#"[ + "0x1::coin::Coin" + ]"#; + let abi_value: Value = serde_json::from_str(abi).unwrap(); + let v = EntryFunction::parse_with_abi(payload_value.clone(), abi_value).unwrap(); + let v = bcs::decode::(&v.args[0]).unwrap(); + assert_eq!(v, 10u64); + } + + fn assert_value_conversion(abi_str: &str, v: V, expected: MoveValue) { + let vm_value = convert_to_move_value(abi_str, json!(v)).unwrap(); + assert_eq!(vm_value, expected); + } + + #[test] + fn test_value_conversion() { + assert_value_conversion("u8", 1i32, MoveValue::U8(1)); + assert_value_conversion("u64", "1", MoveValue::U64(1)); + assert_value_conversion("u128", "1", MoveValue::U128(1)); + assert_value_conversion("bool", true, MoveValue::Bool(true)); + + let address = AccountAddress::from_hex_literal("0x1").unwrap(); + assert_value_conversion("address", "0x1", MoveValue::Address(address)); + + assert_value_conversion("0x1::string::String", "hello", new_vm_utf8_string("hello")); + + assert_value_conversion( + "vector", + "0x0102", + MoveValue::Vector(vec![MoveValue::U8(1), MoveValue::U8(2)]), + ); + assert_value_conversion( + "vector", + ["1", "2"], + MoveValue::Vector(vec![MoveValue::U64(1), MoveValue::U64(2)]), + ); + + assert_value_conversion( + "0x1::guid::ID", // As we do not have access to the module resolver, the types of the struct should be provided as params + ["1", "0x1"], + MoveValue::Struct(MoveStruct::Runtime(vec![ + MoveValue::U64(1), + MoveValue::Address(address), + ])), + ); + } } diff --git a/rust/chains/tw_aptos/tests/signer.rs b/rust/chains/tw_aptos/tests/signer.rs index e69c6894839..eb4f41c8232 100644 --- a/rust/chains/tw_aptos/tests/signer.rs +++ b/rust/chains/tw_aptos/tests/signer.rs @@ -29,6 +29,12 @@ pub struct TokenTransfer { tag: TypeTag, } +pub struct FungibleAssetTransfer { + metadata_address: String, + to: String, + amount: u64, +} + pub struct RegisterToken { coin_type: TypeTag, } @@ -41,6 +47,7 @@ pub enum OpsDetails { TokenTransfer(TokenTransfer), ImplicitTokenTransfer(TokenTransfer), NftOps(NftOperation), + FungibleAssetTransfer(FungibleAssetTransfer), } fn setup_proto_transaction<'a>( @@ -53,6 +60,7 @@ fn setup_proto_transaction<'a>( timestamp: u64, gas_unit_price: u64, any_encoded: &'a str, + abi: &'a str, ops_details: Option, ) -> SigningInput<'a> { let private = hex::decode(keypair_str).unwrap(); @@ -134,6 +142,20 @@ fn setup_proto_transaction<'a>( panic!("Unsupported arguments") } }, + "fungible_asset_transfer" => { + if let OpsDetails::FungibleAssetTransfer(fungible_asset_transfer) = ops_details.unwrap() + { + Proto::mod_SigningInput::OneOftransaction_payload::fungible_asset_transfer( + Proto::FungibleAssetTransferMessage { + to: fungible_asset_transfer.to.into(), + amount: fungible_asset_transfer.amount, + metadata_address: fungible_asset_transfer.metadata_address.into(), + }, + ) + } else { + panic!("Unsupported arguments") + } + }, "blind_sign_json" => Proto::mod_SigningInput::OneOftransaction_payload::None, _ => Proto::mod_SigningInput::OneOftransaction_payload::None, }; @@ -148,6 +170,7 @@ fn setup_proto_transaction<'a>( private_key: private.into(), any_encoded: any_encoded.into(), transaction_payload: payload, + abi: abi.into(), }; input @@ -194,6 +217,7 @@ fn test_aptos_sign_transaction_transfer() { 3664390082, 100, "", + "", Some(OpsDetails::Transfer(Transfer { to: "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30".to_string(), amount: 1000, @@ -237,6 +261,7 @@ fn test_aptos_sign_create_account() { 3664390082, 100, "", + "", Some(OpsDetails::AccountCreation(AccountCreation { to: "0x3aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30e".to_string(), })), @@ -279,6 +304,7 @@ fn test_aptos_sign_coin_transfer() { 3664390082, 100, "", + "", Some(OpsDetails::TokenTransfer(TokenTransfer { transfer: Transfer { to: "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30" @@ -316,6 +342,52 @@ fn test_aptos_sign_coin_transfer() { }"#); } +// Successfully broadcasted https://explorer.aptoslabs.com/txn/0x475fc97bcba87907166a720676e1b2f5320e613fd13014df37dcf17b09ff0e98/balanceChange?network=mainnet +#[test] +fn test_aptos_sign_fungible_asset_transfer() { + let input = setup_proto_transaction( + "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "fungible_asset_transfer", + 74, // Sequence number + 1, + 20, + 1736060099, + 100, + "", + "", + Some(OpsDetails::FungibleAssetTransfer(FungibleAssetTransfer { + metadata_address: "0x2ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12" + .to_string(), + to: "0x2d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e52".to_string(), + amount: 100000000, + })), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304a00000000000000020000000000000000000000000000000000000000000000000000000000000001167072696d6172795f66756e6769626c655f73746f7265087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010e66756e6769626c655f6173736574084d657461646174610003202ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12202d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e520800e1f5050000000014000000000000006400000000000000c32c7a670000000001", // Expected raw transaction bytes + "2d4c5cbb710b6ef92813597054dbf8d3014529a7d85f6393f01e2a3e978c461c6aa656475b98b453ed3faebf7aa1fdd912bfc59a0c1b6fc44330793994b2e40c", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304a00000000000000020000000000000000000000000000000000000000000000000000000000000001167072696d6172795f66756e6769626c655f73746f7265087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010e66756e6769626c655f6173736574084d657461646174610003202ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12202d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e520800e1f5050000000014000000000000006400000000000000c32c7a6700000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c402d4c5cbb710b6ef92813597054dbf8d3014529a7d85f6393f01e2a3e978c461c6aa656475b98b453ed3faebf7aa1fdd912bfc59a0c1b6fc44330793994b2e40c", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "1736060099", + "gas_unit_price": "100", + "max_gas_amount": "20", + "payload": { + "arguments": ["0x2ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12","0x2d92d71078f11d923c2b703b95a288c0e2ae63c0d29154e6278bf8004f9b4e52", "100000000"], + "function": "0x1::primary_fungible_store::transfer", + "type": "entry_function_payload", + "type_arguments": ["0x1::fungible_asset::Metadata"] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "74", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x2d4c5cbb710b6ef92813597054dbf8d3014529a7d85f6393f01e2a3e978c461c6aa656475b98b453ed3faebf7aa1fdd912bfc59a0c1b6fc44330793994b2e40c", + "type": "ed25519_signature" + } + }"#); +} + // Successfully broadcasted https://explorer.aptoslabs.com/txn/0x197d40ea12e2bfc65a0a913b9f4ca3b0b0208fe0c1514d3d55cef3d5bcf25211?network=mainnet #[test] fn test_implicit_aptos_sign_coin_transfer() { @@ -328,6 +400,7 @@ fn test_implicit_aptos_sign_coin_transfer() { 3664390082, 100, "", + "", Some(OpsDetails::ImplicitTokenTransfer(TokenTransfer { transfer: Transfer { to: "0xb7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c".to_string(), amount: 10000 }, tag: TypeTag::from_str("0xe9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9::mee_coin::MeeCoin").unwrap() })), ); let output = Signer::sign_proto(input); @@ -368,6 +441,7 @@ fn test_aptos_nft_offer() { 3664390082, 100, "", + "", Some(OpsDetails::NftOps(NftOperation::Offer(Offer { receiver: AccountAddress::from_str( "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", @@ -424,6 +498,7 @@ fn test_aptos_cancel_nft_offer() { 3664390082, 100, "", + "", Some(OpsDetails::NftOps(NftOperation::Cancel(Offer { receiver: AccountAddress::from_str( "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", @@ -480,6 +555,7 @@ fn test_aptos_nft_claim() { 3664390082, 100, "", + "", Some(OpsDetails::NftOps(NftOperation::Claim(Claim { sender: AccountAddress::from_str( "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", @@ -534,6 +610,7 @@ fn test_aptos_register_token() { 3664390082, 100, "", + "", Some(OpsDetails::RegisterToken(RegisterToken { coin_type: TypeTag::from_str("0xe4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379::move_coin::MoveCoin").unwrap() })), ); let output = Signer::sign_proto(input); @@ -574,6 +651,7 @@ fn test_aptos_tortuga_stake() { 1670240203, 100, "", + "", Some(OpsDetails::LiquidStakingOps(LiquidStakingOperation::Stake( Stake { amount: 100000000, @@ -624,6 +702,7 @@ fn test_aptos_tortuga_unstake() { 1670304949, 120, "", + "", Some(OpsDetails::LiquidStakingOps( LiquidStakingOperation::Unstake(Unstake { amount: 99178100, @@ -674,6 +753,7 @@ fn test_aptos_tortuga_claim() { 1682066783, 148, "", + "", Some(OpsDetails::LiquidStakingOps(LiquidStakingOperation::Claim( liquid_staking::Claim { idx: 0, @@ -737,6 +817,7 @@ fn test_aptos_blind_sign() { ], "type": "entry_function_payload" }"#, + "", None, ); let output = Signer::sign_proto(input); @@ -772,6 +853,68 @@ fn test_aptos_blind_sign() { }"#); } +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x1ee2aa55382bf6b5a9f7a7f2b2066e16979489c6b2868704a2cf2c482f12b5ca/payload?network=mainnet +#[test] +fn test_aptos_blind_sign_with_abi() { + let input = setup_proto_transaction( + "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "blind_sign_json", + 69, // Sequence number + 1, + 50000, + 1735902711, + 100, + r#"{ + "function": "0x9770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da3::controller::deposit", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin" + ], + "arguments": [ + "0x4d61696e204163636f756e74", + "10000000", + false + ], + "type": "entry_function_payload" + }"#, + r#"[ + "vector", + "u64", + "bool" + ]"#, + None, + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304500000000000000029770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da30a636f6e74726f6c6c6572076465706f736974010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00030d0c4d61696e204163636f756e74088096980000000000010050c30000000000006400000000000000f7c577670000000001", // Expected raw transaction bytes + "13dcf1636abd31996729ded4d3bf56e9c7869a7188df4f185cbcce42f0dc74b6e1b54d31703ee3babbea2ef72b3338b8c2866cec68cbd761ccc7f80910124304", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304500000000000000029770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da30a636f6e74726f6c6c6572076465706f736974010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00030d0c4d61696e204163636f756e74088096980000000000010050c30000000000006400000000000000f7c5776700000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c4013dcf1636abd31996729ded4d3bf56e9c7869a7188df4f185cbcce42f0dc74b6e1b54d31703ee3babbea2ef72b3338b8c2866cec68cbd761ccc7f80910124304", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "1735902711", + "gas_unit_price": "100", + "max_gas_amount": "50000", + "payload": { + "function": "0x9770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da3::controller::deposit", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin" + ], + "arguments": [ + "0x4d61696e204163636f756e74", + "10000000", + false + ], + "type": "entry_function_payload" + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "69", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x13dcf1636abd31996729ded4d3bf56e9c7869a7188df4f185cbcce42f0dc74b6e1b54d31703ee3babbea2ef72b3338b8c2866cec68cbd761ccc7f80910124304", + "type": "ed25519_signature" + } + }"#); +} + // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x25dca849cb4ebacbff223139f7ad5d24c37c225d9506b8b12a925de70429e685/payload #[test] fn test_aptos_blind_sign_staking() { @@ -792,6 +935,7 @@ fn test_aptos_blind_sign_staking() { ], "type": "entry_function_payload" }"#, + "", None, ); let output = Signer::sign_proto(input); @@ -841,6 +985,7 @@ fn test_aptos_blind_sign_unstaking() { ], "type": "entry_function_payload" }"#, + "", None, ); let output = Signer::sign_proto(input); diff --git a/rust/chains/tw_bitcoin/Cargo.toml b/rust/chains/tw_bitcoin/Cargo.toml index 1d5571814b0..ce4101b5407 100644 --- a/rust/chains/tw_bitcoin/Cargo.toml +++ b/rust/chains/tw_bitcoin/Cargo.toml @@ -4,7 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] -bitcoin = { version = "0.30.0", features = ["rand-std"] } +bitcoin = { version = "0.30.0", features = ["rand-std", "serde"] } +itertools = "0.10.5" +lazy_static = "1.4.0" secp256k1 = { version = "0.27.0", features = ["global-context", "rand-std"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/rust/chains/tw_bitcoin/src/babylon/conditions.rs b/rust/chains/tw_bitcoin/src/babylon/conditions.rs new file mode 100644 index 00000000000..e31b97ddb92 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/conditions.rs @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::babylon::multi_sig_ordered::MultiSigOrderedKeys; +use tw_hash::H32; +use tw_keypair::schnorr; +use tw_utxo::script::standard_script::opcodes::*; +use tw_utxo::script::Script; + +const VERIFY: bool = true; +const NO_VERIFY: bool = false; + +/// https://github.com/babylonchain/babylon/blob/dev/docs/transaction-impl-spec.md#op_return-output-description +/// ```txt +/// OP_RETURN OP_DATA_71 +/// ``` +pub fn new_op_return_script( + tag: &H32, + version: u8, + staker_key: &schnorr::XOnlyPublicKey, + finality_provider_key: &schnorr::XOnlyPublicKey, + locktime: u16, +) -> Script { + let mut buf = Vec::with_capacity(71); + buf.extend_from_slice(tag.as_slice()); + buf.push(version); + buf.extend_from_slice(staker_key.bytes().as_slice()); + buf.extend_from_slice(finality_provider_key.bytes().as_slice()); + buf.extend_from_slice(&locktime.to_be_bytes()); + + let mut s = Script::new(); + s.push(OP_RETURN); + s.push_slice(&buf); + s +} + +/// The timelock path locks the staker's Bitcoin for a pre-determined number of Bitcoin blocks. +/// https://github.com/babylonchain/babylon/blob/dev/docs/staking-script.md#1-timelock-path +/// +/// ```txt +/// OP_CHECKSIGVERIFY OP_CHECKSEQUENCEVERIFY +/// ``` +pub fn new_timelock_script(staker_key: &schnorr::XOnlyPublicKey, locktime: u16) -> Script { + let mut s = Script::with_capacity(64); + append_single_sig(&mut s, staker_key, VERIFY); + s.push_int(locktime as i64); + s.push(OP_CHECKSEQUENCEVERIFY); + s +} + +/// The unbonding path allows the staker to on-demand unlock their locked Bitcoin before the timelock expires. +/// https://github.com/babylonchain/babylon/blob/dev/docs/staking-script.md#2-unbonding-path +/// +/// ```txt +/// OP_CHECKSIGVERIFY +/// OP_CHECKSIG OP_CHECKSIGADD ... OP_CHECKSIGADD +/// OP_NUMEQUAL +/// ``` +pub fn new_unbonding_script( + staker_key: &schnorr::XOnlyPublicKey, + covenants: &MultiSigOrderedKeys, +) -> Script { + let mut s = Script::with_capacity(64); + append_single_sig(&mut s, staker_key, VERIFY); + // Covenant multisig is always last in script so we do not run verify and leave + // last value on the stack. If we do not leave at least one element on the stack + // script will always error. + append_multi_sig( + &mut s, + covenants.public_keys_ordered(), + covenants.quorum(), + NO_VERIFY, + ); + s +} + +/// The slashing path is utilized for punishing finality providers and their delegators in the case of double signing. +/// https://github.com/babylonchain/babylon/blob/dev/docs/staking-script.md#3-slashing-path +/// +/// ```txt +/// OP_CHECKSIGVERIFY +/// OP_CHECKSIG OP_CHECKSIGADD ... OP_CHECKSIGADD +/// 1 OP_NUMEQUAL +/// OP_CHECKSIG OP_CHECKSIGADD ... OP_CHECKSIGADD +/// OP_NUMEQUAL +/// ``` +pub fn new_slashing_script( + staker_key: &schnorr::XOnlyPublicKey, + finality_providers_keys: &MultiSigOrderedKeys, + covenants: &MultiSigOrderedKeys, +) -> Script { + let mut s = Script::with_capacity(64); + append_single_sig(&mut s, staker_key, VERIFY); + // We need to run verify to clear the stack, as finality provider multisig is in the middle of the script. + append_multi_sig( + &mut s, + finality_providers_keys.public_keys_ordered(), + finality_providers_keys.quorum(), + VERIFY, + ); + // Covenant multisig is always last in script so we do not run verify and leave + // last value on the stack. If we do not leave at least one element on the stack + // script will always error. + append_multi_sig( + &mut s, + covenants.public_keys_ordered(), + covenants.quorum(), + NO_VERIFY, + ); + s +} + +fn append_single_sig(dst: &mut Script, key: &schnorr::XOnlyPublicKey, verify: bool) { + dst.push_slice(key.bytes().as_slice()); + if verify { + dst.push(OP_CHECKSIGVERIFY); + } else { + dst.push(OP_CHECKSIG); + } +} + +/// Creates a multisig script with given keys and signer threshold to +/// successfully execute script. +/// It validates whether threshold is not greater than number of keys. +/// If there is only one key provided it will return single key sig script. +/// Note: It is up to the caller to ensure that the keys are unique and sorted. +fn append_multi_sig( + dst: &mut Script, + pubkeys: &[schnorr::XOnlyPublicKey], + quorum: u32, + verify: bool, +) { + if pubkeys.len() == 1 { + return append_single_sig(dst, &pubkeys[0], verify); + } + + for (i, pk_xonly) in pubkeys.iter().enumerate() { + dst.push_slice(pk_xonly.bytes().as_slice()); + if i == 0 { + dst.push(OP_CHECKSIG); + } else { + dst.push(OP_CHECKSIGADD); + } + } + + dst.push_int(quorum as i64); + if verify { + dst.push(OP_NUMEQUALVERIFY); + } else { + dst.push(OP_NUMEQUAL); + } +} diff --git a/rust/chains/tw_bitcoin/src/babylon/mod.rs b/rust/chains/tw_bitcoin/src/babylon/mod.rs new file mode 100644 index 00000000000..c59063ae77d --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/mod.rs @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod conditions; +pub mod multi_sig_ordered; +pub mod proto_builder; +pub mod spending_data; +pub mod spending_info; +pub mod tx_builder; diff --git a/rust/chains/tw_bitcoin/src/babylon/multi_sig_ordered.rs b/rust/chains/tw_bitcoin/src/babylon/multi_sig_ordered.rs new file mode 100644 index 00000000000..9faa7256f18 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/multi_sig_ordered.rs @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use itertools::Itertools; +use std::collections::BTreeMap; +use tw_coin_entry::error::prelude::{ + OrTWError, ResultContext, SigningError, SigningErrorType, SigningResult, +}; +use tw_keypair::schnorr; +use tw_utxo::signature::BitcoinSchnorrSignature; + +type OptionalSignature = Option; +type PubkeySigMap = BTreeMap; + +pub struct MultiSigOrderedKeys { + pks: Vec, + quorum: u32, +} + +impl MultiSigOrderedKeys { + /// Sort the keys in lexicographical order. + pub fn new(mut pks: Vec, quorum: u32) -> SigningResult { + if pks.is_empty() { + return SigningError::err(SigningErrorType::Error_invalid_params) + .context("No public keys provided"); + } + + if pks.len() < quorum as usize { + return SigningError::err(SigningErrorType::Error_invalid_params).context( + "Required number of valid signers is greater than number of provided keys", + ); + } + + // TODO it's not optimal to use a `HashSet` because the keys are sorted already. + if !pks.iter().all_unique() { + return SigningError::err(SigningErrorType::Error_invalid_params) + .context("Public keys must be unique"); + } + + pks.sort(); + Ok(MultiSigOrderedKeys { pks, quorum }) + } + + pub fn public_keys_ordered(&self) -> &[schnorr::XOnlyPublicKey] { + &self.pks + } + + pub fn quorum(&self) -> u32 { + self.quorum + } + + pub fn with_partial_signatures<'a, I>(self, sigs: I) -> SigningResult + where + I: IntoIterator, + { + let mut pk_sig_map = MultiSigOrdered::checked(self.pks, self.quorum); + pk_sig_map.set_partial_signatures(sigs)?; + pk_sig_map.check_quorum()?; + Ok(pk_sig_map) + } +} + +#[derive(Clone, Debug)] +pub struct MultiSigOrdered { + pk_sig_map: PubkeySigMap, + quorum: u32, +} + +impl MultiSigOrdered { + /// `pk_sig_map` and `quorum` must be checked at [`MultiSigOrderedKeys::new`] already. + fn checked(pks: Vec, quorum: u32) -> Self { + let mut pk_sig_map = PubkeySigMap::new(); + + // Initialize the map with public keys and null signatures first. + for pk in pks { + pk_sig_map.insert(pk, None); + } + + MultiSigOrdered { pk_sig_map, quorum } + } + + fn set_partial_signatures<'a, I>(&mut self, sigs: I) -> SigningResult<()> + where + I: IntoIterator, + { + // Set the signatures for the specific public keys. + // There can be less signatures than public keys, but not less than `quorum`. + for (pk, sig) in sigs { + // Find the signature of the corresponding public key. + let pk_sig = self + .pk_sig_map + .get_mut(pk) + .or_tw_err(SigningErrorType::Error_invalid_params) + .context("Signature provided for an unknown public key")?; + + // Only one signature per public key allowed. + if pk_sig.is_some() { + return SigningError::err(SigningErrorType::Error_invalid_params) + .context("Duplicate public key"); + } + *pk_sig = Some(sig.clone()); + } + Ok(()) + } + + fn check_quorum(&self) -> SigningResult<()> { + let sig_num = self.pk_sig_map.values().filter(|sig| sig.is_some()).count(); + if sig_num < self.quorum as usize { + return SigningError::err(SigningErrorType::Error_invalid_params).context(format!( + "Number of signatures '{sig_num}' is less than quorum '{}'", + self.quorum + )); + } + Ok(()) + } + + /// Get signatures sorted by corresponding public keys in reverse lexicographical order + /// because the script interpreter will pop the left-most byte-array as the first stack element, + /// the second-left-most byte array as the second stack element, and so on. + /// + /// In other words, + /// `[SigN] [SigN-1] ... [Sig0]` + /// where the list `Sig0 ... SigN` are the Schnorr signatures corresponding to the public keys `Pk0 ... PkN`. + /// + /// https://gnusha.org/pi/bitcoindev/20220820134850.ofvz7225zwcyffit@artanis (=== Spending K-of-N Multisig outputs ===) + pub fn get_signatures_reverse_order(&self) -> Vec { + self.pk_sig_map + .iter() + .rev() + .map(|(_pk, sig)| sig.clone()) + .collect() + } +} diff --git a/rust/chains/tw_bitcoin/src/babylon/proto_builder/mod.rs b/rust/chains/tw_bitcoin/src/babylon/proto_builder/mod.rs new file mode 100644 index 00000000000..8f3f6224bd6 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/proto_builder/mod.rs @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::babylon::multi_sig_ordered::MultiSigOrderedKeys; +use crate::babylon::tx_builder::BabylonStakingParams; +use std::borrow::Cow; +use tw_coin_entry::error::prelude::*; +use tw_keypair::schnorr; +use tw_proto::BabylonStaking::Proto; +use tw_utxo::sighash::SighashType; +use tw_utxo::signature::BitcoinSchnorrSignature; + +pub mod output_protobuf; +pub mod utxo_protobuf; + +/// We always require only one finality provider to sign, +/// even if there are multiple finality providers in the script. +const FINALITY_PROVIDERS_QUORUM: u32 = 1; + +pub fn staking_params_from_proto( + params: &Option, +) -> SigningResult { + let params = params + .as_ref() + .or_tw_err(SigningErrorType::Error_invalid_params) + .context("No 'StakingInfo' params provided")?; + + let staker = schnorr::PublicKey::try_from(params.staker_public_key.as_ref()) + .into_tw() + .context("Invalid stakerPublicKey")?; + let staking_locktime: u16 = params + .staking_time + .try_into() + .tw_err(|_| SigningErrorType::Error_invalid_params) + .context("stakingTime cannot be greater than 65535")?; + + let covenants_pks = parse_schnorr_pks(¶ms.covenant_committee_public_keys) + .context("Invalid covenantCommitteePublicKeys")?; + let covenants = MultiSigOrderedKeys::new(covenants_pks, params.covenant_quorum) + .context("Invalid covenantCommitteePublicKeys")?; + + let finality_provider = parse_schnorr_pk(¶ms.finality_provider_public_key) + .context("Invalid finalityProviderPublicKey")?; + let finality_providers = + MultiSigOrderedKeys::new(vec![finality_provider], FINALITY_PROVIDERS_QUORUM) + .context("Invalid finalityProviderPublicKey")?; + + Ok(BabylonStakingParams { + staker, + staking_locktime, + finality_providers, + covenants, + }) +} + +pub fn parse_schnorr_pk(bytes: T) -> SigningResult +where + T: AsRef<[u8]>, +{ + schnorr::XOnlyPublicKey::try_from(bytes.as_ref()).into_tw() +} + +pub fn parse_schnorr_pks(pks: &[Cow<[u8]>]) -> SigningResult> { + pks.iter().map(parse_schnorr_pk).collect() +} + +pub fn parse_schnorr_pubkey_sig( + pubkey_sig: &Proto::PublicKeySignature, + sighash_ty: SighashType, +) -> SigningResult<(schnorr::XOnlyPublicKey, BitcoinSchnorrSignature)> { + let pk = parse_schnorr_pk(pubkey_sig.public_key.as_ref())?; + let sig = schnorr::Signature::try_from(pubkey_sig.signature.as_ref()) + .tw_err(|_| SigningErrorType::Error_invalid_params) + .context("Invalid signature")?; + let btc_sign = BitcoinSchnorrSignature::new(sig, sighash_ty)?; + Ok((pk, btc_sign)) +} diff --git a/rust/chains/tw_bitcoin/src/babylon/proto_builder/output_protobuf.rs b/rust/chains/tw_bitcoin/src/babylon/proto_builder/output_protobuf.rs new file mode 100644 index 00000000000..f7be9a9dbc9 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/proto_builder/output_protobuf.rs @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::babylon::proto_builder::{parse_schnorr_pk, staking_params_from_proto}; +use crate::babylon::tx_builder::output::BabylonOutputBuilder; +use crate::modules::tx_builder::output_protobuf::OutputProtobuf; +use tw_coin_entry::error::prelude::*; +use tw_hash::H32; +use tw_proto::BabylonStaking::Proto; +use tw_utxo::context::UtxoContext; +use tw_utxo::transaction::standard_transaction::TransactionOutput; + +pub trait BabylonOutputProtobuf { + fn babylon_staking( + &self, + timelock: &Proto::mod_OutputBuilder::StakingOutput, + ) -> SigningResult; + + fn babylon_staking_op_return( + &self, + timelock: &Proto::mod_OutputBuilder::OpReturn, + ) -> SigningResult; + + fn babylon_unbonding( + &self, + unbonding: &Proto::mod_OutputBuilder::UnbondingOutput, + ) -> SigningResult; +} + +impl<'a, Context: UtxoContext> BabylonOutputProtobuf for OutputProtobuf<'a, Context> { + fn babylon_staking( + &self, + staking: &Proto::mod_OutputBuilder::StakingOutput, + ) -> SigningResult { + let params = staking_params_from_proto(&staking.params)?; + self.prepare_builder()?.babylon_staking(params) + } + + fn babylon_staking_op_return( + &self, + op_return: &Proto::mod_OutputBuilder::OpReturn, + ) -> SigningResult { + let tag = H32::try_from(op_return.tag.as_ref()) + .tw_err(|_| SigningErrorType::Error_invalid_params) + .context("Expected exactly 4 bytes tag")?; + let staker = + parse_schnorr_pk(&op_return.staker_public_key).context("Invalid stakerPublicKey")?; + let staking_locktime: u16 = op_return + .staking_time + .try_into() + .tw_err(|_| SigningErrorType::Error_invalid_params) + .context("stakingTime cannot be greater than 65535")?; + let finality_provider = &parse_schnorr_pk(&op_return.finality_provider_public_key) + .context("Invalid finalityProviderPublicKeys")?; + + Ok(self.prepare_builder()?.babylon_staking_op_return( + &tag, + &staker, + finality_provider, + staking_locktime, + )) + } + + fn babylon_unbonding( + &self, + unbonding: &Proto::mod_OutputBuilder::UnbondingOutput, + ) -> SigningResult { + let params = staking_params_from_proto(&unbonding.params)?; + self.prepare_builder()?.babylon_unbonding(params) + } +} diff --git a/rust/chains/tw_bitcoin/src/babylon/proto_builder/utxo_protobuf.rs b/rust/chains/tw_bitcoin/src/babylon/proto_builder/utxo_protobuf.rs new file mode 100644 index 00000000000..bc12f4f9157 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/proto_builder/utxo_protobuf.rs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::babylon::proto_builder::{parse_schnorr_pubkey_sig, staking_params_from_proto}; +use crate::babylon::tx_builder::utxo::BabylonUtxoBuilder; +use crate::modules::tx_builder::utxo_protobuf::UtxoProtobuf; +use tw_coin_entry::error::prelude::*; +use tw_proto::BabylonStaking::Proto; +use tw_utxo::context::UtxoContext; +use tw_utxo::transaction::standard_transaction::TransactionInput; +use tw_utxo::transaction::UtxoToSign; + +pub trait BabylonUtxoProtobuf { + fn babylon_staking_timelock( + &self, + timelock: &Proto::mod_InputBuilder::StakingTimelockPath, + ) -> SigningResult<(TransactionInput, UtxoToSign)>; + + fn babylon_staking_unbonding( + &self, + unbonding: &Proto::mod_InputBuilder::StakingUnbondingPath, + ) -> SigningResult<(TransactionInput, UtxoToSign)>; + + fn babylon_staking_slashing( + &self, + slashing: &Proto::mod_InputBuilder::StakingSlashingPath, + ) -> SigningResult<(TransactionInput, UtxoToSign)>; + + fn babylon_unbonding_timelock( + &self, + timelock: &Proto::mod_InputBuilder::UnbondingTimelockPath, + ) -> SigningResult<(TransactionInput, UtxoToSign)>; + + fn babylon_unbonding_slashing( + &self, + slashing: &Proto::mod_InputBuilder::UnbondingSlashingPath, + ) -> SigningResult<(TransactionInput, UtxoToSign)>; +} + +impl<'a, Context: UtxoContext> BabylonUtxoProtobuf for UtxoProtobuf<'a, Context> { + fn babylon_staking_timelock( + &self, + timelock: &Proto::mod_InputBuilder::StakingTimelockPath, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let params = staking_params_from_proto(&timelock.params)?; + self.prepare_builder()? + .babylon_staking_timelock_path(params) + } + + fn babylon_staking_unbonding( + &self, + unbonding: &Proto::mod_InputBuilder::StakingUnbondingPath, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let params = staking_params_from_proto(&unbonding.params)?; + let sighash_ty = self.sighash_ty()?; + + let covenant_signatures = unbonding + .covenant_committee_signatures + .iter() + .map(|pk_sig| parse_schnorr_pubkey_sig(pk_sig, sighash_ty)) + .collect::>>()?; + self.prepare_builder()? + .babylon_staking_unbonding_path(params, &covenant_signatures) + } + + fn babylon_staking_slashing( + &self, + _slashing: &Proto::mod_InputBuilder::StakingSlashingPath, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + SigningError::err(SigningErrorType::Error_not_supported) + .context("'babylonStakingSlashing' is not supported at the moment") + } + + fn babylon_unbonding_timelock( + &self, + timelock: &Proto::mod_InputBuilder::UnbondingTimelockPath, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let params = staking_params_from_proto(&timelock.params)?; + self.prepare_builder()? + .babylon_unbonding_timelock_path(params) + } + + fn babylon_unbonding_slashing( + &self, + _slashing: &Proto::mod_InputBuilder::UnbondingSlashingPath, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + SigningError::err(SigningErrorType::Error_not_supported) + .context("'babylonUnbondingSlashing' is not supported at the moment") + } +} diff --git a/rust/chains/tw_bitcoin/src/babylon/spending_data.rs b/rust/chains/tw_bitcoin/src/babylon/spending_data.rs new file mode 100644 index 00000000000..5b50ad66e0b --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/spending_data.rs @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::babylon::multi_sig_ordered::MultiSigOrdered; +use tw_memory::Data; +use tw_utxo::script::standard_script::claims; +use tw_utxo::script::Script; +use tw_utxo::signature::BitcoinSchnorrSignature; +use tw_utxo::spending_data::{SchnorrSpendingDataConstructor, SpendingData}; + +#[derive(Clone, Debug)] +pub struct BabylonUnbondingPath { + unbonding_script: Script, + control_block: Data, + /// Signatures signed by covenant committees. + /// Sorted by covenant committees public keys in reverse order. + covenant_committee_signatures: MultiSigOrdered, +} + +impl BabylonUnbondingPath { + pub fn new( + unbonding_script: Script, + control_block: Data, + covenant_committee_signatures: MultiSigOrdered, + ) -> Self { + BabylonUnbondingPath { + unbonding_script, + control_block, + covenant_committee_signatures, + } + } +} + +impl SchnorrSpendingDataConstructor for BabylonUnbondingPath { + fn get_spending_data(&self, sig: &BitcoinSchnorrSignature) -> SpendingData { + // Covenant committee signatures are sorted by corresponding public keys in reverse lexicographical order. + // That's because the script interpreter will pop the left-most byte-array as the first stack element, + // the second-left-most byte array as the second stack element, and so on. + let mut unbonding_sigs = self + .covenant_committee_signatures + .get_signatures_reverse_order(); + // User's signature is always last. + unbonding_sigs.push(Some(sig.clone())); + + SpendingData { + script_sig: Script::default(), + witness: claims::new_p2tr_script_path( + &unbonding_sigs, + self.unbonding_script.clone(), + self.control_block.clone(), + ), + } + } +} diff --git a/rust/chains/tw_bitcoin/src/babylon/spending_info.rs b/rust/chains/tw_bitcoin/src/babylon/spending_info.rs new file mode 100644 index 00000000000..9257803e34d --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/spending_info.rs @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::babylon::conditions; +use crate::babylon::tx_builder::BabylonStakingParams; +use bitcoin::hashes::Hash; +use lazy_static::lazy_static; +use tw_coin_entry::error::prelude::*; +use tw_hash::{H256, H264}; +use tw_keypair::schnorr; +use tw_utxo::script::Script; + +lazy_static! { + /// Point with unknown discrete logarithm defined in: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs + /// using it as internal public key effectively disables taproot key spends. + pub static ref UNSPENDABLE_KEY_PATH_BYTES: H264 = + H264::from("0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"); + pub static ref UNSPENDABLE_KEY_PATH: schnorr::PublicKey = + schnorr::PublicKey::try_from(UNSPENDABLE_KEY_PATH_BYTES.as_slice()) + .expect("Expected a valid unspendable key path"); + pub static ref UNSPENDABLE_KEY_PATH_XONLY: bitcoin::key::UntweakedPublicKey = + bitcoin::key::UntweakedPublicKey::from_slice(UNSPENDABLE_KEY_PATH.x_only().bytes().as_slice()) + .expect("Expected a valid unspendable key path"); +} + +pub struct StakingSpendInfo { + timelock_script: Script, + unbonding_script: Script, + slashing_script: Script, + spend_info: bitcoin::taproot::TaprootSpendInfo, +} + +impl StakingSpendInfo { + pub fn new(params: &BabylonStakingParams) -> SigningResult { + let staker_xonly = params.staker.x_only(); + + let timelock_script = + conditions::new_timelock_script(&staker_xonly, params.staking_locktime); + let unbonding_script = conditions::new_unbonding_script(&staker_xonly, ¶ms.covenants); + let slashing_script = conditions::new_slashing_script( + &staker_xonly, + ¶ms.finality_providers, + ¶ms.covenants, + ); + + // IMPORTANT - order and leaf depths are important! + let spend_info = bitcoin::taproot::TaprootBuilder::new() + .add_leaf(2, timelock_script.clone().into()) + .expect("Leaf added at a valid depth") + .add_leaf(2, unbonding_script.clone().into()) + .expect("Leaf added at a valid depth") + .add_leaf(1, slashing_script.clone().into()) + .expect("Leaf added at a valid depth") + .finalize(secp256k1::SECP256K1, *UNSPENDABLE_KEY_PATH_XONLY) + .expect("Expected a valid Taproot tree"); + + Ok(StakingSpendInfo { + timelock_script, + unbonding_script, + slashing_script, + spend_info, + }) + } + + pub fn merkle_root(&self) -> SigningResult { + merkle_root(&self.spend_info) + } + + pub fn timelock_script(&self) -> &Script { + &self.timelock_script + } + + pub fn unbonding_script(&self) -> &Script { + &self.unbonding_script + } + + pub fn slashing_script(&self) -> &Script { + &self.slashing_script + } + + pub fn timelock_control_block(&self) -> SigningResult { + control_block(&self.spend_info, &self.timelock_script) + } + + pub fn unbonding_control_block(&self) -> SigningResult { + control_block(&self.spend_info, &self.unbonding_script) + } + + pub fn slashing_control_block(&self) -> SigningResult { + control_block(&self.spend_info, &self.slashing_script) + } +} + +pub struct UnbondingSpendInfo { + timelock_script: Script, + slashing_script: Script, + spend_info: bitcoin::taproot::TaprootSpendInfo, +} + +impl UnbondingSpendInfo { + pub fn new(params: &BabylonStakingParams) -> SigningResult { + let staker_xonly = params.staker.x_only(); + + let timelock_script = + conditions::new_timelock_script(&staker_xonly, params.staking_locktime); + let slashing_script = conditions::new_slashing_script( + &staker_xonly, + ¶ms.finality_providers, + ¶ms.covenants, + ); + + // IMPORTANT - order and leaf depths are important! + let spend_info = bitcoin::taproot::TaprootBuilder::new() + .add_leaf(1, slashing_script.clone().into()) + .expect("Leaf added at a valid depth") + .add_leaf(1, timelock_script.clone().into()) + .expect("Leaf added at a valid depth") + .finalize(secp256k1::SECP256K1, *UNSPENDABLE_KEY_PATH_XONLY) + .expect("Expected a valid Taproot tree"); + + Ok(UnbondingSpendInfo { + timelock_script, + slashing_script, + spend_info, + }) + } + + pub fn merkle_root(&self) -> SigningResult { + merkle_root(&self.spend_info) + } + + pub fn timelock_script(&self) -> &Script { + &self.timelock_script + } + + pub fn slashing_script(&self) -> &Script { + &self.slashing_script + } + + pub fn timelock_control_block(&self) -> SigningResult { + control_block(&self.spend_info, &self.timelock_script) + } + + pub fn slashing_control_block(&self) -> SigningResult { + control_block(&self.spend_info, &self.slashing_script) + } +} + +fn control_block( + spend_info: &bitcoin::taproot::TaprootSpendInfo, + script: &Script, +) -> SigningResult { + let script = bitcoin::script::ScriptBuf::from_bytes(script.to_vec()); + spend_info + .control_block(&(script, bitcoin::taproot::LeafVersion::TapScript)) + .or_tw_err(SigningErrorType::Error_internal) + .context("'TaprootSpendInfo::control_block' is None") +} + +fn merkle_root(spend_info: &bitcoin::taproot::TaprootSpendInfo) -> SigningResult { + spend_info + .merkle_root() + .map(|root| H256::from(root.to_byte_array())) + .or_tw_err(SigningErrorType::Error_internal) + .context("No merkle root of the Babylon Staking transaction spend info") +} diff --git a/rust/chains/tw_bitcoin/src/babylon/tx_builder/mod.rs b/rust/chains/tw_bitcoin/src/babylon/tx_builder/mod.rs new file mode 100644 index 00000000000..f2ad565b831 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/tx_builder/mod.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::babylon::multi_sig_ordered::MultiSigOrderedKeys; +use tw_keypair::schnorr; + +pub mod output; +pub mod utxo; + +/// Unbonding parameters are the same as Staking except `staking_locktime` means an unbonding timelock. +pub type BabylonUnbondingParams = BabylonStakingParams; + +pub struct BabylonStakingParams { + pub staker: schnorr::PublicKey, + pub staking_locktime: u16, + pub finality_providers: MultiSigOrderedKeys, + pub covenants: MultiSigOrderedKeys, +} diff --git a/rust/chains/tw_bitcoin/src/babylon/tx_builder/output.rs b/rust/chains/tw_bitcoin/src/babylon/tx_builder/output.rs new file mode 100644 index 00000000000..e8b9d61ba1f --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/tx_builder/output.rs @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::babylon; +use crate::babylon::tx_builder::{BabylonStakingParams, BabylonUnbondingParams}; +use tw_coin_entry::error::prelude::*; +use tw_hash::H32; +use tw_keypair::schnorr; +use tw_utxo::script::standard_script::conditions; +use tw_utxo::transaction::standard_transaction::builder::OutputBuilder; +use tw_utxo::transaction::standard_transaction::TransactionOutput; + +pub const VERSION: u8 = 0; + +/// An extension of the [`OutputBuilder`] with Babylon BTC Staking outputs. +pub trait BabylonOutputBuilder: Sized { + /// Create a Staking Output. + fn babylon_staking(self, params: BabylonStakingParams) -> SigningResult; + + /// Creates an OP_RETURN output used to identify the staking transaction among other transactions in the Bitcoin ledger. + fn babylon_staking_op_return( + self, + tag: &H32, + staker_key: &schnorr::XOnlyPublicKey, + finality_provider_key: &schnorr::XOnlyPublicKey, + staking_locktime: u16, + ) -> TransactionOutput; + + fn babylon_unbonding(self, params: BabylonUnbondingParams) -> SigningResult; +} + +impl BabylonOutputBuilder for OutputBuilder { + fn babylon_staking(self, params: BabylonStakingParams) -> SigningResult { + let spend_info = babylon::spending_info::StakingSpendInfo::new(¶ms)?; + let merkle_root = spend_info.merkle_root()?; + + Ok(TransactionOutput { + value: self.get_amount(), + script_pubkey: conditions::new_p2tr_script_path( + // Using an unspendable key as a P2TR internal public key effectively disables taproot key spends. + &babylon::spending_info::UNSPENDABLE_KEY_PATH.compressed(), + &merkle_root, + ), + }) + } + + fn babylon_staking_op_return( + self, + tag: &H32, + staker_key: &schnorr::XOnlyPublicKey, + finality_provider_key: &schnorr::XOnlyPublicKey, + staking_locktime: u16, + ) -> TransactionOutput { + let op_return = babylon::conditions::new_op_return_script( + tag, + VERSION, + staker_key, + finality_provider_key, + staking_locktime, + ); + TransactionOutput { + value: self.get_amount(), + script_pubkey: op_return, + } + } + + fn babylon_unbonding(self, params: BabylonUnbondingParams) -> SigningResult { + let spend_info = babylon::spending_info::UnbondingSpendInfo::new(¶ms)?; + let merkle_root = spend_info.merkle_root()?; + + Ok(TransactionOutput { + value: self.get_amount(), + script_pubkey: conditions::new_p2tr_script_path( + // Using an unspendable key as a P2TR internal public key effectively disables taproot key spends. + &babylon::spending_info::UNSPENDABLE_KEY_PATH.compressed(), + &merkle_root, + ), + }) + } +} diff --git a/rust/chains/tw_bitcoin/src/babylon/tx_builder/utxo.rs b/rust/chains/tw_bitcoin/src/babylon/tx_builder/utxo.rs new file mode 100644 index 00000000000..293dc160241 --- /dev/null +++ b/rust/chains/tw_bitcoin/src/babylon/tx_builder/utxo.rs @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::babylon; +use crate::babylon::spending_data; +use crate::babylon::spending_info::UNSPENDABLE_KEY_PATH; +use crate::babylon::tx_builder::{BabylonStakingParams, BabylonUnbondingParams}; +use tw_coin_entry::error::prelude::*; +use tw_keypair::schnorr; +use tw_utxo::signature::BitcoinSchnorrSignature; +use tw_utxo::spending_data::SpendingDataConstructor; +use tw_utxo::transaction::standard_transaction::builder::UtxoBuilder; +use tw_utxo::transaction::standard_transaction::TransactionInput; +use tw_utxo::transaction::UtxoToSign; + +/// An extension of the [`UtxoBuilder`] with Babylon BTC Staking outputs. +pub trait BabylonUtxoBuilder: Sized { + /// Spend a Staking Output via timelock path (staking time expired). + /// In other words, create a Withdraw transaction. + fn babylon_staking_timelock_path( + self, + params: BabylonStakingParams, + ) -> SigningResult<(TransactionInput, UtxoToSign)>; + + /// Spend a Staking Output via unbonding path. + /// In other words, create an Unbonding transaction. + fn babylon_staking_unbonding_path( + self, + params: BabylonStakingParams, + covenant_committee_signatures: &[(schnorr::XOnlyPublicKey, BitcoinSchnorrSignature)], + ) -> SigningResult<(TransactionInput, UtxoToSign)>; + + fn babylon_unbonding_timelock_path( + self, + params: BabylonUnbondingParams, + ) -> SigningResult<(TransactionInput, UtxoToSign)>; +} + +impl BabylonUtxoBuilder for UtxoBuilder { + fn babylon_staking_timelock_path( + self, + params: BabylonStakingParams, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let spend_info = babylon::spending_info::StakingSpendInfo::new(¶ms)?; + + let control_block = spend_info.timelock_control_block()?.serialize(); + let merkle_root = spend_info.merkle_root()?; + let timelock_script = spend_info.timelock_script().clone(); + + self.p2tr_script_path() + .reveal_script_pubkey(timelock_script) + // Staker is responsible to sign the UTXO. + .spender_public_key(¶ms.staker) + // Babylon Staking or Unbonding output was created using an unspendable internal public key, + // that means taproot key spends is disabled. + .restore_prevout_script_pubkey(&UNSPENDABLE_KEY_PATH, &merkle_root) + .control_block(control_block) + .build() + } + + fn babylon_staking_unbonding_path( + self, + params: BabylonStakingParams, + covenant_committee_signatures: &[(schnorr::XOnlyPublicKey, BitcoinSchnorrSignature)], + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let spend_info = babylon::spending_info::StakingSpendInfo::new(¶ms)?; + let signatures = params + .covenants + .with_partial_signatures(covenant_committee_signatures)?; + let unbonding_script = spend_info.unbonding_script(); + + let unbonding_control_block = spend_info.unbonding_control_block()?.serialize(); + let spending_data_ctor = + SpendingDataConstructor::schnorr(spending_data::BabylonUnbondingPath::new( + unbonding_script.clone(), + unbonding_control_block.clone(), + signatures, + )); + + let merkle_root = spend_info.merkle_root()?; + + self.p2tr_script_path() + .reveal_script_pubkey(unbonding_script.clone()) + // Staker is responsible to sign the UTXO. + .spender_public_key(¶ms.staker) + // Babylon Staking or Unbonding output was created using an unspendable internal public key, + // that means taproot key spends is disabled. + .restore_prevout_script_pubkey(&UNSPENDABLE_KEY_PATH, &merkle_root) + .control_block(unbonding_control_block) + // For Babylon Unbonding path we use a custom spending data constructor. + .custom_spending_data_ctor(spending_data_ctor) + .build() + } + + fn babylon_unbonding_timelock_path( + self, + params: BabylonUnbondingParams, + ) -> SigningResult<(TransactionInput, UtxoToSign)> { + let spend_info = babylon::spending_info::UnbondingSpendInfo::new(¶ms)?; + + let control_block = spend_info.timelock_control_block()?.serialize(); + let merkle_root = spend_info.merkle_root()?; + let timelock_script = spend_info.timelock_script().clone(); + + self.p2tr_script_path() + .reveal_script_pubkey(timelock_script.clone()) + // Staker is responsible to sign the UTXO. + .spender_public_key(¶ms.staker) + // Babylon Staking or Unbonding output was created using an unspendable internal public key, + // that means taproot key spends is disabled. + .restore_prevout_script_pubkey(&UNSPENDABLE_KEY_PATH, &merkle_root) + .control_block(control_block) + .build() + } +} diff --git a/rust/chains/tw_bitcoin/src/lib.rs b/rust/chains/tw_bitcoin/src/lib.rs index 6f1db4b73ee..d4f6d0206ee 100644 --- a/rust/chains/tw_bitcoin/src/lib.rs +++ b/rust/chains/tw_bitcoin/src/lib.rs @@ -2,6 +2,7 @@ // // Copyright © 2017 Trust Wallet. +pub mod babylon; pub mod context; pub mod entry; pub mod modules; diff --git a/rust/chains/tw_bitcoin/src/modules/tx_builder/output_protobuf.rs b/rust/chains/tw_bitcoin/src/modules/tx_builder/output_protobuf.rs index 7b503a0f925..fcdb1801da5 100644 --- a/rust/chains/tw_bitcoin/src/modules/tx_builder/output_protobuf.rs +++ b/rust/chains/tw_bitcoin/src/modules/tx_builder/output_protobuf.rs @@ -2,6 +2,7 @@ // // Copyright © 2017 Trust Wallet. +use crate::babylon::proto_builder::output_protobuf::BabylonOutputProtobuf; use crate::modules::tx_builder::BitcoinChainInfo; use std::marker::PhantomData; use std::str::FromStr; @@ -50,6 +51,11 @@ impl<'a, Context: UtxoContext> OutputProtobuf<'a, Context> { }, BuilderType::brc20_inscribe(ref inscription) => self.brc20_inscribe(inscription), BuilderType::op_return(ref data) => self.op_return(data), + BuilderType::babylon_staking(ref staking) => self.babylon_staking(staking), + BuilderType::babylon_staking_op_return(ref op_return) => { + self.babylon_staking_op_return(op_return) + }, + BuilderType::babylon_unbonding(ref unbonding) => self.babylon_unbonding(unbonding), BuilderType::None => SigningError::err(SigningErrorType::Error_invalid_params) .context("No Output Builder type provided"), }, diff --git a/rust/chains/tw_bitcoin/src/modules/tx_builder/utxo_protobuf.rs b/rust/chains/tw_bitcoin/src/modules/tx_builder/utxo_protobuf.rs index aa21a02f41b..d64c1cbe08d 100644 --- a/rust/chains/tw_bitcoin/src/modules/tx_builder/utxo_protobuf.rs +++ b/rust/chains/tw_bitcoin/src/modules/tx_builder/utxo_protobuf.rs @@ -2,6 +2,7 @@ // // Copyright © 2017 Trust Wallet. +use crate::babylon::proto_builder::utxo_protobuf::BabylonUtxoProtobuf; use crate::modules::tx_builder::public_keys::PublicKeys; use crate::modules::tx_builder::script_parser::{StandardScript, StandardScriptParser}; use crate::modules::tx_builder::BitcoinChainInfo; @@ -56,6 +57,21 @@ impl<'a, Context: UtxoContext> UtxoProtobuf<'a, Context> { BuilderType::p2tr_key_path(ref key_path) => self.p2tr_key_path(key_path), // BuilderType::p2tr_script_path(ref script) => self.p2tr_script_path(script), BuilderType::brc20_inscribe(ref inscription) => self.brc20_inscribe(inscription), + BuilderType::babylon_staking_timelock_path(ref timelock) => { + self.babylon_staking_timelock(timelock) + }, + BuilderType::babylon_staking_unbonding_path(ref unbonding) => { + self.babylon_staking_unbonding(unbonding) + }, + BuilderType::babylon_staking_slashing_path(ref slashing) => { + self.babylon_staking_slashing(slashing) + }, + BuilderType::babylon_unbonding_timelock_path(ref timelock) => { + self.babylon_unbonding_timelock(timelock) + }, + BuilderType::babylon_unbonding_slashing_path(ref slashing) => { + self.babylon_unbonding_slashing(slashing) + }, BuilderType::None => SigningError::err(SigningErrorType::Error_invalid_params) .context("No Input Builder type provided"), }, @@ -174,7 +190,7 @@ impl<'a, Context: UtxoContext> UtxoProtobuf<'a, Context> { pub fn prepare_builder(&self) -> SigningResult { let OutPoint { hash, index } = parse_out_point(&self.input.out_point)?; - let sighash_ty = SighashType::from_u32(self.input.sighash_type)?; + let sighash_ty = self.sighash_ty()?; if self.input.value < 0 { return SigningError::err(SigningErrorType::Error_invalid_utxo_amount) @@ -197,6 +213,10 @@ impl<'a, Context: UtxoContext> UtxoProtobuf<'a, Context> { .sighash_type(sighash_ty)) } + pub fn sighash_ty(&self) -> SigningResult { + SighashType::from_u32(self.input.sighash_type) + } + /// Tries to convert [`Proto::PublicKeyOrHash`] to [`Hash`]. /// Please note `P2PKH` and `P2WPKH` use the same `ripemd(sha256(x))` hash function. fn get_ecdsa_pubkey_from_proto( diff --git a/rust/chains/tw_bitcoin/tests/babylon_staking.rs b/rust/chains/tw_bitcoin/tests/babylon_staking.rs new file mode 100644 index 00000000000..801d7cfc4fe --- /dev/null +++ b/rust/chains/tw_bitcoin/tests/babylon_staking.rs @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use bitcoin::hashes::Hash; +use bitcoin::taproot::TaprootBuilder; +use bitcoin::ScriptBuf; +use secp256k1::{PublicKey, SECP256K1}; +use tw_bitcoin::babylon::conditions; +use tw_bitcoin::babylon::multi_sig_ordered::MultiSigOrderedKeys; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_keypair::schnorr; + +const BABYLON_MERKLE_ROOTS: &str = include_str!("data/babylon_staking_merkle_roots.json"); +const BABYLON_TRANSACTIONS: &str = include_str!("data/babylon_staking_transactions.json"); + +fn parse_pk(value: &serde_json::Value) -> schnorr::XOnlyPublicKey { + let pk = value.as_str().unwrap().decode_hex().unwrap(); + schnorr::PublicKey::try_from(pk.as_slice()) + .unwrap() + .x_only() +} + +fn parse_pks(value: &serde_json::Value) -> Vec { + value.as_array().unwrap().iter().map(parse_pk).collect() +} + +#[test] +fn test_babylon_scripts() { + let input: serde_json::Value = serde_json::from_str(BABYLON_TRANSACTIONS).unwrap(); + + for test in input.as_array().unwrap() { + let name = test["name"].as_str().unwrap(); + let params = &test["parameters"]; + let expected = &test["expected"]; + + let covenant_public_keys = parse_pks(¶ms["covenant_public_keys"]); + let convenant_quorum = params["covenant_quorum"].as_u64().unwrap() as u32; + let finality_provider_public_keys = parse_pks(¶ms["finality_provider_public_keys"]); + let staker_public_key = parse_pk(¶ms["staker_public_key"]); + let staker_time = params["staking_time"].as_u64().unwrap() as u16; + + let covenants = MultiSigOrderedKeys::new(covenant_public_keys, convenant_quorum).unwrap(); + let finality_providers = + MultiSigOrderedKeys::new(finality_provider_public_keys, 1).unwrap(); + + let expected_timelock_script = expected["staking_transaction_timelock_script_hex"] + .as_str() + .unwrap(); + let expected_unbonding_script = expected["staking_transaction_unbonding_script_hex"] + .as_str() + .unwrap(); + let expected_slashing_script = expected["staking_transaction_slashing_script_hex"] + .as_str() + .unwrap(); + + let timelock_script = conditions::new_timelock_script(&staker_public_key, staker_time); + let unbonding_script = conditions::new_unbonding_script(&staker_public_key, &covenants); + let slashing_script = + conditions::new_slashing_script(&staker_public_key, &finality_providers, &covenants); + + assert_eq!( + timelock_script.as_slice().to_hex(), + expected_timelock_script, + "Test '{}' Invalid timelock script", + name + ); + assert_eq!( + unbonding_script.as_slice().to_hex(), + expected_unbonding_script, + "Test '{}' Invalid unbonding script", + name + ); + assert_eq!( + slashing_script.as_slice().to_hex(), + expected_slashing_script, + "Test '{}' Invalid slashing script", + name + ); + } +} + +#[test] +fn test_babylon_staking_taproot_tree() { + let input: serde_json::Value = serde_json::from_str(BABYLON_MERKLE_ROOTS).unwrap(); + + // Any public key. + let pubkey = "024000bd2c8b975d351c5f3a42618aca31e07e2b253fcd571e9630540a3cb6eafd" + .decode_hex() + .unwrap(); + let pubkey = PublicKey::from_slice(&pubkey).unwrap(); + let (xonly, _) = pubkey.x_only_public_key(); + + for test in input.as_array().unwrap() { + let timelock = test["timelock"].as_str().unwrap().decode_hex().unwrap(); + let unbond = test["unbond"].as_str().unwrap().decode_hex().unwrap(); + let slash = test["slash"].as_str().unwrap().decode_hex().unwrap(); + let root_hash = test["root_hash"].as_str().unwrap(); + + let timelock = ScriptBuf::from_bytes(timelock); + let unbond = ScriptBuf::from_bytes(unbond); + let slash = ScriptBuf::from_bytes(slash); + + let spend_info = TaprootBuilder::new() + .add_leaf(2, timelock) + .unwrap() + .add_leaf(2, unbond) + .unwrap() + .add_leaf(1, slash) + .unwrap() + .finalize(&SECP256K1, xonly) + .unwrap(); + + let mut actual_root = spend_info.merkle_root().unwrap().to_byte_array(); + actual_root.reverse(); + assert_eq!(actual_root.to_hex(), root_hash); + } +} diff --git a/rust/chains/tw_bitcoin/tests/data/babylon_staking_merkle_roots.json b/rust/chains/tw_bitcoin/tests/data/babylon_staking_merkle_roots.json new file mode 100644 index 00000000000..4e53cbf6eb3 --- /dev/null +++ b/rust/chains/tw_bitcoin/tests/data/babylon_staking_merkle_roots.json @@ -0,0 +1,722 @@ +[ + { + "timelock": "200ccdf306ec819daa0e37c612b60ac3ed7161da8c71bd879e21b0c4d89cf17655ad02e803b2", + "unbond": "200ccdf306ec819daa0e37c612b60ac3ed7161da8c71bd879e21b0c4d89cf17655ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "200ccdf306ec819daa0e37c612b60ac3ed7161da8c71bd879e21b0c4d89cf17655ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "909f7b753a4c8dc1e6d5236c59dd5ec870f46b853bd7473f75621505df8c8897" + }, + { + "timelock": "2039d43dd42cf3080dd75c95076e8909ac631d3251b6c0bec0c280ac93066e63d7ad02e803b2", + "unbond": "2039d43dd42cf3080dd75c95076e8909ac631d3251b6c0bec0c280ac93066e63d7ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2039d43dd42cf3080dd75c95076e8909ac631d3251b6c0bec0c280ac93066e63d7ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "4f988ae405b11d7f9116127f55168850b74a8ec15c73402283baa2c6ccf1c41e" + }, + { + "timelock": "2005c4a8f23df120b503e6dea8f281ced6d8e750c8a28f349742fd830be9f12cf3ad02e803b2", + "unbond": "2005c4a8f23df120b503e6dea8f281ced6d8e750c8a28f349742fd830be9f12cf3ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2005c4a8f23df120b503e6dea8f281ced6d8e750c8a28f349742fd830be9f12cf3ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "db97fb09eaf3547b1837c806c74546e7b7d99fccaf1131ba1adc699bf5a684ec" + }, + { + "timelock": "2005ca6027527ba006ce23fba369daa3cfa412b8ce755931b123ec1dc3f73a4770ad02e803b2", + "unbond": "2005ca6027527ba006ce23fba369daa3cfa412b8ce755931b123ec1dc3f73a4770ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2005ca6027527ba006ce23fba369daa3cfa412b8ce755931b123ec1dc3f73a4770ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "f63181ca1755460952ed5205e4ea2d617e4b22456d79c5e19b03f9a97bbd2d45" + }, + { + "timelock": "2013ca6721d19c4e43257aae38455bd4af2a1146304724c2bd3c8105064420cb06ad02e803b2", + "unbond": "2013ca6721d19c4e43257aae38455bd4af2a1146304724c2bd3c8105064420cb06ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2013ca6721d19c4e43257aae38455bd4af2a1146304724c2bd3c8105064420cb06ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "414fa954443067e41ea4e2c6b58dd26ae407421e09fd2b53cd540f57e3dd42fc" + }, + { + "timelock": "207ff701ec3f3a00b1677b5ac57892032be689fb227963be484cafd39d50540dadad02e803b2", + "unbond": "207ff701ec3f3a00b1677b5ac57892032be689fb227963be484cafd39d50540dadad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "207ff701ec3f3a00b1677b5ac57892032be689fb227963be484cafd39d50540dadad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "89c30df4587e8749dbb287c91cf49bfb9104128db331c08113d555fb79f06297" + }, + { + "timelock": "20ce447259457b41e38d7d5ac0229ff27aa19087c2e664e89d34645bbd1c6cf822ad02e803b2", + "unbond": "20ce447259457b41e38d7d5ac0229ff27aa19087c2e664e89d34645bbd1c6cf822ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20ce447259457b41e38d7d5ac0229ff27aa19087c2e664e89d34645bbd1c6cf822ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "321338a5ed106fcc00b778668beb0769697836a972e8929fe82fa8d2adcaef0c" + }, + { + "timelock": "20481e18a05e725cfcc1e9fb107268bd90f3dd5bea476dbddee94978c15f35c8a5ad02e803b2", + "unbond": "20481e18a05e725cfcc1e9fb107268bd90f3dd5bea476dbddee94978c15f35c8a5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20481e18a05e725cfcc1e9fb107268bd90f3dd5bea476dbddee94978c15f35c8a5ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "4dda164d280edc383144e3c985070f363d416ad7c8b53836388409bb74afae4f" + }, + { + "timelock": "20cc6a4ccf8f9a0fda5a5c9298d77730452a4ab305321e2f48f3662494fdc603cead02e803b2", + "unbond": "20cc6a4ccf8f9a0fda5a5c9298d77730452a4ab305321e2f48f3662494fdc603cead204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20cc6a4ccf8f9a0fda5a5c9298d77730452a4ab305321e2f48f3662494fdc603cead2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "73a7e10f7c5c2749e78a7d5c14b5a0d4c79579a47b621f7ea4c4308861b371ce" + }, + { + "timelock": "2019721d18cf18d53a151a41898f186ca7583debebb58b3dab947075de51844304ad02e803b2", + "unbond": "2019721d18cf18d53a151a41898f186ca7583debebb58b3dab947075de51844304ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2019721d18cf18d53a151a41898f186ca7583debebb58b3dab947075de51844304ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "8b5c55c50148edf3c3e16686eb909c4c02b04af0ab1f99ff6e5d9f78c57050b2" + }, + { + "timelock": "20566ff807867dfb4edad6c102cb0f3fac3b780e461a37fcf905d8d938dbb9a59dad02e803b2", + "unbond": "20566ff807867dfb4edad6c102cb0f3fac3b780e461a37fcf905d8d938dbb9a59dad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20566ff807867dfb4edad6c102cb0f3fac3b780e461a37fcf905d8d938dbb9a59dad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "6d256a204489db25fdd4e72d90a535b01a9a493e2d0a656dcb7709ac1befd73f" + }, + { + "timelock": "206a5ce87906ff32eff00096fb4dcf565d1e3b4583178d963202192b5b2d11f479ad02e803b2", + "unbond": "206a5ce87906ff32eff00096fb4dcf565d1e3b4583178d963202192b5b2d11f479ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "206a5ce87906ff32eff00096fb4dcf565d1e3b4583178d963202192b5b2d11f479ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "ec9797c32470b13cffdf057a8b592ed49969b3a04185df8234624a6ecb15677a" + }, + { + "timelock": "209f16b1f696c04b7eee01dd6d8fee62c35a091aae3aa1b5914abae91d7d9f2b52ad02e803b2", + "unbond": "209f16b1f696c04b7eee01dd6d8fee62c35a091aae3aa1b5914abae91d7d9f2b52ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "209f16b1f696c04b7eee01dd6d8fee62c35a091aae3aa1b5914abae91d7d9f2b52ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "388bc73a00ffdd56a692e1f5f1b3f65e013579d1dff09ea7f3a00a19f5831ac6" + }, + { + "timelock": "200bd371d96e48efdd3c405f027b08341c9278e2652fb1ad7edc2f8985389c1530ad02e803b2", + "unbond": "200bd371d96e48efdd3c405f027b08341c9278e2652fb1ad7edc2f8985389c1530ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "200bd371d96e48efdd3c405f027b08341c9278e2652fb1ad7edc2f8985389c1530ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "9915a9b5c8ebab144a6bcd19385fb748011de7b88ee8db432ec2408a7462676a" + }, + { + "timelock": "20dedc2e51daaaa19b337341b5fcd779c57b59bec18a31cf883fb2f489cf27c4b4ad02e803b2", + "unbond": "20dedc2e51daaaa19b337341b5fcd779c57b59bec18a31cf883fb2f489cf27c4b4ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20dedc2e51daaaa19b337341b5fcd779c57b59bec18a31cf883fb2f489cf27c4b4ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "8f0e14deaee7ea3d82f247e9e9f07c3767dc4857a1425aaf16979307e4154b2c" + }, + { + "timelock": "20190af905f87d3e035b1b8cf27fe6ba9f13512d446cdf98de2702f4c753555de7ad02e803b2", + "unbond": "20190af905f87d3e035b1b8cf27fe6ba9f13512d446cdf98de2702f4c753555de7ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20190af905f87d3e035b1b8cf27fe6ba9f13512d446cdf98de2702f4c753555de7ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "35a41b18bc48fe25e97c8960e37df41d9651af883b3582b1f61d4b86ed47f401" + }, + { + "timelock": "20f90ed4b62d8e09c8ec2c518caea33d59553b20ed942b7d01d197b5592eb32b11ad02e803b2", + "unbond": "20f90ed4b62d8e09c8ec2c518caea33d59553b20ed942b7d01d197b5592eb32b11ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20f90ed4b62d8e09c8ec2c518caea33d59553b20ed942b7d01d197b5592eb32b11ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "5cecdc132b60cba551a315217757412cd2c49d22e24113367305ed39edbf3235" + }, + { + "timelock": "209f6b3a98d800353c4daffa635fe7fc3ed48750680534a8cbb9f65aec090ba5fead02e803b2", + "unbond": "209f6b3a98d800353c4daffa635fe7fc3ed48750680534a8cbb9f65aec090ba5fead204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "209f6b3a98d800353c4daffa635fe7fc3ed48750680534a8cbb9f65aec090ba5fead2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "b3d2ad47db5cb358d517fd6a2e8d94ceb5252e41d1fa4e94974ee4affe5d90bf" + }, + { + "timelock": "20e24d0d721201a665f776e7a27ee378426843735599870adaa620fbd96a19fdc2ad02e803b2", + "unbond": "20e24d0d721201a665f776e7a27ee378426843735599870adaa620fbd96a19fdc2ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20e24d0d721201a665f776e7a27ee378426843735599870adaa620fbd96a19fdc2ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "7ef011c1b261b2653e65c7389e873e9661b8a0efa5b84f3ec9da41304f63b3c6" + }, + { + "timelock": "2079113365be732b6e3b09778728ed611d65c8e0bbbdaaa6e4655bfd92bc15b892ad02e803b2", + "unbond": "2079113365be732b6e3b09778728ed611d65c8e0bbbdaaa6e4655bfd92bc15b892ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2079113365be732b6e3b09778728ed611d65c8e0bbbdaaa6e4655bfd92bc15b892ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "1b7bffc2d328f90b21b1bc1d9e30d6425c74075258b3ac374b7e9f95777d876f" + }, + { + "timelock": "20baa1c3537d978c3656be7b6d69388ae8e8b33aefb2118a034cf688367f84d349ad02e803b2", + "unbond": "20baa1c3537d978c3656be7b6d69388ae8e8b33aefb2118a034cf688367f84d349ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20baa1c3537d978c3656be7b6d69388ae8e8b33aefb2118a034cf688367f84d349ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "ffe53943d93305cab5beb20adc2ace52981d1d561eaf93ac1aff70e1d81c1101" + }, + { + "timelock": "2022fe44bb002da47c44defa93bf6349691a2287a3feadecc96b19bfc404deddb1ad02e803b2", + "unbond": "2022fe44bb002da47c44defa93bf6349691a2287a3feadecc96b19bfc404deddb1ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2022fe44bb002da47c44defa93bf6349691a2287a3feadecc96b19bfc404deddb1ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "81ab658818dc213ca6802dbbf81dee2bde245749dc0e64c5e9166cee326ba285" + }, + { + "timelock": "20eaaebdf6f55e80dce34f0d22079d43b8a7b99d287a8959b3ab12fc23520738faad02e803b2", + "unbond": "20eaaebdf6f55e80dce34f0d22079d43b8a7b99d287a8959b3ab12fc23520738faad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20eaaebdf6f55e80dce34f0d22079d43b8a7b99d287a8959b3ab12fc23520738faad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "53f2604209cddf55b4ecdd8c2312d2180d8aa1a43c52000fdf2eb1adc420911b" + }, + { + "timelock": "20901a9a6088ed587093a0f180b0340a46d8b834820bfefe495fad1499170b8a82ad02e803b2", + "unbond": "20901a9a6088ed587093a0f180b0340a46d8b834820bfefe495fad1499170b8a82ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20901a9a6088ed587093a0f180b0340a46d8b834820bfefe495fad1499170b8a82ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "ca4c62a585d2509e4b58f9583037276e9a5b2815e79908ed7537b7f25faffa92" + }, + { + "timelock": "205c92c077fead6d6ef11a0f2b017d1b33c997ff54ffeb0e9e1aca50a12f3def37ad02e803b2", + "unbond": "205c92c077fead6d6ef11a0f2b017d1b33c997ff54ffeb0e9e1aca50a12f3def37ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "205c92c077fead6d6ef11a0f2b017d1b33c997ff54ffeb0e9e1aca50a12f3def37ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "74033064aa217cc572a55febe4f41ca649d6e9114336504403afb855d07e046b" + }, + { + "timelock": "2010e47e55f91b254c88df1cabb3aa1fc082892fbf6c55dbcb7a132a4f902a47a7ad02e803b2", + "unbond": "2010e47e55f91b254c88df1cabb3aa1fc082892fbf6c55dbcb7a132a4f902a47a7ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2010e47e55f91b254c88df1cabb3aa1fc082892fbf6c55dbcb7a132a4f902a47a7ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "9aeb6e1232b227872713306a76925e7fcc44f08e7cb27fe8f5e3ba9096556f68" + }, + { + "timelock": "2028ff0add7eab2e779c1ef9dc2553c37559b4fc7f746d1fb641e15f1125aea404ad02e803b2", + "unbond": "2028ff0add7eab2e779c1ef9dc2553c37559b4fc7f746d1fb641e15f1125aea404ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2028ff0add7eab2e779c1ef9dc2553c37559b4fc7f746d1fb641e15f1125aea404ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "0d2a34244379760fba4eec97b6e7eda1b9f6e6e61a32c484498b56654860e196" + }, + { + "timelock": "20f4155e5efd6dacabbf3d8a046fc12db3bae3b6ec0e92f91c6e38a9a9f79ba12cad02e803b2", + "unbond": "20f4155e5efd6dacabbf3d8a046fc12db3bae3b6ec0e92f91c6e38a9a9f79ba12cad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20f4155e5efd6dacabbf3d8a046fc12db3bae3b6ec0e92f91c6e38a9a9f79ba12cad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "46abeddfd47a942e214e345d31c6d301d9dd0bca98cf9882ea3fcc2d9515f23c" + }, + { + "timelock": "200d7aa0b6f2edad783937aa602c9f14ea7fc45ed8c1f68c530223ba77074705a5ad02e803b2", + "unbond": "200d7aa0b6f2edad783937aa602c9f14ea7fc45ed8c1f68c530223ba77074705a5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "200d7aa0b6f2edad783937aa602c9f14ea7fc45ed8c1f68c530223ba77074705a5ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "d76f4b2e77dd81e336bf44d2477a1ed26b1382371b57c31fd70d7dbadc8726dc" + }, + { + "timelock": "206743aa184a5b5c8443fe9fb0a4403911712efb5694e807c59b31e19100770591ad02e803b2", + "unbond": "206743aa184a5b5c8443fe9fb0a4403911712efb5694e807c59b31e19100770591ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "206743aa184a5b5c8443fe9fb0a4403911712efb5694e807c59b31e19100770591ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "727e7fc99d217d51479fb8ede481031e2129f7daf77379f9b91bfdac15d423a6" + }, + { + "timelock": "2029b9404f75307c067270d1f3d9200cb6dfc5754814a2a168d212764984c5a3f3ad02e803b2", + "unbond": "2029b9404f75307c067270d1f3d9200cb6dfc5754814a2a168d212764984c5a3f3ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2029b9404f75307c067270d1f3d9200cb6dfc5754814a2a168d212764984c5a3f3ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "bc44c6d623f1be68406442ed9bd1d851fd42e11dc164b61db423155a93483856" + }, + { + "timelock": "2043e13092bccd4fcf1baa175c4392fe83e26e4e831d867e6ecaf3872843fa9effad02e803b2", + "unbond": "2043e13092bccd4fcf1baa175c4392fe83e26e4e831d867e6ecaf3872843fa9effad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2043e13092bccd4fcf1baa175c4392fe83e26e4e831d867e6ecaf3872843fa9effad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "a7243cef3f66a5d21740f9f208d174f0cfd677fc203f1b96060d62bd7e694c48" + }, + { + "timelock": "20cae6dae566624eef92152a2d3c3754e09fdbf5cded31efb9355dd18376c7e907ad02e803b2", + "unbond": "20cae6dae566624eef92152a2d3c3754e09fdbf5cded31efb9355dd18376c7e907ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20cae6dae566624eef92152a2d3c3754e09fdbf5cded31efb9355dd18376c7e907ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "84c92fdb0bb15ae24c6e84b40ac8f17abe9d2951cc2a3dbfc09658d2c37d9826" + }, + { + "timelock": "20ea24599975c726b4db869351bd413f4300bcea7e8006e74730e6ba17b516bbc0ad02e803b2", + "unbond": "20ea24599975c726b4db869351bd413f4300bcea7e8006e74730e6ba17b516bbc0ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20ea24599975c726b4db869351bd413f4300bcea7e8006e74730e6ba17b516bbc0ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "1d07ea64db8a780a94ee0d3598462ac179349dc9fb7c68177f9d06987c319edf" + }, + { + "timelock": "206795d66b42f2c9e46158a90d7f33e1e3c13009d30c67da371041f252a796440ead02e803b2", + "unbond": "206795d66b42f2c9e46158a90d7f33e1e3c13009d30c67da371041f252a796440ead204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "206795d66b42f2c9e46158a90d7f33e1e3c13009d30c67da371041f252a796440ead2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "b9f447e2534232ce777d87199ef32e26f2e7c7745faac986d5262b016ff23176" + }, + { + "timelock": "206f487d35f5fd28ba9959db650d8583846cccc795f59b79f233eb7fd36e2b3009ad02e803b2", + "unbond": "206f487d35f5fd28ba9959db650d8583846cccc795f59b79f233eb7fd36e2b3009ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "206f487d35f5fd28ba9959db650d8583846cccc795f59b79f233eb7fd36e2b3009ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "d8ec8cd4af186cc9c399ef834597ec002e3528894111902601e96606032449bc" + }, + { + "timelock": "208eedbf83f4bf8ba6adeb82868b5c76b48e98bec4c6770d3f76d7c68b729c5f04ad02e803b2", + "unbond": "208eedbf83f4bf8ba6adeb82868b5c76b48e98bec4c6770d3f76d7c68b729c5f04ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "208eedbf83f4bf8ba6adeb82868b5c76b48e98bec4c6770d3f76d7c68b729c5f04ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "416603edfd59abf4360ad14e3502773541e4161ee160e0c7d9015e8be53a22d8" + }, + { + "timelock": "20c83a76b4ef4f706d87905d38f7c390044be80404a19770f94acc8be4e29df7b4ad02e803b2", + "unbond": "20c83a76b4ef4f706d87905d38f7c390044be80404a19770f94acc8be4e29df7b4ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20c83a76b4ef4f706d87905d38f7c390044be80404a19770f94acc8be4e29df7b4ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "694a9803da4741009384580e8e12f1ec209652ba030c70a859e768060a3c15d2" + }, + { + "timelock": "202ef0eed98f37e381327230b15401387b0f9d7bd412aeea7c39a45bb0d10d4d66ad02e803b2", + "unbond": "202ef0eed98f37e381327230b15401387b0f9d7bd412aeea7c39a45bb0d10d4d66ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "202ef0eed98f37e381327230b15401387b0f9d7bd412aeea7c39a45bb0d10d4d66ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "2e7367a5284dcd6d883914e0753ee04b885f137a231400c1d9679f1ff55691dc" + }, + { + "timelock": "20341aa635ad233ed00a025bf13f07e32e67f5bb87f716e1bb6af4f8854b9d00b3ad02e803b2", + "unbond": "20341aa635ad233ed00a025bf13f07e32e67f5bb87f716e1bb6af4f8854b9d00b3ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20341aa635ad233ed00a025bf13f07e32e67f5bb87f716e1bb6af4f8854b9d00b3ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "751dc74292eb7eb66308ea5ec023696226af4fca5ddcfb252bdf36690b5a0481" + }, + { + "timelock": "2046026a0dd1ba1969deb63ab168cc0a367760a2c87957942326f6816f2c4d441ead02e803b2", + "unbond": "2046026a0dd1ba1969deb63ab168cc0a367760a2c87957942326f6816f2c4d441ead204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2046026a0dd1ba1969deb63ab168cc0a367760a2c87957942326f6816f2c4d441ead2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "c4a58a8c7cf3a9dc40025a1658b3cde6026734331c0e4da76f42e93ecbd29049" + }, + { + "timelock": "201a70f30c3c96cf01145b3e292c7aaf7295f5276f15886bbbd6765da3753b91ebad02e803b2", + "unbond": "201a70f30c3c96cf01145b3e292c7aaf7295f5276f15886bbbd6765da3753b91ebad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "201a70f30c3c96cf01145b3e292c7aaf7295f5276f15886bbbd6765da3753b91ebad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "5e5d536edc35df1d6041c20d0373fdb56f3b9f00c8b0b349bc914a7dcdf99b98" + }, + { + "timelock": "207e0baafad59419810bb9b36ca2c2c05d46750d14082229ad88dee603321ba362ad02e803b2", + "unbond": "207e0baafad59419810bb9b36ca2c2c05d46750d14082229ad88dee603321ba362ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "207e0baafad59419810bb9b36ca2c2c05d46750d14082229ad88dee603321ba362ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "5c68141be3722b43a59b21f640b5f41c80df877b88b106f6ca9714b4b55c6b4f" + }, + { + "timelock": "201edb96768b6fd3ffc9999173021f3d0ee325b163387d145f167d67f8c78b5796ad02e803b2", + "unbond": "201edb96768b6fd3ffc9999173021f3d0ee325b163387d145f167d67f8c78b5796ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "201edb96768b6fd3ffc9999173021f3d0ee325b163387d145f167d67f8c78b5796ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "5950054d2bdbfac845a967034cd6dd11cc27cd960ad4a88666402025757f191f" + }, + { + "timelock": "20d2c359a97c7c97087171de640ecf23a972588fec3a5dc96f91693a270e4fe283ad02e803b2", + "unbond": "20d2c359a97c7c97087171de640ecf23a972588fec3a5dc96f91693a270e4fe283ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20d2c359a97c7c97087171de640ecf23a972588fec3a5dc96f91693a270e4fe283ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "98ccea5e2e76ce34b1258234e3d498b6031189012a8cbb6740576e8835bc4ea7" + }, + { + "timelock": "206025af02f133d46d2a24b48a6c8629d314954af86e0a1745fb714518bc3d16cfad02e803b2", + "unbond": "206025af02f133d46d2a24b48a6c8629d314954af86e0a1745fb714518bc3d16cfad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "206025af02f133d46d2a24b48a6c8629d314954af86e0a1745fb714518bc3d16cfad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "912eb58313f73efe47047e845a6fc399aca28083c6cb7fe35354d61655e81015" + }, + { + "timelock": "20ba384ee73dbd536547ffb2014cddcb0b99dec2899ebecbd0ec3d810279777d01ad02e803b2", + "unbond": "20ba384ee73dbd536547ffb2014cddcb0b99dec2899ebecbd0ec3d810279777d01ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20ba384ee73dbd536547ffb2014cddcb0b99dec2899ebecbd0ec3d810279777d01ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "304114c44f24035cc5d410e087958d6052effbd68541ec9e14c927ff6252396a" + }, + { + "timelock": "2082b427c82b033567bf583614df46204fc8278c19df948c0df20d99c2b1dcd96dad02e803b2", + "unbond": "2082b427c82b033567bf583614df46204fc8278c19df948c0df20d99c2b1dcd96dad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2082b427c82b033567bf583614df46204fc8278c19df948c0df20d99c2b1dcd96dad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "5725fb86df1d7abdb5ea2fddda53b64731f2a5ea882398a58193399fea92f668" + }, + { + "timelock": "20bd807fca2f7a53f279c47b5a6703878698edeb3ed06c53e93ba5a1c3f4ac79f2ad02e803b2", + "unbond": "20bd807fca2f7a53f279c47b5a6703878698edeb3ed06c53e93ba5a1c3f4ac79f2ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20bd807fca2f7a53f279c47b5a6703878698edeb3ed06c53e93ba5a1c3f4ac79f2ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "6a13a484773a35e18d6417fb504f28a25c3c1251fdfdc947d1baa2b664c563d6" + }, + { + "timelock": "20d544ff2ec9a5a31bb3b4f7d2c39602108ac29490c0c1c0581b5430eb175dd415ad02e803b2", + "unbond": "20d544ff2ec9a5a31bb3b4f7d2c39602108ac29490c0c1c0581b5430eb175dd415ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20d544ff2ec9a5a31bb3b4f7d2c39602108ac29490c0c1c0581b5430eb175dd415ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "c4883eef52f04756b54c396ab47034a49934641465c9dbbda71ea73462f331c0" + }, + { + "timelock": "20182e7e50bb4c0ec1c8987d941f9f3abf3d21ddc895570d42c03ce3c48e0ed5e1ad02e803b2", + "unbond": "20182e7e50bb4c0ec1c8987d941f9f3abf3d21ddc895570d42c03ce3c48e0ed5e1ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20182e7e50bb4c0ec1c8987d941f9f3abf3d21ddc895570d42c03ce3c48e0ed5e1ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "549753118ac562c7792f65d9da0ac26e1164cb73b4560a3b8fbb5159b0b30121" + }, + { + "timelock": "201d67a16e967f3d68f61fe531f305df4ef3fcbaa31a905fc7969a5f88c162dc63ad02e803b2", + "unbond": "201d67a16e967f3d68f61fe531f305df4ef3fcbaa31a905fc7969a5f88c162dc63ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "201d67a16e967f3d68f61fe531f305df4ef3fcbaa31a905fc7969a5f88c162dc63ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "b4cac5715f103bcf6d8256925a1217fb0d9d465b704b435163df0d6f7a3a611a" + }, + { + "timelock": "208449339b33f3c9866c55b2fa7b3ff864998fc2337f1ee94032118b0b187d4536ad02e803b2", + "unbond": "208449339b33f3c9866c55b2fa7b3ff864998fc2337f1ee94032118b0b187d4536ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "208449339b33f3c9866c55b2fa7b3ff864998fc2337f1ee94032118b0b187d4536ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "8c6a1b51e5e4eab5ac6034d99f5d56d3c6611508e84c01f91da00cca3116d42a" + }, + { + "timelock": "20c9475411e0b3330390d41506dda19ccd7702925081c72097ba60912bf26f85e6ad02e803b2", + "unbond": "20c9475411e0b3330390d41506dda19ccd7702925081c72097ba60912bf26f85e6ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20c9475411e0b3330390d41506dda19ccd7702925081c72097ba60912bf26f85e6ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "e26e57e640d6fe0c96bdeb26076565c080248ea3bc30482b892d544235cd32d0" + }, + { + "timelock": "20822dd09895f723b5312224c77ac8e30883be9ae5249bbb4a70fecbf4ccfa0a71ad02e803b2", + "unbond": "20822dd09895f723b5312224c77ac8e30883be9ae5249bbb4a70fecbf4ccfa0a71ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20822dd09895f723b5312224c77ac8e30883be9ae5249bbb4a70fecbf4ccfa0a71ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "2da3a683eab38dc1e986891e19c434af8b329919e3dac51283a9d47e5eff4de2" + }, + { + "timelock": "20a92dd068dd1f91a10a35df4baa29ff61a40b138b5dbcb153ca8cad649e664686ad02e803b2", + "unbond": "20a92dd068dd1f91a10a35df4baa29ff61a40b138b5dbcb153ca8cad649e664686ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20a92dd068dd1f91a10a35df4baa29ff61a40b138b5dbcb153ca8cad649e664686ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "b6f94b6960cdcd46c6ad4a24e46125f1e4499b45624c210d80119d61e88fa822" + }, + { + "timelock": "20f4562571f946d3c85f396e461802d062712c95416431f81356407d032da20332ad02e803b2", + "unbond": "20f4562571f946d3c85f396e461802d062712c95416431f81356407d032da20332ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20f4562571f946d3c85f396e461802d062712c95416431f81356407d032da20332ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "aee1a13a81927cf88a8942813bb6e3a8b9ae43d7d93173d5f4d61a83f35e466c" + }, + { + "timelock": "20e5dbd23459fd79ff78356042922b7511452dfae59212468b71a491498c0cd708ad02e803b2", + "unbond": "20e5dbd23459fd79ff78356042922b7511452dfae59212468b71a491498c0cd708ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20e5dbd23459fd79ff78356042922b7511452dfae59212468b71a491498c0cd708ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "b2d4b96f75031d635813a2ec8fc871ca47bf1d1fe3890cbe04f6de42331c55ae" + }, + { + "timelock": "204c0b46f2dc46bd1ff04d98d96a3778ecde96cbc4312ef1f91873a90fbfea9e66ad02e803b2", + "unbond": "204c0b46f2dc46bd1ff04d98d96a3778ecde96cbc4312ef1f91873a90fbfea9e66ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "204c0b46f2dc46bd1ff04d98d96a3778ecde96cbc4312ef1f91873a90fbfea9e66ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "08dc413075fb9b98dc98e51a2123890668d953e41b3863b0fecd8fab1d652ce9" + }, + { + "timelock": "200ff99a01ce795cd27bc210d35286d45464edf4d61c6f7f640cc2e06f8420f0c2ad02e803b2", + "unbond": "200ff99a01ce795cd27bc210d35286d45464edf4d61c6f7f640cc2e06f8420f0c2ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "200ff99a01ce795cd27bc210d35286d45464edf4d61c6f7f640cc2e06f8420f0c2ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "da0631fed323f79a150446974fdb5ba8a10ae7e901e26480bd9915996ee6db6d" + }, + { + "timelock": "2095af137b7036ad1720fca6a9061ae58f071992f59c9aec7c74591964ac5aea2fad02e803b2", + "unbond": "2095af137b7036ad1720fca6a9061ae58f071992f59c9aec7c74591964ac5aea2fad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2095af137b7036ad1720fca6a9061ae58f071992f59c9aec7c74591964ac5aea2fad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "a09dd506b287cef36a27b62e605ea68d8db7df68c948b775751567a92544e337" + }, + { + "timelock": "20a4193ca5a1d0a7facbaf9d8dbe23d46aa8a8b9f5dff0bfd1f6a3e2eab3cba5d1ad02e803b2", + "unbond": "20a4193ca5a1d0a7facbaf9d8dbe23d46aa8a8b9f5dff0bfd1f6a3e2eab3cba5d1ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20a4193ca5a1d0a7facbaf9d8dbe23d46aa8a8b9f5dff0bfd1f6a3e2eab3cba5d1ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "95d0b995c558e41e999818f0ebacf7204f5aa28146942c25f02c4e67e34d0b1e" + }, + { + "timelock": "20c7b7ed8c05693a7057bd7d86ae866cc7449dbd8a9949678c7f3ed8e2174b2b16ad02e803b2", + "unbond": "20c7b7ed8c05693a7057bd7d86ae866cc7449dbd8a9949678c7f3ed8e2174b2b16ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20c7b7ed8c05693a7057bd7d86ae866cc7449dbd8a9949678c7f3ed8e2174b2b16ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "0ffdfae72eab9e36619feac1696bc0cf8c2f6db4f420f49c0a5d4e6da0836e8f" + }, + { + "timelock": "20eb088f08256453773028ea184446d2b16c3ae0b788045a19915a776465757aedad02e803b2", + "unbond": "20eb088f08256453773028ea184446d2b16c3ae0b788045a19915a776465757aedad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20eb088f08256453773028ea184446d2b16c3ae0b788045a19915a776465757aedad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "21f428703c6685d1161b7765db7695eb759bc170a55fef959dc56a5081f440ed" + }, + { + "timelock": "20ee875a0d9901fbd7a2af56ffa872911fbf22f75ba7638727f9e0a158913f17e1ad02e803b2", + "unbond": "20ee875a0d9901fbd7a2af56ffa872911fbf22f75ba7638727f9e0a158913f17e1ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20ee875a0d9901fbd7a2af56ffa872911fbf22f75ba7638727f9e0a158913f17e1ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "534df4a7aa7c229241229d7b911ddc1628257451cdac1a2afeb080503fb7d94a" + }, + { + "timelock": "20849a374a587f92193f6ef88a6a99ceed99e2ee417bb2be6ebc3d8b411e4b12eaad02e803b2", + "unbond": "20849a374a587f92193f6ef88a6a99ceed99e2ee417bb2be6ebc3d8b411e4b12eaad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20849a374a587f92193f6ef88a6a99ceed99e2ee417bb2be6ebc3d8b411e4b12eaad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "0bf80d7cab1381fa7aef4aceb5861510e94dad54072816cb28f42cd9dde37728" + }, + { + "timelock": "20db319dbeee541226322e1d2904e3438e2ab660d82f1aa138e219096fa4867655ad02e803b2", + "unbond": "20db319dbeee541226322e1d2904e3438e2ab660d82f1aa138e219096fa4867655ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20db319dbeee541226322e1d2904e3438e2ab660d82f1aa138e219096fa4867655ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "a44f9a356f79d9663d184bfb2448fa7c748c1ca9fe400785da83872826bda7f9" + }, + { + "timelock": "20c76fa80c517a594bd4772ceb4bfe5ce529364244d313333d91f4527306ae94f4ad02e803b2", + "unbond": "20c76fa80c517a594bd4772ceb4bfe5ce529364244d313333d91f4527306ae94f4ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20c76fa80c517a594bd4772ceb4bfe5ce529364244d313333d91f4527306ae94f4ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "17ba509be3a762bcd7c1830129f77f40437447cc759f5a9b528f92e447d0b3e0" + }, + { + "timelock": "20c5f05c1c87e5289e3e80085bcd25319eb5a55348039372934517ab1e7e0f2ac2ad02e803b2", + "unbond": "20c5f05c1c87e5289e3e80085bcd25319eb5a55348039372934517ab1e7e0f2ac2ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20c5f05c1c87e5289e3e80085bcd25319eb5a55348039372934517ab1e7e0f2ac2ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "b9a7eeecf6818d705ed8e4a94a235a8a88390813437e8cc4fa32369e6e8a6a8a" + }, + { + "timelock": "201be14a77fb2d6ad3e25551fd59010174a008d9f97939db408b6a0f40f31eb49fad02e803b2", + "unbond": "201be14a77fb2d6ad3e25551fd59010174a008d9f97939db408b6a0f40f31eb49fad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "201be14a77fb2d6ad3e25551fd59010174a008d9f97939db408b6a0f40f31eb49fad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "b571b0be2b7129f757cae6f515b43ec3a7830324425ee2637bffed5370195264" + }, + { + "timelock": "20492829f913a2f7d6533aca4e512c5ed64f2a06d8d01a2a5268e36a90aa6f306ead02e803b2", + "unbond": "20492829f913a2f7d6533aca4e512c5ed64f2a06d8d01a2a5268e36a90aa6f306ead204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20492829f913a2f7d6533aca4e512c5ed64f2a06d8d01a2a5268e36a90aa6f306ead2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "150aac474ffd78471646f9a8c68fa7079a57bf1a4ef7758fe541108c680b4cc9" + }, + { + "timelock": "20b37582c6d2572294d0d0ef6571f238295848a495ffb71a30dd928383d736624ead02e803b2", + "unbond": "20b37582c6d2572294d0d0ef6571f238295848a495ffb71a30dd928383d736624ead204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20b37582c6d2572294d0d0ef6571f238295848a495ffb71a30dd928383d736624ead2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "4b2ebfc7347bb794dc1e1e38718bed5fa83fc6749355fbbfc64121abed48358e" + }, + { + "timelock": "2099e0589d02d3201f8a22eb7979585e7ea1218a44f6cf8ffa3addfc69f35116e0ad02e803b2", + "unbond": "2099e0589d02d3201f8a22eb7979585e7ea1218a44f6cf8ffa3addfc69f35116e0ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2099e0589d02d3201f8a22eb7979585e7ea1218a44f6cf8ffa3addfc69f35116e0ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "99a6dc36a82b0e849ddede3cf6389d617d89d4761cda82eb41d01b44114d3439" + }, + { + "timelock": "202d1e02a2097d3842d503d65992b77079356c0c503ecd5c4db27a2dbaa76dba95ad02e803b2", + "unbond": "202d1e02a2097d3842d503d65992b77079356c0c503ecd5c4db27a2dbaa76dba95ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "202d1e02a2097d3842d503d65992b77079356c0c503ecd5c4db27a2dbaa76dba95ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "5181c3daecbf92261d47426b6e7ab0ae1e04f2913a6bb0d3c9c85450f63c4040" + }, + { + "timelock": "20792876213a5814c453280cda9b11e0a296e3c267fe300afff7694c2e88f4e25dad02e803b2", + "unbond": "20792876213a5814c453280cda9b11e0a296e3c267fe300afff7694c2e88f4e25dad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20792876213a5814c453280cda9b11e0a296e3c267fe300afff7694c2e88f4e25dad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "ba9cec13e07363c3b6da13c5227634d023fee0d7735b69360a36da8c255aa09f" + }, + { + "timelock": "209fb297561380f69edd242fe0010f74dc2e37446b340087d996e3b281a61e57d3ad02e803b2", + "unbond": "209fb297561380f69edd242fe0010f74dc2e37446b340087d996e3b281a61e57d3ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "209fb297561380f69edd242fe0010f74dc2e37446b340087d996e3b281a61e57d3ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "42523ba352f12c28fa6805afc5373a79a9a95f8f40baef85667c14a8ae145f75" + }, + { + "timelock": "2010a58de6bb0e40ab507aa6f42389ef93f9a9ff724f2a258ce03feee18c77b87fad02e803b2", + "unbond": "2010a58de6bb0e40ab507aa6f42389ef93f9a9ff724f2a258ce03feee18c77b87fad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2010a58de6bb0e40ab507aa6f42389ef93f9a9ff724f2a258ce03feee18c77b87fad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "84529decdd062b884f4abd56c5a3b0015b7ed905a2b66734d6ce00d69ee8ad1c" + }, + { + "timelock": "2064e5034beb7eacdc3eedc0cce2dd729a6c472c09708bd836bd6ae920e90320bcad02e803b2", + "unbond": "2064e5034beb7eacdc3eedc0cce2dd729a6c472c09708bd836bd6ae920e90320bcad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2064e5034beb7eacdc3eedc0cce2dd729a6c472c09708bd836bd6ae920e90320bcad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "fecb1240acba8bb080486a8ea976071c86486838d95ba39ae08d7476b332f867" + }, + { + "timelock": "205a649b806260c6e830296a33b19bd95d67032a62533d1cdd659c194d0814f9dbad02e803b2", + "unbond": "205a649b806260c6e830296a33b19bd95d67032a62533d1cdd659c194d0814f9dbad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "205a649b806260c6e830296a33b19bd95d67032a62533d1cdd659c194d0814f9dbad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "6294417a7c7cd166472f35f256908549f639f31ab9e10c3090c3d03cd6dd6ad0" + }, + { + "timelock": "20ba1a1c15c2d98ac68bddd390e3bb88c9857685e0522f41f1b2e93e2b27804284ad02e803b2", + "unbond": "20ba1a1c15c2d98ac68bddd390e3bb88c9857685e0522f41f1b2e93e2b27804284ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20ba1a1c15c2d98ac68bddd390e3bb88c9857685e0522f41f1b2e93e2b27804284ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "cd6b669affca8716a5d7761c6759e08c845a08426a99861c5f8f96a9917afa43" + }, + { + "timelock": "2087ebb21e31bb16872ac5501fc953ad088f9b1335ea9d660e763c6814495882b0ad02e803b2", + "unbond": "2087ebb21e31bb16872ac5501fc953ad088f9b1335ea9d660e763c6814495882b0ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2087ebb21e31bb16872ac5501fc953ad088f9b1335ea9d660e763c6814495882b0ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "cf6cf5426a55f7258f3a3fbed046cf751e5c2b2208dd644ed81b9f0c212362e9" + }, + { + "timelock": "207d3a6f16875cccc9043874f7d4a49acc95e6836455d2f7e64f4ddab08130b99ead02e803b2", + "unbond": "207d3a6f16875cccc9043874f7d4a49acc95e6836455d2f7e64f4ddab08130b99ead204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "207d3a6f16875cccc9043874f7d4a49acc95e6836455d2f7e64f4ddab08130b99ead2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "cc9d9ace8ce2e0aad7b783e3f97e5355bfe17cdc02589a404bfe8c5d2d28cf83" + }, + { + "timelock": "20944a8d87ea60ca1fa09daa9144962f532774ddf89899d2c579d22374f24de042ad02e803b2", + "unbond": "20944a8d87ea60ca1fa09daa9144962f532774ddf89899d2c579d22374f24de042ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20944a8d87ea60ca1fa09daa9144962f532774ddf89899d2c579d22374f24de042ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "e7e92e0d4a60db9a399442044aa80249312fed480b7cd9b26b365f08ed383e40" + }, + { + "timelock": "2088b72c0ad71ff42807c0d724002eb24c1fe306c24a31b32733fa5a22665f2060ad02e803b2", + "unbond": "2088b72c0ad71ff42807c0d724002eb24c1fe306c24a31b32733fa5a22665f2060ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2088b72c0ad71ff42807c0d724002eb24c1fe306c24a31b32733fa5a22665f2060ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "242ff3f67dd3465013fb05aaafca1b591b1bf9d12db12d0f86818055dd8662e3" + }, + { + "timelock": "2008fbdc74407489c3c5df5b53215dd3f819ba517b2d306fd6aa583a3e60788194ad02e803b2", + "unbond": "2008fbdc74407489c3c5df5b53215dd3f819ba517b2d306fd6aa583a3e60788194ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2008fbdc74407489c3c5df5b53215dd3f819ba517b2d306fd6aa583a3e60788194ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "5657ad0e65d211a3e56d413fa4f7f2bdb1bf74735e41a7c6ab35296a5b0b2328" + }, + { + "timelock": "205cd53ee7b240d245d40915100d32323039f208fcbff4ed37726ccbb28af0bde8ad02e803b2", + "unbond": "205cd53ee7b240d245d40915100d32323039f208fcbff4ed37726ccbb28af0bde8ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "205cd53ee7b240d245d40915100d32323039f208fcbff4ed37726ccbb28af0bde8ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "935dddf8786d8701abd1058dd2978a0ba0d70a3c6c9d54e0b3d00c5ef81fdf07" + }, + { + "timelock": "2064b0dfe3626671bc39ac2f6a366cc33c0eb7b2b3df17470187d5fa0b033b6b0fad02e803b2", + "unbond": "2064b0dfe3626671bc39ac2f6a366cc33c0eb7b2b3df17470187d5fa0b033b6b0fad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2064b0dfe3626671bc39ac2f6a366cc33c0eb7b2b3df17470187d5fa0b033b6b0fad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "85e991858e8cb1a5a051e4b76d429d746e4bcb1df02eccf32825dd4ada5b6de1" + }, + { + "timelock": "200b2874cfe4a613191a652940300042cdc43c6e71d5c82031878296d5e8a9b139ad02e803b2", + "unbond": "200b2874cfe4a613191a652940300042cdc43c6e71d5c82031878296d5e8a9b139ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "200b2874cfe4a613191a652940300042cdc43c6e71d5c82031878296d5e8a9b139ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "f3a415527d0a20b9b3b9955ea463a1ce74b1ab7a7035499a84d123cafb1d1075" + }, + { + "timelock": "20e81505ffa5ff2e6f4c63c561576f6a9d033713bf72a51e7546ca73c9a158d3e4ad02e803b2", + "unbond": "20e81505ffa5ff2e6f4c63c561576f6a9d033713bf72a51e7546ca73c9a158d3e4ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20e81505ffa5ff2e6f4c63c561576f6a9d033713bf72a51e7546ca73c9a158d3e4ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "02a50b1ac1ba93cefefb9e19b4542574f54334eed443495d1008835f9f9e8ef8" + }, + { + "timelock": "204c13540405a69f47e9afcb8393db7a9c25163e3fd210dcd4037e8af8d6081926ad02e803b2", + "unbond": "204c13540405a69f47e9afcb8393db7a9c25163e3fd210dcd4037e8af8d6081926ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "204c13540405a69f47e9afcb8393db7a9c25163e3fd210dcd4037e8af8d6081926ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "6e8793882a1a66f0112995f4b748556e2a8cc2fa1d0f7d320b5df16d4406d2ee" + }, + { + "timelock": "204e4ab90943a4f25dff9ec0cc26d6b83ccec0d29eb9b0c7f8a8aed88cb1220f01ad02e803b2", + "unbond": "204e4ab90943a4f25dff9ec0cc26d6b83ccec0d29eb9b0c7f8a8aed88cb1220f01ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "204e4ab90943a4f25dff9ec0cc26d6b83ccec0d29eb9b0c7f8a8aed88cb1220f01ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "3b6adc645809d461693d23b32e1f025d48389fab1749d54253426414b5cedd28" + }, + { + "timelock": "20d86168fc3ae1b8fde9d4311a693cdfed221f63f681a6953065b38a7785c25a23ad02e803b2", + "unbond": "20d86168fc3ae1b8fde9d4311a693cdfed221f63f681a6953065b38a7785c25a23ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20d86168fc3ae1b8fde9d4311a693cdfed221f63f681a6953065b38a7785c25a23ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "cd1aa6e3e191d3023c39ff0953ccfb304702b6b29d124fd324c602637f34fe83" + }, + { + "timelock": "20744b20d7813cdb1597a6796db452c890c6918e8fd27c92a5eeaabd37fd0bbf2dad02e803b2", + "unbond": "20744b20d7813cdb1597a6796db452c890c6918e8fd27c92a5eeaabd37fd0bbf2dad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20744b20d7813cdb1597a6796db452c890c6918e8fd27c92a5eeaabd37fd0bbf2dad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "d1d9823a32c8c8abc751ed17345000a2ba3b50c1a2b1222c9708b97a574d3b28" + }, + { + "timelock": "20a3ce645632976176d258aa8c9b689feacf6f5d4c9026e3819b6309b12ad074a9ad02e803b2", + "unbond": "20a3ce645632976176d258aa8c9b689feacf6f5d4c9026e3819b6309b12ad074a9ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20a3ce645632976176d258aa8c9b689feacf6f5d4c9026e3819b6309b12ad074a9ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "eb0b3d77b328413a9eddaac177eeced2b001dcd7a0dc6ba653be7a01b35619da" + }, + { + "timelock": "20d4052afc0074b7c8badfed57d7e8e059315514eaaa189a07d59e38acb2c6eacfad02e803b2", + "unbond": "20d4052afc0074b7c8badfed57d7e8e059315514eaaa189a07d59e38acb2c6eacfad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20d4052afc0074b7c8badfed57d7e8e059315514eaaa189a07d59e38acb2c6eacfad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "421b242a686942d1129b7cba7b6ce1f1c5d27562a8635ec7c67bee9fff7e5b07" + }, + { + "timelock": "2022c7652dc8cea2bce20db60e29cd7e57f7444c82456eb0a15eee53174e502c7bad02e803b2", + "unbond": "2022c7652dc8cea2bce20db60e29cd7e57f7444c82456eb0a15eee53174e502c7bad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2022c7652dc8cea2bce20db60e29cd7e57f7444c82456eb0a15eee53174e502c7bad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "52d37d201884823dcb66477d4a76a91479907471b3771df9b8b13126e9fc6ec8" + }, + { + "timelock": "203b02158b4453adf1f39589b873bce873e11c4e1439d246847d25611acbce63e0ad02e803b2", + "unbond": "203b02158b4453adf1f39589b873bce873e11c4e1439d246847d25611acbce63e0ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "203b02158b4453adf1f39589b873bce873e11c4e1439d246847d25611acbce63e0ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "a77cb217817b3a27c90b420a0c0e2b2e7e62b65f18c87c9e11b86f8065931abd" + }, + { + "timelock": "20486a1a18aedb35b15f10cff4da4890979748944aad337dc328d497296cc1c4d6ad02e803b2", + "unbond": "20486a1a18aedb35b15f10cff4da4890979748944aad337dc328d497296cc1c4d6ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20486a1a18aedb35b15f10cff4da4890979748944aad337dc328d497296cc1c4d6ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "805b33fef92eb1b81bda0cf7bef159a8ab313502e8653e9f829de51438239c0a" + }, + { + "timelock": "20c8b70664224d3839c9302c9bfa86ef0d5b040901f3b55e6af6d66d496dda9509ad02e803b2", + "unbond": "20c8b70664224d3839c9302c9bfa86ef0d5b040901f3b55e6af6d66d496dda9509ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20c8b70664224d3839c9302c9bfa86ef0d5b040901f3b55e6af6d66d496dda9509ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "3d5a0e9ff97d70ff3f2dfa045f85a20ce26a982946f4c7d8d2b7dbdf4def5f8f" + }, + { + "timelock": "20f53677b2abdb111cd922cd47a692440e969e2a45de60be0d8d877142d7ccc0dfad02e803b2", + "unbond": "20f53677b2abdb111cd922cd47a692440e969e2a45de60be0d8d877142d7ccc0dfad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20f53677b2abdb111cd922cd47a692440e969e2a45de60be0d8d877142d7ccc0dfad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "9b8ed41999a9b73e54288c336ce6d1f00fe66d58b3cf2bccf9a2df2b64b17434" + }, + { + "timelock": "20b14fc7eca519812bfe50a5fe2a048072d87fc53b81971bc8f126962afa28d92fad02e803b2", + "unbond": "20b14fc7eca519812bfe50a5fe2a048072d87fc53b81971bc8f126962afa28d92fad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20b14fc7eca519812bfe50a5fe2a048072d87fc53b81971bc8f126962afa28d92fad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "c9800bd59f0573cc461358f78257d44bf42d84db955ae705302a2e6c182e3b04" + }, + { + "timelock": "20f17518b03760fe452b7ec720df35183dbafa040dc8fcc35b33a08f285a0059abad02e803b2", + "unbond": "20f17518b03760fe452b7ec720df35183dbafa040dc8fcc35b33a08f285a0059abad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20f17518b03760fe452b7ec720df35183dbafa040dc8fcc35b33a08f285a0059abad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "b9b367cd58b3ca643e90f7e0fcd13d96fd9b7915ef47f864e0cbb66072d80298" + }, + { + "timelock": "205483f9994c13872bf0dc143f29b9fc1bbcc9322ea1e4d92b2fadb9211a87fa56ad02e803b2", + "unbond": "205483f9994c13872bf0dc143f29b9fc1bbcc9322ea1e4d92b2fadb9211a87fa56ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "205483f9994c13872bf0dc143f29b9fc1bbcc9322ea1e4d92b2fadb9211a87fa56ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "3fd1d2f564cd806bf61dfa4655e30b47c4025dfcc676e7c0d54f9076b96f9a8e" + }, + { + "timelock": "2052a0a6b0cf1e0d32aba9af2ab82cdae1922c4e761e2d7e78d9bfb898f5ed0258ad02e803b2", + "unbond": "2052a0a6b0cf1e0d32aba9af2ab82cdae1922c4e761e2d7e78d9bfb898f5ed0258ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2052a0a6b0cf1e0d32aba9af2ab82cdae1922c4e761e2d7e78d9bfb898f5ed0258ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "1dcc8ee68ba3b1203cefcb6d94dece16568f17ac959cd4ccf8fe369ba4fb9b2d" + }, + { + "timelock": "20677a553654f6556d8c4e93e282d83e4f1ea8116925df7c22e29d174d2cac5256ad02e803b2", + "unbond": "20677a553654f6556d8c4e93e282d83e4f1ea8116925df7c22e29d174d2cac5256ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20677a553654f6556d8c4e93e282d83e4f1ea8116925df7c22e29d174d2cac5256ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "66de5cf0a58b21d00145e883a855e948494d7ae89e73ef6fd0b68ea6ca93721c" + }, + { + "timelock": "207efca69268c86a7a04d8ee6782b9f7b3a701d854283103a0f01d656e76aec1c9ad02e803b2", + "unbond": "207efca69268c86a7a04d8ee6782b9f7b3a701d854283103a0f01d656e76aec1c9ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "207efca69268c86a7a04d8ee6782b9f7b3a701d854283103a0f01d656e76aec1c9ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "61b22e12f123e9a4de298b8379ddcdf2d2bf37732128caa794efa2ad25b629ee" + }, + { + "timelock": "2012864ed4041fa22d8f360c8f142b0563439fe3279d7829505ea3d94ea76f9617ad02e803b2", + "unbond": "2012864ed4041fa22d8f360c8f142b0563439fe3279d7829505ea3d94ea76f9617ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2012864ed4041fa22d8f360c8f142b0563439fe3279d7829505ea3d94ea76f9617ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "b17bee0d25b5d3610b389e416ea9b916bf2a25e409cfa8ff749fe2993f3be1e8" + }, + { + "timelock": "204976f8fb9aa3dcdb6cbeabe1148ddfec6dcc88e28dec97565fe265f30cc5b983ad02e803b2", + "unbond": "204976f8fb9aa3dcdb6cbeabe1148ddfec6dcc88e28dec97565fe265f30cc5b983ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "204976f8fb9aa3dcdb6cbeabe1148ddfec6dcc88e28dec97565fe265f30cc5b983ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "30fad5d1ac265e1993c7b340619d27f2343e712c6b1ce46dd6de9be55e1fa799" + }, + { + "timelock": "20b00fdbe77efd81005f1792e0f15dd9df59d99c2fc8d2e85721b0cb71123d5cedad02e803b2", + "unbond": "20b00fdbe77efd81005f1792e0f15dd9df59d99c2fc8d2e85721b0cb71123d5cedad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20b00fdbe77efd81005f1792e0f15dd9df59d99c2fc8d2e85721b0cb71123d5cedad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "b50a97970ec1c1bc04c8c9b42a818ddde62f33cb6e88c288f0a8a0f7d118a1a2" + }, + { + "timelock": "20406edf60fa07480479a245fbfbb76dd6ca4d38f68fce634e63f4210a759be27fad02e803b2", + "unbond": "20406edf60fa07480479a245fbfbb76dd6ca4d38f68fce634e63f4210a759be27fad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20406edf60fa07480479a245fbfbb76dd6ca4d38f68fce634e63f4210a759be27fad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "2b21df7b937bc4cee6e7f53c89581859fde4a47e0b16a2e9aa6a60e8404b6666" + }, + { + "timelock": "20b8a47b9c4cb7714bc414fccc52696ea6c32cf33c8369c077b74ba6a4af7f1426ad02e803b2", + "unbond": "20b8a47b9c4cb7714bc414fccc52696ea6c32cf33c8369c077b74ba6a4af7f1426ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20b8a47b9c4cb7714bc414fccc52696ea6c32cf33c8369c077b74ba6a4af7f1426ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "922f1fb6d545f29409119d017398a92a1634fd02f8ea52d1c9d42500b868d387" + }, + { + "timelock": "20ba8f4f5e67a07d31d071d5f8cf1ccdc786569e11df9b3da4ec15c9602ede3cb0ad02e803b2", + "unbond": "20ba8f4f5e67a07d31d071d5f8cf1ccdc786569e11df9b3da4ec15c9602ede3cb0ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20ba8f4f5e67a07d31d071d5f8cf1ccdc786569e11df9b3da4ec15c9602ede3cb0ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "db8eeef4e4734f43cf3c7e4d15954d7df86456534e8045b334a1d4f06c620e44" + }, + { + "timelock": "205d246bcd7958db5f25af672367a1c26cb1416d52280f0df5413cf794e23fb8d3ad02e803b2", + "unbond": "205d246bcd7958db5f25af672367a1c26cb1416d52280f0df5413cf794e23fb8d3ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "205d246bcd7958db5f25af672367a1c26cb1416d52280f0df5413cf794e23fb8d3ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "386f339f381d4b7b50a2f0d5d45ddd1ea71f5a2d9b2dfa5b83bfcf9d48ae50a7" + }, + { + "timelock": "20d6dc28290aceb39b73c3757e7851fc7fac805afaba8621bec516a1d5777d2c93ad02e803b2", + "unbond": "20d6dc28290aceb39b73c3757e7851fc7fac805afaba8621bec516a1d5777d2c93ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20d6dc28290aceb39b73c3757e7851fc7fac805afaba8621bec516a1d5777d2c93ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "002721fb5c2e5024eebf30a8274b4709f5895b79c9a5458d0c846cc817a2c784" + }, + { + "timelock": "20312020a8ec1238ff8723970899ceea440af08b0de66999c5d5382011ab6cffa8ad02e803b2", + "unbond": "20312020a8ec1238ff8723970899ceea440af08b0de66999c5d5382011ab6cffa8ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20312020a8ec1238ff8723970899ceea440af08b0de66999c5d5382011ab6cffa8ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "1683cc9be899b4a92374d02d4f50cdee7c2046081bdd47a4752c9d47aa083238" + }, + { + "timelock": "20707a8ea294045d6d70d9d7481c47b644c9814d2b8a84f3c2c6110a154b52b2dead02e803b2", + "unbond": "20707a8ea294045d6d70d9d7481c47b644c9814d2b8a84f3c2c6110a154b52b2dead204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20707a8ea294045d6d70d9d7481c47b644c9814d2b8a84f3c2c6110a154b52b2dead2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "8600c43ee66851a8467b307d5b36dcf5ef164aa7558c9bddaa2f8aef0f4dedbe" + }, + { + "timelock": "200440239c52b1c1a014644a3751ac98ed7585fbefcf8404239074a621864cb471ad02e803b2", + "unbond": "200440239c52b1c1a014644a3751ac98ed7585fbefcf8404239074a621864cb471ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "200440239c52b1c1a014644a3751ac98ed7585fbefcf8404239074a621864cb471ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "43825f43fa778cdf45b13e98e2fb664c944b98a5d731b142f9aff2fa5972527c" + }, + { + "timelock": "2057efa3c68cfb5575fa96acd0265673c620eae612c88ca82eef827852f04a7160ad02e803b2", + "unbond": "2057efa3c68cfb5575fa96acd0265673c620eae612c88ca82eef827852f04a7160ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "2057efa3c68cfb5575fa96acd0265673c620eae612c88ca82eef827852f04a7160ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "d0225504e7ae838930a15189af74156b28c97b27a8bc2c02e12e8bb02b50e542" + }, + { + "timelock": "201718f5ebb5bde4c7b315a0c389aeb996d2fed6bd6ee19a784a57e138ca9ac88dad02e803b2", + "unbond": "201718f5ebb5bde4c7b315a0c389aeb996d2fed6bd6ee19a784a57e138ca9ac88dad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "201718f5ebb5bde4c7b315a0c389aeb996d2fed6bd6ee19a784a57e138ca9ac88dad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "0a00d868385e99d9ce5f85c9ac64ec02929c5bb63edda9f52a35b38599cbc417" + }, + { + "timelock": "20a16a72c01c54535a8ebbe5b4398435595c40e66ad89f09388d38e15da2e7df49ad02e803b2", + "unbond": "20a16a72c01c54535a8ebbe5b4398435595c40e66ad89f09388d38e15da2e7df49ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "slash": "20a16a72c01c54535a8ebbe5b4398435595c40e66ad89f09388d38e15da2e7df49ad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "root_hash": "4aaa52374ea1f75b86b93bbff2df68ff362f50fc2869957eab6f2ac3201eb13d" + } +] diff --git a/rust/chains/tw_bitcoin/tests/data/babylon_staking_transactions.json b/rust/chains/tw_bitcoin/tests/data/babylon_staking_transactions.json new file mode 100644 index 00000000000..eab9db9f383 --- /dev/null +++ b/rust/chains/tw_bitcoin/tests/data/babylon_staking_transactions.json @@ -0,0 +1,213 @@ +[ + { + "name": "1 finality key, 1 covenant key, 1 staker key with op_return", + "parameters": { + "covenant_public_keys": [ + "024852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30fa" + ], + "covenant_quorum": 1, + "finality_provider_public_keys": [ + "0246542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5" + ], + "staker_public_key": "024000bd2c8b975d351c5f3a42618aca31e07e2b253fcd571e9630540a3cb6eafd", + "staking_time": 1000, + "staking_value": 100000, + "staking_tx_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "staking_output_index": 0, + "unbonding_tx_version": 2, + "unbonding_time": 100, + "unbonding_fee": 2000, + "tag": "01020304", + "network": "mainnet" + }, + "expected": { + "staking_output_pkscript_hex": "5120b2b169d39fb8ea9828f6ed8dbbeaa12594706d03bfd1638a912608a085fdd7a5", + "staking_output_value": 100000, + "staking_transaction_timelock_script_hex": "204000bd2c8b975d351c5f3a42618aca31e07e2b253fcd571e9630540a3cb6eafdad02e803b2", + "staking_transaction_unbonding_script_hex": "204000bd2c8b975d351c5f3a42618aca31e07e2b253fcd571e9630540a3cb6eafdad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "staking_transaction_slashing_script_hex": "204000bd2c8b975d351c5f3a42618aca31e07e2b253fcd571e9630540a3cb6eafdad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "unbonding_transaction_hex": "020000000100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff01d07e01000000000022512071f38cba0e3c0453eb34ac8f9a60805965e9500a9ab69724f636a00fcafc252f00000000", + "unbonding_transaction_time_lock_script_hex": "204000bd2c8b975d351c5f3a42618aca31e07e2b253fcd571e9630540a3cb6eafdad0164b2", + "unbonding_transaction_slashing_script_hex": "204000bd2c8b975d351c5f3a42618aca31e07e2b253fcd571e9630540a3cb6eafdad2046542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e5ad204852a5d3e79cfdee1bda4ea729e879c1db0f19230eadd3cb96d3cb1efa8d30faac", + "op_return_script_hex": "6a4701020304004000bd2c8b975d351c5f3a42618aca31e07e2b253fcd571e9630540a3cb6eafd46542ccdcbde8a8c147c8d00f14d47f0d5c13c684d27497fc4610c7a4def15e503e8" + } + }, + { + "name": "1 finality key, 3/5 covenant committe, 1 staker key with op_return", + "parameters": { + "covenant_public_keys": [ + "02cc5c77da065c490a320834fdcf2c3da70ecd442054c90f874a1edb4669607b83", + "022f57b6d267043beda2deebab1187a67316121f1eac24047c16d1209c5e6cd0a5", + "03784bdab9a1c71ea51fa5904227e721681453e59d20af3d97485e79e5518bdfff", + "03f31cf8fb9ee1d9157e172d65b1bd00a0b54cee3906df13ced709bfd8d407e9a2", + "035c3e1bb7c6b475a9caeebcb09fab74546a9b8a364dd0ccede7e5aea5c5b12ed0" + ], + "covenant_quorum": 3, + "finality_provider_public_keys": [ + "03b5e37d93a8d04daee62838ac680ead407d7cbe0c858525781f8834495be0bce7" + ], + "staker_public_key": "03cc327c2a1ee1a70d7033bee61ed2e3e6ffc7c5f5d6c637c4f0d2ddfae8a257fe", + "staking_time": 10000, + "staking_value": 1000000, + "staking_tx_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "staking_output_index": 0, + "unbonding_tx_version": 2, + "unbonding_time": 50, + "unbonding_fee": 20000, + "tag": "01020304", + "network": "mainnet" + }, + "expected": { + "staking_output_pkscript_hex": "5120bbc6be1fff9cb4d93cb638f0fc9e5df7036c137b89c8baa762fb8637acdf3da7", + "staking_output_value": 1000000, + "staking_transaction_timelock_script_hex": "20cc327c2a1ee1a70d7033bee61ed2e3e6ffc7c5f5d6c637c4f0d2ddfae8a257fead021027b2", + "staking_transaction_unbonding_script_hex": "20cc327c2a1ee1a70d7033bee61ed2e3e6ffc7c5f5d6c637c4f0d2ddfae8a257fead202f57b6d267043beda2deebab1187a67316121f1eac24047c16d1209c5e6cd0a5ac205c3e1bb7c6b475a9caeebcb09fab74546a9b8a364dd0ccede7e5aea5c5b12ed0ba20784bdab9a1c71ea51fa5904227e721681453e59d20af3d97485e79e5518bdfffba20cc5c77da065c490a320834fdcf2c3da70ecd442054c90f874a1edb4669607b83ba20f31cf8fb9ee1d9157e172d65b1bd00a0b54cee3906df13ced709bfd8d407e9a2ba539c", + "staking_transaction_slashing_script_hex": "20cc327c2a1ee1a70d7033bee61ed2e3e6ffc7c5f5d6c637c4f0d2ddfae8a257fead20b5e37d93a8d04daee62838ac680ead407d7cbe0c858525781f8834495be0bce7ad202f57b6d267043beda2deebab1187a67316121f1eac24047c16d1209c5e6cd0a5ac205c3e1bb7c6b475a9caeebcb09fab74546a9b8a364dd0ccede7e5aea5c5b12ed0ba20784bdab9a1c71ea51fa5904227e721681453e59d20af3d97485e79e5518bdfffba20cc5c77da065c490a320834fdcf2c3da70ecd442054c90f874a1edb4669607b83ba20f31cf8fb9ee1d9157e172d65b1bd00a0b54cee3906df13ced709bfd8d407e9a2ba539c", + "unbonding_transaction_hex": "020000000100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0120f40e0000000000225120d4ea23776e49e47df891d52a42a2ec389786aa3bdafa874b5e69df13070fb4bf00000000", + "unbonding_transaction_time_lock_script_hex": "20cc327c2a1ee1a70d7033bee61ed2e3e6ffc7c5f5d6c637c4f0d2ddfae8a257fead0132b2", + "unbonding_transaction_slashing_script_hex": "20cc327c2a1ee1a70d7033bee61ed2e3e6ffc7c5f5d6c637c4f0d2ddfae8a257fead20b5e37d93a8d04daee62838ac680ead407d7cbe0c858525781f8834495be0bce7ad202f57b6d267043beda2deebab1187a67316121f1eac24047c16d1209c5e6cd0a5ac205c3e1bb7c6b475a9caeebcb09fab74546a9b8a364dd0ccede7e5aea5c5b12ed0ba20784bdab9a1c71ea51fa5904227e721681453e59d20af3d97485e79e5518bdfffba20cc5c77da065c490a320834fdcf2c3da70ecd442054c90f874a1edb4669607b83ba20f31cf8fb9ee1d9157e172d65b1bd00a0b54cee3906df13ced709bfd8d407e9a2ba539c", + "op_return_script_hex": "6a470102030400cc327c2a1ee1a70d7033bee61ed2e3e6ffc7c5f5d6c637c4f0d2ddfae8a257feb5e37d93a8d04daee62838ac680ead407d7cbe0c858525781f8834495be0bce72710" + } + }, + { + "name": "3 finality keys, 3/5 covenant committe, 1 staker key with no op_return", + "parameters": { + "covenant_public_keys": [ + "02042916c9cd52cfa146118c37b6b118f082bb50fb91da1c51b76dfc2100e66f00", + "03a2bfd2843357efda60fd26238b6affe6cbafa65d1f3cfbfb63ae3841d35887e6", + "035df1524dfc57fc1b0288ebac9b7abbcf5c61cfbfb8e4667ae0009445b04ecf22", + "03652ea9ac1c526218691e6b94769beaebbaedda080c5f1f036c239d5af69088b6", + "02dca4c9e5848db62e0f62e2c1cafca10f4464c10ae00bf579a8f7b5d81cd697f6" + ], + "covenant_quorum": 3, + "finality_provider_public_keys": [ + "0275e96ca4067b937f8e93bcb962523a1713c25d2af1a26f61034dd81f4e4687fe", + "0396c5c62af047a0839f8ea2b230315db93af25fa7074c71a2b95deeb47e507a63", + "034d4fb121fbe9f13ed7b55440fb9f3ad0f8bce8bd92589423b5f90d871db5142d" + ], + "staker_public_key": "037b251c30f5bd7a29f3b1749d599c60f2bcdc11553e500a6c32e4075b80d5bd29", + "staking_time": 10000, + "staking_value": 1000000, + "staking_tx_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "staking_output_index": 0, + "unbonding_tx_version": 2, + "unbonding_time": 50, + "unbonding_fee": 20000, + "tag": "01020304", + "network": "mainnet" + }, + "expected": { + "staking_output_pkscript_hex": "51206136db5cccc1b9fca70164b3445020d6f6f8dd1428e78cef9111b295bd4eec89", + "staking_output_value": 1000000, + "staking_transaction_timelock_script_hex": "207b251c30f5bd7a29f3b1749d599c60f2bcdc11553e500a6c32e4075b80d5bd29ad021027b2", + "staking_transaction_unbonding_script_hex": "207b251c30f5bd7a29f3b1749d599c60f2bcdc11553e500a6c32e4075b80d5bd29ad20042916c9cd52cfa146118c37b6b118f082bb50fb91da1c51b76dfc2100e66f00ac205df1524dfc57fc1b0288ebac9b7abbcf5c61cfbfb8e4667ae0009445b04ecf22ba20652ea9ac1c526218691e6b94769beaebbaedda080c5f1f036c239d5af69088b6ba20a2bfd2843357efda60fd26238b6affe6cbafa65d1f3cfbfb63ae3841d35887e6ba20dca4c9e5848db62e0f62e2c1cafca10f4464c10ae00bf579a8f7b5d81cd697f6ba539c", + "staking_transaction_slashing_script_hex": "207b251c30f5bd7a29f3b1749d599c60f2bcdc11553e500a6c32e4075b80d5bd29ad204d4fb121fbe9f13ed7b55440fb9f3ad0f8bce8bd92589423b5f90d871db5142dac2075e96ca4067b937f8e93bcb962523a1713c25d2af1a26f61034dd81f4e4687feba2096c5c62af047a0839f8ea2b230315db93af25fa7074c71a2b95deeb47e507a63ba519d20042916c9cd52cfa146118c37b6b118f082bb50fb91da1c51b76dfc2100e66f00ac205df1524dfc57fc1b0288ebac9b7abbcf5c61cfbfb8e4667ae0009445b04ecf22ba20652ea9ac1c526218691e6b94769beaebbaedda080c5f1f036c239d5af69088b6ba20a2bfd2843357efda60fd26238b6affe6cbafa65d1f3cfbfb63ae3841d35887e6ba20dca4c9e5848db62e0f62e2c1cafca10f4464c10ae00bf579a8f7b5d81cd697f6ba539c", + "unbonding_transaction_hex": "020000000100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0120f40e0000000000225120a52e08ebd0db898e6a52870ca3e72a78391b799eac97a1675eccd416adace90e00000000", + "unbonding_transaction_time_lock_script_hex": "207b251c30f5bd7a29f3b1749d599c60f2bcdc11553e500a6c32e4075b80d5bd29ad0132b2", + "unbonding_transaction_slashing_script_hex": "207b251c30f5bd7a29f3b1749d599c60f2bcdc11553e500a6c32e4075b80d5bd29ad204d4fb121fbe9f13ed7b55440fb9f3ad0f8bce8bd92589423b5f90d871db5142dac2075e96ca4067b937f8e93bcb962523a1713c25d2af1a26f61034dd81f4e4687feba2096c5c62af047a0839f8ea2b230315db93af25fa7074c71a2b95deeb47e507a63ba519d20042916c9cd52cfa146118c37b6b118f082bb50fb91da1c51b76dfc2100e66f00ac205df1524dfc57fc1b0288ebac9b7abbcf5c61cfbfb8e4667ae0009445b04ecf22ba20652ea9ac1c526218691e6b94769beaebbaedda080c5f1f036c239d5af69088b6ba20a2bfd2843357efda60fd26238b6affe6cbafa65d1f3cfbfb63ae3841d35887e6ba20dca4c9e5848db62e0f62e2c1cafca10f4464c10ae00bf579a8f7b5d81cd697f6ba539c", + "op_return_script_hex": "" + } + }, + { + "name": "1 finality keys, 7/9 covenant committe, 1 staker key with op_return", + "parameters": { + "covenant_public_keys": [ + "0287ed5bb2d036baf209eb49520327f6bd05285dabd30c97f239c3a69ff419950b", + "02ce45e9c7828b2f1b52f29076b0fbbac380db5e6a8318cb7718c99e0b3fe6228f", + "0354967fa36311ba41a19a725925a68053579122e7e833c5b76a7dde99eec8fb31", + "02cdf01d771c38b4ee3363daf4d33c25796adbade4da3f621e954fd1d2e40d7c1f", + "038a7f080c5629980a5f3ccbfb1ea8a1bc20e7ce0dccbfefe3f35620e69c589e63", + "03b2729d612586227170a2116d9f97e2728f7cdc5f1c2e8eb4c4acd35a143917fd", + "0357925d2886601e7d9cecc60352cf435111f429b3fc97be8704dbf1f0fe491089", + "0369769314404fb9c68cd5c77adb2a5837372c4d5e5ef5d564e8de98f886a1a13e", + "02792a29aeaaaa1bc69ed57612ef050b29345e6f987951b2e22fcb00195b3ae2d5" + ], + "covenant_quorum": 7, + "finality_provider_public_keys": [ + "03bc9d21a6400b831e1741056dd45aaeecd42f23fafdf3ea49b815e49ebb4f95fc" + ], + "staker_public_key": "03a9200483d40c3c81a7a983a8f0a0de37955fbea587595b4bf8fcb878d857c0c0", + "staking_time": 20000, + "staking_value": 10000000, + "staking_tx_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "staking_output_index": 0, + "unbonding_tx_version": 2, + "unbonding_time": 201, + "unbonding_fee": 50000, + "tag": "01020304", + "network": "mainnet" + }, + "expected": { + "staking_output_pkscript_hex": "51208b763bd20b8bca50c7bc059e3c7810471bfe30111f546bbbf4d879e437dd6ae2", + "staking_output_value": 10000000, + "staking_transaction_timelock_script_hex": "20a9200483d40c3c81a7a983a8f0a0de37955fbea587595b4bf8fcb878d857c0c0ad02204eb2", + "staking_transaction_unbonding_script_hex": "20a9200483d40c3c81a7a983a8f0a0de37955fbea587595b4bf8fcb878d857c0c0ad2054967fa36311ba41a19a725925a68053579122e7e833c5b76a7dde99eec8fb31ac2057925d2886601e7d9cecc60352cf435111f429b3fc97be8704dbf1f0fe491089ba2069769314404fb9c68cd5c77adb2a5837372c4d5e5ef5d564e8de98f886a1a13eba20792a29aeaaaa1bc69ed57612ef050b29345e6f987951b2e22fcb00195b3ae2d5ba2087ed5bb2d036baf209eb49520327f6bd05285dabd30c97f239c3a69ff419950bba208a7f080c5629980a5f3ccbfb1ea8a1bc20e7ce0dccbfefe3f35620e69c589e63ba20b2729d612586227170a2116d9f97e2728f7cdc5f1c2e8eb4c4acd35a143917fdba20cdf01d771c38b4ee3363daf4d33c25796adbade4da3f621e954fd1d2e40d7c1fba20ce45e9c7828b2f1b52f29076b0fbbac380db5e6a8318cb7718c99e0b3fe6228fba579c", + "staking_transaction_slashing_script_hex": "20a9200483d40c3c81a7a983a8f0a0de37955fbea587595b4bf8fcb878d857c0c0ad20bc9d21a6400b831e1741056dd45aaeecd42f23fafdf3ea49b815e49ebb4f95fcad2054967fa36311ba41a19a725925a68053579122e7e833c5b76a7dde99eec8fb31ac2057925d2886601e7d9cecc60352cf435111f429b3fc97be8704dbf1f0fe491089ba2069769314404fb9c68cd5c77adb2a5837372c4d5e5ef5d564e8de98f886a1a13eba20792a29aeaaaa1bc69ed57612ef050b29345e6f987951b2e22fcb00195b3ae2d5ba2087ed5bb2d036baf209eb49520327f6bd05285dabd30c97f239c3a69ff419950bba208a7f080c5629980a5f3ccbfb1ea8a1bc20e7ce0dccbfefe3f35620e69c589e63ba20b2729d612586227170a2116d9f97e2728f7cdc5f1c2e8eb4c4acd35a143917fdba20cdf01d771c38b4ee3363daf4d33c25796adbade4da3f621e954fd1d2e40d7c1fba20ce45e9c7828b2f1b52f29076b0fbbac380db5e6a8318cb7718c99e0b3fe6228fba579c", + "unbonding_transaction_hex": "020000000100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0130d3970000000000225120bd14e11a13e473ed65a7233cd8ae5ccae670fedc9b50bb99c1a09b9cff669fc800000000", + "unbonding_transaction_time_lock_script_hex": "20a9200483d40c3c81a7a983a8f0a0de37955fbea587595b4bf8fcb878d857c0c0ad02c900b2", + "unbonding_transaction_slashing_script_hex": "20a9200483d40c3c81a7a983a8f0a0de37955fbea587595b4bf8fcb878d857c0c0ad20bc9d21a6400b831e1741056dd45aaeecd42f23fafdf3ea49b815e49ebb4f95fcad2054967fa36311ba41a19a725925a68053579122e7e833c5b76a7dde99eec8fb31ac2057925d2886601e7d9cecc60352cf435111f429b3fc97be8704dbf1f0fe491089ba2069769314404fb9c68cd5c77adb2a5837372c4d5e5ef5d564e8de98f886a1a13eba20792a29aeaaaa1bc69ed57612ef050b29345e6f987951b2e22fcb00195b3ae2d5ba2087ed5bb2d036baf209eb49520327f6bd05285dabd30c97f239c3a69ff419950bba208a7f080c5629980a5f3ccbfb1ea8a1bc20e7ce0dccbfefe3f35620e69c589e63ba20b2729d612586227170a2116d9f97e2728f7cdc5f1c2e8eb4c4acd35a143917fdba20cdf01d771c38b4ee3363daf4d33c25796adbade4da3f621e954fd1d2e40d7c1fba20ce45e9c7828b2f1b52f29076b0fbbac380db5e6a8318cb7718c99e0b3fe6228fba579c", + "op_return_script_hex": "6a470102030400a9200483d40c3c81a7a983a8f0a0de37955fbea587595b4bf8fcb878d857c0c0bc9d21a6400b831e1741056dd45aaeecd42f23fafdf3ea49b815e49ebb4f95fc4e20" + } + }, + { + "name": "10 finality keys, 18/20 covenant committe, 1 staker key with no op_return", + "parameters": { + "covenant_public_keys": [ + "02e3803a6ecff76daf35709c8484f382783d211970f22397d7a258f40ca3b46304", + "03378f464f4365136230d30c3ad7d6425457d6c0742ed7fefd1015f0040cc70769", + "03583c7fbf7450f8f78977b8c9f2981ff10676efefb6399ec925ba54eed7978a2e", + "021fccb5af3a431a928c32267e556f1ba256871f98428419fae1cce6e7b8356cb9", + "03243b84dd7a170a6e1edc32b22357b20f8365a4149a12ef1d340342701d535991", + "037947f704a584a804c91d07250239ec528300bd01dd3137770f7227309f63db0a", + "021e435d3bbab5fb0244c273e38d6f078bfe57a1bd151016ab5c4ac84457d724b3", + "039010f319cde80190717d96dd60823b8e05905a2da458c69f4de19d5c9226cca8", + "03db2bb56299fe3219ea5901198d1872f0523234d529a29fa912a76c9cb6d70db6", + "030f14729c14b6a9b292e7b5142fc5483d7c25196af1ef4b9722ea5821719e2c40", + "02dbf2c13d6e3b0a8ad7286966bd00164ca0a163b483d305df6b492119cbffbabb", + "03f399ff078d6d954a390f67989705ed21e575ccf43a1340ca8752cd1613ca7ce0", + "030feacb370b7fcfcdd5ee7a363d73fda02d596ce61fd00e308d0ab68a7b955f48", + "022367edcf01edd517d46c1b3cc3d40f5a28608e0313636dd09b0904799bccd635", + "03a63763c4100bacdb1c06e5e1ba5599e379a2e23553d56d2b1d0ee3c990bd1192", + "0364df415d63ef3ce23ddd639c4a5ec8fdbfa67254db91228ca77fbec9610367ae", + "03f6a33481886c7d5ed5429c3bb0a83dbdefafd6d140b22a87aa7081cf7073136c", + "0336612620ae82332e4f5da8d78d0d8a4ff7c8b09e88ee988b3dde18ef6a04c497", + "03e0647bb72f1b3d33d69f360d7e9f58f6a693d40b6ef58f49f140a212bf16931c", + "0339eca9e5cf04e22471d6cde649604e6fd0aae077cd5b1af9a1dc26e56d8502f3" + ], + "covenant_quorum": 18, + "finality_provider_public_keys": [ + "027bb40e3cb76545a71fafcb29738fb27b452c3176b41ffd614f7231b40ff5ce68", + "0378c5d7a263ab7e98e311c8d2d7305d13d78a014d4ae0603b427bb55cb42dbed1", + "03f1209d7248936f9ff2fa7b0ec97e5c0d26e4f41ad6de9f2e423128780da5dfec", + "0350eff2669c6d83f858937cd9229fc93ad57c10cc8d8cfb7ec9bc40e32a9f7ff2", + "03fe96402099f8387ce0ddef84bee2d0f869b35bb599a58b9aaec4a73db08dc490", + "0329f526a81bc92dd2ed8cf1682fc4ae5956987f3f6d3c0da71fbcf230c71781ff", + "03cca2d8aa1feb47ad4933154ef8196db229cfbba676607c3c0583397d65a1e4ee", + "02ae3c70699ae235ddebef85378a550f796723a6b961dcff1f33716d5dbb096a1e", + "03a7165ffc90d12c25187b75b2ebdcb57b1c759e357d655d8371cfb96dc4df2ecc", + "034bcfb4042e5d13286fcd3d481b81a61cdfc3715aaf7909c4ee93de49356cd0b4" + ], + "staker_public_key": "037aa0dafe26a7b663b679418f2c19fe7ea1215919051aa5ac76e4a9fbef2f33c5", + "staking_time": 65535, + "staking_value": 100000000, + "staking_tx_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "staking_output_index": 0, + "unbonding_tx_version": 2, + "unbonding_time": 201, + "unbonding_fee": 100000, + "tag": "01020304", + "network": "mainnet" + }, + "expected": { + "staking_output_pkscript_hex": "51205c7f92282283bf913e9a1cf7659b574104b4ca10095cb4cd293d77664e9d6a2d", + "staking_output_value": 100000000, + "staking_transaction_timelock_script_hex": "207aa0dafe26a7b663b679418f2c19fe7ea1215919051aa5ac76e4a9fbef2f33c5ad03ffff00b2", + "staking_transaction_unbonding_script_hex": "207aa0dafe26a7b663b679418f2c19fe7ea1215919051aa5ac76e4a9fbef2f33c5ad200f14729c14b6a9b292e7b5142fc5483d7c25196af1ef4b9722ea5821719e2c40ac200feacb370b7fcfcdd5ee7a363d73fda02d596ce61fd00e308d0ab68a7b955f48ba201e435d3bbab5fb0244c273e38d6f078bfe57a1bd151016ab5c4ac84457d724b3ba201fccb5af3a431a928c32267e556f1ba256871f98428419fae1cce6e7b8356cb9ba202367edcf01edd517d46c1b3cc3d40f5a28608e0313636dd09b0904799bccd635ba20243b84dd7a170a6e1edc32b22357b20f8365a4149a12ef1d340342701d535991ba2036612620ae82332e4f5da8d78d0d8a4ff7c8b09e88ee988b3dde18ef6a04c497ba20378f464f4365136230d30c3ad7d6425457d6c0742ed7fefd1015f0040cc70769ba2039eca9e5cf04e22471d6cde649604e6fd0aae077cd5b1af9a1dc26e56d8502f3ba20583c7fbf7450f8f78977b8c9f2981ff10676efefb6399ec925ba54eed7978a2eba2064df415d63ef3ce23ddd639c4a5ec8fdbfa67254db91228ca77fbec9610367aeba207947f704a584a804c91d07250239ec528300bd01dd3137770f7227309f63db0aba209010f319cde80190717d96dd60823b8e05905a2da458c69f4de19d5c9226cca8ba20a63763c4100bacdb1c06e5e1ba5599e379a2e23553d56d2b1d0ee3c990bd1192ba20db2bb56299fe3219ea5901198d1872f0523234d529a29fa912a76c9cb6d70db6ba20dbf2c13d6e3b0a8ad7286966bd00164ca0a163b483d305df6b492119cbffbabbba20e0647bb72f1b3d33d69f360d7e9f58f6a693d40b6ef58f49f140a212bf16931cba20e3803a6ecff76daf35709c8484f382783d211970f22397d7a258f40ca3b46304ba20f399ff078d6d954a390f67989705ed21e575ccf43a1340ca8752cd1613ca7ce0ba20f6a33481886c7d5ed5429c3bb0a83dbdefafd6d140b22a87aa7081cf7073136cba01129c", + "staking_transaction_slashing_script_hex": "207aa0dafe26a7b663b679418f2c19fe7ea1215919051aa5ac76e4a9fbef2f33c5ad2029f526a81bc92dd2ed8cf1682fc4ae5956987f3f6d3c0da71fbcf230c71781ffac204bcfb4042e5d13286fcd3d481b81a61cdfc3715aaf7909c4ee93de49356cd0b4ba2050eff2669c6d83f858937cd9229fc93ad57c10cc8d8cfb7ec9bc40e32a9f7ff2ba2078c5d7a263ab7e98e311c8d2d7305d13d78a014d4ae0603b427bb55cb42dbed1ba207bb40e3cb76545a71fafcb29738fb27b452c3176b41ffd614f7231b40ff5ce68ba20a7165ffc90d12c25187b75b2ebdcb57b1c759e357d655d8371cfb96dc4df2eccba20ae3c70699ae235ddebef85378a550f796723a6b961dcff1f33716d5dbb096a1eba20cca2d8aa1feb47ad4933154ef8196db229cfbba676607c3c0583397d65a1e4eeba20f1209d7248936f9ff2fa7b0ec97e5c0d26e4f41ad6de9f2e423128780da5dfecba20fe96402099f8387ce0ddef84bee2d0f869b35bb599a58b9aaec4a73db08dc490ba519d200f14729c14b6a9b292e7b5142fc5483d7c25196af1ef4b9722ea5821719e2c40ac200feacb370b7fcfcdd5ee7a363d73fda02d596ce61fd00e308d0ab68a7b955f48ba201e435d3bbab5fb0244c273e38d6f078bfe57a1bd151016ab5c4ac84457d724b3ba201fccb5af3a431a928c32267e556f1ba256871f98428419fae1cce6e7b8356cb9ba202367edcf01edd517d46c1b3cc3d40f5a28608e0313636dd09b0904799bccd635ba20243b84dd7a170a6e1edc32b22357b20f8365a4149a12ef1d340342701d535991ba2036612620ae82332e4f5da8d78d0d8a4ff7c8b09e88ee988b3dde18ef6a04c497ba20378f464f4365136230d30c3ad7d6425457d6c0742ed7fefd1015f0040cc70769ba2039eca9e5cf04e22471d6cde649604e6fd0aae077cd5b1af9a1dc26e56d8502f3ba20583c7fbf7450f8f78977b8c9f2981ff10676efefb6399ec925ba54eed7978a2eba2064df415d63ef3ce23ddd639c4a5ec8fdbfa67254db91228ca77fbec9610367aeba207947f704a584a804c91d07250239ec528300bd01dd3137770f7227309f63db0aba209010f319cde80190717d96dd60823b8e05905a2da458c69f4de19d5c9226cca8ba20a63763c4100bacdb1c06e5e1ba5599e379a2e23553d56d2b1d0ee3c990bd1192ba20db2bb56299fe3219ea5901198d1872f0523234d529a29fa912a76c9cb6d70db6ba20dbf2c13d6e3b0a8ad7286966bd00164ca0a163b483d305df6b492119cbffbabbba20e0647bb72f1b3d33d69f360d7e9f58f6a693d40b6ef58f49f140a212bf16931cba20e3803a6ecff76daf35709c8484f382783d211970f22397d7a258f40ca3b46304ba20f399ff078d6d954a390f67989705ed21e575ccf43a1340ca8752cd1613ca7ce0ba20f6a33481886c7d5ed5429c3bb0a83dbdefafd6d140b22a87aa7081cf7073136cba01129c", + "unbonding_transaction_hex": "020000000100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff01605af4050000000022512044b4c847be70ba60488426ae7c59a636dc952b8494fb2f01584c9f533cd4b07c00000000", + "unbonding_transaction_time_lock_script_hex": "207aa0dafe26a7b663b679418f2c19fe7ea1215919051aa5ac76e4a9fbef2f33c5ad02c900b2", + "unbonding_transaction_slashing_script_hex": "207aa0dafe26a7b663b679418f2c19fe7ea1215919051aa5ac76e4a9fbef2f33c5ad2029f526a81bc92dd2ed8cf1682fc4ae5956987f3f6d3c0da71fbcf230c71781ffac204bcfb4042e5d13286fcd3d481b81a61cdfc3715aaf7909c4ee93de49356cd0b4ba2050eff2669c6d83f858937cd9229fc93ad57c10cc8d8cfb7ec9bc40e32a9f7ff2ba2078c5d7a263ab7e98e311c8d2d7305d13d78a014d4ae0603b427bb55cb42dbed1ba207bb40e3cb76545a71fafcb29738fb27b452c3176b41ffd614f7231b40ff5ce68ba20a7165ffc90d12c25187b75b2ebdcb57b1c759e357d655d8371cfb96dc4df2eccba20ae3c70699ae235ddebef85378a550f796723a6b961dcff1f33716d5dbb096a1eba20cca2d8aa1feb47ad4933154ef8196db229cfbba676607c3c0583397d65a1e4eeba20f1209d7248936f9ff2fa7b0ec97e5c0d26e4f41ad6de9f2e423128780da5dfecba20fe96402099f8387ce0ddef84bee2d0f869b35bb599a58b9aaec4a73db08dc490ba519d200f14729c14b6a9b292e7b5142fc5483d7c25196af1ef4b9722ea5821719e2c40ac200feacb370b7fcfcdd5ee7a363d73fda02d596ce61fd00e308d0ab68a7b955f48ba201e435d3bbab5fb0244c273e38d6f078bfe57a1bd151016ab5c4ac84457d724b3ba201fccb5af3a431a928c32267e556f1ba256871f98428419fae1cce6e7b8356cb9ba202367edcf01edd517d46c1b3cc3d40f5a28608e0313636dd09b0904799bccd635ba20243b84dd7a170a6e1edc32b22357b20f8365a4149a12ef1d340342701d535991ba2036612620ae82332e4f5da8d78d0d8a4ff7c8b09e88ee988b3dde18ef6a04c497ba20378f464f4365136230d30c3ad7d6425457d6c0742ed7fefd1015f0040cc70769ba2039eca9e5cf04e22471d6cde649604e6fd0aae077cd5b1af9a1dc26e56d8502f3ba20583c7fbf7450f8f78977b8c9f2981ff10676efefb6399ec925ba54eed7978a2eba2064df415d63ef3ce23ddd639c4a5ec8fdbfa67254db91228ca77fbec9610367aeba207947f704a584a804c91d07250239ec528300bd01dd3137770f7227309f63db0aba209010f319cde80190717d96dd60823b8e05905a2da458c69f4de19d5c9226cca8ba20a63763c4100bacdb1c06e5e1ba5599e379a2e23553d56d2b1d0ee3c990bd1192ba20db2bb56299fe3219ea5901198d1872f0523234d529a29fa912a76c9cb6d70db6ba20dbf2c13d6e3b0a8ad7286966bd00164ca0a163b483d305df6b492119cbffbabbba20e0647bb72f1b3d33d69f360d7e9f58f6a693d40b6ef58f49f140a212bf16931cba20e3803a6ecff76daf35709c8484f382783d211970f22397d7a258f40ca3b46304ba20f399ff078d6d954a390f67989705ed21e575ccf43a1340ca8752cd1613ca7ce0ba20f6a33481886c7d5ed5429c3bb0a83dbdefafd6d140b22a87aa7081cf7073136cba01129c", + "op_return_script_hex": "" + } + } +] diff --git a/rust/chains/tw_bitcoincash/src/address.rs b/rust/chains/tw_bitcoincash/src/address.rs index 748a8636f0f..af23b0bd7ce 100644 --- a/rust/chains/tw_bitcoincash/src/address.rs +++ b/rust/chains/tw_bitcoincash/src/address.rs @@ -7,7 +7,7 @@ use std::fmt; use std::str::FromStr; use tw_coin_entry::coin_context::CoinContext; use tw_coin_entry::coin_entry::CoinAddress; -use tw_coin_entry::error::prelude::{AddressError, AddressResult}; +use tw_coin_entry::error::prelude::*; use tw_coin_entry::prefix::BitcoinBase58Prefix; use tw_keypair::ecdsa; use tw_memory::Data; diff --git a/rust/chains/tw_bitcoincash/src/cash_address/unchecked.rs b/rust/chains/tw_bitcoincash/src/cash_address/unchecked.rs index 9a4a0d5c7fa..4e1de1490e2 100644 --- a/rust/chains/tw_bitcoincash/src/cash_address/unchecked.rs +++ b/rust/chains/tw_bitcoincash/src/cash_address/unchecked.rs @@ -6,7 +6,7 @@ use crate::cash_address::checksum::{calculate_checksum, CHECKSUM_LEN}; use crate::cash_address::{cash_base32, CashAddress, CashAddressType}; use std::fmt; use std::str::FromStr; -use tw_coin_entry::error::prelude::{AddressError, AddressResult}; +use tw_coin_entry::error::prelude::*; use tw_encoding::bech32; use tw_hash::H160; use tw_memory::Data; diff --git a/rust/chains/tw_pactus/src/encoder/error.rs b/rust/chains/tw_pactus/src/encoder/error.rs index b3643854a90..4baba85527f 100644 --- a/rust/chains/tw_pactus/src/encoder/error.rs +++ b/rust/chains/tw_pactus/src/encoder/error.rs @@ -1,4 +1,4 @@ -use tw_coin_entry::error::prelude::{SigningError, SigningErrorType}; +use tw_coin_entry::error::prelude::*; /// Errors encountered when encoding or decoding data. #[derive(Debug)] diff --git a/rust/chains/tw_solana/src/compiler.rs b/rust/chains/tw_solana/src/compiler.rs index 818f04a8ce9..77f991477c9 100644 --- a/rust/chains/tw_solana/src/compiler.rs +++ b/rust/chains/tw_solana/src/compiler.rs @@ -8,7 +8,6 @@ use crate::modules::proto_builder::ProtoBuilder; use crate::modules::tx_signer::TxSigner; use crate::SOLANA_ALPHABET; use std::borrow::Cow; -use std::collections::HashMap; use tw_coin_entry::coin_context::CoinContext; use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; use tw_coin_entry::error::prelude::*; @@ -79,11 +78,13 @@ impl SolanaCompiler { } let builder = MessageBuilder::new(input); + // `key_signs` is pre-initialized with external signatures present in the raw transaction already. + // Later, this will be extended with the `signatures` provided by the user. + let mut key_signs = builder.external_signatures()?; let unsigned_msg = builder.build()?; let data_to_sign = TxSigner::preimage_versioned(&unsigned_msg)?; // Verify the given signatures and collect the key-signature map. - let mut key_signs = HashMap::default(); for (sign, pubkey) in signatures.iter().zip(public_keys.iter()) { let signature = ed25519::Signature::try_from(sign.as_slice())?; let pubkey = ed25519::sha512::PublicKey::try_from(pubkey.as_slice())?; diff --git a/rust/chains/tw_solana/src/modules/message_decompiler.rs b/rust/chains/tw_solana/src/modules/message_decompiler.rs index d74e6393785..70b8c556250 100644 --- a/rust/chains/tw_solana/src/modules/message_decompiler.rs +++ b/rust/chains/tw_solana/src/modules/message_decompiler.rs @@ -5,7 +5,7 @@ use crate::address::SolanaAddress; use crate::transaction::versioned::VersionedMessage; use crate::transaction::CompiledInstruction; -use tw_coin_entry::error::prelude::{OrTWError, ResultContext, SigningErrorType, SigningResult}; +use tw_coin_entry::error::prelude::*; use tw_memory::Data; /// [`Instruction`] without `accounts` field. diff --git a/rust/frameworks/tw_ton_sdk/src/address/user_friendly_address.rs b/rust/frameworks/tw_ton_sdk/src/address/user_friendly_address.rs index b1723e108d1..514dc58bda7 100644 --- a/rust/frameworks/tw_ton_sdk/src/address/user_friendly_address.rs +++ b/rust/frameworks/tw_ton_sdk/src/address/user_friendly_address.rs @@ -4,7 +4,7 @@ use crate::address::address_data::AddressData; use crate::crc::CRC_16_XMODEM; -use tw_coin_entry::error::prelude::{AddressError, AddressResult}; +use tw_coin_entry::error::prelude::*; use tw_encoding::base64; use tw_encoding::base64::{NO_PAD, URL_NO_PAD}; use tw_hash::{H256, H288}; diff --git a/rust/frameworks/tw_ton_sdk/src/boc/binary_writer.rs b/rust/frameworks/tw_ton_sdk/src/boc/binary_writer.rs index 91d75f4430b..4264ecb55bb 100644 --- a/rust/frameworks/tw_ton_sdk/src/boc/binary_writer.rs +++ b/rust/frameworks/tw_ton_sdk/src/boc/binary_writer.rs @@ -4,7 +4,7 @@ use crate::error::{CellErrorType, CellResult}; use bitstream_io::{BigEndian, BitWrite, BitWriter, Numeric}; -use tw_coin_entry::error::prelude::{MapTWError, OrTWError, ResultContext}; +use tw_coin_entry::error::prelude::*; use tw_memory::Data; pub struct BinaryWriter { diff --git a/rust/frameworks/tw_ton_sdk/src/boc/boc_to_raw_boc.rs b/rust/frameworks/tw_ton_sdk/src/boc/boc_to_raw_boc.rs index c3f4e3d60e0..7957446c83b 100644 --- a/rust/frameworks/tw_ton_sdk/src/boc/boc_to_raw_boc.rs +++ b/rust/frameworks/tw_ton_sdk/src/boc/boc_to_raw_boc.rs @@ -11,7 +11,7 @@ use crate::error::{CellErrorType, CellResult}; use std::cell::RefCell; use std::collections::BTreeMap; use std::sync::Arc; -use tw_coin_entry::error::prelude::{OrTWError, ResultContext}; +use tw_coin_entry::error::prelude::*; use tw_hash::H256; type IndexedCellRef = RefCell; diff --git a/rust/frameworks/tw_ton_sdk/src/cell/cell_builder.rs b/rust/frameworks/tw_ton_sdk/src/cell/cell_builder.rs index 99d394ca169..a12f505d98c 100644 --- a/rust/frameworks/tw_ton_sdk/src/cell/cell_builder.rs +++ b/rust/frameworks/tw_ton_sdk/src/cell/cell_builder.rs @@ -11,7 +11,7 @@ use crate::cell::{Cell, CellArc}; use crate::error::{CellError, CellErrorType, CellResult}; use bitstream_io::Numeric; use std::sync::Arc; -use tw_coin_entry::error::prelude::{MapTWError, ResultContext}; +use tw_coin_entry::error::prelude::*; use tw_number::U256; const MAX_CELL_BITS: usize = 1023; diff --git a/rust/frameworks/tw_ton_sdk/src/cell/cell_parser.rs b/rust/frameworks/tw_ton_sdk/src/cell/cell_parser.rs index e7143ded0a3..0dadb162f2c 100644 --- a/rust/frameworks/tw_ton_sdk/src/cell/cell_parser.rs +++ b/rust/frameworks/tw_ton_sdk/src/cell/cell_parser.rs @@ -8,7 +8,7 @@ use crate::address::address_data::AddressData; use crate::error::{CellError, CellErrorType, CellResult}; use bitreader::BitReader; use num_bigint::BigUint; -use tw_coin_entry::error::prelude::{MapTWError, ResultContext}; +use tw_coin_entry::error::prelude::*; use tw_hash::H256; use tw_memory::Data; use tw_number::U256; diff --git a/rust/frameworks/tw_ton_sdk/src/cell/mod.rs b/rust/frameworks/tw_ton_sdk/src/cell/mod.rs index c6edaf7adfb..2ef481ca280 100644 --- a/rust/frameworks/tw_ton_sdk/src/cell/mod.rs +++ b/rust/frameworks/tw_ton_sdk/src/cell/mod.rs @@ -8,7 +8,7 @@ use crate::boc::binary_writer::BinaryWriter; use crate::cell::cell_parser::CellParser; use std::fmt; use std::sync::Arc; -use tw_coin_entry::error::prelude::{MapTWError, OrTWError, ResultContext}; +use tw_coin_entry::error::prelude::*; use tw_encoding::base64::{self, URL_NO_PAD}; use tw_encoding::hex::ToHex; use tw_hash::sha2::sha256; diff --git a/rust/frameworks/tw_utxo/src/context.rs b/rust/frameworks/tw_utxo/src/context.rs index f0fd5e7b109..3b9cb66fb63 100644 --- a/rust/frameworks/tw_utxo/src/context.rs +++ b/rust/frameworks/tw_utxo/src/context.rs @@ -4,7 +4,7 @@ use crate::script::Script; use std::str::FromStr; -use tw_coin_entry::error::prelude::{AddressError, SigningResult}; +use tw_coin_entry::error::prelude::*; pub struct AddressPrefixes { pub p2pkh_prefix: u8, diff --git a/rust/frameworks/tw_utxo/src/modules/sighash_computer.rs b/rust/frameworks/tw_utxo/src/modules/sighash_computer.rs index f2ee8880590..81029ac191a 100644 --- a/rust/frameworks/tw_utxo/src/modules/sighash_computer.rs +++ b/rust/frameworks/tw_utxo/src/modules/sighash_computer.rs @@ -65,7 +65,7 @@ where let utxo_args = UtxoPreimageArgs { input_index: signing_input_index, - script_pubkey: utxo.script_pubkey.clone(), + script_pubkey: utxo.reveal_script_pubkey.clone(), amount: utxo.amount, // TODO move `leaf_hash_code_separator` to `UtxoTaprootPreimageArgs`. leaf_hash_code_separator: utxo.leaf_hash_code_separator, @@ -91,15 +91,14 @@ where .input_args() .iter() .enumerate() - .map(|(i, utxo)| { - if i == signing_input_index { - // Use the scriptPubkey required to spend this UTXO. - utxo.script_pubkey.clone() - } else { - // Use the original scriptPubkey declared in the unspent output for other UTXOs - // (different from that we sign at this iteration). - utxo.prevout_script_pubkey.clone() - } + .map(|(i, utxo)| match utxo.taproot_reveal_script_pubkey { + // Use the scriptPubkey required to spend this UTXO. + Some(ref tr_reveal_script) if i == signing_input_index => { + tr_reveal_script.clone() + }, + // Use the original scriptPubkey declared in the unspent output for other UTXOs + // (different from that we sign at this iteration). + _ => utxo.prevout_script_pubkey.clone(), }) .collect(); diff --git a/rust/frameworks/tw_utxo/src/script/mod.rs b/rust/frameworks/tw_utxo/src/script/mod.rs index 7600a1e5ba8..dbcfc3de181 100644 --- a/rust/frameworks/tw_utxo/src/script/mod.rs +++ b/rust/frameworks/tw_utxo/src/script/mod.rs @@ -89,6 +89,26 @@ impl Script { self.bytes.extend_from_slice(data); } + /// Adds instructions to push an integer onto the stack. + /// + /// Integers are encoded as little-endian signed-magnitude numbers, but there are dedicated + /// opcodes to push some small integers. + pub fn push_int(&mut self, data: i64) { + // We can special-case -1, 1-16 + if data == -1 || (1..=16).contains(&data) { + let opcode = (data - 1 + OP_TRUE as i64) as u8; + self.push(opcode) + } + // We can also special-case zero + else if data == 0 { + self.push(OP_0) + } + // Otherwise encode it as data + else { + self.push_int_non_minimal(data) + } + } + /// Appends the given data to the end of the script as-is. pub fn append(&mut self, data: &[u8]) { self.bytes.extend_from_slice(data); @@ -101,6 +121,16 @@ impl Script { pub fn to_vec(&self) -> Data { self.bytes.clone() } + + /// Adds instructions to push an integer onto the stack without optimization. + /// + /// This uses the explicit encoding regardless of the availability of dedicated opcodes. + fn push_int_non_minimal(&mut self, data: i64) { + let mut buf = [0u8; 8]; + // Use rust-bitcoin crate for now. + let len = bitcoin::script::write_scriptint(&mut buf, data); + self.push_slice(&buf[..len]) + } } impl From