From 00a87444218221944b45a7481a2d5d999be8f370 Mon Sep 17 00:00:00 2001 From: Viacheslav Kulish <32914239+vcoolish@users.noreply.github.com> Date: Tue, 1 Oct 2024 03:18:16 -0700 Subject: [PATCH 01/23] Kotlin multiplatform leaking memory (#4037) * Add deinit for KMP iOS and JVM targets * Add deinit for JS target * Add deinit for JS target * Fix JVM native name * Reuse one thread on JVM --------- Co-authored-by: satoshiotomakan <127754187+satoshiotomakan@users.noreply.github.com> --- .../lib/templates/kotlin/android_class.erb | 9 ++++ codegen/lib/templates/kotlin/ios_class.erb | 6 +++ .../templates/kotlin/js_accessors_class.erb | 3 ++ codegen/lib/templates/kotlin/js_class.erb | 15 ++++++ .../core/GenericPhantomReference.kt | 47 +++++++++++++++++++ 5 files changed, 80 insertions(+) create mode 100644 kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/GenericPhantomReference.kt diff --git a/codegen/lib/templates/kotlin/android_class.erb b/codegen/lib/templates/kotlin/android_class.erb index 3091b094ca7..e853300300c 100644 --- a/codegen/lib/templates/kotlin/android_class.erb +++ b/codegen/lib/templates/kotlin/android_class.erb @@ -9,6 +9,9 @@ actual class <%= entity.name %> private constructor( init { if (nativeHandle == 0L) throw IllegalArgumentException() +<% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%> + GenericPhantomReference.register(this, nativeHandle, ::delete) +<% end -%> } <%# Constructors -%> <%- constructors.each do |constructor| -%> @@ -52,6 +55,12 @@ actual class <%= entity.name %> private constructor( @JvmStatic @JvmName("createFromNative") private fun createFromNative(nativeHandle: Long) = <%= entity.name %>(nativeHandle) + +<% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%> + @JvmStatic + @JvmName("delete") + private external fun delete(handle: Long) +<%- end -%> <%- constructors.each do |constructor| -%> @JvmStatic diff --git a/codegen/lib/templates/kotlin/ios_class.erb b/codegen/lib/templates/kotlin/ios_class.erb index 36bc94e8276..240b2dc7f23 100644 --- a/codegen/lib/templates/kotlin/ios_class.erb +++ b/codegen/lib/templates/kotlin/ios_class.erb @@ -9,6 +9,12 @@ import kotlinx.cinterop.CPointer 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 -> + TW<%= entity.name %>Delete(ptr) + } +<% end -%> <%# Constructors -%> <%- constructors.each do |constructor| -%> diff --git a/codegen/lib/templates/kotlin/js_accessors_class.erb b/codegen/lib/templates/kotlin/js_accessors_class.erb index 77645552c83..3e3577d4ead 100644 --- a/codegen/lib/templates/kotlin/js_accessors_class.erb +++ b/codegen/lib/templates/kotlin/js_accessors_class.erb @@ -8,6 +8,9 @@ external interface Js<%= entity.name %> { <%- entity.properties.each do |property| -%> fun <%= KotlinHelper.fix_name(WasmCppHelper.format_name(property.name)) %>()<%= KotlinHelper.js_return_type(property.return_type) %> <%- end -%> +<% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%> + fun delete() +<% end -%> <% entity.methods.each do |method| -%> <% next if method.name == "Delete" -%> fun <%= KotlinHelper.fix_name(WasmCppHelper.format_name(method.name)) %>(<%= KotlinHelper.js_parameters(method.parameters.drop(1)) %>)<%= KotlinHelper.js_return_type(method.return_type) %> diff --git a/codegen/lib/templates/kotlin/js_class.erb b/codegen/lib/templates/kotlin/js_class.erb index 14cd6ef5e40..5241d394cb5 100644 --- a/codegen/lib/templates/kotlin/js_class.erb +++ b/codegen/lib/templates/kotlin/js_class.erb @@ -6,6 +6,21 @@ actual class <%= entity.name %> constructor( val jsValue: Js<%= entity.name %>, ) { +<% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%> + private val finalizationRegistry = + js( + """ + new FinalizationRegistry(function(heldValue) { + heldValue.delete(); + }) + """ + ) + + init { + finalizationRegistry.register(this, jsValue) + } +<% end -%> + <%# Constructors -%> <%- constructors.each do |constructor| -%> diff --git a/kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/GenericPhantomReference.kt b/kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/GenericPhantomReference.kt new file mode 100644 index 00000000000..b6ca8dd3653 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/GenericPhantomReference.kt @@ -0,0 +1,47 @@ +package com.trustwallet.core + +import java.lang.ref.PhantomReference +import java.lang.ref.ReferenceQueue + +internal class GenericPhantomReference private constructor( + referent: Any, + private val handle: Long, + private val onDelete: (Long) -> Unit, +) : PhantomReference(referent, queue) { + + companion object { + private val references: MutableSet = HashSet() + private val queue: ReferenceQueue = ReferenceQueue() + + init { + Thread { + try { + doDeletes() + } catch (e: InterruptedException) { + Thread.currentThread().interrupt() + } + }.apply { + name = "WCFinalizingDaemon" + isDaemon = true + priority = Thread.NORM_PRIORITY + start() + } + } + + fun register( + referent: Any, + handle: Long, + onDelete: (Long) -> Unit, + ) { + references.add(GenericPhantomReference(referent, handle, onDelete)) + } + + private fun doDeletes() { + while (true) { + val ref = queue.remove() as GenericPhantomReference + ref.onDelete(ref.handle) + references.remove(ref) + } + } + } +} From 2ed70985daab17e2014748ceff19ba6b210f9a1f Mon Sep 17 00:00:00 2001 From: 10gic Date: Tue, 1 Oct 2024 19:19:42 +0800 Subject: [PATCH 02/23] [KMP] Fix issue: memory leak found in Base58.decode in iOS (#4031) * Fix kmp issue: memory leak found in Base58.decode in iOS * Remove unused functions * Fix failed test cases * Revert "Fix failed test cases" This reverts commit 57eee395df508d95a5e570b1f54143b7de20eb31. * Revert val -> value argument name refactoring * Output better indentation * Revert changes in TWEthereumAbiFunction.h * Fix inconsistent naming --------- Co-authored-by: satoshiotomakan <127754187+satoshiotomakan@users.noreply.github.com> --- codegen/lib/kotlin_helper.rb | 25 +++++----- codegen/lib/templates/kotlin/ios_class.erb | 50 +++++++++++++++---- codegen/lib/templates/kotlin/ios_enum.erb | 8 ++- .../templates/kotlin/ios_parameter_access.erb | 27 ++++++++++ .../kotlin/ios_parameter_release.erb | 23 +++++++++ codegen/lib/templates/kotlin/ios_struct.erb | 10 +++- .../kotlin/com/trustwallet/core/AnySigner.kt | 28 ++++++++--- .../com/trustwallet/core/ByteArrayExt.kt | 11 ++-- .../kotlin/com/trustwallet/core/StringExt.kt | 11 ++-- 9 files changed, 149 insertions(+), 44 deletions(-) create mode 100644 codegen/lib/templates/kotlin/ios_parameter_access.erb create mode 100644 codegen/lib/templates/kotlin/ios_parameter_release.erb diff --git a/codegen/lib/kotlin_helper.rb b/codegen/lib/kotlin_helper.rb index b37537b4fa4..b99bf1a631d 100644 --- a/codegen/lib/kotlin_helper.rb +++ b/codegen/lib/kotlin_helper.rb @@ -31,7 +31,13 @@ def self.js_parameters(params) def self.calling_parameters_ios(params) names = params.map do |param| name = fix_name(param.name) + if param.type.name == :data + "#{name}Data#{convert_calling_type_ios(param.type)}" + elsif param.type.name == :string + "#{name}String#{convert_calling_type_ios(param.type)}" + else "#{name}#{convert_calling_type_ios(param.type)}" + end end names.join(', ') end @@ -56,7 +62,7 @@ def self.fix_name(name) when '' "value" when 'val' - "value" + "value" when 'return' '`return`' else @@ -65,19 +71,12 @@ def self.fix_name(name) end def self.convert_calling_type_ios(t) - case t.name - when :data - "#{if t.is_nullable then '?' else '' end}.toTwData()" - when :string - "#{if t.is_nullable then '?' else '' end}.toTwString()" + if t.is_enum + "#{if t.is_nullable then '?' else '' end}.nativeValue" + elsif t.is_class + "#{if t.is_nullable then '?' else '' end}.pointer" else - if t.is_enum - "#{if t.is_nullable then '?' else '' end}.nativeValue" - elsif t.is_class - "#{if t.is_nullable then '?' else '' end}.pointer" - else - '' - end + '' end end diff --git a/codegen/lib/templates/kotlin/ios_class.erb b/codegen/lib/templates/kotlin/ios_class.erb index 240b2dc7f23..94d7a42f24b 100644 --- a/codegen/lib/templates/kotlin/ios_class.erb +++ b/codegen/lib/templates/kotlin/ios_class.erb @@ -2,6 +2,7 @@ import cnames.structs.TW<%= entity.name %> import kotlinx.cinterop.CPointer +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') } -%> @@ -20,12 +21,9 @@ actual class <%= entity.name %> constructor( <% if constructor.return_type.is_nullable -%> @Throws(IllegalArgumentException::class) - actual constructor(<%= KotlinHelper.parameters(constructor.parameters) %>) : this( - TW<%= entity.name %><%= constructor.name %>(<%= KotlinHelper.calling_parameters_ios(constructor.parameters) %>) ?: throw IllegalArgumentException() -<% else -%> - actual constructor(<%= KotlinHelper.parameters(constructor.parameters) %>) : this( - TW<%= entity.name %><%= constructor.name %>(<%= KotlinHelper.calling_parameters_ios(constructor.parameters) %>)!! <% end -%> + actual constructor(<%= KotlinHelper.parameters(constructor.parameters) %>) : this( + wrapperTW<%= entity.name %><%= constructor.name %>(<%= KotlinHelper.arguments(constructor.parameters) %>) ) <% end -%> <%# Property declarations -%> @@ -37,12 +35,38 @@ actual class <%= entity.name %> constructor( <%# Method declarations -%> <% methods.each do |method| -%> - actual fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters.drop(1)) %>)<%= KotlinHelper.return_type(method.return_type) %> = - <%= KotlinHelper.convert_calling_return_type_ios(method.return_type, "TW#{entity.name}#{method.name}(pointer#{', ' if not method.parameters.one?}#{KotlinHelper.calling_parameters_ios(method.parameters.drop(1))})") %> + actual fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters.drop(1)) %>)<%= KotlinHelper.return_type(method.return_type) %> { +<%= render('kotlin/ios_parameter_access.erb', { method: method }) -%> + val result = <%= KotlinHelper.convert_calling_return_type_ios(method.return_type, "TW#{entity.name}#{method.name}(pointer#{', ' if not method.parameters.one?}#{KotlinHelper.calling_parameters_ios(method.parameters.drop(1))})") %> +<%= render('kotlin/ios_parameter_release.erb', { method: method }) -%> + return result + } <% end -%> -<% if entity.static_properties.any? || static_methods.any? -%> +<% if entity.static_properties.any? || static_methods.any? || constructors.any? -%> + + <%= if entity.static_properties.any? || static_methods.any? then "actual" else "private" end %> companion object { +<%# Constructor wrappers -%> +<% if constructors.any? -%> +<% constructors.each do |constructor| -%> - actual companion object { +<% if constructor.return_type.is_nullable -%> + @Throws(IllegalArgumentException::class) +<% end -%> + private fun wrapperTW<%= entity.name %><%= constructor.name %>(<%= KotlinHelper.parameters(constructor.parameters) %>): CPointer> { +<%= render('kotlin/ios_parameter_access.erb', { method: constructor, more_index: 4 }) -%> + val result = TW<%= entity.name %><%= constructor.name %>(<%= KotlinHelper.calling_parameters_ios(constructor.parameters) %>) +<%= render('kotlin/ios_parameter_release.erb', { method: constructor, more_index: 4 }) -%> +<% if constructor.return_type.is_nullable -%> + if (result == null) { + throw IllegalArgumentException() + } + return result +<% else -%> + return result!! +<% end -%> + } +<% end -%> +<% end -%> <%# Static property declarations -%> <% entity.static_properties.each do |property| -%> @@ -52,8 +76,12 @@ actual class <%= entity.name %> constructor( <%# Static method declarations -%> <% static_methods.each do |method| -%> - actual fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters) %>)<%= KotlinHelper.return_type(method.return_type) %> = - <%= KotlinHelper.convert_calling_return_type_ios(method.return_type, "TW#{entity.name}#{method.name}(#{KotlinHelper.calling_parameters_ios(method.parameters)})") %> + actual fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters) %>)<%= KotlinHelper.return_type(method.return_type) %> { +<%= render('kotlin/ios_parameter_access.erb', { method: method, more_index: 4 }) -%> + val result = <%= KotlinHelper.convert_calling_return_type_ios(method.return_type, "TW#{entity.name}#{method.name}(#{KotlinHelper.calling_parameters_ios(method.parameters)})") %> +<%= render('kotlin/ios_parameter_release.erb', { method: method, more_index: 4 }) -%> + return result + } <% end -%> } <% end -%> diff --git a/codegen/lib/templates/kotlin/ios_enum.erb b/codegen/lib/templates/kotlin/ios_enum.erb index 5f64c80a7fa..16d26ba89b4 100644 --- a/codegen/lib/templates/kotlin/ios_enum.erb +++ b/codegen/lib/templates/kotlin/ios_enum.erb @@ -41,8 +41,12 @@ actual enum class <%= entity.name %>( <%- entity.methods.each do |method| -%> <%- next if method.name.start_with?('Delete') -%> - actual fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters.drop(1)) %>)<%= KotlinHelper.return_type(method.return_type) %> = - TW<%= entity.name %><%= method.name %>(value<%= ', ' if not method.parameters.one? %><%= KotlinHelper.calling_parameters_ios(method.parameters.drop(1)) %>)<%= KotlinHelper.convert_calling_return_type_ios(method.return_type) %> + actual fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters.drop(1)) %>)<%= KotlinHelper.return_type(method.return_type) %> { +<%= render('kotlin/ios_parameter_access.erb', { method: method }) -%> + val result = TW<%= entity.name %><%= method.name %>(value<%= ', ' if not method.parameters.one? %><%= KotlinHelper.calling_parameters_ios(method.parameters.drop(1)) %>)<%= KotlinHelper.convert_calling_return_type_ios(method.return_type) %> +<%= render('kotlin/ios_parameter_release.erb', { method: method }) -%> + return result + } <%- end -%> <%# Value -%> <% if entity.cases.any? { |e| !e.value.nil? } -%> diff --git a/codegen/lib/templates/kotlin/ios_parameter_access.erb b/codegen/lib/templates/kotlin/ios_parameter_access.erb new file mode 100644 index 00000000000..b16ce477107 --- /dev/null +++ b/codegen/lib/templates/kotlin/ios_parameter_access.erb @@ -0,0 +1,27 @@ +<% + method = locals[:method] + more_index = locals[:more_index] || 0 + + method.parameters.each do |param| -%> +<% if param.type.name == :data -%> +<% if param.type.is_nullable -%> +<%= ' ' * more_index %> val <%= KotlinHelper.fix_name(param.name) %>Data = if (<%= KotlinHelper.fix_name(param.name) %> == null) { +<%= ' ' * more_index %> null +<%= ' ' * more_index %> } else { +<%= ' ' * more_index %> TWDataCreateWithBytes(<%= KotlinHelper.fix_name(param.name) %>.toUByteArray().toCValues(), <%= KotlinHelper.fix_name(param.name) %>.size.toULong()) +<%= ' ' * more_index %> } +<% else -%> +<%= ' ' * more_index %> val <%= KotlinHelper.fix_name(param.name) %>Data = TWDataCreateWithBytes(<%= KotlinHelper.fix_name(param.name) %>.toUByteArray().toCValues(), <%= KotlinHelper.fix_name(param.name) %>.size.toULong()) +<% end -%> +<% elsif param.type.name == :string -%> +<% if param.type.is_nullable -%> +<%= ' ' * more_index %> val <%= KotlinHelper.fix_name(param.name) %>String = if (<%= KotlinHelper.fix_name(param.name) %> == null) { +<%= ' ' * more_index %> null +<%= ' ' * more_index %> } else { +<%= ' ' * more_index %> TWStringCreateWithUTF8Bytes(<%= KotlinHelper.fix_name(param.name) %>) +<%= ' ' * more_index %> } +<% else -%> +<%= ' ' * more_index %> val <%= KotlinHelper.fix_name(param.name) %>String = TWStringCreateWithUTF8Bytes(<%= KotlinHelper.fix_name(param.name) %>) +<% end -%> +<% end -%> +<% end -%> diff --git a/codegen/lib/templates/kotlin/ios_parameter_release.erb b/codegen/lib/templates/kotlin/ios_parameter_release.erb new file mode 100644 index 00000000000..8f90bb6aeaf --- /dev/null +++ b/codegen/lib/templates/kotlin/ios_parameter_release.erb @@ -0,0 +1,23 @@ +<% + method = locals[:method] + more_index = locals[:more_index] || 0 + + method.parameters.each do |param| -%> +<% if param.type.name == :data -%> +<% if param.type.is_nullable -%> +<%= ' ' * more_index %> if (<%= KotlinHelper.fix_name(param.name) %>Data != null) { +<%= ' ' * more_index %> TWDataDelete(<%= KotlinHelper.fix_name(param.name) %>Data) +<%= ' ' * more_index %> } +<% else -%> +<%= ' ' * more_index %> TWDataDelete(<%= KotlinHelper.fix_name(param.name) %>Data) +<% end -%> +<% elsif param.type.name == :string -%> +<% if param.type.is_nullable -%> +<%= ' ' * more_index %> if (<%= KotlinHelper.fix_name(param.name) %>String != null) { +<%= ' ' * more_index %> TWStringDelete(<%= KotlinHelper.fix_name(param.name) %>String) +<%= ' ' * more_index %> } +<% else -%> +<%= ' ' * more_index %> TWStringDelete(<%= KotlinHelper.fix_name(param.name) %>String) +<% end -%> +<% end -%> +<% end -%> diff --git a/codegen/lib/templates/kotlin/ios_struct.erb b/codegen/lib/templates/kotlin/ios_struct.erb index 6bf3da876ee..08fbcc34ef8 100644 --- a/codegen/lib/templates/kotlin/ios_struct.erb +++ b/codegen/lib/templates/kotlin/ios_struct.erb @@ -1,5 +1,7 @@ <%= render('kotlin/package.erb') %> +import kotlinx.cinterop.toCValues + actual object <%= entity.name %> { <%# Static property declarations -%> <% entity.static_properties.each do |property| -%> @@ -10,7 +12,11 @@ actual object <%= entity.name %> { <% entity.static_methods.each do |method| -%> <% next if method.name.start_with?('Create') -%> - actual fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters) %>)<%= KotlinHelper.return_type(method.return_type) %> = - <%= KotlinHelper.convert_calling_return_type_ios(method.return_type, "TW#{entity.name}#{method.name}(#{KotlinHelper.calling_parameters_ios(method.parameters)})") %> + actual fun <%= KotlinHelper.format_name(method.name) %>(<%= KotlinHelper.parameters(method.parameters) %>)<%= KotlinHelper.return_type(method.return_type) %> { +<%= render('kotlin/ios_parameter_access.erb', { method: method }) -%> + val result = <%= KotlinHelper.convert_calling_return_type_ios(method.return_type, "TW#{entity.name}#{method.name}(#{KotlinHelper.calling_parameters_ios(method.parameters)})") %> +<%= render('kotlin/ios_parameter_release.erb', { method: method }) -%> + return result + } <% end -%> } 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 6de0b110bea..480d260002f 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 @@ -4,17 +4,33 @@ package com.trustwallet.core +import kotlinx.cinterop.toCValues + actual object AnySigner { - actual fun sign(input: ByteArray, coin: CoinType): ByteArray = - TWAnySignerSign(input.toTwData(), coin.value)!!.readTwBytes()!! + actual fun sign(input: ByteArray, coin: CoinType): ByteArray { + val inputData = TWDataCreateWithBytes(input.toUByteArray().toCValues(), input.size.toULong()) + val result = TWAnySignerSign(inputData, coin.value)!!.readTwBytes()!! + TWDataDelete(inputData) + return result + } actual fun supportsJson(coin: CoinType): Boolean = TWAnySignerSupportsJSON(coin.value) - actual fun signJson(json: String, key: ByteArray, coin: CoinType): String = - TWAnySignerSignJSON(json.toTwString(), key.toTwData(), coin.value).fromTwString()!! + actual fun signJson(json: String, key: ByteArray, coin: CoinType): String { + val jsonString = TWStringCreateWithUTF8Bytes(json) + val keyData = TWDataCreateWithBytes(key.toUByteArray().toCValues(), key.size.toULong()) + val result = TWAnySignerSignJSON(jsonString, keyData, coin.value).fromTwString()!! + TWStringDelete(jsonString) + TWDataDelete(keyData) + return result + } - actual fun plan(input: ByteArray, coin: CoinType): ByteArray = - TWAnySignerPlan(input.toTwData(), coin.value)?.readTwBytes()!! + actual fun plan(input: ByteArray, coin: CoinType): ByteArray { + val inputData = TWDataCreateWithBytes(input.toUByteArray().toCValues(), input.size.toULong()) + val result = TWAnySignerPlan(inputData, coin.value)?.readTwBytes()!! + TWDataDelete(inputData) + return result + } } 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 ec5923272a1..bc537de453d 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 @@ -8,9 +8,10 @@ import kotlinx.cinterop.COpaquePointer import kotlinx.cinterop.readBytes import kotlinx.cinterop.toCValues +// Build ByteArray from TWData, and then delete TWData internal fun COpaquePointer?.readTwBytes(): ByteArray? = - TWDataBytes(this)?.readBytes(TWDataSize(this).toInt()) - -@OptIn(ExperimentalUnsignedTypes::class) -internal fun ByteArray?.toTwData(): COpaquePointer? = - TWDataCreateWithBytes(this?.toUByteArray()?.toCValues(), this?.size?.toULong() ?: 0u) + this?.let { + val result = TWDataBytes(it)?.readBytes(TWDataSize(it).toInt()) + TWDataDelete(it) + result + } 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 2eb7fdd311e..79cd8125d84 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 @@ -4,12 +4,13 @@ package com.trustwallet.core -import kotlinx.cinterop.COpaquePointer import kotlinx.cinterop.CValuesRef import kotlinx.cinterop.toKString -internal fun String?.toTwString(): COpaquePointer? = - this?.let { TWStringCreateWithUTF8Bytes(it) } - +// Build String from TWString, and then delete TWString internal fun CValuesRef<*>?.fromTwString(): String? = - this?.let { TWStringUTF8Bytes(it)?.toKString() } + this?.let { + val result = TWStringUTF8Bytes(it)?.toKString() + TWStringDelete(it) + result + } From 0b167710b325d299398ca82ef57ee8e630e54633 Mon Sep 17 00:00:00 2001 From: satoshiotomakan <127754187+satoshiotomakan@users.noreply.github.com> Date: Fri, 4 Oct 2024 15:49:54 +0700 Subject: [PATCH 03/23] [TON]: Add support for TON 24-words mnemonic (#3998) * feat(ton): Add support for TON 24-words mnemonic in Rust * feat(ton): Add tw_ton_wallet FFIs * feat(ton): Add TWTONWallet FFI in C++ * feat(ton): Add tonMnemonic StoredKey type * feat(ton): Add StoredKey TON tests * feat(ton): Add TWStoredKey TON tests * feat(ton): Add TONWallet support in Swift * TODO add iOS tests * feat(ton): Add `KeyStore` iOS tests * feat(ton): Add TONWallet support in JavaScript * Add `KeyStore` TypeScript tests * feat(ton): Remove `TonMnemonic` structure, replace with a `validate_mnemonic_words` function * [CI] Trigger CI * feat(ton): Fix rustfmt * feat(ton): Fix C++ build * feat(ton): Fix C++ build * feat(ton): Fix C++ build * feat(ton): Fix C++ address analyzer * feat(ton): Fix C++ tests * feat(ton): Add Android tests * feat(ton): Bump `actions/upload-artifact` to v4 * Bump `dawidd6/action-download-artifact` to v6 * feat(eth): Fix PR comments --- .../TestTheOpenNetworkWallet.kt | 17 + .../core/app/utils/TestKeyStore.kt | 48 + include/TrustWalletCore/TWStoredKey.h | 38 + include/TrustWalletCore/TWTONWallet.h | 36 +- rust/Cargo.lock | 28 + rust/Cargo.toml | 1 + rust/chains/tw_ton/Cargo.toml | 1 + .../src/test_utils/address_utils.rs | 4 +- rust/tw_hash/Cargo.toml | 1 + rust/tw_hash/src/ffi.rs | 2 + rust/tw_hash/src/hmac.rs | 15 +- rust/tw_hash/src/lib.rs | 2 + rust/tw_hash/src/pbkdf2.rs | 10 + rust/tw_hd_wallet/Cargo.toml | 14 + rust/tw_hd_wallet/src/bip39/bip39_english.rs | 2070 +++++++++++++++++ rust/tw_hd_wallet/src/bip39/mod.rs | 9 + rust/tw_hd_wallet/src/lib.rs | 18 + rust/tw_hd_wallet/src/ton/mnemonic.rs | 38 + rust/tw_hd_wallet/src/ton/mod.rs | 169 ++ rust/tw_hd_wallet/tests/ton_mnemonic.rs | 156 ++ rust/tw_keypair/src/ed25519/keypair.rs | 9 + rust/tw_keypair/src/ed25519/private.rs | 27 +- rust/tw_keypair/src/ffi/privkey.rs | 18 +- rust/tw_keypair/src/ffi/pubkey.rs | 2 +- .../src/test_utils/tw_crypto_box_helpers.rs | 6 +- .../src/test_utils/tw_private_key_helper.rs | 17 +- rust/tw_keypair/src/tw/private.rs | 13 + rust/tw_keypair/tests/crypto_box_ffi_tests.rs | 19 +- rust/tw_memory/Cargo.toml | 1 + rust/tw_memory/src/ffi/tw_data.rs | 13 + rust/tw_memory/src/test_utils/tw_wrapper.rs | 38 +- rust/tw_tests/tests/chains/ton/ton_wallet.rs | 32 +- rust/wallet_core_rs/Cargo.toml | 3 + rust/wallet_core_rs/src/ffi/mod.rs | 2 + rust/wallet_core_rs/src/ffi/ton/mod.rs | 1 - rust/wallet_core_rs/src/ffi/ton/wallet.rs | 55 - rust/wallet_core_rs/src/ffi/wallet/mod.rs | 5 + .../src/ffi/wallet/ton_wallet.rs | 128 + src/DerivationPath.cpp | 4 + src/Keystore/StoredKey.cpp | 112 +- src/Keystore/StoredKey.h | 44 +- src/TheOpenNetwork/TONWallet.cpp | 54 + src/TheOpenNetwork/TONWallet.h | 47 + src/interface/TWStoredKey.cpp | 26 + src/interface/TWTONWallet.cpp | 35 + src/rust/Wrapper.h | 8 +- swift/Sources/KeyStore.swift | 157 +- swift/Sources/Wallet.swift | 6 +- swift/Tests/Keystore/Data/ton_wallet.json | 30 + swift/Tests/Keystore/KeyStoreTests.swift | 111 +- swift/Tests/Keystore/WalletTests.swift | 50 + .../TheOpenNetwork/TWTONWalletTests.cpp | 19 + tests/common/Keystore/Data/ton-wallet.json | 30 + tests/common/Keystore/StoredKeyConstants.h | 19 + tests/common/Keystore/StoredKeyTONTests.cpp | 172 ++ tests/common/Keystore/StoredKeyTests.cpp | 22 +- tests/interface/TWStoredKeyTests.cpp | 126 +- wasm/src/keystore/default-impl.ts | 46 +- wasm/src/keystore/types.ts | 12 +- wasm/tests/KeyStore+extension.test.ts | 45 + wasm/tests/setup.test.ts | 2 + 61 files changed, 4041 insertions(+), 202 deletions(-) create mode 100644 rust/tw_hash/src/pbkdf2.rs create mode 100644 rust/tw_hd_wallet/Cargo.toml create mode 100644 rust/tw_hd_wallet/src/bip39/bip39_english.rs create mode 100644 rust/tw_hd_wallet/src/bip39/mod.rs create mode 100644 rust/tw_hd_wallet/src/lib.rs create mode 100644 rust/tw_hd_wallet/src/ton/mnemonic.rs create mode 100644 rust/tw_hd_wallet/src/ton/mod.rs create mode 100644 rust/tw_hd_wallet/tests/ton_mnemonic.rs delete mode 100644 rust/wallet_core_rs/src/ffi/ton/wallet.rs create mode 100644 rust/wallet_core_rs/src/ffi/wallet/mod.rs create mode 100644 rust/wallet_core_rs/src/ffi/wallet/ton_wallet.rs create mode 100644 src/TheOpenNetwork/TONWallet.cpp create mode 100644 src/TheOpenNetwork/TONWallet.h create mode 100644 swift/Tests/Keystore/Data/ton_wallet.json create mode 100644 tests/common/Keystore/Data/ton-wallet.json create mode 100644 tests/common/Keystore/StoredKeyConstants.h create mode 100644 tests/common/Keystore/StoredKeyTONTests.cpp diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkWallet.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkWallet.kt index 9305072bb75..b8f4b7d1b56 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkWallet.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkWallet.kt @@ -4,6 +4,7 @@ package com.trustwallet.core.app.blockchains.theopennetwork +import com.trustwallet.core.app.utils.toHex import com.trustwallet.core.app.utils.toHexByteArray import org.junit.Assert.assertEquals import org.junit.Test @@ -26,4 +27,20 @@ class TestTheOpenNetworkWallet { val expected = "te6cckECFgEAAwQAAgE0AQIBFP8A9KQT9LzyyAsDAFEAAAAAKamjF/IpqTcfp8IQiz2Q6iLJvnBf9dDP6u6cu5Nm/wFxV5NXQAIBIAQFAgFIBgcE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8ICQoLAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNDA0CASAODwBu0gf6ANTUIvkABcjKBxXL/8nQd3SAGMjLBcsCIs8WUAX6AhTLaxLMzMlz+wDIQBSBAQj0UfKnAgBwgQEI1xj6ANM/yFQgR4EBCPRR8qeCEG5vdGVwdIAYyMsFywJQBs8WUAT6AhTLahLLH8s/yXP7AAIAbIEBCNcY+gDTPzBSJIEBCPRZ8qeCEGRzdHJwdIAYyMsFywJQBc8WUAP6AhPLassfEss/yXP7AAAK9ADJ7VQAeAH6APQEMPgnbyIwUAqhIb7y4FCCEHBsdWeDHrFwgBhQBMsFJs8WWPoCGfQAy2kXyx9SYMs/IMmAQPsABgCKUASBAQj0WTDtRNCBAUDXIMgBzxb0AMntVAFysI4jghBkc3Rygx6xcIAYUAXLBVADzxYj+gITy2rLH8s/yYBA+wCSXwPiAgEgEBEAWb0kK29qJoQICga5D6AhhHDUCAhHpJN9KZEM5pA+n/mDeBKAG3gQFImHFZ8xhAIBWBITABG4yX7UTQ1wsfgAPbKd+1E0IEBQNch9AQwAsjKB8v/ydABgQEI9ApvoTGACASAUFQAZrc52omhAIGuQ64X/wAAZrx32omhAEGuQ64WPwEXtMkg=" assertEquals(stateInit, expected) } + + @Test + fun TheOpenNetworkWalletIsValidMnemonic() { + val validMnemonic = "sight shed side garbage illness clean health wet all win bench wide exist find galaxy drift task suggest portion fresh valve crime radar combine" + val noPassphrase = "" + val invalidPassphrase = "Expected empty passphrase" + assert(TONWallet.isValidMnemonic(validMnemonic, noPassphrase)) + assert(!TONWallet.isValidMnemonic(validMnemonic, invalidPassphrase)) + } + + @Test + fun TheOpenNetworkWalletGetKey() { + val tonMnemonic = "sight shed side garbage illness clean health wet all win bench wide exist find galaxy drift task suggest portion fresh valve crime radar combine" + val wallet = TONWallet(tonMnemonic, "") + assertEquals(wallet.key.data().toHex(), "0xb471884e691a9f5bb641b14f33bb9e555f759c24e368c4c0d997db3a60704220") + } } \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt index 599d3369216..1843ea86257 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt @@ -4,6 +4,7 @@ import org.junit.Assert.* import org.junit.Test import wallet.core.jni.StoredKey import wallet.core.jni.CoinType +import wallet.core.jni.Derivation import wallet.core.jni.StoredKeyEncryption class TestKeyStore { @@ -17,9 +18,12 @@ class TestKeyStore { val keyStore = StoredKey("Test Wallet", "password".toByteArray()) val result = keyStore.decryptMnemonic("wrong".toByteArray()) val result2 = keyStore.decryptMnemonic("password".toByteArray()) + val result3 = keyStore.decryptTONMnemonic("password".toByteArray()) assertNull(result) assertNotNull(result2) + // StoredKey is an HD by default, so `decryptTONMnemonic` should return null. + assertNull(result3) } @Test @@ -91,4 +95,48 @@ class TestKeyStore { val privateKey = newKeyStore.decryptPrivateKey("".toByteArray()) assertNull(privateKey) } + + @Test + fun testImportTONWallet() { + val tonMnemonic = "laundry myself fitness beyond prize piano match acid vacuum already abandon dance occur pause grocery company inject excuse weasel carpet fog grunt trick spike" + val password = "password".toByteArray() + + val keyStore = StoredKey.importTONWallet(tonMnemonic, "Test Wallet", password, CoinType.TON) + + val decrypted1 = keyStore.decryptTONMnemonic("wrong".toByteArray()) + val decrypted2 = keyStore.decryptTONMnemonic("password".toByteArray()) + assertNull(decrypted1) + assertNotNull(decrypted2) + + assertEquals(keyStore.accountCount(), 1) + + // `StoredKey.account(index)` is only allowed. + // `StoredKey.accountForCoin(coin, wallet)` is not supported. + val tonAccount = keyStore.account(0) + assertEquals(tonAccount.address(), "UQDdB2lMwYM9Gxc-ln--Tu8cz-TYksQxYuUsMs2Pd4cHerYz") + assertEquals(tonAccount.coin(), CoinType.TON) + assertEquals(tonAccount.publicKey(), "c9af50596bd5c1c5a15fb32bef8d4f1ee5244b287aea1f49f6023a79f9b2f055") + assertEquals(tonAccount.extendedPublicKey(), "") + assertEquals(tonAccount.derivation(), Derivation.DEFAULT) + assertEquals(tonAccount.derivationPath(), "") + + val privateKey = keyStore.privateKey(CoinType.TON, password) + assertEquals(privateKey.data().toHex(), "0x859cd74ab605afb7ce9f5316a1f6d59217a130b75b494efd249913be874c9d46") + + // HD wallet is not supported for TON wallet + val hdWallet = keyStore.wallet(password) + assertNull(hdWallet) + } + + @Test + fun testExportTONWallet() { + val tonMnemonic = "laundry myself fitness beyond prize piano match acid vacuum already abandon dance occur pause grocery company inject excuse weasel carpet fog grunt trick spike" + val password = "password".toByteArray() + + val keyStore = StoredKey.importTONWallet(tonMnemonic, "Test Wallet", password, CoinType.TON) + val json = keyStore.exportJSON() + + val newKeyStore = StoredKey.importJSON(json) + assertEquals(newKeyStore.decryptTONMnemonic(password), tonMnemonic) + } } diff --git a/include/TrustWalletCore/TWStoredKey.h b/include/TrustWalletCore/TWStoredKey.h index 58a07e521c0..6df17e7a220 100644 --- a/include/TrustWalletCore/TWStoredKey.h +++ b/include/TrustWalletCore/TWStoredKey.h @@ -74,6 +74,29 @@ struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemo TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nullable TWStoredKeyImportHDWalletWithEncryption(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption); +/// Imports a TON-specific wallet with a 24-words mnemonic. +/// +/// \param tonMnemonic Non-null TON mnemonic +/// \param name The name of the stored key to import as a non-null string +/// \param password Non-null block of data, password of the stored key +/// \param coin the coin type +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return Nullptr if the key can't be imported, the stored key otherwise +TW_EXPORT_STATIC_METHOD +struct TWStoredKey* _Nullable TWStoredKeyImportTONWallet(TWString* _Nonnull tonMnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin); + +/// Imports a TON-specific wallet with a 24-words mnemonic. +/// +/// \param tonMnemonic Non-null TON mnemonic +/// \param name The name of the stored key to import as a non-null string +/// \param password Non-null block of data, password of the stored key +/// \param coin the coin type +/// \param encryption cipher encryption mode +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return Nullptr if the key can't be imported, the stored key otherwise +TW_EXPORT_STATIC_METHOD +struct TWStoredKey* _Nullable TWStoredKeyImportTONWalletWithEncryption(TWString* _Nonnull tonMnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption); + /// Imports a key from JSON. /// /// \param json Json stored key import format as a non-null block of data @@ -152,6 +175,13 @@ TWString* _Nonnull TWStoredKeyName(struct TWStoredKey* _Nonnull key); TW_EXPORT_PROPERTY bool TWStoredKeyIsMnemonic(struct TWStoredKey* _Nonnull key); +/// Whether this key is a TON mnemonic phrase. +/// +/// \param key Non-null pointer to a stored key +/// \return true if the given stored key is a TON mnemonic, false otherwise +TW_EXPORT_PROPERTY +bool TWStoredKeyIsTONMnemonic(struct TWStoredKey* _Nonnull key); + /// The number of accounts. /// /// \param key Non-null pointer to a stored key @@ -261,6 +291,14 @@ TWData* _Nullable TWStoredKeyDecryptPrivateKey(struct TWStoredKey* _Nonnull key, TW_EXPORT_METHOD TWString* _Nullable TWStoredKeyDecryptMnemonic(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); +/// Decrypts the TON mnemonic phrase. +/// +/// \param key Non-null pointer to a stored key +/// \param password Non-null block of data, password of the stored key +/// \return TON decrypted mnemonic if success, null pointer otherwise +TW_EXPORT_METHOD +TWString* _Nullable TWStoredKeyDecryptTONMnemonic(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); + /// Returns the private key for a specific coin. Returned object needs to be deleted. /// /// \param key Non-null pointer to a stored key diff --git a/include/TrustWalletCore/TWTONWallet.h b/include/TrustWalletCore/TWTONWallet.h index 098702faa6a..53d230482bc 100644 --- a/include/TrustWalletCore/TWTONWallet.h +++ b/include/TrustWalletCore/TWTONWallet.h @@ -5,6 +5,7 @@ #pragma once #include "TWBase.h" +#include "TWPrivateKey.h" #include "TWPublicKey.h" #include "TWString.h" @@ -14,6 +15,39 @@ TW_EXTERN_C_BEGIN TW_EXPORT_CLASS struct TWTONWallet; +/// Determines whether the English mnemonic and passphrase are valid. +/// +/// \param mnemonic Non-null english mnemonic +/// \param passphrase Nullable optional passphrase +/// \note passphrase can be null or empty string if no passphrase required +/// \return whether the mnemonic and passphrase are valid (valid checksum) +TW_EXPORT_STATIC_METHOD +bool TWTONWalletIsValidMnemonic(TWString* _Nonnull mnemonic, TWString* _Nullable passphrase); + +/// Creates a \TONWallet from a valid TON mnemonic and passphrase. +/// +/// \param mnemonic Non-null english mnemonic +/// \param passphrase Nullable optional passphrase +/// \note Null is returned on invalid mnemonic and passphrase +/// \note passphrase can be null or empty string if no passphrase required +/// \return Nullable TWTONWallet +TW_EXPORT_STATIC_METHOD +struct TWTONWallet* _Nullable TWTONWalletCreateWithMnemonic(TWString* _Nonnull mnemonic, TWString* _Nullable passphrase); + +/// Delete the given \TONWallet +/// +/// \param wallet Non-null pointer to private key +TW_EXPORT_METHOD +void TWTONWalletDelete(struct TWTONWallet* _Nonnull wallet); + +/// Generates Ed25519 private key associated with the wallet. +/// +/// \param wallet non-null TWTONWallet +/// \note Returned object needs to be deleted with \TWPrivateKeyDelete +/// \return The Ed25519 private key +TW_EXPORT_METHOD +struct TWPrivateKey* _Nonnull TWTONWalletGetKey(struct TWTONWallet* _Nonnull wallet); + /// Constructs a TON Wallet V4R2 stateInit encoded as BoC (BagOfCells) for the given `public_key`. /// /// \param publicKey wallet's public key. @@ -21,6 +55,6 @@ struct TWTONWallet; /// \param walletId wallet's ID allows to create multiple wallets for the same private key. /// \return Pointer to a base64 encoded Bag Of Cells (BoC) StateInit. Null if invalid public key provided. TW_EXPORT_STATIC_METHOD -TWString *_Nullable TWTONWalletBuildV4R2StateInit(struct TWPublicKey *_Nonnull publicKey, int32_t workchain, int32_t walletId); +TWString *_Nullable TWTONWalletBuildV4R2StateInit(struct TWPublicKey* _Nonnull publicKey, int32_t workchain, int32_t walletId); TW_EXTERN_C_END diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 108bb84e1e0..0a4e30effd7 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1105,6 +1105,16 @@ dependencies = [ "nom", ] +[[package]] +name = "pbkdf2" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0ca0b5a68607598bf3bad68f32227a8164f6254833f84eafaac409cd6746c31" +dependencies = [ + "digest 0.10.6", + "hmac", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -1954,6 +1964,7 @@ dependencies = [ "digest 0.10.6", "groestl", "hmac", + "pbkdf2", "ripemd", "serde", "serde_json", @@ -1965,6 +1976,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "tw_hd_wallet" +version = "0.1.0" +dependencies = [ + "lazy_static", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_misc", + "zeroize", +] + [[package]] name = "tw_internet_computer" version = "0.1.0" @@ -2015,6 +2038,9 @@ dependencies = [ [[package]] name = "tw_memory" version = "0.1.0" +dependencies = [ + "zeroize", +] [[package]] name = "tw_misc" @@ -2166,6 +2192,7 @@ dependencies = [ "tw_number", "tw_proto", "tw_ton_sdk", + "zeroize", ] [[package]] @@ -2281,6 +2308,7 @@ dependencies = [ "tw_encoding", "tw_ethereum", "tw_hash", + "tw_hd_wallet", "tw_keypair", "tw_memory", "tw_misc", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index ca41fb6099d..dce2a6f9154 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -25,6 +25,7 @@ members = [ "tw_encoding", "tw_evm", "tw_hash", + "tw_hd_wallet", "tw_keypair", "tw_memory", "tw_misc", diff --git a/rust/chains/tw_ton/Cargo.toml b/rust/chains/tw_ton/Cargo.toml index 076a7d84f61..145508522a6 100644 --- a/rust/chains/tw_ton/Cargo.toml +++ b/rust/chains/tw_ton/Cargo.toml @@ -14,3 +14,4 @@ tw_number = { path = "../../tw_number" } tw_misc = { path = "../../tw_misc" } tw_proto = { path = "../../tw_proto" } tw_ton_sdk = { path = "../../frameworks/tw_ton_sdk" } +zeroize = "1.8.1" diff --git a/rust/tw_any_coin/src/test_utils/address_utils.rs b/rust/tw_any_coin/src/test_utils/address_utils.rs index 528dce6092a..17fc481e95b 100644 --- a/rust/tw_any_coin/src/test_utils/address_utils.rs +++ b/rust/tw_any_coin/src/test_utils/address_utils.rs @@ -19,9 +19,9 @@ use tw_keypair::test_utils::tw_public_key_helper::TWPublicKeyHelper; use tw_keypair::tw::PublicKeyType; use tw_memory::test_utils::tw_data_helper::TWDataHelper; use tw_memory::test_utils::tw_string_helper::TWStringHelper; -use tw_memory::test_utils::tw_wrapper::{TWWrapper, WithDestructor}; +use tw_memory::test_utils::tw_wrapper::{TWAutoWrapper, WithDestructor}; -pub type TWAnyAddressHelper = TWWrapper; +pub type TWAnyAddressHelper = TWAutoWrapper; impl WithDestructor for TWAnyAddress { fn destructor() -> unsafe extern "C" fn(*mut Self) { diff --git a/rust/tw_hash/Cargo.toml b/rust/tw_hash/Cargo.toml index c9b8ab18c42..3373eda15c9 100644 --- a/rust/tw_hash/Cargo.toml +++ b/rust/tw_hash/Cargo.toml @@ -13,6 +13,7 @@ blake2b-ref = "0.3.1" digest = "0.10.6" groestl = "0.10.1" hmac = "0.12.1" +pbkdf2 = "0.12.1" ripemd = "0.1.3" serde = { version = "1.0", features = ["derive"], optional = true } sha1 = "0.10.5" diff --git a/rust/tw_hash/src/ffi.rs b/rust/tw_hash/src/ffi.rs index 08c4faf44b9..2867ffa4a8d 100644 --- a/rust/tw_hash/src/ffi.rs +++ b/rust/tw_hash/src/ffi.rs @@ -14,6 +14,7 @@ pub enum CHashingCode { Ok = 0, InvalidHashLength = 1, InvalidArgument = 2, + InvalidPassword = 3, } impl From for CHashingCode { @@ -21,6 +22,7 @@ impl From for CHashingCode { match e { Error::FromHexError(_) | Error::InvalidArgument => CHashingCode::InvalidArgument, Error::InvalidHashLength => CHashingCode::InvalidHashLength, + Error::InvalidPassword => CHashingCode::InvalidPassword, } } } diff --git a/rust/tw_hash/src/hmac.rs b/rust/tw_hash/src/hmac.rs index 475c5bbdb7f..538d0409b15 100644 --- a/rust/tw_hash/src/hmac.rs +++ b/rust/tw_hash/src/hmac.rs @@ -3,14 +3,19 @@ // Copyright © 2017 Trust Wallet. use hmac::{Hmac, Mac}; -use sha2::Sha256; +use sha2::{Sha256, Sha512}; type HmacSha256 = Hmac; +type HmacSha512 = Hmac; pub fn hmac_sha256(key: &[u8], input: &[u8]) -> Vec { - let mut mac = HmacSha256::new_from_slice(key).unwrap(); + let mut mac = HmacSha256::new_from_slice(key).expect("Hmac constructor should never fail"); mac.update(input); - let res = mac.finalize(); - let code_bytes = res.into_bytes(); - code_bytes.to_vec() + mac.finalize().into_bytes().to_vec() +} + +pub fn hmac_sha512(key: &[u8], input: &[u8]) -> Vec { + let mut mac = HmacSha512::new_from_slice(key).expect("Hmac constructor should never fail"); + mac.update(input); + mac.finalize().into_bytes().to_vec() } diff --git a/rust/tw_hash/src/lib.rs b/rust/tw_hash/src/lib.rs index 2371346ee7a..6d1817edea6 100644 --- a/rust/tw_hash/src/lib.rs +++ b/rust/tw_hash/src/lib.rs @@ -9,6 +9,7 @@ pub mod ffi; pub mod groestl; pub mod hasher; pub mod hmac; +pub mod pbkdf2; pub mod ripemd; pub mod sha1; pub mod sha2; @@ -28,6 +29,7 @@ pub enum Error { FromHexError(FromHexError), InvalidHashLength, InvalidArgument, + InvalidPassword, } impl From for Error { diff --git a/rust/tw_hash/src/pbkdf2.rs b/rust/tw_hash/src/pbkdf2.rs new file mode 100644 index 00000000000..30135d2dc17 --- /dev/null +++ b/rust/tw_hash/src/pbkdf2.rs @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::H512; +use sha2::Sha512; + +pub fn pbkdf2_hmac_sha512(password: &[u8], salt: &[u8], rounds: u32) -> H512 { + pbkdf2::pbkdf2_hmac_array::(password, salt, rounds).into() +} diff --git a/rust/tw_hd_wallet/Cargo.toml b/rust/tw_hd_wallet/Cargo.toml new file mode 100644 index 00000000000..72acc5d0022 --- /dev/null +++ b/rust/tw_hd_wallet/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tw_hd_wallet" +version = "0.1.0" +edition = "2021" + +[dependencies] +lazy_static = "1.4.0" +tw_hash = { path = "../tw_hash" } +tw_keypair = { path = "../tw_keypair" } +zeroize = "1.8.1" + +[dev-dependencies] +tw_encoding = { path = "../tw_encoding" } +tw_misc = { path = "../tw_misc" } diff --git a/rust/tw_hd_wallet/src/bip39/bip39_english.rs b/rust/tw_hd_wallet/src/bip39/bip39_english.rs new file mode 100644 index 00000000000..ae0c461da23 --- /dev/null +++ b/rust/tw_hd_wallet/src/bip39/bip39_english.rs @@ -0,0 +1,2070 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use lazy_static::lazy_static; +use std::collections::HashMap; + +lazy_static! { + pub static ref BIP39_WORDS_MAP: HashMap<&'static str, usize> = { + BIP39_WORDS_LIST + .iter() + .enumerate() + .map(|(idx, word)| (*word, idx)) + .collect() + }; +} + +/// https://github.com/trustwallet/wallet-core/blob/43c92837db9f5d773f2545473f29c8a597d86de5/trezor-crypto/include/TrezorCrypto/bip39_english.h#L24-L367 +/// https://github.com/dvc94ch/rust-bip39/blob/master/src/language/english.rs +#[rustfmt::skip] +pub const BIP39_WORDS_LIST: [&str; 2048] = [ + "abandon", + "ability", + "able", + "about", + "above", + "absent", + "absorb", + "abstract", + "absurd", + "abuse", + "access", + "accident", + "account", + "accuse", + "achieve", + "acid", + "acoustic", + "acquire", + "across", + "act", + "action", + "actor", + "actress", + "actual", + "adapt", + "add", + "addict", + "address", + "adjust", + "admit", + "adult", + "advance", + "advice", + "aerobic", + "affair", + "afford", + "afraid", + "again", + "age", + "agent", + "agree", + "ahead", + "aim", + "air", + "airport", + "aisle", + "alarm", + "album", + "alcohol", + "alert", + "alien", + "all", + "alley", + "allow", + "almost", + "alone", + "alpha", + "already", + "also", + "alter", + "always", + "amateur", + "amazing", + "among", + "amount", + "amused", + "analyst", + "anchor", + "ancient", + "anger", + "angle", + "angry", + "animal", + "ankle", + "announce", + "annual", + "another", + "answer", + "antenna", + "antique", + "anxiety", + "any", + "apart", + "apology", + "appear", + "apple", + "approve", + "april", + "arch", + "arctic", + "area", + "arena", + "argue", + "arm", + "armed", + "armor", + "army", + "around", + "arrange", + "arrest", + "arrive", + "arrow", + "art", + "artefact", + "artist", + "artwork", + "ask", + "aspect", + "assault", + "asset", + "assist", + "assume", + "asthma", + "athlete", + "atom", + "attack", + "attend", + "attitude", + "attract", + "auction", + "audit", + "august", + "aunt", + "author", + "auto", + "autumn", + "average", + "avocado", + "avoid", + "awake", + "aware", + "away", + "awesome", + "awful", + "awkward", + "axis", + "baby", + "bachelor", + "bacon", + "badge", + "bag", + "balance", + "balcony", + "ball", + "bamboo", + "banana", + "banner", + "bar", + "barely", + "bargain", + "barrel", + "base", + "basic", + "basket", + "battle", + "beach", + "bean", + "beauty", + "because", + "become", + "beef", + "before", + "begin", + "behave", + "behind", + "believe", + "below", + "belt", + "bench", + "benefit", + "best", + "betray", + "better", + "between", + "beyond", + "bicycle", + "bid", + "bike", + "bind", + "biology", + "bird", + "birth", + "bitter", + "black", + "blade", + "blame", + "blanket", + "blast", + "bleak", + "bless", + "blind", + "blood", + "blossom", + "blouse", + "blue", + "blur", + "blush", + "board", + "boat", + "body", + "boil", + "bomb", + "bone", + "bonus", + "book", + "boost", + "border", + "boring", + "borrow", + "boss", + "bottom", + "bounce", + "box", + "boy", + "bracket", + "brain", + "brand", + "brass", + "brave", + "bread", + "breeze", + "brick", + "bridge", + "brief", + "bright", + "bring", + "brisk", + "broccoli", + "broken", + "bronze", + "broom", + "brother", + "brown", + "brush", + "bubble", + "buddy", + "budget", + "buffalo", + "build", + "bulb", + "bulk", + "bullet", + "bundle", + "bunker", + "burden", + "burger", + "burst", + "bus", + "business", + "busy", + "butter", + "buyer", + "buzz", + "cabbage", + "cabin", + "cable", + "cactus", + "cage", + "cake", + "call", + "calm", + "camera", + "camp", + "can", + "canal", + "cancel", + "candy", + "cannon", + "canoe", + "canvas", + "canyon", + "capable", + "capital", + "captain", + "car", + "carbon", + "card", + "cargo", + "carpet", + "carry", + "cart", + "case", + "cash", + "casino", + "castle", + "casual", + "cat", + "catalog", + "catch", + "category", + "cattle", + "caught", + "cause", + "caution", + "cave", + "ceiling", + "celery", + "cement", + "census", + "century", + "cereal", + "certain", + "chair", + "chalk", + "champion", + "change", + "chaos", + "chapter", + "charge", + "chase", + "chat", + "cheap", + "check", + "cheese", + "chef", + "cherry", + "chest", + "chicken", + "chief", + "child", + "chimney", + "choice", + "choose", + "chronic", + "chuckle", + "chunk", + "churn", + "cigar", + "cinnamon", + "circle", + "citizen", + "city", + "civil", + "claim", + "clap", + "clarify", + "claw", + "clay", + "clean", + "clerk", + "clever", + "click", + "client", + "cliff", + "climb", + "clinic", + "clip", + "clock", + "clog", + "close", + "cloth", + "cloud", + "clown", + "club", + "clump", + "cluster", + "clutch", + "coach", + "coast", + "coconut", + "code", + "coffee", + "coil", + "coin", + "collect", + "color", + "column", + "combine", + "come", + "comfort", + "comic", + "common", + "company", + "concert", + "conduct", + "confirm", + "congress", + "connect", + "consider", + "control", + "convince", + "cook", + "cool", + "copper", + "copy", + "coral", + "core", + "corn", + "correct", + "cost", + "cotton", + "couch", + "country", + "couple", + "course", + "cousin", + "cover", + "coyote", + "crack", + "cradle", + "craft", + "cram", + "crane", + "crash", + "crater", + "crawl", + "crazy", + "cream", + "credit", + "creek", + "crew", + "cricket", + "crime", + "crisp", + "critic", + "crop", + "cross", + "crouch", + "crowd", + "crucial", + "cruel", + "cruise", + "crumble", + "crunch", + "crush", + "cry", + "crystal", + "cube", + "culture", + "cup", + "cupboard", + "curious", + "current", + "curtain", + "curve", + "cushion", + "custom", + "cute", + "cycle", + "dad", + "damage", + "damp", + "dance", + "danger", + "daring", + "dash", + "daughter", + "dawn", + "day", + "deal", + "debate", + "debris", + "decade", + "december", + "decide", + "decline", + "decorate", + "decrease", + "deer", + "defense", + "define", + "defy", + "degree", + "delay", + "deliver", + "demand", + "demise", + "denial", + "dentist", + "deny", + "depart", + "depend", + "deposit", + "depth", + "deputy", + "derive", + "describe", + "desert", + "design", + "desk", + "despair", + "destroy", + "detail", + "detect", + "develop", + "device", + "devote", + "diagram", + "dial", + "diamond", + "diary", + "dice", + "diesel", + "diet", + "differ", + "digital", + "dignity", + "dilemma", + "dinner", + "dinosaur", + "direct", + "dirt", + "disagree", + "discover", + "disease", + "dish", + "dismiss", + "disorder", + "display", + "distance", + "divert", + "divide", + "divorce", + "dizzy", + "doctor", + "document", + "dog", + "doll", + "dolphin", + "domain", + "donate", + "donkey", + "donor", + "door", + "dose", + "double", + "dove", + "draft", + "dragon", + "drama", + "drastic", + "draw", + "dream", + "dress", + "drift", + "drill", + "drink", + "drip", + "drive", + "drop", + "drum", + "dry", + "duck", + "dumb", + "dune", + "during", + "dust", + "dutch", + "duty", + "dwarf", + "dynamic", + "eager", + "eagle", + "early", + "earn", + "earth", + "easily", + "east", + "easy", + "echo", + "ecology", + "economy", + "edge", + "edit", + "educate", + "effort", + "egg", + "eight", + "either", + "elbow", + "elder", + "electric", + "elegant", + "element", + "elephant", + "elevator", + "elite", + "else", + "embark", + "embody", + "embrace", + "emerge", + "emotion", + "employ", + "empower", + "empty", + "enable", + "enact", + "end", + "endless", + "endorse", + "enemy", + "energy", + "enforce", + "engage", + "engine", + "enhance", + "enjoy", + "enlist", + "enough", + "enrich", + "enroll", + "ensure", + "enter", + "entire", + "entry", + "envelope", + "episode", + "equal", + "equip", + "era", + "erase", + "erode", + "erosion", + "error", + "erupt", + "escape", + "essay", + "essence", + "estate", + "eternal", + "ethics", + "evidence", + "evil", + "evoke", + "evolve", + "exact", + "example", + "excess", + "exchange", + "excite", + "exclude", + "excuse", + "execute", + "exercise", + "exhaust", + "exhibit", + "exile", + "exist", + "exit", + "exotic", + "expand", + "expect", + "expire", + "explain", + "expose", + "express", + "extend", + "extra", + "eye", + "eyebrow", + "fabric", + "face", + "faculty", + "fade", + "faint", + "faith", + "fall", + "false", + "fame", + "family", + "famous", + "fan", + "fancy", + "fantasy", + "farm", + "fashion", + "fat", + "fatal", + "father", + "fatigue", + "fault", + "favorite", + "feature", + "february", + "federal", + "fee", + "feed", + "feel", + "female", + "fence", + "festival", + "fetch", + "fever", + "few", + "fiber", + "fiction", + "field", + "figure", + "file", + "film", + "filter", + "final", + "find", + "fine", + "finger", + "finish", + "fire", + "firm", + "first", + "fiscal", + "fish", + "fit", + "fitness", + "fix", + "flag", + "flame", + "flash", + "flat", + "flavor", + "flee", + "flight", + "flip", + "float", + "flock", + "floor", + "flower", + "fluid", + "flush", + "fly", + "foam", + "focus", + "fog", + "foil", + "fold", + "follow", + "food", + "foot", + "force", + "forest", + "forget", + "fork", + "fortune", + "forum", + "forward", + "fossil", + "foster", + "found", + "fox", + "fragile", + "frame", + "frequent", + "fresh", + "friend", + "fringe", + "frog", + "front", + "frost", + "frown", + "frozen", + "fruit", + "fuel", + "fun", + "funny", + "furnace", + "fury", + "future", + "gadget", + "gain", + "galaxy", + "gallery", + "game", + "gap", + "garage", + "garbage", + "garden", + "garlic", + "garment", + "gas", + "gasp", + "gate", + "gather", + "gauge", + "gaze", + "general", + "genius", + "genre", + "gentle", + "genuine", + "gesture", + "ghost", + "giant", + "gift", + "giggle", + "ginger", + "giraffe", + "girl", + "give", + "glad", + "glance", + "glare", + "glass", + "glide", + "glimpse", + "globe", + "gloom", + "glory", + "glove", + "glow", + "glue", + "goat", + "goddess", + "gold", + "good", + "goose", + "gorilla", + "gospel", + "gossip", + "govern", + "gown", + "grab", + "grace", + "grain", + "grant", + "grape", + "grass", + "gravity", + "great", + "green", + "grid", + "grief", + "grit", + "grocery", + "group", + "grow", + "grunt", + "guard", + "guess", + "guide", + "guilt", + "guitar", + "gun", + "gym", + "habit", + "hair", + "half", + "hammer", + "hamster", + "hand", + "happy", + "harbor", + "hard", + "harsh", + "harvest", + "hat", + "have", + "hawk", + "hazard", + "head", + "health", + "heart", + "heavy", + "hedgehog", + "height", + "hello", + "helmet", + "help", + "hen", + "hero", + "hidden", + "high", + "hill", + "hint", + "hip", + "hire", + "history", + "hobby", + "hockey", + "hold", + "hole", + "holiday", + "hollow", + "home", + "honey", + "hood", + "hope", + "horn", + "horror", + "horse", + "hospital", + "host", + "hotel", + "hour", + "hover", + "hub", + "huge", + "human", + "humble", + "humor", + "hundred", + "hungry", + "hunt", + "hurdle", + "hurry", + "hurt", + "husband", + "hybrid", + "ice", + "icon", + "idea", + "identify", + "idle", + "ignore", + "ill", + "illegal", + "illness", + "image", + "imitate", + "immense", + "immune", + "impact", + "impose", + "improve", + "impulse", + "inch", + "include", + "income", + "increase", + "index", + "indicate", + "indoor", + "industry", + "infant", + "inflict", + "inform", + "inhale", + "inherit", + "initial", + "inject", + "injury", + "inmate", + "inner", + "innocent", + "input", + "inquiry", + "insane", + "insect", + "inside", + "inspire", + "install", + "intact", + "interest", + "into", + "invest", + "invite", + "involve", + "iron", + "island", + "isolate", + "issue", + "item", + "ivory", + "jacket", + "jaguar", + "jar", + "jazz", + "jealous", + "jeans", + "jelly", + "jewel", + "job", + "join", + "joke", + "journey", + "joy", + "judge", + "juice", + "jump", + "jungle", + "junior", + "junk", + "just", + "kangaroo", + "keen", + "keep", + "ketchup", + "key", + "kick", + "kid", + "kidney", + "kind", + "kingdom", + "kiss", + "kit", + "kitchen", + "kite", + "kitten", + "kiwi", + "knee", + "knife", + "knock", + "know", + "lab", + "label", + "labor", + "ladder", + "lady", + "lake", + "lamp", + "language", + "laptop", + "large", + "later", + "latin", + "laugh", + "laundry", + "lava", + "law", + "lawn", + "lawsuit", + "layer", + "lazy", + "leader", + "leaf", + "learn", + "leave", + "lecture", + "left", + "leg", + "legal", + "legend", + "leisure", + "lemon", + "lend", + "length", + "lens", + "leopard", + "lesson", + "letter", + "level", + "liar", + "liberty", + "library", + "license", + "life", + "lift", + "light", + "like", + "limb", + "limit", + "link", + "lion", + "liquid", + "list", + "little", + "live", + "lizard", + "load", + "loan", + "lobster", + "local", + "lock", + "logic", + "lonely", + "long", + "loop", + "lottery", + "loud", + "lounge", + "love", + "loyal", + "lucky", + "luggage", + "lumber", + "lunar", + "lunch", + "luxury", + "lyrics", + "machine", + "mad", + "magic", + "magnet", + "maid", + "mail", + "main", + "major", + "make", + "mammal", + "man", + "manage", + "mandate", + "mango", + "mansion", + "manual", + "maple", + "marble", + "march", + "margin", + "marine", + "market", + "marriage", + "mask", + "mass", + "master", + "match", + "material", + "math", + "matrix", + "matter", + "maximum", + "maze", + "meadow", + "mean", + "measure", + "meat", + "mechanic", + "medal", + "media", + "melody", + "melt", + "member", + "memory", + "mention", + "menu", + "mercy", + "merge", + "merit", + "merry", + "mesh", + "message", + "metal", + "method", + "middle", + "midnight", + "milk", + "million", + "mimic", + "mind", + "minimum", + "minor", + "minute", + "miracle", + "mirror", + "misery", + "miss", + "mistake", + "mix", + "mixed", + "mixture", + "mobile", + "model", + "modify", + "mom", + "moment", + "monitor", + "monkey", + "monster", + "month", + "moon", + "moral", + "more", + "morning", + "mosquito", + "mother", + "motion", + "motor", + "mountain", + "mouse", + "move", + "movie", + "much", + "muffin", + "mule", + "multiply", + "muscle", + "museum", + "mushroom", + "music", + "must", + "mutual", + "myself", + "mystery", + "myth", + "naive", + "name", + "napkin", + "narrow", + "nasty", + "nation", + "nature", + "near", + "neck", + "need", + "negative", + "neglect", + "neither", + "nephew", + "nerve", + "nest", + "net", + "network", + "neutral", + "never", + "news", + "next", + "nice", + "night", + "noble", + "noise", + "nominee", + "noodle", + "normal", + "north", + "nose", + "notable", + "note", + "nothing", + "notice", + "novel", + "now", + "nuclear", + "number", + "nurse", + "nut", + "oak", + "obey", + "object", + "oblige", + "obscure", + "observe", + "obtain", + "obvious", + "occur", + "ocean", + "october", + "odor", + "off", + "offer", + "office", + "often", + "oil", + "okay", + "old", + "olive", + "olympic", + "omit", + "once", + "one", + "onion", + "online", + "only", + "open", + "opera", + "opinion", + "oppose", + "option", + "orange", + "orbit", + "orchard", + "order", + "ordinary", + "organ", + "orient", + "original", + "orphan", + "ostrich", + "other", + "outdoor", + "outer", + "output", + "outside", + "oval", + "oven", + "over", + "own", + "owner", + "oxygen", + "oyster", + "ozone", + "pact", + "paddle", + "page", + "pair", + "palace", + "palm", + "panda", + "panel", + "panic", + "panther", + "paper", + "parade", + "parent", + "park", + "parrot", + "party", + "pass", + "patch", + "path", + "patient", + "patrol", + "pattern", + "pause", + "pave", + "payment", + "peace", + "peanut", + "pear", + "peasant", + "pelican", + "pen", + "penalty", + "pencil", + "people", + "pepper", + "perfect", + "permit", + "person", + "pet", + "phone", + "photo", + "phrase", + "physical", + "piano", + "picnic", + "picture", + "piece", + "pig", + "pigeon", + "pill", + "pilot", + "pink", + "pioneer", + "pipe", + "pistol", + "pitch", + "pizza", + "place", + "planet", + "plastic", + "plate", + "play", + "please", + "pledge", + "pluck", + "plug", + "plunge", + "poem", + "poet", + "point", + "polar", + "pole", + "police", + "pond", + "pony", + "pool", + "popular", + "portion", + "position", + "possible", + "post", + "potato", + "pottery", + "poverty", + "powder", + "power", + "practice", + "praise", + "predict", + "prefer", + "prepare", + "present", + "pretty", + "prevent", + "price", + "pride", + "primary", + "print", + "priority", + "prison", + "private", + "prize", + "problem", + "process", + "produce", + "profit", + "program", + "project", + "promote", + "proof", + "property", + "prosper", + "protect", + "proud", + "provide", + "public", + "pudding", + "pull", + "pulp", + "pulse", + "pumpkin", + "punch", + "pupil", + "puppy", + "purchase", + "purity", + "purpose", + "purse", + "push", + "put", + "puzzle", + "pyramid", + "quality", + "quantum", + "quarter", + "question", + "quick", + "quit", + "quiz", + "quote", + "rabbit", + "raccoon", + "race", + "rack", + "radar", + "radio", + "rail", + "rain", + "raise", + "rally", + "ramp", + "ranch", + "random", + "range", + "rapid", + "rare", + "rate", + "rather", + "raven", + "raw", + "razor", + "ready", + "real", + "reason", + "rebel", + "rebuild", + "recall", + "receive", + "recipe", + "record", + "recycle", + "reduce", + "reflect", + "reform", + "refuse", + "region", + "regret", + "regular", + "reject", + "relax", + "release", + "relief", + "rely", + "remain", + "remember", + "remind", + "remove", + "render", + "renew", + "rent", + "reopen", + "repair", + "repeat", + "replace", + "report", + "require", + "rescue", + "resemble", + "resist", + "resource", + "response", + "result", + "retire", + "retreat", + "return", + "reunion", + "reveal", + "review", + "reward", + "rhythm", + "rib", + "ribbon", + "rice", + "rich", + "ride", + "ridge", + "rifle", + "right", + "rigid", + "ring", + "riot", + "ripple", + "risk", + "ritual", + "rival", + "river", + "road", + "roast", + "robot", + "robust", + "rocket", + "romance", + "roof", + "rookie", + "room", + "rose", + "rotate", + "rough", + "round", + "route", + "royal", + "rubber", + "rude", + "rug", + "rule", + "run", + "runway", + "rural", + "sad", + "saddle", + "sadness", + "safe", + "sail", + "salad", + "salmon", + "salon", + "salt", + "salute", + "same", + "sample", + "sand", + "satisfy", + "satoshi", + "sauce", + "sausage", + "save", + "say", + "scale", + "scan", + "scare", + "scatter", + "scene", + "scheme", + "school", + "science", + "scissors", + "scorpion", + "scout", + "scrap", + "screen", + "script", + "scrub", + "sea", + "search", + "season", + "seat", + "second", + "secret", + "section", + "security", + "seed", + "seek", + "segment", + "select", + "sell", + "seminar", + "senior", + "sense", + "sentence", + "series", + "service", + "session", + "settle", + "setup", + "seven", + "shadow", + "shaft", + "shallow", + "share", + "shed", + "shell", + "sheriff", + "shield", + "shift", + "shine", + "ship", + "shiver", + "shock", + "shoe", + "shoot", + "shop", + "short", + "shoulder", + "shove", + "shrimp", + "shrug", + "shuffle", + "shy", + "sibling", + "sick", + "side", + "siege", + "sight", + "sign", + "silent", + "silk", + "silly", + "silver", + "similar", + "simple", + "since", + "sing", + "siren", + "sister", + "situate", + "six", + "size", + "skate", + "sketch", + "ski", + "skill", + "skin", + "skirt", + "skull", + "slab", + "slam", + "sleep", + "slender", + "slice", + "slide", + "slight", + "slim", + "slogan", + "slot", + "slow", + "slush", + "small", + "smart", + "smile", + "smoke", + "smooth", + "snack", + "snake", + "snap", + "sniff", + "snow", + "soap", + "soccer", + "social", + "sock", + "soda", + "soft", + "solar", + "soldier", + "solid", + "solution", + "solve", + "someone", + "song", + "soon", + "sorry", + "sort", + "soul", + "sound", + "soup", + "source", + "south", + "space", + "spare", + "spatial", + "spawn", + "speak", + "special", + "speed", + "spell", + "spend", + "sphere", + "spice", + "spider", + "spike", + "spin", + "spirit", + "split", + "spoil", + "sponsor", + "spoon", + "sport", + "spot", + "spray", + "spread", + "spring", + "spy", + "square", + "squeeze", + "squirrel", + "stable", + "stadium", + "staff", + "stage", + "stairs", + "stamp", + "stand", + "start", + "state", + "stay", + "steak", + "steel", + "stem", + "step", + "stereo", + "stick", + "still", + "sting", + "stock", + "stomach", + "stone", + "stool", + "story", + "stove", + "strategy", + "street", + "strike", + "strong", + "struggle", + "student", + "stuff", + "stumble", + "style", + "subject", + "submit", + "subway", + "success", + "such", + "sudden", + "suffer", + "sugar", + "suggest", + "suit", + "summer", + "sun", + "sunny", + "sunset", + "super", + "supply", + "supreme", + "sure", + "surface", + "surge", + "surprise", + "surround", + "survey", + "suspect", + "sustain", + "swallow", + "swamp", + "swap", + "swarm", + "swear", + "sweet", + "swift", + "swim", + "swing", + "switch", + "sword", + "symbol", + "symptom", + "syrup", + "system", + "table", + "tackle", + "tag", + "tail", + "talent", + "talk", + "tank", + "tape", + "target", + "task", + "taste", + "tattoo", + "taxi", + "teach", + "team", + "tell", + "ten", + "tenant", + "tennis", + "tent", + "term", + "test", + "text", + "thank", + "that", + "theme", + "then", + "theory", + "there", + "they", + "thing", + "this", + "thought", + "three", + "thrive", + "throw", + "thumb", + "thunder", + "ticket", + "tide", + "tiger", + "tilt", + "timber", + "time", + "tiny", + "tip", + "tired", + "tissue", + "title", + "toast", + "tobacco", + "today", + "toddler", + "toe", + "together", + "toilet", + "token", + "tomato", + "tomorrow", + "tone", + "tongue", + "tonight", + "tool", + "tooth", + "top", + "topic", + "topple", + "torch", + "tornado", + "tortoise", + "toss", + "total", + "tourist", + "toward", + "tower", + "town", + "toy", + "track", + "trade", + "traffic", + "tragic", + "train", + "transfer", + "trap", + "trash", + "travel", + "tray", + "treat", + "tree", + "trend", + "trial", + "tribe", + "trick", + "trigger", + "trim", + "trip", + "trophy", + "trouble", + "truck", + "true", + "truly", + "trumpet", + "trust", + "truth", + "try", + "tube", + "tuition", + "tumble", + "tuna", + "tunnel", + "turkey", + "turn", + "turtle", + "twelve", + "twenty", + "twice", + "twin", + "twist", + "two", + "type", + "typical", + "ugly", + "umbrella", + "unable", + "unaware", + "uncle", + "uncover", + "under", + "undo", + "unfair", + "unfold", + "unhappy", + "uniform", + "unique", + "unit", + "universe", + "unknown", + "unlock", + "until", + "unusual", + "unveil", + "update", + "upgrade", + "uphold", + "upon", + "upper", + "upset", + "urban", + "urge", + "usage", + "use", + "used", + "useful", + "useless", + "usual", + "utility", + "vacant", + "vacuum", + "vague", + "valid", + "valley", + "valve", + "van", + "vanish", + "vapor", + "various", + "vast", + "vault", + "vehicle", + "velvet", + "vendor", + "venture", + "venue", + "verb", + "verify", + "version", + "very", + "vessel", + "veteran", + "viable", + "vibrant", + "vicious", + "victory", + "video", + "view", + "village", + "vintage", + "violin", + "virtual", + "virus", + "visa", + "visit", + "visual", + "vital", + "vivid", + "vocal", + "voice", + "void", + "volcano", + "volume", + "vote", + "voyage", + "wage", + "wagon", + "wait", + "walk", + "wall", + "walnut", + "want", + "warfare", + "warm", + "warrior", + "wash", + "wasp", + "waste", + "water", + "wave", + "way", + "wealth", + "weapon", + "wear", + "weasel", + "weather", + "web", + "wedding", + "weekend", + "weird", + "welcome", + "west", + "wet", + "whale", + "what", + "wheat", + "wheel", + "when", + "where", + "whip", + "whisper", + "wide", + "width", + "wife", + "wild", + "will", + "win", + "window", + "wine", + "wing", + "wink", + "winner", + "winter", + "wire", + "wisdom", + "wise", + "wish", + "witness", + "wolf", + "woman", + "wonder", + "wood", + "wool", + "word", + "work", + "world", + "worry", + "worth", + "wrap", + "wreck", + "wrestle", + "wrist", + "write", + "wrong", + "yard", + "year", + "yellow", + "you", + "young", + "youth", + "zebra", + "zero", + "zone", + "zoo", +]; diff --git a/rust/tw_hd_wallet/src/bip39/mod.rs b/rust/tw_hd_wallet/src/bip39/mod.rs new file mode 100644 index 00000000000..d0e1db66b2e --- /dev/null +++ b/rust/tw_hd_wallet/src/bip39/mod.rs @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod bip39_english; + +pub fn normalize_mnemonic(mnemonic: &str) -> String { + mnemonic.trim().to_string() +} diff --git a/rust/tw_hd_wallet/src/lib.rs b/rust/tw_hd_wallet/src/lib.rs new file mode 100644 index 00000000000..cfa3a09fa16 --- /dev/null +++ b/rust/tw_hd_wallet/src/lib.rs @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +extern crate core; + +pub mod bip39; +pub mod ton; + +pub type WalletResult = Result; + +#[derive(Debug, Eq, PartialEq)] +pub enum WalletError { + InvalidMnemonicWordCount, + InvalidMnemonicUnknownWord, + InvalidMnemonicEntropy, + InvalidChecksum, +} diff --git a/rust/tw_hd_wallet/src/ton/mnemonic.rs b/rust/tw_hd_wallet/src/ton/mnemonic.rs new file mode 100644 index 00000000000..27013cdc283 --- /dev/null +++ b/rust/tw_hd_wallet/src/ton/mnemonic.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::bip39::bip39_english::BIP39_WORDS_MAP; +use crate::{WalletError, WalletResult}; + +pub const WORDS_LEN: usize = 24; + +/// Validates the given `mnemonic` string if it consists of 24 known words (see BIP39 words list). +/// +/// Please note there this function doesn't validate the mnemonic but it words only. +/// See [`TonWallet::new`]. +pub fn validate_mnemonic_words(mnemonic: &str) -> WalletResult<()> { + let mut invalid = false; + let mut words_count = 0; + for word in mnemonic.split(" ") { + words_count += 1; + + // Although this operation is not security-critical, we aim for constant-time operation here as well + // (i.e., no early exit on match) + // + // We expect words in lowercase only. + // It's a responsibility of the WalletCore user to transform the mnemonic if needed. + if !BIP39_WORDS_MAP.contains_key(word) { + invalid = true; + } + } + + if invalid { + return Err(WalletError::InvalidMnemonicUnknownWord); + } + if words_count != WORDS_LEN { + return Err(WalletError::InvalidMnemonicWordCount); + } + + Ok(()) +} diff --git a/rust/tw_hd_wallet/src/ton/mod.rs b/rust/tw_hd_wallet/src/ton/mod.rs new file mode 100644 index 00000000000..2971881654a --- /dev/null +++ b/rust/tw_hd_wallet/src/ton/mod.rs @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::bip39::normalize_mnemonic; +use crate::ton::mnemonic::validate_mnemonic_words; +use crate::{WalletError, WalletResult}; +use tw_hash::hmac::hmac_sha512; +use tw_hash::pbkdf2::pbkdf2_hmac_sha512; +use tw_hash::{H256, H512}; +use tw_keypair::ed25519::sha512::KeyPair; +use zeroize::ZeroizeOnDrop; + +pub const TON_WALLET_SALT: &[u8] = b"TON default seed"; +pub const TON_WALLET_PBKDF_ITERATIONS: u32 = 100_000; + +/// Used to verify a pair of mnemonic and passphrase only. +const TON_BASIC_SEED_SALT: &[u8] = b"TON seed version"; +/// Equals to `(TON_WALLET_PBKDF_ITERATIONS as f64 / 256.0).floor() as u32`. +const TON_BASIC_SEED_ROUNDS: u32 = 390; + +/// Used to verify a pair of mnemonic and passphrase only. +const TON_PASSPHRASE_SEED_SALT: &[u8] = b"TON fast seed version"; +const TON_PASSPHRASE_SEED_ROUNDS: u32 = 1; + +mod mnemonic; + +#[derive(Debug, ZeroizeOnDrop)] +pub struct TonWallet { + mnemonic: String, + passphrase: Option, + entropy: H512, + seed: H512, +} + +impl TonWallet { + pub const MNEMONIC_WORDS: usize = 24; + + /// Creates `TonWallet` while validating if there should or shouldn't be a passphrase. + /// https://github.com/toncenter/tonweb-mnemonic/blob/a338a00d4ca0ed833431e0e49e4cfad766ac713c/src/functions/validate-mnemonic.ts#L20-L28 + pub fn new(mnemonic: &str, passphrase: Option) -> WalletResult { + let mnemonic = normalize_mnemonic(mnemonic); + validate_mnemonic_words(&mnemonic)?; + + match passphrase { + Some(ref passphrase) if !passphrase.is_empty() => { + // Check whether the passphrase is really needed. + if !Self::is_password_needed(&mnemonic) { + return Err(WalletError::InvalidMnemonicEntropy); + } + }, + _ => (), + }; + + let entropy = Self::ton_mnemonic_to_entropy(&mnemonic, passphrase.as_deref()); + + // The pair of `[mnemonic, Option]` should give a `basic` seed. + // Otherwise, `passphrase` is either not set or invalid. + if !is_basic_seed(&entropy) { + return Err(WalletError::InvalidMnemonicEntropy); + } + + let seed = ton_seed(&entropy); + Ok(TonWallet { + mnemonic, + passphrase, + entropy, + seed, + }) + } + + pub fn to_key_pair(&self) -> KeyPair { + let (secret, _): (H256, H256) = self.seed.split(); + KeyPair::from(secret) + } + + /// Whether the mnemonic can be used with a passphrase only. + /// https://github.com/toncenter/tonweb-mnemonic/blob/a338a00d4ca0ed833431e0e49e4cfad766ac713c/src/functions/is-password-needed.ts#L5-L11 + fn is_password_needed(mnemonic: &str) -> bool { + // Password mnemonic (without password) should be password seed, but not basic seed. + let entropy = Self::ton_mnemonic_to_entropy(mnemonic, None); + is_password_seed(&entropy) && !is_basic_seed(&entropy) + } + + /// https://github.com/toncenter/tonweb-mnemonic/blob/a338a00d4ca0ed833431e0e49e4cfad766ac713c/src/functions/common.ts#L20-L23 + fn ton_mnemonic_to_entropy(mnemonic: &str, passphrase: Option<&str>) -> H512 { + let passphrase_bytes = passphrase.map(str::as_bytes).unwrap_or(&[]); + let entropy = hmac_sha512(mnemonic.as_bytes(), passphrase_bytes); + H512::try_from(entropy.as_slice()).expect("hmac_sha512 must return 64 bytes") + } +} + +/// https://github.com/toncenter/tonweb-mnemonic/blob/a338a00d4ca0ed833431e0e49e4cfad766ac713c/src/functions/common.ts#L8-L11 +fn is_basic_seed(entropy: &H512) -> bool { + basic_seed(entropy)[0] == 0 +} + +/// https://github.com/toncenter/tonweb-mnemonic/blob/a338a00d4ca0ed833431e0e49e4cfad766ac713c/src/functions/mnemonic-to-seed.ts#L5-L17 +fn ton_seed(entropy: &H512) -> H512 { + pbkdf2_hmac_sha512( + entropy.as_slice(), + TON_WALLET_SALT, + TON_WALLET_PBKDF_ITERATIONS, + ) +} + +/// https://github.com/toncenter/tonweb-mnemonic/blob/a338a00d4ca0ed833431e0e49e4cfad766ac713c/src/functions/common.ts#L9 +fn basic_seed(entropy: &H512) -> H512 { + pbkdf2_hmac_sha512( + entropy.as_slice(), + TON_BASIC_SEED_SALT, + TON_BASIC_SEED_ROUNDS, + ) +} + +/// https://github.com/toncenter/tonweb-mnemonic/blob/a338a00d4ca0ed833431e0e49e4cfad766ac713c/src/functions/common.ts#L15 +fn password_seed(entropy: &H512) -> H512 { + pbkdf2_hmac_sha512( + entropy.as_slice(), + TON_PASSPHRASE_SEED_SALT, + TON_PASSPHRASE_SEED_ROUNDS, + ) +} + +/// https://github.com/toncenter/tonweb-mnemonic/blob/a338a00d4ca0ed833431e0e49e4cfad766ac713c/src/functions/common.ts#L14-L17 +fn is_password_seed(entropy: &H512) -> bool { + password_seed(entropy)[0] == 1 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_basic_seed() { + let entropy = H512::from("db315e4b9a05d29d4921f3f99c754b52c1035bd20a6c38ae4c39068a844d2d4dccd1b16ab639494155bd635737c9120eab0b0382c6f27d941993ad2a98ee037b"); + let actual = basic_seed(&entropy); + let expected = H512::from("008303d550147ad20420875810a81496510e32e2ec7b1c4129c8fe55c0886ad2b6c8b6ad88427d3614a27997ec3760e4a6aaae45c8a1c70684aad5206e8559ce"); + assert_eq!(actual, expected); + assert!(is_basic_seed(&entropy)); + } + + #[test] + fn test_is_not_basic_seed() { + let entropy = H512::from("5ad5da67282f932eb9e0b66246af357e1e99f73b066ba1095fedf324629f67048c82e7c6d4cf09f204e2b2fcd002ab9bae25da67f99ecb918861d11ec6553a78"); + let actual = basic_seed(&entropy); + let expected = H512::from("fd1ab5001f7fec237ad7b90dbd3a8a6d716409688d23517cc80314b79f36f93a21aac798c39778684688b4763bf0294874c067ef3d28d854101c4e5616839dfd"); + assert_eq!(actual, expected); + assert!(!is_basic_seed(&entropy)); + } + + #[test] + fn test_is_password_seed() { + let entropy = H512::from("f0fc0e9610e3a215db47df1fc8dc2142ec4e8559ee1ec384fc3e51234d63c34087f4a6bdb2ad2c5a46a4504e30c9ab4ef7d92dc1836c2854ecef1f7988e60100"); + let actual = password_seed(&entropy); + let expected = H512::from("014944aac60ad889acab074b850df15b745b2c6ca5367c3ba02f4ae22e5a8953ac33413c0cd7fdb9108935bd6ed82acc73d4ac94202d83933a5480642c371eed"); + assert_eq!(actual, expected); + assert!(is_password_seed(&entropy)); + } + + #[test] + fn test_is_not_password_seed() { + let entropy = H512::from("85092586b7d675688d52b5870966546c7fb0144a2d94badd7c3960d6e9de3094016cea3aa155f5a9b3ce61d5b4ad8393984ed153dc0866304c911ba2edd2ea9e"); + let actual = password_seed(&entropy); + let expected = H512::from("31f4dd02b333be5635dc46245d67792b85e9b74d788a749caf3d45bfb1cd094a039626ba1a8bff77fa2436a6a228568d29884c2b274e09c0fe722f07980aab8e"); + assert_eq!(actual, expected); + assert!(!is_password_seed(&entropy)); + } +} diff --git a/rust/tw_hd_wallet/tests/ton_mnemonic.rs b/rust/tw_hd_wallet/tests/ton_mnemonic.rs new file mode 100644 index 00000000000..3125140ad90 --- /dev/null +++ b/rust/tw_hd_wallet/tests/ton_mnemonic.rs @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_encoding::hex::ToHex; +use tw_hd_wallet::ton::TonWallet; +use tw_hd_wallet::WalletError; +use tw_keypair::traits::KeyPairTrait; +use tw_misc::traits::ToBytesZeroizing; + +struct MnemonicTest { + mnemonic: &'static str, + passphrase: &'static str, + expected_private: &'static str, + expected_public: &'static str, +} + +fn mnemonic_to_keypair_impl(input: MnemonicTest) { + let passphrase = if input.passphrase.is_empty() { + None + } else { + Some(input.passphrase.to_string()) + }; + + let wallet = TonWallet::new(input.mnemonic, passphrase).unwrap(); + let key_pair = wallet.to_key_pair(); + + assert_eq!( + key_pair.private().to_zeroizing_vec().to_hex(), + input.expected_private, + "Invalid private key" + ); + assert_eq!( + key_pair.public().to_bytes().to_hex(), + input.expected_public, + "Invalid public key" + ); +} + +struct MnemonicErrorTest { + mnemonic: &'static str, + passphrase: &'static str, +} + +fn mnemonic_to_keypair_error(input: MnemonicErrorTest) { + let passphrase = if input.passphrase.is_empty() { + None + } else { + Some(input.passphrase.to_string()) + }; + + assert!( + TonWallet::new(input.mnemonic, passphrase).is_err(), + "Expected an error" + ); +} + +/// All tests generated by using `https://github.com/toncenter/tonweb-mnemonic/`. +#[test] +fn test_mnemonic_to_keypair_no_passphrase() { + mnemonic_to_keypair_impl(MnemonicTest { + mnemonic: "document shield addict crime broom point story depend suit satisfy test chicken valid tail speak fortune sound drill seek cube cheap body music recipe", + passphrase: "", + expected_private: "112d4e2e700a468f1eae699329202f1ee671d6b665caa2d92dea038cf3868c18", + expected_public: "4d656c35c830bf78d239d3225727dd1da051be0ec521c98e3012beafbb06f306", + }); + mnemonic_to_keypair_impl(MnemonicTest { + mnemonic: "slogan train glide measure mercy dizzy when satoshi vote change length pluck token walnut actress hollow guard soup solve rival summer vicious anxiety device", + passphrase: "", + expected_private: "ee11da8e64d17a8416c88a6a24a1e16569cc85a077b7b209528975c32a44a0c8", + expected_public: "3bab20a5f77e277e39443fc16c64e0479b4a9db542bf9e11c638598384c853f1", + }); + mnemonic_to_keypair_impl(MnemonicTest { + mnemonic: "laundry myself fitness beyond prize piano match acid vacuum already abandon dance occur pause grocery company inject excuse weasel carpet fog grunt trick spike", + passphrase: "", + expected_private: "859cd74ab605afb7ce9f5316a1f6d59217a130b75b494efd249913be874c9d46", + expected_public: "c9af50596bd5c1c5a15fb32bef8d4f1ee5244b287aea1f49f6023a79f9b2f055", + }); + mnemonic_to_keypair_impl(MnemonicTest { + mnemonic: "slim holiday tiny pizza donor egg round three verify post chat social offer mix rack soft loud code option learn this pipe mouse mango", + passphrase: "", + expected_private: "cdfd1e2a1f947701bddba2636c26a6d6d13efacba5e6fdc624254be9bf8cbc3b", + expected_public: "c1266dc4e8040e462af34fa7da9130950caf8c8a1bab78c9b938d2eb6e3d9a69", + }); +} + +/// All tests generated by using `https://github.com/toncenter/tonweb-mnemonic/`. +#[test] +fn test_mnemonic_to_keypair_with_passphrase() { + mnemonic_to_keypair_impl(MnemonicTest { + mnemonic: "afford skate husband stamp style affair jeans episode afraid mom pupil canal borrow artwork fetch excite shiver conduct acoustic rail crisp consider pave people", + passphrase: ".", + expected_private: "ddaed03c283c9e60883b6c7cda86af40a1a820a8181276900094db0d23b55144", + expected_public: "46aacb0e9e1faba24e0e87d4bf7ee5e54beaa4142fa1bd608324d7c67d78070e", + }); + mnemonic_to_keypair_impl(MnemonicTest { + mnemonic: "mimic close sibling chair shuffle goat fashion chunk increase tennis scene ceiling divert cross treat happy soccer sample umbrella oyster advance quality perfect call", + passphrase: "My passphrase", + expected_private: "78a6d95981847d6b7fb6b85be43071fbd83f4b0cebc1fd0c75147fde3c88d9e2", + expected_public: "452a6031290df95e972a269f3c042f5b18497ab27b8fe9915e5b5c94037382a6", + }); + mnemonic_to_keypair_impl(MnemonicTest { + mnemonic: "kind loan rifle gadget forward tortoise switch tuition orchard ball monkey glow gallery diary nature dynamic survey flush correct employ autumn wife disease coin", + passphrase: "189r012 9jr90fj--901hr8921'0r912j90", + expected_private: "ced0feac4f8cc46909a5a172d390f126afe46540c07c5163c194429269e6eb08", + expected_public: "482c9619307639a4b1699e83d771d656e5cf7be3ef877d849940cfebd718783e", + }); + mnemonic_to_keypair_impl(MnemonicTest { + mnemonic: " predict pelican worry swallow brother real truck fiber trophy melody joy kitten luggage lake woman clutch frost crop about stumble frozen kitchen mutual food ", + passphrase: "Foo Bar Zar", + expected_private: "f93ed43c379cb8210754016ffa669880b259854160446c1a12c5858258c32601", + expected_public: "f44d7f29bc8801f882097611544fcafe84cca05408006fa5b76b63f55464e4d0", + }); +} + +#[test] +fn test_mnemonic_to_keypair_error_expected_passphrase() { + // This mnemonic can only be used along with the "My passphrase" passphrase. + let mnemonic = "mimic close sibling chair shuffle goat fashion chunk increase tennis scene ceiling divert cross treat happy soccer sample umbrella oyster advance quality perfect call"; + mnemonic_to_keypair_error(MnemonicErrorTest { + mnemonic, + passphrase: "", + }); + mnemonic_to_keypair_error(MnemonicErrorTest { + mnemonic, + passphrase: "Unexpected passphrase", + }); +} + +#[test] +fn test_mnemonic_to_keypair_error_expected_no_passphrase() { + // This mnemonic can only be used without passphrase. + let mnemonic = "slogan train glide measure mercy dizzy when satoshi vote change length pluck token walnut actress hollow guard soup solve rival summer vicious anxiety device"; + mnemonic_to_keypair_error(MnemonicErrorTest { + mnemonic, + passphrase: "Hello world", + }); + mnemonic_to_keypair_error(MnemonicErrorTest { + mnemonic, + passphrase: "...", + }); +} + +#[test] +fn test_invalid_mnemonic() { + // 24 words mnemonic is supported only. + let error = TonWallet::new("cost dash dress stove morning robust group affair stomach vacant route volume yellow salute laugh", None).unwrap_err(); + assert_eq!(error, WalletError::InvalidMnemonicWordCount); + + let error = TonWallet::new("foo bar oooo edit wash faint patient cancel roof edit silly battle half engine reunion hotel joy fan unhappy oil alone sense empty mesh", None).unwrap_err(); + assert_eq!(error, WalletError::InvalidMnemonicUnknownWord); + + // Upper-case mnemonic is not allowed. + let error = TonWallet::new("TAIL SWING SUGGEST EDIT WASH FAINT PATIENT CANCEL ROOF EDIT SILLY BATTLE HALF ENGINE REUNION HOTEL JOY FAN UNHAPPY OIL ALONE SENSE EMPTY MESH", None).unwrap_err(); + assert_eq!(error, WalletError::InvalidMnemonicUnknownWord); +} diff --git a/rust/tw_keypair/src/ed25519/keypair.rs b/rust/tw_keypair/src/ed25519/keypair.rs index 3ac2ac1e1fa..eed4a8ca6d0 100644 --- a/rust/tw_keypair/src/ed25519/keypair.rs +++ b/rust/tw_keypair/src/ed25519/keypair.rs @@ -6,6 +6,7 @@ use crate::ed25519::{private::PrivateKey, public::PublicKey, signature::Signatur use crate::traits::{KeyPairTrait, SigningKeyTrait, VerifyingKeyTrait}; use crate::{KeyPairError, KeyPairResult}; use tw_encoding::hex; +use tw_hash::H256; use zeroize::Zeroizing; /// Represents a pair of `ed25519` private and public keys. @@ -46,6 +47,14 @@ impl VerifyingKeyTrait for KeyPair { } } +impl From for KeyPair { + fn from(secret: H256) -> Self { + let private = PrivateKey::from(secret); + let public = private.public(); + KeyPair { private, public } + } +} + impl<'a, H: Hasher512> TryFrom<&'a [u8]> for KeyPair { type Error = KeyPairError; diff --git a/rust/tw_keypair/src/ed25519/private.rs b/rust/tw_keypair/src/ed25519/private.rs index 984392393a6..dc5ab54a68e 100644 --- a/rust/tw_keypair/src/ed25519/private.rs +++ b/rust/tw_keypair/src/ed25519/private.rs @@ -11,6 +11,7 @@ use crate::{KeyPairError, KeyPairResult}; use std::fmt; use tw_encoding::hex; use tw_hash::H256; +use tw_memory::Data; use tw_misc::traits::ToBytesZeroizing; use zeroize::{ZeroizeOnDrop, Zeroizing}; @@ -37,6 +38,12 @@ impl PrivateKey { PublicKey::with_expanded_secret(&self.expanded_key) } + /// Returns a reference to the private key bytes. + /// Please note if clone bytes, it must be zeroized with [`zeroize::Zeroize::zeroize`]. + pub fn bytes(&self) -> &H256 { + &self.secret + } + /// `ed25519` signing uses a public key associated with the private key. pub(crate) fn sign_with_public_key( &self, @@ -49,7 +56,7 @@ impl PrivateKey { } impl SigningKeyTrait for PrivateKey { - type SigningMessage = Vec; + type SigningMessage = Data; type Signature = Signature; fn sign(&self, message: Self::SigningMessage) -> KeyPairResult { @@ -57,16 +64,22 @@ impl SigningKeyTrait for PrivateKey { } } +impl From for PrivateKey { + fn from(secret: H256) -> Self { + let expanded_key = ExpandedSecretKey::::with_secret(secret); + PrivateKey { + secret, + expanded_key, + } + } +} + impl TryFrom<&[u8]> for PrivateKey { type Error = KeyPairError; fn try_from(data: &[u8]) -> Result { let secret = H256::try_from(data).map_err(|_| KeyPairError::InvalidSecretKey)?; - let expanded_key = ExpandedSecretKey::::with_secret(secret); - Ok(PrivateKey { - secret, - expanded_key, - }) + Ok(PrivateKey::from(secret)) } } @@ -80,7 +93,7 @@ impl<'a, H: Hasher512> TryFrom<&'a str> for PrivateKey { } impl ToBytesZeroizing for PrivateKey { - fn to_zeroizing_vec(&self) -> Zeroizing> { + fn to_zeroizing_vec(&self) -> Zeroizing { Zeroizing::new(self.secret.to_vec()) } } diff --git a/rust/tw_keypair/src/ffi/privkey.rs b/rust/tw_keypair/src/ffi/privkey.rs index 9d4efa7f8f4..c1a1b978569 100644 --- a/rust/tw_keypair/src/ffi/privkey.rs +++ b/rust/tw_keypair/src/ffi/privkey.rs @@ -8,10 +8,11 @@ use crate::ffi::pubkey::TWPublicKey; use crate::tw::{Curve, PrivateKey, PublicKeyType}; use tw_memory::ffi::c_byte_array::CByteArray; use tw_memory::ffi::c_byte_array_ref::CByteArrayRef; +use tw_memory::ffi::tw_data::TWData; use tw_memory::ffi::RawPtrTrait; use tw_misc::{try_or_else, try_or_false}; -pub struct TWPrivateKey(pub(crate) PrivateKey); +pub struct TWPrivateKey(pub PrivateKey); impl RawPtrTrait for TWPrivateKey {} @@ -67,6 +68,17 @@ pub unsafe extern "C" fn tw_private_key_is_valid( PrivateKey::is_valid(priv_key_slice, curve) } +/// Convert the given private key to raw-bytes block of data. +/// +/// \param key Non-null pointer to the private key +/// \note The returned block should be deleted with \tw_data_delete_zeroizing +/// \return Non-null block of data (raw bytes) of the given private key +#[no_mangle] +pub unsafe extern "C" fn tw_private_key_data(key: *const TWPrivateKey) -> *mut TWData { + let key = try_or_else!(TWPrivateKey::from_ptr_as_ref(key), std::ptr::null_mut); + TWData::from(key.0.bytes().to_vec()).into_ptr() +} + /// Signs a digest using ECDSA and given curve. /// /// \param key *non-null* pointer to a Private key @@ -76,7 +88,7 @@ pub unsafe extern "C" fn tw_private_key_is_valid( /// \return Signature as a C-compatible result with a C-compatible byte array. #[no_mangle] pub unsafe extern "C" fn tw_private_key_sign( - key: *mut TWPrivateKey, + key: *const TWPrivateKey, message: *const u8, message_len: usize, curve: u32, @@ -100,7 +112,7 @@ pub unsafe extern "C" fn tw_private_key_sign( /// \return *non-null* pointer to the corresponding public key. #[no_mangle] pub unsafe extern "C" fn tw_private_key_get_public_key_by_type( - key: *mut TWPrivateKey, + key: *const TWPrivateKey, pubkey_type: u32, ) -> *mut TWPublicKey { let ty = try_or_else!(PublicKeyType::from_raw(pubkey_type), std::ptr::null_mut); diff --git a/rust/tw_keypair/src/ffi/pubkey.rs b/rust/tw_keypair/src/ffi/pubkey.rs index 7f05f5f0fe9..690f1c3e645 100644 --- a/rust/tw_keypair/src/ffi/pubkey.rs +++ b/rust/tw_keypair/src/ffi/pubkey.rs @@ -10,7 +10,7 @@ use tw_memory::ffi::c_byte_array_ref::CByteArrayRef; use tw_memory::ffi::RawPtrTrait; use tw_misc::{try_or_else, try_or_false}; -pub struct TWPublicKey(pub(crate) PublicKey); +pub struct TWPublicKey(pub PublicKey); impl AsRef for TWPublicKey { fn as_ref(&self) -> &PublicKey { diff --git a/rust/tw_keypair/src/test_utils/tw_crypto_box_helpers.rs b/rust/tw_keypair/src/test_utils/tw_crypto_box_helpers.rs index 88b4a146003..34c2a0f965a 100644 --- a/rust/tw_keypair/src/test_utils/tw_crypto_box_helpers.rs +++ b/rust/tw_keypair/src/test_utils/tw_crypto_box_helpers.rs @@ -4,10 +4,10 @@ use crate::ffi::crypto_box::public_key::{tw_crypto_box_public_key_delete, TWCryptoBoxPublicKey}; use crate::ffi::crypto_box::secret_key::{tw_crypto_box_secret_key_delete, TWCryptoBoxSecretKey}; -use tw_memory::test_utils::tw_wrapper::{TWWrapper, WithDestructor}; +use tw_memory::test_utils::tw_wrapper::{TWAutoWrapper, WithDestructor}; -pub type TWCryptoBoxSecretKeyHelper = TWWrapper; -pub type TWCryptoBoxPublicKeyHelper = TWWrapper; +pub type TWCryptoBoxSecretKeyHelper = TWAutoWrapper; +pub type TWCryptoBoxPublicKeyHelper = TWAutoWrapper; impl WithDestructor for TWCryptoBoxSecretKey { fn destructor() -> unsafe extern "C" fn(*mut Self) { diff --git a/rust/tw_keypair/src/test_utils/tw_private_key_helper.rs b/rust/tw_keypair/src/test_utils/tw_private_key_helper.rs index 6c3c3bfc629..d745fb2a1e3 100644 --- a/rust/tw_keypair/src/test_utils/tw_private_key_helper.rs +++ b/rust/tw_keypair/src/test_utils/tw_private_key_helper.rs @@ -2,9 +2,12 @@ // // Copyright © 2017 Trust Wallet. -use crate::ffi::privkey::{tw_private_key_create_with_data, tw_private_key_delete, TWPrivateKey}; +use crate::ffi::privkey::{ + tw_private_key_create_with_data, tw_private_key_data, tw_private_key_delete, TWPrivateKey, +}; use tw_encoding::hex; use tw_memory::ffi::c_byte_array::CByteArray; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; use tw_memory::Data; pub struct TWPrivateKeyHelper { @@ -12,6 +15,18 @@ pub struct TWPrivateKeyHelper { } impl TWPrivateKeyHelper { + pub fn wrap(ptr: *mut TWPrivateKey) -> TWPrivateKeyHelper { + TWPrivateKeyHelper { ptr } + } + + pub fn bytes(&self) -> Option { + if self.ptr.is_null() { + return None; + } + let data = TWDataHelper::wrap(unsafe { tw_private_key_data(self.ptr) }); + data.to_vec() + } + pub fn with_bytes>(bytes: T) -> TWPrivateKeyHelper { let priv_key_raw = CByteArray::from(bytes.into()); let ptr = diff --git a/rust/tw_keypair/src/tw/private.rs b/rust/tw_keypair/src/tw/private.rs index 0516f451798..b325b3ca834 100644 --- a/rust/tw_keypair/src/tw/private.rs +++ b/rust/tw_keypair/src/tw/private.rs @@ -55,6 +55,11 @@ impl PrivateKey { Ok(&self.bytes[Self::EXTENDED_CARDANO_RANGE]) } + /// Returns bytes (32 or 192 bytes). + pub fn bytes(&self) -> &[u8] { + &self.bytes + } + /// Checks if the given `bytes` secret is valid in general (without a concrete curve). pub fn is_valid_general(bytes: &[u8]) -> bool { if bytes.len() != Self::SIZE && bytes.len() != Self::CARDANO_SIZE { @@ -203,3 +208,11 @@ impl PrivateKey { schnorr::PrivateKey::try_from(self.key().as_slice()) } } + +impl From for PrivateKey { + fn from(key: ed25519::sha512::PrivateKey) -> Self { + PrivateKey { + bytes: key.bytes().to_vec(), + } + } +} diff --git a/rust/tw_keypair/tests/crypto_box_ffi_tests.rs b/rust/tw_keypair/tests/crypto_box_ffi_tests.rs index bf98b67b07e..4dbe6bb67e5 100644 --- a/rust/tw_keypair/tests/crypto_box_ffi_tests.rs +++ b/rust/tw_keypair/tests/crypto_box_ffi_tests.rs @@ -17,11 +17,12 @@ use tw_keypair::test_utils::tw_crypto_box_helpers::{ TWCryptoBoxPublicKeyHelper, TWCryptoBoxSecretKeyHelper, }; use tw_memory::test_utils::tw_data_helper::TWDataHelper; -use tw_memory::test_utils::tw_wrapper::TWWrapper; +use tw_memory::test_utils::tw_wrapper::TWAutoWrapper; fn random_key_pair() -> (TWCryptoBoxSecretKeyHelper, TWCryptoBoxPublicKeyHelper) { - let secret = TWWrapper::wrap(unsafe { tw_crypto_box_secret_key_create() }); - let pubkey = TWWrapper::wrap(unsafe { tw_crypto_box_secret_key_get_public_key(secret.ptr()) }); + let secret = TWAutoWrapper::wrap(unsafe { tw_crypto_box_secret_key_create() }); + let pubkey = + TWAutoWrapper::wrap(unsafe { tw_crypto_box_secret_key_get_public_key(secret.ptr()) }); (secret, pubkey) } @@ -55,7 +56,7 @@ fn test_encrypt_decrypt_easy_error() { .decode_hex() .unwrap(), ); - let other_pubkey = TWWrapper::wrap(unsafe { + let other_pubkey = TWAutoWrapper::wrap(unsafe { tw_crypto_box_public_key_create_with_data(other_pubkey_data.ptr()) }); @@ -83,8 +84,9 @@ fn test_public_key() { let pubkey_data = TWDataHelper::create(pubkey_bytes.clone()); assert!(unsafe { tw_crypto_box_public_key_is_valid(pubkey_data.ptr()) }); - let pubkey = - TWWrapper::wrap(unsafe { tw_crypto_box_public_key_create_with_data(pubkey_data.ptr()) }); + let pubkey = TWAutoWrapper::wrap(unsafe { + tw_crypto_box_public_key_create_with_data(pubkey_data.ptr()) + }); let actual_data = TWDataHelper::wrap(unsafe { tw_crypto_box_public_key_data(pubkey.ptr()) }); assert_eq!(actual_data.to_vec().unwrap(), pubkey_bytes); } @@ -98,8 +100,9 @@ fn test_secret_key() { let secret_data = TWDataHelper::create(secret_bytes.clone()); assert!(unsafe { tw_crypto_box_secret_key_is_valid(secret_data.ptr()) }); - let pubkey = - TWWrapper::wrap(unsafe { tw_crypto_box_secret_key_create_with_data(secret_data.ptr()) }); + let pubkey = TWAutoWrapper::wrap(unsafe { + tw_crypto_box_secret_key_create_with_data(secret_data.ptr()) + }); let actual_data = TWDataHelper::wrap(unsafe { tw_crypto_box_secret_key_data(pubkey.ptr()) }); assert_eq!(actual_data.to_vec().unwrap(), secret_bytes); } diff --git a/rust/tw_memory/Cargo.toml b/rust/tw_memory/Cargo.toml index 2c7b443f83f..642b3494812 100644 --- a/rust/tw_memory/Cargo.toml +++ b/rust/tw_memory/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +zeroize = "1.8.1" [features] test-utils = [] diff --git a/rust/tw_memory/src/ffi/tw_data.rs b/rust/tw_memory/src/ffi/tw_data.rs index 7a573d8fe4b..675d0d11e08 100644 --- a/rust/tw_memory/src/ffi/tw_data.rs +++ b/rust/tw_memory/src/ffi/tw_data.rs @@ -5,6 +5,7 @@ use crate::ffi::c_byte_array_ref::CByteArrayRef; use crate::ffi::RawPtrTrait; use crate::Data; +use zeroize::Zeroize; /// Defines a resizable block of data. /// @@ -79,6 +80,18 @@ pub unsafe extern "C" fn tw_data_delete(data: *mut TWData) { let _ = TWData::from_ptr(data); } +/// Deletes a block of data zeroizing the memory. +/// +/// \param data A non-null valid block of data +#[no_mangle] +pub unsafe extern "C" fn tw_data_delete_zeroizing(data: *mut TWData) { + // Take the ownership back to rust and drop the owner. + let Some(mut data) = TWData::from_ptr(data) else { + return; + }; + data.0.zeroize(); +} + /// Returns the raw pointer to the contents of data. /// /// \param data A non-null valid block of data diff --git a/rust/tw_memory/src/test_utils/tw_wrapper.rs b/rust/tw_memory/src/test_utils/tw_wrapper.rs index 448b16097e9..397ee144d6c 100644 --- a/rust/tw_memory/src/test_utils/tw_wrapper.rs +++ b/rust/tw_memory/src/test_utils/tw_wrapper.rs @@ -2,17 +2,22 @@ // // Copyright © 2017 Trust Wallet. +pub type Destructor = unsafe extern "C" fn(*mut T); + pub trait WithDestructor: Sized { fn destructor() -> unsafe extern "C" fn(*mut Self); } -pub struct TWWrapper { +/// Structure pointer wrapper. +/// Use this wrapper when `T` can implement the `WithDestructor` trait. +/// Otherwise, consider using `TWWrapper`. +pub struct TWAutoWrapper { ptr: *mut T, } -impl TWWrapper { +impl TWAutoWrapper { pub fn wrap(ptr: *mut T) -> Self { - TWWrapper { ptr } + TWAutoWrapper { ptr } } pub fn ptr(&self) -> *mut T { @@ -20,7 +25,7 @@ impl TWWrapper { } } -impl Drop for TWWrapper { +impl Drop for TWAutoWrapper { fn drop(&mut self) { if self.ptr.is_null() { return; @@ -28,3 +33,28 @@ impl Drop for TWWrapper { unsafe { (T::destructor())(self.ptr) } } } + +/// Structure pointer wrapper with a manual destructor. +pub struct TWWrapper { + ptr: *mut T, + destructor: Destructor, +} + +impl TWWrapper { + pub fn wrap(ptr: *mut T, destructor: Destructor) -> Self { + TWWrapper { ptr, destructor } + } + + pub fn ptr(&self) -> *mut T { + self.ptr + } +} + +impl Drop for TWWrapper { + fn drop(&mut self) { + if self.ptr.is_null() { + return; + } + unsafe { (self.destructor)(self.ptr) } + } +} diff --git a/rust/tw_tests/tests/chains/ton/ton_wallet.rs b/rust/tw_tests/tests/chains/ton/ton_wallet.rs index e7cdf8f35f9..a2187e038ed 100644 --- a/rust/tw_tests/tests/chains/ton/ton_wallet.rs +++ b/rust/tw_tests/tests/chains/ton/ton_wallet.rs @@ -2,14 +2,44 @@ // // Copyright © 2017 Trust Wallet. +use tw_encoding::hex::ToHex; +use tw_keypair::test_utils::tw_private_key_helper::TWPrivateKeyHelper; use tw_keypair::test_utils::tw_public_key_helper::TWPublicKeyHelper; use tw_keypair::tw::PublicKeyType; use tw_memory::test_utils::tw_string_helper::TWStringHelper; +use tw_memory::test_utils::tw_wrapper::TWWrapper; use tw_ton::resources::WALLET_ID_V5R1_TON_MAINNET; -use wallet_core_rs::ffi::ton::wallet::{ +use wallet_core_rs::ffi::wallet::ton_wallet::{ tw_ton_wallet_build_v4_r2_state_init, tw_ton_wallet_build_v5_r1_state_init, + tw_ton_wallet_create_with_mnemonic, tw_ton_wallet_delete, tw_ton_wallet_get_key, + tw_ton_wallet_is_valid_mnemonic, }; +#[test] +fn test_ton_wallet_is_valid_mnemonic() { + let mnemonic = TWStringHelper::create("protect drill sugar gallery note admit input wrist chicken swarm scheme hedgehog orbit ritual glove ski buddy slogan fragile sun delay toy lucky require"); + let passphrase = TWStringHelper::create(""); + let invalid_passphrase = TWStringHelper::create("Expected empty passphrase"); + assert!(unsafe { tw_ton_wallet_is_valid_mnemonic(mnemonic.ptr(), passphrase.ptr()) }); + assert!(!unsafe { tw_ton_wallet_is_valid_mnemonic(mnemonic.ptr(), invalid_passphrase.ptr()) }); +} + +#[test] +fn test_ton_wallet_get_key() { + let mnemonic = TWStringHelper::create("protect drill sugar gallery note admit input wrist chicken swarm scheme hedgehog orbit ritual glove ski buddy slogan fragile sun delay toy lucky require"); + let passphrase = std::ptr::null(); + let wallet = TWWrapper::wrap( + unsafe { tw_ton_wallet_create_with_mnemonic(mnemonic.ptr(), passphrase) }, + tw_ton_wallet_delete, + ); + let key = TWPrivateKeyHelper::wrap(unsafe { tw_ton_wallet_get_key(wallet.ptr()) }); + let key_data = key.bytes().unwrap(); + assert_eq!( + key_data.to_hex(), + "cdcea50b87d3f1ca859e7b2bdf9a5339b7b6804b5c70ac85198829f9607dc43b" + ); +} + #[test] fn test_ton_wallet_v4_r2_create_state_init() { let public_key = TWPublicKeyHelper::with_hex( diff --git a/rust/wallet_core_rs/Cargo.toml b/rust/wallet_core_rs/Cargo.toml index 1b8f34f853a..3ce587ab341 100644 --- a/rust/wallet_core_rs/Cargo.toml +++ b/rust/wallet_core_rs/Cargo.toml @@ -12,6 +12,7 @@ default = [ "any-coin", "bitcoin", "ethereum", + "hd_wallet", "keypair", "solana", "ton", @@ -20,6 +21,7 @@ default = [ any-coin = ["tw_any_coin"] bitcoin = ["tw_bitcoin", "tw_coin_registry"] ethereum = ["tw_ethereum", "tw_coin_registry"] +hd_wallet = ["tw_hd_wallet"] keypair = ["tw_keypair"] solana = ["tw_solana"] ton = ["tw_ton"] @@ -47,4 +49,5 @@ tw_misc = { path = "../tw_misc" } tw_proto = { path = "../tw_proto", optional = true } tw_solana = { path = "../chains/tw_solana", optional = true } tw_ton = { path = "../chains/tw_ton", optional = true } +tw_hd_wallet = { path = "../tw_hd_wallet", optional = true } uuid = { version = "1.7", features = ["v4"], optional = true } diff --git a/rust/wallet_core_rs/src/ffi/mod.rs b/rust/wallet_core_rs/src/ffi/mod.rs index 7b77f1ed28a..513396b4a06 100644 --- a/rust/wallet_core_rs/src/ffi/mod.rs +++ b/rust/wallet_core_rs/src/ffi/mod.rs @@ -12,3 +12,5 @@ pub mod solana; pub mod ton; #[cfg(feature = "utils")] pub mod utils; +#[cfg(feature = "hd_wallet")] +pub mod wallet; diff --git a/rust/wallet_core_rs/src/ffi/ton/mod.rs b/rust/wallet_core_rs/src/ffi/ton/mod.rs index 02721354dfc..fe4f7cf25f6 100644 --- a/rust/wallet_core_rs/src/ffi/ton/mod.rs +++ b/rust/wallet_core_rs/src/ffi/ton/mod.rs @@ -4,4 +4,3 @@ pub mod address_converter; pub mod message_signer; -pub mod wallet; diff --git a/rust/wallet_core_rs/src/ffi/ton/wallet.rs b/rust/wallet_core_rs/src/ffi/ton/wallet.rs deleted file mode 100644 index 0e3c81a1971..00000000000 --- a/rust/wallet_core_rs/src/ffi/ton/wallet.rs +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#![allow(clippy::missing_safety_doc)] - -use tw_keypair::ffi::pubkey::TWPublicKey; -use tw_memory::ffi::tw_string::TWString; -use tw_memory::ffi::RawPtrTrait; -use tw_misc::try_or_else; -use tw_ton::modules::wallet_provider::WalletProvider; - -/// Constructs a TON Wallet V4R2 stateInit encoded as BoC (BagOfCells) for the given `public_key`. -/// -/// \param public_key wallet's public key. -/// \param workchain TON workchain to which the wallet belongs. Usually, base chain is used (0). -/// \param wallet_id wallet's ID allows to create multiple wallets for the same private key. -/// \return Pointer to a base64 encoded Bag Of Cells (BoC) StateInit. Null if invalid public key provided. -#[no_mangle] -pub unsafe extern "C" fn tw_ton_wallet_build_v4_r2_state_init( - public_key: *const TWPublicKey, - workchain: i32, - wallet_id: i32, -) -> *mut TWString { - let public_key = try_or_else!(TWPublicKey::from_ptr_as_ref(public_key), std::ptr::null_mut); - let ed_pubkey = try_or_else!(public_key.as_ref().to_ed25519(), std::ptr::null_mut).clone(); - - let state_init = try_or_else!( - WalletProvider::v4r2_state_init(ed_pubkey, workchain, wallet_id), - std::ptr::null_mut - ); - TWString::from(state_init).into_ptr() -} - -// Constructs a TON Wallet V5R1 stateInit encoded as BoC (BagOfCells) for the given `public_key`. -/// -/// \param public_key wallet's public key. -/// \param workchain TON workchain to which the wallet belongs. Usually, base chain is used (0). -/// \param wallet_id wallet's ID allows to create multiple wallets for the same private key. -/// \return Pointer to a base64 encoded Bag Of Cells (BoC) StateInit. Null if invalid public key provided. -#[no_mangle] -pub unsafe extern "C" fn tw_ton_wallet_build_v5_r1_state_init( - public_key: *const TWPublicKey, - workchain: i32, - wallet_id: i32, -) -> *mut TWString { - let public_key = try_or_else!(TWPublicKey::from_ptr_as_ref(public_key), std::ptr::null_mut); - let ed_pubkey = try_or_else!(public_key.as_ref().to_ed25519(), std::ptr::null_mut).clone(); - - let state_init = try_or_else!( - WalletProvider::v5r1_state_init(ed_pubkey, workchain, wallet_id), - std::ptr::null_mut - ); - TWString::from(state_init).into_ptr() -} diff --git a/rust/wallet_core_rs/src/ffi/wallet/mod.rs b/rust/wallet_core_rs/src/ffi/wallet/mod.rs new file mode 100644 index 00000000000..3a0788a1e2c --- /dev/null +++ b/rust/wallet_core_rs/src/ffi/wallet/mod.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod ton_wallet; diff --git a/rust/wallet_core_rs/src/ffi/wallet/ton_wallet.rs b/rust/wallet_core_rs/src/ffi/wallet/ton_wallet.rs new file mode 100644 index 00000000000..d367824ed81 --- /dev/null +++ b/rust/wallet_core_rs/src/ffi/wallet/ton_wallet.rs @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#![allow(clippy::missing_safety_doc)] + +use tw_hd_wallet::ton::TonWallet; +use tw_keypair::ffi::privkey::TWPrivateKey; +use tw_keypair::ffi::pubkey::TWPublicKey; +use tw_keypair::traits::KeyPairTrait; +use tw_keypair::tw; +use tw_memory::ffi::tw_string::TWString; +use tw_memory::ffi::RawPtrTrait; +use tw_misc::{try_or_else, try_or_false}; +use tw_ton::modules::wallet_provider::WalletProvider; + +pub struct TWTONWallet(TonWallet); + +impl RawPtrTrait for TWTONWallet {} + +/// Determines whether the English mnemonic and passphrase are valid. +/// +/// \param mnemonic Non-null english mnemonic +/// \param passphrase Nullable optional passphrase +/// \note passphrase can be null or empty string if no passphrase required +/// \return whether the mnemonic and passphrase are valid (valid checksum) +#[no_mangle] +pub unsafe extern "C" fn tw_ton_wallet_is_valid_mnemonic( + mnemonic: *const TWString, + passphrase: *const TWString, +) -> bool { + let mnemonic = try_or_false!(TWString::from_ptr_as_ref(mnemonic)); + let mnemonic = try_or_false!(mnemonic.as_str()); + + let passphrase = TWString::from_ptr_as_ref(passphrase) + .and_then(TWString::as_str) + .map(|pass| pass.to_string()); + + TonWallet::new(mnemonic, passphrase).is_ok() +} + +/// Creates a `TONWallet` from a valid TON mnemonic and passphrase. +/// +/// \param mnemonic Non-null english mnemonic +/// \param passphrase Nullable optional passphrase +/// \note Null is returned on invalid mnemonic and passphrase +/// \note passphrase can be null or empty string if no passphrase required +/// \return Nullable TWTONWallet +#[no_mangle] +pub unsafe extern "C" fn tw_ton_wallet_create_with_mnemonic( + mnemonic: *const TWString, + passphrase: *const TWString, +) -> *mut TWTONWallet { + let mnemonic = try_or_else!(TWString::from_ptr_as_ref(mnemonic), std::ptr::null_mut); + let mnemonic = try_or_else!(mnemonic.as_str(), std::ptr::null_mut); + + let passphrase = TWString::from_ptr_as_ref(passphrase) + .and_then(TWString::as_str) + .map(|pass| pass.to_string()); + + let wallet = try_or_else!(TonWallet::new(mnemonic, passphrase), std::ptr::null_mut); + TWTONWallet(wallet).into_ptr() +} + +/// Delete the given TON mnemonic. +/// +/// \param wallet *non-null* pointer to TWTONMnemonic. +#[no_mangle] +pub unsafe extern "C" fn tw_ton_wallet_delete(wallet: *mut TWTONWallet) { + // Take the ownership back to rust and drop the owner. + let _ = TWTONWallet::from_ptr(wallet); +} + +/// Generates Ed25519 private key associated with the wallet. +/// +/// \param wallet non-null TWTONWallet +/// \note Returned object needs to be deleted with \TWPrivateKeyDelete +/// \return The Ed25519 private key +#[no_mangle] +pub unsafe extern "C" fn tw_ton_wallet_get_key(wallet: *const TWTONWallet) -> *mut TWPrivateKey { + let wallet = try_or_else!(TWTONWallet::from_ptr_as_ref(wallet), std::ptr::null_mut); + let key = wallet.0.to_key_pair().private().clone(); + TWPrivateKey(tw::PrivateKey::from(key)).into_ptr() +} + +/// Constructs a TON Wallet V4R2 stateInit encoded as BoC (BagOfCells) for the given `public_key`. +/// +/// \param public_key wallet's public key. +/// \param workchain TON workchain to which the wallet belongs. Usually, base chain is used (0). +/// \param wallet_id wallet's ID allows to create multiple wallets for the same private key. +/// \return Pointer to a base64 encoded Bag Of Cells (BoC) StateInit. Null if invalid public key provided. +#[no_mangle] +pub unsafe extern "C" fn tw_ton_wallet_build_v4_r2_state_init( + public_key: *const TWPublicKey, + workchain: i32, + wallet_id: i32, +) -> *mut TWString { + let public_key = try_or_else!(TWPublicKey::from_ptr_as_ref(public_key), std::ptr::null_mut); + let ed_pubkey = try_or_else!(public_key.as_ref().to_ed25519(), std::ptr::null_mut).clone(); + + let state_init = try_or_else!( + WalletProvider::v4r2_state_init(ed_pubkey, workchain, wallet_id), + std::ptr::null_mut + ); + TWString::from(state_init).into_ptr() +} + +// Constructs a TON Wallet V5R1 stateInit encoded as BoC (BagOfCells) for the given `public_key`. +/// +/// \param public_key wallet's public key. +/// \param workchain TON workchain to which the wallet belongs. Usually, base chain is used (0). +/// \param wallet_id wallet's ID allows to create multiple wallets for the same private key. +/// \return Pointer to a base64 encoded Bag Of Cells (BoC) StateInit. Null if invalid public key provided. +#[no_mangle] +pub unsafe extern "C" fn tw_ton_wallet_build_v5_r1_state_init( + public_key: *const TWPublicKey, + workchain: i32, + wallet_id: i32, +) -> *mut TWString { + let public_key = try_or_else!(TWPublicKey::from_ptr_as_ref(public_key), std::ptr::null_mut); + let ed_pubkey = try_or_else!(public_key.as_ref().to_ed25519(), std::ptr::null_mut).clone(); + + let state_init = try_or_else!( + WalletProvider::v5r1_state_init(ed_pubkey, workchain, wallet_id), + std::ptr::null_mut + ); + TWString::from(state_init).into_ptr() +} diff --git a/src/DerivationPath.cpp b/src/DerivationPath.cpp index dd54e28efbc..c2d369ee413 100644 --- a/src/DerivationPath.cpp +++ b/src/DerivationPath.cpp @@ -46,6 +46,10 @@ DerivationPath::DerivationPath(const std::string& string) { } std::string DerivationPath::string() const noexcept { + if (indices.empty()) { + return {}; + } + std::string result = "m/"; for (auto& index : indices) { result += index.string(); diff --git a/src/Keystore/StoredKey.cpp b/src/Keystore/StoredKey.cpp index b216461fe31..732e0953e37 100644 --- a/src/Keystore/StoredKey.cpp +++ b/src/Keystore/StoredKey.cpp @@ -8,6 +8,7 @@ #include "HexCoding.h" #include "Mnemonic.h" #include "PrivateKey.h" +#include "TheOpenNetwork/TONWallet.h" #include #include @@ -67,6 +68,24 @@ StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& na return key; } +StoredKey StoredKey::createWithTonMnemonic(const std::string& name, const Data& password, const std::string& tonMnemonic, TWStoredKeyEncryption encryption) { + if (!TheOpenNetwork::TONWallet::isValidMnemonic(tonMnemonic, std::nullopt)) { + throw std::invalid_argument("Invalid TON mnemonic"); + } + + Data mnemonicData = TW::Data(tonMnemonic.begin(), tonMnemonic.end()); + StoredKey key(StoredKeyType::tonMnemonicPhrase, name, password, mnemonicData, TWStoredKeyEncryptionLevelDefault, encryption); + memzero(mnemonicData.data(), mnemonicData.size()); + return key; +} + +StoredKey StoredKey::createWithTonMnemonicAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const std::string& tonMnemonic, TWStoredKeyEncryption encryption) { + StoredKey key = createWithTonMnemonic(name, password, tonMnemonic, encryption); + const auto tonWallet = key.tonWallet(password); + key.account(coin, TWDerivationDefault, tonWallet); + return key; +} + StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption) : type(type), id(), name(std::move(name)), accounts() { const auto encryptionParams = EncryptionParameters::getPreset(encryptionLevel, encryption); @@ -86,6 +105,20 @@ const HDWallet<> StoredKey::wallet(const Data& password) const { return HDWallet<>(mnemonic, ""); } +TheOpenNetwork::TONWallet StoredKey::tonWallet(const Data& password) const { + if (type != StoredKeyType::tonMnemonicPhrase) { + throw std::invalid_argument("Invalid account requested."); + } + const auto data = payload.decrypt(password); + const auto tonMnemonic = std::string(reinterpret_cast(data.data()), data.size()); + + auto maybeTonWallet = TheOpenNetwork::TONWallet::createWithMnemonic(tonMnemonic, std::nullopt); + if (!maybeTonWallet.has_value()) { + throw std::invalid_argument("Invalid TON mnemonic phrase."); + } + return std::move(*maybeTonWallet); +} + std::vector StoredKey::getAccounts(TWCoinType coin) const { std::vector result; for (auto& account : accounts) { @@ -143,6 +176,12 @@ std::optional StoredKey::getAccount(TWCoinType coin, TWDerivation deriv return getAccount(coin, address); } +std::optional StoredKey::getAccount(TWCoinType coin, TWDerivation derivation, const TheOpenNetwork::TONWallet& wallet) const { + // obtain address + const auto address = wallet.deriveAddress(coin, derivation); + return getAccount(coin, address); +} + Account StoredKey::fillAddressIfMissing(Account& account, const HDWallet<>* wallet) const { if (account.address.empty() && wallet != nullptr) { account.address = wallet->deriveAddress(account.coin, account.derivation); @@ -201,6 +240,28 @@ Account StoredKey::account(TWCoinType coin, TWDerivation derivation, const HDWal return accounts.back(); } +Account StoredKey::account(TWCoinType coin, TWDerivation derivation, const TheOpenNetwork::TONWallet& tonWallet) { + const auto coinAccount = getAccount(coin, derivation, tonWallet); + if (coinAccount.has_value()) { + // No need to use `fillAddressIfMissing` + // because `getAccount` searches for an account with both `coin` and `address` equal to expected. + // So `address` is always set. + return coinAccount.value(); + } + // Not found, add it. + + // No derivation path for TON wallet. Use default + const DerivationPath derivationPath {}; + const auto address = tonWallet.deriveAddress(coin, derivation); + // No extended public key for TON wallet. Use default + const std::string extendedPublicKey {}; + const auto pubKeyType = TW::publicKeyType(coin); + const auto pubKey = tonWallet.getKey(coin, derivation).getPublicKey(pubKeyType); + + addAccount(address, coin, derivation, derivationPath, hex(pubKey.bytes), extendedPublicKey); + return accounts.back(); +} + std::optional StoredKey::account(TWCoinType coin) const { return getDefaultAccountOrAny(coin, nullptr); } @@ -254,14 +315,23 @@ const PrivateKey StoredKey::privateKey(TWCoinType coin, const Data& password) { return privateKey(coin, TWDerivationDefault, password); } -const PrivateKey StoredKey::privateKey(TWCoinType coin, [[maybe_unused]] TWDerivation derivation, const Data& password) { - if (type == StoredKeyType::mnemonicPhrase) { - const auto wallet = this->wallet(password); - const Account& account = this->account(coin, derivation, wallet); - return wallet.getKey(coin, account.derivationPath); +const PrivateKey StoredKey::privateKey(TWCoinType coin, TWDerivation derivation, const Data& password) { + switch (type) { + case StoredKeyType::mnemonicPhrase: { + const auto wallet = this->wallet(password); + const Account account = this->account(coin, derivation, wallet); + return wallet.getKey(coin, account.derivationPath); + } + case StoredKeyType::privateKey: { + return PrivateKey(payload.decrypt(password)); + } + case StoredKeyType::tonMnemonicPhrase: { + const auto tonWallet = this->tonWallet(password); + return tonWallet.getKey(coin, derivation); + } + default: + throw std::invalid_argument("Unexpected StoredKey type"); } - // type == StoredKeyType::privateKey - return PrivateKey(payload.decrypt(password)); } void StoredKey::fixAddresses(const Data& password) { @@ -289,6 +359,21 @@ void StoredKey::fixAddresses(const Data& password) { updateAddressForAccount(key, account); } } break; + + case StoredKeyType::tonMnemonicPhrase: { + const auto tonWallet = this->tonWallet(password); + for (auto& account : accounts) { + if (!account.address.empty() && !account.publicKey.empty() && + TW::validateAddress(account.coin, account.address)) { + continue; + } + const auto key = tonWallet.getKey(account.coin, account.derivation); + updateAddressForAccount(key, account); + } + } break; + + default: + throw std::invalid_argument("Unexpected StoredKey type"); } } @@ -340,12 +425,18 @@ static const auto crypto = "Crypto"; namespace TypeString { static const auto privateKey = "private-key"; static const auto mnemonic = "mnemonic"; +static const auto tonMnemonic = "ton-mnemonic"; } // namespace TypeString void StoredKey::loadJson(const nlohmann::json& json) { - if (json.count(CodingKeys::SK::type) != 0 && - json[CodingKeys::SK::type].get() == TypeString::mnemonic) { + const auto isSKType = [](const nlohmann::json& json, const std::string& expected) -> bool { + return json.count(CodingKeys::SK::type) != 0 && json[CodingKeys::SK::type].get() == expected; + }; + + if (isSKType(json, TypeString::mnemonic)) { type = StoredKeyType::mnemonicPhrase; + } else if (isSKType(json, TypeString::tonMnemonic)) { + type = StoredKeyType::tonMnemonicPhrase; } else { type = StoredKeyType::privateKey; } @@ -396,6 +487,9 @@ nlohmann::json StoredKey::json() const { case StoredKeyType::mnemonicPhrase: j[CodingKeys::SK::type] = TypeString::mnemonic; break; + case StoredKeyType::tonMnemonicPhrase: + j[CodingKeys::SK::type] = TypeString::tonMnemonic; + break; } if (id) { diff --git a/src/Keystore/StoredKey.h b/src/Keystore/StoredKey.h index 12eaf5038f4..a98aace31df 100644 --- a/src/Keystore/StoredKey.h +++ b/src/Keystore/StoredKey.h @@ -7,7 +7,8 @@ #include "Account.h" #include "EncryptionParameters.h" #include "Data.h" -#include "../HDWallet.h" +#include "HDWallet.h" +#include "TheOpenNetwork/TONWallet.h" #include #include @@ -20,9 +21,13 @@ namespace TW::Keystore { -/// An stored key can be either a private key or a mnemonic phrase for a HD -/// wallet. -enum class StoredKeyType { privateKey, mnemonicPhrase }; +/// An stored key can be either a private key, or a mnemonic phrase for a HD +/// wallet, or a TON-specific mnemonic phrase. +enum class StoredKeyType { + privateKey, + mnemonicPhrase, + tonMnemonicPhrase +}; /// Represents a key stored as an encrypted file. class StoredKey { @@ -62,6 +67,16 @@ class StoredKey { /// @throws std::invalid_argument if privateKeyData is not a valid private key static StoredKey createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); + /// Create a new StoredKey, with the given name and TON specific mnemonic. + /// @throws std::invalid_argument if menmonic is invalid + /// @note mnemonic created with a passphrase is not supported yet + static StoredKey createWithTonMnemonic(const std::string& name, const Data& password, const std::string& tonMnemonic, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); + + /// Create a new StoredKey, with the given name and TON specific mnemonic, and also add the default address for the given coin. + /// @throws std::invalid_argument if menmonic is invalid + /// @note mnemonic created with a passphrase is not supported yet + static StoredKey createWithTonMnemonicAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const std::string& tonMnemonic, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); + /// Create a StoredKey from a JSON object. static StoredKey createWithJson(const nlohmann::json& json); @@ -70,19 +85,31 @@ class StoredKey { /// @throws std::invalid_argument if this key is of a type other than `mnemonicPhrase`. const HDWallet<> wallet(const Data& password) const; + /// Returns the TONWallet for this key. + /// + /// @throws std::invalid_argument if this key is of a type other than `tonMnemonicPhrase`. + TheOpenNetwork::TONWallet tonWallet(const Data& password) const; + /// Returns all the accounts for a specific coin: 0, 1, or more. std::vector getAccounts(TWCoinType coin) const; - /// If found, returns the account for a specific coin. In case of muliple accounts, the default derivation is returned, or the first one is returned. + /// If found, returns the account for a specific coin. + /// In case of multiple accounts, the default derivation is returned, or the first one is returned. /// If none exists, and wallet is not null, an account is created (with default derivation). std::optional account(TWCoinType coin, const HDWallet<>* wallet); - /// If found, returns the account for a specific coin and derivation. In case of muliple accounts, the first one is returned. + /// If found, returns the account for a specific coin and derivation. + /// In case of multiple accounts, the first one is returned. /// If none exists, an account is created. Account account(TWCoinType coin, TWDerivation derivation, const HDWallet<>& wallet); + /// If found, returns the account for a specific coin. + /// In case of multiple accounts, the first one is returned. + /// If none exists, an account is created. + Account account(TWCoinType coin, TWDerivation derivation, const TheOpenNetwork::TONWallet& tonWallet); + /// Returns the account for a specific coin if it exists. - /// In case of muliple accounts, the default derivation is returned, or the first one is returned. + /// In case of multiple accounts, the default derivation is returned, or the first one is returned. std::optional account(TWCoinType coin) const; /// Returns the account for a specific coin and derivation, if it exists. @@ -173,6 +200,9 @@ class StoredKey { /// Find account by coin+derivation (should be one, if multiple, first is returned) std::optional getAccount(TWCoinType coin, TWDerivation derivation, const HDWallet<>& wallet) const; + /// Find account by coin+derivation (should be one, if multiple, first is returned) + std::optional getAccount(TWCoinType coin, TWDerivation derivation, const TheOpenNetwork::TONWallet& wallet) const; + /// Re-derive account address if missing Account fillAddressIfMissing(Account& account, const HDWallet<>* wallet) const; diff --git a/src/TheOpenNetwork/TONWallet.cpp b/src/TheOpenNetwork/TONWallet.cpp new file mode 100644 index 00000000000..a4b4adc6c28 --- /dev/null +++ b/src/TheOpenNetwork/TONWallet.cpp @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TONWallet.h" +#include "Coin.h" + +namespace TW::TheOpenNetwork { + +bool TONWallet::isValidMnemonic(const std::string& mnemonic, const MaybePassphrase& passphrase) { + const Rust::TWStringWrapper mnemonicRust = mnemonic; + + if (passphrase.has_value()) { + const Rust::TWStringWrapper passphraseRust = passphrase.value(); + return Rust::tw_ton_wallet_is_valid_mnemonic(mnemonicRust.get(), passphraseRust.get()); + } else { + return Rust::tw_ton_wallet_is_valid_mnemonic(mnemonicRust.get(), nullptr); + } +} + +MaybeTONWallet TONWallet::createWithMnemonic(const std::string& mnemonic, const MaybePassphrase& passphrase) { + const Rust::TWStringWrapper mnemonicRust = mnemonic; + + Rust::TWTONWallet* walletPtr; + if (passphrase.has_value()) { + const Rust::TWStringWrapper passphraseRust = passphrase.value(); + walletPtr = Rust::tw_ton_wallet_create_with_mnemonic(mnemonicRust.get(), passphraseRust.get()); + } else { + walletPtr = Rust::tw_ton_wallet_create_with_mnemonic(mnemonicRust.get(), nullptr); + } + + if (!walletPtr) { + return std::nullopt; + } + + return TONWallet(TONWalletPtr(walletPtr, Rust::tw_ton_wallet_delete)); +} + +PrivateKey TONWallet::getKey(TWCoinType coin, TWDerivation derivation) const { + if (coin != TWCoinTypeTON || derivation != TWDerivationDefault) { + throw std::invalid_argument("'TONWallet' supports TON coin and Default derivation only"); + } + + const auto privateKeyRust = wrapTWPrivateKey(Rust::tw_ton_wallet_get_key(impl.get())); + const Rust::TWDataWrapper privateKeyBytes = Rust::tw_private_key_data(privateKeyRust.get()); + return PrivateKey(privateKeyBytes.toDataOrDefault()); +} + +std::string TONWallet::deriveAddress(TWCoinType coin, TWDerivation derivation) const { + const auto key = getKey(coin, derivation); + return TW::deriveAddress(coin, key, derivation); +} + +} // namespace TW::TheOpenNetwork diff --git a/src/TheOpenNetwork/TONWallet.h b/src/TheOpenNetwork/TONWallet.h new file mode 100644 index 00000000000..b6d80e1f0e2 --- /dev/null +++ b/src/TheOpenNetwork/TONWallet.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "PrivateKey.h" +#include "rust/Wrapper.h" + +#include "TrustWalletCore/TWCoinType.h" +#include "TrustWalletCore/TWDerivation.h" + +namespace TW::TheOpenNetwork { + +class TONWallet; + +using TONWalletPtr = std::shared_ptr; +using MaybePassphrase = std::optional; +using MaybeTONWallet = std::optional; + +class TONWallet { +public: + static bool isValidMnemonic(const std::string& mnemonic, const MaybePassphrase& passphrase); + + static MaybeTONWallet createWithMnemonic(const std::string& mnemonic, const MaybePassphrase& passphrase); + + /// Returns the private key with the given coin and derivation. + /// \throws std exception if `coin` or `derivation` aren't `TWCoinTypeTON` and `TWDerivationDefault` correspondingly. + PrivateKey getKey(TWCoinType coin = TWCoinTypeTON, TWDerivation derivation = TWDerivationDefault) const; + + /// Derives the address for the given coin and derivation. + /// \throws std exception if `coin` or `derivation` aren't `TWCoinTypeTON` and `TWDerivationDefault` correspondingly. + std::string deriveAddress(TWCoinType coin = TWCoinTypeTON, TWDerivation derivation = TWDerivationDefault) const; + +private: + explicit TONWallet(TONWalletPtr ptr): impl(std::move(ptr)) { + } + + TONWalletPtr impl; +}; + +} // namespace TW::TheOpenNetwork + +/// Wrapper for C interface. +struct TWTONWallet { + TW::TheOpenNetwork::TONWallet impl; +}; diff --git a/src/interface/TWStoredKey.cpp b/src/interface/TWStoredKey.cpp index f756ce5e6b3..90c78c94e13 100644 --- a/src/interface/TWStoredKey.cpp +++ b/src/interface/TWStoredKey.cpp @@ -72,6 +72,21 @@ struct TWStoredKey* _Nullable TWStoredKeyImportHDWalletWithEncryption(TWString* } } +struct TWStoredKey* _Nullable TWStoredKeyImportTONWallet(TWString* _Nonnull tonMnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin) { + return TWStoredKeyImportTONWalletWithEncryption(tonMnemonic, name, password, coin, TWStoredKeyEncryptionAes128Ctr); +} + +struct TWStoredKey* _Nullable TWStoredKeyImportTONWalletWithEncryption(TWString* _Nonnull tonMnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption) { + try { + const auto& tonMnemonicString = *reinterpret_cast(tonMnemonic); + const auto& nameString = *reinterpret_cast(name); + const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); + return new TWStoredKey{ KeyStore::StoredKey::createWithTonMnemonicAddDefaultAddress(nameString, passwordData, coin, tonMnemonicString, encryption) }; + } catch (...) { + return nullptr; + } +} + struct TWStoredKey* _Nullable TWStoredKeyImportJSON(TWData* _Nonnull json) { try { const auto& d = *reinterpret_cast(json); @@ -101,6 +116,10 @@ bool TWStoredKeyIsMnemonic(struct TWStoredKey* _Nonnull key) { return key->impl.type == KeyStore::StoredKeyType::mnemonicPhrase; } +bool TWStoredKeyIsTONMnemonic(struct TWStoredKey* _Nonnull key) { + return key->impl.type == KeyStore::StoredKeyType::tonMnemonicPhrase; +} + size_t TWStoredKeyAccountCount(struct TWStoredKey* _Nonnull key) { return key->impl.accounts.size(); } @@ -191,6 +210,13 @@ TWString* _Nullable TWStoredKeyDecryptMnemonic(struct TWStoredKey* _Nonnull key, } } +TWString* _Nullable TWStoredKeyDecryptTONMnemonic(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password) { + if (!TWStoredKeyIsTONMnemonic(key)) { + return nullptr; + } + return TWStoredKeyDecryptMnemonic(key, password); +} + struct TWPrivateKey* _Nullable TWStoredKeyPrivateKey(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, TWData* _Nonnull password) { try { const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); diff --git a/src/interface/TWTONWallet.cpp b/src/interface/TWTONWallet.cpp index 7e6495491ab..137a672f2c1 100644 --- a/src/interface/TWTONWallet.cpp +++ b/src/interface/TWTONWallet.cpp @@ -3,11 +3,46 @@ // Copyright © 2017 Trust Wallet. #include "TrustWalletCore/TWTONWallet.h" +#include "TheOpenNetwork/TONWallet.h" #include "rust/Wrapper.h" #include "PublicKey.h" using namespace TW; +bool TWTONWalletIsValidMnemonic(TWString* _Nonnull mnemonic, TWString* _Nullable passphrase) { + const auto& mnemonicRef = *reinterpret_cast(mnemonic); + std::string passphraseObj; + if (passphrase) { + passphraseObj = std::string(TWStringUTF8Bytes(passphrase), TWStringSize(passphrase)); + } + + return TheOpenNetwork::TONWallet::isValidMnemonic(mnemonicRef, passphraseObj); +} + +struct TWTONWallet* _Nullable TWTONWalletCreateWithMnemonic(TWString* _Nonnull mnemonic, TWString* _Nullable passphrase) { + const auto& mnemonicRef = *reinterpret_cast(mnemonic); + std::string passphraseObj; + if (passphrase) { + passphraseObj = std::string(TWStringUTF8Bytes(passphrase), TWStringSize(passphrase)); + } + + const auto maybeWallet = TheOpenNetwork::TONWallet::createWithMnemonic(mnemonicRef, passphraseObj); + if (!maybeWallet.has_value()) { + return nullptr; + } + + return new TWTONWallet { .impl = *maybeWallet }; +} + +void TWTONWalletDelete(struct TWTONWallet* _Nonnull wallet) { + delete wallet; +} + +struct TWPrivateKey* _Nonnull TWTONWalletGetKey(struct TWTONWallet* _Nonnull wallet) { + const auto privateKey = wallet->impl.getKey(); + return new TWPrivateKey { .impl = privateKey }; +} + TWString *_Nullable TWTONWalletBuildV4R2StateInit(struct TWPublicKey *_Nonnull publicKey, int32_t workchain, int32_t walletId) { auto keyType = static_cast(TWPublicKeyKeyType(publicKey)); auto* publicKeyRustRaw = Rust::tw_public_key_create_with_data(publicKey->impl.bytes.data(), publicKey->impl.bytes.size(), keyType); diff --git a/src/rust/Wrapper.h b/src/rust/Wrapper.h index 04d97a9104c..1ac64605951 100644 --- a/src/rust/Wrapper.h +++ b/src/rust/Wrapper.h @@ -13,11 +13,15 @@ namespace TW::Rust { inline std::shared_ptr wrapTWAnyAddress(TWAnyAddress* anyAddress) { - return std::shared_ptr(anyAddress, tw_any_address_delete); + return { anyAddress, tw_any_address_delete }; +} + +inline std::shared_ptr wrapTWPrivateKey(TWPrivateKey* privateKey) { + return { privateKey, tw_private_key_delete }; } inline std::shared_ptr wrapTWPublicKey(TWPublicKey* publicKey) { - return std::shared_ptr(publicKey, tw_public_key_delete); + return { publicKey, tw_public_key_delete }; } struct TWDataVectorWrapper { diff --git a/swift/Sources/KeyStore.swift b/swift/Sources/KeyStore.swift index 0b6c3368d77..e1a6b7bfa8f 100644 --- a/swift/Sources/KeyStore.swift +++ b/swift/Sources/KeyStore.swift @@ -6,6 +6,12 @@ import Foundation +private enum StoredKeyType { + case privateKey(PrivateKey) + case mnemonic(String) + case tonMnemonic(String) +} + /// Manages directories of key and wallet files and presents them as accounts. public final class KeyStore { static let watchesFileName = "watches.json" @@ -130,25 +136,49 @@ public final class KeyStore { guard let key = StoredKey.importJSON(json: json) else { throw Error.invalidJSON } - guard let data = key.decryptPrivateKey(password: Data(password.utf8)) else { + + switch try decryptSecret(key: key, password: Data(password.utf8)) { + case .privateKey(let privateKey): + return try self.import(privateKey: privateKey, name: name, password: newPassword, coin: coins.first ?? .ethereum) + case .mnemonic(let mnemonic): + return try self.import(mnemonic: mnemonic, name: name, encryptPassword: newPassword, coins: coins) + case .tonMnemonic(let tonMnemonic): + return try self.importTON(tonMnemonic: tonMnemonic, name: name, encryptPassword: newPassword, coin: coins.first ?? .ton) + } + } + + /// Decrypts an inner secret as a `privateKey`, `mnemonic` or another type. + /// - Returns: a `StoredKeyType` enum. + /// - Note: `StoredKey::type` is not always set, and mnemonic or private key can be stored encrypted without any tag. + private func decryptSecret(key: StoredKey, password: Data) throws -> StoredKeyType { + guard var secretData = key.decryptPrivateKey(password: password) else { throw Error.invalidPassword } + defer { + secretData.resetBytes(in: 0 ..< secretData.count) + } - if let mnemonic = checkMnemonic(data) { - return try self.import(mnemonic: mnemonic, name: name, encryptPassword: newPassword, coins: coins) + // First, check whether the key is init with a TON mnemonic. + // That's because TON Wallet is a new feature, and `StoredKey::type` is always set for these kind of keys. + if key.isTONMnemonic { + guard let tonMnemonic = String(data: secretData, encoding: .ascii), TONWallet.isValidMnemonic(mnemonic: tonMnemonic, passphrase: nil) else { + throw Error.invalidMnemonic + } + return StoredKeyType.tonMnemonic(tonMnemonic) } - guard let privateKey = PrivateKey(data: data) else { - throw Error.invalidKey + // The next, we should try to convert the secret into a string and check whether it's a valid BIP39 mnemonic phrase. + if let mnemonic = String(data: secretData, encoding: .ascii), Mnemonic.isValid(mnemonic: mnemonic) { + return StoredKeyType.mnemonic(mnemonic) } - return try self.import(privateKey: privateKey, name: name, password: newPassword, coin: coins.first ?? .ethereum) - } - private func checkMnemonic(_ data: Data) -> String? { - guard let mnemonic = String(data: data, encoding: .ascii), Mnemonic.isValid(mnemonic: mnemonic) else { - return nil + // Otherwise, we consider the secret as a private key. + + if let privateKey = PrivateKey(data: secretData) { + return StoredKeyType.privateKey(privateKey) } - return mnemonic + + throw Error.invalidKey } /// Imports a private key. @@ -193,6 +223,31 @@ public final class KeyStore { return wallet } + + /// Imports a TON wallet. + /// + /// - Parameters: + /// - tonMnemonic: TON wallet's mnemonic phrase + /// - encryptPassword: password to use for encrypting + /// - coin: coins to use for this wallet + /// - Returns: new account + public func `importTON`(tonMnemonic: String, name: String, encryptPassword: String, coin: CoinType, encryption: StoredKeyEncryption = .aes128Ctr) throws -> Wallet { + guard let newKey = StoredKey.importTONWalletWithEncryption(tonMnemonic: tonMnemonic, name: name, password: Data(encryptPassword.utf8), coin: coin, encryption: encryption) else { + throw Error.invalidKey + } + + let url = makeAccountURL() + let wallet = Wallet(keyURL: url, key: newKey) + // `StoredKey.importTONWalletWithEncryption` should create exactly one account only. + if wallet.accounts.count != 1 { + throw Error.invalidKey + } + wallets.append(wallet) + + try save(wallet: wallet) + + return wallet + } /// Exports a wallet as JSON data. /// @@ -202,28 +257,29 @@ public final class KeyStore { /// - newPassword: password to use for exported key /// - Returns: encrypted JSON key public func export(wallet: Wallet, password: String, newPassword: String, encryption: StoredKeyEncryption = .aes128Ctr) throws -> Data { - var privateKeyData = try exportPrivateKey(wallet: wallet, password: password) - defer { - privateKeyData.resetBytes(in: 0 ..< privateKeyData.count) - } - + // TODO why `importHDWalletWithEncryption` is called with a single coin? + // I guess that's because we don't want other wallets to know all user accounts. guard let coin = wallet.key.account(index: 0)?.coin else { throw Error.accountNotFound } - if let mnemonic = checkMnemonic(privateKeyData), let newKey = StoredKey.importHDWalletWithEncryption(mnemonic: mnemonic, name: "", password: Data(newPassword.utf8), coin: coin, encryption: encryption) { - guard let json = newKey.exportJSON() else { - throw Error.invalidKey - } - return json - } else if let newKey = StoredKey.importPrivateKeyWithEncryption(privateKey: privateKeyData, name: "", password: Data(newPassword.utf8), coin: coin, encryption: encryption) { - guard let json = newKey.exportJSON() else { - throw Error.invalidKey - } - return json + let secretType = try decryptSecret(key: wallet.key, password: Data(password.utf8)) + + let maybeNewKey: StoredKey? = switch secretType { + case .privateKey(let privateKey): + StoredKey.importPrivateKeyWithEncryption(privateKey: privateKey.data, name: "", password: Data(newPassword.utf8), coin: coin, encryption: encryption) + + case .mnemonic(let mnemonic): + StoredKey.importHDWalletWithEncryption(mnemonic: mnemonic, name: "", password: Data(newPassword.utf8), coin: coin, encryption: encryption) + + case .tonMnemonic(let tonMnemonic): + StoredKey.importTONWalletWithEncryption(tonMnemonic: tonMnemonic, name: "", password: Data(newPassword.utf8), coin: coin, encryption: encryption) } - throw Error.invalidKey + guard let newKey = maybeNewKey, let json = newKey.exportJSON() else { + throw Error.invalidKey + } + return json } /// Exports a wallet as private key data. @@ -232,6 +288,7 @@ public final class KeyStore { /// - wallet: wallet to export /// - password: account password /// - Returns: private key data for encrypted keys or mnemonic phrase for HD wallets + /// - Throws: `EncryptError.invalidPassword` if the password is incorrect. public func exportPrivateKey(wallet: Wallet, password: String) throws -> Data { guard let key = wallet.key.decryptPrivateKey(password: Data(password.utf8)) else { throw Error.invalidPassword @@ -245,13 +302,27 @@ public final class KeyStore { /// - wallet: wallet to export /// - password: account password /// - Returns: mnemonic phrase - /// - Throws: `EncryptError.invalidMnemonic` if the account is not an HD wallet. + /// - Throws: `EncryptError.invalidPassword` if the password is incorrect. public func exportMnemonic(wallet: Wallet, password: String) throws -> String { guard let mnemonic = wallet.key.decryptMnemonic(password: Data(password.utf8)) else { throw Error.invalidPassword } return mnemonic } + + /// Exports a wallet as a TON mnemonic phrase. + /// + /// - Parameters: + /// - wallet: wallet to export + /// - password: account password + /// - Returns: TON mnemonic phrase + /// - Throws: `EncryptError.invalidPassword` if the password is incorrect. + public func exportTONMnemonic(wallet: Wallet, password: String) throws -> String { + guard let tonMnemonic = wallet.key.decryptTONMnemonic(password: Data(password.utf8)) else { + throw Error.invalidPassword + } + return tonMnemonic + } /// Updates the password of an existing account. /// @@ -278,28 +349,30 @@ public final class KeyStore { fatalError("Missing wallet") } - guard var privateKeyData = wallet.key.decryptPrivateKey(password: Data(password.utf8)) else { - throw Error.invalidPassword - } - defer { - privateKeyData.resetBytes(in: 0 ..< privateKeyData.count) - } - let coins = wallet.accounts.map({ $0.coin }) guard !coins.isEmpty else { throw Error.accountNotFound } - if let mnemonic = checkMnemonic(privateKeyData), - let key = StoredKey.importHDWalletWithEncryption(mnemonic: mnemonic, name: newName, password: Data(newPassword.utf8), coin: coins[0], encryption: encryption) { - wallets[index].key = key - } else if let key = StoredKey.importPrivateKeyWithEncryption( - privateKey: privateKeyData, name: newName, password: Data(newPassword.utf8), coin: coins[0], encryption: encryption) { - wallets[index].key = key - } else { + let secretType = try decryptSecret(key: wallet.key, password: Data(password.utf8)) + + let maybeNewKey: StoredKey? = switch secretType { + case .privateKey(let privateKey): + StoredKey.importPrivateKeyWithEncryption( + privateKey: privateKey.data, name: newName, password: Data(newPassword.utf8), coin: coins[0], encryption: encryption) + + case .mnemonic(let mnemonic): + StoredKey.importHDWalletWithEncryption(mnemonic: mnemonic, name: newName, password: Data(newPassword.utf8), coin: coins[0], encryption: encryption) + + case .tonMnemonic(let tonMnemonic): + StoredKey.importTONWalletWithEncryption(tonMnemonic: tonMnemonic, name: newName, password: Data(newPassword.utf8), coin: coins[0], encryption: encryption) + } + + guard let newKey = maybeNewKey else { throw Error.invalidKey } + wallets[index].key = newKey _ = try wallets[index].getAccounts(password: newPassword, coins: coins) try save(wallet: wallets[index]) } diff --git a/swift/Sources/Wallet.swift b/swift/Sources/Wallet.swift index 04749b5d564..0dc2ec1c6d3 100644 --- a/swift/Sources/Wallet.swift +++ b/swift/Sources/Wallet.swift @@ -59,7 +59,7 @@ public final class Wallet: Hashable, Equatable { return account } - /// Returns the accounts for a specific coins. + /// Returns the accounts for specific coins. /// /// - Parameters: /// - password: wallet encryption password @@ -67,6 +67,10 @@ public final class Wallet: Hashable, Equatable { /// - Returns: the added accounts /// - Throws: `KeyStore.Error.invalidPassword` if the password is incorrect. public func getAccounts(password: String, coins: [CoinType]) throws -> [Account] { + if !key.isMnemonic { + return coins.compactMap({ key.accountForCoin(coin: $0, wallet: nil) }) + } + guard let wallet = key.wallet(password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword } diff --git a/swift/Tests/Keystore/Data/ton_wallet.json b/swift/Tests/Keystore/Data/ton_wallet.json new file mode 100644 index 00000000000..3b744a4b01e --- /dev/null +++ b/swift/Tests/Keystore/Data/ton_wallet.json @@ -0,0 +1,30 @@ +{ + "activeAccounts": [ + { + "address": "UQBlm676c6vy6Q9Js732pvf3ivfmIkVc0MVDQy-F6NAFJd4k", + "coin": 607, + "derivationPath": "", + "publicKey": "9016f03f9cfa4e183707761f25407e0e1975194a33a56b3e8d2c26f2438fa3d1" + } + ], + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "459bbf197fe8e1cdfb949fc516257238" + }, + "ciphertext": "efbda7237b41d2340a4b4c7f0ab6a103e31f7a2b8148137abc815a0d346c3ae851d842ad068ad0218ef9f81647f41e52f7b056d568a314433a0b7980601f96e8974a071b5ddedd50079eebdb9281a8d84a0b9d4649ef3125f6605e303a78c0a4ed67556e3cd4e88b78b120267544eb44a912c92516562acb9782e0ea989cb50bce5948e2dfa26107053caf838af096ce47357061a2b49654", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 16384, + "p": 4, + "r": 8, + "salt": "" + }, + "mac": "be9e9a26076334683799c2dbe22bfb2d9f2ff92ad130aa61b4687b392e7b98e6" + }, + "id": "f7a2172e-fb7a-427a-8526-99779fc47c0a", + "name": "name", + "type": "ton-mnemonic", + "version": 3 +} diff --git a/swift/Tests/Keystore/KeyStoreTests.swift b/swift/Tests/Keystore/KeyStoreTests.swift index 3017d10c304..087dcd5912c 100755 --- a/swift/Tests/Keystore/KeyStoreTests.swift +++ b/swift/Tests/Keystore/KeyStoreTests.swift @@ -25,12 +25,17 @@ extension KeyStore { var bnbWallet: Wallet { return wallets.first(where: { $0.identifier == "bnb_wallet.json"})! } + + var tonWallet: Wallet? { + return wallets.first(where: { $0.identifier == "ton_wallet.json"}) + } } class KeyStoreTests: XCTestCase { let keyAddress = AnyAddress(string: "0x008AeEda4D805471dF9b2A5B0f38A0C3bCBA786b", coin: .ethereum)! let walletAddress = AnyAddress(string: "0x32dd55E0BCF509a35A3F5eEb8593fbEb244796b1", coin: .ethereum)! let mnemonic = "often tobacco bread scare imitate song kind common bar forest yard wisdom" + let tonMnemonic = "laundry myself fitness beyond prize piano match acid vacuum already abandon dance occur pause grocery company inject excuse weasel carpet fog grunt trick spike" let fileManager = FileManager.default var keyDirectory: URL! @@ -73,11 +78,17 @@ class KeyStoreTests: XCTestCase { try? fileManager.removeItem(at: bnbWalletDestination) try? fileManager.copyItem(at: bnbWalletURL, to: bnbWalletDestination) + + let tonWalletURL = Bundle(for: type(of: self)).url(forResource: "ton_wallet", withExtension: "json")! + let tonWalletDestination = keyDirectory.appendingPathComponent("ton_wallet.json") + + try? fileManager.removeItem(at: tonWalletDestination) + try? fileManager.copyItem(at: tonWalletURL, to: tonWalletDestination) } func testLoadKeyStore() { let keyStore = try! KeyStore(keyDirectory: keyDirectory) - XCTAssertEqual(keyStore.wallets.count, 4) + XCTAssertEqual(keyStore.wallets.count, 5) XCTAssertEqual(keyStore.watches.count, 1) } @@ -87,13 +98,13 @@ class KeyStoreTests: XCTestCase { let newWallet = try keyStore.createWallet(name: "name", password: "password", coins: coins) XCTAssertEqual(newWallet.accounts.count, 3) - XCTAssertEqual(keyStore.wallets.count, 5) + XCTAssertEqual(keyStore.wallets.count, 6) XCTAssertNoThrow(try newWallet.getAccount(password: "password", coin: .ethereum)) XCTAssertNoThrow(try newWallet.getAccount(password: "password", coin: .binance)) XCTAssertNoThrow(try newWallet.getAccount(password: "password", coin: .smartChain)) } - func testUpdateKey() throws { + func testUpdatePassword() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) let coins = [CoinType.ethereum, .callisto, .poanetwork] let wallet = try keyStore.createWallet(name: "name", password: "password", coins: coins) @@ -113,6 +124,21 @@ class KeyStoreTests: XCTestCase { XCTAssertEqual(savedWallet.key.name, "name") } + func testUpdatePasswordTON() throws { + let keyStore = try KeyStore(keyDirectory: keyDirectory) + + try keyStore.update(wallet: keyStore.tonWallet!, password: "password", newPassword: "testpassword") + + let savedKeyStore = try KeyStore(keyDirectory: keyDirectory) + let savedWallet = savedKeyStore.tonWallet! + + let tonMnemonic = savedWallet.key.decryptTONMnemonic(password: Data("testpassword".utf8))! + + XCTAssertEqual(savedWallet.accounts.count, 1) + XCTAssert(TONWallet.isValidMnemonic(mnemonic: tonMnemonic, passphrase: nil)) + XCTAssertEqual(savedWallet.key.name, "name") + } + func testUpdateName() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) let coins = [CoinType.ethereum, .callisto, .poanetwork] @@ -155,6 +181,17 @@ class KeyStoreTests: XCTestCase { XCTAssertEqual(savedWallet.accounts.count, 1) XCTAssertEqual(savedWallet.accounts[0].coin, coins.last) } + + func testRemoveTONAccounts() throws { + let keyStore = try KeyStore(keyDirectory: keyDirectory) + + _ = try keyStore.removeAccounts(wallet: keyStore.tonWallet!, coins: [.ton], password: "password") + + let savedKeyStore = try KeyStore(keyDirectory: keyDirectory) + let savedWallet = savedKeyStore.tonWallet! + // The only account should have been removed. + XCTAssertEqual(savedWallet.accounts.count, 0) + } func testDeleteKey() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) @@ -169,6 +206,13 @@ class KeyStoreTests: XCTestCase { try keyStore.delete(wallet: wallet, password: "password") XCTAssertNil(keyStore.hdWallet) } + + func testDeleteTONWallet() throws { + let keyStore = try KeyStore(keyDirectory: keyDirectory) + let wallet = keyStore.tonWallet! + try keyStore.delete(wallet: wallet, password: "password") + XCTAssertNil(keyStore.tonWallet) + } func testImportKey() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) @@ -232,7 +276,60 @@ class KeyStoreTests: XCTestCase { XCTAssertEqual(wallet.accounts.count, 1) XCTAssertNotNil(keyStore.hdWallet) } + + func testImportTONWallet() throws { + let keyStore = try KeyStore(keyDirectory: keyDirectory) + let wallet = try keyStore.importTON(tonMnemonic: tonMnemonic, name: "name", encryptPassword: "newPassword", coin: .ton, encryption: .aes128Ctr) + let storedData = wallet.key.decryptMnemonic(password: Data("newPassword".utf8)) + XCTAssertNotNil(storedData) + XCTAssertEqual(wallet.accounts.count, 1) + } + + func testImportTONWalletJSON() throws { + let json = """ + { + "activeAccounts": [ + { + "address": "UQByxuJBNpeC4QjGdgnfeO8oM4G9srUG1FyIGmqX3YnVQ4p1", + "coin": 607, + "derivationPath": "", + "publicKey": "3bab20a5f77e277e39443fc16c64e0479b4a9db542bf9e11c638598384c853f1" + } + ], + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "d7fdc3fa7a09e1163094d14a557ed1b6" + }, + "ciphertext": "58017dfbee52c6f22fa1eed323cda21e3413de8da48083e09be9a826bc4f9c184f14e7a47ffcc5f539dc94435d1742dfdc0785e612d039c4858777da9dcd92960580cb9c755434832d94f88b8f562a23ad16f7b6165bbd709a701b3ec46efbe5f6aa858000ce19641abcb7d20475fa1e9cfed5f2f5dae7c76d6496d54bd6db593050617c85c0f6bc3cf8fac89b671d53924202037e1c0e1ecd521492e5", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 16384, + "p": 4, + "r": 8, + "salt": "" + }, + "mac": "ddc49eecdec579021cd18526982ec9519a82f8c39ff20aa7d9aa7f02d5ebd36e" + }, + "id": "e11e5404-73d5-416c-b957-a65164fb0171", + "name": "name", + "type": "ton-mnemonic", + "version": 3 + } + """ + + let password = "password" + let keyStore = try KeyStore(keyDirectory: keyDirectory) + + let wallet = try keyStore.import(json: Data(json.utf8), name: "newName", password: "password", newPassword: "newPassword", coins: [.ton]) + XCTAssertEqual(wallet.accounts.first!.address, "UQByxuJBNpeC4QjGdgnfeO8oM4G9srUG1FyIGmqX3YnVQ4p1") + let actualMnemonic = try keyStore.exportTONMnemonic(wallet: wallet, password: "newPassword") + let expectedMnemonic = "slogan train glide measure mercy dizzy when satoshi vote change length pluck token walnut actress hollow guard soup solve rival summer vicious anxiety device" + XCTAssertEqual(actualMnemonic, expectedMnemonic) + } + func testImportJSON() throws { let expected = """ { @@ -332,6 +429,14 @@ class KeyStoreTests: XCTestCase { XCTAssertEqual(mnemonic, exported) } + + func testExportTONMnemonic() throws { + let keyStore = try KeyStore(keyDirectory: keyDirectory) + let wallet = try keyStore.importTON(tonMnemonic: tonMnemonic, name: "name", encryptPassword: "newPassword", coin: .ton) + let exported = try keyStore.exportTONMnemonic(wallet: wallet, password: "newPassword") + + XCTAssertEqual(tonMnemonic, exported) + } func testFileName() { let keyStore = try! KeyStore(keyDirectory: keyDirectory) diff --git a/swift/Tests/Keystore/WalletTests.swift b/swift/Tests/Keystore/WalletTests.swift index 00815ed0271..48cf71436c5 100755 --- a/swift/Tests/Keystore/WalletTests.swift +++ b/swift/Tests/Keystore/WalletTests.swift @@ -25,4 +25,54 @@ class WalletTests: XCTestCase { let wallet = Wallet(keyURL: url, key: key) XCTAssertEqual(wallet.identifier, "UTC--2018-07-23T15-42-07.380692005-42000--6E199F01-FA96-4ADF-9A4B-36EE4B1E08C7") } + + func testPrivateKeyGetAccount() throws { + let url = Bundle(for: type(of: self)).url(forResource: "key", withExtension: "json")! + let key = StoredKey.load(path: url.path)! + let wallet = Wallet(keyURL: url, key: key) + + // The wallet already contains `Ethereum` account. No exception expected. + let ethAccount = try wallet.getAccount(password: "testpassword", coin: .ethereum) + XCTAssertEqual(ethAccount.address, "0x008AeEda4D805471dF9b2A5B0f38A0C3bCBA786b") + + let accounts1 = try wallet.getAccounts(password: "testpassword", coins: [.ethereum]) + XCTAssertEqual(accounts1.count, 1) + + // Should fail because `getAccount` currently doesn't support creating a new account from PrivateKey. + XCTAssertThrowsError(try wallet.getAccount(password: "testpassword", coin: .bitcoin)) + + // Should return only `Ethereum` account. + let accounts2 = try wallet.getAccounts(password: "testpassword", coins: [.bitcoin, .ethereum]) + XCTAssertEqual(accounts2.count, 1) + } + + func testTONWalletGetAccount() throws { + let url = Bundle(for: type(of: self)).url(forResource: "ton_wallet", withExtension: "json")! + let key = StoredKey.load(path: url.path)! + let wallet = Wallet(keyURL: url, key: key) + + // The wallet already contains `TON` account. No exception expected. + let tonAccount = try wallet.getAccount(password: "password", coin: .ton) + XCTAssertEqual(tonAccount.address, "UQBlm676c6vy6Q9Js732pvf3ivfmIkVc0MVDQy-F6NAFJd4k") + + let accounts1 = try wallet.getAccounts(password: "password", coins: [.ton]) + XCTAssertEqual(accounts1.count, 1) + + // Should fail because `getAccount` currently doesn't support creating a new account from PrivateKey. + XCTAssertThrowsError(try wallet.getAccount(password: "password", coin: .ethereum)) + + // Should return only `TON` account. + let accounts2 = try wallet.getAccounts(password: "password", coins: [.bitcoin, .ton]) + XCTAssertEqual(accounts2.count, 1) + } + + func testTONWalletGetPrivateKey() throws { + let url = Bundle(for: type(of: self)).url(forResource: "ton_wallet", withExtension: "json")! + let key = StoredKey.load(path: url.path)! + let wallet = Wallet(keyURL: url, key: key) + + // The wallet already contains `TON` account. No exception expected. + let privateKey = try wallet.privateKey(password: "password", coin: .ton) + XCTAssertEqual(privateKey.data.hexString, "cdcea50b87d3f1ca859e7b2bdf9a5339b7b6804b5c70ac85198829f9607dc43b") + } } diff --git a/tests/chains/TheOpenNetwork/TWTONWalletTests.cpp b/tests/chains/TheOpenNetwork/TWTONWalletTests.cpp index b346ee5568b..b77b47773be 100644 --- a/tests/chains/TheOpenNetwork/TWTONWalletTests.cpp +++ b/tests/chains/TheOpenNetwork/TWTONWalletTests.cpp @@ -9,6 +9,25 @@ namespace TW::TheOpenNetwork::tests { +TEST(TWTONWallet, IsValidMnemonic) { + const auto mnemonic = STRING("sight shed side garbage illness clean health wet all win bench wide exist find galaxy drift task suggest portion fresh valve crime radar combine"); + const auto emptyPassphrase = STRING(""); + const auto invalidPassphrase = STRING("Expected empty passphrase"); + EXPECT_TRUE(TWTONWalletIsValidMnemonic(mnemonic.get(), nullptr)); + EXPECT_TRUE(TWTONWalletIsValidMnemonic(mnemonic.get(), emptyPassphrase.get())); + EXPECT_FALSE(TWTONWalletIsValidMnemonic(mnemonic.get(), invalidPassphrase.get())); +} + +TEST(TWTONWallet, MnemonicToPrivateKey) { + const auto mnemonic = STRING("sight shed side garbage illness clean health wet all win bench wide exist find galaxy drift task suggest portion fresh valve crime radar combine"); + const auto wallet = WRAP(TWTONWallet, TWTONWalletCreateWithMnemonic(mnemonic.get(), nullptr)); + EXPECT_TRUE(wallet); + const auto key = WRAP(TWPrivateKey, TWTONWalletGetKey(wallet.get())); + EXPECT_TRUE(key); + const auto keyBytes = WRAPD(TWPrivateKeyData(key.get())); + assertHexEqual(keyBytes, "b471884e691a9f5bb641b14f33bb9e555f759c24e368c4c0d997db3a60704220"); +} + TEST(TWTONWallet, BuildV4R2StateInit) { auto publicKeyBytes = DATA("f229a9371fa7c2108b3d90ea22c9be705ff5d0cfeaee9cbb9366ff0171579357"); auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(publicKeyBytes.get(), TWPublicKeyTypeED25519)); diff --git a/tests/common/Keystore/Data/ton-wallet.json b/tests/common/Keystore/Data/ton-wallet.json new file mode 100644 index 00000000000..3ed1c08b087 --- /dev/null +++ b/tests/common/Keystore/Data/ton-wallet.json @@ -0,0 +1,30 @@ +{ + "activeAccounts": [ + { + "address": "UQBlm676c6vy6Q9Js732pvf3ivfmIkVc0MVDQy-F6NAFJd4k", + "coin": 607, + "derivationPath": "", + "publicKey": "9016f03f9cfa4e183707761f25407e0e1975194a33a56b3e8d2c26f2438fa3d1" + } + ], + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "459bbf197fe8e1cdfb949fc516257238" + }, + "ciphertext": "efbda7237b41d2340a4b4c7f0ab6a103e31f7a2b8148137abc815a0d346c3ae851d842ad068ad0218ef9f81647f41e52f7b056d568a314433a0b7980601f96e8974a071b5ddedd50079eebdb9281a8d84a0b9d4649ef3125f6605e303a78c0a4ed67556e3cd4e88b78b120267544eb44a912c92516562acb9782e0ea989cb50bce5948e2dfa26107053caf838af096ce47357061a2b49654", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 16384, + "p": 4, + "r": 8, + "salt": "" + }, + "mac": "be9e9a26076334683799c2dbe22bfb2d9f2ff92ad130aa61b4687b392e7b98e6" + }, + "id": "f7a2172e-fb7a-427a-8526-99779fc47c0a", + "name": "Test TON Account", + "type": "ton-mnemonic", + "version": 3 +} diff --git a/tests/common/Keystore/StoredKeyConstants.h b/tests/common/Keystore/StoredKeyConstants.h new file mode 100644 index 00000000000..93ce7404702 --- /dev/null +++ b/tests/common/Keystore/StoredKeyConstants.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +extern std::string TESTS_ROOT; + +namespace TW::Keystore::tests { + +const auto gName = "name"; +const auto gPasswordString = "password"; +const auto gPassword = TW::data(std::string(gPasswordString)); + +inline std::string testDataPath(const char *subpath) { + return TESTS_ROOT + "/common/Keystore/Data/" + subpath; +} + +} // namespace TW::Keystore::tests diff --git a/tests/common/Keystore/StoredKeyTONTests.cpp b/tests/common/Keystore/StoredKeyTONTests.cpp new file mode 100644 index 00000000000..6be77a508d3 --- /dev/null +++ b/tests/common/Keystore/StoredKeyTONTests.cpp @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Keystore/StoredKey.h" + +#include "Coin.h" +#include "Data.h" +#include "HexCoding.h" +#include "StoredKeyConstants.h" + +#include +#include + +namespace TW::Keystore::tests { + +using namespace std; + +const auto gTONMnemonic = "protect drill sugar gallery note admit input wrist chicken swarm scheme hedgehog orbit ritual glove ski buddy slogan fragile sun delay toy lucky require"; +// The following TON mnemonic requires a passphrase to be used that we don't support right now. +const auto gTONInvalidMnemonic = "mimic close sibling chair shuffle goat fashion chunk increase tennis scene ceiling divert cross treat happy soccer sample umbrella oyster advance quality perfect call"; +const auto gTONPrivateKey = "cdcea50b87d3f1ca859e7b2bdf9a5339b7b6804b5c70ac85198829f9607dc43b"; +const auto gTONPublicKey = "9016f03f9cfa4e183707761f25407e0e1975194a33a56b3e8d2c26f2438fa3d1"; +const auto gBounceableAddress = "EQBlm676c6vy6Q9Js732pvf3ivfmIkVc0MVDQy-F6NAFJYPh"; +const auto gNonBounceableAddress = "UQBlm676c6vy6Q9Js732pvf3ivfmIkVc0MVDQy-F6NAFJd4k"; + +TEST(StoredKeyTON, CreateWithTonMnemonicAddDefault) { + auto key = StoredKey::createWithTonMnemonicAddDefaultAddress(gName, gPassword, TWCoinTypeTON, gTONMnemonic); + EXPECT_EQ(key.type, StoredKeyType::tonMnemonicPhrase); + const Data& mnemo2Data = key.payload.decrypt(gPassword); + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gTONMnemonic)); + EXPECT_EQ(key.accounts.size(), 1ul); + EXPECT_EQ(key.accounts[0].coin, TWCoinTypeTON); + EXPECT_EQ(key.accounts[0].address, "UQBlm676c6vy6Q9Js732pvf3ivfmIkVc0MVDQy-F6NAFJd4k"); + EXPECT_EQ(key.accounts[0].publicKey, gTONPublicKey); + EXPECT_EQ(key.accounts[0].extendedPublicKey, ""); + EXPECT_EQ(key.accounts[0].derivationPath.string(), ""); + EXPECT_EQ(key.accounts[0].derivation, TWDerivationDefault); + EXPECT_EQ(hex(key.privateKey(TWCoinTypeTON, gPassword).bytes), gTONPrivateKey); + EXPECT_EQ(key.payload.params.cipher(), "aes-128-ctr"); + + const auto json = key.json(); + EXPECT_EQ(json["name"], gName); + EXPECT_EQ(json["type"], "ton-mnemonic"); + EXPECT_EQ(json["version"], 3); +} + +TEST(StoredKeyTON, CreateWithTonMnemonicInvalid) { + EXPECT_THROW( + StoredKey::createWithTonMnemonicAddDefaultAddress(gName, gPassword, TWCoinTypeTON, gTONInvalidMnemonic), + std::invalid_argument + ); +} + +TEST(StoredKeyTON, CreateWithTonMnemonicInvalidCoinType) { + EXPECT_THROW( + StoredKey::createWithTonMnemonicAddDefaultAddress(gName, gPassword, TWCoinTypeBitcoin, gTONMnemonic), + std::invalid_argument + ); +} + +TEST(StoredKeyTON, CreateWithMnemonicAddDefaultAddressAes256) { + auto key = StoredKey::createWithTonMnemonicAddDefaultAddress(gName, gPassword, TWCoinTypeTON, gTONMnemonic, TWStoredKeyEncryptionAes256Ctr); + EXPECT_EQ(key.type, StoredKeyType::tonMnemonicPhrase); + const Data& mnemo2Data = key.payload.decrypt(gPassword); + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gTONMnemonic)); + EXPECT_EQ(key.accounts.size(), 1ul); + EXPECT_EQ(key.accounts[0].coin, TWCoinTypeTON); + EXPECT_EQ(key.accounts[0].address, "UQBlm676c6vy6Q9Js732pvf3ivfmIkVc0MVDQy-F6NAFJd4k"); + EXPECT_EQ(key.payload.params.cipher(), "aes-256-ctr"); +} + +TEST(StoredKeyTON, HDWalletNotSupported) { + auto key = StoredKey::createWithTonMnemonicAddDefaultAddress(gName, gPassword, TWCoinTypeTON, gTONMnemonic); + EXPECT_THROW(key.wallet(gPassword), std::invalid_argument); +} + +TEST(StoredKeyTON, AddRemoveAccount) { + auto key = StoredKey::createWithTonMnemonicAddDefaultAddress(gName, gPassword, TWCoinTypeTON, gTONMnemonic); + EXPECT_EQ(key.accounts.size(), 1ul); + + // Add another dummy (doesn't belong to the mnemonic) TON account. + { + const DerivationPath derivationPath {}; + const auto publicKey = "b191d35f81aa8b144aa91c90a6b887e0b165ad9c2933b1c5266eb5c4e8bea241"; + const auto extendedPublicKey = ""; + key.addAccount("UQDSRYDMMez8BdcOuPEiaR6aJZpO6EjlIwmOBFn14mMbnRah", TWCoinTypeTON, TWDerivationDefault, derivationPath, publicKey, extendedPublicKey); + EXPECT_EQ(key.accounts.size(), 2ul); + } + + key.removeAccount(TWCoinTypeTON, TWDerivationDefault); + EXPECT_EQ(key.accounts.size(), 0ul); +} + +TEST(StoredKeyTON, FixAddressHasNoEffect) { + // `StoredKey::createWithTonMnemonicAddDefaultAddress` derives the correct address. + auto key = StoredKey::createWithTonMnemonicAddDefaultAddress(gName, gPassword, TWCoinTypeTON, gTONMnemonic); + EXPECT_EQ(key.accounts.size(), 1ul); + + key.fixAddresses(gPassword); + EXPECT_EQ(key.accounts[0].address, gNonBounceableAddress); +} + +TEST(StoredKeyTON, FixAddress) { + auto key = StoredKey::createWithTonMnemonic(gName, gPassword, gTONMnemonic); + EXPECT_EQ(key.accounts.size(), 0ul); + + // Add an account with an invalid address manually. + { + const DerivationPath derivationPath {}; + const auto publicKey = gTONPublicKey; + const auto extendedPublicKey = ""; + const auto invalidAddress = "INVALID_ADDRESS"; + key.addAccount(invalidAddress, TWCoinTypeTON, TWDerivationDefault, derivationPath, publicKey, extendedPublicKey); + EXPECT_EQ(key.accounts.size(), 1ul); + } + + key.fixAddresses(gPassword); + EXPECT_EQ(key.accounts.size(), 1ul); + EXPECT_EQ(key.accounts[0].coin, TWCoinTypeTON); + // Address should be fixed to a valid non-bounceable address. + EXPECT_EQ(key.accounts[0].address, gNonBounceableAddress); +} + +TEST(StoredKeyTON, UpdateAddress) { + auto key = StoredKey::createWithTonMnemonic(gName, gPassword, gTONMnemonic); + EXPECT_EQ(key.accounts.size(), 0ul); + + // Add an account with a bounceable (EQ) address. + { + const DerivationPath derivationPath {}; + const auto publicKey = gTONPublicKey; + const auto extendedPublicKey = ""; + const auto invalidAddress = gBounceableAddress; + key.addAccount(invalidAddress, TWCoinTypeTON, TWDerivationDefault, derivationPath, publicKey, extendedPublicKey); + EXPECT_EQ(key.accounts.size(), 1ul); + } + + key.updateAddress(TWCoinTypeTON); + EXPECT_EQ(key.accounts.size(), 1ul); + EXPECT_EQ(key.accounts[0].coin, TWCoinTypeTON); + // Address should be fixed to a valid non-bounceable address. + EXPECT_EQ(key.accounts[0].address, gNonBounceableAddress); +} + +TEST(StoredKeyTON, LoadNonexistent) { + ASSERT_THROW(StoredKey::load(testDataPath("nonexistent.json")), invalid_argument); +} + +TEST(StoredKeyTON, LoadTonMnemonic) { + const auto key = StoredKey::load(testDataPath("ton-wallet.json")); + EXPECT_EQ(key.type, StoredKeyType::tonMnemonicPhrase); + EXPECT_EQ(key.id, "f7a2172e-fb7a-427a-8526-99779fc47c0a"); + EXPECT_EQ(key.name, "Test TON Account"); + + const auto data = key.payload.decrypt(gPassword); + const auto mnemonic = string(reinterpret_cast(data.data()), data.size()); + EXPECT_EQ(mnemonic, gTONMnemonic); + + EXPECT_EQ(key.accounts[0].coin, TWCoinTypeTON); + EXPECT_EQ(key.accounts[0].derivationPath.string(), ""); + EXPECT_EQ(key.accounts[0].address, gNonBounceableAddress); + EXPECT_EQ(key.accounts[0].publicKey, gTONPublicKey); +} + +TEST(StoredKeyTON, InvalidPassword) { + const auto key = StoredKey::load(testDataPath("ton-wallet.json")); + + ASSERT_THROW(key.payload.decrypt(TW::data("INVALID PASSWORD")), DecryptionError); +} + +} // namespace TW::Keystore diff --git a/tests/common/Keystore/StoredKeyTests.cpp b/tests/common/Keystore/StoredKeyTests.cpp index ba189cfe5a1..f6170c8e2d7 100644 --- a/tests/common/Keystore/StoredKeyTests.cpp +++ b/tests/common/Keystore/StoredKeyTests.cpp @@ -10,28 +10,21 @@ #include "HexCoding.h" #include "Mnemonic.h" #include "PrivateKey.h" +#include "StoredKeyConstants.h" #include #include -extern std::string TESTS_ROOT; - namespace TW::Keystore::tests { using namespace std; -const auto passwordString = "password"; -const auto gPassword = TW::data(string(passwordString)); -const auto gMnemonic = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; -const TWCoinType coinTypeBc = TWCoinTypeBitcoin; -const TWCoinType coinTypeBnb = TWCoinTypeBinance; -const TWCoinType coinTypeBsc = TWCoinTypeSmartChain; -const TWCoinType coinTypeEth = TWCoinTypeEthereum; -const TWCoinType coinTypeBscLegacy = TWCoinTypeSmartChainLegacy; - -const std::string testDataPath(const char* subpath) { - return TESTS_ROOT + "/common/Keystore/Data/" + subpath; -} +static const auto gMnemonic = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; +static const TWCoinType coinTypeBc = TWCoinTypeBitcoin; +static const TWCoinType coinTypeBnb = TWCoinTypeBinance; +static const TWCoinType coinTypeBsc = TWCoinTypeSmartChain; +static const TWCoinType coinTypeEth = TWCoinTypeEthereum; +static const TWCoinType coinTypeBscLegacy = TWCoinTypeSmartChainLegacy; TEST(StoredKey, CreateWithMnemonic) { auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelDefault); @@ -355,6 +348,7 @@ TEST(StoredKey, LoadLegacyMnemonic) { EXPECT_EQ(key.id, "629aad29-0b22-488e-a0e7-b4219d4f311c"); const auto data = key.payload.decrypt(gPassword); + // In this case, the encrypted mnemonic contains `\0` value at the end. const auto mnemonic = string(reinterpret_cast(data.data())); EXPECT_EQ(mnemonic, "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn back"); diff --git a/tests/interface/TWStoredKeyTests.cpp b/tests/interface/TWStoredKeyTests.cpp index ac9bd0cc366..ebb52d6b676 100644 --- a/tests/interface/TWStoredKeyTests.cpp +++ b/tests/interface/TWStoredKeyTests.cpp @@ -38,6 +38,32 @@ struct std::shared_ptr createDefaultStoredKey(TWStoredKeyEncryption return createAStoredKey(TWCoinTypeBitcoin, password.get(), encryption); } +/// Return a StoredKey instance that can be used for further tests. Needs to be deleted at the end. +struct std::shared_ptr createTONStoredKey(TWData* password, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr) { + const auto mnemonic = WRAPS(TWStringCreateWithUTF8Bytes("slim holiday tiny pizza donor egg round three verify post chat social offer mix rack soft loud code option learn this pipe mouse mango")); + const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); + const auto coin = TWCoinTypeTON; + + return WRAP(TWStoredKey, TWStoredKeyImportTONWalletWithEncryption(mnemonic.get(), name.get(), password, coin, encryption)); +} + +Data readFileData(const std::string& path) { + // read contents of file + ifstream ifs(path); + // get length of file: + ifs.seekg (0, ifs.end); + auto length = ifs.tellg(); + ifs.seekg (0, ifs.beg); + EXPECT_TRUE(length > 20); + + Data data(length); + size_t idx = 0; + // read the slow way, ifs.read gave some false warnings with codacy + while (!ifs.eof() && idx < static_cast(length)) { char c = ifs.get(); data[idx++] = (uint8_t)c; } + + return data; +} + TEST(TWStoredKey, loadPBKDF2Key) { const auto filename = WRAPS(TWStringCreateWithUTF8Bytes((TESTS_ROOT + "/common/Keystore/Data/pbkdf2.json").c_str())); const auto key = WRAP(TWStoredKey, TWStoredKeyLoad(filename.get())); @@ -127,6 +153,36 @@ TEST(TWStoredKey, importHDWalletAES256) { EXPECT_EQ(nokey.get(), nullptr); } +TEST(TWStoredKey, importTONWallet) { + const auto mnemonicStr = "slim holiday tiny pizza donor egg round three verify post chat social offer mix rack soft loud code option learn this pipe mouse mango"; + const auto mnemonic = WRAPS(TWStringCreateWithUTF8Bytes(mnemonicStr)); + const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + const auto coin = TWCoinTypeTON; + const auto key = WRAP(TWStoredKey, TWStoredKeyImportTONWallet(mnemonic.get(), name.get(), password.get(), coin)); + EXPECT_FALSE(TWStoredKeyIsMnemonic(key.get())); + EXPECT_TRUE(TWStoredKeyIsTONMnemonic(key.get())); + + const auto actualMnemonic = WRAPS(TWStoredKeyDecryptTONMnemonic(key.get(), password.get())); + assertStringsEqual(actualMnemonic, mnemonicStr); + + // invalid mnemonic + const auto mnemonicInvalid = WRAPS(TWStringCreateWithUTF8Bytes("_THIS_IS_AN_INVALID_MNEMONIC_")); + const auto nokey = WRAP(TWStoredKey, TWStoredKeyImportTONWallet(mnemonicInvalid.get(), name.get(), password.get(), coin)); + EXPECT_EQ(nokey.get(), nullptr); +} + +TEST(TWStoredKey, importTONWalletAES256) { + const auto mnemonic = WRAPS(TWStringCreateWithUTF8Bytes("slim holiday tiny pizza donor egg round three verify post chat social offer mix rack soft loud code option learn this pipe mouse mango")); + const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + const auto coin = TWCoinTypeTON; + const auto key = WRAP(TWStoredKey, TWStoredKeyImportTONWalletWithEncryption(mnemonic.get(), name.get(), password.get(), coin, TWStoredKeyEncryptionAes256Ctr)); + EXPECT_TRUE(TWStoredKeyIsTONMnemonic(key.get())); +} + TEST(TWStoredKey, addressAddRemove) { const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); @@ -162,6 +218,15 @@ TEST(TWStoredKey, addressAddRemove) { EXPECT_EQ(TWStoredKeyAccount(key.get(), 1001), nullptr); } +/// HDWallet cannot be created from a TON mnemonic. +TEST(TWStoredKey, TONWalletGetWalletNotSupported) { + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + + const auto key = createTONStoredKey(password.get()); + EXPECT_EQ(TWStoredKeyWallet(key.get(), password.get()), nullptr); +} + TEST(TWStoredKey, addressAddRemoveDerivationPath) { const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); @@ -214,25 +279,26 @@ TEST(TWStoredKey, exportJSON) { EXPECT_EQ(TWDataGet(json.get(), 0), '{'); } +TEST(TWStoredKey, TONWalletExportJSON) { + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + + const auto key = createTONStoredKey(password.get()); + + const auto jsonData = WRAPD(TWStoredKeyExportJSON(key.get())); + const auto jsonStr = WRAPS(TWStringCreateWithRawBytes(TWDataBytes(jsonData.get()), TWDataSize(jsonData.get()))); + const auto json = nlohmann::json::parse(string(TWStringUTF8Bytes(jsonStr.get()))); + EXPECT_EQ(json["type"], "ton-mnemonic"); + EXPECT_EQ(json["activeAccounts"].size(), 1ul); +} + TEST(TWStoredKey, storeAndImportJSONAES256) { const auto key = createDefaultStoredKey(TWStoredKeyEncryptionAes256Ctr); const auto outFileName = string(getTestTempDir() + "/TWStoredKey_store.json"); const auto outFileNameStr = WRAPS(TWStringCreateWithUTF8Bytes(outFileName.c_str())); EXPECT_TRUE(TWStoredKeyStore(key.get(), outFileNameStr.get())); - // read contents of file - ifstream ifs(outFileName); - // get length of file: - ifs.seekg (0, ifs.end); - auto length = ifs.tellg(); - ifs.seekg (0, ifs.beg); - EXPECT_TRUE(length > 20); - - Data json(length); - size_t idx = 0; - // read the slow way, ifs.read gave some false warnings with codacy - while (!ifs.eof() && idx < static_cast(length)) { char c = ifs.get(); json[idx++] = (uint8_t)c; } - + const auto json = readFileData(outFileName); const auto key2 = WRAP(TWStoredKey, TWStoredKeyImportJSON(WRAPD(TWDataCreateWithData(&json)).get())); const auto name2 = WRAPS(TWStoredKeyName(key2.get())); EXPECT_EQ(string(TWStringUTF8Bytes(name2.get())), "name"); @@ -243,24 +309,28 @@ TEST(TWStoredKey, storeAndImportJSON) { const auto outFileName = string(getTestTempDir() + "/TWStoredKey_store.json"); const auto outFileNameStr = WRAPS(TWStringCreateWithUTF8Bytes(outFileName.c_str())); EXPECT_TRUE(TWStoredKeyStore(key.get(), outFileNameStr.get())); - //EXPECT_TRUE(filesystem::exists(outFileName)); // some linker issues with filesystem - - // read contents of file - ifstream ifs(outFileName); - // get length of file: - ifs.seekg (0, ifs.end); - auto length = ifs.tellg(); - ifs.seekg (0, ifs.beg); - EXPECT_TRUE(length > 20); - Data json(length); - size_t idx = 0; - // read the slow way, ifs.read gave some false warnings with codacy - while (!ifs.eof() && idx < static_cast(length)) { char c = ifs.get(); json[idx++] = (uint8_t)c; } + const auto json = readFileData(outFileName); + const auto key2 = WRAP(TWStoredKey, TWStoredKeyImportJSON(WRAPD(TWDataCreateWithData(&json)).get())); + const auto name2 = WRAPS(TWStoredKeyName(key2.get())); + EXPECT_EQ(string(TWStringUTF8Bytes(name2.get())), "name"); + EXPECT_TRUE(TWStoredKeyIsMnemonic(key2.get())); +} + +TEST(TWStoredKey, TONWalletStoreAndImport) { + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + + const auto key = createTONStoredKey(password.get()); + const auto outFileName = string(getTestTempDir() + "/TWStoredKey_storeTON.json"); + const auto outFileNameStr = WRAPS(TWStringCreateWithUTF8Bytes(outFileName.c_str())); + EXPECT_TRUE(TWStoredKeyStore(key.get(), outFileNameStr.get())); + const auto json = readFileData(outFileName); const auto key2 = WRAP(TWStoredKey, TWStoredKeyImportJSON(WRAPD(TWDataCreateWithData(&json)).get())); const auto name2 = WRAPS(TWStoredKeyName(key2.get())); EXPECT_EQ(string(TWStringUTF8Bytes(name2.get())), "name"); + EXPECT_TRUE(TWStoredKeyIsTONMnemonic(key2.get())); } TEST(TWStoredKey, importJsonInvalid) { @@ -392,10 +462,10 @@ TEST(TWStoredKey, getWalletPasswordInvalid) { const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); - + const auto invalidString = WRAPS(TWStringCreateWithUTF8Bytes("_THIS_IS_INVALID_PASSWORD_")); const auto passwordInvalid = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(invalidString.get())), TWStringSize(invalidString.get()))); - + auto key = WRAP(TWStoredKey, TWStoredKeyCreate(name.get(), password.get())); ASSERT_NE(WRAP(TWHDWallet, TWStoredKeyWallet(key.get(), password.get())).get(), nullptr); ASSERT_EQ(WRAP(TWHDWallet, TWStoredKeyWallet(key.get(), passwordInvalid.get())).get(), nullptr); diff --git a/wasm/src/keystore/default-impl.ts b/wasm/src/keystore/default-impl.ts index 6da4ae6c57a..04ecb4bae87 100644 --- a/wasm/src/keystore/default-impl.ts +++ b/wasm/src/keystore/default-impl.ts @@ -104,6 +104,31 @@ export class Default implements Types.IKeyStore { }); } + importTON( + tonMnemonic: string, + name: string, + password: string, + coin: CoinType, + encryption: StoredKeyEncryption + ): Promise { + return new Promise((resolve, reject) => { + const { StoredKey, TONWallet } = this.core; + + const passphrase = ""; + if (!TONWallet.isValidMnemonic(tonMnemonic, passphrase)) { + throw Types.Error.InvalidMnemonic; + } + + let pass = Buffer.from(password); + let storedKey = StoredKey.importTONWalletWithEncryption(tonMnemonic, name, pass, coin, encryption); + let wallet = this.mapWallet(storedKey); + storedKey.delete(); + this.importWallet(wallet) + .then(() => resolve(wallet)) + .catch((error) => reject(error)); + }); + } + addAccounts( id: string, password: string, @@ -129,11 +154,23 @@ export class Default implements Types.IKeyStore { ): Promise { return this.load(id).then((wallet) => { let storedKey = this.mapStoredKey(wallet); - let hdWallet = storedKey.wallet(Buffer.from(password)); let coin = (this.core.CoinType as any).values["" + account.coin]; - let privateKey = hdWallet.getKey(coin, account.derivationPath); + + let privateKey: PrivateKey; + switch (wallet.type) { + // In case of BIP39 mnemonic, we should use the custom derivation path. + case Types.WalletType.Mnemonic: + let hdWallet = storedKey.wallet(Buffer.from(password)); + privateKey = hdWallet.getKey(coin, account.derivationPath); + hdWallet.delete(); + break; + // Otherwise, use the default implementation. + default: + privateKey = storedKey.privateKey(coin, Buffer.from(password)); + break; + } + storedKey.delete(); - hdWallet.delete(); return privateKey; }); } @@ -149,6 +186,9 @@ export class Default implements Types.IKeyStore { case Types.WalletType.PrivateKey: value = storedKey.decryptPrivateKey(Buffer.from(password)); break; + case Types.WalletType.TonMnemonic: + value = storedKey.decryptTONMnemonic(Buffer.from(password)); + break; default: throw Types.Error.InvalidJSON; } diff --git a/wasm/src/keystore/types.ts b/wasm/src/keystore/types.ts index bccb05ef58a..ecdd3280db1 100644 --- a/wasm/src/keystore/types.ts +++ b/wasm/src/keystore/types.ts @@ -9,6 +9,7 @@ export enum WalletType { PrivateKey = "privateKey", WatchOnly = "watchOnly", Hardware = "hardware", + TonMnemonic = "ton-mnemonic" } export enum Error { @@ -68,6 +69,15 @@ export interface IKeyStore { // Import a Wallet object directly importWallet(wallet: Wallet): Promise; + // Import a TON wallet by 24-words mnemonic, name, password and initial active account (from coinType) + importTON( + tonMnemonic: string, + name: string, + password: string, + coin: CoinType, + encryption: StoredKeyEncryption + ): Promise; + // Add active accounts to a wallet by wallet id, password, coin addAccounts(id: string, password: string, coins: CoinType[]): Promise; @@ -78,7 +88,7 @@ export interface IKeyStore { account: ActiveAccount ): Promise; - // Delete a wallet by wallet id and password.aq1aq + // Delete a wallet by wallet id and password delete(id: string, password: string): Promise; // Export a wallet by wallet id and password, returns mnemonic or private key diff --git a/wasm/tests/KeyStore+extension.test.ts b/wasm/tests/KeyStore+extension.test.ts index 9cc1dcf5c22..a44b7cb3504 100644 --- a/wasm/tests/KeyStore+extension.test.ts +++ b/wasm/tests/KeyStore+extension.test.ts @@ -64,6 +64,51 @@ describe("KeyStore", async () => { }); }).timeout(10000); + it("test ExtensionStorage TONWallet", async () => { + const { CoinType, HexCoding, StoredKeyEncryption } = globalThis.core; + const tonMnemonic = globalThis.tonMnemonic as string; + const password = globalThis.password as string; + + const walletIdsKey = "all-wallet-ids"; + const storage = new KeyStore.ExtensionStorage( + walletIdsKey, + new ChromeStorageMock() + ); + const keystore = new KeyStore.Default(globalThis.core, storage); + + const wallet = await keystore.importTON(tonMnemonic, "Coolton", password, CoinType.ton, StoredKeyEncryption.aes128Ctr); + + assert.equal(wallet.name, "Coolton"); + assert.equal(wallet.type, "ton-mnemonic"); + assert.equal(wallet.version, 3); + + const account = wallet.activeAccounts[0]; + const key = await keystore.getKey(wallet.id, password, account); + + assert.equal( + HexCoding.encode(key.data()), + "0x859cd74ab605afb7ce9f5316a1f6d59217a130b75b494efd249913be874c9d46" + ); + assert.equal(account.address, "UQDdB2lMwYM9Gxc-ln--Tu8cz-TYksQxYuUsMs2Pd4cHerYz"); + assert.isUndefined(account.extendedPublicKey); + assert.equal( + account.publicKey, + "c9af50596bd5c1c5a15fb32bef8d4f1ee5244b287aea1f49f6023a79f9b2f055" + ); + + assert.isTrue(await keystore.hasWallet(wallet.id)); + assert.isFalse(await keystore.hasWallet("invalid-id")); + + const exported = await keystore.export(wallet.id, password); + assert.equal(exported, tonMnemonic); + + const wallets = await keystore.loadAll(); + + await wallets.forEach((w) => { + keystore.delete(w.id, password); + }); + }).timeout(10000); + it("test ExtensionStorage AES256", async () => { const { CoinType, HexCoding, StoredKeyEncryption } = globalThis.core; const mnemonic = globalThis.mnemonic as string; diff --git a/wasm/tests/setup.test.ts b/wasm/tests/setup.test.ts index 54d6664beee..98ede91486e 100644 --- a/wasm/tests/setup.test.ts +++ b/wasm/tests/setup.test.ts @@ -4,6 +4,8 @@ import { initWasm } from "../dist"; before(async () => { globalThis.mnemonic = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; + globalThis.tonMnemonic = + "laundry myself fitness beyond prize piano match acid vacuum already abandon dance occur pause grocery company inject excuse weasel carpet fog grunt trick spike"; globalThis.password = "password"; globalThis.core = await initWasm(); }); From 812124328cb76ef21bdfeb688fbf43f629c576c3 Mon Sep 17 00:00:00 2001 From: Viacheslav Kulish <32914239+vcoolish@users.noreply.github.com> Date: Mon, 4 Nov 2024 12:45:17 +0400 Subject: [PATCH 04/23] Fix Java JVM leak (#4092) --- codegen/lib/templates/java/class.erb | 28 ++----------------- kotlin/gradle/libs.versions.toml | 4 +++ kotlin/wallet-core-kotlin/build.gradle.kts | 17 ++++++----- .../kotlin/com/trustwallet/core/LibLoader.kt | 7 +++++ .../kotlin/com/trustwallet/core/LibLoader.kt | 2 +- .../trustwallet/core/WalletCoreLibLoader.kt | 0 6 files changed, 25 insertions(+), 33 deletions(-) create mode 100644 kotlin/wallet-core-kotlin/src/androidInstrumentedTest/kotlin/com/trustwallet/core/LibLoader.kt rename kotlin/wallet-core-kotlin/src/{jvmMain => commonAndroidJvmMain}/kotlin/com/trustwallet/core/WalletCoreLibLoader.kt (100%) diff --git a/codegen/lib/templates/java/class.erb b/codegen/lib/templates/java/class.erb index fa491ff2560..a10c6008ecb 100644 --- a/codegen/lib/templates/java/class.erb +++ b/codegen/lib/templates/java/class.erb @@ -96,32 +96,10 @@ public final class <%= entity.name %> { throw new InvalidParameterException(); } - <%= entity.name %>PhantomReference.register(this, nativeHandle); + GenericPhantomReference.register(someObject, someHandle, handle -> { + nativeDelete(nativeHandle); + }); } <%- end -%> } -<% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%> -class <%= entity.name %>PhantomReference extends java.lang.ref.PhantomReference<<%= entity.name %>> { - private static java.util.Set<<%= entity.name %>PhantomReference> references = new HashSet<<%= entity.name %>PhantomReference>(); - private static java.lang.ref.ReferenceQueue<<%= entity.name %>> queue = new java.lang.ref.ReferenceQueue<<%= entity.name %>>(); - private long nativeHandle; - - private <%= entity.name %>PhantomReference(<%= entity.name %> referent, long nativeHandle) { - super(referent, queue); - this.nativeHandle = nativeHandle; - } - - static void register(<%= entity.name %> referent, long nativeHandle) { - references.add(new <%= entity.name %>PhantomReference(referent, nativeHandle)); - } - - public static void doDeletes() { - <%= entity.name %>PhantomReference ref = (<%= entity.name %>PhantomReference) queue.poll(); - for (; ref != null; ref = (<%= entity.name %>PhantomReference) queue.poll()) { - <%= entity.name %>.nativeDelete(ref.nativeHandle); - references.remove(ref); - } - } -} -<% end -%> diff --git a/kotlin/gradle/libs.versions.toml b/kotlin/gradle/libs.versions.toml index e6e4289dd86..3d233c16d10 100644 --- a/kotlin/gradle/libs.versions.toml +++ b/kotlin/gradle/libs.versions.toml @@ -9,6 +9,10 @@ kotlin = "1.8.21" agp = "8.0.0" wire = "4.5.6" +androidx-test-runner = "1.5.2" + [libraries] wire-runtime = { module = "com.squareup.wire:wire-runtime", version.ref = "wire" } wire-compiler = { module = "com.squareup.wire:wire-compiler", version.ref = "wire" } + +androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" } diff --git a/kotlin/wallet-core-kotlin/build.gradle.kts b/kotlin/wallet-core-kotlin/build.gradle.kts index 63d879ca7c3..22782599f26 100644 --- a/kotlin/wallet-core-kotlin/build.gradle.kts +++ b/kotlin/wallet-core-kotlin/build.gradle.kts @@ -58,7 +58,7 @@ kotlin { } } - getByName("commonTest") { + val commonTest by getting { dependencies { implementation(kotlin("test")) } @@ -85,6 +85,13 @@ kotlin { implementation(npm(name = "webpack", version = "5.89.0")) } } + + getByName("androidInstrumentedTest") { + dependsOn(commonTest) + dependencies { + implementation(libs.androidx.test.runner) + } + } } nativeTargets.forEach { nativeTarget -> @@ -119,6 +126,8 @@ android { arguments += listOf("-DCMAKE_BUILD_TYPE=Release", "-DKOTLIN=True", "-DTW_UNITY_BUILD=ON") } } + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } buildFeatures { @@ -132,12 +141,6 @@ android { viewBinding = false } - androidComponents { - beforeVariants { - it.enable = it.name == "release" - } - } - externalNativeBuild { cmake { version = libs.versions.android.cmake.get() diff --git a/kotlin/wallet-core-kotlin/src/androidInstrumentedTest/kotlin/com/trustwallet/core/LibLoader.kt b/kotlin/wallet-core-kotlin/src/androidInstrumentedTest/kotlin/com/trustwallet/core/LibLoader.kt new file mode 100644 index 00000000000..e37b1e33897 --- /dev/null +++ b/kotlin/wallet-core-kotlin/src/androidInstrumentedTest/kotlin/com/trustwallet/core/LibLoader.kt @@ -0,0 +1,7 @@ +package com.trustwallet.core + +actual object LibLoader { + actual fun loadLibrary() { + System.loadLibrary("TrustWalletCore") + } +} diff --git a/kotlin/wallet-core-kotlin/src/androidUnitTest/kotlin/com/trustwallet/core/LibLoader.kt b/kotlin/wallet-core-kotlin/src/androidUnitTest/kotlin/com/trustwallet/core/LibLoader.kt index 1d4f9311712..e37b1e33897 100644 --- a/kotlin/wallet-core-kotlin/src/androidUnitTest/kotlin/com/trustwallet/core/LibLoader.kt +++ b/kotlin/wallet-core-kotlin/src/androidUnitTest/kotlin/com/trustwallet/core/LibLoader.kt @@ -2,6 +2,6 @@ package com.trustwallet.core actual object LibLoader { actual fun loadLibrary() { - throw NotImplementedError() + System.loadLibrary("TrustWalletCore") } } diff --git a/kotlin/wallet-core-kotlin/src/jvmMain/kotlin/com/trustwallet/core/WalletCoreLibLoader.kt b/kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/WalletCoreLibLoader.kt similarity index 100% rename from kotlin/wallet-core-kotlin/src/jvmMain/kotlin/com/trustwallet/core/WalletCoreLibLoader.kt rename to kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/WalletCoreLibLoader.kt From c61daac73195b4130b82c931bb9d1b57e8b2bc8c Mon Sep 17 00:00:00 2001 From: satoshiotomakan <127754187+satoshiotomakan@users.noreply.github.com> Date: Tue, 5 Nov 2024 19:00:03 +0700 Subject: [PATCH 05/23] [Chore]: Fix Android bindings (#4095) * [Chore]: Add GenericPhantomReference.java * [Chore]: Fix unnecessary null assertion in WalletCoreLibLoader.kt --- codegen/lib/templates/java/class.erb | 8 ++- .../core/java/GenericPhantomReference.java | 51 +++++++++++++++++++ .../trustwallet/core/WalletCoreLibLoader.kt | 4 +- 3 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 jni/java/wallet/core/java/GenericPhantomReference.java diff --git a/codegen/lib/templates/java/class.erb b/codegen/lib/templates/java/class.erb index a10c6008ecb..3b49d35a49d 100644 --- a/codegen/lib/templates/java/class.erb +++ b/codegen/lib/templates/java/class.erb @@ -1,5 +1,5 @@ import java.security.InvalidParameterException; -import java.util.HashSet; +import wallet.core.java.GenericPhantomReference; <% less = entity.static_methods.detect{ |i| i.name == 'Less' } -%> <% equal = entity.static_methods.detect{ |i| i.name == 'Equal' } -%> @@ -21,7 +21,7 @@ public final class <%= entity.name %> { <%= entity.name %> instance = new <%= entity.name %>(); instance.nativeHandle = nativeHandle; <% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%> - <%= entity.name %>PhantomReference.register(instance, nativeHandle); + GenericPhantomReference.register(instance, nativeHandle, <%= entity.name %>::nativeDelete); <% end -%> return instance; } @@ -96,9 +96,7 @@ public final class <%= entity.name %> { throw new InvalidParameterException(); } - GenericPhantomReference.register(someObject, someHandle, handle -> { - nativeDelete(nativeHandle); - }); + GenericPhantomReference.register(this, nativeHandle, <%= entity.name %>::nativeDelete); } <%- end -%> diff --git a/jni/java/wallet/core/java/GenericPhantomReference.java b/jni/java/wallet/core/java/GenericPhantomReference.java new file mode 100644 index 00000000000..1c5ee46dc76 --- /dev/null +++ b/jni/java/wallet/core/java/GenericPhantomReference.java @@ -0,0 +1,51 @@ +package wallet.core.java; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; +import java.util.Set; +import java.util.HashSet; + +public class GenericPhantomReference extends PhantomReference { + private final long nativeHandle; + private final OnDeleteCallback onDeleteCallback; + + private static final Set references = new HashSet<>(); + private static final ReferenceQueue queue = new ReferenceQueue<>(); + + static { + Thread finalizingDaemon = new Thread(() -> { + try { + doDeletes(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + finalizingDaemon.setName("WCFinalizingDaemon"); + finalizingDaemon.setDaemon(true); + finalizingDaemon.setPriority(Thread.NORM_PRIORITY); + finalizingDaemon.start(); + } + + private GenericPhantomReference(Object referent, long handle, OnDeleteCallback onDelete) { + super(referent, queue); + this.nativeHandle = handle; + this.onDeleteCallback = onDelete; + } + + public static void register(Object referent, long handle, OnDeleteCallback onDelete) { + references.add(new GenericPhantomReference(referent, handle, onDelete)); + } + + private static void doDeletes() throws InterruptedException { + GenericPhantomReference ref = (GenericPhantomReference) queue.remove(); + for (; ref != null; ref = (GenericPhantomReference) queue.remove()) { + ref.onDeleteCallback.nativeDelete(ref.nativeHandle); + references.remove(ref); + } + } + + @FunctionalInterface + public interface OnDeleteCallback { + void nativeDelete(long handle); + } +} diff --git a/kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/WalletCoreLibLoader.kt b/kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/WalletCoreLibLoader.kt index 23c20a24897..7c2b43eed0e 100644 --- a/kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/WalletCoreLibLoader.kt +++ b/kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/WalletCoreLibLoader.kt @@ -34,9 +34,9 @@ object WalletCoreLibLoader { } private fun getLibResourcePath(): String { - val osNameOriginal = System.getProperty("os.name").lowercase() + val osNameOriginal = System.getProperty("os.name")!!.lowercase() val osName = osNameOriginal.lowercase() - val archOriginal = System.getProperty("os.arch").lowercase() + val archOriginal = System.getProperty("os.arch")!!.lowercase() val arch = archOriginal.lowercase() return when { From 0479584c880e587c0aed04b1c704f65e23ca02ab Mon Sep 17 00:00:00 2001 From: 10gic Date: Wed, 6 Nov 2024 16:39:31 +0800 Subject: [PATCH 06/23] Fix memory lead found in public key in kmp binding (#4097) --- jni/cpp/TWJNIData.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jni/cpp/TWJNIData.cpp b/jni/cpp/TWJNIData.cpp index 942505f593f..0859a37671e 100644 --- a/jni/cpp/TWJNIData.cpp +++ b/jni/cpp/TWJNIData.cpp @@ -18,5 +18,7 @@ jbyteArray TWDataJByteArray(TWData *_Nonnull data, JNIEnv *env) { TWData *_Nonnull TWDataCreateWithJByteArray(JNIEnv *env, jbyteArray _Nonnull array) { jsize size = env->GetArrayLength(array); jbyte *bytes = env->GetByteArrayElements(array, nullptr); - return TWDataCreateWithBytes((uint8_t *) bytes, size); + const auto *twdata = TWDataCreateWithBytes((uint8_t *) bytes, size); + env->ReleaseByteArrayElements(array, bytes, JNI_ABORT); + return twdata; } From 20208f0df6814b7bac165788786ae1afd4038b2c Mon Sep 17 00:00:00 2001 From: Viacheslav Kulish <32914239+vcoolish@users.noreply.github.com> Date: Tue, 26 Nov 2024 01:10:31 -0800 Subject: [PATCH 07/23] Update Callisto explorer (#4131) --- registry.json | 4 ++-- tests/chains/Callisto/TWCoinTypeTests.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/registry.json b/registry.json index d88798aff9c..2934d0c651c 100644 --- a/registry.json +++ b/registry.json @@ -2541,9 +2541,9 @@ "chainId": "820", "addressHasher": "keccak256", "explorer": { - "url": "https://explorer.callisto.network", + "url": "https://explorer.callistodao.org", "txPath": "/tx/", - "accountPath": "/addr/" + "accountPath": "/address/" }, "info": { "url": "https://callisto.network", diff --git a/tests/chains/Callisto/TWCoinTypeTests.cpp b/tests/chains/Callisto/TWCoinTypeTests.cpp index 752e2349656..ba8ed18e998 100644 --- a/tests/chains/Callisto/TWCoinTypeTests.cpp +++ b/tests/chains/Callisto/TWCoinTypeTests.cpp @@ -25,8 +25,8 @@ TEST(TWCallistoCoinType, TWCoinType) { ASSERT_EQ(0x0, TWCoinTypeP2shPrefix(TWCoinTypeCallisto)); ASSERT_EQ(0x0, TWCoinTypeStaticPrefix(TWCoinTypeCallisto)); assertStringsEqual(symbol, "CLO"); - assertStringsEqual(txUrl, "https://explorer.callisto.network/tx/t123"); - assertStringsEqual(accUrl, "https://explorer.callisto.network/addr/a12"); + assertStringsEqual(txUrl, "https://explorer.callistodao.org/tx/t123"); + assertStringsEqual(accUrl, "https://explorer.callistodao.org/address/a12"); assertStringsEqual(id, "callisto"); assertStringsEqual(name, "Callisto"); } From c0075471a5ff2fdc7d697245ec16c4989a643292 Mon Sep 17 00:00:00 2001 From: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Date: Thu, 5 Dec 2024 18:14:43 +0700 Subject: [PATCH 08/23] Revert "[TON]: Add support for TON 24-words mnemonic (#3998)" (#4148) This reverts commit 0b167710b325d299398ca82ef57ee8e630e54633. --- .../TestTheOpenNetworkWallet.kt | 17 - .../core/app/utils/TestKeyStore.kt | 48 - include/TrustWalletCore/TWStoredKey.h | 38 - include/TrustWalletCore/TWTONWallet.h | 36 +- rust/Cargo.lock | 28 - rust/Cargo.toml | 1 - rust/chains/tw_ton/Cargo.toml | 1 - .../src/test_utils/address_utils.rs | 4 +- rust/tw_hash/Cargo.toml | 1 - rust/tw_hash/src/ffi.rs | 2 - rust/tw_hash/src/hmac.rs | 15 +- rust/tw_hash/src/lib.rs | 2 - rust/tw_hash/src/pbkdf2.rs | 10 - rust/tw_hd_wallet/Cargo.toml | 14 - rust/tw_hd_wallet/src/bip39/bip39_english.rs | 2070 ----------------- rust/tw_hd_wallet/src/bip39/mod.rs | 9 - rust/tw_hd_wallet/src/lib.rs | 18 - rust/tw_hd_wallet/src/ton/mnemonic.rs | 38 - rust/tw_hd_wallet/src/ton/mod.rs | 169 -- rust/tw_hd_wallet/tests/ton_mnemonic.rs | 156 -- rust/tw_keypair/src/ed25519/keypair.rs | 9 - rust/tw_keypair/src/ed25519/private.rs | 27 +- rust/tw_keypair/src/ffi/privkey.rs | 18 +- rust/tw_keypair/src/ffi/pubkey.rs | 2 +- .../src/test_utils/tw_crypto_box_helpers.rs | 6 +- .../src/test_utils/tw_private_key_helper.rs | 17 +- rust/tw_keypair/src/tw/private.rs | 13 - rust/tw_keypair/tests/crypto_box_ffi_tests.rs | 19 +- rust/tw_memory/Cargo.toml | 1 - rust/tw_memory/src/ffi/tw_data.rs | 13 - rust/tw_memory/src/test_utils/tw_wrapper.rs | 38 +- rust/tw_tests/tests/chains/ton/ton_wallet.rs | 32 +- rust/wallet_core_rs/Cargo.toml | 3 - rust/wallet_core_rs/src/ffi/mod.rs | 2 - rust/wallet_core_rs/src/ffi/ton/mod.rs | 1 + rust/wallet_core_rs/src/ffi/ton/wallet.rs | 55 + rust/wallet_core_rs/src/ffi/wallet/mod.rs | 5 - .../src/ffi/wallet/ton_wallet.rs | 128 - src/DerivationPath.cpp | 4 - src/Keystore/StoredKey.cpp | 112 +- src/Keystore/StoredKey.h | 44 +- src/TheOpenNetwork/TONWallet.cpp | 54 - src/TheOpenNetwork/TONWallet.h | 47 - src/interface/TWStoredKey.cpp | 26 - src/interface/TWTONWallet.cpp | 35 - src/rust/Wrapper.h | 8 +- swift/Sources/KeyStore.swift | 157 +- swift/Sources/Wallet.swift | 6 +- swift/Tests/Keystore/Data/ton_wallet.json | 30 - swift/Tests/Keystore/KeyStoreTests.swift | 111 +- swift/Tests/Keystore/WalletTests.swift | 50 - .../TheOpenNetwork/TWTONWalletTests.cpp | 19 - tests/common/Keystore/Data/ton-wallet.json | 30 - tests/common/Keystore/StoredKeyConstants.h | 19 - tests/common/Keystore/StoredKeyTONTests.cpp | 172 -- tests/common/Keystore/StoredKeyTests.cpp | 22 +- tests/interface/TWStoredKeyTests.cpp | 126 +- wasm/src/keystore/default-impl.ts | 46 +- wasm/src/keystore/types.ts | 12 +- wasm/tests/KeyStore+extension.test.ts | 45 - wasm/tests/setup.test.ts | 2 - 61 files changed, 202 insertions(+), 4041 deletions(-) delete mode 100644 rust/tw_hash/src/pbkdf2.rs delete mode 100644 rust/tw_hd_wallet/Cargo.toml delete mode 100644 rust/tw_hd_wallet/src/bip39/bip39_english.rs delete mode 100644 rust/tw_hd_wallet/src/bip39/mod.rs delete mode 100644 rust/tw_hd_wallet/src/lib.rs delete mode 100644 rust/tw_hd_wallet/src/ton/mnemonic.rs delete mode 100644 rust/tw_hd_wallet/src/ton/mod.rs delete mode 100644 rust/tw_hd_wallet/tests/ton_mnemonic.rs create mode 100644 rust/wallet_core_rs/src/ffi/ton/wallet.rs delete mode 100644 rust/wallet_core_rs/src/ffi/wallet/mod.rs delete mode 100644 rust/wallet_core_rs/src/ffi/wallet/ton_wallet.rs delete mode 100644 src/TheOpenNetwork/TONWallet.cpp delete mode 100644 src/TheOpenNetwork/TONWallet.h delete mode 100644 swift/Tests/Keystore/Data/ton_wallet.json delete mode 100644 tests/common/Keystore/Data/ton-wallet.json delete mode 100644 tests/common/Keystore/StoredKeyConstants.h delete mode 100644 tests/common/Keystore/StoredKeyTONTests.cpp diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkWallet.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkWallet.kt index b8f4b7d1b56..9305072bb75 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkWallet.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkWallet.kt @@ -4,7 +4,6 @@ package com.trustwallet.core.app.blockchains.theopennetwork -import com.trustwallet.core.app.utils.toHex import com.trustwallet.core.app.utils.toHexByteArray import org.junit.Assert.assertEquals import org.junit.Test @@ -27,20 +26,4 @@ class TestTheOpenNetworkWallet { val expected = "te6cckECFgEAAwQAAgE0AQIBFP8A9KQT9LzyyAsDAFEAAAAAKamjF/IpqTcfp8IQiz2Q6iLJvnBf9dDP6u6cu5Nm/wFxV5NXQAIBIAQFAgFIBgcE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8ICQoLAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNDA0CASAODwBu0gf6ANTUIvkABcjKBxXL/8nQd3SAGMjLBcsCIs8WUAX6AhTLaxLMzMlz+wDIQBSBAQj0UfKnAgBwgQEI1xj6ANM/yFQgR4EBCPRR8qeCEG5vdGVwdIAYyMsFywJQBs8WUAT6AhTLahLLH8s/yXP7AAIAbIEBCNcY+gDTPzBSJIEBCPRZ8qeCEGRzdHJwdIAYyMsFywJQBc8WUAP6AhPLassfEss/yXP7AAAK9ADJ7VQAeAH6APQEMPgnbyIwUAqhIb7y4FCCEHBsdWeDHrFwgBhQBMsFJs8WWPoCGfQAy2kXyx9SYMs/IMmAQPsABgCKUASBAQj0WTDtRNCBAUDXIMgBzxb0AMntVAFysI4jghBkc3Rygx6xcIAYUAXLBVADzxYj+gITy2rLH8s/yYBA+wCSXwPiAgEgEBEAWb0kK29qJoQICga5D6AhhHDUCAhHpJN9KZEM5pA+n/mDeBKAG3gQFImHFZ8xhAIBWBITABG4yX7UTQ1wsfgAPbKd+1E0IEBQNch9AQwAsjKB8v/ydABgQEI9ApvoTGACASAUFQAZrc52omhAIGuQ64X/wAAZrx32omhAEGuQ64WPwEXtMkg=" assertEquals(stateInit, expected) } - - @Test - fun TheOpenNetworkWalletIsValidMnemonic() { - val validMnemonic = "sight shed side garbage illness clean health wet all win bench wide exist find galaxy drift task suggest portion fresh valve crime radar combine" - val noPassphrase = "" - val invalidPassphrase = "Expected empty passphrase" - assert(TONWallet.isValidMnemonic(validMnemonic, noPassphrase)) - assert(!TONWallet.isValidMnemonic(validMnemonic, invalidPassphrase)) - } - - @Test - fun TheOpenNetworkWalletGetKey() { - val tonMnemonic = "sight shed side garbage illness clean health wet all win bench wide exist find galaxy drift task suggest portion fresh valve crime radar combine" - val wallet = TONWallet(tonMnemonic, "") - assertEquals(wallet.key.data().toHex(), "0xb471884e691a9f5bb641b14f33bb9e555f759c24e368c4c0d997db3a60704220") - } } \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt index 1843ea86257..599d3369216 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt @@ -4,7 +4,6 @@ import org.junit.Assert.* import org.junit.Test import wallet.core.jni.StoredKey import wallet.core.jni.CoinType -import wallet.core.jni.Derivation import wallet.core.jni.StoredKeyEncryption class TestKeyStore { @@ -18,12 +17,9 @@ class TestKeyStore { val keyStore = StoredKey("Test Wallet", "password".toByteArray()) val result = keyStore.decryptMnemonic("wrong".toByteArray()) val result2 = keyStore.decryptMnemonic("password".toByteArray()) - val result3 = keyStore.decryptTONMnemonic("password".toByteArray()) assertNull(result) assertNotNull(result2) - // StoredKey is an HD by default, so `decryptTONMnemonic` should return null. - assertNull(result3) } @Test @@ -95,48 +91,4 @@ class TestKeyStore { val privateKey = newKeyStore.decryptPrivateKey("".toByteArray()) assertNull(privateKey) } - - @Test - fun testImportTONWallet() { - val tonMnemonic = "laundry myself fitness beyond prize piano match acid vacuum already abandon dance occur pause grocery company inject excuse weasel carpet fog grunt trick spike" - val password = "password".toByteArray() - - val keyStore = StoredKey.importTONWallet(tonMnemonic, "Test Wallet", password, CoinType.TON) - - val decrypted1 = keyStore.decryptTONMnemonic("wrong".toByteArray()) - val decrypted2 = keyStore.decryptTONMnemonic("password".toByteArray()) - assertNull(decrypted1) - assertNotNull(decrypted2) - - assertEquals(keyStore.accountCount(), 1) - - // `StoredKey.account(index)` is only allowed. - // `StoredKey.accountForCoin(coin, wallet)` is not supported. - val tonAccount = keyStore.account(0) - assertEquals(tonAccount.address(), "UQDdB2lMwYM9Gxc-ln--Tu8cz-TYksQxYuUsMs2Pd4cHerYz") - assertEquals(tonAccount.coin(), CoinType.TON) - assertEquals(tonAccount.publicKey(), "c9af50596bd5c1c5a15fb32bef8d4f1ee5244b287aea1f49f6023a79f9b2f055") - assertEquals(tonAccount.extendedPublicKey(), "") - assertEquals(tonAccount.derivation(), Derivation.DEFAULT) - assertEquals(tonAccount.derivationPath(), "") - - val privateKey = keyStore.privateKey(CoinType.TON, password) - assertEquals(privateKey.data().toHex(), "0x859cd74ab605afb7ce9f5316a1f6d59217a130b75b494efd249913be874c9d46") - - // HD wallet is not supported for TON wallet - val hdWallet = keyStore.wallet(password) - assertNull(hdWallet) - } - - @Test - fun testExportTONWallet() { - val tonMnemonic = "laundry myself fitness beyond prize piano match acid vacuum already abandon dance occur pause grocery company inject excuse weasel carpet fog grunt trick spike" - val password = "password".toByteArray() - - val keyStore = StoredKey.importTONWallet(tonMnemonic, "Test Wallet", password, CoinType.TON) - val json = keyStore.exportJSON() - - val newKeyStore = StoredKey.importJSON(json) - assertEquals(newKeyStore.decryptTONMnemonic(password), tonMnemonic) - } } diff --git a/include/TrustWalletCore/TWStoredKey.h b/include/TrustWalletCore/TWStoredKey.h index 6df17e7a220..58a07e521c0 100644 --- a/include/TrustWalletCore/TWStoredKey.h +++ b/include/TrustWalletCore/TWStoredKey.h @@ -74,29 +74,6 @@ struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemo TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nullable TWStoredKeyImportHDWalletWithEncryption(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption); -/// Imports a TON-specific wallet with a 24-words mnemonic. -/// -/// \param tonMnemonic Non-null TON mnemonic -/// \param name The name of the stored key to import as a non-null string -/// \param password Non-null block of data, password of the stored key -/// \param coin the coin type -/// \note Returned object needs to be deleted with \TWStoredKeyDelete -/// \return Nullptr if the key can't be imported, the stored key otherwise -TW_EXPORT_STATIC_METHOD -struct TWStoredKey* _Nullable TWStoredKeyImportTONWallet(TWString* _Nonnull tonMnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin); - -/// Imports a TON-specific wallet with a 24-words mnemonic. -/// -/// \param tonMnemonic Non-null TON mnemonic -/// \param name The name of the stored key to import as a non-null string -/// \param password Non-null block of data, password of the stored key -/// \param coin the coin type -/// \param encryption cipher encryption mode -/// \note Returned object needs to be deleted with \TWStoredKeyDelete -/// \return Nullptr if the key can't be imported, the stored key otherwise -TW_EXPORT_STATIC_METHOD -struct TWStoredKey* _Nullable TWStoredKeyImportTONWalletWithEncryption(TWString* _Nonnull tonMnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption); - /// Imports a key from JSON. /// /// \param json Json stored key import format as a non-null block of data @@ -175,13 +152,6 @@ TWString* _Nonnull TWStoredKeyName(struct TWStoredKey* _Nonnull key); TW_EXPORT_PROPERTY bool TWStoredKeyIsMnemonic(struct TWStoredKey* _Nonnull key); -/// Whether this key is a TON mnemonic phrase. -/// -/// \param key Non-null pointer to a stored key -/// \return true if the given stored key is a TON mnemonic, false otherwise -TW_EXPORT_PROPERTY -bool TWStoredKeyIsTONMnemonic(struct TWStoredKey* _Nonnull key); - /// The number of accounts. /// /// \param key Non-null pointer to a stored key @@ -291,14 +261,6 @@ TWData* _Nullable TWStoredKeyDecryptPrivateKey(struct TWStoredKey* _Nonnull key, TW_EXPORT_METHOD TWString* _Nullable TWStoredKeyDecryptMnemonic(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); -/// Decrypts the TON mnemonic phrase. -/// -/// \param key Non-null pointer to a stored key -/// \param password Non-null block of data, password of the stored key -/// \return TON decrypted mnemonic if success, null pointer otherwise -TW_EXPORT_METHOD -TWString* _Nullable TWStoredKeyDecryptTONMnemonic(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password); - /// Returns the private key for a specific coin. Returned object needs to be deleted. /// /// \param key Non-null pointer to a stored key diff --git a/include/TrustWalletCore/TWTONWallet.h b/include/TrustWalletCore/TWTONWallet.h index 53d230482bc..098702faa6a 100644 --- a/include/TrustWalletCore/TWTONWallet.h +++ b/include/TrustWalletCore/TWTONWallet.h @@ -5,7 +5,6 @@ #pragma once #include "TWBase.h" -#include "TWPrivateKey.h" #include "TWPublicKey.h" #include "TWString.h" @@ -15,39 +14,6 @@ TW_EXTERN_C_BEGIN TW_EXPORT_CLASS struct TWTONWallet; -/// Determines whether the English mnemonic and passphrase are valid. -/// -/// \param mnemonic Non-null english mnemonic -/// \param passphrase Nullable optional passphrase -/// \note passphrase can be null or empty string if no passphrase required -/// \return whether the mnemonic and passphrase are valid (valid checksum) -TW_EXPORT_STATIC_METHOD -bool TWTONWalletIsValidMnemonic(TWString* _Nonnull mnemonic, TWString* _Nullable passphrase); - -/// Creates a \TONWallet from a valid TON mnemonic and passphrase. -/// -/// \param mnemonic Non-null english mnemonic -/// \param passphrase Nullable optional passphrase -/// \note Null is returned on invalid mnemonic and passphrase -/// \note passphrase can be null or empty string if no passphrase required -/// \return Nullable TWTONWallet -TW_EXPORT_STATIC_METHOD -struct TWTONWallet* _Nullable TWTONWalletCreateWithMnemonic(TWString* _Nonnull mnemonic, TWString* _Nullable passphrase); - -/// Delete the given \TONWallet -/// -/// \param wallet Non-null pointer to private key -TW_EXPORT_METHOD -void TWTONWalletDelete(struct TWTONWallet* _Nonnull wallet); - -/// Generates Ed25519 private key associated with the wallet. -/// -/// \param wallet non-null TWTONWallet -/// \note Returned object needs to be deleted with \TWPrivateKeyDelete -/// \return The Ed25519 private key -TW_EXPORT_METHOD -struct TWPrivateKey* _Nonnull TWTONWalletGetKey(struct TWTONWallet* _Nonnull wallet); - /// Constructs a TON Wallet V4R2 stateInit encoded as BoC (BagOfCells) for the given `public_key`. /// /// \param publicKey wallet's public key. @@ -55,6 +21,6 @@ struct TWPrivateKey* _Nonnull TWTONWalletGetKey(struct TWTONWallet* _Nonnull wal /// \param walletId wallet's ID allows to create multiple wallets for the same private key. /// \return Pointer to a base64 encoded Bag Of Cells (BoC) StateInit. Null if invalid public key provided. TW_EXPORT_STATIC_METHOD -TWString *_Nullable TWTONWalletBuildV4R2StateInit(struct TWPublicKey* _Nonnull publicKey, int32_t workchain, int32_t walletId); +TWString *_Nullable TWTONWalletBuildV4R2StateInit(struct TWPublicKey *_Nonnull publicKey, int32_t workchain, int32_t walletId); TW_EXTERN_C_END diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 55201c9282e..0c16a96d581 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1105,16 +1105,6 @@ dependencies = [ "nom", ] -[[package]] -name = "pbkdf2" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0ca0b5a68607598bf3bad68f32227a8164f6254833f84eafaac409cd6746c31" -dependencies = [ - "digest 0.10.6", - "hmac", -] - [[package]] name = "pkcs8" version = "0.10.2" @@ -1981,7 +1971,6 @@ dependencies = [ "digest 0.10.6", "groestl", "hmac", - "pbkdf2", "ripemd", "serde", "serde_json", @@ -1993,18 +1982,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "tw_hd_wallet" -version = "0.1.0" -dependencies = [ - "lazy_static", - "tw_encoding", - "tw_hash", - "tw_keypair", - "tw_misc", - "zeroize", -] - [[package]] name = "tw_internet_computer" version = "0.1.0" @@ -2055,9 +2032,6 @@ dependencies = [ [[package]] name = "tw_memory" version = "0.1.0" -dependencies = [ - "zeroize", -] [[package]] name = "tw_misc" @@ -2224,7 +2198,6 @@ dependencies = [ "tw_number", "tw_proto", "tw_ton_sdk", - "zeroize", ] [[package]] @@ -2340,7 +2313,6 @@ dependencies = [ "tw_encoding", "tw_ethereum", "tw_hash", - "tw_hd_wallet", "tw_keypair", "tw_memory", "tw_misc", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 0b427b07caa..692beec2692 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -27,7 +27,6 @@ members = [ "tw_encoding", "tw_evm", "tw_hash", - "tw_hd_wallet", "tw_keypair", "tw_memory", "tw_misc", diff --git a/rust/chains/tw_ton/Cargo.toml b/rust/chains/tw_ton/Cargo.toml index 145508522a6..076a7d84f61 100644 --- a/rust/chains/tw_ton/Cargo.toml +++ b/rust/chains/tw_ton/Cargo.toml @@ -14,4 +14,3 @@ tw_number = { path = "../../tw_number" } tw_misc = { path = "../../tw_misc" } tw_proto = { path = "../../tw_proto" } tw_ton_sdk = { path = "../../frameworks/tw_ton_sdk" } -zeroize = "1.8.1" diff --git a/rust/tw_any_coin/src/test_utils/address_utils.rs b/rust/tw_any_coin/src/test_utils/address_utils.rs index a1039463f9e..b7e896d2664 100644 --- a/rust/tw_any_coin/src/test_utils/address_utils.rs +++ b/rust/tw_any_coin/src/test_utils/address_utils.rs @@ -19,9 +19,9 @@ use tw_keypair::test_utils::tw_public_key_helper::TWPublicKeyHelper; use tw_keypair::tw::PublicKeyType; use tw_memory::test_utils::tw_data_helper::TWDataHelper; use tw_memory::test_utils::tw_string_helper::TWStringHelper; -use tw_memory::test_utils::tw_wrapper::{TWAutoWrapper, WithDestructor}; +use tw_memory::test_utils::tw_wrapper::{TWWrapper, WithDestructor}; -pub type TWAnyAddressHelper = TWAutoWrapper; +pub type TWAnyAddressHelper = TWWrapper; impl WithDestructor for TWAnyAddress { fn destructor() -> unsafe extern "C" fn(*mut Self) { diff --git a/rust/tw_hash/Cargo.toml b/rust/tw_hash/Cargo.toml index 3373eda15c9..c9b8ab18c42 100644 --- a/rust/tw_hash/Cargo.toml +++ b/rust/tw_hash/Cargo.toml @@ -13,7 +13,6 @@ blake2b-ref = "0.3.1" digest = "0.10.6" groestl = "0.10.1" hmac = "0.12.1" -pbkdf2 = "0.12.1" ripemd = "0.1.3" serde = { version = "1.0", features = ["derive"], optional = true } sha1 = "0.10.5" diff --git a/rust/tw_hash/src/ffi.rs b/rust/tw_hash/src/ffi.rs index 2867ffa4a8d..08c4faf44b9 100644 --- a/rust/tw_hash/src/ffi.rs +++ b/rust/tw_hash/src/ffi.rs @@ -14,7 +14,6 @@ pub enum CHashingCode { Ok = 0, InvalidHashLength = 1, InvalidArgument = 2, - InvalidPassword = 3, } impl From for CHashingCode { @@ -22,7 +21,6 @@ impl From for CHashingCode { match e { Error::FromHexError(_) | Error::InvalidArgument => CHashingCode::InvalidArgument, Error::InvalidHashLength => CHashingCode::InvalidHashLength, - Error::InvalidPassword => CHashingCode::InvalidPassword, } } } diff --git a/rust/tw_hash/src/hmac.rs b/rust/tw_hash/src/hmac.rs index 538d0409b15..475c5bbdb7f 100644 --- a/rust/tw_hash/src/hmac.rs +++ b/rust/tw_hash/src/hmac.rs @@ -3,19 +3,14 @@ // Copyright © 2017 Trust Wallet. use hmac::{Hmac, Mac}; -use sha2::{Sha256, Sha512}; +use sha2::Sha256; type HmacSha256 = Hmac; -type HmacSha512 = Hmac; pub fn hmac_sha256(key: &[u8], input: &[u8]) -> Vec { - let mut mac = HmacSha256::new_from_slice(key).expect("Hmac constructor should never fail"); + let mut mac = HmacSha256::new_from_slice(key).unwrap(); mac.update(input); - mac.finalize().into_bytes().to_vec() -} - -pub fn hmac_sha512(key: &[u8], input: &[u8]) -> Vec { - let mut mac = HmacSha512::new_from_slice(key).expect("Hmac constructor should never fail"); - mac.update(input); - mac.finalize().into_bytes().to_vec() + let res = mac.finalize(); + let code_bytes = res.into_bytes(); + code_bytes.to_vec() } diff --git a/rust/tw_hash/src/lib.rs b/rust/tw_hash/src/lib.rs index 6d1817edea6..2371346ee7a 100644 --- a/rust/tw_hash/src/lib.rs +++ b/rust/tw_hash/src/lib.rs @@ -9,7 +9,6 @@ pub mod ffi; pub mod groestl; pub mod hasher; pub mod hmac; -pub mod pbkdf2; pub mod ripemd; pub mod sha1; pub mod sha2; @@ -29,7 +28,6 @@ pub enum Error { FromHexError(FromHexError), InvalidHashLength, InvalidArgument, - InvalidPassword, } impl From for Error { diff --git a/rust/tw_hash/src/pbkdf2.rs b/rust/tw_hash/src/pbkdf2.rs deleted file mode 100644 index 30135d2dc17..00000000000 --- a/rust/tw_hash/src/pbkdf2.rs +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -use crate::H512; -use sha2::Sha512; - -pub fn pbkdf2_hmac_sha512(password: &[u8], salt: &[u8], rounds: u32) -> H512 { - pbkdf2::pbkdf2_hmac_array::(password, salt, rounds).into() -} diff --git a/rust/tw_hd_wallet/Cargo.toml b/rust/tw_hd_wallet/Cargo.toml deleted file mode 100644 index 72acc5d0022..00000000000 --- a/rust/tw_hd_wallet/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "tw_hd_wallet" -version = "0.1.0" -edition = "2021" - -[dependencies] -lazy_static = "1.4.0" -tw_hash = { path = "../tw_hash" } -tw_keypair = { path = "../tw_keypair" } -zeroize = "1.8.1" - -[dev-dependencies] -tw_encoding = { path = "../tw_encoding" } -tw_misc = { path = "../tw_misc" } diff --git a/rust/tw_hd_wallet/src/bip39/bip39_english.rs b/rust/tw_hd_wallet/src/bip39/bip39_english.rs deleted file mode 100644 index ae0c461da23..00000000000 --- a/rust/tw_hd_wallet/src/bip39/bip39_english.rs +++ /dev/null @@ -1,2070 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -use lazy_static::lazy_static; -use std::collections::HashMap; - -lazy_static! { - pub static ref BIP39_WORDS_MAP: HashMap<&'static str, usize> = { - BIP39_WORDS_LIST - .iter() - .enumerate() - .map(|(idx, word)| (*word, idx)) - .collect() - }; -} - -/// https://github.com/trustwallet/wallet-core/blob/43c92837db9f5d773f2545473f29c8a597d86de5/trezor-crypto/include/TrezorCrypto/bip39_english.h#L24-L367 -/// https://github.com/dvc94ch/rust-bip39/blob/master/src/language/english.rs -#[rustfmt::skip] -pub const BIP39_WORDS_LIST: [&str; 2048] = [ - "abandon", - "ability", - "able", - "about", - "above", - "absent", - "absorb", - "abstract", - "absurd", - "abuse", - "access", - "accident", - "account", - "accuse", - "achieve", - "acid", - "acoustic", - "acquire", - "across", - "act", - "action", - "actor", - "actress", - "actual", - "adapt", - "add", - "addict", - "address", - "adjust", - "admit", - "adult", - "advance", - "advice", - "aerobic", - "affair", - "afford", - "afraid", - "again", - "age", - "agent", - "agree", - "ahead", - "aim", - "air", - "airport", - "aisle", - "alarm", - "album", - "alcohol", - "alert", - "alien", - "all", - "alley", - "allow", - "almost", - "alone", - "alpha", - "already", - "also", - "alter", - "always", - "amateur", - "amazing", - "among", - "amount", - "amused", - "analyst", - "anchor", - "ancient", - "anger", - "angle", - "angry", - "animal", - "ankle", - "announce", - "annual", - "another", - "answer", - "antenna", - "antique", - "anxiety", - "any", - "apart", - "apology", - "appear", - "apple", - "approve", - "april", - "arch", - "arctic", - "area", - "arena", - "argue", - "arm", - "armed", - "armor", - "army", - "around", - "arrange", - "arrest", - "arrive", - "arrow", - "art", - "artefact", - "artist", - "artwork", - "ask", - "aspect", - "assault", - "asset", - "assist", - "assume", - "asthma", - "athlete", - "atom", - "attack", - "attend", - "attitude", - "attract", - "auction", - "audit", - "august", - "aunt", - "author", - "auto", - "autumn", - "average", - "avocado", - "avoid", - "awake", - "aware", - "away", - "awesome", - "awful", - "awkward", - "axis", - "baby", - "bachelor", - "bacon", - "badge", - "bag", - "balance", - "balcony", - "ball", - "bamboo", - "banana", - "banner", - "bar", - "barely", - "bargain", - "barrel", - "base", - "basic", - "basket", - "battle", - "beach", - "bean", - "beauty", - "because", - "become", - "beef", - "before", - "begin", - "behave", - "behind", - "believe", - "below", - "belt", - "bench", - "benefit", - "best", - "betray", - "better", - "between", - "beyond", - "bicycle", - "bid", - "bike", - "bind", - "biology", - "bird", - "birth", - "bitter", - "black", - "blade", - "blame", - "blanket", - "blast", - "bleak", - "bless", - "blind", - "blood", - "blossom", - "blouse", - "blue", - "blur", - "blush", - "board", - "boat", - "body", - "boil", - "bomb", - "bone", - "bonus", - "book", - "boost", - "border", - "boring", - "borrow", - "boss", - "bottom", - "bounce", - "box", - "boy", - "bracket", - "brain", - "brand", - "brass", - "brave", - "bread", - "breeze", - "brick", - "bridge", - "brief", - "bright", - "bring", - "brisk", - "broccoli", - "broken", - "bronze", - "broom", - "brother", - "brown", - "brush", - "bubble", - "buddy", - "budget", - "buffalo", - "build", - "bulb", - "bulk", - "bullet", - "bundle", - "bunker", - "burden", - "burger", - "burst", - "bus", - "business", - "busy", - "butter", - "buyer", - "buzz", - "cabbage", - "cabin", - "cable", - "cactus", - "cage", - "cake", - "call", - "calm", - "camera", - "camp", - "can", - "canal", - "cancel", - "candy", - "cannon", - "canoe", - "canvas", - "canyon", - "capable", - "capital", - "captain", - "car", - "carbon", - "card", - "cargo", - "carpet", - "carry", - "cart", - "case", - "cash", - "casino", - "castle", - "casual", - "cat", - "catalog", - "catch", - "category", - "cattle", - "caught", - "cause", - "caution", - "cave", - "ceiling", - "celery", - "cement", - "census", - "century", - "cereal", - "certain", - "chair", - "chalk", - "champion", - "change", - "chaos", - "chapter", - "charge", - "chase", - "chat", - "cheap", - "check", - "cheese", - "chef", - "cherry", - "chest", - "chicken", - "chief", - "child", - "chimney", - "choice", - "choose", - "chronic", - "chuckle", - "chunk", - "churn", - "cigar", - "cinnamon", - "circle", - "citizen", - "city", - "civil", - "claim", - "clap", - "clarify", - "claw", - "clay", - "clean", - "clerk", - "clever", - "click", - "client", - "cliff", - "climb", - "clinic", - "clip", - "clock", - "clog", - "close", - "cloth", - "cloud", - "clown", - "club", - "clump", - "cluster", - "clutch", - "coach", - "coast", - "coconut", - "code", - "coffee", - "coil", - "coin", - "collect", - "color", - "column", - "combine", - "come", - "comfort", - "comic", - "common", - "company", - "concert", - "conduct", - "confirm", - "congress", - "connect", - "consider", - "control", - "convince", - "cook", - "cool", - "copper", - "copy", - "coral", - "core", - "corn", - "correct", - "cost", - "cotton", - "couch", - "country", - "couple", - "course", - "cousin", - "cover", - "coyote", - "crack", - "cradle", - "craft", - "cram", - "crane", - "crash", - "crater", - "crawl", - "crazy", - "cream", - "credit", - "creek", - "crew", - "cricket", - "crime", - "crisp", - "critic", - "crop", - "cross", - "crouch", - "crowd", - "crucial", - "cruel", - "cruise", - "crumble", - "crunch", - "crush", - "cry", - "crystal", - "cube", - "culture", - "cup", - "cupboard", - "curious", - "current", - "curtain", - "curve", - "cushion", - "custom", - "cute", - "cycle", - "dad", - "damage", - "damp", - "dance", - "danger", - "daring", - "dash", - "daughter", - "dawn", - "day", - "deal", - "debate", - "debris", - "decade", - "december", - "decide", - "decline", - "decorate", - "decrease", - "deer", - "defense", - "define", - "defy", - "degree", - "delay", - "deliver", - "demand", - "demise", - "denial", - "dentist", - "deny", - "depart", - "depend", - "deposit", - "depth", - "deputy", - "derive", - "describe", - "desert", - "design", - "desk", - "despair", - "destroy", - "detail", - "detect", - "develop", - "device", - "devote", - "diagram", - "dial", - "diamond", - "diary", - "dice", - "diesel", - "diet", - "differ", - "digital", - "dignity", - "dilemma", - "dinner", - "dinosaur", - "direct", - "dirt", - "disagree", - "discover", - "disease", - "dish", - "dismiss", - "disorder", - "display", - "distance", - "divert", - "divide", - "divorce", - "dizzy", - "doctor", - "document", - "dog", - "doll", - "dolphin", - "domain", - "donate", - "donkey", - "donor", - "door", - "dose", - "double", - "dove", - "draft", - "dragon", - "drama", - "drastic", - "draw", - "dream", - "dress", - "drift", - "drill", - "drink", - "drip", - "drive", - "drop", - "drum", - "dry", - "duck", - "dumb", - "dune", - "during", - "dust", - "dutch", - "duty", - "dwarf", - "dynamic", - "eager", - "eagle", - "early", - "earn", - "earth", - "easily", - "east", - "easy", - "echo", - "ecology", - "economy", - "edge", - "edit", - "educate", - "effort", - "egg", - "eight", - "either", - "elbow", - "elder", - "electric", - "elegant", - "element", - "elephant", - "elevator", - "elite", - "else", - "embark", - "embody", - "embrace", - "emerge", - "emotion", - "employ", - "empower", - "empty", - "enable", - "enact", - "end", - "endless", - "endorse", - "enemy", - "energy", - "enforce", - "engage", - "engine", - "enhance", - "enjoy", - "enlist", - "enough", - "enrich", - "enroll", - "ensure", - "enter", - "entire", - "entry", - "envelope", - "episode", - "equal", - "equip", - "era", - "erase", - "erode", - "erosion", - "error", - "erupt", - "escape", - "essay", - "essence", - "estate", - "eternal", - "ethics", - "evidence", - "evil", - "evoke", - "evolve", - "exact", - "example", - "excess", - "exchange", - "excite", - "exclude", - "excuse", - "execute", - "exercise", - "exhaust", - "exhibit", - "exile", - "exist", - "exit", - "exotic", - "expand", - "expect", - "expire", - "explain", - "expose", - "express", - "extend", - "extra", - "eye", - "eyebrow", - "fabric", - "face", - "faculty", - "fade", - "faint", - "faith", - "fall", - "false", - "fame", - "family", - "famous", - "fan", - "fancy", - "fantasy", - "farm", - "fashion", - "fat", - "fatal", - "father", - "fatigue", - "fault", - "favorite", - "feature", - "february", - "federal", - "fee", - "feed", - "feel", - "female", - "fence", - "festival", - "fetch", - "fever", - "few", - "fiber", - "fiction", - "field", - "figure", - "file", - "film", - "filter", - "final", - "find", - "fine", - "finger", - "finish", - "fire", - "firm", - "first", - "fiscal", - "fish", - "fit", - "fitness", - "fix", - "flag", - "flame", - "flash", - "flat", - "flavor", - "flee", - "flight", - "flip", - "float", - "flock", - "floor", - "flower", - "fluid", - "flush", - "fly", - "foam", - "focus", - "fog", - "foil", - "fold", - "follow", - "food", - "foot", - "force", - "forest", - "forget", - "fork", - "fortune", - "forum", - "forward", - "fossil", - "foster", - "found", - "fox", - "fragile", - "frame", - "frequent", - "fresh", - "friend", - "fringe", - "frog", - "front", - "frost", - "frown", - "frozen", - "fruit", - "fuel", - "fun", - "funny", - "furnace", - "fury", - "future", - "gadget", - "gain", - "galaxy", - "gallery", - "game", - "gap", - "garage", - "garbage", - "garden", - "garlic", - "garment", - "gas", - "gasp", - "gate", - "gather", - "gauge", - "gaze", - "general", - "genius", - "genre", - "gentle", - "genuine", - "gesture", - "ghost", - "giant", - "gift", - "giggle", - "ginger", - "giraffe", - "girl", - "give", - "glad", - "glance", - "glare", - "glass", - "glide", - "glimpse", - "globe", - "gloom", - "glory", - "glove", - "glow", - "glue", - "goat", - "goddess", - "gold", - "good", - "goose", - "gorilla", - "gospel", - "gossip", - "govern", - "gown", - "grab", - "grace", - "grain", - "grant", - "grape", - "grass", - "gravity", - "great", - "green", - "grid", - "grief", - "grit", - "grocery", - "group", - "grow", - "grunt", - "guard", - "guess", - "guide", - "guilt", - "guitar", - "gun", - "gym", - "habit", - "hair", - "half", - "hammer", - "hamster", - "hand", - "happy", - "harbor", - "hard", - "harsh", - "harvest", - "hat", - "have", - "hawk", - "hazard", - "head", - "health", - "heart", - "heavy", - "hedgehog", - "height", - "hello", - "helmet", - "help", - "hen", - "hero", - "hidden", - "high", - "hill", - "hint", - "hip", - "hire", - "history", - "hobby", - "hockey", - "hold", - "hole", - "holiday", - "hollow", - "home", - "honey", - "hood", - "hope", - "horn", - "horror", - "horse", - "hospital", - "host", - "hotel", - "hour", - "hover", - "hub", - "huge", - "human", - "humble", - "humor", - "hundred", - "hungry", - "hunt", - "hurdle", - "hurry", - "hurt", - "husband", - "hybrid", - "ice", - "icon", - "idea", - "identify", - "idle", - "ignore", - "ill", - "illegal", - "illness", - "image", - "imitate", - "immense", - "immune", - "impact", - "impose", - "improve", - "impulse", - "inch", - "include", - "income", - "increase", - "index", - "indicate", - "indoor", - "industry", - "infant", - "inflict", - "inform", - "inhale", - "inherit", - "initial", - "inject", - "injury", - "inmate", - "inner", - "innocent", - "input", - "inquiry", - "insane", - "insect", - "inside", - "inspire", - "install", - "intact", - "interest", - "into", - "invest", - "invite", - "involve", - "iron", - "island", - "isolate", - "issue", - "item", - "ivory", - "jacket", - "jaguar", - "jar", - "jazz", - "jealous", - "jeans", - "jelly", - "jewel", - "job", - "join", - "joke", - "journey", - "joy", - "judge", - "juice", - "jump", - "jungle", - "junior", - "junk", - "just", - "kangaroo", - "keen", - "keep", - "ketchup", - "key", - "kick", - "kid", - "kidney", - "kind", - "kingdom", - "kiss", - "kit", - "kitchen", - "kite", - "kitten", - "kiwi", - "knee", - "knife", - "knock", - "know", - "lab", - "label", - "labor", - "ladder", - "lady", - "lake", - "lamp", - "language", - "laptop", - "large", - "later", - "latin", - "laugh", - "laundry", - "lava", - "law", - "lawn", - "lawsuit", - "layer", - "lazy", - "leader", - "leaf", - "learn", - "leave", - "lecture", - "left", - "leg", - "legal", - "legend", - "leisure", - "lemon", - "lend", - "length", - "lens", - "leopard", - "lesson", - "letter", - "level", - "liar", - "liberty", - "library", - "license", - "life", - "lift", - "light", - "like", - "limb", - "limit", - "link", - "lion", - "liquid", - "list", - "little", - "live", - "lizard", - "load", - "loan", - "lobster", - "local", - "lock", - "logic", - "lonely", - "long", - "loop", - "lottery", - "loud", - "lounge", - "love", - "loyal", - "lucky", - "luggage", - "lumber", - "lunar", - "lunch", - "luxury", - "lyrics", - "machine", - "mad", - "magic", - "magnet", - "maid", - "mail", - "main", - "major", - "make", - "mammal", - "man", - "manage", - "mandate", - "mango", - "mansion", - "manual", - "maple", - "marble", - "march", - "margin", - "marine", - "market", - "marriage", - "mask", - "mass", - "master", - "match", - "material", - "math", - "matrix", - "matter", - "maximum", - "maze", - "meadow", - "mean", - "measure", - "meat", - "mechanic", - "medal", - "media", - "melody", - "melt", - "member", - "memory", - "mention", - "menu", - "mercy", - "merge", - "merit", - "merry", - "mesh", - "message", - "metal", - "method", - "middle", - "midnight", - "milk", - "million", - "mimic", - "mind", - "minimum", - "minor", - "minute", - "miracle", - "mirror", - "misery", - "miss", - "mistake", - "mix", - "mixed", - "mixture", - "mobile", - "model", - "modify", - "mom", - "moment", - "monitor", - "monkey", - "monster", - "month", - "moon", - "moral", - "more", - "morning", - "mosquito", - "mother", - "motion", - "motor", - "mountain", - "mouse", - "move", - "movie", - "much", - "muffin", - "mule", - "multiply", - "muscle", - "museum", - "mushroom", - "music", - "must", - "mutual", - "myself", - "mystery", - "myth", - "naive", - "name", - "napkin", - "narrow", - "nasty", - "nation", - "nature", - "near", - "neck", - "need", - "negative", - "neglect", - "neither", - "nephew", - "nerve", - "nest", - "net", - "network", - "neutral", - "never", - "news", - "next", - "nice", - "night", - "noble", - "noise", - "nominee", - "noodle", - "normal", - "north", - "nose", - "notable", - "note", - "nothing", - "notice", - "novel", - "now", - "nuclear", - "number", - "nurse", - "nut", - "oak", - "obey", - "object", - "oblige", - "obscure", - "observe", - "obtain", - "obvious", - "occur", - "ocean", - "october", - "odor", - "off", - "offer", - "office", - "often", - "oil", - "okay", - "old", - "olive", - "olympic", - "omit", - "once", - "one", - "onion", - "online", - "only", - "open", - "opera", - "opinion", - "oppose", - "option", - "orange", - "orbit", - "orchard", - "order", - "ordinary", - "organ", - "orient", - "original", - "orphan", - "ostrich", - "other", - "outdoor", - "outer", - "output", - "outside", - "oval", - "oven", - "over", - "own", - "owner", - "oxygen", - "oyster", - "ozone", - "pact", - "paddle", - "page", - "pair", - "palace", - "palm", - "panda", - "panel", - "panic", - "panther", - "paper", - "parade", - "parent", - "park", - "parrot", - "party", - "pass", - "patch", - "path", - "patient", - "patrol", - "pattern", - "pause", - "pave", - "payment", - "peace", - "peanut", - "pear", - "peasant", - "pelican", - "pen", - "penalty", - "pencil", - "people", - "pepper", - "perfect", - "permit", - "person", - "pet", - "phone", - "photo", - "phrase", - "physical", - "piano", - "picnic", - "picture", - "piece", - "pig", - "pigeon", - "pill", - "pilot", - "pink", - "pioneer", - "pipe", - "pistol", - "pitch", - "pizza", - "place", - "planet", - "plastic", - "plate", - "play", - "please", - "pledge", - "pluck", - "plug", - "plunge", - "poem", - "poet", - "point", - "polar", - "pole", - "police", - "pond", - "pony", - "pool", - "popular", - "portion", - "position", - "possible", - "post", - "potato", - "pottery", - "poverty", - "powder", - "power", - "practice", - "praise", - "predict", - "prefer", - "prepare", - "present", - "pretty", - "prevent", - "price", - "pride", - "primary", - "print", - "priority", - "prison", - "private", - "prize", - "problem", - "process", - "produce", - "profit", - "program", - "project", - "promote", - "proof", - "property", - "prosper", - "protect", - "proud", - "provide", - "public", - "pudding", - "pull", - "pulp", - "pulse", - "pumpkin", - "punch", - "pupil", - "puppy", - "purchase", - "purity", - "purpose", - "purse", - "push", - "put", - "puzzle", - "pyramid", - "quality", - "quantum", - "quarter", - "question", - "quick", - "quit", - "quiz", - "quote", - "rabbit", - "raccoon", - "race", - "rack", - "radar", - "radio", - "rail", - "rain", - "raise", - "rally", - "ramp", - "ranch", - "random", - "range", - "rapid", - "rare", - "rate", - "rather", - "raven", - "raw", - "razor", - "ready", - "real", - "reason", - "rebel", - "rebuild", - "recall", - "receive", - "recipe", - "record", - "recycle", - "reduce", - "reflect", - "reform", - "refuse", - "region", - "regret", - "regular", - "reject", - "relax", - "release", - "relief", - "rely", - "remain", - "remember", - "remind", - "remove", - "render", - "renew", - "rent", - "reopen", - "repair", - "repeat", - "replace", - "report", - "require", - "rescue", - "resemble", - "resist", - "resource", - "response", - "result", - "retire", - "retreat", - "return", - "reunion", - "reveal", - "review", - "reward", - "rhythm", - "rib", - "ribbon", - "rice", - "rich", - "ride", - "ridge", - "rifle", - "right", - "rigid", - "ring", - "riot", - "ripple", - "risk", - "ritual", - "rival", - "river", - "road", - "roast", - "robot", - "robust", - "rocket", - "romance", - "roof", - "rookie", - "room", - "rose", - "rotate", - "rough", - "round", - "route", - "royal", - "rubber", - "rude", - "rug", - "rule", - "run", - "runway", - "rural", - "sad", - "saddle", - "sadness", - "safe", - "sail", - "salad", - "salmon", - "salon", - "salt", - "salute", - "same", - "sample", - "sand", - "satisfy", - "satoshi", - "sauce", - "sausage", - "save", - "say", - "scale", - "scan", - "scare", - "scatter", - "scene", - "scheme", - "school", - "science", - "scissors", - "scorpion", - "scout", - "scrap", - "screen", - "script", - "scrub", - "sea", - "search", - "season", - "seat", - "second", - "secret", - "section", - "security", - "seed", - "seek", - "segment", - "select", - "sell", - "seminar", - "senior", - "sense", - "sentence", - "series", - "service", - "session", - "settle", - "setup", - "seven", - "shadow", - "shaft", - "shallow", - "share", - "shed", - "shell", - "sheriff", - "shield", - "shift", - "shine", - "ship", - "shiver", - "shock", - "shoe", - "shoot", - "shop", - "short", - "shoulder", - "shove", - "shrimp", - "shrug", - "shuffle", - "shy", - "sibling", - "sick", - "side", - "siege", - "sight", - "sign", - "silent", - "silk", - "silly", - "silver", - "similar", - "simple", - "since", - "sing", - "siren", - "sister", - "situate", - "six", - "size", - "skate", - "sketch", - "ski", - "skill", - "skin", - "skirt", - "skull", - "slab", - "slam", - "sleep", - "slender", - "slice", - "slide", - "slight", - "slim", - "slogan", - "slot", - "slow", - "slush", - "small", - "smart", - "smile", - "smoke", - "smooth", - "snack", - "snake", - "snap", - "sniff", - "snow", - "soap", - "soccer", - "social", - "sock", - "soda", - "soft", - "solar", - "soldier", - "solid", - "solution", - "solve", - "someone", - "song", - "soon", - "sorry", - "sort", - "soul", - "sound", - "soup", - "source", - "south", - "space", - "spare", - "spatial", - "spawn", - "speak", - "special", - "speed", - "spell", - "spend", - "sphere", - "spice", - "spider", - "spike", - "spin", - "spirit", - "split", - "spoil", - "sponsor", - "spoon", - "sport", - "spot", - "spray", - "spread", - "spring", - "spy", - "square", - "squeeze", - "squirrel", - "stable", - "stadium", - "staff", - "stage", - "stairs", - "stamp", - "stand", - "start", - "state", - "stay", - "steak", - "steel", - "stem", - "step", - "stereo", - "stick", - "still", - "sting", - "stock", - "stomach", - "stone", - "stool", - "story", - "stove", - "strategy", - "street", - "strike", - "strong", - "struggle", - "student", - "stuff", - "stumble", - "style", - "subject", - "submit", - "subway", - "success", - "such", - "sudden", - "suffer", - "sugar", - "suggest", - "suit", - "summer", - "sun", - "sunny", - "sunset", - "super", - "supply", - "supreme", - "sure", - "surface", - "surge", - "surprise", - "surround", - "survey", - "suspect", - "sustain", - "swallow", - "swamp", - "swap", - "swarm", - "swear", - "sweet", - "swift", - "swim", - "swing", - "switch", - "sword", - "symbol", - "symptom", - "syrup", - "system", - "table", - "tackle", - "tag", - "tail", - "talent", - "talk", - "tank", - "tape", - "target", - "task", - "taste", - "tattoo", - "taxi", - "teach", - "team", - "tell", - "ten", - "tenant", - "tennis", - "tent", - "term", - "test", - "text", - "thank", - "that", - "theme", - "then", - "theory", - "there", - "they", - "thing", - "this", - "thought", - "three", - "thrive", - "throw", - "thumb", - "thunder", - "ticket", - "tide", - "tiger", - "tilt", - "timber", - "time", - "tiny", - "tip", - "tired", - "tissue", - "title", - "toast", - "tobacco", - "today", - "toddler", - "toe", - "together", - "toilet", - "token", - "tomato", - "tomorrow", - "tone", - "tongue", - "tonight", - "tool", - "tooth", - "top", - "topic", - "topple", - "torch", - "tornado", - "tortoise", - "toss", - "total", - "tourist", - "toward", - "tower", - "town", - "toy", - "track", - "trade", - "traffic", - "tragic", - "train", - "transfer", - "trap", - "trash", - "travel", - "tray", - "treat", - "tree", - "trend", - "trial", - "tribe", - "trick", - "trigger", - "trim", - "trip", - "trophy", - "trouble", - "truck", - "true", - "truly", - "trumpet", - "trust", - "truth", - "try", - "tube", - "tuition", - "tumble", - "tuna", - "tunnel", - "turkey", - "turn", - "turtle", - "twelve", - "twenty", - "twice", - "twin", - "twist", - "two", - "type", - "typical", - "ugly", - "umbrella", - "unable", - "unaware", - "uncle", - "uncover", - "under", - "undo", - "unfair", - "unfold", - "unhappy", - "uniform", - "unique", - "unit", - "universe", - "unknown", - "unlock", - "until", - "unusual", - "unveil", - "update", - "upgrade", - "uphold", - "upon", - "upper", - "upset", - "urban", - "urge", - "usage", - "use", - "used", - "useful", - "useless", - "usual", - "utility", - "vacant", - "vacuum", - "vague", - "valid", - "valley", - "valve", - "van", - "vanish", - "vapor", - "various", - "vast", - "vault", - "vehicle", - "velvet", - "vendor", - "venture", - "venue", - "verb", - "verify", - "version", - "very", - "vessel", - "veteran", - "viable", - "vibrant", - "vicious", - "victory", - "video", - "view", - "village", - "vintage", - "violin", - "virtual", - "virus", - "visa", - "visit", - "visual", - "vital", - "vivid", - "vocal", - "voice", - "void", - "volcano", - "volume", - "vote", - "voyage", - "wage", - "wagon", - "wait", - "walk", - "wall", - "walnut", - "want", - "warfare", - "warm", - "warrior", - "wash", - "wasp", - "waste", - "water", - "wave", - "way", - "wealth", - "weapon", - "wear", - "weasel", - "weather", - "web", - "wedding", - "weekend", - "weird", - "welcome", - "west", - "wet", - "whale", - "what", - "wheat", - "wheel", - "when", - "where", - "whip", - "whisper", - "wide", - "width", - "wife", - "wild", - "will", - "win", - "window", - "wine", - "wing", - "wink", - "winner", - "winter", - "wire", - "wisdom", - "wise", - "wish", - "witness", - "wolf", - "woman", - "wonder", - "wood", - "wool", - "word", - "work", - "world", - "worry", - "worth", - "wrap", - "wreck", - "wrestle", - "wrist", - "write", - "wrong", - "yard", - "year", - "yellow", - "you", - "young", - "youth", - "zebra", - "zero", - "zone", - "zoo", -]; diff --git a/rust/tw_hd_wallet/src/bip39/mod.rs b/rust/tw_hd_wallet/src/bip39/mod.rs deleted file mode 100644 index d0e1db66b2e..00000000000 --- a/rust/tw_hd_wallet/src/bip39/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -pub mod bip39_english; - -pub fn normalize_mnemonic(mnemonic: &str) -> String { - mnemonic.trim().to_string() -} diff --git a/rust/tw_hd_wallet/src/lib.rs b/rust/tw_hd_wallet/src/lib.rs deleted file mode 100644 index cfa3a09fa16..00000000000 --- a/rust/tw_hd_wallet/src/lib.rs +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -extern crate core; - -pub mod bip39; -pub mod ton; - -pub type WalletResult = Result; - -#[derive(Debug, Eq, PartialEq)] -pub enum WalletError { - InvalidMnemonicWordCount, - InvalidMnemonicUnknownWord, - InvalidMnemonicEntropy, - InvalidChecksum, -} diff --git a/rust/tw_hd_wallet/src/ton/mnemonic.rs b/rust/tw_hd_wallet/src/ton/mnemonic.rs deleted file mode 100644 index 27013cdc283..00000000000 --- a/rust/tw_hd_wallet/src/ton/mnemonic.rs +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -use crate::bip39::bip39_english::BIP39_WORDS_MAP; -use crate::{WalletError, WalletResult}; - -pub const WORDS_LEN: usize = 24; - -/// Validates the given `mnemonic` string if it consists of 24 known words (see BIP39 words list). -/// -/// Please note there this function doesn't validate the mnemonic but it words only. -/// See [`TonWallet::new`]. -pub fn validate_mnemonic_words(mnemonic: &str) -> WalletResult<()> { - let mut invalid = false; - let mut words_count = 0; - for word in mnemonic.split(" ") { - words_count += 1; - - // Although this operation is not security-critical, we aim for constant-time operation here as well - // (i.e., no early exit on match) - // - // We expect words in lowercase only. - // It's a responsibility of the WalletCore user to transform the mnemonic if needed. - if !BIP39_WORDS_MAP.contains_key(word) { - invalid = true; - } - } - - if invalid { - return Err(WalletError::InvalidMnemonicUnknownWord); - } - if words_count != WORDS_LEN { - return Err(WalletError::InvalidMnemonicWordCount); - } - - Ok(()) -} diff --git a/rust/tw_hd_wallet/src/ton/mod.rs b/rust/tw_hd_wallet/src/ton/mod.rs deleted file mode 100644 index 2971881654a..00000000000 --- a/rust/tw_hd_wallet/src/ton/mod.rs +++ /dev/null @@ -1,169 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -use crate::bip39::normalize_mnemonic; -use crate::ton::mnemonic::validate_mnemonic_words; -use crate::{WalletError, WalletResult}; -use tw_hash::hmac::hmac_sha512; -use tw_hash::pbkdf2::pbkdf2_hmac_sha512; -use tw_hash::{H256, H512}; -use tw_keypair::ed25519::sha512::KeyPair; -use zeroize::ZeroizeOnDrop; - -pub const TON_WALLET_SALT: &[u8] = b"TON default seed"; -pub const TON_WALLET_PBKDF_ITERATIONS: u32 = 100_000; - -/// Used to verify a pair of mnemonic and passphrase only. -const TON_BASIC_SEED_SALT: &[u8] = b"TON seed version"; -/// Equals to `(TON_WALLET_PBKDF_ITERATIONS as f64 / 256.0).floor() as u32`. -const TON_BASIC_SEED_ROUNDS: u32 = 390; - -/// Used to verify a pair of mnemonic and passphrase only. -const TON_PASSPHRASE_SEED_SALT: &[u8] = b"TON fast seed version"; -const TON_PASSPHRASE_SEED_ROUNDS: u32 = 1; - -mod mnemonic; - -#[derive(Debug, ZeroizeOnDrop)] -pub struct TonWallet { - mnemonic: String, - passphrase: Option, - entropy: H512, - seed: H512, -} - -impl TonWallet { - pub const MNEMONIC_WORDS: usize = 24; - - /// Creates `TonWallet` while validating if there should or shouldn't be a passphrase. - /// https://github.com/toncenter/tonweb-mnemonic/blob/a338a00d4ca0ed833431e0e49e4cfad766ac713c/src/functions/validate-mnemonic.ts#L20-L28 - pub fn new(mnemonic: &str, passphrase: Option) -> WalletResult { - let mnemonic = normalize_mnemonic(mnemonic); - validate_mnemonic_words(&mnemonic)?; - - match passphrase { - Some(ref passphrase) if !passphrase.is_empty() => { - // Check whether the passphrase is really needed. - if !Self::is_password_needed(&mnemonic) { - return Err(WalletError::InvalidMnemonicEntropy); - } - }, - _ => (), - }; - - let entropy = Self::ton_mnemonic_to_entropy(&mnemonic, passphrase.as_deref()); - - // The pair of `[mnemonic, Option]` should give a `basic` seed. - // Otherwise, `passphrase` is either not set or invalid. - if !is_basic_seed(&entropy) { - return Err(WalletError::InvalidMnemonicEntropy); - } - - let seed = ton_seed(&entropy); - Ok(TonWallet { - mnemonic, - passphrase, - entropy, - seed, - }) - } - - pub fn to_key_pair(&self) -> KeyPair { - let (secret, _): (H256, H256) = self.seed.split(); - KeyPair::from(secret) - } - - /// Whether the mnemonic can be used with a passphrase only. - /// https://github.com/toncenter/tonweb-mnemonic/blob/a338a00d4ca0ed833431e0e49e4cfad766ac713c/src/functions/is-password-needed.ts#L5-L11 - fn is_password_needed(mnemonic: &str) -> bool { - // Password mnemonic (without password) should be password seed, but not basic seed. - let entropy = Self::ton_mnemonic_to_entropy(mnemonic, None); - is_password_seed(&entropy) && !is_basic_seed(&entropy) - } - - /// https://github.com/toncenter/tonweb-mnemonic/blob/a338a00d4ca0ed833431e0e49e4cfad766ac713c/src/functions/common.ts#L20-L23 - fn ton_mnemonic_to_entropy(mnemonic: &str, passphrase: Option<&str>) -> H512 { - let passphrase_bytes = passphrase.map(str::as_bytes).unwrap_or(&[]); - let entropy = hmac_sha512(mnemonic.as_bytes(), passphrase_bytes); - H512::try_from(entropy.as_slice()).expect("hmac_sha512 must return 64 bytes") - } -} - -/// https://github.com/toncenter/tonweb-mnemonic/blob/a338a00d4ca0ed833431e0e49e4cfad766ac713c/src/functions/common.ts#L8-L11 -fn is_basic_seed(entropy: &H512) -> bool { - basic_seed(entropy)[0] == 0 -} - -/// https://github.com/toncenter/tonweb-mnemonic/blob/a338a00d4ca0ed833431e0e49e4cfad766ac713c/src/functions/mnemonic-to-seed.ts#L5-L17 -fn ton_seed(entropy: &H512) -> H512 { - pbkdf2_hmac_sha512( - entropy.as_slice(), - TON_WALLET_SALT, - TON_WALLET_PBKDF_ITERATIONS, - ) -} - -/// https://github.com/toncenter/tonweb-mnemonic/blob/a338a00d4ca0ed833431e0e49e4cfad766ac713c/src/functions/common.ts#L9 -fn basic_seed(entropy: &H512) -> H512 { - pbkdf2_hmac_sha512( - entropy.as_slice(), - TON_BASIC_SEED_SALT, - TON_BASIC_SEED_ROUNDS, - ) -} - -/// https://github.com/toncenter/tonweb-mnemonic/blob/a338a00d4ca0ed833431e0e49e4cfad766ac713c/src/functions/common.ts#L15 -fn password_seed(entropy: &H512) -> H512 { - pbkdf2_hmac_sha512( - entropy.as_slice(), - TON_PASSPHRASE_SEED_SALT, - TON_PASSPHRASE_SEED_ROUNDS, - ) -} - -/// https://github.com/toncenter/tonweb-mnemonic/blob/a338a00d4ca0ed833431e0e49e4cfad766ac713c/src/functions/common.ts#L14-L17 -fn is_password_seed(entropy: &H512) -> bool { - password_seed(entropy)[0] == 1 -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_is_basic_seed() { - let entropy = H512::from("db315e4b9a05d29d4921f3f99c754b52c1035bd20a6c38ae4c39068a844d2d4dccd1b16ab639494155bd635737c9120eab0b0382c6f27d941993ad2a98ee037b"); - let actual = basic_seed(&entropy); - let expected = H512::from("008303d550147ad20420875810a81496510e32e2ec7b1c4129c8fe55c0886ad2b6c8b6ad88427d3614a27997ec3760e4a6aaae45c8a1c70684aad5206e8559ce"); - assert_eq!(actual, expected); - assert!(is_basic_seed(&entropy)); - } - - #[test] - fn test_is_not_basic_seed() { - let entropy = H512::from("5ad5da67282f932eb9e0b66246af357e1e99f73b066ba1095fedf324629f67048c82e7c6d4cf09f204e2b2fcd002ab9bae25da67f99ecb918861d11ec6553a78"); - let actual = basic_seed(&entropy); - let expected = H512::from("fd1ab5001f7fec237ad7b90dbd3a8a6d716409688d23517cc80314b79f36f93a21aac798c39778684688b4763bf0294874c067ef3d28d854101c4e5616839dfd"); - assert_eq!(actual, expected); - assert!(!is_basic_seed(&entropy)); - } - - #[test] - fn test_is_password_seed() { - let entropy = H512::from("f0fc0e9610e3a215db47df1fc8dc2142ec4e8559ee1ec384fc3e51234d63c34087f4a6bdb2ad2c5a46a4504e30c9ab4ef7d92dc1836c2854ecef1f7988e60100"); - let actual = password_seed(&entropy); - let expected = H512::from("014944aac60ad889acab074b850df15b745b2c6ca5367c3ba02f4ae22e5a8953ac33413c0cd7fdb9108935bd6ed82acc73d4ac94202d83933a5480642c371eed"); - assert_eq!(actual, expected); - assert!(is_password_seed(&entropy)); - } - - #[test] - fn test_is_not_password_seed() { - let entropy = H512::from("85092586b7d675688d52b5870966546c7fb0144a2d94badd7c3960d6e9de3094016cea3aa155f5a9b3ce61d5b4ad8393984ed153dc0866304c911ba2edd2ea9e"); - let actual = password_seed(&entropy); - let expected = H512::from("31f4dd02b333be5635dc46245d67792b85e9b74d788a749caf3d45bfb1cd094a039626ba1a8bff77fa2436a6a228568d29884c2b274e09c0fe722f07980aab8e"); - assert_eq!(actual, expected); - assert!(!is_password_seed(&entropy)); - } -} diff --git a/rust/tw_hd_wallet/tests/ton_mnemonic.rs b/rust/tw_hd_wallet/tests/ton_mnemonic.rs deleted file mode 100644 index 3125140ad90..00000000000 --- a/rust/tw_hd_wallet/tests/ton_mnemonic.rs +++ /dev/null @@ -1,156 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -use tw_encoding::hex::ToHex; -use tw_hd_wallet::ton::TonWallet; -use tw_hd_wallet::WalletError; -use tw_keypair::traits::KeyPairTrait; -use tw_misc::traits::ToBytesZeroizing; - -struct MnemonicTest { - mnemonic: &'static str, - passphrase: &'static str, - expected_private: &'static str, - expected_public: &'static str, -} - -fn mnemonic_to_keypair_impl(input: MnemonicTest) { - let passphrase = if input.passphrase.is_empty() { - None - } else { - Some(input.passphrase.to_string()) - }; - - let wallet = TonWallet::new(input.mnemonic, passphrase).unwrap(); - let key_pair = wallet.to_key_pair(); - - assert_eq!( - key_pair.private().to_zeroizing_vec().to_hex(), - input.expected_private, - "Invalid private key" - ); - assert_eq!( - key_pair.public().to_bytes().to_hex(), - input.expected_public, - "Invalid public key" - ); -} - -struct MnemonicErrorTest { - mnemonic: &'static str, - passphrase: &'static str, -} - -fn mnemonic_to_keypair_error(input: MnemonicErrorTest) { - let passphrase = if input.passphrase.is_empty() { - None - } else { - Some(input.passphrase.to_string()) - }; - - assert!( - TonWallet::new(input.mnemonic, passphrase).is_err(), - "Expected an error" - ); -} - -/// All tests generated by using `https://github.com/toncenter/tonweb-mnemonic/`. -#[test] -fn test_mnemonic_to_keypair_no_passphrase() { - mnemonic_to_keypair_impl(MnemonicTest { - mnemonic: "document shield addict crime broom point story depend suit satisfy test chicken valid tail speak fortune sound drill seek cube cheap body music recipe", - passphrase: "", - expected_private: "112d4e2e700a468f1eae699329202f1ee671d6b665caa2d92dea038cf3868c18", - expected_public: "4d656c35c830bf78d239d3225727dd1da051be0ec521c98e3012beafbb06f306", - }); - mnemonic_to_keypair_impl(MnemonicTest { - mnemonic: "slogan train glide measure mercy dizzy when satoshi vote change length pluck token walnut actress hollow guard soup solve rival summer vicious anxiety device", - passphrase: "", - expected_private: "ee11da8e64d17a8416c88a6a24a1e16569cc85a077b7b209528975c32a44a0c8", - expected_public: "3bab20a5f77e277e39443fc16c64e0479b4a9db542bf9e11c638598384c853f1", - }); - mnemonic_to_keypair_impl(MnemonicTest { - mnemonic: "laundry myself fitness beyond prize piano match acid vacuum already abandon dance occur pause grocery company inject excuse weasel carpet fog grunt trick spike", - passphrase: "", - expected_private: "859cd74ab605afb7ce9f5316a1f6d59217a130b75b494efd249913be874c9d46", - expected_public: "c9af50596bd5c1c5a15fb32bef8d4f1ee5244b287aea1f49f6023a79f9b2f055", - }); - mnemonic_to_keypair_impl(MnemonicTest { - mnemonic: "slim holiday tiny pizza donor egg round three verify post chat social offer mix rack soft loud code option learn this pipe mouse mango", - passphrase: "", - expected_private: "cdfd1e2a1f947701bddba2636c26a6d6d13efacba5e6fdc624254be9bf8cbc3b", - expected_public: "c1266dc4e8040e462af34fa7da9130950caf8c8a1bab78c9b938d2eb6e3d9a69", - }); -} - -/// All tests generated by using `https://github.com/toncenter/tonweb-mnemonic/`. -#[test] -fn test_mnemonic_to_keypair_with_passphrase() { - mnemonic_to_keypair_impl(MnemonicTest { - mnemonic: "afford skate husband stamp style affair jeans episode afraid mom pupil canal borrow artwork fetch excite shiver conduct acoustic rail crisp consider pave people", - passphrase: ".", - expected_private: "ddaed03c283c9e60883b6c7cda86af40a1a820a8181276900094db0d23b55144", - expected_public: "46aacb0e9e1faba24e0e87d4bf7ee5e54beaa4142fa1bd608324d7c67d78070e", - }); - mnemonic_to_keypair_impl(MnemonicTest { - mnemonic: "mimic close sibling chair shuffle goat fashion chunk increase tennis scene ceiling divert cross treat happy soccer sample umbrella oyster advance quality perfect call", - passphrase: "My passphrase", - expected_private: "78a6d95981847d6b7fb6b85be43071fbd83f4b0cebc1fd0c75147fde3c88d9e2", - expected_public: "452a6031290df95e972a269f3c042f5b18497ab27b8fe9915e5b5c94037382a6", - }); - mnemonic_to_keypair_impl(MnemonicTest { - mnemonic: "kind loan rifle gadget forward tortoise switch tuition orchard ball monkey glow gallery diary nature dynamic survey flush correct employ autumn wife disease coin", - passphrase: "189r012 9jr90fj--901hr8921'0r912j90", - expected_private: "ced0feac4f8cc46909a5a172d390f126afe46540c07c5163c194429269e6eb08", - expected_public: "482c9619307639a4b1699e83d771d656e5cf7be3ef877d849940cfebd718783e", - }); - mnemonic_to_keypair_impl(MnemonicTest { - mnemonic: " predict pelican worry swallow brother real truck fiber trophy melody joy kitten luggage lake woman clutch frost crop about stumble frozen kitchen mutual food ", - passphrase: "Foo Bar Zar", - expected_private: "f93ed43c379cb8210754016ffa669880b259854160446c1a12c5858258c32601", - expected_public: "f44d7f29bc8801f882097611544fcafe84cca05408006fa5b76b63f55464e4d0", - }); -} - -#[test] -fn test_mnemonic_to_keypair_error_expected_passphrase() { - // This mnemonic can only be used along with the "My passphrase" passphrase. - let mnemonic = "mimic close sibling chair shuffle goat fashion chunk increase tennis scene ceiling divert cross treat happy soccer sample umbrella oyster advance quality perfect call"; - mnemonic_to_keypair_error(MnemonicErrorTest { - mnemonic, - passphrase: "", - }); - mnemonic_to_keypair_error(MnemonicErrorTest { - mnemonic, - passphrase: "Unexpected passphrase", - }); -} - -#[test] -fn test_mnemonic_to_keypair_error_expected_no_passphrase() { - // This mnemonic can only be used without passphrase. - let mnemonic = "slogan train glide measure mercy dizzy when satoshi vote change length pluck token walnut actress hollow guard soup solve rival summer vicious anxiety device"; - mnemonic_to_keypair_error(MnemonicErrorTest { - mnemonic, - passphrase: "Hello world", - }); - mnemonic_to_keypair_error(MnemonicErrorTest { - mnemonic, - passphrase: "...", - }); -} - -#[test] -fn test_invalid_mnemonic() { - // 24 words mnemonic is supported only. - let error = TonWallet::new("cost dash dress stove morning robust group affair stomach vacant route volume yellow salute laugh", None).unwrap_err(); - assert_eq!(error, WalletError::InvalidMnemonicWordCount); - - let error = TonWallet::new("foo bar oooo edit wash faint patient cancel roof edit silly battle half engine reunion hotel joy fan unhappy oil alone sense empty mesh", None).unwrap_err(); - assert_eq!(error, WalletError::InvalidMnemonicUnknownWord); - - // Upper-case mnemonic is not allowed. - let error = TonWallet::new("TAIL SWING SUGGEST EDIT WASH FAINT PATIENT CANCEL ROOF EDIT SILLY BATTLE HALF ENGINE REUNION HOTEL JOY FAN UNHAPPY OIL ALONE SENSE EMPTY MESH", None).unwrap_err(); - assert_eq!(error, WalletError::InvalidMnemonicUnknownWord); -} diff --git a/rust/tw_keypair/src/ed25519/keypair.rs b/rust/tw_keypair/src/ed25519/keypair.rs index eed4a8ca6d0..3ac2ac1e1fa 100644 --- a/rust/tw_keypair/src/ed25519/keypair.rs +++ b/rust/tw_keypair/src/ed25519/keypair.rs @@ -6,7 +6,6 @@ use crate::ed25519::{private::PrivateKey, public::PublicKey, signature::Signatur use crate::traits::{KeyPairTrait, SigningKeyTrait, VerifyingKeyTrait}; use crate::{KeyPairError, KeyPairResult}; use tw_encoding::hex; -use tw_hash::H256; use zeroize::Zeroizing; /// Represents a pair of `ed25519` private and public keys. @@ -47,14 +46,6 @@ impl VerifyingKeyTrait for KeyPair { } } -impl From for KeyPair { - fn from(secret: H256) -> Self { - let private = PrivateKey::from(secret); - let public = private.public(); - KeyPair { private, public } - } -} - impl<'a, H: Hasher512> TryFrom<&'a [u8]> for KeyPair { type Error = KeyPairError; diff --git a/rust/tw_keypair/src/ed25519/private.rs b/rust/tw_keypair/src/ed25519/private.rs index dc5ab54a68e..984392393a6 100644 --- a/rust/tw_keypair/src/ed25519/private.rs +++ b/rust/tw_keypair/src/ed25519/private.rs @@ -11,7 +11,6 @@ use crate::{KeyPairError, KeyPairResult}; use std::fmt; use tw_encoding::hex; use tw_hash::H256; -use tw_memory::Data; use tw_misc::traits::ToBytesZeroizing; use zeroize::{ZeroizeOnDrop, Zeroizing}; @@ -38,12 +37,6 @@ impl PrivateKey { PublicKey::with_expanded_secret(&self.expanded_key) } - /// Returns a reference to the private key bytes. - /// Please note if clone bytes, it must be zeroized with [`zeroize::Zeroize::zeroize`]. - pub fn bytes(&self) -> &H256 { - &self.secret - } - /// `ed25519` signing uses a public key associated with the private key. pub(crate) fn sign_with_public_key( &self, @@ -56,7 +49,7 @@ impl PrivateKey { } impl SigningKeyTrait for PrivateKey { - type SigningMessage = Data; + type SigningMessage = Vec; type Signature = Signature; fn sign(&self, message: Self::SigningMessage) -> KeyPairResult { @@ -64,22 +57,16 @@ impl SigningKeyTrait for PrivateKey { } } -impl From for PrivateKey { - fn from(secret: H256) -> Self { - let expanded_key = ExpandedSecretKey::::with_secret(secret); - PrivateKey { - secret, - expanded_key, - } - } -} - impl TryFrom<&[u8]> for PrivateKey { type Error = KeyPairError; fn try_from(data: &[u8]) -> Result { let secret = H256::try_from(data).map_err(|_| KeyPairError::InvalidSecretKey)?; - Ok(PrivateKey::from(secret)) + let expanded_key = ExpandedSecretKey::::with_secret(secret); + Ok(PrivateKey { + secret, + expanded_key, + }) } } @@ -93,7 +80,7 @@ impl<'a, H: Hasher512> TryFrom<&'a str> for PrivateKey { } impl ToBytesZeroizing for PrivateKey { - fn to_zeroizing_vec(&self) -> Zeroizing { + fn to_zeroizing_vec(&self) -> Zeroizing> { Zeroizing::new(self.secret.to_vec()) } } diff --git a/rust/tw_keypair/src/ffi/privkey.rs b/rust/tw_keypair/src/ffi/privkey.rs index c1a1b978569..9d4efa7f8f4 100644 --- a/rust/tw_keypair/src/ffi/privkey.rs +++ b/rust/tw_keypair/src/ffi/privkey.rs @@ -8,11 +8,10 @@ use crate::ffi::pubkey::TWPublicKey; use crate::tw::{Curve, PrivateKey, PublicKeyType}; use tw_memory::ffi::c_byte_array::CByteArray; use tw_memory::ffi::c_byte_array_ref::CByteArrayRef; -use tw_memory::ffi::tw_data::TWData; use tw_memory::ffi::RawPtrTrait; use tw_misc::{try_or_else, try_or_false}; -pub struct TWPrivateKey(pub PrivateKey); +pub struct TWPrivateKey(pub(crate) PrivateKey); impl RawPtrTrait for TWPrivateKey {} @@ -68,17 +67,6 @@ pub unsafe extern "C" fn tw_private_key_is_valid( PrivateKey::is_valid(priv_key_slice, curve) } -/// Convert the given private key to raw-bytes block of data. -/// -/// \param key Non-null pointer to the private key -/// \note The returned block should be deleted with \tw_data_delete_zeroizing -/// \return Non-null block of data (raw bytes) of the given private key -#[no_mangle] -pub unsafe extern "C" fn tw_private_key_data(key: *const TWPrivateKey) -> *mut TWData { - let key = try_or_else!(TWPrivateKey::from_ptr_as_ref(key), std::ptr::null_mut); - TWData::from(key.0.bytes().to_vec()).into_ptr() -} - /// Signs a digest using ECDSA and given curve. /// /// \param key *non-null* pointer to a Private key @@ -88,7 +76,7 @@ pub unsafe extern "C" fn tw_private_key_data(key: *const TWPrivateKey) -> *mut T /// \return Signature as a C-compatible result with a C-compatible byte array. #[no_mangle] pub unsafe extern "C" fn tw_private_key_sign( - key: *const TWPrivateKey, + key: *mut TWPrivateKey, message: *const u8, message_len: usize, curve: u32, @@ -112,7 +100,7 @@ pub unsafe extern "C" fn tw_private_key_sign( /// \return *non-null* pointer to the corresponding public key. #[no_mangle] pub unsafe extern "C" fn tw_private_key_get_public_key_by_type( - key: *const TWPrivateKey, + key: *mut TWPrivateKey, pubkey_type: u32, ) -> *mut TWPublicKey { let ty = try_or_else!(PublicKeyType::from_raw(pubkey_type), std::ptr::null_mut); diff --git a/rust/tw_keypair/src/ffi/pubkey.rs b/rust/tw_keypair/src/ffi/pubkey.rs index 690f1c3e645..7f05f5f0fe9 100644 --- a/rust/tw_keypair/src/ffi/pubkey.rs +++ b/rust/tw_keypair/src/ffi/pubkey.rs @@ -10,7 +10,7 @@ use tw_memory::ffi::c_byte_array_ref::CByteArrayRef; use tw_memory::ffi::RawPtrTrait; use tw_misc::{try_or_else, try_or_false}; -pub struct TWPublicKey(pub PublicKey); +pub struct TWPublicKey(pub(crate) PublicKey); impl AsRef for TWPublicKey { fn as_ref(&self) -> &PublicKey { diff --git a/rust/tw_keypair/src/test_utils/tw_crypto_box_helpers.rs b/rust/tw_keypair/src/test_utils/tw_crypto_box_helpers.rs index 34c2a0f965a..88b4a146003 100644 --- a/rust/tw_keypair/src/test_utils/tw_crypto_box_helpers.rs +++ b/rust/tw_keypair/src/test_utils/tw_crypto_box_helpers.rs @@ -4,10 +4,10 @@ use crate::ffi::crypto_box::public_key::{tw_crypto_box_public_key_delete, TWCryptoBoxPublicKey}; use crate::ffi::crypto_box::secret_key::{tw_crypto_box_secret_key_delete, TWCryptoBoxSecretKey}; -use tw_memory::test_utils::tw_wrapper::{TWAutoWrapper, WithDestructor}; +use tw_memory::test_utils::tw_wrapper::{TWWrapper, WithDestructor}; -pub type TWCryptoBoxSecretKeyHelper = TWAutoWrapper; -pub type TWCryptoBoxPublicKeyHelper = TWAutoWrapper; +pub type TWCryptoBoxSecretKeyHelper = TWWrapper; +pub type TWCryptoBoxPublicKeyHelper = TWWrapper; impl WithDestructor for TWCryptoBoxSecretKey { fn destructor() -> unsafe extern "C" fn(*mut Self) { diff --git a/rust/tw_keypair/src/test_utils/tw_private_key_helper.rs b/rust/tw_keypair/src/test_utils/tw_private_key_helper.rs index d745fb2a1e3..6c3c3bfc629 100644 --- a/rust/tw_keypair/src/test_utils/tw_private_key_helper.rs +++ b/rust/tw_keypair/src/test_utils/tw_private_key_helper.rs @@ -2,12 +2,9 @@ // // Copyright © 2017 Trust Wallet. -use crate::ffi::privkey::{ - tw_private_key_create_with_data, tw_private_key_data, tw_private_key_delete, TWPrivateKey, -}; +use crate::ffi::privkey::{tw_private_key_create_with_data, tw_private_key_delete, TWPrivateKey}; use tw_encoding::hex; use tw_memory::ffi::c_byte_array::CByteArray; -use tw_memory::test_utils::tw_data_helper::TWDataHelper; use tw_memory::Data; pub struct TWPrivateKeyHelper { @@ -15,18 +12,6 @@ pub struct TWPrivateKeyHelper { } impl TWPrivateKeyHelper { - pub fn wrap(ptr: *mut TWPrivateKey) -> TWPrivateKeyHelper { - TWPrivateKeyHelper { ptr } - } - - pub fn bytes(&self) -> Option { - if self.ptr.is_null() { - return None; - } - let data = TWDataHelper::wrap(unsafe { tw_private_key_data(self.ptr) }); - data.to_vec() - } - pub fn with_bytes>(bytes: T) -> TWPrivateKeyHelper { let priv_key_raw = CByteArray::from(bytes.into()); let ptr = diff --git a/rust/tw_keypair/src/tw/private.rs b/rust/tw_keypair/src/tw/private.rs index b325b3ca834..0516f451798 100644 --- a/rust/tw_keypair/src/tw/private.rs +++ b/rust/tw_keypair/src/tw/private.rs @@ -55,11 +55,6 @@ impl PrivateKey { Ok(&self.bytes[Self::EXTENDED_CARDANO_RANGE]) } - /// Returns bytes (32 or 192 bytes). - pub fn bytes(&self) -> &[u8] { - &self.bytes - } - /// Checks if the given `bytes` secret is valid in general (without a concrete curve). pub fn is_valid_general(bytes: &[u8]) -> bool { if bytes.len() != Self::SIZE && bytes.len() != Self::CARDANO_SIZE { @@ -208,11 +203,3 @@ impl PrivateKey { schnorr::PrivateKey::try_from(self.key().as_slice()) } } - -impl From for PrivateKey { - fn from(key: ed25519::sha512::PrivateKey) -> Self { - PrivateKey { - bytes: key.bytes().to_vec(), - } - } -} diff --git a/rust/tw_keypair/tests/crypto_box_ffi_tests.rs b/rust/tw_keypair/tests/crypto_box_ffi_tests.rs index 4dbe6bb67e5..bf98b67b07e 100644 --- a/rust/tw_keypair/tests/crypto_box_ffi_tests.rs +++ b/rust/tw_keypair/tests/crypto_box_ffi_tests.rs @@ -17,12 +17,11 @@ use tw_keypair::test_utils::tw_crypto_box_helpers::{ TWCryptoBoxPublicKeyHelper, TWCryptoBoxSecretKeyHelper, }; use tw_memory::test_utils::tw_data_helper::TWDataHelper; -use tw_memory::test_utils::tw_wrapper::TWAutoWrapper; +use tw_memory::test_utils::tw_wrapper::TWWrapper; fn random_key_pair() -> (TWCryptoBoxSecretKeyHelper, TWCryptoBoxPublicKeyHelper) { - let secret = TWAutoWrapper::wrap(unsafe { tw_crypto_box_secret_key_create() }); - let pubkey = - TWAutoWrapper::wrap(unsafe { tw_crypto_box_secret_key_get_public_key(secret.ptr()) }); + let secret = TWWrapper::wrap(unsafe { tw_crypto_box_secret_key_create() }); + let pubkey = TWWrapper::wrap(unsafe { tw_crypto_box_secret_key_get_public_key(secret.ptr()) }); (secret, pubkey) } @@ -56,7 +55,7 @@ fn test_encrypt_decrypt_easy_error() { .decode_hex() .unwrap(), ); - let other_pubkey = TWAutoWrapper::wrap(unsafe { + let other_pubkey = TWWrapper::wrap(unsafe { tw_crypto_box_public_key_create_with_data(other_pubkey_data.ptr()) }); @@ -84,9 +83,8 @@ fn test_public_key() { let pubkey_data = TWDataHelper::create(pubkey_bytes.clone()); assert!(unsafe { tw_crypto_box_public_key_is_valid(pubkey_data.ptr()) }); - let pubkey = TWAutoWrapper::wrap(unsafe { - tw_crypto_box_public_key_create_with_data(pubkey_data.ptr()) - }); + let pubkey = + TWWrapper::wrap(unsafe { tw_crypto_box_public_key_create_with_data(pubkey_data.ptr()) }); let actual_data = TWDataHelper::wrap(unsafe { tw_crypto_box_public_key_data(pubkey.ptr()) }); assert_eq!(actual_data.to_vec().unwrap(), pubkey_bytes); } @@ -100,9 +98,8 @@ fn test_secret_key() { let secret_data = TWDataHelper::create(secret_bytes.clone()); assert!(unsafe { tw_crypto_box_secret_key_is_valid(secret_data.ptr()) }); - let pubkey = TWAutoWrapper::wrap(unsafe { - tw_crypto_box_secret_key_create_with_data(secret_data.ptr()) - }); + let pubkey = + TWWrapper::wrap(unsafe { tw_crypto_box_secret_key_create_with_data(secret_data.ptr()) }); let actual_data = TWDataHelper::wrap(unsafe { tw_crypto_box_secret_key_data(pubkey.ptr()) }); assert_eq!(actual_data.to_vec().unwrap(), secret_bytes); } diff --git a/rust/tw_memory/Cargo.toml b/rust/tw_memory/Cargo.toml index 642b3494812..2c7b443f83f 100644 --- a/rust/tw_memory/Cargo.toml +++ b/rust/tw_memory/Cargo.toml @@ -4,7 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] -zeroize = "1.8.1" [features] test-utils = [] diff --git a/rust/tw_memory/src/ffi/tw_data.rs b/rust/tw_memory/src/ffi/tw_data.rs index 675d0d11e08..7a573d8fe4b 100644 --- a/rust/tw_memory/src/ffi/tw_data.rs +++ b/rust/tw_memory/src/ffi/tw_data.rs @@ -5,7 +5,6 @@ use crate::ffi::c_byte_array_ref::CByteArrayRef; use crate::ffi::RawPtrTrait; use crate::Data; -use zeroize::Zeroize; /// Defines a resizable block of data. /// @@ -80,18 +79,6 @@ pub unsafe extern "C" fn tw_data_delete(data: *mut TWData) { let _ = TWData::from_ptr(data); } -/// Deletes a block of data zeroizing the memory. -/// -/// \param data A non-null valid block of data -#[no_mangle] -pub unsafe extern "C" fn tw_data_delete_zeroizing(data: *mut TWData) { - // Take the ownership back to rust and drop the owner. - let Some(mut data) = TWData::from_ptr(data) else { - return; - }; - data.0.zeroize(); -} - /// Returns the raw pointer to the contents of data. /// /// \param data A non-null valid block of data diff --git a/rust/tw_memory/src/test_utils/tw_wrapper.rs b/rust/tw_memory/src/test_utils/tw_wrapper.rs index 397ee144d6c..448b16097e9 100644 --- a/rust/tw_memory/src/test_utils/tw_wrapper.rs +++ b/rust/tw_memory/src/test_utils/tw_wrapper.rs @@ -2,22 +2,17 @@ // // Copyright © 2017 Trust Wallet. -pub type Destructor = unsafe extern "C" fn(*mut T); - pub trait WithDestructor: Sized { fn destructor() -> unsafe extern "C" fn(*mut Self); } -/// Structure pointer wrapper. -/// Use this wrapper when `T` can implement the `WithDestructor` trait. -/// Otherwise, consider using `TWWrapper`. -pub struct TWAutoWrapper { +pub struct TWWrapper { ptr: *mut T, } -impl TWAutoWrapper { +impl TWWrapper { pub fn wrap(ptr: *mut T) -> Self { - TWAutoWrapper { ptr } + TWWrapper { ptr } } pub fn ptr(&self) -> *mut T { @@ -25,7 +20,7 @@ impl TWAutoWrapper { } } -impl Drop for TWAutoWrapper { +impl Drop for TWWrapper { fn drop(&mut self) { if self.ptr.is_null() { return; @@ -33,28 +28,3 @@ impl Drop for TWAutoWrapper { unsafe { (T::destructor())(self.ptr) } } } - -/// Structure pointer wrapper with a manual destructor. -pub struct TWWrapper { - ptr: *mut T, - destructor: Destructor, -} - -impl TWWrapper { - pub fn wrap(ptr: *mut T, destructor: Destructor) -> Self { - TWWrapper { ptr, destructor } - } - - pub fn ptr(&self) -> *mut T { - self.ptr - } -} - -impl Drop for TWWrapper { - fn drop(&mut self) { - if self.ptr.is_null() { - return; - } - unsafe { (self.destructor)(self.ptr) } - } -} diff --git a/rust/tw_tests/tests/chains/ton/ton_wallet.rs b/rust/tw_tests/tests/chains/ton/ton_wallet.rs index a2187e038ed..e7cdf8f35f9 100644 --- a/rust/tw_tests/tests/chains/ton/ton_wallet.rs +++ b/rust/tw_tests/tests/chains/ton/ton_wallet.rs @@ -2,44 +2,14 @@ // // Copyright © 2017 Trust Wallet. -use tw_encoding::hex::ToHex; -use tw_keypair::test_utils::tw_private_key_helper::TWPrivateKeyHelper; use tw_keypair::test_utils::tw_public_key_helper::TWPublicKeyHelper; use tw_keypair::tw::PublicKeyType; use tw_memory::test_utils::tw_string_helper::TWStringHelper; -use tw_memory::test_utils::tw_wrapper::TWWrapper; use tw_ton::resources::WALLET_ID_V5R1_TON_MAINNET; -use wallet_core_rs::ffi::wallet::ton_wallet::{ +use wallet_core_rs::ffi::ton::wallet::{ tw_ton_wallet_build_v4_r2_state_init, tw_ton_wallet_build_v5_r1_state_init, - tw_ton_wallet_create_with_mnemonic, tw_ton_wallet_delete, tw_ton_wallet_get_key, - tw_ton_wallet_is_valid_mnemonic, }; -#[test] -fn test_ton_wallet_is_valid_mnemonic() { - let mnemonic = TWStringHelper::create("protect drill sugar gallery note admit input wrist chicken swarm scheme hedgehog orbit ritual glove ski buddy slogan fragile sun delay toy lucky require"); - let passphrase = TWStringHelper::create(""); - let invalid_passphrase = TWStringHelper::create("Expected empty passphrase"); - assert!(unsafe { tw_ton_wallet_is_valid_mnemonic(mnemonic.ptr(), passphrase.ptr()) }); - assert!(!unsafe { tw_ton_wallet_is_valid_mnemonic(mnemonic.ptr(), invalid_passphrase.ptr()) }); -} - -#[test] -fn test_ton_wallet_get_key() { - let mnemonic = TWStringHelper::create("protect drill sugar gallery note admit input wrist chicken swarm scheme hedgehog orbit ritual glove ski buddy slogan fragile sun delay toy lucky require"); - let passphrase = std::ptr::null(); - let wallet = TWWrapper::wrap( - unsafe { tw_ton_wallet_create_with_mnemonic(mnemonic.ptr(), passphrase) }, - tw_ton_wallet_delete, - ); - let key = TWPrivateKeyHelper::wrap(unsafe { tw_ton_wallet_get_key(wallet.ptr()) }); - let key_data = key.bytes().unwrap(); - assert_eq!( - key_data.to_hex(), - "cdcea50b87d3f1ca859e7b2bdf9a5339b7b6804b5c70ac85198829f9607dc43b" - ); -} - #[test] fn test_ton_wallet_v4_r2_create_state_init() { let public_key = TWPublicKeyHelper::with_hex( diff --git a/rust/wallet_core_rs/Cargo.toml b/rust/wallet_core_rs/Cargo.toml index 3ce587ab341..1b8f34f853a 100644 --- a/rust/wallet_core_rs/Cargo.toml +++ b/rust/wallet_core_rs/Cargo.toml @@ -12,7 +12,6 @@ default = [ "any-coin", "bitcoin", "ethereum", - "hd_wallet", "keypair", "solana", "ton", @@ -21,7 +20,6 @@ default = [ any-coin = ["tw_any_coin"] bitcoin = ["tw_bitcoin", "tw_coin_registry"] ethereum = ["tw_ethereum", "tw_coin_registry"] -hd_wallet = ["tw_hd_wallet"] keypair = ["tw_keypair"] solana = ["tw_solana"] ton = ["tw_ton"] @@ -49,5 +47,4 @@ tw_misc = { path = "../tw_misc" } tw_proto = { path = "../tw_proto", optional = true } tw_solana = { path = "../chains/tw_solana", optional = true } tw_ton = { path = "../chains/tw_ton", optional = true } -tw_hd_wallet = { path = "../tw_hd_wallet", optional = true } uuid = { version = "1.7", features = ["v4"], optional = true } diff --git a/rust/wallet_core_rs/src/ffi/mod.rs b/rust/wallet_core_rs/src/ffi/mod.rs index 513396b4a06..7b77f1ed28a 100644 --- a/rust/wallet_core_rs/src/ffi/mod.rs +++ b/rust/wallet_core_rs/src/ffi/mod.rs @@ -12,5 +12,3 @@ pub mod solana; pub mod ton; #[cfg(feature = "utils")] pub mod utils; -#[cfg(feature = "hd_wallet")] -pub mod wallet; diff --git a/rust/wallet_core_rs/src/ffi/ton/mod.rs b/rust/wallet_core_rs/src/ffi/ton/mod.rs index fe4f7cf25f6..02721354dfc 100644 --- a/rust/wallet_core_rs/src/ffi/ton/mod.rs +++ b/rust/wallet_core_rs/src/ffi/ton/mod.rs @@ -4,3 +4,4 @@ pub mod address_converter; pub mod message_signer; +pub mod wallet; diff --git a/rust/wallet_core_rs/src/ffi/ton/wallet.rs b/rust/wallet_core_rs/src/ffi/ton/wallet.rs new file mode 100644 index 00000000000..0e3c81a1971 --- /dev/null +++ b/rust/wallet_core_rs/src/ffi/ton/wallet.rs @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#![allow(clippy::missing_safety_doc)] + +use tw_keypair::ffi::pubkey::TWPublicKey; +use tw_memory::ffi::tw_string::TWString; +use tw_memory::ffi::RawPtrTrait; +use tw_misc::try_or_else; +use tw_ton::modules::wallet_provider::WalletProvider; + +/// Constructs a TON Wallet V4R2 stateInit encoded as BoC (BagOfCells) for the given `public_key`. +/// +/// \param public_key wallet's public key. +/// \param workchain TON workchain to which the wallet belongs. Usually, base chain is used (0). +/// \param wallet_id wallet's ID allows to create multiple wallets for the same private key. +/// \return Pointer to a base64 encoded Bag Of Cells (BoC) StateInit. Null if invalid public key provided. +#[no_mangle] +pub unsafe extern "C" fn tw_ton_wallet_build_v4_r2_state_init( + public_key: *const TWPublicKey, + workchain: i32, + wallet_id: i32, +) -> *mut TWString { + let public_key = try_or_else!(TWPublicKey::from_ptr_as_ref(public_key), std::ptr::null_mut); + let ed_pubkey = try_or_else!(public_key.as_ref().to_ed25519(), std::ptr::null_mut).clone(); + + let state_init = try_or_else!( + WalletProvider::v4r2_state_init(ed_pubkey, workchain, wallet_id), + std::ptr::null_mut + ); + TWString::from(state_init).into_ptr() +} + +// Constructs a TON Wallet V5R1 stateInit encoded as BoC (BagOfCells) for the given `public_key`. +/// +/// \param public_key wallet's public key. +/// \param workchain TON workchain to which the wallet belongs. Usually, base chain is used (0). +/// \param wallet_id wallet's ID allows to create multiple wallets for the same private key. +/// \return Pointer to a base64 encoded Bag Of Cells (BoC) StateInit. Null if invalid public key provided. +#[no_mangle] +pub unsafe extern "C" fn tw_ton_wallet_build_v5_r1_state_init( + public_key: *const TWPublicKey, + workchain: i32, + wallet_id: i32, +) -> *mut TWString { + let public_key = try_or_else!(TWPublicKey::from_ptr_as_ref(public_key), std::ptr::null_mut); + let ed_pubkey = try_or_else!(public_key.as_ref().to_ed25519(), std::ptr::null_mut).clone(); + + let state_init = try_or_else!( + WalletProvider::v5r1_state_init(ed_pubkey, workchain, wallet_id), + std::ptr::null_mut + ); + TWString::from(state_init).into_ptr() +} diff --git a/rust/wallet_core_rs/src/ffi/wallet/mod.rs b/rust/wallet_core_rs/src/ffi/wallet/mod.rs deleted file mode 100644 index 3a0788a1e2c..00000000000 --- a/rust/wallet_core_rs/src/ffi/wallet/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -pub mod ton_wallet; diff --git a/rust/wallet_core_rs/src/ffi/wallet/ton_wallet.rs b/rust/wallet_core_rs/src/ffi/wallet/ton_wallet.rs deleted file mode 100644 index d367824ed81..00000000000 --- a/rust/wallet_core_rs/src/ffi/wallet/ton_wallet.rs +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#![allow(clippy::missing_safety_doc)] - -use tw_hd_wallet::ton::TonWallet; -use tw_keypair::ffi::privkey::TWPrivateKey; -use tw_keypair::ffi::pubkey::TWPublicKey; -use tw_keypair::traits::KeyPairTrait; -use tw_keypair::tw; -use tw_memory::ffi::tw_string::TWString; -use tw_memory::ffi::RawPtrTrait; -use tw_misc::{try_or_else, try_or_false}; -use tw_ton::modules::wallet_provider::WalletProvider; - -pub struct TWTONWallet(TonWallet); - -impl RawPtrTrait for TWTONWallet {} - -/// Determines whether the English mnemonic and passphrase are valid. -/// -/// \param mnemonic Non-null english mnemonic -/// \param passphrase Nullable optional passphrase -/// \note passphrase can be null or empty string if no passphrase required -/// \return whether the mnemonic and passphrase are valid (valid checksum) -#[no_mangle] -pub unsafe extern "C" fn tw_ton_wallet_is_valid_mnemonic( - mnemonic: *const TWString, - passphrase: *const TWString, -) -> bool { - let mnemonic = try_or_false!(TWString::from_ptr_as_ref(mnemonic)); - let mnemonic = try_or_false!(mnemonic.as_str()); - - let passphrase = TWString::from_ptr_as_ref(passphrase) - .and_then(TWString::as_str) - .map(|pass| pass.to_string()); - - TonWallet::new(mnemonic, passphrase).is_ok() -} - -/// Creates a `TONWallet` from a valid TON mnemonic and passphrase. -/// -/// \param mnemonic Non-null english mnemonic -/// \param passphrase Nullable optional passphrase -/// \note Null is returned on invalid mnemonic and passphrase -/// \note passphrase can be null or empty string if no passphrase required -/// \return Nullable TWTONWallet -#[no_mangle] -pub unsafe extern "C" fn tw_ton_wallet_create_with_mnemonic( - mnemonic: *const TWString, - passphrase: *const TWString, -) -> *mut TWTONWallet { - let mnemonic = try_or_else!(TWString::from_ptr_as_ref(mnemonic), std::ptr::null_mut); - let mnemonic = try_or_else!(mnemonic.as_str(), std::ptr::null_mut); - - let passphrase = TWString::from_ptr_as_ref(passphrase) - .and_then(TWString::as_str) - .map(|pass| pass.to_string()); - - let wallet = try_or_else!(TonWallet::new(mnemonic, passphrase), std::ptr::null_mut); - TWTONWallet(wallet).into_ptr() -} - -/// Delete the given TON mnemonic. -/// -/// \param wallet *non-null* pointer to TWTONMnemonic. -#[no_mangle] -pub unsafe extern "C" fn tw_ton_wallet_delete(wallet: *mut TWTONWallet) { - // Take the ownership back to rust and drop the owner. - let _ = TWTONWallet::from_ptr(wallet); -} - -/// Generates Ed25519 private key associated with the wallet. -/// -/// \param wallet non-null TWTONWallet -/// \note Returned object needs to be deleted with \TWPrivateKeyDelete -/// \return The Ed25519 private key -#[no_mangle] -pub unsafe extern "C" fn tw_ton_wallet_get_key(wallet: *const TWTONWallet) -> *mut TWPrivateKey { - let wallet = try_or_else!(TWTONWallet::from_ptr_as_ref(wallet), std::ptr::null_mut); - let key = wallet.0.to_key_pair().private().clone(); - TWPrivateKey(tw::PrivateKey::from(key)).into_ptr() -} - -/// Constructs a TON Wallet V4R2 stateInit encoded as BoC (BagOfCells) for the given `public_key`. -/// -/// \param public_key wallet's public key. -/// \param workchain TON workchain to which the wallet belongs. Usually, base chain is used (0). -/// \param wallet_id wallet's ID allows to create multiple wallets for the same private key. -/// \return Pointer to a base64 encoded Bag Of Cells (BoC) StateInit. Null if invalid public key provided. -#[no_mangle] -pub unsafe extern "C" fn tw_ton_wallet_build_v4_r2_state_init( - public_key: *const TWPublicKey, - workchain: i32, - wallet_id: i32, -) -> *mut TWString { - let public_key = try_or_else!(TWPublicKey::from_ptr_as_ref(public_key), std::ptr::null_mut); - let ed_pubkey = try_or_else!(public_key.as_ref().to_ed25519(), std::ptr::null_mut).clone(); - - let state_init = try_or_else!( - WalletProvider::v4r2_state_init(ed_pubkey, workchain, wallet_id), - std::ptr::null_mut - ); - TWString::from(state_init).into_ptr() -} - -// Constructs a TON Wallet V5R1 stateInit encoded as BoC (BagOfCells) for the given `public_key`. -/// -/// \param public_key wallet's public key. -/// \param workchain TON workchain to which the wallet belongs. Usually, base chain is used (0). -/// \param wallet_id wallet's ID allows to create multiple wallets for the same private key. -/// \return Pointer to a base64 encoded Bag Of Cells (BoC) StateInit. Null if invalid public key provided. -#[no_mangle] -pub unsafe extern "C" fn tw_ton_wallet_build_v5_r1_state_init( - public_key: *const TWPublicKey, - workchain: i32, - wallet_id: i32, -) -> *mut TWString { - let public_key = try_or_else!(TWPublicKey::from_ptr_as_ref(public_key), std::ptr::null_mut); - let ed_pubkey = try_or_else!(public_key.as_ref().to_ed25519(), std::ptr::null_mut).clone(); - - let state_init = try_or_else!( - WalletProvider::v5r1_state_init(ed_pubkey, workchain, wallet_id), - std::ptr::null_mut - ); - TWString::from(state_init).into_ptr() -} diff --git a/src/DerivationPath.cpp b/src/DerivationPath.cpp index c2d369ee413..dd54e28efbc 100644 --- a/src/DerivationPath.cpp +++ b/src/DerivationPath.cpp @@ -46,10 +46,6 @@ DerivationPath::DerivationPath(const std::string& string) { } std::string DerivationPath::string() const noexcept { - if (indices.empty()) { - return {}; - } - std::string result = "m/"; for (auto& index : indices) { result += index.string(); diff --git a/src/Keystore/StoredKey.cpp b/src/Keystore/StoredKey.cpp index 732e0953e37..b216461fe31 100644 --- a/src/Keystore/StoredKey.cpp +++ b/src/Keystore/StoredKey.cpp @@ -8,7 +8,6 @@ #include "HexCoding.h" #include "Mnemonic.h" #include "PrivateKey.h" -#include "TheOpenNetwork/TONWallet.h" #include #include @@ -68,24 +67,6 @@ StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& na return key; } -StoredKey StoredKey::createWithTonMnemonic(const std::string& name, const Data& password, const std::string& tonMnemonic, TWStoredKeyEncryption encryption) { - if (!TheOpenNetwork::TONWallet::isValidMnemonic(tonMnemonic, std::nullopt)) { - throw std::invalid_argument("Invalid TON mnemonic"); - } - - Data mnemonicData = TW::Data(tonMnemonic.begin(), tonMnemonic.end()); - StoredKey key(StoredKeyType::tonMnemonicPhrase, name, password, mnemonicData, TWStoredKeyEncryptionLevelDefault, encryption); - memzero(mnemonicData.data(), mnemonicData.size()); - return key; -} - -StoredKey StoredKey::createWithTonMnemonicAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const std::string& tonMnemonic, TWStoredKeyEncryption encryption) { - StoredKey key = createWithTonMnemonic(name, password, tonMnemonic, encryption); - const auto tonWallet = key.tonWallet(password); - key.account(coin, TWDerivationDefault, tonWallet); - return key; -} - StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption) : type(type), id(), name(std::move(name)), accounts() { const auto encryptionParams = EncryptionParameters::getPreset(encryptionLevel, encryption); @@ -105,20 +86,6 @@ const HDWallet<> StoredKey::wallet(const Data& password) const { return HDWallet<>(mnemonic, ""); } -TheOpenNetwork::TONWallet StoredKey::tonWallet(const Data& password) const { - if (type != StoredKeyType::tonMnemonicPhrase) { - throw std::invalid_argument("Invalid account requested."); - } - const auto data = payload.decrypt(password); - const auto tonMnemonic = std::string(reinterpret_cast(data.data()), data.size()); - - auto maybeTonWallet = TheOpenNetwork::TONWallet::createWithMnemonic(tonMnemonic, std::nullopt); - if (!maybeTonWallet.has_value()) { - throw std::invalid_argument("Invalid TON mnemonic phrase."); - } - return std::move(*maybeTonWallet); -} - std::vector StoredKey::getAccounts(TWCoinType coin) const { std::vector result; for (auto& account : accounts) { @@ -176,12 +143,6 @@ std::optional StoredKey::getAccount(TWCoinType coin, TWDerivation deriv return getAccount(coin, address); } -std::optional StoredKey::getAccount(TWCoinType coin, TWDerivation derivation, const TheOpenNetwork::TONWallet& wallet) const { - // obtain address - const auto address = wallet.deriveAddress(coin, derivation); - return getAccount(coin, address); -} - Account StoredKey::fillAddressIfMissing(Account& account, const HDWallet<>* wallet) const { if (account.address.empty() && wallet != nullptr) { account.address = wallet->deriveAddress(account.coin, account.derivation); @@ -240,28 +201,6 @@ Account StoredKey::account(TWCoinType coin, TWDerivation derivation, const HDWal return accounts.back(); } -Account StoredKey::account(TWCoinType coin, TWDerivation derivation, const TheOpenNetwork::TONWallet& tonWallet) { - const auto coinAccount = getAccount(coin, derivation, tonWallet); - if (coinAccount.has_value()) { - // No need to use `fillAddressIfMissing` - // because `getAccount` searches for an account with both `coin` and `address` equal to expected. - // So `address` is always set. - return coinAccount.value(); - } - // Not found, add it. - - // No derivation path for TON wallet. Use default - const DerivationPath derivationPath {}; - const auto address = tonWallet.deriveAddress(coin, derivation); - // No extended public key for TON wallet. Use default - const std::string extendedPublicKey {}; - const auto pubKeyType = TW::publicKeyType(coin); - const auto pubKey = tonWallet.getKey(coin, derivation).getPublicKey(pubKeyType); - - addAccount(address, coin, derivation, derivationPath, hex(pubKey.bytes), extendedPublicKey); - return accounts.back(); -} - std::optional StoredKey::account(TWCoinType coin) const { return getDefaultAccountOrAny(coin, nullptr); } @@ -315,23 +254,14 @@ const PrivateKey StoredKey::privateKey(TWCoinType coin, const Data& password) { return privateKey(coin, TWDerivationDefault, password); } -const PrivateKey StoredKey::privateKey(TWCoinType coin, TWDerivation derivation, const Data& password) { - switch (type) { - case StoredKeyType::mnemonicPhrase: { - const auto wallet = this->wallet(password); - const Account account = this->account(coin, derivation, wallet); - return wallet.getKey(coin, account.derivationPath); - } - case StoredKeyType::privateKey: { - return PrivateKey(payload.decrypt(password)); - } - case StoredKeyType::tonMnemonicPhrase: { - const auto tonWallet = this->tonWallet(password); - return tonWallet.getKey(coin, derivation); - } - default: - throw std::invalid_argument("Unexpected StoredKey type"); +const PrivateKey StoredKey::privateKey(TWCoinType coin, [[maybe_unused]] TWDerivation derivation, const Data& password) { + if (type == StoredKeyType::mnemonicPhrase) { + const auto wallet = this->wallet(password); + const Account& account = this->account(coin, derivation, wallet); + return wallet.getKey(coin, account.derivationPath); } + // type == StoredKeyType::privateKey + return PrivateKey(payload.decrypt(password)); } void StoredKey::fixAddresses(const Data& password) { @@ -359,21 +289,6 @@ void StoredKey::fixAddresses(const Data& password) { updateAddressForAccount(key, account); } } break; - - case StoredKeyType::tonMnemonicPhrase: { - const auto tonWallet = this->tonWallet(password); - for (auto& account : accounts) { - if (!account.address.empty() && !account.publicKey.empty() && - TW::validateAddress(account.coin, account.address)) { - continue; - } - const auto key = tonWallet.getKey(account.coin, account.derivation); - updateAddressForAccount(key, account); - } - } break; - - default: - throw std::invalid_argument("Unexpected StoredKey type"); } } @@ -425,18 +340,12 @@ static const auto crypto = "Crypto"; namespace TypeString { static const auto privateKey = "private-key"; static const auto mnemonic = "mnemonic"; -static const auto tonMnemonic = "ton-mnemonic"; } // namespace TypeString void StoredKey::loadJson(const nlohmann::json& json) { - const auto isSKType = [](const nlohmann::json& json, const std::string& expected) -> bool { - return json.count(CodingKeys::SK::type) != 0 && json[CodingKeys::SK::type].get() == expected; - }; - - if (isSKType(json, TypeString::mnemonic)) { + if (json.count(CodingKeys::SK::type) != 0 && + json[CodingKeys::SK::type].get() == TypeString::mnemonic) { type = StoredKeyType::mnemonicPhrase; - } else if (isSKType(json, TypeString::tonMnemonic)) { - type = StoredKeyType::tonMnemonicPhrase; } else { type = StoredKeyType::privateKey; } @@ -487,9 +396,6 @@ nlohmann::json StoredKey::json() const { case StoredKeyType::mnemonicPhrase: j[CodingKeys::SK::type] = TypeString::mnemonic; break; - case StoredKeyType::tonMnemonicPhrase: - j[CodingKeys::SK::type] = TypeString::tonMnemonic; - break; } if (id) { diff --git a/src/Keystore/StoredKey.h b/src/Keystore/StoredKey.h index a98aace31df..12eaf5038f4 100644 --- a/src/Keystore/StoredKey.h +++ b/src/Keystore/StoredKey.h @@ -7,8 +7,7 @@ #include "Account.h" #include "EncryptionParameters.h" #include "Data.h" -#include "HDWallet.h" -#include "TheOpenNetwork/TONWallet.h" +#include "../HDWallet.h" #include #include @@ -21,13 +20,9 @@ namespace TW::Keystore { -/// An stored key can be either a private key, or a mnemonic phrase for a HD -/// wallet, or a TON-specific mnemonic phrase. -enum class StoredKeyType { - privateKey, - mnemonicPhrase, - tonMnemonicPhrase -}; +/// An stored key can be either a private key or a mnemonic phrase for a HD +/// wallet. +enum class StoredKeyType { privateKey, mnemonicPhrase }; /// Represents a key stored as an encrypted file. class StoredKey { @@ -67,16 +62,6 @@ class StoredKey { /// @throws std::invalid_argument if privateKeyData is not a valid private key static StoredKey createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); - /// Create a new StoredKey, with the given name and TON specific mnemonic. - /// @throws std::invalid_argument if menmonic is invalid - /// @note mnemonic created with a passphrase is not supported yet - static StoredKey createWithTonMnemonic(const std::string& name, const Data& password, const std::string& tonMnemonic, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); - - /// Create a new StoredKey, with the given name and TON specific mnemonic, and also add the default address for the given coin. - /// @throws std::invalid_argument if menmonic is invalid - /// @note mnemonic created with a passphrase is not supported yet - static StoredKey createWithTonMnemonicAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const std::string& tonMnemonic, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); - /// Create a StoredKey from a JSON object. static StoredKey createWithJson(const nlohmann::json& json); @@ -85,31 +70,19 @@ class StoredKey { /// @throws std::invalid_argument if this key is of a type other than `mnemonicPhrase`. const HDWallet<> wallet(const Data& password) const; - /// Returns the TONWallet for this key. - /// - /// @throws std::invalid_argument if this key is of a type other than `tonMnemonicPhrase`. - TheOpenNetwork::TONWallet tonWallet(const Data& password) const; - /// Returns all the accounts for a specific coin: 0, 1, or more. std::vector getAccounts(TWCoinType coin) const; - /// If found, returns the account for a specific coin. - /// In case of multiple accounts, the default derivation is returned, or the first one is returned. + /// If found, returns the account for a specific coin. In case of muliple accounts, the default derivation is returned, or the first one is returned. /// If none exists, and wallet is not null, an account is created (with default derivation). std::optional account(TWCoinType coin, const HDWallet<>* wallet); - /// If found, returns the account for a specific coin and derivation. - /// In case of multiple accounts, the first one is returned. + /// If found, returns the account for a specific coin and derivation. In case of muliple accounts, the first one is returned. /// If none exists, an account is created. Account account(TWCoinType coin, TWDerivation derivation, const HDWallet<>& wallet); - /// If found, returns the account for a specific coin. - /// In case of multiple accounts, the first one is returned. - /// If none exists, an account is created. - Account account(TWCoinType coin, TWDerivation derivation, const TheOpenNetwork::TONWallet& tonWallet); - /// Returns the account for a specific coin if it exists. - /// In case of multiple accounts, the default derivation is returned, or the first one is returned. + /// In case of muliple accounts, the default derivation is returned, or the first one is returned. std::optional account(TWCoinType coin) const; /// Returns the account for a specific coin and derivation, if it exists. @@ -200,9 +173,6 @@ class StoredKey { /// Find account by coin+derivation (should be one, if multiple, first is returned) std::optional getAccount(TWCoinType coin, TWDerivation derivation, const HDWallet<>& wallet) const; - /// Find account by coin+derivation (should be one, if multiple, first is returned) - std::optional getAccount(TWCoinType coin, TWDerivation derivation, const TheOpenNetwork::TONWallet& wallet) const; - /// Re-derive account address if missing Account fillAddressIfMissing(Account& account, const HDWallet<>* wallet) const; diff --git a/src/TheOpenNetwork/TONWallet.cpp b/src/TheOpenNetwork/TONWallet.cpp deleted file mode 100644 index a4b4adc6c28..00000000000 --- a/src/TheOpenNetwork/TONWallet.cpp +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#include "TONWallet.h" -#include "Coin.h" - -namespace TW::TheOpenNetwork { - -bool TONWallet::isValidMnemonic(const std::string& mnemonic, const MaybePassphrase& passphrase) { - const Rust::TWStringWrapper mnemonicRust = mnemonic; - - if (passphrase.has_value()) { - const Rust::TWStringWrapper passphraseRust = passphrase.value(); - return Rust::tw_ton_wallet_is_valid_mnemonic(mnemonicRust.get(), passphraseRust.get()); - } else { - return Rust::tw_ton_wallet_is_valid_mnemonic(mnemonicRust.get(), nullptr); - } -} - -MaybeTONWallet TONWallet::createWithMnemonic(const std::string& mnemonic, const MaybePassphrase& passphrase) { - const Rust::TWStringWrapper mnemonicRust = mnemonic; - - Rust::TWTONWallet* walletPtr; - if (passphrase.has_value()) { - const Rust::TWStringWrapper passphraseRust = passphrase.value(); - walletPtr = Rust::tw_ton_wallet_create_with_mnemonic(mnemonicRust.get(), passphraseRust.get()); - } else { - walletPtr = Rust::tw_ton_wallet_create_with_mnemonic(mnemonicRust.get(), nullptr); - } - - if (!walletPtr) { - return std::nullopt; - } - - return TONWallet(TONWalletPtr(walletPtr, Rust::tw_ton_wallet_delete)); -} - -PrivateKey TONWallet::getKey(TWCoinType coin, TWDerivation derivation) const { - if (coin != TWCoinTypeTON || derivation != TWDerivationDefault) { - throw std::invalid_argument("'TONWallet' supports TON coin and Default derivation only"); - } - - const auto privateKeyRust = wrapTWPrivateKey(Rust::tw_ton_wallet_get_key(impl.get())); - const Rust::TWDataWrapper privateKeyBytes = Rust::tw_private_key_data(privateKeyRust.get()); - return PrivateKey(privateKeyBytes.toDataOrDefault()); -} - -std::string TONWallet::deriveAddress(TWCoinType coin, TWDerivation derivation) const { - const auto key = getKey(coin, derivation); - return TW::deriveAddress(coin, key, derivation); -} - -} // namespace TW::TheOpenNetwork diff --git a/src/TheOpenNetwork/TONWallet.h b/src/TheOpenNetwork/TONWallet.h deleted file mode 100644 index b6d80e1f0e2..00000000000 --- a/src/TheOpenNetwork/TONWallet.h +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#pragma once - -#include "PrivateKey.h" -#include "rust/Wrapper.h" - -#include "TrustWalletCore/TWCoinType.h" -#include "TrustWalletCore/TWDerivation.h" - -namespace TW::TheOpenNetwork { - -class TONWallet; - -using TONWalletPtr = std::shared_ptr; -using MaybePassphrase = std::optional; -using MaybeTONWallet = std::optional; - -class TONWallet { -public: - static bool isValidMnemonic(const std::string& mnemonic, const MaybePassphrase& passphrase); - - static MaybeTONWallet createWithMnemonic(const std::string& mnemonic, const MaybePassphrase& passphrase); - - /// Returns the private key with the given coin and derivation. - /// \throws std exception if `coin` or `derivation` aren't `TWCoinTypeTON` and `TWDerivationDefault` correspondingly. - PrivateKey getKey(TWCoinType coin = TWCoinTypeTON, TWDerivation derivation = TWDerivationDefault) const; - - /// Derives the address for the given coin and derivation. - /// \throws std exception if `coin` or `derivation` aren't `TWCoinTypeTON` and `TWDerivationDefault` correspondingly. - std::string deriveAddress(TWCoinType coin = TWCoinTypeTON, TWDerivation derivation = TWDerivationDefault) const; - -private: - explicit TONWallet(TONWalletPtr ptr): impl(std::move(ptr)) { - } - - TONWalletPtr impl; -}; - -} // namespace TW::TheOpenNetwork - -/// Wrapper for C interface. -struct TWTONWallet { - TW::TheOpenNetwork::TONWallet impl; -}; diff --git a/src/interface/TWStoredKey.cpp b/src/interface/TWStoredKey.cpp index 90c78c94e13..f756ce5e6b3 100644 --- a/src/interface/TWStoredKey.cpp +++ b/src/interface/TWStoredKey.cpp @@ -72,21 +72,6 @@ struct TWStoredKey* _Nullable TWStoredKeyImportHDWalletWithEncryption(TWString* } } -struct TWStoredKey* _Nullable TWStoredKeyImportTONWallet(TWString* _Nonnull tonMnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin) { - return TWStoredKeyImportTONWalletWithEncryption(tonMnemonic, name, password, coin, TWStoredKeyEncryptionAes128Ctr); -} - -struct TWStoredKey* _Nullable TWStoredKeyImportTONWalletWithEncryption(TWString* _Nonnull tonMnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption) { - try { - const auto& tonMnemonicString = *reinterpret_cast(tonMnemonic); - const auto& nameString = *reinterpret_cast(name); - const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); - return new TWStoredKey{ KeyStore::StoredKey::createWithTonMnemonicAddDefaultAddress(nameString, passwordData, coin, tonMnemonicString, encryption) }; - } catch (...) { - return nullptr; - } -} - struct TWStoredKey* _Nullable TWStoredKeyImportJSON(TWData* _Nonnull json) { try { const auto& d = *reinterpret_cast(json); @@ -116,10 +101,6 @@ bool TWStoredKeyIsMnemonic(struct TWStoredKey* _Nonnull key) { return key->impl.type == KeyStore::StoredKeyType::mnemonicPhrase; } -bool TWStoredKeyIsTONMnemonic(struct TWStoredKey* _Nonnull key) { - return key->impl.type == KeyStore::StoredKeyType::tonMnemonicPhrase; -} - size_t TWStoredKeyAccountCount(struct TWStoredKey* _Nonnull key) { return key->impl.accounts.size(); } @@ -210,13 +191,6 @@ TWString* _Nullable TWStoredKeyDecryptMnemonic(struct TWStoredKey* _Nonnull key, } } -TWString* _Nullable TWStoredKeyDecryptTONMnemonic(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password) { - if (!TWStoredKeyIsTONMnemonic(key)) { - return nullptr; - } - return TWStoredKeyDecryptMnemonic(key, password); -} - struct TWPrivateKey* _Nullable TWStoredKeyPrivateKey(struct TWStoredKey* _Nonnull key, enum TWCoinType coin, TWData* _Nonnull password) { try { const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); diff --git a/src/interface/TWTONWallet.cpp b/src/interface/TWTONWallet.cpp index 137a672f2c1..7e6495491ab 100644 --- a/src/interface/TWTONWallet.cpp +++ b/src/interface/TWTONWallet.cpp @@ -3,46 +3,11 @@ // Copyright © 2017 Trust Wallet. #include "TrustWalletCore/TWTONWallet.h" -#include "TheOpenNetwork/TONWallet.h" #include "rust/Wrapper.h" #include "PublicKey.h" using namespace TW; -bool TWTONWalletIsValidMnemonic(TWString* _Nonnull mnemonic, TWString* _Nullable passphrase) { - const auto& mnemonicRef = *reinterpret_cast(mnemonic); - std::string passphraseObj; - if (passphrase) { - passphraseObj = std::string(TWStringUTF8Bytes(passphrase), TWStringSize(passphrase)); - } - - return TheOpenNetwork::TONWallet::isValidMnemonic(mnemonicRef, passphraseObj); -} - -struct TWTONWallet* _Nullable TWTONWalletCreateWithMnemonic(TWString* _Nonnull mnemonic, TWString* _Nullable passphrase) { - const auto& mnemonicRef = *reinterpret_cast(mnemonic); - std::string passphraseObj; - if (passphrase) { - passphraseObj = std::string(TWStringUTF8Bytes(passphrase), TWStringSize(passphrase)); - } - - const auto maybeWallet = TheOpenNetwork::TONWallet::createWithMnemonic(mnemonicRef, passphraseObj); - if (!maybeWallet.has_value()) { - return nullptr; - } - - return new TWTONWallet { .impl = *maybeWallet }; -} - -void TWTONWalletDelete(struct TWTONWallet* _Nonnull wallet) { - delete wallet; -} - -struct TWPrivateKey* _Nonnull TWTONWalletGetKey(struct TWTONWallet* _Nonnull wallet) { - const auto privateKey = wallet->impl.getKey(); - return new TWPrivateKey { .impl = privateKey }; -} - TWString *_Nullable TWTONWalletBuildV4R2StateInit(struct TWPublicKey *_Nonnull publicKey, int32_t workchain, int32_t walletId) { auto keyType = static_cast(TWPublicKeyKeyType(publicKey)); auto* publicKeyRustRaw = Rust::tw_public_key_create_with_data(publicKey->impl.bytes.data(), publicKey->impl.bytes.size(), keyType); diff --git a/src/rust/Wrapper.h b/src/rust/Wrapper.h index 1ac64605951..04d97a9104c 100644 --- a/src/rust/Wrapper.h +++ b/src/rust/Wrapper.h @@ -13,15 +13,11 @@ namespace TW::Rust { inline std::shared_ptr wrapTWAnyAddress(TWAnyAddress* anyAddress) { - return { anyAddress, tw_any_address_delete }; -} - -inline std::shared_ptr wrapTWPrivateKey(TWPrivateKey* privateKey) { - return { privateKey, tw_private_key_delete }; + return std::shared_ptr(anyAddress, tw_any_address_delete); } inline std::shared_ptr wrapTWPublicKey(TWPublicKey* publicKey) { - return { publicKey, tw_public_key_delete }; + return std::shared_ptr(publicKey, tw_public_key_delete); } struct TWDataVectorWrapper { diff --git a/swift/Sources/KeyStore.swift b/swift/Sources/KeyStore.swift index e1a6b7bfa8f..0b6c3368d77 100644 --- a/swift/Sources/KeyStore.swift +++ b/swift/Sources/KeyStore.swift @@ -6,12 +6,6 @@ import Foundation -private enum StoredKeyType { - case privateKey(PrivateKey) - case mnemonic(String) - case tonMnemonic(String) -} - /// Manages directories of key and wallet files and presents them as accounts. public final class KeyStore { static let watchesFileName = "watches.json" @@ -136,49 +130,25 @@ public final class KeyStore { guard let key = StoredKey.importJSON(json: json) else { throw Error.invalidJSON } - - switch try decryptSecret(key: key, password: Data(password.utf8)) { - case .privateKey(let privateKey): - return try self.import(privateKey: privateKey, name: name, password: newPassword, coin: coins.first ?? .ethereum) - case .mnemonic(let mnemonic): - return try self.import(mnemonic: mnemonic, name: name, encryptPassword: newPassword, coins: coins) - case .tonMnemonic(let tonMnemonic): - return try self.importTON(tonMnemonic: tonMnemonic, name: name, encryptPassword: newPassword, coin: coins.first ?? .ton) - } - } - - /// Decrypts an inner secret as a `privateKey`, `mnemonic` or another type. - /// - Returns: a `StoredKeyType` enum. - /// - Note: `StoredKey::type` is not always set, and mnemonic or private key can be stored encrypted without any tag. - private func decryptSecret(key: StoredKey, password: Data) throws -> StoredKeyType { - guard var secretData = key.decryptPrivateKey(password: password) else { + guard let data = key.decryptPrivateKey(password: Data(password.utf8)) else { throw Error.invalidPassword } - defer { - secretData.resetBytes(in: 0 ..< secretData.count) - } - // First, check whether the key is init with a TON mnemonic. - // That's because TON Wallet is a new feature, and `StoredKey::type` is always set for these kind of keys. - if key.isTONMnemonic { - guard let tonMnemonic = String(data: secretData, encoding: .ascii), TONWallet.isValidMnemonic(mnemonic: tonMnemonic, passphrase: nil) else { - throw Error.invalidMnemonic - } - return StoredKeyType.tonMnemonic(tonMnemonic) + if let mnemonic = checkMnemonic(data) { + return try self.import(mnemonic: mnemonic, name: name, encryptPassword: newPassword, coins: coins) } - // The next, we should try to convert the secret into a string and check whether it's a valid BIP39 mnemonic phrase. - if let mnemonic = String(data: secretData, encoding: .ascii), Mnemonic.isValid(mnemonic: mnemonic) { - return StoredKeyType.mnemonic(mnemonic) + guard let privateKey = PrivateKey(data: data) else { + throw Error.invalidKey } + return try self.import(privateKey: privateKey, name: name, password: newPassword, coin: coins.first ?? .ethereum) + } - // Otherwise, we consider the secret as a private key. - - if let privateKey = PrivateKey(data: secretData) { - return StoredKeyType.privateKey(privateKey) + private func checkMnemonic(_ data: Data) -> String? { + guard let mnemonic = String(data: data, encoding: .ascii), Mnemonic.isValid(mnemonic: mnemonic) else { + return nil } - - throw Error.invalidKey + return mnemonic } /// Imports a private key. @@ -223,31 +193,6 @@ public final class KeyStore { return wallet } - - /// Imports a TON wallet. - /// - /// - Parameters: - /// - tonMnemonic: TON wallet's mnemonic phrase - /// - encryptPassword: password to use for encrypting - /// - coin: coins to use for this wallet - /// - Returns: new account - public func `importTON`(tonMnemonic: String, name: String, encryptPassword: String, coin: CoinType, encryption: StoredKeyEncryption = .aes128Ctr) throws -> Wallet { - guard let newKey = StoredKey.importTONWalletWithEncryption(tonMnemonic: tonMnemonic, name: name, password: Data(encryptPassword.utf8), coin: coin, encryption: encryption) else { - throw Error.invalidKey - } - - let url = makeAccountURL() - let wallet = Wallet(keyURL: url, key: newKey) - // `StoredKey.importTONWalletWithEncryption` should create exactly one account only. - if wallet.accounts.count != 1 { - throw Error.invalidKey - } - wallets.append(wallet) - - try save(wallet: wallet) - - return wallet - } /// Exports a wallet as JSON data. /// @@ -257,29 +202,28 @@ public final class KeyStore { /// - newPassword: password to use for exported key /// - Returns: encrypted JSON key public func export(wallet: Wallet, password: String, newPassword: String, encryption: StoredKeyEncryption = .aes128Ctr) throws -> Data { - // TODO why `importHDWalletWithEncryption` is called with a single coin? - // I guess that's because we don't want other wallets to know all user accounts. + var privateKeyData = try exportPrivateKey(wallet: wallet, password: password) + defer { + privateKeyData.resetBytes(in: 0 ..< privateKeyData.count) + } + guard let coin = wallet.key.account(index: 0)?.coin else { throw Error.accountNotFound } - let secretType = try decryptSecret(key: wallet.key, password: Data(password.utf8)) - - let maybeNewKey: StoredKey? = switch secretType { - case .privateKey(let privateKey): - StoredKey.importPrivateKeyWithEncryption(privateKey: privateKey.data, name: "", password: Data(newPassword.utf8), coin: coin, encryption: encryption) - - case .mnemonic(let mnemonic): - StoredKey.importHDWalletWithEncryption(mnemonic: mnemonic, name: "", password: Data(newPassword.utf8), coin: coin, encryption: encryption) - - case .tonMnemonic(let tonMnemonic): - StoredKey.importTONWalletWithEncryption(tonMnemonic: tonMnemonic, name: "", password: Data(newPassword.utf8), coin: coin, encryption: encryption) + if let mnemonic = checkMnemonic(privateKeyData), let newKey = StoredKey.importHDWalletWithEncryption(mnemonic: mnemonic, name: "", password: Data(newPassword.utf8), coin: coin, encryption: encryption) { + guard let json = newKey.exportJSON() else { + throw Error.invalidKey + } + return json + } else if let newKey = StoredKey.importPrivateKeyWithEncryption(privateKey: privateKeyData, name: "", password: Data(newPassword.utf8), coin: coin, encryption: encryption) { + guard let json = newKey.exportJSON() else { + throw Error.invalidKey + } + return json } - guard let newKey = maybeNewKey, let json = newKey.exportJSON() else { - throw Error.invalidKey - } - return json + throw Error.invalidKey } /// Exports a wallet as private key data. @@ -288,7 +232,6 @@ public final class KeyStore { /// - wallet: wallet to export /// - password: account password /// - Returns: private key data for encrypted keys or mnemonic phrase for HD wallets - /// - Throws: `EncryptError.invalidPassword` if the password is incorrect. public func exportPrivateKey(wallet: Wallet, password: String) throws -> Data { guard let key = wallet.key.decryptPrivateKey(password: Data(password.utf8)) else { throw Error.invalidPassword @@ -302,27 +245,13 @@ public final class KeyStore { /// - wallet: wallet to export /// - password: account password /// - Returns: mnemonic phrase - /// - Throws: `EncryptError.invalidPassword` if the password is incorrect. + /// - Throws: `EncryptError.invalidMnemonic` if the account is not an HD wallet. public func exportMnemonic(wallet: Wallet, password: String) throws -> String { guard let mnemonic = wallet.key.decryptMnemonic(password: Data(password.utf8)) else { throw Error.invalidPassword } return mnemonic } - - /// Exports a wallet as a TON mnemonic phrase. - /// - /// - Parameters: - /// - wallet: wallet to export - /// - password: account password - /// - Returns: TON mnemonic phrase - /// - Throws: `EncryptError.invalidPassword` if the password is incorrect. - public func exportTONMnemonic(wallet: Wallet, password: String) throws -> String { - guard let tonMnemonic = wallet.key.decryptTONMnemonic(password: Data(password.utf8)) else { - throw Error.invalidPassword - } - return tonMnemonic - } /// Updates the password of an existing account. /// @@ -349,30 +278,28 @@ public final class KeyStore { fatalError("Missing wallet") } + guard var privateKeyData = wallet.key.decryptPrivateKey(password: Data(password.utf8)) else { + throw Error.invalidPassword + } + defer { + privateKeyData.resetBytes(in: 0 ..< privateKeyData.count) + } + let coins = wallet.accounts.map({ $0.coin }) guard !coins.isEmpty else { throw Error.accountNotFound } - let secretType = try decryptSecret(key: wallet.key, password: Data(password.utf8)) - - let maybeNewKey: StoredKey? = switch secretType { - case .privateKey(let privateKey): - StoredKey.importPrivateKeyWithEncryption( - privateKey: privateKey.data, name: newName, password: Data(newPassword.utf8), coin: coins[0], encryption: encryption) - - case .mnemonic(let mnemonic): - StoredKey.importHDWalletWithEncryption(mnemonic: mnemonic, name: newName, password: Data(newPassword.utf8), coin: coins[0], encryption: encryption) - - case .tonMnemonic(let tonMnemonic): - StoredKey.importTONWalletWithEncryption(tonMnemonic: tonMnemonic, name: newName, password: Data(newPassword.utf8), coin: coins[0], encryption: encryption) - } - - guard let newKey = maybeNewKey else { + if let mnemonic = checkMnemonic(privateKeyData), + let key = StoredKey.importHDWalletWithEncryption(mnemonic: mnemonic, name: newName, password: Data(newPassword.utf8), coin: coins[0], encryption: encryption) { + wallets[index].key = key + } else if let key = StoredKey.importPrivateKeyWithEncryption( + privateKey: privateKeyData, name: newName, password: Data(newPassword.utf8), coin: coins[0], encryption: encryption) { + wallets[index].key = key + } else { throw Error.invalidKey } - wallets[index].key = newKey _ = try wallets[index].getAccounts(password: newPassword, coins: coins) try save(wallet: wallets[index]) } diff --git a/swift/Sources/Wallet.swift b/swift/Sources/Wallet.swift index 0dc2ec1c6d3..04749b5d564 100644 --- a/swift/Sources/Wallet.swift +++ b/swift/Sources/Wallet.swift @@ -59,7 +59,7 @@ public final class Wallet: Hashable, Equatable { return account } - /// Returns the accounts for specific coins. + /// Returns the accounts for a specific coins. /// /// - Parameters: /// - password: wallet encryption password @@ -67,10 +67,6 @@ public final class Wallet: Hashable, Equatable { /// - Returns: the added accounts /// - Throws: `KeyStore.Error.invalidPassword` if the password is incorrect. public func getAccounts(password: String, coins: [CoinType]) throws -> [Account] { - if !key.isMnemonic { - return coins.compactMap({ key.accountForCoin(coin: $0, wallet: nil) }) - } - guard let wallet = key.wallet(password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword } diff --git a/swift/Tests/Keystore/Data/ton_wallet.json b/swift/Tests/Keystore/Data/ton_wallet.json deleted file mode 100644 index 3b744a4b01e..00000000000 --- a/swift/Tests/Keystore/Data/ton_wallet.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "activeAccounts": [ - { - "address": "UQBlm676c6vy6Q9Js732pvf3ivfmIkVc0MVDQy-F6NAFJd4k", - "coin": 607, - "derivationPath": "", - "publicKey": "9016f03f9cfa4e183707761f25407e0e1975194a33a56b3e8d2c26f2438fa3d1" - } - ], - "crypto": { - "cipher": "aes-128-ctr", - "cipherparams": { - "iv": "459bbf197fe8e1cdfb949fc516257238" - }, - "ciphertext": "efbda7237b41d2340a4b4c7f0ab6a103e31f7a2b8148137abc815a0d346c3ae851d842ad068ad0218ef9f81647f41e52f7b056d568a314433a0b7980601f96e8974a071b5ddedd50079eebdb9281a8d84a0b9d4649ef3125f6605e303a78c0a4ed67556e3cd4e88b78b120267544eb44a912c92516562acb9782e0ea989cb50bce5948e2dfa26107053caf838af096ce47357061a2b49654", - "kdf": "scrypt", - "kdfparams": { - "dklen": 32, - "n": 16384, - "p": 4, - "r": 8, - "salt": "" - }, - "mac": "be9e9a26076334683799c2dbe22bfb2d9f2ff92ad130aa61b4687b392e7b98e6" - }, - "id": "f7a2172e-fb7a-427a-8526-99779fc47c0a", - "name": "name", - "type": "ton-mnemonic", - "version": 3 -} diff --git a/swift/Tests/Keystore/KeyStoreTests.swift b/swift/Tests/Keystore/KeyStoreTests.swift index 4ce0748dd3d..99b2815b825 100755 --- a/swift/Tests/Keystore/KeyStoreTests.swift +++ b/swift/Tests/Keystore/KeyStoreTests.swift @@ -25,17 +25,12 @@ extension KeyStore { var bnbWallet: Wallet { return wallets.first(where: { $0.identifier == "bnb_wallet.json"})! } - - var tonWallet: Wallet? { - return wallets.first(where: { $0.identifier == "ton_wallet.json"}) - } } class KeyStoreTests: XCTestCase { let keyAddress = AnyAddress(string: "0x008AeEda4D805471dF9b2A5B0f38A0C3bCBA786b", coin: .ethereum)! let walletAddress = AnyAddress(string: "0x32dd55E0BCF509a35A3F5eEb8593fbEb244796b1", coin: .ethereum)! let mnemonic = "often tobacco bread scare imitate song kind common bar forest yard wisdom" - let tonMnemonic = "laundry myself fitness beyond prize piano match acid vacuum already abandon dance occur pause grocery company inject excuse weasel carpet fog grunt trick spike" let fileManager = FileManager.default var keyDirectory: URL! @@ -78,17 +73,11 @@ class KeyStoreTests: XCTestCase { try? fileManager.removeItem(at: bnbWalletDestination) try? fileManager.copyItem(at: bnbWalletURL, to: bnbWalletDestination) - - let tonWalletURL = Bundle(for: type(of: self)).url(forResource: "ton_wallet", withExtension: "json")! - let tonWalletDestination = keyDirectory.appendingPathComponent("ton_wallet.json") - - try? fileManager.removeItem(at: tonWalletDestination) - try? fileManager.copyItem(at: tonWalletURL, to: tonWalletDestination) } func testLoadKeyStore() { let keyStore = try! KeyStore(keyDirectory: keyDirectory) - XCTAssertEqual(keyStore.wallets.count, 5) + XCTAssertEqual(keyStore.wallets.count, 4) XCTAssertEqual(keyStore.watches.count, 1) } @@ -98,13 +87,13 @@ class KeyStoreTests: XCTestCase { let newWallet = try keyStore.createWallet(name: "name", password: "password", coins: coins) XCTAssertEqual(newWallet.accounts.count, 3) - XCTAssertEqual(keyStore.wallets.count, 6) + XCTAssertEqual(keyStore.wallets.count, 5) XCTAssertNoThrow(try newWallet.getAccount(password: "password", coin: .ethereum)) XCTAssertNoThrow(try newWallet.getAccount(password: "password", coin: .binance)) XCTAssertNoThrow(try newWallet.getAccount(password: "password", coin: .smartChain)) } - func testUpdatePassword() throws { + func testUpdateKey() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) let coins = [CoinType.ethereum, .callisto, .poanetwork] let wallet = try keyStore.createWallet(name: "name", password: "password", coins: coins) @@ -124,21 +113,6 @@ class KeyStoreTests: XCTestCase { XCTAssertEqual(savedWallet.key.name, "name") } - func testUpdatePasswordTON() throws { - let keyStore = try KeyStore(keyDirectory: keyDirectory) - - try keyStore.update(wallet: keyStore.tonWallet!, password: "password", newPassword: "testpassword") - - let savedKeyStore = try KeyStore(keyDirectory: keyDirectory) - let savedWallet = savedKeyStore.tonWallet! - - let tonMnemonic = savedWallet.key.decryptTONMnemonic(password: Data("testpassword".utf8))! - - XCTAssertEqual(savedWallet.accounts.count, 1) - XCTAssert(TONWallet.isValidMnemonic(mnemonic: tonMnemonic, passphrase: nil)) - XCTAssertEqual(savedWallet.key.name, "name") - } - func testUpdateName() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) let coins = [CoinType.ethereum, .callisto, .poanetwork] @@ -181,17 +155,6 @@ class KeyStoreTests: XCTestCase { XCTAssertEqual(savedWallet.accounts.count, 1) XCTAssertEqual(savedWallet.accounts[0].coin, coins.last) } - - func testRemoveTONAccounts() throws { - let keyStore = try KeyStore(keyDirectory: keyDirectory) - - _ = try keyStore.removeAccounts(wallet: keyStore.tonWallet!, coins: [.ton], password: "password") - - let savedKeyStore = try KeyStore(keyDirectory: keyDirectory) - let savedWallet = savedKeyStore.tonWallet! - // The only account should have been removed. - XCTAssertEqual(savedWallet.accounts.count, 0) - } func testDeleteKey() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) @@ -206,13 +169,6 @@ class KeyStoreTests: XCTestCase { try keyStore.delete(wallet: wallet, password: "password") XCTAssertNil(keyStore.hdWallet) } - - func testDeleteTONWallet() throws { - let keyStore = try KeyStore(keyDirectory: keyDirectory) - let wallet = keyStore.tonWallet! - try keyStore.delete(wallet: wallet, password: "password") - XCTAssertNil(keyStore.tonWallet) - } func testImportKey() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) @@ -276,60 +232,7 @@ class KeyStoreTests: XCTestCase { XCTAssertEqual(wallet.accounts.count, 1) XCTAssertNotNil(keyStore.hdWallet) } - - func testImportTONWallet() throws { - let keyStore = try KeyStore(keyDirectory: keyDirectory) - let wallet = try keyStore.importTON(tonMnemonic: tonMnemonic, name: "name", encryptPassword: "newPassword", coin: .ton, encryption: .aes128Ctr) - let storedData = wallet.key.decryptMnemonic(password: Data("newPassword".utf8)) - XCTAssertNotNil(storedData) - XCTAssertEqual(wallet.accounts.count, 1) - } - - func testImportTONWalletJSON() throws { - let json = """ - { - "activeAccounts": [ - { - "address": "UQByxuJBNpeC4QjGdgnfeO8oM4G9srUG1FyIGmqX3YnVQ4p1", - "coin": 607, - "derivationPath": "", - "publicKey": "3bab20a5f77e277e39443fc16c64e0479b4a9db542bf9e11c638598384c853f1" - } - ], - "crypto": { - "cipher": "aes-128-ctr", - "cipherparams": { - "iv": "d7fdc3fa7a09e1163094d14a557ed1b6" - }, - "ciphertext": "58017dfbee52c6f22fa1eed323cda21e3413de8da48083e09be9a826bc4f9c184f14e7a47ffcc5f539dc94435d1742dfdc0785e612d039c4858777da9dcd92960580cb9c755434832d94f88b8f562a23ad16f7b6165bbd709a701b3ec46efbe5f6aa858000ce19641abcb7d20475fa1e9cfed5f2f5dae7c76d6496d54bd6db593050617c85c0f6bc3cf8fac89b671d53924202037e1c0e1ecd521492e5", - "kdf": "scrypt", - "kdfparams": { - "dklen": 32, - "n": 16384, - "p": 4, - "r": 8, - "salt": "" - }, - "mac": "ddc49eecdec579021cd18526982ec9519a82f8c39ff20aa7d9aa7f02d5ebd36e" - }, - "id": "e11e5404-73d5-416c-b957-a65164fb0171", - "name": "name", - "type": "ton-mnemonic", - "version": 3 - } - """ - - let password = "password" - let keyStore = try KeyStore(keyDirectory: keyDirectory) - - let wallet = try keyStore.import(json: Data(json.utf8), name: "newName", password: "password", newPassword: "newPassword", coins: [.ton]) - XCTAssertEqual(wallet.accounts.first!.address, "UQByxuJBNpeC4QjGdgnfeO8oM4G9srUG1FyIGmqX3YnVQ4p1") - let actualMnemonic = try keyStore.exportTONMnemonic(wallet: wallet, password: "newPassword") - let expectedMnemonic = "slogan train glide measure mercy dizzy when satoshi vote change length pluck token walnut actress hollow guard soup solve rival summer vicious anxiety device" - XCTAssertEqual(actualMnemonic, expectedMnemonic) - } - func testImportJSON() throws { let expected = """ { @@ -429,14 +332,6 @@ class KeyStoreTests: XCTestCase { XCTAssertEqual(mnemonic, exported) } - - func testExportTONMnemonic() throws { - let keyStore = try KeyStore(keyDirectory: keyDirectory) - let wallet = try keyStore.importTON(tonMnemonic: tonMnemonic, name: "name", encryptPassword: "newPassword", coin: .ton) - let exported = try keyStore.exportTONMnemonic(wallet: wallet, password: "newPassword") - - XCTAssertEqual(tonMnemonic, exported) - } func testFileName() { let keyStore = try! KeyStore(keyDirectory: keyDirectory) diff --git a/swift/Tests/Keystore/WalletTests.swift b/swift/Tests/Keystore/WalletTests.swift index 48cf71436c5..00815ed0271 100755 --- a/swift/Tests/Keystore/WalletTests.swift +++ b/swift/Tests/Keystore/WalletTests.swift @@ -25,54 +25,4 @@ class WalletTests: XCTestCase { let wallet = Wallet(keyURL: url, key: key) XCTAssertEqual(wallet.identifier, "UTC--2018-07-23T15-42-07.380692005-42000--6E199F01-FA96-4ADF-9A4B-36EE4B1E08C7") } - - func testPrivateKeyGetAccount() throws { - let url = Bundle(for: type(of: self)).url(forResource: "key", withExtension: "json")! - let key = StoredKey.load(path: url.path)! - let wallet = Wallet(keyURL: url, key: key) - - // The wallet already contains `Ethereum` account. No exception expected. - let ethAccount = try wallet.getAccount(password: "testpassword", coin: .ethereum) - XCTAssertEqual(ethAccount.address, "0x008AeEda4D805471dF9b2A5B0f38A0C3bCBA786b") - - let accounts1 = try wallet.getAccounts(password: "testpassword", coins: [.ethereum]) - XCTAssertEqual(accounts1.count, 1) - - // Should fail because `getAccount` currently doesn't support creating a new account from PrivateKey. - XCTAssertThrowsError(try wallet.getAccount(password: "testpassword", coin: .bitcoin)) - - // Should return only `Ethereum` account. - let accounts2 = try wallet.getAccounts(password: "testpassword", coins: [.bitcoin, .ethereum]) - XCTAssertEqual(accounts2.count, 1) - } - - func testTONWalletGetAccount() throws { - let url = Bundle(for: type(of: self)).url(forResource: "ton_wallet", withExtension: "json")! - let key = StoredKey.load(path: url.path)! - let wallet = Wallet(keyURL: url, key: key) - - // The wallet already contains `TON` account. No exception expected. - let tonAccount = try wallet.getAccount(password: "password", coin: .ton) - XCTAssertEqual(tonAccount.address, "UQBlm676c6vy6Q9Js732pvf3ivfmIkVc0MVDQy-F6NAFJd4k") - - let accounts1 = try wallet.getAccounts(password: "password", coins: [.ton]) - XCTAssertEqual(accounts1.count, 1) - - // Should fail because `getAccount` currently doesn't support creating a new account from PrivateKey. - XCTAssertThrowsError(try wallet.getAccount(password: "password", coin: .ethereum)) - - // Should return only `TON` account. - let accounts2 = try wallet.getAccounts(password: "password", coins: [.bitcoin, .ton]) - XCTAssertEqual(accounts2.count, 1) - } - - func testTONWalletGetPrivateKey() throws { - let url = Bundle(for: type(of: self)).url(forResource: "ton_wallet", withExtension: "json")! - let key = StoredKey.load(path: url.path)! - let wallet = Wallet(keyURL: url, key: key) - - // The wallet already contains `TON` account. No exception expected. - let privateKey = try wallet.privateKey(password: "password", coin: .ton) - XCTAssertEqual(privateKey.data.hexString, "cdcea50b87d3f1ca859e7b2bdf9a5339b7b6804b5c70ac85198829f9607dc43b") - } } diff --git a/tests/chains/TheOpenNetwork/TWTONWalletTests.cpp b/tests/chains/TheOpenNetwork/TWTONWalletTests.cpp index b77b47773be..b346ee5568b 100644 --- a/tests/chains/TheOpenNetwork/TWTONWalletTests.cpp +++ b/tests/chains/TheOpenNetwork/TWTONWalletTests.cpp @@ -9,25 +9,6 @@ namespace TW::TheOpenNetwork::tests { -TEST(TWTONWallet, IsValidMnemonic) { - const auto mnemonic = STRING("sight shed side garbage illness clean health wet all win bench wide exist find galaxy drift task suggest portion fresh valve crime radar combine"); - const auto emptyPassphrase = STRING(""); - const auto invalidPassphrase = STRING("Expected empty passphrase"); - EXPECT_TRUE(TWTONWalletIsValidMnemonic(mnemonic.get(), nullptr)); - EXPECT_TRUE(TWTONWalletIsValidMnemonic(mnemonic.get(), emptyPassphrase.get())); - EXPECT_FALSE(TWTONWalletIsValidMnemonic(mnemonic.get(), invalidPassphrase.get())); -} - -TEST(TWTONWallet, MnemonicToPrivateKey) { - const auto mnemonic = STRING("sight shed side garbage illness clean health wet all win bench wide exist find galaxy drift task suggest portion fresh valve crime radar combine"); - const auto wallet = WRAP(TWTONWallet, TWTONWalletCreateWithMnemonic(mnemonic.get(), nullptr)); - EXPECT_TRUE(wallet); - const auto key = WRAP(TWPrivateKey, TWTONWalletGetKey(wallet.get())); - EXPECT_TRUE(key); - const auto keyBytes = WRAPD(TWPrivateKeyData(key.get())); - assertHexEqual(keyBytes, "b471884e691a9f5bb641b14f33bb9e555f759c24e368c4c0d997db3a60704220"); -} - TEST(TWTONWallet, BuildV4R2StateInit) { auto publicKeyBytes = DATA("f229a9371fa7c2108b3d90ea22c9be705ff5d0cfeaee9cbb9366ff0171579357"); auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(publicKeyBytes.get(), TWPublicKeyTypeED25519)); diff --git a/tests/common/Keystore/Data/ton-wallet.json b/tests/common/Keystore/Data/ton-wallet.json deleted file mode 100644 index 3ed1c08b087..00000000000 --- a/tests/common/Keystore/Data/ton-wallet.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "activeAccounts": [ - { - "address": "UQBlm676c6vy6Q9Js732pvf3ivfmIkVc0MVDQy-F6NAFJd4k", - "coin": 607, - "derivationPath": "", - "publicKey": "9016f03f9cfa4e183707761f25407e0e1975194a33a56b3e8d2c26f2438fa3d1" - } - ], - "crypto": { - "cipher": "aes-128-ctr", - "cipherparams": { - "iv": "459bbf197fe8e1cdfb949fc516257238" - }, - "ciphertext": "efbda7237b41d2340a4b4c7f0ab6a103e31f7a2b8148137abc815a0d346c3ae851d842ad068ad0218ef9f81647f41e52f7b056d568a314433a0b7980601f96e8974a071b5ddedd50079eebdb9281a8d84a0b9d4649ef3125f6605e303a78c0a4ed67556e3cd4e88b78b120267544eb44a912c92516562acb9782e0ea989cb50bce5948e2dfa26107053caf838af096ce47357061a2b49654", - "kdf": "scrypt", - "kdfparams": { - "dklen": 32, - "n": 16384, - "p": 4, - "r": 8, - "salt": "" - }, - "mac": "be9e9a26076334683799c2dbe22bfb2d9f2ff92ad130aa61b4687b392e7b98e6" - }, - "id": "f7a2172e-fb7a-427a-8526-99779fc47c0a", - "name": "Test TON Account", - "type": "ton-mnemonic", - "version": 3 -} diff --git a/tests/common/Keystore/StoredKeyConstants.h b/tests/common/Keystore/StoredKeyConstants.h deleted file mode 100644 index 93ce7404702..00000000000 --- a/tests/common/Keystore/StoredKeyConstants.h +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#pragma once - -extern std::string TESTS_ROOT; - -namespace TW::Keystore::tests { - -const auto gName = "name"; -const auto gPasswordString = "password"; -const auto gPassword = TW::data(std::string(gPasswordString)); - -inline std::string testDataPath(const char *subpath) { - return TESTS_ROOT + "/common/Keystore/Data/" + subpath; -} - -} // namespace TW::Keystore::tests diff --git a/tests/common/Keystore/StoredKeyTONTests.cpp b/tests/common/Keystore/StoredKeyTONTests.cpp deleted file mode 100644 index 6be77a508d3..00000000000 --- a/tests/common/Keystore/StoredKeyTONTests.cpp +++ /dev/null @@ -1,172 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#include "Keystore/StoredKey.h" - -#include "Coin.h" -#include "Data.h" -#include "HexCoding.h" -#include "StoredKeyConstants.h" - -#include -#include - -namespace TW::Keystore::tests { - -using namespace std; - -const auto gTONMnemonic = "protect drill sugar gallery note admit input wrist chicken swarm scheme hedgehog orbit ritual glove ski buddy slogan fragile sun delay toy lucky require"; -// The following TON mnemonic requires a passphrase to be used that we don't support right now. -const auto gTONInvalidMnemonic = "mimic close sibling chair shuffle goat fashion chunk increase tennis scene ceiling divert cross treat happy soccer sample umbrella oyster advance quality perfect call"; -const auto gTONPrivateKey = "cdcea50b87d3f1ca859e7b2bdf9a5339b7b6804b5c70ac85198829f9607dc43b"; -const auto gTONPublicKey = "9016f03f9cfa4e183707761f25407e0e1975194a33a56b3e8d2c26f2438fa3d1"; -const auto gBounceableAddress = "EQBlm676c6vy6Q9Js732pvf3ivfmIkVc0MVDQy-F6NAFJYPh"; -const auto gNonBounceableAddress = "UQBlm676c6vy6Q9Js732pvf3ivfmIkVc0MVDQy-F6NAFJd4k"; - -TEST(StoredKeyTON, CreateWithTonMnemonicAddDefault) { - auto key = StoredKey::createWithTonMnemonicAddDefaultAddress(gName, gPassword, TWCoinTypeTON, gTONMnemonic); - EXPECT_EQ(key.type, StoredKeyType::tonMnemonicPhrase); - const Data& mnemo2Data = key.payload.decrypt(gPassword); - EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gTONMnemonic)); - EXPECT_EQ(key.accounts.size(), 1ul); - EXPECT_EQ(key.accounts[0].coin, TWCoinTypeTON); - EXPECT_EQ(key.accounts[0].address, "UQBlm676c6vy6Q9Js732pvf3ivfmIkVc0MVDQy-F6NAFJd4k"); - EXPECT_EQ(key.accounts[0].publicKey, gTONPublicKey); - EXPECT_EQ(key.accounts[0].extendedPublicKey, ""); - EXPECT_EQ(key.accounts[0].derivationPath.string(), ""); - EXPECT_EQ(key.accounts[0].derivation, TWDerivationDefault); - EXPECT_EQ(hex(key.privateKey(TWCoinTypeTON, gPassword).bytes), gTONPrivateKey); - EXPECT_EQ(key.payload.params.cipher(), "aes-128-ctr"); - - const auto json = key.json(); - EXPECT_EQ(json["name"], gName); - EXPECT_EQ(json["type"], "ton-mnemonic"); - EXPECT_EQ(json["version"], 3); -} - -TEST(StoredKeyTON, CreateWithTonMnemonicInvalid) { - EXPECT_THROW( - StoredKey::createWithTonMnemonicAddDefaultAddress(gName, gPassword, TWCoinTypeTON, gTONInvalidMnemonic), - std::invalid_argument - ); -} - -TEST(StoredKeyTON, CreateWithTonMnemonicInvalidCoinType) { - EXPECT_THROW( - StoredKey::createWithTonMnemonicAddDefaultAddress(gName, gPassword, TWCoinTypeBitcoin, gTONMnemonic), - std::invalid_argument - ); -} - -TEST(StoredKeyTON, CreateWithMnemonicAddDefaultAddressAes256) { - auto key = StoredKey::createWithTonMnemonicAddDefaultAddress(gName, gPassword, TWCoinTypeTON, gTONMnemonic, TWStoredKeyEncryptionAes256Ctr); - EXPECT_EQ(key.type, StoredKeyType::tonMnemonicPhrase); - const Data& mnemo2Data = key.payload.decrypt(gPassword); - EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gTONMnemonic)); - EXPECT_EQ(key.accounts.size(), 1ul); - EXPECT_EQ(key.accounts[0].coin, TWCoinTypeTON); - EXPECT_EQ(key.accounts[0].address, "UQBlm676c6vy6Q9Js732pvf3ivfmIkVc0MVDQy-F6NAFJd4k"); - EXPECT_EQ(key.payload.params.cipher(), "aes-256-ctr"); -} - -TEST(StoredKeyTON, HDWalletNotSupported) { - auto key = StoredKey::createWithTonMnemonicAddDefaultAddress(gName, gPassword, TWCoinTypeTON, gTONMnemonic); - EXPECT_THROW(key.wallet(gPassword), std::invalid_argument); -} - -TEST(StoredKeyTON, AddRemoveAccount) { - auto key = StoredKey::createWithTonMnemonicAddDefaultAddress(gName, gPassword, TWCoinTypeTON, gTONMnemonic); - EXPECT_EQ(key.accounts.size(), 1ul); - - // Add another dummy (doesn't belong to the mnemonic) TON account. - { - const DerivationPath derivationPath {}; - const auto publicKey = "b191d35f81aa8b144aa91c90a6b887e0b165ad9c2933b1c5266eb5c4e8bea241"; - const auto extendedPublicKey = ""; - key.addAccount("UQDSRYDMMez8BdcOuPEiaR6aJZpO6EjlIwmOBFn14mMbnRah", TWCoinTypeTON, TWDerivationDefault, derivationPath, publicKey, extendedPublicKey); - EXPECT_EQ(key.accounts.size(), 2ul); - } - - key.removeAccount(TWCoinTypeTON, TWDerivationDefault); - EXPECT_EQ(key.accounts.size(), 0ul); -} - -TEST(StoredKeyTON, FixAddressHasNoEffect) { - // `StoredKey::createWithTonMnemonicAddDefaultAddress` derives the correct address. - auto key = StoredKey::createWithTonMnemonicAddDefaultAddress(gName, gPassword, TWCoinTypeTON, gTONMnemonic); - EXPECT_EQ(key.accounts.size(), 1ul); - - key.fixAddresses(gPassword); - EXPECT_EQ(key.accounts[0].address, gNonBounceableAddress); -} - -TEST(StoredKeyTON, FixAddress) { - auto key = StoredKey::createWithTonMnemonic(gName, gPassword, gTONMnemonic); - EXPECT_EQ(key.accounts.size(), 0ul); - - // Add an account with an invalid address manually. - { - const DerivationPath derivationPath {}; - const auto publicKey = gTONPublicKey; - const auto extendedPublicKey = ""; - const auto invalidAddress = "INVALID_ADDRESS"; - key.addAccount(invalidAddress, TWCoinTypeTON, TWDerivationDefault, derivationPath, publicKey, extendedPublicKey); - EXPECT_EQ(key.accounts.size(), 1ul); - } - - key.fixAddresses(gPassword); - EXPECT_EQ(key.accounts.size(), 1ul); - EXPECT_EQ(key.accounts[0].coin, TWCoinTypeTON); - // Address should be fixed to a valid non-bounceable address. - EXPECT_EQ(key.accounts[0].address, gNonBounceableAddress); -} - -TEST(StoredKeyTON, UpdateAddress) { - auto key = StoredKey::createWithTonMnemonic(gName, gPassword, gTONMnemonic); - EXPECT_EQ(key.accounts.size(), 0ul); - - // Add an account with a bounceable (EQ) address. - { - const DerivationPath derivationPath {}; - const auto publicKey = gTONPublicKey; - const auto extendedPublicKey = ""; - const auto invalidAddress = gBounceableAddress; - key.addAccount(invalidAddress, TWCoinTypeTON, TWDerivationDefault, derivationPath, publicKey, extendedPublicKey); - EXPECT_EQ(key.accounts.size(), 1ul); - } - - key.updateAddress(TWCoinTypeTON); - EXPECT_EQ(key.accounts.size(), 1ul); - EXPECT_EQ(key.accounts[0].coin, TWCoinTypeTON); - // Address should be fixed to a valid non-bounceable address. - EXPECT_EQ(key.accounts[0].address, gNonBounceableAddress); -} - -TEST(StoredKeyTON, LoadNonexistent) { - ASSERT_THROW(StoredKey::load(testDataPath("nonexistent.json")), invalid_argument); -} - -TEST(StoredKeyTON, LoadTonMnemonic) { - const auto key = StoredKey::load(testDataPath("ton-wallet.json")); - EXPECT_EQ(key.type, StoredKeyType::tonMnemonicPhrase); - EXPECT_EQ(key.id, "f7a2172e-fb7a-427a-8526-99779fc47c0a"); - EXPECT_EQ(key.name, "Test TON Account"); - - const auto data = key.payload.decrypt(gPassword); - const auto mnemonic = string(reinterpret_cast(data.data()), data.size()); - EXPECT_EQ(mnemonic, gTONMnemonic); - - EXPECT_EQ(key.accounts[0].coin, TWCoinTypeTON); - EXPECT_EQ(key.accounts[0].derivationPath.string(), ""); - EXPECT_EQ(key.accounts[0].address, gNonBounceableAddress); - EXPECT_EQ(key.accounts[0].publicKey, gTONPublicKey); -} - -TEST(StoredKeyTON, InvalidPassword) { - const auto key = StoredKey::load(testDataPath("ton-wallet.json")); - - ASSERT_THROW(key.payload.decrypt(TW::data("INVALID PASSWORD")), DecryptionError); -} - -} // namespace TW::Keystore diff --git a/tests/common/Keystore/StoredKeyTests.cpp b/tests/common/Keystore/StoredKeyTests.cpp index a0a98840cb4..02c9bed047b 100644 --- a/tests/common/Keystore/StoredKeyTests.cpp +++ b/tests/common/Keystore/StoredKeyTests.cpp @@ -10,21 +10,28 @@ #include "HexCoding.h" #include "Mnemonic.h" #include "PrivateKey.h" -#include "StoredKeyConstants.h" #include #include +extern std::string TESTS_ROOT; + namespace TW::Keystore::tests { using namespace std; -static const auto gMnemonic = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; -static const TWCoinType coinTypeBc = TWCoinTypeBitcoin; -static const TWCoinType coinTypeBnb = TWCoinTypeBinance; -static const TWCoinType coinTypeBsc = TWCoinTypeSmartChain; -static const TWCoinType coinTypeEth = TWCoinTypeEthereum; -static const TWCoinType coinTypeBscLegacy = TWCoinTypeSmartChainLegacy; +const auto passwordString = "password"; +const auto gPassword = TW::data(string(passwordString)); +const auto gMnemonic = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; +const TWCoinType coinTypeBc = TWCoinTypeBitcoin; +const TWCoinType coinTypeBnb = TWCoinTypeBinance; +const TWCoinType coinTypeBsc = TWCoinTypeSmartChain; +const TWCoinType coinTypeEth = TWCoinTypeEthereum; +const TWCoinType coinTypeBscLegacy = TWCoinTypeSmartChainLegacy; + +const std::string testDataPath(const char* subpath) { + return TESTS_ROOT + "/common/Keystore/Data/" + subpath; +} TEST(StoredKey, CreateWithMnemonic) { auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelDefault); @@ -350,7 +357,6 @@ TEST(StoredKey, LoadLegacyMnemonic) { EXPECT_EQ(key.id, "629aad29-0b22-488e-a0e7-b4219d4f311c"); const auto data = key.payload.decrypt(gPassword); - // In this case, the encrypted mnemonic contains `\0` value at the end. const auto mnemonic = string(reinterpret_cast(data.data())); EXPECT_EQ(mnemonic, "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn back"); diff --git a/tests/interface/TWStoredKeyTests.cpp b/tests/interface/TWStoredKeyTests.cpp index 2376972fa52..ee758923b47 100644 --- a/tests/interface/TWStoredKeyTests.cpp +++ b/tests/interface/TWStoredKeyTests.cpp @@ -38,32 +38,6 @@ struct std::shared_ptr createDefaultStoredKey(TWStoredKeyEncryption return createAStoredKey(TWCoinTypeBitcoin, password.get(), encryption); } -/// Return a StoredKey instance that can be used for further tests. Needs to be deleted at the end. -struct std::shared_ptr createTONStoredKey(TWData* password, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr) { - const auto mnemonic = WRAPS(TWStringCreateWithUTF8Bytes("slim holiday tiny pizza donor egg round three verify post chat social offer mix rack soft loud code option learn this pipe mouse mango")); - const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); - const auto coin = TWCoinTypeTON; - - return WRAP(TWStoredKey, TWStoredKeyImportTONWalletWithEncryption(mnemonic.get(), name.get(), password, coin, encryption)); -} - -Data readFileData(const std::string& path) { - // read contents of file - ifstream ifs(path); - // get length of file: - ifs.seekg (0, ifs.end); - auto length = ifs.tellg(); - ifs.seekg (0, ifs.beg); - EXPECT_TRUE(length > 20); - - Data data(length); - size_t idx = 0; - // read the slow way, ifs.read gave some false warnings with codacy - while (!ifs.eof() && idx < static_cast(length)) { char c = ifs.get(); data[idx++] = (uint8_t)c; } - - return data; -} - TEST(TWStoredKey, loadPBKDF2Key) { const auto filename = WRAPS(TWStringCreateWithUTF8Bytes((TESTS_ROOT + "/common/Keystore/Data/pbkdf2.json").c_str())); const auto key = WRAP(TWStoredKey, TWStoredKeyLoad(filename.get())); @@ -153,36 +127,6 @@ TEST(TWStoredKey, importHDWalletAES256) { EXPECT_EQ(nokey.get(), nullptr); } -TEST(TWStoredKey, importTONWallet) { - const auto mnemonicStr = "slim holiday tiny pizza donor egg round three verify post chat social offer mix rack soft loud code option learn this pipe mouse mango"; - const auto mnemonic = WRAPS(TWStringCreateWithUTF8Bytes(mnemonicStr)); - const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); - const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); - const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); - const auto coin = TWCoinTypeTON; - const auto key = WRAP(TWStoredKey, TWStoredKeyImportTONWallet(mnemonic.get(), name.get(), password.get(), coin)); - EXPECT_FALSE(TWStoredKeyIsMnemonic(key.get())); - EXPECT_TRUE(TWStoredKeyIsTONMnemonic(key.get())); - - const auto actualMnemonic = WRAPS(TWStoredKeyDecryptTONMnemonic(key.get(), password.get())); - assertStringsEqual(actualMnemonic, mnemonicStr); - - // invalid mnemonic - const auto mnemonicInvalid = WRAPS(TWStringCreateWithUTF8Bytes("_THIS_IS_AN_INVALID_MNEMONIC_")); - const auto nokey = WRAP(TWStoredKey, TWStoredKeyImportTONWallet(mnemonicInvalid.get(), name.get(), password.get(), coin)); - EXPECT_EQ(nokey.get(), nullptr); -} - -TEST(TWStoredKey, importTONWalletAES256) { - const auto mnemonic = WRAPS(TWStringCreateWithUTF8Bytes("slim holiday tiny pizza donor egg round three verify post chat social offer mix rack soft loud code option learn this pipe mouse mango")); - const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); - const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); - const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); - const auto coin = TWCoinTypeTON; - const auto key = WRAP(TWStoredKey, TWStoredKeyImportTONWalletWithEncryption(mnemonic.get(), name.get(), password.get(), coin, TWStoredKeyEncryptionAes256Ctr)); - EXPECT_TRUE(TWStoredKeyIsTONMnemonic(key.get())); -} - TEST(TWStoredKey, addressAddRemove) { const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); @@ -218,15 +162,6 @@ TEST(TWStoredKey, addressAddRemove) { EXPECT_EQ(TWStoredKeyAccount(key.get(), 1001), nullptr); } -/// HDWallet cannot be created from a TON mnemonic. -TEST(TWStoredKey, TONWalletGetWalletNotSupported) { - const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); - const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); - - const auto key = createTONStoredKey(password.get()); - EXPECT_EQ(TWStoredKeyWallet(key.get(), password.get()), nullptr); -} - TEST(TWStoredKey, addressAddRemoveDerivationPath) { const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); @@ -283,26 +218,25 @@ TEST(TWStoredKey, exportJSON) { EXPECT_EQ(TWDataGet(json.get(), 0), '{'); } -TEST(TWStoredKey, TONWalletExportJSON) { - const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); - const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); - - const auto key = createTONStoredKey(password.get()); - - const auto jsonData = WRAPD(TWStoredKeyExportJSON(key.get())); - const auto jsonStr = WRAPS(TWStringCreateWithRawBytes(TWDataBytes(jsonData.get()), TWDataSize(jsonData.get()))); - const auto json = nlohmann::json::parse(string(TWStringUTF8Bytes(jsonStr.get()))); - EXPECT_EQ(json["type"], "ton-mnemonic"); - EXPECT_EQ(json["activeAccounts"].size(), 1ul); -} - TEST(TWStoredKey, storeAndImportJSONAES256) { const auto key = createDefaultStoredKey(TWStoredKeyEncryptionAes256Ctr); const auto outFileName = string(getTestTempDir() + "/TWStoredKey_store.json"); const auto outFileNameStr = WRAPS(TWStringCreateWithUTF8Bytes(outFileName.c_str())); EXPECT_TRUE(TWStoredKeyStore(key.get(), outFileNameStr.get())); - const auto json = readFileData(outFileName); + // read contents of file + ifstream ifs(outFileName); + // get length of file: + ifs.seekg (0, ifs.end); + auto length = ifs.tellg(); + ifs.seekg (0, ifs.beg); + EXPECT_TRUE(length > 20); + + Data json(length); + size_t idx = 0; + // read the slow way, ifs.read gave some false warnings with codacy + while (!ifs.eof() && idx < static_cast(length)) { char c = ifs.get(); json[idx++] = (uint8_t)c; } + const auto key2 = WRAP(TWStoredKey, TWStoredKeyImportJSON(WRAPD(TWDataCreateWithData(&json)).get())); const auto name2 = WRAPS(TWStoredKeyName(key2.get())); EXPECT_EQ(string(TWStringUTF8Bytes(name2.get())), "name"); @@ -313,28 +247,24 @@ TEST(TWStoredKey, storeAndImportJSON) { const auto outFileName = string(getTestTempDir() + "/TWStoredKey_store.json"); const auto outFileNameStr = WRAPS(TWStringCreateWithUTF8Bytes(outFileName.c_str())); EXPECT_TRUE(TWStoredKeyStore(key.get(), outFileNameStr.get())); + //EXPECT_TRUE(filesystem::exists(outFileName)); // some linker issues with filesystem + + // read contents of file + ifstream ifs(outFileName); + // get length of file: + ifs.seekg (0, ifs.end); + auto length = ifs.tellg(); + ifs.seekg (0, ifs.beg); + EXPECT_TRUE(length > 20); - const auto json = readFileData(outFileName); - const auto key2 = WRAP(TWStoredKey, TWStoredKeyImportJSON(WRAPD(TWDataCreateWithData(&json)).get())); - const auto name2 = WRAPS(TWStoredKeyName(key2.get())); - EXPECT_EQ(string(TWStringUTF8Bytes(name2.get())), "name"); - EXPECT_TRUE(TWStoredKeyIsMnemonic(key2.get())); -} - -TEST(TWStoredKey, TONWalletStoreAndImport) { - const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); - const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); - - const auto key = createTONStoredKey(password.get()); - const auto outFileName = string(getTestTempDir() + "/TWStoredKey_storeTON.json"); - const auto outFileNameStr = WRAPS(TWStringCreateWithUTF8Bytes(outFileName.c_str())); - EXPECT_TRUE(TWStoredKeyStore(key.get(), outFileNameStr.get())); + Data json(length); + size_t idx = 0; + // read the slow way, ifs.read gave some false warnings with codacy + while (!ifs.eof() && idx < static_cast(length)) { char c = ifs.get(); json[idx++] = (uint8_t)c; } - const auto json = readFileData(outFileName); const auto key2 = WRAP(TWStoredKey, TWStoredKeyImportJSON(WRAPD(TWDataCreateWithData(&json)).get())); const auto name2 = WRAPS(TWStoredKeyName(key2.get())); EXPECT_EQ(string(TWStringUTF8Bytes(name2.get())), "name"); - EXPECT_TRUE(TWStoredKeyIsTONMnemonic(key2.get())); } TEST(TWStoredKey, importJsonInvalid) { @@ -466,10 +396,10 @@ TEST(TWStoredKey, getWalletPasswordInvalid) { const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); - + const auto invalidString = WRAPS(TWStringCreateWithUTF8Bytes("_THIS_IS_INVALID_PASSWORD_")); const auto passwordInvalid = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(invalidString.get())), TWStringSize(invalidString.get()))); - + auto key = WRAP(TWStoredKey, TWStoredKeyCreate(name.get(), password.get())); ASSERT_NE(WRAP(TWHDWallet, TWStoredKeyWallet(key.get(), password.get())).get(), nullptr); ASSERT_EQ(WRAP(TWHDWallet, TWStoredKeyWallet(key.get(), passwordInvalid.get())).get(), nullptr); diff --git a/wasm/src/keystore/default-impl.ts b/wasm/src/keystore/default-impl.ts index 04ecb4bae87..6da4ae6c57a 100644 --- a/wasm/src/keystore/default-impl.ts +++ b/wasm/src/keystore/default-impl.ts @@ -104,31 +104,6 @@ export class Default implements Types.IKeyStore { }); } - importTON( - tonMnemonic: string, - name: string, - password: string, - coin: CoinType, - encryption: StoredKeyEncryption - ): Promise { - return new Promise((resolve, reject) => { - const { StoredKey, TONWallet } = this.core; - - const passphrase = ""; - if (!TONWallet.isValidMnemonic(tonMnemonic, passphrase)) { - throw Types.Error.InvalidMnemonic; - } - - let pass = Buffer.from(password); - let storedKey = StoredKey.importTONWalletWithEncryption(tonMnemonic, name, pass, coin, encryption); - let wallet = this.mapWallet(storedKey); - storedKey.delete(); - this.importWallet(wallet) - .then(() => resolve(wallet)) - .catch((error) => reject(error)); - }); - } - addAccounts( id: string, password: string, @@ -154,23 +129,11 @@ export class Default implements Types.IKeyStore { ): Promise { return this.load(id).then((wallet) => { let storedKey = this.mapStoredKey(wallet); + let hdWallet = storedKey.wallet(Buffer.from(password)); let coin = (this.core.CoinType as any).values["" + account.coin]; - - let privateKey: PrivateKey; - switch (wallet.type) { - // In case of BIP39 mnemonic, we should use the custom derivation path. - case Types.WalletType.Mnemonic: - let hdWallet = storedKey.wallet(Buffer.from(password)); - privateKey = hdWallet.getKey(coin, account.derivationPath); - hdWallet.delete(); - break; - // Otherwise, use the default implementation. - default: - privateKey = storedKey.privateKey(coin, Buffer.from(password)); - break; - } - + let privateKey = hdWallet.getKey(coin, account.derivationPath); storedKey.delete(); + hdWallet.delete(); return privateKey; }); } @@ -186,9 +149,6 @@ export class Default implements Types.IKeyStore { case Types.WalletType.PrivateKey: value = storedKey.decryptPrivateKey(Buffer.from(password)); break; - case Types.WalletType.TonMnemonic: - value = storedKey.decryptTONMnemonic(Buffer.from(password)); - break; default: throw Types.Error.InvalidJSON; } diff --git a/wasm/src/keystore/types.ts b/wasm/src/keystore/types.ts index ecdd3280db1..bccb05ef58a 100644 --- a/wasm/src/keystore/types.ts +++ b/wasm/src/keystore/types.ts @@ -9,7 +9,6 @@ export enum WalletType { PrivateKey = "privateKey", WatchOnly = "watchOnly", Hardware = "hardware", - TonMnemonic = "ton-mnemonic" } export enum Error { @@ -69,15 +68,6 @@ export interface IKeyStore { // Import a Wallet object directly importWallet(wallet: Wallet): Promise; - // Import a TON wallet by 24-words mnemonic, name, password and initial active account (from coinType) - importTON( - tonMnemonic: string, - name: string, - password: string, - coin: CoinType, - encryption: StoredKeyEncryption - ): Promise; - // Add active accounts to a wallet by wallet id, password, coin addAccounts(id: string, password: string, coins: CoinType[]): Promise; @@ -88,7 +78,7 @@ export interface IKeyStore { account: ActiveAccount ): Promise; - // Delete a wallet by wallet id and password + // Delete a wallet by wallet id and password.aq1aq delete(id: string, password: string): Promise; // Export a wallet by wallet id and password, returns mnemonic or private key diff --git a/wasm/tests/KeyStore+extension.test.ts b/wasm/tests/KeyStore+extension.test.ts index a44b7cb3504..9cc1dcf5c22 100644 --- a/wasm/tests/KeyStore+extension.test.ts +++ b/wasm/tests/KeyStore+extension.test.ts @@ -64,51 +64,6 @@ describe("KeyStore", async () => { }); }).timeout(10000); - it("test ExtensionStorage TONWallet", async () => { - const { CoinType, HexCoding, StoredKeyEncryption } = globalThis.core; - const tonMnemonic = globalThis.tonMnemonic as string; - const password = globalThis.password as string; - - const walletIdsKey = "all-wallet-ids"; - const storage = new KeyStore.ExtensionStorage( - walletIdsKey, - new ChromeStorageMock() - ); - const keystore = new KeyStore.Default(globalThis.core, storage); - - const wallet = await keystore.importTON(tonMnemonic, "Coolton", password, CoinType.ton, StoredKeyEncryption.aes128Ctr); - - assert.equal(wallet.name, "Coolton"); - assert.equal(wallet.type, "ton-mnemonic"); - assert.equal(wallet.version, 3); - - const account = wallet.activeAccounts[0]; - const key = await keystore.getKey(wallet.id, password, account); - - assert.equal( - HexCoding.encode(key.data()), - "0x859cd74ab605afb7ce9f5316a1f6d59217a130b75b494efd249913be874c9d46" - ); - assert.equal(account.address, "UQDdB2lMwYM9Gxc-ln--Tu8cz-TYksQxYuUsMs2Pd4cHerYz"); - assert.isUndefined(account.extendedPublicKey); - assert.equal( - account.publicKey, - "c9af50596bd5c1c5a15fb32bef8d4f1ee5244b287aea1f49f6023a79f9b2f055" - ); - - assert.isTrue(await keystore.hasWallet(wallet.id)); - assert.isFalse(await keystore.hasWallet("invalid-id")); - - const exported = await keystore.export(wallet.id, password); - assert.equal(exported, tonMnemonic); - - const wallets = await keystore.loadAll(); - - await wallets.forEach((w) => { - keystore.delete(w.id, password); - }); - }).timeout(10000); - it("test ExtensionStorage AES256", async () => { const { CoinType, HexCoding, StoredKeyEncryption } = globalThis.core; const mnemonic = globalThis.mnemonic as string; diff --git a/wasm/tests/setup.test.ts b/wasm/tests/setup.test.ts index 98ede91486e..54d6664beee 100644 --- a/wasm/tests/setup.test.ts +++ b/wasm/tests/setup.test.ts @@ -4,8 +4,6 @@ import { initWasm } from "../dist"; before(async () => { globalThis.mnemonic = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; - globalThis.tonMnemonic = - "laundry myself fitness beyond prize piano match acid vacuum already abandon dance occur pause grocery company inject excuse weasel carpet fog grunt trick spike"; globalThis.password = "password"; globalThis.core = await initWasm(); }); From 8b2cdb7e74de4379a920c16b019804381064e8d8 Mon Sep 17 00:00:00 2001 From: Viacheslav Kulish <32914239+vcoolish@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:30:52 -0800 Subject: [PATCH 09/23] Fix JVM synchronization issue (#4218) * Fix Java JVM leak * clean * apply to jni * [Misc]: Upgrade Rust toolchain to `nightly-2025-01-16` * [Misc]: Fix Clippy warnings --------- Co-authored-by: Satoshi Otomakan --- jni/java/wallet/core/java/GenericPhantomReference.java | 3 ++- .../kotlin/com/trustwallet/core/GenericPhantomReference.kt | 3 ++- rust/chains/tw_solana/src/transaction/versioned.rs | 2 +- rust/frameworks/tw_ton_sdk/src/boc/binary_writer.rs | 2 +- rust/frameworks/tw_ton_sdk/src/boc/raw.rs | 2 +- rust/tw_any_coin/src/wallet_connect_request.rs | 4 +--- rust/tw_encoding/src/hex.rs | 2 +- rust/tw_evm/src/message/eip712/message_types.rs | 2 +- rust/tw_misc/src/test_utils/json.rs | 4 ++-- tools/install-rust-dependencies | 2 +- 10 files changed, 13 insertions(+), 13 deletions(-) diff --git a/jni/java/wallet/core/java/GenericPhantomReference.java b/jni/java/wallet/core/java/GenericPhantomReference.java index 1c5ee46dc76..ef7808b902e 100644 --- a/jni/java/wallet/core/java/GenericPhantomReference.java +++ b/jni/java/wallet/core/java/GenericPhantomReference.java @@ -4,12 +4,13 @@ import java.lang.ref.ReferenceQueue; import java.util.Set; import java.util.HashSet; +import java.util.Collections; public class GenericPhantomReference extends PhantomReference { private final long nativeHandle; private final OnDeleteCallback onDeleteCallback; - private static final Set references = new HashSet<>(); + private static final Set references = Collections.synchronizedSet(new HashSet<>()); private static final ReferenceQueue queue = new ReferenceQueue<>(); static { diff --git a/kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/GenericPhantomReference.kt b/kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/GenericPhantomReference.kt index b6ca8dd3653..0e2ffdd5b8f 100644 --- a/kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/GenericPhantomReference.kt +++ b/kotlin/wallet-core-kotlin/src/commonAndroidJvmMain/kotlin/com/trustwallet/core/GenericPhantomReference.kt @@ -2,6 +2,7 @@ package com.trustwallet.core import java.lang.ref.PhantomReference import java.lang.ref.ReferenceQueue +import java.util.Collections internal class GenericPhantomReference private constructor( referent: Any, @@ -10,7 +11,7 @@ internal class GenericPhantomReference private constructor( ) : PhantomReference(referent, queue) { companion object { - private val references: MutableSet = HashSet() + private val references: MutableSet = Collections.synchronizedSet(HashSet()) private val queue: ReferenceQueue = ReferenceQueue() init { diff --git a/rust/chains/tw_solana/src/transaction/versioned.rs b/rust/chains/tw_solana/src/transaction/versioned.rs index 18b81a81576..a43c73d4380 100644 --- a/rust/chains/tw_solana/src/transaction/versioned.rs +++ b/rust/chains/tw_solana/src/transaction/versioned.rs @@ -189,7 +189,7 @@ impl<'de> Deserialize<'de> for MessagePrefix { { struct PrefixVisitor; - impl<'de> Visitor<'de> for PrefixVisitor { + impl Visitor<'_> for PrefixVisitor { type Value = MessagePrefix; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 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 688d71f4e18..91d75f4430b 100644 --- a/rust/frameworks/tw_ton_sdk/src/boc/binary_writer.rs +++ b/rust/frameworks/tw_ton_sdk/src/boc/binary_writer.rs @@ -54,7 +54,7 @@ impl BinaryWriter { } else { self.write_bytes(&data[..data_len - 1])?; let last_byte = data[data_len - 1]; - let l = last_byte | 1 << (8 - rest_bits - 1); + let l = last_byte | (1 << (8 - rest_bits - 1)); self.write(8, l)?; } diff --git a/rust/frameworks/tw_ton_sdk/src/boc/raw.rs b/rust/frameworks/tw_ton_sdk/src/boc/raw.rs index 3b02a02d415..8a210da9006 100644 --- a/rust/frameworks/tw_ton_sdk/src/boc/raw.rs +++ b/rust/frameworks/tw_ton_sdk/src/boc/raw.rs @@ -263,7 +263,7 @@ fn write_raw_cell( if !full_bytes { writer.write_bytes(&data[..data_len_bytes - 1])?; let last_byte = data[data_len_bytes - 1]; - let l = last_byte | 1 << (8 - padding_bits - 1); + let l = last_byte | (1 << (8 - padding_bits - 1)); writer.write(8, l)?; } else { writer.write_bytes(data)?; diff --git a/rust/tw_any_coin/src/wallet_connect_request.rs b/rust/tw_any_coin/src/wallet_connect_request.rs index 22099edad58..77867b48925 100644 --- a/rust/tw_any_coin/src/wallet_connect_request.rs +++ b/rust/tw_any_coin/src/wallet_connect_request.rs @@ -16,8 +16,6 @@ impl WalletConnectRequest { #[inline] pub fn parse(coin: CoinType, input: &[u8]) -> SigningResult { let (ctx, entry) = coin_dispatcher(coin)?; - entry - .wallet_connect_parse_request(&ctx, input) - .map_err(SigningError::from) + entry.wallet_connect_parse_request(&ctx, input) } } diff --git a/rust/tw_encoding/src/hex.rs b/rust/tw_encoding/src/hex.rs index 5db6e8038a3..2d683c60e23 100644 --- a/rust/tw_encoding/src/hex.rs +++ b/rust/tw_encoding/src/hex.rs @@ -30,7 +30,7 @@ pub trait DecodeHex { fn decode_hex(&self) -> FromHexResult; } -impl<'a> DecodeHex for &'a str { +impl DecodeHex for &str { fn decode_hex(&self) -> FromHexResult { decode(self) } diff --git a/rust/tw_evm/src/message/eip712/message_types.rs b/rust/tw_evm/src/message/eip712/message_types.rs index 4a0d8f0aeae..85321686b15 100644 --- a/rust/tw_evm/src/message/eip712/message_types.rs +++ b/rust/tw_evm/src/message/eip712/message_types.rs @@ -38,7 +38,7 @@ pub struct CustomTypeBuilder<'a> { type_properties: &'a mut Vec, } -impl<'a> CustomTypeBuilder<'a> { +impl CustomTypeBuilder<'_> { pub fn add_property(&mut self, name: &str, property_type: PropertyType) -> &mut Self { self.type_properties.push(Property { name: name.to_string(), diff --git a/rust/tw_misc/src/test_utils/json.rs b/rust/tw_misc/src/test_utils/json.rs index 06da9d7ed63..93f5fa826f7 100644 --- a/rust/tw_misc/src/test_utils/json.rs +++ b/rust/tw_misc/src/test_utils/json.rs @@ -16,7 +16,7 @@ impl ToJson for Json { } } -impl<'a> ToJson for Cow<'a, str> { +impl ToJson for Cow<'_, str> { #[track_caller] fn to_json(&self) -> Json { self.as_ref().to_json() @@ -30,7 +30,7 @@ impl ToJson for String { } } -impl<'a> ToJson for &'a str { +impl ToJson for &str { #[track_caller] fn to_json(&self) -> Json { serde_json::from_str(self).expect("Error on deserializing JSON from string") diff --git a/tools/install-rust-dependencies b/tools/install-rust-dependencies index 1b12239c010..5ab14b69151 100755 --- a/tools/install-rust-dependencies +++ b/tools/install-rust-dependencies @@ -2,7 +2,7 @@ set -e -NIGHTLY="nightly-2024-06-13" +NIGHTLY="nightly-2025-01-16" rustup toolchain install $NIGHTLY rustup default $NIGHTLY From 4f47e1354686251e3ec132da19d1be78a7bbcd04 Mon Sep 17 00:00:00 2001 From: Satoshi Otomakan Date: Fri, 17 Jan 2025 00:49:17 +0100 Subject: [PATCH 10/23] [Misc]: Fix Clippy warnings --- .../tw_bitcoin/src/babylon/proto_builder/output_protobuf.rs | 2 +- .../tw_bitcoin/src/babylon/proto_builder/utxo_protobuf.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index f7be9a9dbc9..70b78f5e576 100644 --- a/rust/chains/tw_bitcoin/src/babylon/proto_builder/output_protobuf.rs +++ b/rust/chains/tw_bitcoin/src/babylon/proto_builder/output_protobuf.rs @@ -28,7 +28,7 @@ pub trait BabylonOutputProtobuf { ) -> SigningResult; } -impl<'a, Context: UtxoContext> BabylonOutputProtobuf for OutputProtobuf<'a, Context> { +impl BabylonOutputProtobuf for OutputProtobuf<'_, Context> { fn babylon_staking( &self, staking: &Proto::mod_OutputBuilder::StakingOutput, 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 index bc12f4f9157..fc9d88e15ef 100644 --- a/rust/chains/tw_bitcoin/src/babylon/proto_builder/utxo_protobuf.rs +++ b/rust/chains/tw_bitcoin/src/babylon/proto_builder/utxo_protobuf.rs @@ -38,7 +38,7 @@ pub trait BabylonUtxoProtobuf { ) -> SigningResult<(TransactionInput, UtxoToSign)>; } -impl<'a, Context: UtxoContext> BabylonUtxoProtobuf for UtxoProtobuf<'a, Context> { +impl BabylonUtxoProtobuf for UtxoProtobuf<'_, Context> { fn babylon_staking_timelock( &self, timelock: &Proto::mod_InputBuilder::StakingTimelockPath, From 14d33aa8f3976c9301a966165ea4a4e735aa4017 Mon Sep 17 00:00:00 2001 From: gupnik Date: Tue, 18 Mar 2025 22:20:44 +0530 Subject: [PATCH 11/23] Generate FFI headers in include/TrustWalletCore/Generated folder (#4303) * Generate FFI headers in include/TrustWalletCore/Generated folder * Adds is_generated flag in type_decl and use that for jni generation * Trigger Build * Minor fix * Fix in CPP code gen as well * Fix for wasm * Fix for kotlin * Handle Generated folder in kotlin build --------- Co-authored-by: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> --- .gitignore | 12 +--------- codegen-v2/src/codegen/cpp/code_gen.rs | 15 ++++++++---- codegen/bin/codegen | 24 ++++++++++++++----- codegen/lib/coin_skeleton_gen.rb | 2 +- codegen/lib/entity_decl.rb | 9 +++++-- codegen/lib/enum_decl.rb | 4 ++++ codegen/lib/parser.rb | 7 +++--- codegen/lib/templates/cpp/includes.erb | 21 +++++++++++----- codegen/lib/templates/jni_c.erb | 17 +++++++++---- codegen/lib/templates/kotlin_jni_c.erb | 17 +++++++++---- codegen/lib/type_decl.rb | 3 ++- codegen/test/test_jni_helper.rb | 2 +- include/TrustWalletCore/TWCryptoBox.h | 4 ++-- kotlin/wallet-core-kotlin/build.gradle.kts | 11 ++++++++- .../chains/Binance/TWWalletConnectSigning.cpp | 2 +- tests/chains/Solana/SolanaMessageSigner.cpp | 2 +- tests/chains/Solana/TWSolanaTransaction.cpp | 2 +- tests/chains/Solana/TWWalletConnectSolana.cpp | 2 +- tests/chains/Sui/MessageSignerTests.cpp | 2 +- .../TWTONAddressConverterTests.cpp | 2 +- .../TWTONMessageSignerTests.cpp | 2 +- .../TheOpenNetwork/TWTONWalletTests.cpp | 2 +- tests/interface/TWCryptoBoxTests.cpp | 4 ++-- 23 files changed, 111 insertions(+), 57 deletions(-) diff --git a/.gitignore b/.gitignore index e7e97f12830..9b126ffe680 100644 --- a/.gitignore +++ b/.gitignore @@ -37,21 +37,11 @@ codegen-v2/bindings/ src/Generated/*.h src/Generated/*.cpp +include/TrustWalletCore/Generated/*.h include/TrustWalletCore/TWHRP.h include/TrustWalletCore/TW*Proto.h include/TrustWalletCore/TWEthereumChainID.h -# Generated -include/TrustWalletCore/TWTONAddressConverter.h -include/TrustWalletCore/TWFFITest.h -include/TrustWalletCore/TWTONWallet.h -include/TrustWalletCore/TWTONMessageSigner.h -include/TrustWalletCore/TWMessageSigner.h -include/TrustWalletCore/TWWalletConnectRequest.h -include/TrustWalletCore/TWSolanaTransaction.h -include/TrustWalletCore/TWCryptoBoxPublicKey.h -include/TrustWalletCore/TWCryptoBoxSecretKey.h - # Wasm emsdk/ wasm-build/ diff --git a/codegen-v2/src/codegen/cpp/code_gen.rs b/codegen-v2/src/codegen/cpp/code_gen.rs index e83f3af603e..64b01443edb 100644 --- a/codegen-v2/src/codegen/cpp/code_gen.rs +++ b/codegen-v2/src/codegen/cpp/code_gen.rs @@ -8,7 +8,8 @@ use crate::Error::BadFormat; use crate::Result; static IN_DIR: &str = "../rust/bindings/"; -static HEADER_OUT_DIR: &str = "../include/TrustWalletCore/"; +static HEADER_IN_DIR: &str = "../include/TrustWalletCore/"; +static HEADER_OUT_DIR: &str = "../include/TrustWalletCore/Generated/"; static SOURCE_OUT_DIR: &str = "../src/Generated/"; fn generate_license(file: &mut std::fs::File) -> Result<()> { @@ -24,7 +25,7 @@ fn generate_header_guard(file: &mut std::fs::File) -> Result<()> { } fn generate_header_includes(file: &mut std::fs::File, info: &TWConfig) -> Result<()> { - writeln!(file, "#include \"TWBase.h\"")?; + writeln!(file, "#include ")?; // Include headers based on argument types let mut included_headers = std::collections::HashSet::new(); @@ -37,7 +38,11 @@ fn generate_header_includes(file: &mut std::fs::File, info: &TWConfig) -> Result continue; } if included_headers.insert(header_name.clone()) { - writeln!(file, "#include \"{}.h\"", header_name)?; + if let Ok(true) = fs::exists(format!("{}{}.h", HEADER_IN_DIR, header_name)) { + writeln!(file, "#include ", header_name)?; + } else { + writeln!(file, "#include ", header_name)?; + } } } TWType::Standard(ty) => { @@ -45,7 +50,7 @@ fn generate_header_includes(file: &mut std::fs::File, info: &TWConfig) -> Result && included_headers.insert("TWCoinType.h".to_string()) { // Need to handle this case separately because it's not a pointer type - writeln!(file, "#include \"TWCoinType.h\"")?; + writeln!(file, "#include ")?; } } } @@ -170,7 +175,7 @@ fn generate_wrapper_header(info: &TWConfig) -> Result<()> { } fn generate_source_includes(file: &mut std::fs::File, info: &TWConfig) -> Result<()> { - writeln!(file, "#include ", info.class)?; + writeln!(file, "#include ", info.class)?; writeln!(file, "#include \"rust/Wrapper.h\"")?; // Include headers based on argument types diff --git a/codegen/bin/codegen b/codegen/bin/codegen index f34931b21ab..78c9a8901b0 100755 --- a/codegen/bin/codegen +++ b/codegen/bin/codegen @@ -62,17 +62,29 @@ end.parse! entities = [] files = [] -Dir.foreach(options.input) do |item| - next if ['.', '..', '.DS_Store'].include?(item) - - file_path = File.expand_path(File.join(options.input, item)) +def process_file(file_path, entities, files) entity = Parser.new(path: file_path).parse - next if entity.nil? + return if entity.nil? entities << entity - files << File.basename(item, '.h').sub(/^TW/, '') + files << File.basename(file_path, '.h').sub(/^TW/, '') +end + +def process_directory(dir_path, entities, files) + Dir.foreach(dir_path) do |item| + next if ['.', '..', '.DS_Store'].include?(item) + + path = File.expand_path(File.join(dir_path, item)) + if File.directory?(path) + process_directory(path, entities, files) + else + process_file(path, entities, files) if item.end_with?('.h') + end + end end +process_directory(options.input, entities, files) + generator = CodeGenerator.new(entities: entities, files: files, output_folder: options.output) if options.swift generator.render_swift diff --git a/codegen/lib/coin_skeleton_gen.rb b/codegen/lib/coin_skeleton_gen.rb index feec8fef8e6..eafdf3c45f1 100755 --- a/codegen/lib/coin_skeleton_gen.rb +++ b/codegen/lib/coin_skeleton_gen.rb @@ -108,7 +108,7 @@ def generate_skeleton(coin_id, mode) json_string = File.read('registry.json') coins = JSON.parse(json_string).sort_by { |x| x['name'] } - entity = EntityDecl.new(name: "New" + coin_id, is_struct: false, comment: '') + entity = EntityDecl.new(name: "New" + coin_id, is_struct: false, is_generated: false, comment: '') file = "new"+ coin_id generator = CodeGenerator.new(entities: [entity], files: [file], output_folder: ".") diff --git a/codegen/lib/entity_decl.rb b/codegen/lib/entity_decl.rb index aee285ef97b..303633c02d6 100644 --- a/codegen/lib/entity_decl.rb +++ b/codegen/lib/entity_decl.rb @@ -3,11 +3,12 @@ # Class or struct declaration class EntityDecl attr_reader :name, :comment - attr_accessor :is_struct, :methods, :properties, :static_methods, :static_properties + attr_accessor :is_struct, :is_generated, :methods, :properties, :static_methods, :static_properties - def initialize(name:, is_struct:, comment:) + def initialize(name:, is_struct:, is_generated:, comment:) @name = name @is_struct = is_struct + @is_generated = is_generated @methods = [] @properties = [] @static_methods = [] @@ -19,6 +20,10 @@ def struct? is_struct end + def generated? + is_generated + end + def class? !is_struct end diff --git a/codegen/lib/enum_decl.rb b/codegen/lib/enum_decl.rb index 0fae5774f50..a74d533c034 100644 --- a/codegen/lib/enum_decl.rb +++ b/codegen/lib/enum_decl.rb @@ -25,6 +25,10 @@ def class? false end + def generated? + false + end + def enum? true end diff --git a/codegen/lib/parser.rb b/codegen/lib/parser.rb index e4e287614a0..55bdcab0a9e 100644 --- a/codegen/lib/parser.rb +++ b/codegen/lib/parser.rb @@ -9,12 +9,13 @@ # C header parser class Parser - attr_reader :path, :entity, :entity_comment + attr_reader :path, :entity, :entity_comment, :is_generated def initialize(path:, string: nil) @path = path @buffer = StringScanner.new(string || File.read(path)) @entity = nil + @is_generated = path.include?('Generated') end # Parses a C header file for class/struct declarations @@ -134,7 +135,7 @@ def handle_class @buffer.skip(/\s*/) report_error 'Invalid type name' if @buffer.scan(/struct TW(\w+)\s*;/).nil? report_error 'Found more than one class/struct in the same file' unless @entity.nil? - @entity = EntityDecl.new(name: @buffer[1], is_struct: false, comment: @entity_comment) + @entity = EntityDecl.new(name: @buffer[1], is_struct: false, is_generated: @is_generated, comment: @entity_comment) puts "Found a class #{@entity.name}" end @@ -142,7 +143,7 @@ def handle_struct @buffer.skip(/\s*/) report_error 'Invalid type name at' if @buffer.scan(/struct TW(\w+)\s*\{?/).nil? report_error 'Found more than one class/struct in the same file' unless @entity.nil? - @entity = EntityDecl.new(name: @buffer[1], is_struct: true, comment: @entity_comment) + @entity = EntityDecl.new(name: @buffer[1], is_struct: true, is_generated: @is_generated, comment: @entity_comment) puts "Found a struct #{@buffer[1]}" end diff --git a/codegen/lib/templates/cpp/includes.erb b/codegen/lib/templates/cpp/includes.erb index 6c3d2e996c4..a38484ee5c2 100644 --- a/codegen/lib/templates/cpp/includes.erb +++ b/codegen/lib/templates/cpp/includes.erb @@ -1,19 +1,28 @@ <%# Include entity headers -%> <% require 'set' -%> -<% includes = Set.new([entity.name]) -%> +<% includes = Set.new -%> +<% includes.add({name: entity.name, generated: entity.generated?}) -%> <% (entity.static_methods + entity.methods).each do |method| -%> -<% includes << method.return_type.name if method.return_type.is_struct || method.return_type.is_class -%> +<% if method.return_type.is_struct || method.return_type.is_class -%> +<% includes.add({name: method.return_type.name, generated: method.return_type.is_generated}) -%> +<% end -%> <% method.parameters.each do |param| -%> -<% includes << param.type.name if param.type.is_struct || param.type.is_class -%> +<% if param.type.is_struct || param.type.is_class -%> +<% includes.add({name: param.type.name, generated: param.type.is_generated}) -%> +<% end -%> <% end -%> <% end -%> <% includes.each do |include| -%> -#include .h> +<% if include[:generated] -%> +#include .h> +<% else -%> +#include .h> +<% end -%> <% end -%> <% includes.each do |include| -%> -<% next if include == entity.name -%> -#include "./<%= include %>.h" +<% next if include[:name] == entity.name -%> +#include "./<%= include[:name] %>.h" <% end -%> #include diff --git a/codegen/lib/templates/jni_c.erb b/codegen/lib/templates/jni_c.erb index 5f7f56125bc..5fccafa36cd 100644 --- a/codegen/lib/templates/jni_c.erb +++ b/codegen/lib/templates/jni_c.erb @@ -3,15 +3,24 @@ #include <% require 'set' -%> -<% includes = Set.new([entity.name]) -%> +<% includes = Set.new -%> +<% includes.add({name: entity.name, generated: entity.generated?}) -%> <% entity.static_methods.each do |method| -%> -<% includes << method.return_type.name if method.return_type.is_struct || method.return_type.is_class -%> +<% if method.return_type.is_struct || method.return_type.is_class -%> +<% includes.add({name: method.return_type.name, generated: method.return_type.is_generated}) -%> +<% end -%> <% method.parameters.each do |param| -%> -<% includes << param.type.name if param.type.is_struct || param.type.is_class -%> +<% if param.type.is_struct || param.type.is_class -%> +<% includes.add({name: param.type.name, generated: param.type.is_generated}) -%> +<% end -%> <% end -%> <% end -%> <% includes.each do |include| -%> -#include .h> +<% if include[:generated] -%> +#include .h> +<% else -%> +#include .h> +<% end -%> <% end -%> #include "TWJNI.h" diff --git a/codegen/lib/templates/kotlin_jni_c.erb b/codegen/lib/templates/kotlin_jni_c.erb index 3b1f05b0588..6eaa3fe1b7d 100644 --- a/codegen/lib/templates/kotlin_jni_c.erb +++ b/codegen/lib/templates/kotlin_jni_c.erb @@ -3,15 +3,24 @@ #include <% require 'set' -%> -<% includes = Set.new([entity.name]) -%> +<% includes = Set.new -%> +<% includes.add({name: entity.name, generated: entity.generated?}) -%> <% entity.static_methods.each do |method| -%> -<% includes << method.return_type.name if method.return_type.is_struct || method.return_type.is_class -%> +<% if method.return_type.is_struct || method.return_type.is_class -%> +<% includes.add({name: method.return_type.name, generated: method.return_type.is_generated}) -%> +<% end -%> <% method.parameters.each do |param| -%> -<% includes << param.type.name if param.type.is_struct || param.type.is_class -%> +<% if param.type.is_struct || param.type.is_class -%> +<% includes.add({name: param.type.name, generated: param.type.is_generated}) -%> +<% end -%> <% end -%> <% end -%> <% includes.each do |include| -%> -#include .h> +<% if include[:generated] -%> +#include .h> +<% else -%> +#include .h> +<% end -%> <% end -%> #include "TWJNI.h" diff --git a/codegen/lib/type_decl.rb b/codegen/lib/type_decl.rb index 1abc1c60104..a6fe4399283 100644 --- a/codegen/lib/type_decl.rb +++ b/codegen/lib/type_decl.rb @@ -3,7 +3,7 @@ # Type declaration class TypeDecl attr_reader :name - attr_accessor :is_class, :is_struct, :is_enum, :is_proto, :is_nullable, :is_inout, :size + attr_accessor :is_class, :is_struct, :is_enum, :is_proto, :is_nullable, :is_inout, :size, :is_generated def initialize(name:, **options) @name = name @@ -14,6 +14,7 @@ def initialize(name:, **options) @is_nullable = options.fetch(:is_nullable, false) @is_inout = options.fetch(:is_inout, false) @size = options.fetch(:size, nil) + @is_generated = !File.exist?("include/TrustWalletCore/TW#{name}.h") end def self.fromPrimitive(string) diff --git a/codegen/test/test_jni_helper.rb b/codegen/test/test_jni_helper.rb index 595ff1e53e2..f1a98713727 100644 --- a/codegen/test/test_jni_helper.rb +++ b/codegen/test/test_jni_helper.rb @@ -8,7 +8,7 @@ def test_format_name end def test_function_name - entity = EntityDecl.new(name: 'Test', is_struct: false, comment: '') + entity = EntityDecl.new(name: 'Test', is_struct: false, is_generated: false, comment: '') method = FunctionDecl.new(name: 'Function', entity: entity, is_method: true) name = JNIHelper.function_name(entity: entity, function: method) assert_equal(name, 'Java_wallet_core_jni_Test_function') diff --git a/include/TrustWalletCore/TWCryptoBox.h b/include/TrustWalletCore/TWCryptoBox.h index abd27ca8191..7894d5806dd 100644 --- a/include/TrustWalletCore/TWCryptoBox.h +++ b/include/TrustWalletCore/TWCryptoBox.h @@ -5,8 +5,8 @@ #pragma once #include "TWBase.h" -#include "TWCryptoBoxPublicKey.h" -#include "TWCryptoBoxSecretKey.h" +#include +#include #include "TWData.h" #include "TWString.h" diff --git a/kotlin/wallet-core-kotlin/build.gradle.kts b/kotlin/wallet-core-kotlin/build.gradle.kts index 9d49f0c003d..e93ef5997e8 100644 --- a/kotlin/wallet-core-kotlin/build.gradle.kts +++ b/kotlin/wallet-core-kotlin/build.gradle.kts @@ -104,7 +104,16 @@ kotlin { rootDir.parentFile.resolve("include"), rootDir.parentFile.resolve("include/TrustWalletCore"), ) - headers(rootDir.parentFile.resolve("include/TrustWalletCore").listFiles()!!) + + // Include headers from TrustWalletCore directory + val trustWalletCoreDir = rootDir.parentFile.resolve("include/TrustWalletCore") + headers(trustWalletCoreDir.listFiles()!!.filter { it.isFile }) + + // Include headers from Generated directory + val generatedDir = rootDir.parentFile.resolve("include/TrustWalletCore/Generated") + if (generatedDir.exists()) { + headers(generatedDir.listFiles()!!.filter { it.isFile }) + } } } } diff --git a/tests/chains/Binance/TWWalletConnectSigning.cpp b/tests/chains/Binance/TWWalletConnectSigning.cpp index 7ee7e7956f4..b9ee8a4094b 100644 --- a/tests/chains/Binance/TWWalletConnectSigning.cpp +++ b/tests/chains/Binance/TWWalletConnectSigning.cpp @@ -7,7 +7,7 @@ #include "proto/WalletConnect.pb.h" #include "Coin.h" #include -#include +#include #include "TestUtilities.h" #include diff --git a/tests/chains/Solana/SolanaMessageSigner.cpp b/tests/chains/Solana/SolanaMessageSigner.cpp index a335ed7109e..eb567d005ec 100644 --- a/tests/chains/Solana/SolanaMessageSigner.cpp +++ b/tests/chains/Solana/SolanaMessageSigner.cpp @@ -2,7 +2,7 @@ // // Copyright © 2017 Trust Wallet. -#include +#include #include #include "Data.h" diff --git a/tests/chains/Solana/TWSolanaTransaction.cpp b/tests/chains/Solana/TWSolanaTransaction.cpp index f562dcfb8b4..7e545a08e72 100644 --- a/tests/chains/Solana/TWSolanaTransaction.cpp +++ b/tests/chains/Solana/TWSolanaTransaction.cpp @@ -2,7 +2,7 @@ // // Copyright © 2017 Trust Wallet. -#include "TrustWalletCore/TWSolanaTransaction.h" +#include "TrustWalletCore/Generated/TWSolanaTransaction.h" #include "TrustWalletCore/TWTransactionDecoder.h" #include "TrustWalletCore/TWAnySigner.h" #include "proto/Solana.pb.h" diff --git a/tests/chains/Solana/TWWalletConnectSolana.cpp b/tests/chains/Solana/TWWalletConnectSolana.cpp index f8c23d0c80e..1be2ca2329e 100644 --- a/tests/chains/Solana/TWWalletConnectSolana.cpp +++ b/tests/chains/Solana/TWWalletConnectSolana.cpp @@ -7,7 +7,7 @@ #include "proto/WalletConnect.pb.h" #include "Coin.h" #include -#include +#include #include "TestUtilities.h" #include diff --git a/tests/chains/Sui/MessageSignerTests.cpp b/tests/chains/Sui/MessageSignerTests.cpp index 414cb6ee95d..1483d1ee476 100644 --- a/tests/chains/Sui/MessageSignerTests.cpp +++ b/tests/chains/Sui/MessageSignerTests.cpp @@ -2,7 +2,7 @@ // // Copyright © 2017 Trust Wallet. -#include +#include #include #include "Data.h" diff --git a/tests/chains/TheOpenNetwork/TWTONAddressConverterTests.cpp b/tests/chains/TheOpenNetwork/TWTONAddressConverterTests.cpp index ec93a9dc78d..f5f7b0a4447 100644 --- a/tests/chains/TheOpenNetwork/TWTONAddressConverterTests.cpp +++ b/tests/chains/TheOpenNetwork/TWTONAddressConverterTests.cpp @@ -4,7 +4,7 @@ #include "TestUtilities.h" -#include +#include namespace TW::TheOpenNetwork::tests { diff --git a/tests/chains/TheOpenNetwork/TWTONMessageSignerTests.cpp b/tests/chains/TheOpenNetwork/TWTONMessageSignerTests.cpp index fd9367dffd7..b331c31f4d8 100644 --- a/tests/chains/TheOpenNetwork/TWTONMessageSignerTests.cpp +++ b/tests/chains/TheOpenNetwork/TWTONMessageSignerTests.cpp @@ -5,7 +5,7 @@ #include "TestUtilities.h" #include "HexCoding.h" -#include "TrustWalletCore/TWTONMessageSigner.h" +#include "TrustWalletCore/Generated/TWTONMessageSigner.h" namespace TW::TheOpenNetwork::tests { diff --git a/tests/chains/TheOpenNetwork/TWTONWalletTests.cpp b/tests/chains/TheOpenNetwork/TWTONWalletTests.cpp index b346ee5568b..e97d8502d62 100644 --- a/tests/chains/TheOpenNetwork/TWTONWalletTests.cpp +++ b/tests/chains/TheOpenNetwork/TWTONWalletTests.cpp @@ -5,7 +5,7 @@ #include "TestUtilities.h" #include "HexCoding.h" -#include "TrustWalletCore/TWTONWallet.h" +#include "TrustWalletCore/Generated/TWTONWallet.h" namespace TW::TheOpenNetwork::tests { diff --git a/tests/interface/TWCryptoBoxTests.cpp b/tests/interface/TWCryptoBoxTests.cpp index cb9a3335c54..301381e171f 100644 --- a/tests/interface/TWCryptoBoxTests.cpp +++ b/tests/interface/TWCryptoBoxTests.cpp @@ -4,8 +4,8 @@ #include "TestUtilities.h" #include "TrustWalletCore/TWCryptoBox.h" -#include "TrustWalletCore/TWCryptoBoxPublicKey.h" -#include "TrustWalletCore/TWCryptoBoxSecretKey.h" +#include "TrustWalletCore/Generated/TWCryptoBoxPublicKey.h" +#include "TrustWalletCore/Generated/TWCryptoBoxSecretKey.h" #include From 70a2a4047d525f4c2a524b78a6121ab8ee11cfec Mon Sep 17 00:00:00 2001 From: gupnik Date: Thu, 20 Mar 2025 14:41:08 +0530 Subject: [PATCH 12/23] Fixes Docker and Cocoapods builds (#4317) * Use Path API * Tries to fix cocoa pods release * Uses include paths * Fixes cocoapods build as well * Bumps docker version --- Dockerfile | 2 +- codegen-v2/src/codegen/cpp/code_gen.rs | 2 +- tools/ios-release | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 082d5fd1969..a2c6f399eda 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,7 +38,7 @@ ENV CXX=/usr/bin/clang++-14 RUN wget "https://sh.rustup.rs" -O rustup.sh \ && sh rustup.sh -y ENV PATH="/root/.cargo/bin:${PATH}" -RUN rustup default nightly-2024-06-13 +RUN rustup default nightly-2025-01-16 RUN cargo install --force cbindgen \ && rustup target add wasm32-unknown-emscripten diff --git a/codegen-v2/src/codegen/cpp/code_gen.rs b/codegen-v2/src/codegen/cpp/code_gen.rs index 64b01443edb..1186ffdf855 100644 --- a/codegen-v2/src/codegen/cpp/code_gen.rs +++ b/codegen-v2/src/codegen/cpp/code_gen.rs @@ -38,7 +38,7 @@ fn generate_header_includes(file: &mut std::fs::File, info: &TWConfig) -> Result continue; } if included_headers.insert(header_name.clone()) { - if let Ok(true) = fs::exists(format!("{}{}.h", HEADER_IN_DIR, header_name)) { + if std::path::Path::new(&format!("{}{}.h", HEADER_IN_DIR, header_name)).exists() { writeln!(file, "#include ", header_name)?; } else { writeln!(file, "#include ", header_name)?; diff --git a/tools/ios-release b/tools/ios-release index 2fce33e89ad..17d764fd17f 100755 --- a/tools/ios-release +++ b/tools/ios-release @@ -59,6 +59,12 @@ Pod::Spec.new do |s| http: '${download_url}' } s.default_subspec = 'Core' + + s.prepare_command = <<-CMD + find include -name "*.h" -print0 | xargs -0 sed -i '' 's/\\\(<\\\)TrustWalletCore\\\/Generated\\\/\\\(.*\\\)\\\(>\\\)/\\\"\\\2\\\"/' + find include -name "*.h" -print0 | xargs -0 sed -i '' 's/\\\(<\\\)TrustWalletCore\\\/\\\(.*\\\)\\\(>\\\)/\\\"\\\2\\\"/' + CMD + s.subspec 'Types' do |ss| ss.source_files = 'Sources/Types/*.swift', From b5f60043359206f8e43ae0f49d1bdafc66421a71 Mon Sep 17 00:00:00 2001 From: gupnik Date: Fri, 28 Mar 2025 14:38:19 +0530 Subject: [PATCH 13/23] [BreakingChange]: Migrates FFI Tests to Private Key V2 APIs (#4333) * feat(eip7702): Add Biz Smart Contract Account Type (#4319) * fix(eip7702): Add `UserOperationMode` * Add `erc4337.biz_account.abi.json` ABI * fix(eip7702): Add `test_barz_transfer_erc7702_eoa` test * fix(eip7702): Fix `Biz.execute4337Ops()` * fix(eip7702): Minor changes * fix(eip7702): Rename `UserOperationMode` to `SCAccountType` * fix: tron message sign (#4326) * Adds ability to specify the curve while constructing Private Key (#4324) * Adds ability to specify the curve while constructing Private Key * Adds signing functions without a curve * Migrates to new API * Use TWCoinTypeCurve * Adds Curve * Adds FFI Tests for Private Key V2 APIs * Migrates Swift tests to new API * Migrates Kotlin tests to V2 API * Migrates WASM tests * Migrates C++ tests to V2 APIs * Removes deprecated APIs and migrates all to new ones --------- Co-authored-by: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Co-authored-by: Yeferson Licet <111311418+y3fers0n@users.noreply.github.com> --- .../app/blockchains/acala/TestAcalaAddress.kt | 2 +- .../acalaevm/TestAcalaEVMAddress.kt | 2 +- .../blockchains/agoric/TestAgoricAddress.kt | 2 +- .../blockchains/agoric/TestAgoricSigner.kt | 2 +- .../core/app/blockchains/aion/TestAion.kt | 2 +- .../algorand/TestAlgorandAddress.kt | 2 +- .../bandchain/TestBandChainAddress.kt | 2 +- .../bandchain/TestBandChainSigner.kt | 3 +- .../binance/TestBinanceTransactionSigning.kt | 4 +- .../blockchains/bitcoin/TestBitcoinPsbt.kt | 2 +- .../blockchains/bitcoin/TestBitcoinSigning.kt | 6 +- .../TestBitcoinDiamondAddress.kt | 2 +- .../bluzelle/TestBluzelleAddress.kt | 2 +- .../bluzelle/TestBluzelleSigner.kt | 3 +- .../blockchains/cardano/TestCardanoAddress.kt | 2 +- .../blockchains/cardano/TestCardanoSigning.kt | 2 +- .../confluxespace/TestConfluxeSpaceAddress.kt | 2 +- .../cosmos/TestCosmosTransactions.kt | 8 +- .../cryptoorg/TestCryptoorgAddress.kt | 2 +- .../cryptoorg/TestCryptoorgSigner.kt | 2 +- .../app/blockchains/dydx/TestDydxAddress.kt | 2 +- .../core/app/blockchains/ethereum/TestBarz.kt | 8 +- .../ethereum/TestEthereumMessageSigner.kt | 12 +- .../ethereum/TestEthereumTransactionSigner.kt | 14 +- .../everscale/TestEverscaleAddress.kt | 2 +- .../app/blockchains/filecoin/TestFilecoin.kt | 4 +- .../app/blockchains/fio/TestFIOAddress.kt | 2 +- .../core/app/blockchains/fio/TestFIOSigner.kt | 6 +- .../greenfield/TestGreenfieldSigner.kt | 4 +- .../blockchains/harmony/TestHarmonyAddress.kt | 2 +- .../TestHarmonyStakingDelegateSigner.kt | 3 +- .../harmony/TestHarmonyTransactionSigner.kt | 3 +- .../TestInternetComputerAddress.kt | 2 +- .../TestInternetComputerSigner.kt | 6 +- .../blockchains/kava/TestKavaTransactions.kt | 2 +- .../kcc/TestKuCoinCommunityChainAddress.kt | 2 +- .../blockchains/kusama/TestKusamaAddress.kt | 2 +- .../multiversx/TestMultiversXAddress.kt | 2 +- .../multiversx/TestMultiversXSigner.kt | 18 +- .../TestNativeInjectiveAddress.kt | 2 +- .../TestNativeInjectiveSigner.kt | 2 +- .../TestNativeZetaChainAddress.kt | 2 +- .../TestNativeZetaChainSigner.kt | 2 +- .../app/blockchains/near/TestNEARAddress.kt | 2 +- .../blockchains/nebulas/TestNebulasAddress.kt | 2 +- .../blockchains/nebulas/TestNebulasSigner.kt | 3 +- .../app/blockchains/neo/TestsNEOAddress.kt | 2 +- .../blockchains/nervos/TestNervosAddress.kt | 2 +- .../blockchains/nervos/TestNervosSigner.kt | 2 +- .../app/blockchains/nuls/TestNULSAddress.kt | 2 +- .../app/blockchains/oasis/TestOasisAddress.kt | 2 +- .../blockchains/osmosis/TestOsmosisAddress.kt | 2 +- .../blockchains/osmosis/TestOsmosisSigner.kt | 2 +- .../blockchains/pactus/TestPactusAddress.kt | 2 +- .../blockchains/pactus/TestPactusSigner.kt | 6 +- .../polkadot/TestPolkadotAddress.kt | 2 +- .../blockchains/polygon/TestPolygonAddress.kt | 2 +- .../polymesh/TestPolymeshAddress.kt | 2 +- .../ripple/TestRippleTransactionSigner.kt | 3 +- .../blockchains/scroll/TestScrollAddress.kt | 2 +- .../blockchains/secret/TestSecretAddress.kt | 2 +- .../blockchains/secret/TestSecretSigner.kt | 2 +- .../TestSmartBitcoinCashAddress.kt | 2 +- .../TestBinanceSmartChainAddress.kt | 2 +- .../blockchains/solana/TestSolanaAddress.kt | 2 +- .../stargaze/TestStargazeAddress.kt | 2 +- .../stargaze/TestStargazeSigner.kt | 4 +- .../starkex/TestStarkExMessageSigner.kt | 4 +- .../blockchains/stellar/TestStellarAddress.kt | 2 +- .../blockchains/stellar/TestStellarSigner.kt | 13 +- .../blockchains/terra/TestTerraClassicTxs.kt | 8 +- .../terra/TestTerraTransactions.kt | 4 +- .../tezos/TestTezosMessageSigner.kt | 2 +- .../TestTheOpenNetworkAddress.kt | 2 +- .../TestTheOpenNetworkMessageSigner.kt | 3 +- .../TestTheOpenNetworkSigner.kt | 6 +- .../thetafuel/TestThetaFuelAddress.kt | 2 +- .../thorchain/TestTHORChainSigner.kt | 2 +- .../thorchain/TestTHORSwapSigning.kt | 3 +- .../blockchains/tron/TestTronMessageSigner.kt | 4 +- .../app/blockchains/waves/TestWavesAddress.kt | 2 +- .../app/blockchains/zen/TestZenAddress.kt | 2 +- .../core/app/utils/TestLiquidStaking.kt | 2 +- .../core/app/utils/TestPrivateKey.kt | 23 +- .../core/app/utils/TestPublicKey.kt | 21 +- include/TrustWalletCore/TWPrivateKey.h | 15 +- rust/tw_evm/src/abi/prebuild/erc4337.rs | 74 ++- .../resource/erc4337.biz_account.abi.json | 515 ++++++++++++++++++ rust/tw_evm/src/modules/tx_builder.rs | 75 +-- rust/tw_evm/tests/barz.rs | 147 ++++- src/Aeternity/Signer.cpp | 4 +- src/Aion/Signer.cpp | 4 +- src/Algorand/Signer.cpp | 6 +- src/Bitcoin/MessageSigner.cpp | 2 +- src/Bitcoin/SigningInput.cpp | 2 +- src/Cardano/Signer.cpp | 6 +- src/Decred/Signer.cpp | 6 +- src/EOS/Signer.cpp | 10 +- src/Ethereum/Barz.cpp | 4 +- src/Everscale/Messages.cpp | 2 +- src/Everscale/Signer.cpp | 2 +- src/FIO/Signer.cpp | 2 +- src/FIO/TransactionBuilder.cpp | 2 +- src/Filecoin/Signer.cpp | 8 +- src/HDWallet.cpp | 14 +- src/Harmony/Signer.cpp | 6 +- src/Hedera/Signer.cpp | 4 +- src/IOST/Account.cpp | 6 +- src/Icon/Signer.cpp | 4 +- src/ImmutableX/StarkKey.cpp | 4 +- src/IoTeX/Signer.cpp | 8 +- src/Keystore/StoredKey.cpp | 8 +- src/MultiversX/Signer.cpp | 4 +- src/NEAR/Serialization.cpp | 2 +- src/NEAR/Signer.cpp | 4 +- src/NEO/Signer.cpp | 4 +- src/NULS/Signer.cpp | 4 +- src/Nano/Signer.cpp | 4 +- src/Nebulas/Signer.cpp | 4 +- src/Nervos/Signer.cpp | 2 +- src/Nervos/Transaction.cpp | 2 +- src/Nimiq/Signer.cpp | 4 +- src/Oasis/Signer.cpp | 6 +- src/Ontology/Oep4TxBuilder.cpp | 4 +- src/Ontology/OngTxBuilder.cpp | 8 +- src/Ontology/OntTxBuilder.cpp | 4 +- src/Ontology/Signer.cpp | 4 +- src/PrivateKey.cpp | 25 +- src/PrivateKey.h | 33 +- src/StarkEx/MessageSigner.cpp | 2 +- src/Stellar/Signer.cpp | 4 +- src/Tezos/BinaryCoding.cpp | 2 +- src/Tezos/MessageSigner.cpp | 2 +- src/Tezos/Signer.cpp | 4 +- src/Theta/Signer.cpp | 4 +- src/Tron/MessageSigner.cpp | 4 +- src/Tron/MessageSigner.h | 2 +- src/Tron/Signer.cpp | 8 +- src/VeChain/Signer.cpp | 4 +- src/Waves/Signer.cpp | 4 +- src/Zilliqa/Signer.cpp | 4 +- src/interface/TWPrivateKey.cpp | 22 +- src/proto/Ethereum.proto | 12 + swift/Podfile.lock | 2 +- swift/Sources/KeyStore.swift | 5 +- .../Tests/Addresses/BitcoinAddressTests.swift | 8 +- swift/Tests/Addresses/NEOAddressTests.swift | 2 +- .../Addresses/OntologyAddressTests.swift | 2 +- swift/Tests/Addresses/TronAddressTests.swift | 4 +- swift/Tests/Blockchains/AcalaEVMTests.swift | 2 +- swift/Tests/Blockchains/AcalaTests.swift | 4 +- swift/Tests/Blockchains/AgoricTests.swift | 4 +- swift/Tests/Blockchains/AionTests.swift | 2 +- swift/Tests/Blockchains/AlgorandTests.swift | 2 +- swift/Tests/Blockchains/AvalancheTests.swift | 2 +- swift/Tests/Blockchains/BandChainTests.swift | 2 +- .../Tests/Blockchains/BinanceChainTests.swift | 4 +- .../Blockchains/BinanceSmartChainTests.swift | 2 +- .../Blockchains/BitcoinDiamondTests.swift | 4 +- swift/Tests/Blockchains/BitcoinTests.swift | 12 +- swift/Tests/Blockchains/BitconCashTests.swift | 2 +- swift/Tests/Blockchains/BluzelleTests.swift | 4 +- swift/Tests/Blockchains/CardanoTests.swift | 4 +- .../Blockchains/ConfluxeSpaceTests.swift | 2 +- swift/Tests/Blockchains/CosmosTests.swift | 8 +- swift/Tests/Blockchains/CryptoorgTests.swift | 4 +- swift/Tests/Blockchains/DashTests.swift | 2 +- swift/Tests/Blockchains/DecredTests.swift | 4 +- swift/Tests/Blockchains/DydxTests.swift | 2 +- swift/Tests/Blockchains/ECashTests.swift | 2 +- swift/Tests/Blockchains/EthereumTests.swift | 10 +- swift/Tests/Blockchains/EverscaleTests.swift | 2 +- swift/Tests/Blockchains/FIOTests.swift | 8 +- swift/Tests/Blockchains/FilecoinTests.swift | 4 +- .../Tests/Blockchains/GroestlcoinTests.swift | 4 +- swift/Tests/Blockchains/IconTests.swift | 4 +- .../Blockchains/InternetComputerTests.swift | 8 +- swift/Tests/Blockchains/IoTeXTests.swift | 2 +- swift/Tests/Blockchains/KavaTests.swift | 2 +- .../KuCoinCommunityChainTests.swift | 2 +- swift/Tests/Blockchains/KusamaTests.swift | 2 +- swift/Tests/Blockchains/LitecoinTests.swift | 8 +- swift/Tests/Blockchains/MonacoinTests.swift | 6 +- swift/Tests/Blockchains/MultiversXTests.swift | 20 +- swift/Tests/Blockchains/NEARTests.swift | 2 +- swift/Tests/Blockchains/NEOTests.swift | 2 +- swift/Tests/Blockchains/NULSTests.swift | 2 +- .../Blockchains/NativeInjectiveTests.swift | 4 +- .../Blockchains/NativeZetaChainTests.swift | 4 +- swift/Tests/Blockchains/NebulasTests.swift | 4 +- swift/Tests/Blockchains/NervosTests.swift | 2 +- swift/Tests/Blockchains/OasisTests.swift | 4 +- swift/Tests/Blockchains/OsmosisTests.swift | 4 +- swift/Tests/Blockchains/PactusTests.swift | 2 +- swift/Tests/Blockchains/PolkadotTests.swift | 6 +- swift/Tests/Blockchains/PolygonTests.swift | 4 +- swift/Tests/Blockchains/PolymeshTests.swift | 2 +- swift/Tests/Blockchains/QtumTests.swift | 4 +- swift/Tests/Blockchains/RippleTests.swift | 2 +- swift/Tests/Blockchains/ScrollTests.swift | 2 +- swift/Tests/Blockchains/SecretTests.swift | 4 +- .../Blockchains/SmartBitcoinCashTests.swift | 2 +- swift/Tests/Blockchains/SolanaTests.swift | 2 +- swift/Tests/Blockchains/StargazeTests.swift | 6 +- swift/Tests/Blockchains/StarkExTests.swift | 2 +- swift/Tests/Blockchains/StellarTests.swift | 2 +- swift/Tests/Blockchains/SyscoinTests.swift | 6 +- swift/Tests/Blockchains/THORChainTests.swift | 2 +- .../Tests/Blockchains/TerraClassicTests.swift | 12 +- swift/Tests/Blockchains/TerraTests.swift | 6 +- swift/Tests/Blockchains/TezosTests.swift | 10 +- .../Blockchains/TheOpenNetworkTests.swift | 4 +- swift/Tests/Blockchains/ThetaFuelTests.swift | 2 +- swift/Tests/Blockchains/TronTests.swift | 4 +- swift/Tests/Blockchains/WavesTests.swift | 2 +- swift/Tests/Blockchains/ZcashTests.swift | 2 +- swift/Tests/Blockchains/ZenTests.swift | 4 +- swift/Tests/Blockchains/ZilliqaTests.swift | 2 +- swift/Tests/HDWalletTests.swift | 2 +- swift/Tests/Keystore/AccountTests.swift | 6 +- swift/Tests/Keystore/KeyStoreTests.swift | 8 +- swift/Tests/PrivateKeyTests.swift | 22 +- swift/Tests/PublicKeyTests.swift | 18 +- tests/chains/Aeternity/SignerTests.cpp | 8 +- tests/chains/Aion/SignerTests.cpp | 4 +- .../chains/Aion/TransactionCompilerTests.cpp | 4 +- tests/chains/Algorand/AddressTests.cpp | 4 +- tests/chains/Algorand/SignerTests.cpp | 10 +- .../Algorand/TransactionCompilerTests.cpp | 4 +- tests/chains/Aptos/AddressTests.cpp | 2 +- tests/chains/Aptos/CompilerTests.cpp | 8 +- tests/chains/Aptos/TWAnySignerTests.cpp | 4 +- .../chains/BinanceSmartChain/SignerTests.cpp | 2 +- .../BinanceSmartChain/TWAnyAddressTests.cpp | 2 +- tests/chains/Bitcoin/MessageSignerTests.cpp | 4 +- tests/chains/Bitcoin/TWAnyAddressTests.cpp | 2 +- tests/chains/Bitcoin/TWBitcoinPsbtTests.cpp | 2 +- .../chains/Bitcoin/TWBitcoinSigningTests.cpp | 62 +-- .../Bitcoin/TransactionCompilerTests.cpp | 8 +- tests/chains/Bitcoin/TxComparisonHelper.cpp | 2 +- .../chains/BitcoinCash/TWBitcoinCashTests.cpp | 2 +- tests/chains/BitcoinDiamond/SignerTests.cpp | 6 +- .../BitcoinDiamond/TWAnySignerTests.cpp | 2 +- tests/chains/BitcoinGold/TWAddressTests.cpp | 2 +- tests/chains/Cardano/AddressTests.cpp | 14 +- tests/chains/Cardano/SigningTests.cpp | 10 +- tests/chains/Cardano/StakingTests.cpp | 10 +- .../chains/Cardano/TWCardanoAddressTests.cpp | 2 +- tests/chains/Cardano/VoteDelegationTests.cpp | 8 +- tests/chains/Cosmos/AddressTests.cpp | 4 +- tests/chains/Cosmos/CosmosTestHelpers.h | 2 +- .../TransactionCompilerTests.cpp | 2 +- tests/chains/Cosmos/SignerTests.cpp | 2 +- tests/chains/Cosmos/THORChain/SwapTests.cpp | 16 +- tests/chains/Cosmos/THORChain/TWSwapTests.cpp | 2 +- .../Cosmos/TransactionCompilerTests.cpp | 2 +- tests/chains/Decred/AddressTests.cpp | 2 +- tests/chains/Decred/SignerTests.cpp | 10 +- .../Decred/TransactionCompilerTests.cpp | 2 +- tests/chains/ECash/TWECashTests.cpp | 2 +- tests/chains/EOS/AddressTests.cpp | 2 +- tests/chains/EOS/TransactionCompilerTests.cpp | 2 +- tests/chains/EOS/TransactionTests.cpp | 6 +- tests/chains/Ethereum/AddressTests.cpp | 2 +- .../Ethereum/EthereumMessageSignerTests.cpp | 26 +- tests/chains/Ethereum/TWAnySignerTests.cpp | 4 +- tests/chains/Everscale/AddressTests.cpp | 2 +- .../chains/Evmos/TransactionCompilerTests.cpp | 2 +- tests/chains/FIO/AddressTests.cpp | 2 +- tests/chains/FIO/EncryptionTests.cpp | 14 +- tests/chains/FIO/SignerTests.cpp | 5 +- tests/chains/FIO/TWFIOTests.cpp | 4 +- tests/chains/FIO/TransactionBuilderTests.cpp | 3 +- tests/chains/FIO/TransactionCompilerTests.cpp | 2 +- tests/chains/Filecoin/SignerTests.cpp | 5 +- .../Filecoin/TransactionCompilerTests.cpp | 4 +- tests/chains/Filecoin/TransactionTests.cpp | 4 +- tests/chains/Firo/TWFiroAddressTests.cpp | 2 +- .../Greenfield/TransactionCompilerTests.cpp | 4 +- tests/chains/Groestlcoin/AddressTests.cpp | 2 +- .../Groestlcoin/TWGroestlcoinSigningTests.cpp | 2 +- .../chains/Groestlcoin/TWGroestlcoinTests.cpp | 2 +- .../Groestlcoin/TransactionCompilerTests.cpp | 2 +- tests/chains/Harmony/AddressTests.cpp | 3 +- tests/chains/Harmony/SignerTests.cpp | 13 +- tests/chains/Harmony/StakingTests.cpp | 2 +- .../chains/Harmony/TWHarmonyStakingTests.cpp | 2 +- .../Harmony/TransactionCompilerTests.cpp | 2 +- tests/chains/Hedera/SignerTests.cpp | 7 +- tests/chains/Hedera/TWAnySignerTests.cpp | 2 +- tests/chains/ICON/AddressTests.cpp | 3 +- .../chains/ICON/TransactionCompilerTests.cpp | 4 +- .../chains/IOST/TransactionCompilerTests.cpp | 2 +- tests/chains/ImmutableX/StarkKeyTests.cpp | 6 +- tests/chains/IoTeX/AddressTests.cpp | 3 +- tests/chains/IoTeX/SignerTests.cpp | 6 +- .../chains/IoTeX/TransactionCompilerTests.cpp | 8 +- tests/chains/Kusama/TWAnyAddressTests.cpp | 2 +- tests/chains/Litecoin/TWLitecoinTests.cpp | 4 +- .../Monacoin/TWMonacoinAddressTests.cpp | 4 +- tests/chains/MultiversX/AddressTests.cpp | 4 +- tests/chains/MultiversX/SignerTests.cpp | 40 +- tests/chains/MultiversX/TWAnySignerTests.cpp | 2 +- .../MultiversX/TransactionCompilerTests.cpp | 6 +- tests/chains/NEAR/AddressTests.cpp | 6 +- tests/chains/NEO/AddressTests.cpp | 2 +- tests/chains/NEO/SignerTests.cpp | 8 +- tests/chains/NEO/TransactionCompilerTests.cpp | 2 +- tests/chains/NULS/AddressTests.cpp | 6 +- tests/chains/NULS/TWAnySignerTests.cpp | 2 +- tests/chains/Nano/SignerTests.cpp | 26 +- .../chains/Nano/TransactionCompilerTests.cpp | 2 +- tests/chains/Nebl/AddressTests.cpp | 2 +- tests/chains/Nebl/SignerTests.cpp | 4 +- tests/chains/Nebl/TWAnySignerTests.cpp | 2 +- tests/chains/Nebl/TransactionBuilderTests.cpp | 2 +- .../chains/Nebl/TransactionCompilerTests.cpp | 2 +- tests/chains/Nebulas/AddressTests.cpp | 4 +- .../chains/Nebulas/TWNebulasAddressTests.cpp | 2 +- tests/chains/Nebulas/TransactionTests.cpp | 4 +- tests/chains/Nervos/AddressTests.cpp | 4 +- tests/chains/Nervos/SignerTests.cpp | 2 +- tests/chains/Nervos/TWAnyAddressTests.cpp | 2 +- tests/chains/Nimiq/SignerTests.cpp | 5 +- tests/chains/Nimiq/TransactionTests.cpp | 5 +- tests/chains/Oasis/AddressTests.cpp | 4 +- .../chains/Oasis/TransactionCompilerTests.cpp | 2 +- tests/chains/Ontology/AccountTests.cpp | 3 +- tests/chains/Ontology/AddressTests.cpp | 8 +- tests/chains/Ontology/Oep4Tests.cpp | 13 +- tests/chains/Ontology/OngTests.cpp | 10 +- tests/chains/Ontology/OntTests.cpp | 8 +- tests/chains/Ontology/TransactionTests.cpp | 5 +- tests/chains/Pactus/AddressTests.cpp | 2 +- tests/chains/Pactus/CompilerTests.cpp | 4 +- tests/chains/Pactus/SignerTests.cpp | 2 +- tests/chains/Polkadot/TWAnyAddressTests.cpp | 2 +- tests/chains/Polkadot/TWAnySignerTests.cpp | 4 +- tests/chains/Polymesh/TWAnyAddressTests.cpp | 2 +- tests/chains/Qtum/TWQtumAddressTests.cpp | 4 +- tests/chains/Solana/AddressTests.cpp | 3 +- tests/chains/StarkEx/MessageSignerTests.cpp | 4 +- tests/chains/Stellar/AddressTests.cpp | 3 +- tests/chains/Stellar/TWAnySignerTests.cpp | 12 +- .../Stellar/TransactionCompilerTests.cpp | 2 +- tests/chains/Stellar/TransactionTests.cpp | 10 +- tests/chains/Stratis/TWStratisTests.cpp | 4 +- tests/chains/Sui/CompilerTests.cpp | 4 +- tests/chains/Sui/SignerTests.cpp | 2 +- tests/chains/Syscoin/TWSyscoinTests.cpp | 4 +- tests/chains/Tezos/ForgingTests.cpp | 4 +- tests/chains/Tezos/MessageSignerTests.cpp | 4 +- tests/chains/Tezos/SignerTests.cpp | 4 +- .../chains/Tezos/TransactionCompilerTests.cpp | 2 +- .../TWTONMessageSignerTests.cpp | 2 +- tests/chains/Theta/SignerTests.cpp | 2 +- .../chains/Theta/TransactionCompilerTests.cpp | 2 +- tests/chains/Tron/AddressTests.cpp | 7 +- tests/chains/Tron/SerializationTests.cpp | 13 +- tests/chains/Tron/SignerTests.cpp | 35 +- .../chains/Tron/TransactionCompilerTests.cpp | 2 +- tests/chains/Tron/TronMessageSignerTests.cpp | 20 +- tests/chains/VeChain/SignerTests.cpp | 2 +- .../VeChain/TransactionCompilerTests.cpp | 2 +- tests/chains/Verge/AddressTests.cpp | 2 +- tests/chains/Verge/SignerTests.cpp | 6 +- tests/chains/Verge/TWAnySignerTests.cpp | 2 +- .../chains/Verge/TransactionBuilderTests.cpp | 2 +- .../chains/Viacoin/TWViacoinAddressTests.cpp | 4 +- tests/chains/WAX/TWAnySignerTests.cpp | 2 +- tests/chains/Waves/AddressTests.cpp | 2 +- tests/chains/Waves/LeaseTests.cpp | 8 +- tests/chains/Waves/SignerTests.cpp | 2 +- tests/chains/Waves/TransactionTests.cpp | 6 +- tests/chains/XRP/TransactionCompilerTests.cpp | 4 +- tests/chains/Zcash/AddressTests.cpp | 4 +- tests/chains/Zcash/TWZcashAddressTests.cpp | 2 +- .../chains/Zcash/TWZcashTransactionTests.cpp | 2 +- .../chains/Zcash/TransactionCompilerTests.cpp | 2 +- .../chains/Zelcash/TWZelcashAddressTests.cpp | 2 +- tests/chains/Zen/AddressTests.cpp | 2 +- tests/chains/Zen/SignerTests.cpp | 2 +- tests/chains/Zen/TWAnySignerTests.cpp | 2 +- tests/chains/Zen/TransactionBuilderTests.cpp | 2 +- tests/chains/Zilliqa/AddressTests.cpp | 2 +- tests/chains/Zilliqa/SignatureTests.cpp | 2 +- tests/chains/Zilliqa/SignerTests.cpp | 6 +- tests/common/Bech32AddressTests.cpp | 14 +- tests/common/CoinAddressDerivationTests.cpp | 4 +- .../common/HDWallet/HDWalletInternalTests.cpp | 2 +- tests/common/PrivateKeyTests.cpp | 58 +- tests/common/PublicKeyTests.cpp | 40 +- tests/interface/TWPrivateKeyTests.cpp | 23 +- tests/interface/TWPublicKeyTests.cpp | 25 +- .../interface/TWTransactionCompilerTests.cpp | 4 +- walletconsole/lib/Address.cpp | 2 +- walletconsole/lib/Keys.cpp | 2 +- wasm/tests/Blockchain/Aptos.test.ts | 3 +- wasm/tests/Blockchain/Bitcoin.test.ts | 16 +- wasm/tests/Blockchain/Ethereum.test.ts | 10 +- wasm/tests/Blockchain/Hedera.test.ts | 3 +- .../tests/Blockchain/InternetComputer.test.ts | 4 +- wasm/tests/Blockchain/Ripple.test.ts | 5 +- wasm/tests/Blockchain/TheOpenNetwork.test.ts | 12 +- wasm/tests/CoinType.test.ts | 2 +- wasm/tests/PrivateKey.test.ts | 26 + 406 files changed, 1910 insertions(+), 1007 deletions(-) create mode 100644 rust/tw_evm/src/abi/prebuild/resource/erc4337.biz_account.abi.json create mode 100644 wasm/tests/PrivateKey.test.ts diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/acala/TestAcalaAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/acala/TestAcalaAddress.kt index 5e50f515ab4..3ad6a63eb03 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/acala/TestAcalaAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/acala/TestAcalaAddress.kt @@ -18,7 +18,7 @@ class TestAcalaAddress { @Test fun testAddress() { - val key = PrivateKey("0x9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0".toHexByteArray()) + val key = PrivateKey("0x9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0".toHexByteArray(), CoinType.ACALA.curve()) val pubkey = key.publicKeyEd25519 val address = AnyAddress(pubkey, CoinType.ACALA) assertEquals(address.description(), "269ZCS3WLGydTN8ynhyhZfzJrXkePUcdhwgLQs6TWFs5wVL5") diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/acalaevm/TestAcalaEVMAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/acalaevm/TestAcalaEVMAddress.kt index 6845bea3e89..7170c4995ae 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/acalaevm/TestAcalaEVMAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/acalaevm/TestAcalaEVMAddress.kt @@ -17,7 +17,7 @@ class TestAcalaEVMAddress { @Test fun testAddress() { - val key = PrivateKey("828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0".toHexByteArray()) + val key = PrivateKey("828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0".toHexByteArray(), CoinType.ACALAEVM.curve()) val pubkey = key.getPublicKeySecp256k1(false) val address = AnyAddress(pubkey, CoinType.ACALAEVM) val expected = AnyAddress("0xe32DC46bfBF78D1eada7b0a68C96903e01418D64", CoinType.ACALAEVM) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/agoric/TestAgoricAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/agoric/TestAgoricAddress.kt index 1164db16e14..f905b9e5397 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/agoric/TestAgoricAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/agoric/TestAgoricAddress.kt @@ -19,7 +19,7 @@ class TestAgoricAddress { @Test fun testAddress() { - val key = PrivateKey("037048190544fa57651452f477c096de4f3073e7835cf3845b04602563a73f73".toHexByteArray()) + val key = PrivateKey("037048190544fa57651452f477c096de4f3073e7835cf3845b04602563a73f73".toHexByteArray(), CoinType.AGORIC.curve()) val pubkey = key.getPublicKeySecp256k1(true) val address = AnyAddress(pubkey, CoinType.AGORIC) val expected = AnyAddress("agoric18zvvgk6j3eq5wd7mqxccgt20gz2w94cy88aek5", CoinType.AGORIC) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/agoric/TestAgoricSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/agoric/TestAgoricSigner.kt index ecdf4372f3a..fe6be0c5c81 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/agoric/TestAgoricSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/agoric/TestAgoricSigner.kt @@ -22,7 +22,7 @@ class TestAgoricSigner { @Test fun AgoricTransactionSigning() { - val key = PrivateKey("037048190544fa57651452f477c096de4f3073e7835cf3845b04602563a73f73".toHexByteArray()) + val key = PrivateKey("037048190544fa57651452f477c096de4f3073e7835cf3845b04602563a73f73".toHexByteArray(), CoinType.AGORIC.curve()) val publicKey = key.getPublicKeySecp256k1(true) val from = AnyAddress(publicKey, CoinType.AGORIC).description() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aion/TestAion.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aion/TestAion.kt index 8ecbff6e286..2b8c07debc3 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aion/TestAion.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/aion/TestAion.kt @@ -22,7 +22,7 @@ class TestAionAddress { @Test fun testAddressFromPublicKey() { - val privateKey = PrivateKey("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9".toHexByteArray()) + val privateKey = PrivateKey("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9".toHexByteArray(), CoinType.AION.curve()) val publicKey = privateKey.getPublicKeyEd25519() val address = AnyAddress(publicKey, CoinType.AION) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/algorand/TestAlgorandAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/algorand/TestAlgorandAddress.kt index 8883b93acc4..86d27a5611a 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/algorand/TestAlgorandAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/algorand/TestAlgorandAddress.kt @@ -14,7 +14,7 @@ class TestAlgorandAddress { @Test fun testAddress() { - val key = PrivateKey("a6c4394041e64fe93d889386d7922af1b9a87f12e433762759608e61434d6cf7".toHexByteArray()) + val key = PrivateKey("a6c4394041e64fe93d889386d7922af1b9a87f12e433762759608e61434d6cf7".toHexByteArray(), CoinType.ALGORAND.curve()) val pubkey = key.publicKeyEd25519 val address = AnyAddress(pubkey, CoinType.ALGORAND) val expected = AnyAddress("ADIYK65L3XR5ODNNCUIQVEET455L56MRKJHRBX5GU4TZI2752QIWK4UL5A", CoinType.ALGORAND) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainAddress.kt index ec361735357..b0ebff4c18c 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainAddress.kt @@ -19,7 +19,7 @@ class TestBandChainAddress { @Test fun testAddress() { - val key = PrivateKey("1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6".toHexByteArray()) + val key = PrivateKey("1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6".toHexByteArray(), CoinType.BANDCHAIN.curve()) val publicKey = key.getPublicKeySecp256k1(true) val address = AnyAddress(publicKey, CoinType.BANDCHAIN) val expected = AnyAddress("band1jf9aaj9myrzsnmpdr7twecnaftzmku2mgms4n3", CoinType.BANDCHAIN) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainSigner.kt index 001e9d90631..5b256ec2b11 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bandchain/TestBandChainSigner.kt @@ -10,6 +10,7 @@ import org.junit.Assert.assertEquals import org.junit.Test import wallet.core.java.AnySigner import wallet.core.jni.AnyAddress +import wallet.core.jni.CoinType import wallet.core.jni.CoinType.BANDCHAIN import wallet.core.jni.PrivateKey import wallet.core.jni.proto.Cosmos @@ -24,7 +25,7 @@ class TestBandChainSigner { @Test fun testSigningTransaction() { val key = - PrivateKey("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005".toHexByteArray()) + PrivateKey("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005".toHexByteArray(), CoinType.BANDCHAIN.curve()) val publicKey = key.getPublicKeySecp256k1(true) val from = AnyAddress(publicKey, BANDCHAIN).description() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binance/TestBinanceTransactionSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binance/TestBinanceTransactionSigning.kt index 011c8e08656..188fa8ce66d 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binance/TestBinanceTransactionSigning.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/binance/TestBinanceTransactionSigning.kt @@ -17,11 +17,11 @@ class TestBinanceTransactionSigning { System.loadLibrary("TrustWalletCore") } - val testKey = PrivateKey("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d".toHexBytes()) + val testKey = PrivateKey("eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d".toHexBytes(), BINANCE.curve()) @Test fun testSignBinanceTransaction() { - val privateKey = PrivateKey("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832".toHexBytes()) + val privateKey = PrivateKey("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832".toHexBytes(), BINANCE.curve()) val publicKey = privateKey.getPublicKeySecp256k1(true) val signingInput = Binance.SigningInput.newBuilder() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinPsbt.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinPsbt.kt index 884120800ca..973be37964b 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinPsbt.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinPsbt.kt @@ -64,7 +64,7 @@ class TestBitcoinPsbt { fun testPlanThorSwap() { // Successfully broadcasted tx: https://mempool.space/tx/634a416e82ac710166725f6a4090ac7b5db69687e86b2d2e38dcb3d91c956c32 - val privateKey = PrivateKey("f00ffbe44c5c2838c13d2778854ac66b75e04eb6054f0241989e223223ad5e55".toHexBytes()) + val privateKey = PrivateKey("f00ffbe44c5c2838c13d2778854ac66b75e04eb6054f0241989e223223ad5e55".toHexBytes(), CoinType.BITCOIN.curve()) val publicKey = privateKey.getPublicKeySecp256k1(true) val psbt = "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d00000000".toHexBytesInByteString() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt index 40b94298cd7..5802b34602f 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoin/TestBitcoinSigning.kt @@ -169,7 +169,7 @@ class TestBitcoinSigning { val dustSatoshis = 546.toLong() val txId = Numeric.hexStringToByteArray("8ec895b4d30adb01e38471ca1019bfc8c3e5fbd1f28d9e7b5653260d89989008").reversedArray() - val privateKey = PrivateKey(privateKeyData) + val privateKey = PrivateKey(privateKeyData, CoinType.BITCOIN.curve()) val publicKey = ByteString.copyFrom(privateKey.getPublicKeySecp256k1(true).data()) val utxo0 = BitcoinV2.Input.newBuilder() @@ -235,7 +235,7 @@ class TestBitcoinSigning { val dustSatoshis = 546.toLong() val txIdCommit = Numeric.hexStringToByteArray("797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1").reversedArray() - val privateKey = PrivateKey(privateKeyData) + val privateKey = PrivateKey(privateKeyData, CoinType.BITCOIN.curve()) val publicKey = ByteString.copyFrom(privateKey.getPublicKeySecp256k1(true).data()) val utxo0 = BitcoinV2.Input.newBuilder() @@ -295,7 +295,7 @@ class TestBitcoinSigning { val txIdInscription = Numeric.hexStringToByteArray("7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca").reversedArray() val txIdForFees = Numeric.hexStringToByteArray("797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1").reversedArray() - val privateKey = PrivateKey(privateKeyData) + val privateKey = PrivateKey(privateKeyData, CoinType.BITCOIN.curve()) val publicKey = ByteString.copyFrom(privateKey.getPublicKeySecp256k1(true).data()) val bobAddress = "bc1qazgc2zhu2kmy42py0vs8d7yff67l3zgpwfzlpk" diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoindiamond/TestBitcoinDiamondAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoindiamond/TestBitcoinDiamondAddress.kt index 497b2d6dedc..7463027119d 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoindiamond/TestBitcoinDiamondAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bitcoindiamond/TestBitcoinDiamondAddress.kt @@ -18,7 +18,7 @@ class TestBitcoinDiamondAddress { @Test fun testAddress() { - val key = PrivateKey("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee".toHexByteArray()) + val key = PrivateKey("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee".toHexByteArray(), CoinType.BITCOINDIAMOND.curve()) val pubkey = key.getPublicKeySecp256k1(true); val address = AnyAddress(pubkey, CoinType.BITCOINDIAMOND) val expected = AnyAddress("1G15VvshDxwFTnahZZECJfFwEkq9fP79o8", CoinType.BITCOINDIAMOND) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleAddress.kt index d8eb3424893..97ffbbab724 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleAddress.kt @@ -19,7 +19,7 @@ class TestBluzelleAddress { @Test fun testAddressPublicKey() { - val key = PrivateKey("1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6".toHexByteArray()) + val key = PrivateKey("1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6".toHexByteArray(), CoinType.BLUZELLE.curve()) val publicKey = key.getPublicKeySecp256k1(true) val expectedAddress = "bluzelle1jf9aaj9myrzsnmpdr7twecnaftzmku2myvn4dg" val actualAddress = AnyAddress(publicKey, CoinType.BLUZELLE).description() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleSigner.kt index bf40f0dcc67..01751a8b32c 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/bluzelle/TestBluzelleSigner.kt @@ -13,6 +13,7 @@ import org.junit.Assert.assertEquals import org.junit.Test import wallet.core.java.AnySigner import wallet.core.jni.AnyAddress +import wallet.core.jni.CoinType import wallet.core.jni.CoinType.BLUZELLE import wallet.core.jni.PrivateKey import wallet.core.jni.proto.Cosmos @@ -29,7 +30,7 @@ class TestBluzelleSigner { // Submitted Realworld tx for the following test : https://bigdipper.net.bluzelle.com/transactions/B3A7F30539CCDF72D210BC995FAF65B43F9BE04FA9F8AFAE0EC969660744002F val key = - PrivateKey("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005".toHexByteArray()) + PrivateKey("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005".toHexByteArray(), CoinType.BLUZELLE.curve()) val publicKey = key.getPublicKeySecp256k1(true) val from = AnyAddress(publicKey, BLUZELLE).description() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoAddress.kt index a505e201176..ac2d43e801d 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoAddress.kt @@ -18,7 +18,7 @@ class TestCardanoAddress { @Test fun testAddress() { - val key = PrivateKey("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7bed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a".toHexByteArray()) + val key = PrivateKey("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7bed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a".toHexByteArray(), CoinType.CARDANO.curve()) val pubkey = key.publicKeyEd25519Cardano val address = AnyAddress(pubkey, CoinType.CARDANO) val expected = AnyAddress("addr1qx4z6twzknkkux0hhp0kq6hvdfutczp56g56y5em8r8mgvxalp7nkkk25vuspleke2zltaetmlwrfxv7t049cq9jmwjswmfw6t", CoinType.CARDANO) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoSigning.kt index d9450e9881c..25d61fb03bf 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoSigning.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cardano/TestCardanoSigning.kt @@ -78,7 +78,7 @@ class TestCardanoSigning { /// https://cardanoscan.io/transaction/0203ce2c91f59f169a26e9ef91254639d2b7911afac9c7c0ae64539f88ba46a5 @Test fun testSignTransferFromLegacy() { - val privateKey = PrivateKey("98f266d1aac660179bc2f456033941238ee6b2beb8ed0f9f34c9902816781f5a9903d1d395d6ab887b65ea5e344ef09b449507c21a75f0ce8c59d0ed1c6764eba7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f4e890ca4eb6bec44bf751b5a843174534af64d6ad1f44e0613db78a7018781f5aa151d2997f52059466b715d8eefab30a78b874ae6ef4931fa58bb21ef8ce2423d46f19d0fbf75afb0b9a24e31d533f4fd74cee3b56e162568e8defe37123afc4".toHexByteArray()) + val privateKey = PrivateKey("98f266d1aac660179bc2f456033941238ee6b2beb8ed0f9f34c9902816781f5a9903d1d395d6ab887b65ea5e344ef09b449507c21a75f0ce8c59d0ed1c6764eba7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f4e890ca4eb6bec44bf751b5a843174534af64d6ad1f44e0613db78a7018781f5aa151d2997f52059466b715d8eefab30a78b874ae6ef4931fa58bb21ef8ce2423d46f19d0fbf75afb0b9a24e31d533f4fd74cee3b56e162568e8defe37123afc4".toHexByteArray(), CoinType.CARDANO.curve()) var publicKey = privateKey.publicKeyEd25519Cardano var byronAddress = wallet.core.jni.Cardano.getByronAddress(publicKey) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/confluxespace/TestConfluxeSpaceAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/confluxespace/TestConfluxeSpaceAddress.kt index 25d5ae1c81e..d45cad3978f 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/confluxespace/TestConfluxeSpaceAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/confluxespace/TestConfluxeSpaceAddress.kt @@ -17,7 +17,7 @@ class TestConfluxeSpaceAddress { @Test fun testAddress() { - val key = PrivateKey("828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0".toHexByteArray()) + val key = PrivateKey("828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0".toHexByteArray(), CoinType.CONFLUXESPACE.curve()) val pubkey = key.getPublicKeySecp256k1(false) val address = AnyAddress(pubkey, CoinType.CONFLUXESPACE) val expected = AnyAddress("0xe32DC46bfBF78D1eada7b0a68C96903e01418D64", CoinType.CONFLUXESPACE) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt index 4de24f9b60f..b9573a8225e 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cosmos/TestCosmosTransactions.kt @@ -22,7 +22,7 @@ class TestCosmosTransactions { @Test fun testAuthStakingTransaction() { val key = - PrivateKey("c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc".toHexByteArray()) + PrivateKey("c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc".toHexByteArray(), CoinType.COSMOS.curve()) val stakeAuth = Cosmos.Message.StakeAuthorization.newBuilder().apply { allowList = Cosmos.Message.StakeAuthorization.Validators.newBuilder().apply { @@ -75,7 +75,7 @@ class TestCosmosTransactions { @Test fun testRemoveAuthStakingTransaction() { val key = - PrivateKey("c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc".toHexByteArray()) + PrivateKey("c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc".toHexByteArray(), CoinType.COSMOS.curve()) val removeAuthStakingMsg = Cosmos.Message.AuthRevoke.newBuilder().apply { grantee = "cosmos1fs7lu28hx5m9akm7rp0c2422cn8r2f7gurujhf" @@ -120,7 +120,7 @@ class TestCosmosTransactions { @Test fun testSigningTransaction() { val key = - PrivateKey("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005".toHexByteArray()) + PrivateKey("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005".toHexByteArray(), CoinType.COSMOS.curve()) val publicKey = key.getPublicKeySecp256k1(true) val from = AnyAddress(publicKey, COSMOS).description() @@ -196,7 +196,7 @@ class TestCosmosTransactions { } """ val key = - "c9b0a273831931aa4a5f8d1a570d5021dda91d3319bd3819becdaabfb7b44e3b".toHexByteArray() + PrivateKey("c9b0a273831931aa4a5f8d1a570d5021dda91d3319bd3819becdaabfb7b44e3b".toHexByteArray(), CoinType.COSMOS.curve()).data() val result = AnySigner.signJSON(json, key, COSMOS.value()) assertEquals( result, diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgAddress.kt index 214c391624d..b62909937f6 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgAddress.kt @@ -18,7 +18,7 @@ class TestCryptoorgAddress { @Test fun testAddress() { - val key = PrivateKey("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e".toHexByteArray()) + val key = PrivateKey("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e".toHexByteArray(), CoinType.CRYPTOORG.curve()) val pubkey = key.getPublicKeySecp256k1(true) val address = AnyAddress(pubkey, CoinType.CRYPTOORG) val expected = AnyAddress("cro1z53wwe7md6cewz9sqwqzn0aavpaun0gw39h3rd", CoinType.CRYPTOORG) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgSigner.kt index 74090e73a29..97748349c63 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/cryptoorg/TestCryptoorgSigner.kt @@ -27,7 +27,7 @@ class TestCryptoorgSigner { @Test fun CryptoorgTransactionSigning() { - val key = PrivateKey("200e439e39cf1aad465ee3de6166247f914cbc0f823fc2dd48bf16dcd556f39d".toHexByteArray()) + val key = PrivateKey("200e439e39cf1aad465ee3de6166247f914cbc0f823fc2dd48bf16dcd556f39d".toHexByteArray(), CoinType.CRYPTOORG.curve()) val publicKey = key.getPublicKeySecp256k1(true) val from = AnyAddress(publicKey, CRYPTOORG).description() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/dydx/TestDydxAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/dydx/TestDydxAddress.kt index 408f8fb4ae3..213439ec841 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/dydx/TestDydxAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/dydx/TestDydxAddress.kt @@ -17,7 +17,7 @@ class TestDydxAddress { @Test fun testAddress() { - val key = PrivateKey("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433".toHexByteArray()) + val key = PrivateKey("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433".toHexByteArray(), CoinType.DYDX.curve()) val pubKey = key.getPublicKeySecp256k1(true) val address = AnyAddress(pubKey, CoinType.DYDX) val expected = AnyAddress("dydx1mry47pkga5tdswtluy0m8teslpalkdq0hc72uz", CoinType.DYDX) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt index ff31c515533..64e421d5559 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt @@ -99,7 +99,7 @@ class TestBarz { fun testSignK1TransferAccountDeployed() { val signingInput = Ethereum.SigningInput.newBuilder() signingInput.apply { - privateKey = ByteString.copyFrom(PrivateKey("3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483".toHexByteArray(), CoinType.ETHEREUM.curve()).data()) chainId = ByteString.copyFrom("0x61".toHexByteArray()) nonce = ByteString.copyFrom("0x2".toHexByteArray()) toAddress = "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9" @@ -140,7 +140,7 @@ class TestBarz { val signingInput = Ethereum.SigningInput.newBuilder() signingInput.apply { - privateKey = ByteString.copyFrom(PrivateKey("3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483".toHexByteArray(), CoinType.ETHEREUM.curve()).data()) chainId = ByteString.copyFrom("0x61".toHexByteArray()) nonce = ByteString.copyFrom("0x00".toHexByteArray()) toAddress = "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9" @@ -187,7 +187,7 @@ class TestBarz { val signingInput = Ethereum.SigningInput.newBuilder() signingInput.apply { - privateKey = ByteString.copyFrom(PrivateKey("3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483".toHexByteArray(), CoinType.ETHEREUM.curve()).data()) chainId = ByteString.copyFrom("0x61".toHexByteArray()) nonce = ByteString.copyFrom("0x03".toHexByteArray()) txMode = TransactionMode.UserOp @@ -284,7 +284,7 @@ class TestBarz { // Create signing input val signingInput = Ethereum.SigningInput.newBuilder().apply { - privateKey = ByteString.copyFrom(PrivateKey("3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483".toHexByteArray(), CoinType.ETHEREUM.curve()).data()) chainId = ByteString.copyFrom(chainIdByteArray) // 31337 nonce = ByteString.copyFrom("0x00".toHexByteArray()) txMode = Ethereum.TransactionMode.UserOp diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumMessageSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumMessageSigner.kt index 6245751d124..8a453fa6a63 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumMessageSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumMessageSigner.kt @@ -17,7 +17,7 @@ class TestEthereumMessageSigner { @Test fun testEthereumSignAndVerifyMessageImmutableX() { val data = Numeric.hexStringToByteArray("3b0a61f46fdae924007146eacb6db6642de7a5603ad843ec58e10331d89d4b84") - val privateKey = PrivateKey(data) + val privateKey = PrivateKey(data, CoinType.ETHEREUM.curve()) val publicKey = privateKey.getPublicKey(CoinType.ETHEREUM) val msg = "Only sign this request if you’ve initiated an action with Immutable X.\n\nFor internal use:\nbd717ba31dca6e0f3f136f7c4197babce5f09a9f25176044c0b3112b1b6017a3" val signature = EthereumMessageSigner.signMessageImmutableX(privateKey, msg) @@ -28,7 +28,7 @@ class TestEthereumMessageSigner { @Test fun testEthereumSignAndVerifyMessageLegacy() { val data = Numeric.hexStringToByteArray("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d") - val privateKey = PrivateKey(data) + val privateKey = PrivateKey(data, CoinType.ETHEREUM.curve()) val publicKey = privateKey.getPublicKey(CoinType.ETHEREUM) val msg = "Foo" val signature = EthereumMessageSigner.signMessage(privateKey, msg) @@ -39,7 +39,7 @@ class TestEthereumMessageSigner { @Test fun testEthereumSignAndVerifyMessageLegacyHex() { val data = Numeric.hexStringToByteArray("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0") - val privateKey = PrivateKey(data) + val privateKey = PrivateKey(data, CoinType.ETHEREUM.curve()) val publicKey = privateKey.getPublicKey(CoinType.ETHEREUM) val msg = "0xc0a96273d5c3fbe4d4000491f08daef9c17f88df846c1d6f57eb5f33c1fbd035" val signature = EthereumMessageSigner.signMessage(privateKey, msg) @@ -50,7 +50,7 @@ class TestEthereumMessageSigner { @Test fun testEthereumSignAndVerifyMessage712Legacy() { val data = Numeric.hexStringToByteArray("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d") - val privateKey = PrivateKey(data) + val privateKey = PrivateKey(data, CoinType.ETHEREUM.curve()) val publicKey = privateKey.getPublicKey(CoinType.ETHEREUM) val msg = """ { @@ -87,7 +87,7 @@ class TestEthereumMessageSigner { @Test fun testEthereumSignAndVerifyMessage712Eip155() { val data = Numeric.hexStringToByteArray("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d") - val privateKey = PrivateKey(data) + val privateKey = PrivateKey(data, CoinType.ETHEREUM.curve()) val publicKey = privateKey.getPublicKey(CoinType.ETHEREUM) val msg = """ { @@ -124,7 +124,7 @@ class TestEthereumMessageSigner { @Test fun testEthereumSignAndVerifyMessageEip155() { val data = Numeric.hexStringToByteArray("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d") - val privateKey = PrivateKey(data) + val privateKey = PrivateKey(data, CoinType.ETHEREUM.curve()) val publicKey = privateKey.getPublicKey(CoinType.ETHEREUM) val msg = "Foo" val signature = EthereumMessageSigner.signMessageEip155(privateKey, msg, 0) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumTransactionSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumTransactionSigner.kt index 61a6b9a42df..aeb84e2eb00 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumTransactionSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumTransactionSigner.kt @@ -26,7 +26,7 @@ class TestEthereumTransactionSigner { fun testEthereumTransactionSigning() { val signingInput = Ethereum.SigningInput.newBuilder() signingInput.apply { - privateKey = ByteString.copyFrom(PrivateKey("0x4646464646464646464646464646464646464646464646464646464646464646".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("0x4646464646464646464646464646464646464646464646464646464646464646".toHexByteArray(), CoinType.ETHEREUM.curve()).data()) toAddress = "0x3535353535353535353535353535353535353535" chainId = ByteString.copyFrom("0x1".toHexByteArray()) nonce = ByteString.copyFrom("0x9".toHexByteArray()) @@ -49,7 +49,7 @@ class TestEthereumTransactionSigner { fun testEthereumERC20Signing() { val signingInput = Ethereum.SigningInput.newBuilder() signingInput.apply { - privateKey = ByteString.copyFrom(PrivateKey("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151".toHexByteArray(), CoinType.ETHEREUM.curve()).data()) toAddress = "0x6b175474e89094c44da98b954eedeac495271d0f" // DAI chainId = ByteString.copyFrom("0x1".toHexByteArray()) nonce = ByteString.copyFrom("0x0".toHexByteArray()) @@ -74,7 +74,7 @@ class TestEthereumTransactionSigner { fun testEthereumERC20_1559_Signing() { val signingInput = Ethereum.SigningInput.newBuilder() signingInput.apply { - privateKey = ByteString.copyFrom(PrivateKey("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151".toHexByteArray(), CoinType.ETHEREUM.curve()).data()) toAddress = "0x6b175474e89094c44da98b954eedeac495271d0f" // DAI chainId = ByteString.copyFrom("0x1".toHexByteArray()) nonce = ByteString.copyFrom("0x0".toHexByteArray()) @@ -99,7 +99,7 @@ class TestEthereumTransactionSigner { fun testEthereumERC721Signing() { val signingInput = Ethereum.SigningInput.newBuilder() signingInput.apply { - privateKey = ByteString.copyFrom(PrivateKey("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151".toHexByteArray(), CoinType.ETHEREUM.curve()).data()) toAddress = "0x0d8c864DA1985525e0af0acBEEF6562881827bd5" chainId = ByteString.copyFrom("0x1".toHexByteArray()) nonce = ByteString.copyFrom("0x02de".toHexByteArray()) @@ -125,7 +125,7 @@ class TestEthereumTransactionSigner { fun testEthereumERC1155Signing() { val signingInput = Ethereum.SigningInput.newBuilder() signingInput.apply { - privateKey = ByteString.copyFrom(PrivateKey("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151".toHexByteArray(), CoinType.ETHEREUM.curve()).data()) toAddress = "0x4e45e92ed38f885d39a733c14f1817217a89d425" // contract chainId = ByteString.copyFrom("0x01".toHexByteArray()) nonce = ByteString.copyFrom("0x00".toHexByteArray()) @@ -162,7 +162,7 @@ class TestEthereumTransactionSigner { maxFeePerGas = ByteString.copyFrom("067ef83700".toHexByteArray()) // 27900000000 maxInclusionFeePerGas = ByteString.copyFrom("3b9aca00".toHexByteArray()) // 1000000000 toAddress = "0x2cac916b2a963bf162f076c0a8a4a8200bcfbfb4" // contract - privateKey = ByteString.copyFrom(PrivateKey("9f56448d33de406db1561aae15fce64bdf0e9706ff15c45d4409e8fcbfd1a498".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("9f56448d33de406db1561aae15fce64bdf0e9706ff15c45d4409e8fcbfd1a498".toHexByteArray(), CoinType.ETHEREUM.curve()).data()) transaction = Ethereum.Transaction.newBuilder().apply { transfer = Ethereum.Transaction.Transfer.newBuilder().apply { amount = ByteString.copyFrom("2386f26fc10000".toHexByteArray()) // 0.01 ETH @@ -193,7 +193,7 @@ class TestEthereumTransactionSigner { maxFeePerGas = ByteString.copyFrom("067ef83700".toHexByteArray()) // 27900000000 maxInclusionFeePerGas = ByteString.copyFrom("3b9aca00".toHexByteArray()) // 1000000000 toAddress = "0xae78736Cd615f374D3085123A210448E74Fc6393" // contract - privateKey = ByteString.copyFrom(PrivateKey("9f56448d33de406db1561aae15fce64bdf0e9706ff15c45d4409e8fcbfd1a498".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("9f56448d33de406db1561aae15fce64bdf0e9706ff15c45d4409e8fcbfd1a498".toHexByteArray(), CoinType.ETHEREUM.curve()).data()) transaction = Ethereum.Transaction.newBuilder().apply { contractGeneric = Ethereum.Transaction.ContractGeneric.newBuilder().apply { amount = ByteString.copyFrom("00".toHexByteArray()) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/everscale/TestEverscaleAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/everscale/TestEverscaleAddress.kt index 5922fa9f191..df03c0a7464 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/everscale/TestEverscaleAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/everscale/TestEverscaleAddress.kt @@ -18,7 +18,7 @@ class TestEverscaleAddress { @Test fun testAddress() { - val key = PrivateKey("5b59e0372d19b6355c73fa8cc708fa3301ae2ec21bb6277e8b79d386ccb7846f".toHexByteArray()) + val key = PrivateKey("5b59e0372d19b6355c73fa8cc708fa3301ae2ec21bb6277e8b79d386ccb7846f".toHexByteArray(), CoinType.EVERSCALE.curve()) val pubkey = key.publicKeyEd25519 val address = AnyAddress(pubkey, CoinType.EVERSCALE) val expected = AnyAddress("0:269fee242eb410786abe1777a14785c8bbeb1e34100c7570e17698b36ad66fb0", CoinType.EVERSCALE) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/filecoin/TestFilecoin.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/filecoin/TestFilecoin.kt index 82f292da61d..de1c1c424bf 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/filecoin/TestFilecoin.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/filecoin/TestFilecoin.kt @@ -18,7 +18,7 @@ class TestFilecoin { @Test fun testCreateAddress() { - val privateKey = PrivateKey("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe".toHexByteArray()) + val privateKey = PrivateKey("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe".toHexByteArray(), CoinType.FILECOIN.curve()) val publicKey = privateKey.getPublicKeySecp256k1(false) val address = AnyAddress(publicKey, CoinType.FILECOIN) assertEquals("f1z4a36sc7mfbv4z3qwutblp2flycdui3baffytbq", address.description()) @@ -26,7 +26,7 @@ class TestFilecoin { @Test fun testCreateDelegatedAddress() { - val privateKey = PrivateKey("825d2bb32965764a98338139412c7591ed54c951dd65504cd8ddaeaa0fea7b2a".toHexByteArray()) + val privateKey = PrivateKey("825d2bb32965764a98338139412c7591ed54c951dd65504cd8ddaeaa0fea7b2a".toHexByteArray(), CoinType.FILECOIN.curve()) val publicKey = privateKey.getPublicKeySecp256k1(false) val address = AnyAddress(publicKey, FilecoinAddressType.DELEGATED) assertEquals("f410fvak24cyg3saddajborn6idt7rrtfj2ptauk5pbq", address.description()) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/fio/TestFIOAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/fio/TestFIOAddress.kt index 4a9bef5fedb..04fed1c36eb 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/fio/TestFIOAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/fio/TestFIOAddress.kt @@ -22,7 +22,7 @@ class TestFIOAddress { val addressFromString = AnyAddress("FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o", CoinType.FIO) assertEquals(addressFromString.description(), "FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o") - val key = PrivateKey("ea8eb60b7e5868e218f248e032769020b4fea5dcfd02f2992861eaf4fb534854".toHexByteArray()) + val key = PrivateKey("ea8eb60b7e5868e218f248e032769020b4fea5dcfd02f2992861eaf4fb534854".toHexByteArray(), CoinType.FIO.curve()) val pubkey = key.getPublicKeySecp256k1(false) val addressFromKey = AnyAddress(pubkey, CoinType.FIO) assertEquals(addressFromKey.description(), "FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o") diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/fio/TestFIOSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/fio/TestFIOSigner.kt index 6992aa0b9f8..3e3402e2da8 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/fio/TestFIOSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/fio/TestFIOSigner.kt @@ -26,7 +26,7 @@ class TestFIOSigner { @Test fun testRegisterFioAddress() { val chainId = ByteString.copyFrom("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77".toHexBytes()) - val privateKey = PrivateKey("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035".toHexByteArray()) + val privateKey = PrivateKey("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035".toHexByteArray(), CoinType.FIO.curve()) val publicKey = privateKey.getPublicKeySecp256k1(false) val address = AnyAddress(publicKey, CoinType.FIO) @@ -57,7 +57,7 @@ class TestFIOSigner { @Test fun testAddPubAddress() { val chainId = ByteString.copyFrom("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77".toHexBytes()) - val privateKey = PrivateKey("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035".toHexByteArray()) + val privateKey = PrivateKey("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035".toHexByteArray(), CoinType.FIO.curve()) val chainParams = FIO.ChainParams.newBuilder() .setChainId(chainId) @@ -88,7 +88,7 @@ class TestFIOSigner { @Test fun testTransfer() { val chainId = ByteString.copyFrom("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77".toHexBytes()) - val privateKey = PrivateKey("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035".toHexByteArray()) + val privateKey = PrivateKey("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035".toHexByteArray(), CoinType.FIO.curve()) val chainParams = FIO.ChainParams.newBuilder() .setChainId(chainId) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/greenfield/TestGreenfieldSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/greenfield/TestGreenfieldSigner.kt index cd7caf5e2ac..71c3c29b4f4 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/greenfield/TestGreenfieldSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/greenfield/TestGreenfieldSigner.kt @@ -24,7 +24,7 @@ class TestGreenfieldSigner { // Successfully broadcasted: https://greenfieldscan.com/tx/ED8508F3C174C4430B8EE718A6D6F0B02A8C516357BE72B1336CF74356529D19 val key = - PrivateKey("825d2bb32965764a98338139412c7591ed54c951dd65504cd8ddaeaa0fea7b2a".toHexByteArray()) + PrivateKey("825d2bb32965764a98338139412c7591ed54c951dd65504cd8ddaeaa0fea7b2a".toHexByteArray(), CoinType.GREENFIELD.curve()) val msgSend = Greenfield.Message.Send.newBuilder().apply { fromAddress = "0xA815ae0b06dC80318121745BE40e7F8c6654e9f3" @@ -74,7 +74,7 @@ class TestGreenfieldSigner { // BSC (parent transaction): https://testnet.bscscan.com/tx/0x7f73c8a362e14e58cb5e0ec17616afc50eff7aa398db472383a6d017c8a5861a val key = - PrivateKey("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0".toHexByteArray()) + PrivateKey("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0".toHexByteArray(), CoinType.GREENFIELD.curve()) val msgTransferOut = Greenfield.Message.BridgeTransferOut.newBuilder().apply { fromAddress = "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1" diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyAddress.kt index 691757d06ca..ea568d4cd5f 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyAddress.kt @@ -14,7 +14,7 @@ class TestHarmonyAddress { @Test fun testAddressFromPrivateKey() { - val key = PrivateKey(Base58.decodeNoCheck("GGzxJ4QmKCXH2juK89RVAmvFAfdUfUARCvxEsBM356vX")) + val key = PrivateKey(Base58.decodeNoCheck("GGzxJ4QmKCXH2juK89RVAmvFAfdUfUARCvxEsBM356vX"), CoinType.HARMONY.curve()) val pubkey = key.getPublicKeySecp256k1(false) val address = AnyAddress(pubkey, CoinType.HARMONY) assertEquals(address.description(), targetAddress) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyStakingDelegateSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyStakingDelegateSigner.kt index 6c411e829f2..684e22173fc 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyStakingDelegateSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyStakingDelegateSigner.kt @@ -6,6 +6,7 @@ import com.trustwallet.core.app.utils.toHexByteArray import org.junit.Assert.assertEquals import org.junit.Test import wallet.core.java.AnySigner +import wallet.core.jni.CoinType import wallet.core.jni.PrivateKey import wallet.core.jni.proto.Harmony import wallet.core.jni.proto.Harmony.SigningOutput @@ -14,7 +15,7 @@ import wallet.core.jni.CoinType.HARMONY class TestHarmonyStakingDelegateSigner { val oneAddress = "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9" - val privateKeyData = ByteString.copyFrom(PrivateKey("4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48".toHexByteArray()).data()) + val privateKeyData = ByteString.copyFrom(PrivateKey("4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48".toHexByteArray(), CoinType.HARMONY.curve()).data()) val pubKeyData = ByteString.copyFrom("b9486167ab9087ab818dc4ce026edb5bf216863364c32e42df2af03c5ced1ad181e7d12f0e6dd5307a73b62247608611".toHexByteArray()) val blsSigData = ByteString.copyFrom("4252b0f1210efb0d5061e8a706a7ea9d62292a7947a975472fb77e1af7278a1c3c2e6eeba73c0581ece398613829940df129f3071c9a24b4b448bb1e880dc5872a58cb07eed94294c4e01a5c864771cafef7b96be541cb3c521a98f01838dd94".toHexByteArray()) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyTransactionSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyTransactionSigner.kt index c0a9af1bd37..906e5c30b3e 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyTransactionSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/harmony/TestHarmonyTransactionSigner.kt @@ -9,6 +9,7 @@ import wallet.core.java.AnySigner import wallet.core.jni.proto.Harmony import wallet.core.jni.proto.Harmony.SigningOutput import com.trustwallet.core.app.utils.Numeric +import wallet.core.jni.CoinType import wallet.core.jni.CoinType.HARMONY class TestHarmonyTransactionSigner { @@ -31,7 +32,7 @@ class TestHarmonyTransactionSigner { } val signingInput = Harmony.SigningInput.newBuilder() signingInput.apply { - privateKey = ByteString.copyFrom(PrivateKey("0xb578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("0xb578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e".toHexByteArray(), CoinType.HARMONY.curve()).data()) chainId = ByteString.copyFrom("0x02".toHexByteArray()) transactionMessage = transaction.build() } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerAddress.kt index 2861aea3a4a..b5904e9a991 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerAddress.kt @@ -18,7 +18,7 @@ class TestInternetComputerAddress { @Test fun testAddress() { - val key = PrivateKey("ee42eaada903e20ef6e5069f0428d552475c1ea7ed940842da6448f6ef9d48e7".toHexByteArray()) + val key = PrivateKey("ee42eaada903e20ef6e5069f0428d552475c1ea7ed940842da6448f6ef9d48e7".toHexByteArray(), CoinType.INTERNETCOMPUTER.curve()) val pubkey = key.getPublicKeySecp256k1(false); val address = AnyAddress(pubkey, CoinType.INTERNETCOMPUTER) val expected = AnyAddress("2f25874478d06cf68b9833524a6390d0ba69c566b02f46626979a3d6a4153211", CoinType.INTERNETCOMPUTER) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerSigner.kt index d3e97de972e..4e1f635967f 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/internetcomputer/TestInternetComputerSigner.kt @@ -25,7 +25,7 @@ class TestInternetComputerSigner { @Test fun InternetComputerTransactionSigning() { - val key = PrivateKey("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be".toHexByteArray()) + val key = PrivateKey("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be".toHexByteArray(), CoinType.INTERNETCOMPUTER.curve()) val input = InternetComputer.SigningInput.newBuilder() .setTransaction(InternetComputer.Transaction.newBuilder().apply { @@ -43,7 +43,7 @@ class TestInternetComputerSigner { @Test fun InternetComputerTransactionSigningWithInvalidToAccountIdentifier() { - val key = PrivateKey("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be".toHexByteArray()) + val key = PrivateKey("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be".toHexByteArray(), CoinType.INTERNETCOMPUTER.curve()) val input = InternetComputer.SigningInput.newBuilder() .setTransaction(InternetComputer.Transaction.newBuilder().apply { @@ -61,7 +61,7 @@ class TestInternetComputerSigner { @Test fun InternetComputerTransactionSigningWithInvalidAmount() { - val key = PrivateKey("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be".toHexByteArray()) + val key = PrivateKey("227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be".toHexByteArray(), CoinType.INTERNETCOMPUTER.curve()) val input = InternetComputer.SigningInput.newBuilder() .setTransaction(InternetComputer.Transaction.newBuilder().apply { diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kava/TestKavaTransactions.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kava/TestKavaTransactions.kt index b715150a080..dc06c59b8de 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kava/TestKavaTransactions.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kava/TestKavaTransactions.kt @@ -19,7 +19,7 @@ class TestKavaTransactions { @Test fun testSigningTransaction() { - val key = PrivateKey("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005".toHexByteArray()) + val key = PrivateKey("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005".toHexByteArray(), CoinType.KAVA.curve()) val publicKey = key.getPublicKeySecp256k1(true) val from = AnyAddress(publicKey, KAVA).description() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kcc/TestKuCoinCommunityChainAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kcc/TestKuCoinCommunityChainAddress.kt index 9a2ecfc3811..b90df357b37 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kcc/TestKuCoinCommunityChainAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kcc/TestKuCoinCommunityChainAddress.kt @@ -18,7 +18,7 @@ class TestKuCoinCommunityChainAddress { @Test fun testAddress() { - val key = PrivateKey("33b85056aabab539bcb68540735ecf054e38bc58b29b751530e2b54ecb4ca564".toHexByteArray()) + val key = PrivateKey("33b85056aabab539bcb68540735ecf054e38bc58b29b751530e2b54ecb4ca564".toHexByteArray(), CoinType.KUCOINCOMMUNITYCHAIN.curve()) val pubkey = key.getPublicKeySecp256k1(false) val address = AnyAddress(pubkey, CoinType.KUCOINCOMMUNITYCHAIN) val expected = AnyAddress("0xE5cA667d795685E9915E5F4b4254ca832eEB398B", CoinType.KUCOINCOMMUNITYCHAIN) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kusama/TestKusamaAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kusama/TestKusamaAddress.kt index 39fc302d0ff..ca3463b784b 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kusama/TestKusamaAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/kusama/TestKusamaAddress.kt @@ -19,7 +19,7 @@ class TestKusamaAddress { @Test fun testAddress() { - val key = PrivateKey("0x85fca134b3fe3fd523d8b528608d803890e26c93c86dc3d97b8d59c7b3540c97".toHexByteArray()) + val key = PrivateKey("0x85fca134b3fe3fd523d8b528608d803890e26c93c86dc3d97b8d59c7b3540c97".toHexByteArray(), CoinType.KUSAMA.curve()) val pubkey = key.publicKeyEd25519 val address = AnyAddress(pubkey, CoinType.KUSAMA) val expected = AnyAddress("HewiDTQv92L2bVtkziZC8ASxrFUxr6ajQ62RXAnwQ8FDVmg", CoinType.KUSAMA) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/multiversx/TestMultiversXAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/multiversx/TestMultiversXAddress.kt index ab3b1ec2483..a80526fac01 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/multiversx/TestMultiversXAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/multiversx/TestMultiversXAddress.kt @@ -22,7 +22,7 @@ class TestMultiversXAddress { @Test fun testAddressFromPrivateKey() { - val key = PrivateKey(aliceSeedHex.toHexByteArray()) + val key = PrivateKey(aliceSeedHex.toHexByteArray(), CoinType.MULTIVERSX.curve()) val pubKey = key.publicKeyEd25519 val address = AnyAddress(pubKey, CoinType.MULTIVERSX) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/multiversx/TestMultiversXSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/multiversx/TestMultiversXSigner.kt index 82929dcb8c2..9290f2a5112 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/multiversx/TestMultiversXSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/multiversx/TestMultiversXSigner.kt @@ -26,7 +26,7 @@ class TestMultiversXSigner { @Test fun signGenericAction() { - val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray(), CoinType.MULTIVERSX.curve()).data()) val accounts = MultiversX.Accounts.newBuilder() .setSenderNonce(7) @@ -58,7 +58,7 @@ class TestMultiversXSigner { @Test fun signGenericActionWithGuardian() { - val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray(), CoinType.MULTIVERSX.curve()).data()) val accounts = MultiversX.Accounts.newBuilder() .setSenderNonce(42) @@ -91,7 +91,7 @@ class TestMultiversXSigner { @Test fun signGenericActionWithRelayer() { - val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray(), CoinType.MULTIVERSX.curve()).data()) val accounts = MultiversX.Accounts.newBuilder() .setSenderNonce(42) @@ -124,7 +124,7 @@ class TestMultiversXSigner { @Test fun signGenericActionUndelegate() { // Successfully broadcasted https://explorer.multiversx.com/transactions/3301ae5a6a77f0ab9ceb5125258f12539a113b0c6787de76a5c5867f2c515d65 - val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray(), CoinType.MULTIVERSX.curve()).data()) val accounts = MultiversX.Accounts.newBuilder() .setSenderNonce(6) @@ -157,7 +157,7 @@ class TestMultiversXSigner { @Test fun signGenericActionDelegate() { // Successfully broadcasted https://explorer.multiversx.com/transactions/e5007662780f8ed677b37b156007c24bf60b7366000f66ec3525cfa16a4564e7 - val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray(), CoinType.MULTIVERSX.curve()).data()) val accounts = MultiversX.Accounts.newBuilder() .setSenderNonce(1) @@ -189,7 +189,7 @@ class TestMultiversXSigner { @Test fun signEGLDTransfer() { - val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray(), CoinType.MULTIVERSX.curve()).data()) val accounts = MultiversX.Accounts.newBuilder() .setSenderNonce(7) @@ -217,7 +217,7 @@ class TestMultiversXSigner { @Test fun signEGLDTransferWithGuardian() { - val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray(), CoinType.MULTIVERSX.curve()).data()) val accounts = MultiversX.Accounts.newBuilder() .setSenderNonce(7) @@ -246,7 +246,7 @@ class TestMultiversXSigner { @Test fun signESDTTransfer() { - val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray(), CoinType.MULTIVERSX.curve()).data()) val accounts = MultiversX.Accounts.newBuilder() .setSenderNonce(7) @@ -276,7 +276,7 @@ class TestMultiversXSigner { @Test fun signESDTNFTTransfer() { - val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data()) + val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray(), CoinType.MULTIVERSX.curve()).data()) val accounts = MultiversX.Accounts.newBuilder() .setSenderNonce(7) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativeinjective/TestNativeInjectiveAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativeinjective/TestNativeInjectiveAddress.kt index 8c70eaf1dfe..74f363ae96f 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativeinjective/TestNativeInjectiveAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativeinjective/TestNativeInjectiveAddress.kt @@ -17,7 +17,7 @@ class TestNativeInjectiveAddress { @Test fun testAddress() { - val key = PrivateKey("9ee18daf8e463877aaf497282abc216852420101430482a28e246c179e2c5ef1".toHexByteArray()) + val key = PrivateKey("9ee18daf8e463877aaf497282abc216852420101430482a28e246c179e2c5ef1".toHexByteArray(), CoinType.NATIVEINJECTIVE.curve()) val pubKey = key.getPublicKeySecp256k1(false) val address = AnyAddress(pubKey, CoinType.NATIVEINJECTIVE) val expected = AnyAddress("inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a", CoinType.NATIVEINJECTIVE) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativeinjective/TestNativeInjectiveSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativeinjective/TestNativeInjectiveSigner.kt index b1295e2d823..dd4b136f975 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativeinjective/TestNativeInjectiveSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativeinjective/TestNativeInjectiveSigner.kt @@ -22,7 +22,7 @@ class TestNativeInjectiveSigner { @Test fun NativeInjectiveTransactionSigning() { - val key = PrivateKey("9ee18daf8e463877aaf497282abc216852420101430482a28e246c179e2c5ef1".toHexByteArray()) + val key = PrivateKey("9ee18daf8e463877aaf497282abc216852420101430482a28e246c179e2c5ef1".toHexByteArray(), CoinType.NATIVEINJECTIVE.curve()) val publicKey = key.getPublicKeySecp256k1(false) val from = AnyAddress(publicKey, CoinType.NATIVEINJECTIVE).description() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainAddress.kt index 93a2fbd2a3a..db9dc7dd8c6 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainAddress.kt @@ -18,7 +18,7 @@ class TestNativeZetaChainAddress { @Test fun testAddress() { - val key = PrivateKey("8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed".toHexByteArray()) + val key = PrivateKey("8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed".toHexByteArray(), CoinType.NATIVEZETACHAIN.curve()) val pubKey = key.getPublicKeySecp256k1(false) val address = AnyAddress(pubKey, CoinType.NATIVEZETACHAIN) val expected = AnyAddress("zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne", CoinType.NATIVEZETACHAIN) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainSigner.kt index 2dd00e4f2f8..4758f4734df 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nativezetachain/TestNativeZetaChainSigner.kt @@ -22,7 +22,7 @@ class TestNativeZetaChainSigner { @Test fun NativeZetaChainTransactionSigning() { - val key = PrivateKey("8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed".toHexByteArray()) + val key = PrivateKey("8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed".toHexByteArray(), CoinType.NATIVEZETACHAIN.curve()) val publicKey = key.getPublicKeySecp256k1(false) val from = AnyAddress(publicKey, CoinType.NATIVEZETACHAIN).description() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/near/TestNEARAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/near/TestNEARAddress.kt index 5a42fe375f8..5c1cb2123dc 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/near/TestNEARAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/near/TestNEARAddress.kt @@ -15,7 +15,7 @@ class TestNEARAddress { @Test fun testAddressFromPrivateKey() { val privateKeyBytes = Base58.decodeNoCheck("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv").sliceArray(0..31) - val key = PrivateKey(privateKeyBytes) + val key = PrivateKey(privateKeyBytes, CoinType.NEAR.curve()) val pubkey = key.getPublicKeyEd25519() val address = AnyAddress(pubkey, CoinType.NEAR) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nebulas/TestNebulasAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nebulas/TestNebulasAddress.kt index a1e25ec6af1..ee56548d785 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nebulas/TestNebulasAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nebulas/TestNebulasAddress.kt @@ -25,7 +25,7 @@ class TestNebulasAddress { @Test fun testAddressFromPublicKey() { - var priKey = PrivateKey(("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b").toHexByteArray()) + var priKey = PrivateKey(("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b").toHexByteArray(), CoinType.NEBULAS.curve()) val pubkey = priKey.getPublicKeySecp256k1(false) val address = AnyAddress(pubkey, CoinType.NEBULAS) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nebulas/TestNebulasSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nebulas/TestNebulasSigner.kt index 4547fad3643..9c1940c5b1c 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nebulas/TestNebulasSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nebulas/TestNebulasSigner.kt @@ -8,6 +8,7 @@ import org.junit.Test import wallet.core.jni.CoinType.NEBULAS import wallet.core.jni.PrivateKey import wallet.core.java.AnySigner +import wallet.core.jni.CoinType import wallet.core.jni.proto.Nebulas import wallet.core.jni.proto.Nebulas.SigningOutput @@ -30,7 +31,7 @@ class TestNebulasSigner { amount = ByteString.copyFrom("0x98a7d9b8314c0000".toHexByteArray()) //11000000000000000000 payload = "" timestamp = ByteString.copyFrom("0x5cfc84ca".toHexByteArray()) //1560052938 - privateKey = ByteString.copyFrom(PrivateKey("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b".toHexByteArray(), CoinType.NEBULAS.curve()).data()) } val output = AnySigner.sign(signingInput.build(), NEBULAS, SigningOutput.parser()) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/neo/TestsNEOAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/neo/TestsNEOAddress.kt index 8eb3f6ac165..71c520429f8 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/neo/TestsNEOAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/neo/TestsNEOAddress.kt @@ -33,7 +33,7 @@ class TestNEOAddress { @Test fun testAddressFromPrivateKey() { - val key = PrivateKey(Numeric.hexStringToByteArray("2A9EAB0FEC93CD94FA0A209AC5604602C1F0105FB02EAB398E17B4517C2FFBAB")) + val key = PrivateKey(Numeric.hexStringToByteArray("2A9EAB0FEC93CD94FA0A209AC5604602C1F0105FB02EAB398E17B4517C2FFBAB"), CoinType.NEO.curve()) val pubkey = key.publicKeyNist256p1 val address = AnyAddress(pubkey, CoinType.NEO) val expectedAddressString = "AQCSMB3oSDA1dHPn6GXN6KB4NHmdo1fX41" diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosAddress.kt index f77ce3858cd..fffb328eb33 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosAddress.kt @@ -17,7 +17,7 @@ class TestNervosAddress { @Test fun testAddress() { - val key = PrivateKey("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb".toHexByteArray()) + val key = PrivateKey("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb".toHexByteArray(), CoinType.NERVOS.curve()) val pubkey = key.getPublicKeySecp256k1(true) val address = AnyAddress(pubkey, CoinType.NERVOS) val expected = AnyAddress("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras980hksatlslfaktks7epf25", CoinType.NERVOS) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosSigner.kt index d556f883f17..2a8cdec36b2 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nervos/TestNervosSigner.kt @@ -23,7 +23,7 @@ class TestNervosSigner { @Test fun testSigning() { - val key = PrivateKey("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb".toHexByteArray()) + val key = PrivateKey("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb".toHexByteArray(), CoinType.NERVOS.curve()) val lockScript = Script.newBuilder().apply { codeHash = "9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8".toHexBytesInByteString() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nuls/TestNULSAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nuls/TestNULSAddress.kt index 43457f768c7..0d1a0b12ac3 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nuls/TestNULSAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/nuls/TestNULSAddress.kt @@ -13,7 +13,7 @@ class TestNULSAddress { @Test fun testAddress() { - val priKey = PrivateKey(Numeric.hexStringToByteArray("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a")) + val priKey = PrivateKey(Numeric.hexStringToByteArray("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a"), CoinType.NULS.curve()) val pubkey = priKey.getPublicKeySecp256k1(true) val address = AnyAddress(pubkey, CoinType.NULS) val expected = AnyAddress("NULSd6HghWa4CN5qdxqMwYVikQxRZyj57Jn4L", CoinType.NULS) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/oasis/TestOasisAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/oasis/TestOasisAddress.kt index 00b315bc40a..465e2e6bbbc 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/oasis/TestOasisAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/oasis/TestOasisAddress.kt @@ -18,7 +18,7 @@ class TestOasisAddress { @Test fun testAddress() { - val key = PrivateKey("4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0".toHexByteArray()) + val key = PrivateKey("4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0".toHexByteArray(), CoinType.OASIS.curve()) val pubkey = key.publicKeyEd25519 val address = AnyAddress(pubkey, CoinType.OASIS) val expected = AnyAddress("oasis1qzawzy5kaa2xgphenf3r0f5enpr3mx5dps559yxm", CoinType.OASIS) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisAddress.kt index 88b33d1b9fd..64f18ba508a 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisAddress.kt @@ -18,7 +18,7 @@ class TestOsmosisAddress { @Test fun testAddress() { - val key = PrivateKey("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af".toHexByteArray()) + val key = PrivateKey("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af".toHexByteArray(), CoinType.OSMOSIS.curve()) val pubkey = key.getPublicKeySecp256k1(true) val address = AnyAddress(pubkey, CoinType.OSMOSIS) val expected = AnyAddress("osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5", CoinType.OSMOSIS) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisSigner.kt index 679583594f7..6358f0219cc 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/osmosis/TestOsmosisSigner.kt @@ -28,7 +28,7 @@ class TestOsmosisSigner { @Test fun OsmosisTransactionSigning() { - val key = PrivateKey("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af".toHexByteArray()) + val key = PrivateKey("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af".toHexByteArray(), CoinType.OSMOSIS.curve()) val publicKey = key.getPublicKeySecp256k1(true) val from = AnyAddress(publicKey, OSMOSIS).description() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusAddress.kt index b58b068b859..443b3e117d5 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusAddress.kt @@ -18,7 +18,7 @@ class TestPactusAddress { @Test fun testAddress() { - val key = PrivateKey("4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6".toHexByteArray()) + val key = PrivateKey("4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6".toHexByteArray(), CoinType.PACTUS.curve()) val pubkey = key.publicKeyEd25519 val address = AnyAddress(pubkey, CoinType.PACTUS) val expected = AnyAddress("pc1rwzvr8rstdqypr80ag3t6hqrtnss9nwymcxy3lr", CoinType.PACTUS) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusSigner.kt index 81bbe9bf532..5a0c8adbf90 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/pactus/TestPactusSigner.kt @@ -32,7 +32,7 @@ class TestPactusSigner { signingInput.apply { privateKey = ByteString.copyFrom( PrivateKey("4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6" - .toHexByteArray()).data() + .toHexByteArray(), CoinType.PACTUS.curve()).data() ) transaction = Pactus.TransactionMessage.newBuilder().apply { lockTime = 2335524 @@ -73,7 +73,7 @@ class TestPactusSigner { signingInput.apply { privateKey = ByteString.copyFrom( PrivateKey("4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6" - .toHexByteArray()).data() + .toHexByteArray(), CoinType.PACTUS.curve()).data() ) transaction = Pactus.TransactionMessage.newBuilder().apply { lockTime = 2339009 @@ -115,7 +115,7 @@ class TestPactusSigner { signingInput.apply { privateKey = ByteString.copyFrom( PrivateKey("4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6" - .toHexByteArray()).data() + .toHexByteArray(), CoinType.PACTUS.curve()).data() ) transaction = Pactus.TransactionMessage.newBuilder().apply { lockTime = 2335580 diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotAddress.kt index 7ab39ef55f5..f1d7c0ec85f 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polkadot/TestPolkadotAddress.kt @@ -19,7 +19,7 @@ class TestPolkadotAddress { @Test fun testAddress() { - val key = PrivateKey("0xd65ed4c1a742699b2e20c0c1f1fe780878b1b9f7d387f934fe0a7dc36f1f9008".toHexByteArray()) + val key = PrivateKey("0xd65ed4c1a742699b2e20c0c1f1fe780878b1b9f7d387f934fe0a7dc36f1f9008".toHexByteArray(), CoinType.POLKADOT.curve()) val pubkey = key.publicKeyEd25519 val address = AnyAddress(pubkey, CoinType.POLKADOT) val expected = AnyAddress("12twBQPiG5yVSf3jQSBkTAKBKqCShQ5fm33KQhH3Hf6VDoKW", CoinType.POLKADOT) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polygon/TestPolygonAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polygon/TestPolygonAddress.kt index ab807796d5a..e2a5a4d3478 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polygon/TestPolygonAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polygon/TestPolygonAddress.kt @@ -18,7 +18,7 @@ class TestPolygonAddress { @Test fun testAddress() { - val key = PrivateKey("828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0".toHexByteArray()) + val key = PrivateKey("828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0".toHexByteArray(), CoinType.POLYGON.curve()) val pubkey = key.getPublicKeySecp256k1(false) val address = AnyAddress(pubkey, CoinType.POLYGON) val expected = AnyAddress("0xe32DC46bfBF78D1eada7b0a68C96903e01418D64", CoinType.POLYGON) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshAddress.kt index a7e77d9a3e0..d3ca5e41ba5 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshAddress.kt @@ -19,7 +19,7 @@ class TestPolymeshAddress { @Test fun testAddress() { - val key = PrivateKey("0x790a0a01ec2e7c7db4abcaffc92ce70a960ef9ad3021dbe3bf327c1c6343aee4".toHexByteArray()) + val key = PrivateKey("0x790a0a01ec2e7c7db4abcaffc92ce70a960ef9ad3021dbe3bf327c1c6343aee4".toHexByteArray(), CoinType.POLYMESH.curve()) val pubkey = key.publicKeyEd25519 val address = AnyAddress(pubkey, CoinType.POLYMESH) val expected = AnyAddress("2EANwBfNsFu9KV8JsW5sbhF6ft8bzvw5EW1LCrgHhrqtK6Ys", CoinType.POLYMESH) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ripple/TestRippleTransactionSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ripple/TestRippleTransactionSigner.kt index 9ec91772b1b..78f2e6df4c1 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ripple/TestRippleTransactionSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ripple/TestRippleTransactionSigner.kt @@ -7,6 +7,7 @@ import org.junit.Assert.assertEquals import org.junit.Test import wallet.core.jni.PrivateKey import wallet.core.java.AnySigner +import wallet.core.jni.CoinType import wallet.core.jni.CoinType.XRP import wallet.core.jni.proto.Ripple import wallet.core.jni.proto.Ripple.SigningOutput @@ -30,7 +31,7 @@ class TestRippleTransactionSigner { fee = 10 sequence = 32268248 lastLedgerSequence = 32268269 - privateKey = ByteString.copyFrom(PrivateKey("a5576c0f63da10e584568c8d134569ff44017b0a249eb70657127ae04f38cc77".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("a5576c0f63da10e584568c8d134569ff44017b0a249eb70657127ae04f38cc77".toHexByteArray(), CoinType.XRP.curve()).data()) opPayment = operation.build() } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/scroll/TestScrollAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/scroll/TestScrollAddress.kt index 23d77ff4ccb..e6e6afe95b6 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/scroll/TestScrollAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/scroll/TestScrollAddress.kt @@ -18,7 +18,7 @@ class TestScrollAddress { @Test fun testAddress() { - val key = PrivateKey("828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0".toHexByteArray()) + val key = PrivateKey("828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0".toHexByteArray(), CoinType.SCROLL.curve()) val pubkey = key.getPublicKeySecp256k1(false) val address = AnyAddress(pubkey, CoinType.SCROLL) val expected = AnyAddress("0xe32DC46bfBF78D1eada7b0a68C96903e01418D64", CoinType.SCROLL) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/secret/TestSecretAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/secret/TestSecretAddress.kt index 5315c57c9ad..65486e85fac 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/secret/TestSecretAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/secret/TestSecretAddress.kt @@ -18,7 +18,7 @@ class TestSecretAddress { @Test fun testAddress() { - val key = PrivateKey("87201512d132ef7a1e57f9e24905fbc24300bd73f676b5716182be5f3e39dada".toHexByteArray()) + val key = PrivateKey("87201512d132ef7a1e57f9e24905fbc24300bd73f676b5716182be5f3e39dada".toHexByteArray(), CoinType.SECRET.curve()) val pubkey = key.getPublicKeySecp256k1(true) val address = AnyAddress(pubkey, CoinType.SECRET) val expected = AnyAddress("secret18mdrja40gfuftt5yx6tgj0fn5lurplezyp894y", CoinType.SECRET) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/secret/TestSecretSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/secret/TestSecretSigner.kt index 3f1ef6330a0..a9040d89155 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/secret/TestSecretSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/secret/TestSecretSigner.kt @@ -27,7 +27,7 @@ class TestSecretSigner { @Test fun SecretTransactionSigning() { - val key = PrivateKey("87201512d132ef7a1e57f9e24905fbc24300bd73f676b5716182be5f3e39dada".toHexByteArray()) + val key = PrivateKey("87201512d132ef7a1e57f9e24905fbc24300bd73f676b5716182be5f3e39dada".toHexByteArray(), CoinType.SECRET.curve()) val publicKey = key.getPublicKeySecp256k1(true) val from = AnyAddress(publicKey, SECRET).description() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartbitcoincash/TestSmartBitcoinCashAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartbitcoincash/TestSmartBitcoinCashAddress.kt index 64c33ba9c58..99bebb7e605 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartbitcoincash/TestSmartBitcoinCashAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartbitcoincash/TestSmartBitcoinCashAddress.kt @@ -18,7 +18,7 @@ class TestSmartBitcoinCashAddress { @Test fun testAddress() { - val key = PrivateKey("155cbd57319f3d938977b4c18000473eb3c432c4e31b667b63e88559c497d82d".toHexByteArray()) + val key = PrivateKey("155cbd57319f3d938977b4c18000473eb3c432c4e31b667b63e88559c497d82d".toHexByteArray(), CoinType.SMARTBITCOINCASH.curve()) val pubkey = key.getPublicKeySecp256k1(false) val address = AnyAddress(pubkey, CoinType.SMARTBITCOINCASH) val expected = AnyAddress("0x8bFC9477684987dcAf0970b9bce5E3D9267C99C0", CoinType.SMARTBITCOINCASH) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartchain/TestBinanceSmartChainAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartchain/TestBinanceSmartChainAddress.kt index a9a83132c36..3303d90a2df 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartchain/TestBinanceSmartChainAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/smartchain/TestBinanceSmartChainAddress.kt @@ -18,7 +18,7 @@ class TestBinanceSmartChainAddress { @Test fun testAddress() { - val key = PrivateKey("727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06".toHexByteArray()) + val key = PrivateKey("727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06".toHexByteArray(), CoinType.SMARTCHAIN.curve()) val pubkey = key.getPublicKeySecp256k1(false) val address = AnyAddress(pubkey, CoinType.SMARTCHAIN) val expected = AnyAddress("0xf3d468DBb386aaD46E92FF222adDdf872C8CC064", CoinType.SMARTCHAIN) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaAddress.kt index f2653b68222..f8116fa2854 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/solana/TestSolanaAddress.kt @@ -14,7 +14,7 @@ class TestSolanaAddress { @Test fun testAddressFromPrivateKey() { - val key = PrivateKey(Base58.decodeNoCheck("A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr")) + val key = PrivateKey(Base58.decodeNoCheck("A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr"), CoinType.SOLANA.curve()) val pubkey = key.publicKeyEd25519 val address = AnyAddress(pubkey, CoinType.SOLANA) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stargaze/TestStargazeAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stargaze/TestStargazeAddress.kt index 54577d7971d..62a993a6d2a 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stargaze/TestStargazeAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stargaze/TestStargazeAddress.kt @@ -18,7 +18,7 @@ class TestStargazeAddress { @Test fun testAddress() { - val key = PrivateKey("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433".toHexByteArray()) + val key = PrivateKey("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433".toHexByteArray(), CoinType.STARGAZE.curve()) val pubkey = key.getPublicKeySecp256k1(true) val address = AnyAddress(pubkey, CoinType.STARGAZE) val expected = AnyAddress("stars1mry47pkga5tdswtluy0m8teslpalkdq02a8nhy", CoinType.STARGAZE) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stargaze/TestStargazeSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stargaze/TestStargazeSigner.kt index 63a7a166b60..c449cfe5c4b 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stargaze/TestStargazeSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stargaze/TestStargazeSigner.kt @@ -23,7 +23,7 @@ class TestStargazeSigner { @Test fun stargazeTransactionCW721Signing() { val key = - PrivateKey("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433".toHexByteArray()) + PrivateKey("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433".toHexByteArray(), CoinType.STARGAZE.curve()) val txMsg = Cosmos.Message.WasmExecuteContractGeneric.newBuilder().apply { senderAddress = "stars1mry47pkga5tdswtluy0m8teslpalkdq02a8nhy" @@ -69,7 +69,7 @@ class TestStargazeSigner { @Test fun stargazeTransactionSigning() { val key = - PrivateKey("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433".toHexByteArray()) + PrivateKey("a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433".toHexByteArray(), CoinType.STARGAZE.curve()) val publicKey = key.getPublicKeySecp256k1(true) val from = AnyAddress(publicKey, CoinType.STARGAZE).description() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/starkex/TestStarkExMessageSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/starkex/TestStarkExMessageSigner.kt index d68cd722b80..b34c7e62cbc 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/starkex/TestStarkExMessageSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/starkex/TestStarkExMessageSigner.kt @@ -4,6 +4,8 @@ import com.trustwallet.core.app.utils.Numeric import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test +import wallet.core.jni.CoinType +import wallet.core.jni.Curve import wallet.core.jni.PrivateKey import wallet.core.jni.PublicKeyType import wallet.core.jni.StarkExMessageSigner @@ -17,7 +19,7 @@ class TestStarkExMessageSigner { @Test fun testStarkExSignAndVerifyMessage() { val data = Numeric.hexStringToByteArray("04be51a04e718c202e4dca60c2b72958252024cfc1070c090dd0f170298249de") - val privateKey = PrivateKey(data) + val privateKey = PrivateKey(data, Curve.STARKEX) val publicKey = privateKey.getPublicKeyByType(PublicKeyType.STARKEX) val msg = "463a2240432264a3aa71a5713f2a4e4c1b9e12bbb56083cd56af6d878217cf" val signature = StarkExMessageSigner.signMessage(privateKey, msg) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarAddress.kt index 9d11510cf1f..f06214dd5b9 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarAddress.kt @@ -14,7 +14,7 @@ class TestAddress { @Test fun testAddressFromPrivateKey() { - val key = PrivateKey("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722".toHexByteArray()) + val key = PrivateKey("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722".toHexByteArray(), CoinType.STELLAR.curve()) val pubkey = key.publicKeyEd25519 val address = AnyAddress(pubkey, CoinType.STELLAR) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarSigner.kt index a76ea84550a..d88d0335cf9 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/stellar/TestStellarSigner.kt @@ -7,6 +7,7 @@ import org.junit.Test import wallet.core.jni.PrivateKey import wallet.core.jni.StellarPassphrase import wallet.core.java.AnySigner +import wallet.core.jni.CoinType import wallet.core.jni.CoinType.STELLAR import wallet.core.jni.proto.Stellar import wallet.core.jni.proto.Stellar.SigningOutput @@ -31,7 +32,7 @@ class TestStellarTransactionSigner { sequence = 2 passphrase = StellarPassphrase.STELLAR.toString() opPayment = operation.build() - privateKey = ByteString.copyFrom(PrivateKey("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722".toHexByteArray(), CoinType.STELLAR.curve()).data()) } val sign = AnySigner.sign(signingInput.build(), STELLAR, SigningOutput.parser()) @@ -55,7 +56,7 @@ class TestStellarTransactionSigner { passphrase = StellarPassphrase.STELLAR.toString() opPayment = operation.build() memoHash = Stellar.MemoHash.newBuilder().setHash(ByteString.copyFrom("315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3".toHexByteArray())).build() - privateKey = ByteString.copyFrom(PrivateKey("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722".toHexByteArray(), CoinType.STELLAR.curve()).data()) } val sign = AnySigner.sign(signingInput.build(), STELLAR, SigningOutput.parser()) @@ -79,7 +80,7 @@ class TestStellarTransactionSigner { passphrase = StellarPassphrase.STELLAR.toString() opPayment = operation.build() memoReturnHash = Stellar.MemoHash.newBuilder().setHash(ByteString.copyFrom("315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3".toHexByteArray())).build() - privateKey = ByteString.copyFrom(PrivateKey("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722".toHexByteArray(), CoinType.STELLAR.curve()).data()) } val sign = AnySigner.sign(signingInput.build(), STELLAR, SigningOutput.parser()) @@ -103,7 +104,7 @@ class TestStellarTransactionSigner { passphrase = StellarPassphrase.STELLAR.toString() opPayment = operation.build() memoId = Stellar.MemoId.newBuilder().setId(1234567890).build() - privateKey = ByteString.copyFrom(PrivateKey("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722".toHexByteArray(), CoinType.STELLAR.curve()).data()) } val sign = AnySigner.sign(signingInput.build(), STELLAR, SigningOutput.parser()) @@ -127,7 +128,7 @@ class TestStellarTransactionSigner { passphrase = StellarPassphrase.STELLAR.toString() opCreateAccount = operation.build() memoId = Stellar.MemoId.newBuilder().setId(1234567890).build() - privateKey = ByteString.copyFrom(PrivateKey("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722".toHexByteArray(), CoinType.STELLAR.curve()).data()) } val sign = AnySigner.sign(signingInput.build(), STELLAR, SigningOutput.parser()) @@ -155,7 +156,7 @@ class TestStellarTransactionSigner { sequence = 144098454883270659 passphrase = StellarPassphrase.STELLAR.toString() opChangeTrust = operation.build() - privateKey = ByteString.copyFrom(PrivateKey("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9".toHexByteArray(), CoinType.STELLAR.curve()).data()) } val sign = AnySigner.sign(signingInput.build(), STELLAR, SigningOutput.parser()) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraClassicTxs.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraClassicTxs.kt index 538d0efb76b..0c1fed7b096 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraClassicTxs.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraClassicTxs.kt @@ -20,7 +20,7 @@ class TestTerraClassicTxs { @Test fun testSigningTransaction() { val key = - PrivateKey("1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6".toHexByteArray()) + PrivateKey("1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6".toHexByteArray(), CoinType.TERRA.curve()) val publicKey = key.getPublicKeySecp256k1(true) val from = AnyAddress(publicKey, TERRA).description() @@ -71,7 +71,7 @@ class TestTerraClassicTxs { @Test fun testSigningWasmTerraTransferTxProtobuf() { val key = - PrivateKey("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616".toHexByteArray()) + PrivateKey("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616".toHexByteArray(), CoinType.TERRA.curve()) val publicKey = key.getPublicKeySecp256k1(true) val from = AnyAddress(publicKey, TERRA).description() @@ -116,7 +116,7 @@ class TestTerraClassicTxs { @Test fun testSigningWasmTerraGenericProtobuf() { val key = - PrivateKey("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616".toHexByteArray()) + PrivateKey("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616".toHexByteArray(), CoinType.TERRA.curve()) val publicKey = key.getPublicKeySecp256k1(true) val from = AnyAddress(publicKey, TERRA).description() @@ -160,7 +160,7 @@ class TestTerraClassicTxs { @Test fun testSigningWasmTerraGenericWithCoinsProtobuf() { val key = - PrivateKey("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616".toHexByteArray()) + PrivateKey("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616".toHexByteArray(), CoinType.TERRA.curve()) val publicKey = key.getPublicKeySecp256k1(true) val from = AnyAddress(publicKey, TERRA).description() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraTransactions.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraTransactions.kt index e4591ff57e3..c83148572ed 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraTransactions.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/terra/TestTerraTransactions.kt @@ -20,7 +20,7 @@ class TestTerraTransactions { @Test fun testSigningTransaction() { val key = - PrivateKey("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005".toHexByteArray()) + PrivateKey("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005".toHexByteArray(), CoinType.TERRAV2.curve()) val publicKey = key.getPublicKeySecp256k1(true) val from = AnyAddress(publicKey, TERRAV2).description() @@ -70,7 +70,7 @@ class TestTerraTransactions { @Test fun testSigningWasmTerraTransferTx() { val key = - PrivateKey("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616".toHexByteArray()) + PrivateKey("cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616".toHexByteArray(), CoinType.TERRAV2.curve()) val publicKey = key.getPublicKeySecp256k1(true) val from = AnyAddress(publicKey, TERRAV2).description() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tezos/TestTezosMessageSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tezos/TestTezosMessageSigner.kt index 57b06f83714..90fe210d099 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tezos/TestTezosMessageSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tezos/TestTezosMessageSigner.kt @@ -18,7 +18,7 @@ class TestTezosMessageSigner { @Test fun testMessageSignerSignAndVerify() { val data = Numeric.hexStringToByteArray("91b4fb8d7348db2e7de2693f58ce1cceb966fa960739adac1d9dba2cbaa0940a") - val privateKey = PrivateKey(data) + val privateKey = PrivateKey(data, CoinType.TEZOS.curve()) val msg = "05010000004254657a6f73205369676e6564204d6573736167653a207465737455726c20323032332d30322d30385431303a33363a31382e3435345a2048656c6c6f20576f726c64" val signature = TezosMessageSigner.signMessage(privateKey, msg) assertEquals("edsigu3se2fcEJUCm1aqxjzbHdf7Wsugr4mLaA9YM2UVZ9Yy5meGv87VqHN3mmDeRwApTj1JKDaYjqmLZifSFdWCqBoghqaowwJ", signature) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkAddress.kt index f37d4c7e4e9..b428a5ac64c 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkAddress.kt @@ -18,7 +18,7 @@ class TestTheOpenNetworkAddress { @Test fun testAddressFromPrivateKey() { - val privateKey = PrivateKey("63474e5fe9511f1526a50567ce142befc343e71a49b865ac3908f58667319cb8".toHexByteArray()) + val privateKey = PrivateKey("63474e5fe9511f1526a50567ce142befc343e71a49b865ac3908f58667319cb8".toHexByteArray(), CoinType.TON.curve()) val publicKey = privateKey.getPublicKeyEd25519() val address = AnyAddress(publicKey, CoinType.TON) assertEquals(publicKey.data().toHex(), "0xf42c77f931bea20ec5d0150731276bbb2e2860947661245b2319ef8133ee8d41") diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkMessageSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkMessageSigner.kt index 64c4627cb97..348636d7486 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkMessageSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkMessageSigner.kt @@ -7,6 +7,7 @@ package com.trustwallet.core.app.blockchains.theopennetwork import com.trustwallet.core.app.utils.toHexByteArray import org.junit.Assert.assertEquals import org.junit.Test +import wallet.core.jni.CoinType import wallet.core.jni.PrivateKey import wallet.core.jni.TONMessageSigner import wallet.core.jni.TONWallet @@ -21,7 +22,7 @@ class TestTheOpenNetworkMessageSigner { // The private key has been derived by using [ton-mnemonic](https://www.npmjs.com/package/tonweb-mnemonic/v/0.0.2) // from the following mnemonic: // document shield addict crime broom point story depend suit satisfy test chicken valid tail speak fortune sound drill seek cube cheap body music recipe - val privateKey = PrivateKey("112d4e2e700a468f1eae699329202f1ee671d6b665caa2d92dea038cf3868c18".toHexByteArray()) + val privateKey = PrivateKey("112d4e2e700a468f1eae699329202f1ee671d6b665caa2d92dea038cf3868c18".toHexByteArray(), CoinType.TON.curve()) val message = "Hello world" val signature = TONMessageSigner.signMessage(privateKey, message) // The following signature has been computed by calling `window.ton.send("ton_personalSign", { data: "Hello world" });`. diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkSigner.kt index 69dd91abada..c46f285bdc1 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/theopennetwork/TestTheOpenNetworkSigner.kt @@ -22,7 +22,7 @@ class TestTheOpenNetworkSigner { @Test fun TheOpenNetworkTransactionSigning() { - val privateKey = PrivateKey("c38f49de2fb13223a9e7d37d5d0ffbdd89a5eb7c8b0ee4d1c299f2cefe7dc4a0".toHexByteArray()) + val privateKey = PrivateKey("c38f49de2fb13223a9e7d37d5d0ffbdd89a5eb7c8b0ee4d1c299f2cefe7dc4a0".toHexByteArray(), CoinType.TON.curve()) val transfer = TheOpenNetwork.Transfer.newBuilder() .setDest("EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q") @@ -49,7 +49,7 @@ class TestTheOpenNetworkSigner { @Test fun TheOpenNetworkJettonTransferSigning() { - val privateKey = PrivateKey("c054900a527538c1b4325688a421c0469b171c29f23a62da216e90b0df2412ee".toHexByteArray()) + val privateKey = PrivateKey("c054900a527538c1b4325688a421c0469b171c29f23a62da216e90b0df2412ee".toHexByteArray(), CoinType.TON.curve()) val jettonTransfer = TheOpenNetwork.JettonTransfer.newBuilder() .setJettonAmount(500 * 1000 * 1000) @@ -84,7 +84,7 @@ class TestTheOpenNetworkSigner { @Test fun TheOpenNetworkTransferCustomPayload() { - val privateKey = PrivateKey("5525e673087587bc0efd7ab09920ef7d3c1bf6b854a661430244ca59ab19e9d1".toHexByteArray()) + val privateKey = PrivateKey("5525e673087587bc0efd7ab09920ef7d3c1bf6b854a661430244ca59ab19e9d1".toHexByteArray(), CoinType.TON.curve()) // Doge chatbot contract payload to be deployed. // Docs: https://docs.ton.org/develop/dapps/ton-connect/transactions#smart-contract-deployment diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thetafuel/TestThetaFuelAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thetafuel/TestThetaFuelAddress.kt index 17e60b89aeb..625279f8f1a 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thetafuel/TestThetaFuelAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thetafuel/TestThetaFuelAddress.kt @@ -17,7 +17,7 @@ class TestThetaFuelAddress { @Test fun testAddress() { - val key = PrivateKey("4646464646464646464646464646464646464646464646464646464646464646".toHexByteArray()) + val key = PrivateKey("4646464646464646464646464646464646464646464646464646464646464646".toHexByteArray(), CoinType.THETAFUEL.curve()) val pubkey = key.getPublicKeySecp256k1(false) val address = AnyAddress(pubkey, CoinType.THETAFUEL) val expected = AnyAddress("0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", CoinType.THETAFUEL) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainSigner.kt index fde6325529f..c57b062da2a 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORChainSigner.kt @@ -28,7 +28,7 @@ class TestTHORChainSigner { @Test fun THORChainTransactionSigning() { val key = - PrivateKey("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e".toHexByteArray()) + PrivateKey("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e".toHexByteArray(), CoinType.THORCHAIN.curve()) val publicKey = key.getPublicKeySecp256k1(true) val from = AnyAddress(publicKey, THORCHAIN).data() val to = AnyAddress("thor1e2ryt8asq4gu0h6z2sx9u7rfrykgxwkmr9upxn", THORCHAIN).data() diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORSwapSigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORSwapSigning.kt index 68f0f8acc55..334ac0ddc58 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORSwapSigning.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/thorchain/TestTHORSwapSigning.kt @@ -12,6 +12,7 @@ import wallet.core.jni.proto.Ethereum.SigningOutput import wallet.core.jni.proto.THORChainSwap import wallet.core.jni.THORChainSwap.buildSwap import com.trustwallet.core.app.utils.Numeric +import wallet.core.jni.CoinType class TestTHORChainSwap { @@ -64,7 +65,7 @@ class TestTHORChainSwap { nonce = ByteString.copyFrom("0x03".toHexByteArray()) gasPrice = ByteString.copyFrom("0x06FC23AC00".toHexByteArray()) gasLimit = ByteString.copyFrom("0x013880".toHexByteArray()) - privateKey = ByteString.copyFrom(PrivateKey("0x4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("0x4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904".toHexByteArray(), CoinType.THORCHAIN.curve()).data()) }.build() // sign and encode resulting input diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tron/TestTronMessageSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tron/TestTronMessageSigner.kt index 23cfb2302d3..c0e8f704fef 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tron/TestTronMessageSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/tron/TestTronMessageSigner.kt @@ -17,10 +17,10 @@ class TestTronMessageSigner { @Test fun testMessageSignerSignAndVerify() { val data = Numeric.hexStringToByteArray("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a") - val privateKey = PrivateKey(data) + val privateKey = PrivateKey(data, CoinType.TRON.curve()) val msg = "Hello World" val signature = TronMessageSigner.signMessage(privateKey, msg) - assertEquals("9bb6d11ec8a6a3fb686a8f55b123e7ec4e9746a26157f6f9e854dd72f5683b450397a7b0a9653865658de8f9243f877539882891bad30c7286c3bf5622b900471b", signature) + assertEquals("bc0753c070cc55693097df11bc11e1a7c4bd5e1a40b9dc94c75568e59bcc9d6b50a7873ef25b469e494490a54de37327b4bc7fc825c81a377b555e34fb7261ba1c", signature) val pubKey = privateKey.getPublicKey(CoinType.TRON) assertTrue(TronMessageSigner.verifyMessage(pubKey, msg, signature)) } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/waves/TestWavesAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/waves/TestWavesAddress.kt index ef8b4217e6c..92d476af82d 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/waves/TestWavesAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/waves/TestWavesAddress.kt @@ -14,7 +14,7 @@ class TestAddress { @Test fun testAddressFromPrivateKey() { - val key = PrivateKey("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a".toHexByteArray()) + val key = PrivateKey("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a".toHexByteArray(), CoinType.WAVES.curve()) val pubkey = key.publicKeyCurve25519 val address = AnyAddress(pubkey, CoinType.WAVES) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/zen/TestZenAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/zen/TestZenAddress.kt index 8398b636928..bbcb4638b6b 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/zen/TestZenAddress.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/zen/TestZenAddress.kt @@ -18,7 +18,7 @@ class TestZenAddress { @Test fun testAddress() { - val key = PrivateKey("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a".toHexByteArray()) + val key = PrivateKey("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a".toHexByteArray(), CoinType.ZEN.curve()) val pubkey = key.getPublicKeySecp256k1(true) val address = AnyAddress(pubkey, CoinType.ZEN) val expected = AnyAddress("znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg", CoinType.ZEN) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestLiquidStaking.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestLiquidStaking.kt index 44701d5f2b1..64f484edb52 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestLiquidStaking.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestLiquidStaking.kt @@ -50,7 +50,7 @@ class TestLiquidStaking { maxFeePerGas = ByteString.copyFrom("0x8fbcc8fcd8".toHexByteArray()) maxInclusionFeePerGas = ByteString.copyFrom("0x085e42c7c0".toHexByteArray()) gasLimit = ByteString.copyFrom("0x01c520".toHexByteArray()) - privateKey = ByteString.copyFrom(PrivateKey("0x4a160b803c4392ea54865d0c5286846e7ad5e68fbd78880547697472b22ea7ab".toHexByteArray()).data()) + privateKey = ByteString.copyFrom(PrivateKey("0x4a160b803c4392ea54865d0c5286846e7ad5e68fbd78880547697472b22ea7ab".toHexByteArray(), CoinType.ETHEREUM.curve()).data()) }.build() val output = AnySigner.sign(txInputFull, CoinType.ETHEREUM, Ethereum.SigningOutput.parser()) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPrivateKey.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPrivateKey.kt index d84bccd69c5..eee64645304 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPrivateKey.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPrivateKey.kt @@ -14,7 +14,7 @@ class TestPrivateKey { @Test fun testCreate() { - var privateKey = PrivateKey() + var privateKey = PrivateKey(Curve.SECP256K1) var data = privateKey.data() assertTrue(data.size == 32); } @@ -23,7 +23,7 @@ class TestPrivateKey { fun testCreateStarkKey() { val data = Numeric.hexStringToByteArray("06cf0a8bf113352eb863157a45c5e5567abb34f8d32cddafd2c22aa803f4892c") assertTrue(PrivateKey.isValid(data, Curve.STARKEX)) - var privateKey = PrivateKey(data) + var privateKey = PrivateKey(data, Curve.STARKEX) var pubKey = privateKey.getPublicKeyByType(PublicKeyType.STARKEX) assertEquals(pubKey.data().toHex(), "0x02d2bbdc1adaf887b0027cdde2113cfd81c60493aa6dc15d7887ddf1a82bc831") } @@ -31,9 +31,9 @@ class TestPrivateKey { @Test fun testCreateStarkKeySigning() { val data = Numeric.hexStringToByteArray("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79") - var privateKey = PrivateKey(data) + var privateKey = PrivateKey(data, Curve.STARKEX) val digest = Numeric.hexStringToByteArray("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76") - val signature = privateKey.sign(digest, Curve.STARKEX) + val signature = privateKey.sign(digest) assertEquals(signature.toHex(), "0x061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a") } @@ -42,7 +42,7 @@ class TestPrivateKey { val bytes = Numeric.hexStringToByteArray("deadbeaf") var privateKey: PrivateKey? = null try { - privateKey = PrivateKey(bytes) + privateKey = PrivateKey(bytes, Curve.SECP256K1) } catch (ex: Exception) { } assertNull(privateKey) @@ -73,7 +73,7 @@ class TestPrivateKey { assertTrue(PrivateKey.isValid(validPrivateKeyData, Curve.SECP256K1)) var privateKey: PrivateKey? = null try { - privateKey = PrivateKey(validPrivateKeyData) + privateKey = PrivateKey(validPrivateKeyData, Curve.SECP256K1) } catch (ex: Exception) { } @@ -83,7 +83,16 @@ class TestPrivateKey { @Test fun testGetPublicKeyCoinType() { val privateKeyData = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5".toHexBytes() - val privateKey = PrivateKey(privateKeyData) + val privateKey = PrivateKey(privateKeyData, Curve.SECP256K1) assertEquals(privateKey.getPublicKey(CoinType.ETHEREUM).data().toHex(), "0x0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91"); } + + @Test + fun testCreateKeySigningWithoutCurve() { + val data = Numeric.hexStringToByteArray("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79") + var privateKey = PrivateKey(data, Curve.STARKEX) + val digest = Numeric.hexStringToByteArray("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76") + val signature = privateKey.sign(digest) + assertEquals(signature.toHex(), "0x061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a") + } } \ No newline at end of file diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPublicKey.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPublicKey.kt index bfecc51014e..e3e5d068726 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPublicKey.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestPublicKey.kt @@ -18,7 +18,7 @@ class TestPublicKey { fun testPublicKeyCompressed() { var privateKey: PrivateKey? = null try { - privateKey = PrivateKey(validPrivateKeyData) + privateKey = PrivateKey(validPrivateKeyData, Curve.SECP256K1) } catch (ex: Exception) { } @@ -41,25 +41,26 @@ class TestPublicKey { fun testSign() { val validSign = "0x8720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de901" val data = Hash.keccak256("hello".toByteArray()) - val sign = PrivateKey(validPrivateKeyData).sign(data, Curve.SECP256K1) + val sign = PrivateKey(validPrivateKeyData, Curve.SECP256K1).sign(data) assertEquals(sign.toHex(), validSign) } @Test fun testVerify() { - val privateKey = PrivateKey(validPrivateKeyData) - val message = Hash.sha256("hello".toByteArray()) - val sig1 = privateKey.sign(message, Curve.ED25519) - val result1 = privateKey.getPublicKeyEd25519().verify(sig1, message) + val privateKey1 = PrivateKey(validPrivateKeyData, Curve.ED25519) + val sig1 = privateKey1.sign(message) + val result1 = privateKey1.getPublicKeyEd25519().verify(sig1, message) assert(result1) - val sig2 = privateKey.sign(message, Curve.ED25519BLAKE2BNANO) - val result2 = privateKey.getPublicKeyEd25519Blake2b().verify(sig2, message) + val privateKey2 = PrivateKey(validPrivateKeyData, Curve.ED25519BLAKE2BNANO) + val sig2 = privateKey2.sign(message) + val result2 = privateKey2.getPublicKeyEd25519Blake2b().verify(sig2, message) assert(result2) - val sig3 = privateKey.sign(message, Curve.SECP256K1) - val result3 = privateKey.getPublicKeySecp256k1(true).verify(sig3, message) + val privateKey3 = PrivateKey(validPrivateKeyData, Curve.SECP256K1) + val sig3 = privateKey3.sign(message) + val result3 = privateKey3.getPublicKeySecp256k1(true).verify(sig3, message) assert(result3) } } diff --git a/include/TrustWalletCore/TWPrivateKey.h b/include/TrustWalletCore/TWPrivateKey.h index 5fdfc61bcde..e0beebb04dd 100644 --- a/include/TrustWalletCore/TWPrivateKey.h +++ b/include/TrustWalletCore/TWPrivateKey.h @@ -18,20 +18,22 @@ struct TWPrivateKey; static const size_t TWPrivateKeySize = 32; -/// Create a random private key +/// Create a random private key with the given curve /// +/// \param curve the curve of the private key /// \note Should be deleted with \TWPrivateKeyDelete /// \return Non-null Private key TW_EXPORT_STATIC_METHOD -struct TWPrivateKey* _Nonnull TWPrivateKeyCreate(void); +struct TWPrivateKey* _Nonnull TWPrivateKeyCreate(enum TWCurve curve); -/// Create a private key with the given block of data +/// Create a private key with the given block of data and curve /// /// \param data a block of data +/// \param curve the curve of the private key /// \note Should be deleted with \TWPrivateKeyDelete /// \return Nullable pointer to Private Key TW_EXPORT_STATIC_METHOD -struct TWPrivateKey* _Nullable TWPrivateKeyCreateWithData(TWData* _Nonnull data); +struct TWPrivateKey* _Nullable TWPrivateKeyCreateWithData(TWData* _Nonnull data, enum TWCurve curve); /// Deep copy a given private key /// @@ -121,14 +123,13 @@ struct TWPublicKey* _Nonnull TWPrivateKeyGetPublicKeyEd25519Cardano(struct TWPri TW_EXPORT_METHOD struct TWPublicKey* _Nonnull TWPrivateKeyGetPublicKeyCurve25519(struct TWPrivateKey* _Nonnull pk); -/// Signs a digest using ECDSA and given curve. +/// Signs a digest using ECDSA /// /// \param pk Non-null pointer to a Private key /// \param digest Non-null digest block of data -/// \param curve Eliptic curve /// \return Signature as a Non-null block of data TW_EXPORT_METHOD -TWData* _Nullable TWPrivateKeySign(struct TWPrivateKey* _Nonnull pk, TWData* _Nonnull digest, enum TWCurve curve); +TWData* _Nullable TWPrivateKeySign(struct TWPrivateKey* _Nonnull pk, TWData* _Nonnull digest); /// Signs a digest using ECDSA. The result is encoded with DER. /// diff --git a/rust/tw_evm/src/abi/prebuild/erc4337.rs b/rust/tw_evm/src/abi/prebuild/erc4337.rs index c6ef8eb76a2..3dd1b74ac10 100644 --- a/rust/tw_evm/src/abi/prebuild/erc4337.rs +++ b/rust/tw_evm/src/abi/prebuild/erc4337.rs @@ -3,21 +3,26 @@ // Copyright © 2017 Trust Wallet. use crate::abi::contract::Contract; +use crate::abi::param_token::NamedToken; use crate::abi::param_type::ParamType; use crate::abi::token::Token; -use crate::abi::AbiResult; +use crate::abi::{AbiError, AbiErrorKind, AbiResult}; use crate::address::Address; use lazy_static::lazy_static; +use tw_coin_entry::error::prelude::{OrTWError, ResultContext}; use tw_memory::Data; use tw_number::U256; /// Generated via https://remix.ethereum.org /// https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/samples/SimpleAccount.sol const ERC4337_SIMPLE_ACCOUNT_ABI: &str = include_str!("resource/erc4337.simple_account.abi.json"); +const ERC4337_BIZ_ACCOUNT_ABI: &str = include_str!("resource/erc4337.biz_account.abi.json"); lazy_static! { static ref ERC4337_SIMPLE_ACCOUNT: Contract = serde_json::from_str(ERC4337_SIMPLE_ACCOUNT_ABI).unwrap(); + static ref ERC4337_BIZ_ACCOUNT: Contract = + serde_json::from_str(ERC4337_BIZ_ACCOUNT_ABI).unwrap(); } pub struct ExecuteArgs { @@ -38,6 +43,15 @@ impl Erc4337SimpleAccount { ]) } + pub fn encode_execute_4337_op(args: ExecuteArgs) -> AbiResult { + let func = ERC4337_BIZ_ACCOUNT.function("execute4337Op")?; + func.encode_input(&[ + Token::Address(args.to), + Token::u256(args.value), + Token::Bytes(args.data), + ]) + } + pub fn encode_execute_batch(args: I) -> AbiResult where I: IntoIterator, @@ -66,4 +80,62 @@ impl Erc4337SimpleAccount { Token::array(ParamType::Bytes, datas), ]) } + + pub fn encode_execute_4337_ops(args: I) -> AbiResult + where + I: IntoIterator, + { + let func = ERC4337_BIZ_ACCOUNT.function("execute4337Ops")?; + + // `tuple[]`, where each item is a tuple of (address, uint256, bytes). + let array_param = func + .inputs + .first() + .or_tw_err(AbiErrorKind::Error_internal) + .context("'Biz.execute4337Ops()' should contain only one argument")?; + + let ParamType::Array { + kind: array_elem_type, + } = array_param.kind.clone() + else { + return AbiError::err(AbiErrorKind::Error_internal).with_context(|| { + format!( + "'Biz.execute4337Ops()' input argument should be an array, found: {:?}", + array_param.kind + ) + }); + }; + + let ParamType::Tuple { + params: tuple_params, + } = array_elem_type.as_ref() + else { + return AbiError::err(AbiErrorKind::Error_internal).with_context(|| { + format!( + "'Biz.execute4337Ops()' input argument should be an array of tuples, found: {array_elem_type:?}", + ) + }); + }; + + if tuple_params.len() != 3 { + return AbiError::err(AbiErrorKind::Error_internal).with_context(|| { + format!( + "'Biz.execute4337Ops()' input argument should be an array of tuples with 3 elements, found: {}", tuple_params.len() + ) + }); + } + + let array_tokens = args + .into_iter() + .map(|call| Token::Tuple { + params: vec![ + NamedToken::with_param_and_token(&tuple_params[0], Token::Address(call.to)), + NamedToken::with_param_and_token(&tuple_params[1], Token::u256(call.value)), + NamedToken::with_param_and_token(&tuple_params[2], Token::Bytes(call.data)), + ], + }) + .collect(); + + func.encode_input(&[Token::array(*array_elem_type, array_tokens)]) + } } diff --git a/rust/tw_evm/src/abi/prebuild/resource/erc4337.biz_account.abi.json b/rust/tw_evm/src/abi/prebuild/resource/erc4337.biz_account.abi.json new file mode 100644 index 00000000000..137ae43cac3 --- /dev/null +++ b/rust/tw_evm/src/abi/prebuild/resource/erc4337.biz_account.abi.json @@ -0,0 +1,515 @@ +[ + { + "type": "constructor", + "inputs": [ + { + "name": "_bizGuard", + "type": "address", + "internalType": "contract BizGuard" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "receive", + "stateMutability": "payable" + }, + { + "type": "function", + "name": "bizGuard", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract BizGuard" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "entryPoint", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "execute", + "inputs": [ + { + "name": "target", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "execute4337Op", + "inputs": [ + { + "name": "target", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "execute4337Ops", + "inputs": [ + { + "name": "calls", + "type": "tuple[]", + "internalType": "struct Biz.Call[]", + "components": [ + { + "name": "target", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "executeBatch", + "inputs": [ + { + "name": "calls", + "type": "tuple[]", + "internalType": "struct Biz.Call[]", + "components": [ + { + "name": "target", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "executeWithSignature", + "inputs": [ + { + "name": "calls", + "type": "tuple[]", + "internalType": "struct Biz.Call[]", + "components": [ + { + "name": "target", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "signature", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "isValidSignature", + "inputs": [ + { + "name": "hash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "signature", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "isValid", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "onERC1155BatchReceived", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "uint256[]", + "internalType": "uint256[]" + }, + { + "name": "", + "type": "uint256[]", + "internalType": "uint256[]" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "onERC1155Received", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "onERC721Received", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "onTokenTransfer", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "tokensReceived", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "validateUserOp", + "inputs": [ + { + "name": "userOp", + "type": "tuple", + "internalType": "struct Biz.PackedUserOperation", + "components": [ + { + "name": "sender", + "type": "address", + "internalType": "address" + }, + { + "name": "nonce", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "initCode", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "callData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "accountGasLimits", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "preVerificationGas", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "gasFees", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "paymasterAndData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "signature", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "userOpHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "missingAccountFunds", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "validationData", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "payable" + }, + { + "type": "error", + "name": "ECDSAInvalidSignature", + "inputs": [] + }, + { + "type": "error", + "name": "ECDSAInvalidSignatureLength", + "inputs": [ + { + "name": "length", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ECDSAInvalidSignatureS", + "inputs": [ + { + "name": "s", + "type": "bytes32", + "internalType": "bytes32" + } + ] + }, + { + "type": "error", + "name": "ERC4337Disabled", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidERC4337Flag", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidSignature", + "inputs": [] + }, + { + "type": "error", + "name": "OnlyEntryPoint", + "inputs": [] + }, + { + "type": "error", + "name": "OnlySelf", + "inputs": [] + } +] \ No newline at end of file diff --git a/rust/tw_evm/src/modules/tx_builder.rs b/rust/tw_evm/src/modules/tx_builder.rs index 932d87f31f2..516f375024b 100644 --- a/rust/tw_evm/src/modules/tx_builder.rs +++ b/rust/tw_evm/src/modules/tx_builder.rs @@ -23,6 +23,10 @@ use tw_memory::Data; use tw_number::U256; use tw_proto::Common::Proto::SigningError as CommonError; use tw_proto::Ethereum::Proto; +use Proto::mod_SigningInput::OneOfuser_operation_oneof as UserOp; +use Proto::mod_Transaction::OneOftransaction_oneof as Tx; +use Proto::SCAccountType; +use Proto::TransactionMode as TxMode; pub struct TxBuilder { _phantom: PhantomData, @@ -32,10 +36,6 @@ impl TxBuilder { pub fn tx_from_proto( input: &Proto::SigningInput<'_>, ) -> SigningResult> { - use Proto::mod_SigningInput::OneOfuser_operation_oneof as UserOp; - use Proto::mod_Transaction::OneOftransaction_oneof as Tx; - use Proto::TransactionMode as TxMode; - let Some(ref transaction) = input.transaction else { return SigningError::err(CommonError::Error_invalid_params) .context("No transaction specified"); @@ -147,21 +147,15 @@ impl TxBuilder { .iter() .map(Self::erc4337_execute_call_from_proto) .collect::, _>>()?; - let payload = Erc4337SimpleAccount::encode_execute_batch(calls) - .map_err(abi_to_signing_error)?; - - return match &input.user_operation_oneof { - UserOp::user_operation_v0_7(user_op_v0_7) => { - Self::user_operation_v0_7_from_proto(input, user_op_v0_7, payload) - .map(UserOperationV0_7::into_boxed) + let user_op_payload = match input.user_operation_mode { + SCAccountType::SimpleAccount => { + Erc4337SimpleAccount::encode_execute_batch(calls) }, - UserOp::user_operation(user_op) => { - Self::user_operation_from_proto(input, user_op, payload) - .map(UserOperation::into_boxed) - }, - UserOp::None => SigningError::err(SigningErrorType::Error_invalid_params) - .context("No user operation specified"), - }; + SCAccountType::Biz4337 => Erc4337SimpleAccount::encode_execute_4337_ops(calls), + } + .map_err(abi_to_signing_error)?; + + return Self::user_operation_from_proto(input, user_op_payload); }, Tx::None => { return SigningError::err(SigningErrorType::Error_invalid_params) @@ -180,31 +174,42 @@ impl TxBuilder { let to = to .or_tw_err(SigningErrorType::Error_invalid_address) .context("No contract/destination address specified")?; - // Payload should match the ERC4337 standard. - let payload = Erc4337SimpleAccount::encode_execute(ExecuteArgs { + let args = ExecuteArgs { to, value: eth_amount, data: payload, - }) - .map_err(abi_to_signing_error)?; + }; - match &input.user_operation_oneof { - UserOp::user_operation_v0_7(user_op_v0_7) => { - Self::user_operation_v0_7_from_proto(input, user_op_v0_7, payload) - .map(UserOperationV0_7::into_boxed) - }, - UserOp::user_operation(user_op) => { - Self::user_operation_from_proto(input, user_op, payload) - .map(UserOperation::into_boxed) - }, - UserOp::None => SigningError::err(SigningErrorType::Error_invalid_params) - .context("No user operation specified"), - }? + let user_op_payload = match input.user_operation_mode { + SCAccountType::SimpleAccount => Erc4337SimpleAccount::encode_execute(args), + SCAccountType::Biz4337 => Erc4337SimpleAccount::encode_execute_4337_op(args), + } + .map_err(abi_to_signing_error)?; + Self::user_operation_from_proto(input, user_op_payload)? }, }; Ok(tx) } + #[inline] + fn user_operation_from_proto( + input: &Proto::SigningInput<'_>, + user_op_payload: Data, + ) -> SigningResult> { + match input.user_operation_oneof { + UserOp::user_operation_v0_7(ref user_op_v0_7) => { + Self::user_operation_v0_7_from_proto(input, user_op_v0_7, user_op_payload) + .map(UserOperationV0_7::into_boxed) + }, + UserOp::user_operation(ref user_op) => { + Self::user_operation_v0_6_from_proto(input, user_op, user_op_payload) + .map(UserOperation::into_boxed) + }, + UserOp::None => SigningError::err(SigningErrorType::Error_invalid_params) + .context("No user operation specified"), + } + } + #[inline] fn erc4337_execute_call_from_proto( call: &Proto::mod_Transaction::mod_Batch::BatchedCall, @@ -289,7 +294,7 @@ impl TxBuilder { }) } - fn user_operation_from_proto( + fn user_operation_v0_6_from_proto( input: &Proto::SigningInput, user_op: &Proto::UserOperation, erc4337_payload: Data, diff --git a/rust/tw_evm/tests/barz.rs b/rust/tw_evm/tests/barz.rs index 5fd6ac38dc7..b795d34607b 100644 --- a/rust/tw_evm/tests/barz.rs +++ b/rust/tw_evm/tests/barz.rs @@ -10,6 +10,7 @@ use tw_evm::abi::prebuild::erc20::Erc20; use tw_evm::address::Address; use tw_evm::evm_context::StandardEvmContext; use tw_evm::modules::signer::Signer; +use tw_misc::traits::ToBytesVec; use tw_number::U256; use tw_proto::Ethereum::Proto; @@ -199,7 +200,7 @@ fn test_barz_batched_account_deployed() { } #[test] -fn test_barz_transfer_account_deployed_v0_7() { +fn test_barz_transfer_account_not_deployed_v0_7() { let private_key = hex::decode("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483").unwrap(); @@ -246,3 +247,147 @@ fn test_barz_transfer_account_deployed_v0_7() { "f177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb" ); } + +#[test] +fn test_barz_transfer_erc7702_eoa() { + let private_key = + hex::decode("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483").unwrap(); + + let user_op = Proto::UserOperationV0_7 { + entry_point: "0x0000000071727De22E5E9d8BAf0edAc6f37da032".into(), + sender: "0x2EF648D7C03412B832726fd4683E2625deA047Ba".into(), + pre_verification_gas: U256::from(1000000u64).to_big_endian_compact().into(), + verification_gas_limit: U256::from(100000u128).to_big_endian_compact().into(), + paymaster: "0xb0086171AC7b6BD4D046580bca6d6A4b0835c232".into(), + paymaster_verification_gas_limit: U256::from(99999u128).to_big_endian_compact().into(), + paymaster_post_op_gas_limit: U256::from(88888u128).to_big_endian_compact().into(), + // Dummy paymaster data. + paymaster_data: "00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b".decode_hex().unwrap().into(), + ..Proto::UserOperationV0_7::default() + }; + + let approve = Proto::mod_Transaction::ERC20Approve { + // Paymaster address. + spender: "0xb0086171AC7b6BD4D046580bca6d6A4b0835c232".into(), + amount: U256::encode_be_compact(655_360_197_115_136_u64), + }; + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(31337u64), + nonce: U256::encode_be_compact(0u64), + tx_mode: Proto::TransactionMode::UserOp, + gas_limit: U256::from(100000u128).to_big_endian_compact().into(), + max_fee_per_gas: U256::from(100000u128).to_big_endian_compact().into(), + max_inclusion_fee_per_gas: U256::from(100000u128).to_big_endian_compact().into(), + // USDT token. + to_address: "0xdac17f958d2ee523a2206206994597c13d831ec7".into(), + private_key: private_key.into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::erc20_approve( + approve, + ), + }), + user_operation_oneof: + Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation_v0_7(user_op), + user_operation_mode: Proto::SCAccountType::Biz4337, + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + assert_eq!( + hex::encode(output.pre_hash, false), + "68109b9caf49f7971b689307c9a77ceec46e4b8fa88421c4276dd846f782d92c" + ); + + let user_op: serde_json::Value = serde_json::from_slice(&output.encoded).unwrap(); + assert_eq!(user_op["callData"], "0x76276c82000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000b0086171ac7b6bd4d046580bca6d6a4b0835c2320000000000000000000000000000000000000000000000000002540befbfbd0000000000000000000000000000000000000000000000000000000000"); +} + +#[test] +fn test_barz_transfer_erc7702_eoa_batch() { + let private_key = + hex::decode("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483").unwrap(); + + let user_op = Proto::UserOperationV0_7 { + entry_point: "0x0000000071727De22E5E9d8BAf0edAc6f37da032".into(), + sender: "0x2EF648D7C03412B832726fd4683E2625deA047Ba".into(), + pre_verification_gas: U256::from(1000000u64).to_big_endian_compact().into(), + verification_gas_limit: U256::from(100000u128).to_big_endian_compact().into(), + paymaster: "0xb0086171AC7b6BD4D046580bca6d6A4b0835c232".into(), + paymaster_verification_gas_limit: U256::from(99999u128).to_big_endian_compact().into(), + paymaster_post_op_gas_limit: U256::from(88888u128).to_big_endian_compact().into(), + // Dummy paymaster data. + paymaster_data: "00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b".decode_hex().unwrap().into(), + ..Proto::UserOperationV0_7::default() + }; + + let mut calls = Vec::with_capacity(2); + + // ERC20 approve. At least one of the calls should be an ERC20.approve() + // so paymaster can collect tokens to cover the fees. + { + // Paymaster address. + let recipient = Address::from("0xb0086171AC7b6BD4D046580bca6d6A4b0835c232"); + let amount = U256::from(655_360_197_115_136_u64); + let payload = Erc20::approve(recipient, amount).unwrap(); + + calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + // USDT + address: "0xdac17f958d2ee523a2206206994597c13d831ec7".into(), + amount: Cow::default(), + payload: payload.into(), + }); + } + + // ERC20 transfer. Regular transaction. + { + let recipient = Address::from("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"); + let amount = U256::from(0x8AC7_2304_89E8_0000_u64); + let payload = Erc20::transfer(recipient, amount).unwrap(); + + calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + address: "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266".into(), + amount: Cow::default(), + payload: payload.into(), + }); + } + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(31337u64), + nonce: U256::encode_be_compact(0u64), + tx_mode: Proto::TransactionMode::UserOp, + gas_limit: U256::from(100000u128).to_big_endian_compact().into(), + max_fee_per_gas: U256::from(100000u128).to_big_endian_compact().into(), + max_inclusion_fee_per_gas: U256::from(100000u128).to_big_endian_compact().into(), + // USDT token. + to_address: "0xdac17f958d2ee523a2206206994597c13d831ec7".into(), + private_key: private_key.into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::batch( + Proto::mod_Transaction::Batch { calls }, + ), + }), + user_operation_oneof: + Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation_v0_7(user_op), + user_operation_mode: Proto::SCAccountType::Biz4337, + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!( + output.error, + SigningErrorType::OK, + "{}", + output.error_message + ); + + assert_eq!( + hex::encode(output.pre_hash, false), + "f6340068891dc3eb78959993151c421dde23982b3a1407c0dbbd62c2c22c3cb8" + ); + + let user_op: serde_json::Value = serde_json::from_slice(&output.encoded).unwrap(); + assert_eq!(user_op["callData"], "0x26da7d880000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000b0086171ac7b6bd4d046580bca6d6a4b0835c2320000000000000000000000000000000000000000000000000002540befbfbd000000000000000000000000000000000000000000000000000000000000000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b1266000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000"); +} diff --git a/src/Aeternity/Signer.cpp b/src/Aeternity/Signer.cpp index ec22012eb88..47b6ff36aea 100644 --- a/src/Aeternity/Signer.cpp +++ b/src/Aeternity/Signer.cpp @@ -15,7 +15,7 @@ using namespace TW; namespace TW::Aeternity { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveED25519); std::string sender_id = input.from_address(); std::string recipient_id = input.to_address(); std::string payload = input.payload(); @@ -34,7 +34,7 @@ Proto::SigningOutput Signer::sign(const TW::PrivateKey& privateKey, Transaction& auto msg = buildMessageToSign(txRlp); /// sign ed25519 - auto sigRaw = privateKey.sign(msg, TWCurveED25519); + auto sigRaw = privateKey.sign(msg); auto signature = Identifiers::prefixSignature + Base58::encodeCheck(sigRaw); /// encode the message using rlp diff --git a/src/Aion/Signer.cpp b/src/Aion/Signer.cpp index 8fe86f3904f..b79afa26bed 100644 --- a/src/Aion/Signer.cpp +++ b/src/Aion/Signer.cpp @@ -9,7 +9,7 @@ using namespace TW; namespace TW::Aion { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveED25519); auto transaction = Signer::buildTransaction(input); Signer::sign(key, transaction); @@ -23,7 +23,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { void Signer::sign(const PrivateKey& privateKey, Transaction& transaction) noexcept { auto encoded = transaction.encode(); auto hashData = Hash::blake2b(encoded, 32); - auto hashSignature = privateKey.sign(hashData, TWCurveED25519); + auto hashSignature = privateKey.sign(hashData); auto publicKeyData = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; // Aion signature = pubKeyBytes + signatureBytes diff --git a/src/Algorand/Signer.cpp b/src/Algorand/Signer.cpp index 71f44b6fcce..02a7bf0b3e7 100644 --- a/src/Algorand/Signer.cpp +++ b/src/Algorand/Signer.cpp @@ -18,11 +18,11 @@ const std::string ASSET_TRANSACTION = "axfer"; Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto protoOutput = Proto::SigningOutput(); - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveED25519); auto pubkey = key.getPublicKey(TWPublicKeyTypeED25519); auto preImageData = Signer::preImage(pubkey, input); - auto signature = key.sign(preImageData, TWCurveED25519); + auto signature = key.sign(preImageData); return Signer::encodeTransaction(signature, pubkey, input); } @@ -37,7 +37,7 @@ Data Signer::sign(const PrivateKey& privateKey, const BaseTransaction& transacti Data data; append(data, TRANSACTION_TAG); append(data, transaction.serialize()); - auto signature = privateKey.sign(data, TWCurveED25519); + auto signature = privateKey.sign(data); return {signature.begin(), signature.end()}; } diff --git a/src/Bitcoin/MessageSigner.cpp b/src/Bitcoin/MessageSigner.cpp index af8a6565322..3fb1107b203 100644 --- a/src/Bitcoin/MessageSigner.cpp +++ b/src/Bitcoin/MessageSigner.cpp @@ -52,7 +52,7 @@ std::string MessageSigner::signMessage(const PrivateKey& privateKey, const std:: throw std::invalid_argument("Address does not match key"); } const auto messageHash = messageToHash(message); - const auto signature = privateKey.sign(messageHash, TWCurveSECP256k1); + const auto signature = privateKey.sign(messageHash); // The V value: add 31 (or 27 for compressed), and move to the first byte const byte v = signature[SignatureRSLength] + PublicKey::SignatureVOffset + (compressed ? 4ul : 0ul); diff --git a/src/Bitcoin/SigningInput.cpp b/src/Bitcoin/SigningInput.cpp index e7cd22d8417..8ef598e9201 100644 --- a/src/Bitcoin/SigningInput.cpp +++ b/src/Bitcoin/SigningInput.cpp @@ -17,7 +17,7 @@ SigningInput::SigningInput(const Proto::SigningInput& input) { toAddress = input.to_address(); changeAddress = input.change_address(); for (auto&& key : input.private_key()) { - privateKeys.emplace_back(key); + privateKeys.emplace_back(key, TWCurveSECP256k1); } for (auto&& script : input.scripts()) { scripts[script.first] = Script(script.second.begin(), script.second.end()); diff --git a/src/Cardano/Signer.cpp b/src/Cardano/Signer.cpp index 42d51f9568e..886499d5286 100644 --- a/src/Cardano/Signer.cpp +++ b/src/Cardano/Signer.cpp @@ -129,7 +129,7 @@ Common::Proto::SigningError Signer::assembleSignatures(std::vector, Common::Proto::SigningError> Signer::signStep(Bitcoin: return Result, Common::Proto::SigningError>::failure(Common::Proto::Error_missing_private_key); } } else { - pubkey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1).bytes; + pubkey = PrivateKey(key, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes; } auto signature = createSignature(transactionToSign, script, key, data, index); @@ -263,7 +263,7 @@ Data Signer::createSignature(const Transaction& transaction, const Bitcoin::Scri return externalSignature; } - auto pk = PrivateKey(key); + auto pk = PrivateKey(key, TWCurveSECP256k1); auto signature = pk.signAsDER(Data(begin(sighash), end(sighash))); if (script.empty()) { return {}; @@ -275,7 +275,7 @@ Data Signer::createSignature(const Transaction& transaction, const Bitcoin::Scri Data Signer::keyForPublicKeyHash(const Data& hash) const { for (auto& key : input.private_key()) { - auto publicKey = PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1); + auto publicKey = PrivateKey(key, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1); auto keyHash = TW::Hash::ripemd(TW::Hash::blake256(publicKey.bytes)); if (std::equal(std::begin(keyHash), std::end(keyHash), std::begin(hash), std::end(hash))) { return Data(key.begin(), key.end()); diff --git a/src/EOS/Signer.cpp b/src/EOS/Signer.cpp index dc8019fbeed..1c1286e17c4 100644 --- a/src/EOS/Signer.cpp +++ b/src/EOS/Signer.cpp @@ -17,6 +17,8 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto signer = Signer(chainId); auto tx = signer.buildTx(input); + // values for Legacy and ModernK1 + TWCurve curve = TWCurveSECP256k1; // get key type EOS::Type type = Type::Legacy; switch (input.private_key_type()) { @@ -29,6 +31,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { break; case Proto::KeyType::MODERNR1: + curve = TWCurveNIST256p1; type = Type::ModernR1; break; default: @@ -36,7 +39,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { } // sign the transaction with a Signer - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), curve); signer.sign(key, type, tx); // Pack the transaction and add the json encoding to Signing outputput @@ -55,17 +58,14 @@ void Signer::sign(const PrivateKey& privateKey, Type type, Transaction& transact throw std::invalid_argument("Invalid transaction!"); } - // values for Legacy and ModernK1 - TWCurve curve = TWCurveSECP256k1; auto canonicalChecker = isCanonical; // Values for ModernR1 if (type == Type::ModernR1) { - curve = TWCurveNIST256p1; canonicalChecker = nullptr; } - const Data result = privateKey.sign(hash(transaction), curve, canonicalChecker); + const Data result = privateKey.sign(hash(transaction), canonicalChecker); transaction.signatures.emplace_back(Signature(result, type)); } diff --git a/src/Ethereum/Barz.cpp b/src/Ethereum/Barz.cpp index 9d1cef7dee7..54af0c466dc 100644 --- a/src/Ethereum/Barz.cpp +++ b/src/Ethereum/Barz.cpp @@ -243,8 +243,8 @@ Data getAuthorizationHash(const Data& chainId, const std::string& contractAddres std::vector getRSVY(const Data& hash, const std::string& privateKey) { auto privateKeyData = parse_hex(privateKey); - auto privateKeyObj = PrivateKey(privateKeyData); - auto signature = privateKeyObj.sign(hash, TWCurveSECP256k1); + auto privateKeyObj = PrivateKey(privateKeyData, TWCurveSECP256k1); + auto signature = privateKeyObj.sign(hash); if (signature.empty()) { return {}; } diff --git a/src/Everscale/Messages.cpp b/src/Everscale/Messages.cpp index 47d2b4d72a2..4d42921d2de 100644 --- a/src/Everscale/Messages.cpp +++ b/src/Everscale/Messages.cpp @@ -65,7 +65,7 @@ MessageData createSignedMessage(PublicKey& publicKey, PrivateKey& key, bool boun auto payloadCell = payloadCopy.intoCell(); Data data(payloadCell->hash.begin(), payloadCell->hash.end()); - auto signature = key.sign(data, TWCurveED25519); + auto signature = key.sign(data); payload.prependRaw(signature, static_cast(signature.size()) * 8); auto header = std::make_shared(InitData(publicKey).computeAddr(WorkchainType::Basechain)); diff --git a/src/Everscale/Signer.cpp b/src/Everscale/Signer.cpp index 33e5ee368b0..b3cbcbcdc57 100644 --- a/src/Everscale/Signer.cpp +++ b/src/Everscale/Signer.cpp @@ -12,7 +12,7 @@ namespace TW::Everscale { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto key = PrivateKey(input.private_key()); + auto key = PrivateKey(input.private_key(), TWCurveED25519); auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); auto protoOutput = Proto::SigningOutput(); diff --git a/src/FIO/Signer.cpp b/src/FIO/Signer.cpp index 0e90e84df30..566af04f1ec 100644 --- a/src/FIO/Signer.cpp +++ b/src/FIO/Signer.cpp @@ -42,7 +42,7 @@ Proto::SigningOutput Signer::compile(const Proto::SigningInput& input, const Dat Data Signer::signData(const PrivateKey& privKey, const Data& data) { Data hash = Hash::sha256(data); - Data signature = privKey.sign(hash, TWCurveSECP256k1, isCanonical); + Data signature = privKey.sign(hash, isCanonical); return signature; } diff --git a/src/FIO/TransactionBuilder.cpp b/src/FIO/TransactionBuilder.cpp index 4a890d81705..3061accf7b2 100644 --- a/src/FIO/TransactionBuilder.cpp +++ b/src/FIO/TransactionBuilder.cpp @@ -70,7 +70,7 @@ string TransactionBuilder::actionName(const Proto::SigningInput& input) { } string TransactionBuilder::sign(Proto::SigningInput in) { - PrivateKey privateKey(in.private_key()); + PrivateKey privateKey(in.private_key(), TWCurveSECP256k1); PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); Address owner(publicKey); diff --git a/src/Filecoin/Signer.cpp b/src/Filecoin/Signer.cpp index d0d72f4dd2c..2a946863192 100644 --- a/src/Filecoin/Signer.cpp +++ b/src/Filecoin/Signer.cpp @@ -47,7 +47,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { Data Signer::sign(const PrivateKey& privateKey, Transaction& transaction) noexcept { Data toSign = Hash::blake2b(transaction.cid(), 32); - auto signature = privateKey.sign(toSign, TWCurveSECP256k1); + auto signature = privateKey.sign(toSign); return Data(signature.begin(), signature.end()); } @@ -77,7 +77,7 @@ Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& pub Proto::SigningOutput Signer::signSecp256k1(const Proto::SigningInput& input) { // Load private key and transaction from Protobuf input. - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); auto pubkey = key.getPublicKey(TWPublicKeyTypeSECP256k1Extended); auto transaction = Signer::buildTx(pubkey, input); @@ -123,7 +123,7 @@ Transaction Signer::buildTx(const PublicKey& publicKey, const Proto::SigningInpu /// https://github.com/filecoin-project/lotus/blob/ce17546a762eef311069e13410d15465d832a45e/chain/messagesigner/messagesigner.go#L197-L211 Proto::SigningOutput Signer::signDelegated(const Proto::SigningInput& input) { // Load private key from Protobuf input. - auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); auto pubkey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); // Load the transaction params. @@ -172,7 +172,7 @@ Proto::SigningOutput Signer::signDelegated(const Proto::SigningInput& input) { auto preHash = data(ethOutput.data_hash()); // Sign transaction as an Ethereum EIP1559 native transfer. - Data signature = privateKey.sign(preHash, TWCurveSECP256k1); + Data signature = privateKey.sign(preHash); // Generate a Filecoin signed message. Transaction filecoinTransaction( diff --git a/src/HDWallet.cpp b/src/HDWallet.cpp index 597484381b0..a329f6634da 100644 --- a/src/HDWallet.cpp +++ b/src/HDWallet.cpp @@ -147,14 +147,14 @@ template PrivateKey HDWallet::getMasterKey(TWCurve curve) const { auto node = getMasterNode(*this, curve); auto data = Data(node.private_key, node.private_key + PrivateKey::_size); - return PrivateKey(data); + return PrivateKey(data, curve); } template PrivateKey HDWallet::getMasterKeyExtension(TWCurve curve) const { auto node = getMasterNode(*this, curve); auto data = Data(node.private_key_extension, node.private_key_extension + PrivateKey::_size); - return PrivateKey(data); + return PrivateKey(data, curve); } template @@ -173,7 +173,7 @@ PrivateKey HDWallet::getKeyByCurve(TWCurve curve, const DerivationPath case TWPrivateKeyTypeCardano: { if (derivationPath.indices.size() < 4 || derivationPath.indices[3].value > 1) { // invalid derivation path - return PrivateKey(Data(PrivateKey::cardanoKeySize)); + return PrivateKey(Data(PrivateKey::cardanoKeySize), curve); } const DerivationPath stakingPath = cardanoStakingDerivationPath(derivationPath); @@ -188,7 +188,7 @@ PrivateKey HDWallet::getKeyByCurve(TWCurve curve, const DerivationPath auto chainCode2 = Data(node2.chain_code, node2.chain_code + PrivateKey::_size); TW::memzero(&node); - return PrivateKey(pkData, extData, chainCode, pkData2, extData2, chainCode2); + return PrivateKey(pkData, extData, chainCode, pkData2, extData2, chainCode2, curve); } case TWPrivateKeyTypeDefault: default: @@ -196,9 +196,9 @@ PrivateKey HDWallet::getKeyByCurve(TWCurve curve, const DerivationPath auto data = Data(node.private_key, node.private_key + PrivateKey::_size); TW::memzero(&node); if (curve == TWCurveStarkex) { - return ImmutableX::getPrivateKeyFromEthPrivKey(PrivateKey(data)); + return ImmutableX::getPrivateKeyFromEthPrivKey(PrivateKey(data, curve)); } - return PrivateKey(data); + return PrivateKey(data, curve); } } @@ -312,7 +312,7 @@ std::optional HDWallet::getPrivateKeyFromExtended(const st hdnode_private_ckd(&node, path.change()); hdnode_private_ckd(&node, path.address()); - return PrivateKey(Data(node.private_key, node.private_key + 32)); + return PrivateKey(Data(node.private_key, node.private_key + 32), curve); } template diff --git a/src/Harmony/Signer.cpp b/src/Harmony/Signer.cpp index 79f59e80f08..8d84778f3c7 100644 --- a/src/Harmony/Signer.cpp +++ b/src/Harmony/Signer.cpp @@ -23,7 +23,7 @@ std::tuple Signer::values(const uint256_t& chai std::tuple Signer::sign(const uint256_t& chainID, const PrivateKey& privateKey, const Data& hash) noexcept { - auto signature = privateKey.sign(hash, TWCurveSECP256k1); + auto signature = privateKey.sign(hash); return values(chainID, signature); } @@ -86,7 +86,7 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { } Proto::SigningOutput Signer::signTransaction(const Proto::SigningInput& input) { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); auto transaction = Signer::buildUnsignedTransaction(input); @@ -131,7 +131,7 @@ uint8_t Signer::getEnum() noexcept { template Proto::SigningOutput Signer::signStaking(const Proto::SigningInput &input, function func) { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); auto stakingTx = buildUnsignedStakingTransaction(input, func); auto signer = Signer(uint256_t(load(input.chain_id()))); diff --git a/src/Hedera/Signer.cpp b/src/Hedera/Signer.cpp index 47628377551..4020a452aaf 100644 --- a/src/Hedera/Signer.cpp +++ b/src/Hedera/Signer.cpp @@ -67,7 +67,7 @@ static inline Proto::SigningOutput sign(const proto::TransactionBody& body, cons auto protoOutput = Proto::SigningOutput(); Data encoded; auto encodedBody = data(body.SerializeAsString()); - auto signedBody = privateKey.sign(encodedBody, TWCurveED25519); + auto signedBody = privateKey.sign(encodedBody); auto sigMap = proto::SignatureMap(); auto* sigPair = sigMap.add_sigpair(); sigPair->set_ed25519(signedBody.data(), signedBody.size()); @@ -86,7 +86,7 @@ static inline Proto::SigningOutput sign(const proto::TransactionBody& body, cons namespace TW::Hedera { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveED25519); auto body = internals::transactionBodyPrerequisites(input); switch (input.body().data_case()) { diff --git a/src/IOST/Account.cpp b/src/IOST/Account.cpp index 1aa3c91f5b7..24b17e61a54 100644 --- a/src/IOST/Account.cpp +++ b/src/IOST/Account.cpp @@ -47,15 +47,15 @@ Account::Account(const Proto::AccountInfo& account) { } Data Account::sign(const Data& digest, TWCurve curve) const { - return PrivateKey(activeKey).sign(digest, curve); + return PrivateKey(activeKey, curve).sign(digest); } Data Account::publicActiveKey() const { - return PrivateKey(activeKey).getPublicKey(TWPublicKeyTypeED25519).bytes; + return PrivateKey(activeKey, TWCurveED25519).getPublicKey(TWPublicKeyTypeED25519).bytes; } Data Account::publicOwnerKey() const { - return PrivateKey(ownerKey).getPublicKey(TWPublicKeyTypeED25519).bytes; + return PrivateKey(ownerKey, TWCurveED25519).getPublicKey(TWPublicKeyTypeED25519).bytes; } std::string Account::address(const std::string& publickey) { diff --git a/src/Icon/Signer.cpp b/src/Icon/Signer.cpp index e537a72740d..e24f30feeb7 100644 --- a/src/Icon/Signer.cpp +++ b/src/Icon/Signer.cpp @@ -79,8 +79,8 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { Proto::SigningOutput Signer::sign() const noexcept { const auto hash = Hash::sha3_256(Signer::preImage()); - const auto key = PrivateKey(input.private_key()); - const auto signature = key.sign(hash, TWCurveSECP256k1); + const auto key = PrivateKey(input.private_key(), TWCurveSECP256k1); + const auto signature = key.sign(hash); auto output = Proto::SigningOutput(); output.set_signature(signature.data(), signature.size()); diff --git a/src/ImmutableX/StarkKey.cpp b/src/ImmutableX/StarkKey.cpp index 51dec1d669f..5adb08cc96e 100644 --- a/src/ImmutableX/StarkKey.cpp +++ b/src/ImmutableX/StarkKey.cpp @@ -34,11 +34,11 @@ std::string grindKey(const Data& seed) { PrivateKey getPrivateKeyFromSeed(const Data& seed, const DerivationPath& path) { auto key = HDWallet<32>::bip32DeriveRawSeed(TWCoinTypeEthereum, seed, path); auto data = parse_hex(grindKey(key.bytes), true); - return PrivateKey(data); + return PrivateKey(data, TWCurveStarkex); } PrivateKey getPrivateKeyFromEthPrivKey(const PrivateKey& ethPrivKey) { - return PrivateKey(parse_hex(ImmutableX::grindKey(ethPrivKey.bytes), true)); + return PrivateKey(parse_hex(ImmutableX::grindKey(ethPrivKey.bytes), true), TWCurveStarkex); } PrivateKey getPrivateKeyFromRawSignature(const Data& signature, const DerivationPath& derivationPath) { diff --git a/src/IoTeX/Signer.cpp b/src/IoTeX/Signer.cpp index 75c0d2cf7a6..9c139888d1c 100644 --- a/src/IoTeX/Signer.cpp +++ b/src/IoTeX/Signer.cpp @@ -16,17 +16,17 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { } Data Signer::sign() const { - auto key = PrivateKey(input.privatekey()); - return key.sign(hash(), TWCurveSECP256k1); + auto key = PrivateKey(input.privatekey(), TWCurveSECP256k1); + return key.sign(hash()); } Proto::SigningOutput Signer::build() const { auto signedAction = Proto::Action(); signedAction.mutable_core()->MergeFrom(action); - auto key = PrivateKey(input.privatekey()); + auto key = PrivateKey(input.privatekey(), TWCurveSECP256k1); auto pk = key.getPublicKey(TWPublicKeyTypeSECP256k1Extended).bytes; signedAction.set_senderpubkey(pk.data(), pk.size()); - auto sig = key.sign(hash(), TWCurveSECP256k1); + auto sig = key.sign(hash()); signedAction.set_signature(sig.data(), sig.size()); auto output = Proto::SigningOutput(); diff --git a/src/Keystore/StoredKey.cpp b/src/Keystore/StoredKey.cpp index b216461fe31..ca66e60c179 100644 --- a/src/Keystore/StoredKey.cpp +++ b/src/Keystore/StoredKey.cpp @@ -61,8 +61,8 @@ StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& na StoredKey key = createWithPrivateKey(name, password, privateKeyData, encryption); const auto derivationPath = TW::derivationPath(coin); const auto pubKeyType = TW::publicKeyType(coin); - const auto pubKey = PrivateKey(privateKeyData).getPublicKey(pubKeyType); - const auto address = TW::deriveAddress(coin, PrivateKey(privateKeyData)); + const auto pubKey = PrivateKey(privateKeyData, TWCoinTypeCurve(coin)).getPublicKey(pubKeyType); + const auto address = TW::deriveAddress(coin, PrivateKey(privateKeyData, TWCoinTypeCurve(coin))); key.accounts.emplace_back(address, coin, TWDerivationDefault, derivationPath, hex(pubKey.bytes), ""); return key; } @@ -261,7 +261,7 @@ const PrivateKey StoredKey::privateKey(TWCoinType coin, [[maybe_unused]] TWDeriv return wallet.getKey(coin, account.derivationPath); } // type == StoredKeyType::privateKey - return PrivateKey(payload.decrypt(password)); + return PrivateKey(payload.decrypt(password), TWCoinTypeCurve(coin)); } void StoredKey::fixAddresses(const Data& password) { @@ -280,12 +280,12 @@ void StoredKey::fixAddresses(const Data& password) { } break; case StoredKeyType::privateKey: { - auto key = PrivateKey(payload.decrypt(password)); for (auto& account : accounts) { if (!account.address.empty() && !account.publicKey.empty() && TW::validateAddress(account.coin, account.address)) { continue; } + auto key = PrivateKey(payload.decrypt(password), TWCoinTypeCurve(account.coin)); updateAddressForAccount(key, account); } } break; diff --git a/src/MultiversX/Signer.cpp b/src/MultiversX/Signer.cpp index a2a80fdedd3..16abbd2c835 100644 --- a/src/MultiversX/Signer.cpp +++ b/src/MultiversX/Signer.cpp @@ -15,9 +15,9 @@ namespace TW::MultiversX { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { TransactionFactory factory; - auto privateKey = PrivateKey(input.private_key()); + auto privateKey = PrivateKey(input.private_key(), TWCurveED25519); auto signableAsData = buildUnsignedTxBytes(input); - auto signature = privateKey.sign(signableAsData, TWCurveED25519); + auto signature = privateKey.sign(signableAsData); return buildSigningOutput(input, signature); } diff --git a/src/NEAR/Serialization.cpp b/src/NEAR/Serialization.cpp index f2272383ede..f1aac366a41 100644 --- a/src/NEAR/Serialization.cpp +++ b/src/NEAR/Serialization.cpp @@ -164,7 +164,7 @@ static void writeAction(Data& data, const Proto::Action& action) { Data transactionData(const Proto::SigningInput& input) { Data data; writeString(data, input.signer_id()); - auto key = PrivateKey(input.private_key()); + auto key = PrivateKey(input.private_key(), TWCurveED25519); auto public_key = key.getPublicKey(TWPublicKeyTypeED25519); auto public_key_proto = Proto::PublicKey(); public_key_proto.set_data(public_key.bytes.data(), public_key.bytes.size()); diff --git a/src/NEAR/Signer.cpp b/src/NEAR/Signer.cpp index 9e2589ed09e..f5ae073d484 100644 --- a/src/NEAR/Signer.cpp +++ b/src/NEAR/Signer.cpp @@ -12,9 +12,9 @@ namespace TW::NEAR { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto transaction = transactionData(input); - auto key = PrivateKey(input.private_key()); + auto key = PrivateKey(input.private_key(), TWCurveED25519); auto hash = Hash::sha256(transaction); - auto signature = key.sign(hash, TWCurveED25519); + auto signature = key.sign(hash); auto output = Proto::SigningOutput(); auto signedTransaction = signedTransactionData(transaction, signature); output.set_signed_transaction(signedTransaction.data(), signedTransaction.size()); diff --git a/src/NEO/Signer.cpp b/src/NEO/Signer.cpp index df3187c9447..c3f210e08d2 100644 --- a/src/NEO/Signer.cpp +++ b/src/NEO/Signer.cpp @@ -46,7 +46,7 @@ void Signer::sign(Transaction& tx) const { } Data Signer::sign(const Data& data) const { - auto signature = getPrivateKey().sign(TW::Hash::sha256(data), TWCurveNIST256p1); + auto signature = getPrivateKey().sign(TW::Hash::sha256(data)); signature.pop_back(); return signature; } @@ -264,7 +264,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto output = Proto::SigningOutput(); try { auto signer = - Signer(PrivateKey(Data(input.private_key().begin(), input.private_key().end()))); + Signer(PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveNIST256p1)); Proto::TransactionPlan plan; if (input.has_plan()) { plan = input.plan(); diff --git a/src/NULS/Signer.cpp b/src/NULS/Signer.cpp index 2e1ffca2784..51b44385159 100644 --- a/src/NULS/Signer.cpp +++ b/src/NULS/Signer.cpp @@ -151,11 +151,11 @@ Data Signer::sign() const { Data txHash = calcTransactionDigest(dataRet); Data privKey = data(input.private_key()); - auto priv = PrivateKey(privKey); + auto priv = PrivateKey(privKey, TWCurveSECP256k1); auto transactionSignature = makeTransactionSignature(priv, txHash); if (Address::isValid(input.fee_payer()) && input.from() != input.fee_payer()) { Data feePayerPrivKey = data(input.fee_payer_private_key()); - auto feePayerPriv = PrivateKey(feePayerPrivKey); + auto feePayerPriv = PrivateKey(feePayerPrivKey, TWCurveSECP256k1); auto feePayerTransactionSignature = makeTransactionSignature(feePayerPriv, txHash); transactionSignature.insert(transactionSignature.end(), feePayerTransactionSignature.begin(), diff --git a/src/Nano/Signer.cpp b/src/Nano/Signer.cpp index 1dc1deeb228..3599953777f 100644 --- a/src/Nano/Signer.cpp +++ b/src/Nano/Signer.cpp @@ -115,7 +115,7 @@ std::array hashBlockData(const PublicKey& publicKey, const Proto::Sign } Signer::Signer(const Proto::SigningInput& input) - : privateKey(Data(input.private_key().begin(), input.private_key().end())), + : privateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveED25519Blake2bNano), publicKey(privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b)), input(input), previous{previousFromInput(input)}, @@ -143,7 +143,7 @@ std::string Signer::signJSON(const std::string& json, const Data& key) { std::array Signer::sign() const noexcept { auto digest = Data(blockHash.begin(), blockHash.end()); - auto sig = privateKey.sign(digest, TWCurveED25519Blake2bNano); + auto sig = privateKey.sign(digest); std::array signature = {0}; std::copy_n(sig.begin(), signature.size(), signature.begin()); diff --git a/src/Nebulas/Signer.cpp b/src/Nebulas/Signer.cpp index ada56c94f8b..026683667e5 100644 --- a/src/Nebulas/Signer.cpp +++ b/src/Nebulas/Signer.cpp @@ -15,7 +15,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto tx = signer.buildTransaction(input); - auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); signer.sign(privateKey, tx); auto output = Proto::SigningOutput(); @@ -29,7 +29,7 @@ void Signer::sign(const PrivateKey& privateKey, Transaction& transaction) const transaction.hash = this->hash(transaction); transaction.chainID = chainID; transaction.algorithm = 1; - transaction.signature = privateKey.sign(transaction.hash, TWCurveSECP256k1); + transaction.signature = privateKey.sign(transaction.hash); transaction.serializeToRaw(); } diff --git a/src/Nervos/Signer.cpp b/src/Nervos/Signer.cpp index d5cb006ad30..1fd9b24347b 100644 --- a/src/Nervos/Signer.cpp +++ b/src/Nervos/Signer.cpp @@ -34,7 +34,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& signingInput) noexc std::vector privateKeys; privateKeys.reserve(signingInput.private_key_size()); for (auto&& privateKey : signingInput.private_key()) { - privateKeys.emplace_back(privateKey); + privateKeys.emplace_back(PrivateKey(privateKey, TWCurveSECP256k1)); } auto error = tx.sign(privateKeys); if (error != Common::Proto::OK) { diff --git a/src/Nervos/Transaction.cpp b/src/Nervos/Transaction.cpp index ca65efa723e..800071110c5 100644 --- a/src/Nervos/Transaction.cpp +++ b/src/Nervos/Transaction.cpp @@ -197,7 +197,7 @@ Common::Proto::SigningError Transaction::signWitnesses(const PrivateKey& private } auto messageHash = Hash::blake2b(message, 32, Constants::gHashPersonalization); - auto signature = privateKey.sign(messageHash, TWCurveSECP256k1); + auto signature = privateKey.sign(messageHash); if (signature.empty()) { // Error: Failed to sign return Common::Proto::Error_signing; diff --git a/src/Nimiq/Signer.cpp b/src/Nimiq/Signer.cpp index e8de7375c62..4ad577cf0a1 100644 --- a/src/Nimiq/Signer.cpp +++ b/src/Nimiq/Signer.cpp @@ -10,7 +10,7 @@ namespace TW::Nimiq { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveED25519); auto pubkey = key.getPublicKey(TWPublicKeyTypeED25519); std::array pubkeyBytes; std::copy(pubkey.bytes.begin(), pubkey.bytes.end(), pubkeyBytes.data()); @@ -34,7 +34,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { void Signer::sign(const PrivateKey& privateKey, Transaction& transaction) const noexcept { auto preImage = transaction.getPreImage(); - auto signature = privateKey.sign(preImage, TWCurveED25519); + auto signature = privateKey.sign(preImage); std::copy(signature.begin(), signature.end(), transaction.signature.begin()); } diff --git a/src/Oasis/Signer.cpp b/src/Oasis/Signer.cpp index ea3405382a8..ca687bccf05 100644 --- a/src/Oasis/Signer.cpp +++ b/src/Oasis/Signer.cpp @@ -24,7 +24,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { } Data Signer::build() const { - auto privateKey = PrivateKey(input.private_key()); + auto privateKey = PrivateKey(input.private_key(), TWCurveED25519); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); if(input.has_transfer()) { @@ -53,7 +53,7 @@ Data Signer::build() const { template Data Signer::signTransaction(T& tx) const { - auto privateKey = PrivateKey(input.private_key()); + auto privateKey = PrivateKey(input.private_key(), TWCurveED25519); // The use of this context thing is explained here --> // https://docs.oasis.dev/oasis-core/common-functionality/crypto#domain-separation @@ -62,7 +62,7 @@ Data Signer::signTransaction(T& tx) const { dataToHash.insert(dataToHash.end(), encodedMessage.begin(), encodedMessage.end()); auto hash = Hash::sha512_256(dataToHash); - auto signature = privateKey.sign(hash, TWCurveED25519); + auto signature = privateKey.sign(hash); return Data(signature.begin(), signature.end()); } diff --git a/src/Ontology/Oep4TxBuilder.cpp b/src/Ontology/Oep4TxBuilder.cpp index 9041adc3b78..2ef9d41ca78 100644 --- a/src/Ontology/Oep4TxBuilder.cpp +++ b/src/Ontology/Oep4TxBuilder.cpp @@ -24,8 +24,8 @@ Data Oep4TxBuilder::balanceOf(const Ontology::Proto::SigningInput& input) { Data Oep4TxBuilder::transfer(const Ontology::Proto::SigningInput& input) { Oep4 oep4(parse_hex(input.contract())); - auto payerSigner = Signer(PrivateKey(input.payer_private_key())); - auto fromSigner = Signer(PrivateKey(input.owner_private_key())); + auto payerSigner = Signer(PrivateKey(input.payer_private_key(), TWCurveNIST256p1)); + auto fromSigner = Signer(PrivateKey(input.owner_private_key(), TWCurveNIST256p1)); auto toAddress = Address(input.to_address()); auto tranferTx = oep4.transfer(fromSigner, toAddress, input.amount(), payerSigner, input.gas_price(), input.gas_limit(), input.nonce()); diff --git a/src/Ontology/OngTxBuilder.cpp b/src/Ontology/OngTxBuilder.cpp index 2cae37b9cb1..6f6fcfc4383 100644 --- a/src/Ontology/OngTxBuilder.cpp +++ b/src/Ontology/OngTxBuilder.cpp @@ -20,8 +20,8 @@ Data OngTxBuilder::balanceOf(const Ontology::Proto::SigningInput& input) { } Data OngTxBuilder::transfer(const Ontology::Proto::SigningInput& input) { - auto payer = Signer(PrivateKey(input.payer_private_key())); - auto owner = Signer(PrivateKey(input.owner_private_key())); + auto payer = Signer(PrivateKey(input.payer_private_key(), TWCurveNIST256p1)); + auto owner = Signer(PrivateKey(input.owner_private_key(), TWCurveNIST256p1)); auto toAddress = Address(input.to_address()); auto transaction = Ong().transfer(owner, toAddress, input.amount(), payer, input.gas_price(), input.gas_limit(), input.nonce()); @@ -30,8 +30,8 @@ Data OngTxBuilder::transfer(const Ontology::Proto::SigningInput& input) { } Data OngTxBuilder::withdraw(const Ontology::Proto::SigningInput& input) { - auto payer = Signer(PrivateKey(input.payer_private_key())); - auto owner = Signer(PrivateKey(input.owner_private_key())); + auto payer = Signer(PrivateKey(input.payer_private_key(), TWCurveNIST256p1)); + auto owner = Signer(PrivateKey(input.owner_private_key(), TWCurveNIST256p1)); auto toAddress = Address(input.to_address()); auto transaction = Ong().withdraw(owner, toAddress, input.amount(), payer, input.gas_price(), input.gas_limit(), input.nonce()); diff --git a/src/Ontology/OntTxBuilder.cpp b/src/Ontology/OntTxBuilder.cpp index dc26b38e192..3da313eda46 100644 --- a/src/Ontology/OntTxBuilder.cpp +++ b/src/Ontology/OntTxBuilder.cpp @@ -20,8 +20,8 @@ Data OntTxBuilder::balanceOf(const Ontology::Proto::SigningInput& input) { } Data OntTxBuilder::transfer(const Ontology::Proto::SigningInput& input) { - auto payerSigner = Signer(PrivateKey(input.payer_private_key())); - auto fromSigner = Signer(PrivateKey(input.owner_private_key())); + auto payerSigner = Signer(PrivateKey(input.payer_private_key(), TWCurveNIST256p1)); + auto fromSigner = Signer(PrivateKey(input.owner_private_key(), TWCurveNIST256p1)); auto toAddress = Address(input.to_address()); auto tranferTx = Ont().transfer(fromSigner, toAddress, input.amount(), payerSigner, input.gas_price(), input.gas_limit(), input.nonce()); diff --git a/src/Ontology/Signer.cpp b/src/Ontology/Signer.cpp index 48cf1bab857..bb3141bc6eb 100644 --- a/src/Ontology/Signer.cpp +++ b/src/Ontology/Signer.cpp @@ -56,7 +56,7 @@ void Signer::sign(Transaction& tx) const { if (tx.sigVec.size() >= Transaction::sigVecLimit) { throw std::runtime_error("the number of transaction signatures should not be over 16."); } - auto signature = getPrivateKey().sign(tx.txHash(), TWCurveNIST256p1); + auto signature = getPrivateKey().sign(tx.txHash()); signature.pop_back(); tx.sigVec.emplace_back(publicKey, signature, 1); } @@ -65,7 +65,7 @@ void Signer::addSign(Transaction& tx) const { if (tx.sigVec.size() >= Transaction::sigVecLimit) { throw std::runtime_error("the number of transaction signatures should not be over 16."); } - auto signature = getPrivateKey().sign(tx.txHash(), TWCurveNIST256p1); + auto signature = getPrivateKey().sign(tx.txHash()); signature.pop_back(); tx.sigVec.emplace_back(publicKey, signature, 1); } diff --git a/src/PrivateKey.cpp b/src/PrivateKey.cpp index 501dfc4124c..be4c8dc4f4b 100644 --- a/src/PrivateKey.cpp +++ b/src/PrivateKey.cpp @@ -114,16 +114,18 @@ TWPrivateKeyType PrivateKey::getType(TWCurve curve) noexcept { } } -PrivateKey::PrivateKey(const Data& data) { - if (!isValid(data)) { +PrivateKey::PrivateKey(const Data& data, TWCurve curve) { + if (!isValid(data, curve)) { throw std::invalid_argument("Invalid private key data"); } bytes = data; + _curve = curve; } PrivateKey::PrivateKey( const Data& key1, const Data& extension1, const Data& chainCode1, - const Data& key2, const Data& extension2, const Data& chainCode2) { + const Data& key2, const Data& extension2, const Data& chainCode2, + TWCurve curve) { if (key1.size() != _size || extension1.size() != _size || chainCode1.size() != _size || key2.size() != _size || extension2.size() != _size || chainCode2.size() != _size) { throw std::invalid_argument("Invalid private key or extended key data"); @@ -134,6 +136,7 @@ PrivateKey::PrivateKey( append(bytes, key2); append(bytes, extension2); append(bytes, chainCode2); + _curve = curve; } PublicKey PrivateKey::getPublicKey(TWPublicKeyType type) const { @@ -205,10 +208,10 @@ int ecdsa_sign_digest_checked(const ecdsa_curve* curve, const uint8_t* priv_key, return ecdsa_sign_digest(curve, priv_key, digest, sig, pby, is_canonical); } -Data PrivateKey::sign(const Data& digest, TWCurve curve) const { +Data PrivateKey::sign(const Data& digest) const { Data result; bool success = false; - switch (curve) { + switch (_curve) { case TWCurveSECP256k1: { result.resize(65); success = ecdsa_sign_digest_checked(&secp256k1, key().data(), digest.data(), digest.size(), result.data(), result.data() + 64, nullptr) == 0; @@ -242,7 +245,7 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve) const { success = ecdsa_sign_digest_checked(&nist256p1, key().data(), digest.data(), digest.size(), result.data(), result.data() + 64, nullptr) == 0; } break; case TWCurveStarkex: { - result = rust_private_key_sign(key(), digest, curve); + result = rust_private_key_sign(key(), digest, _curve); success = result.size() == 64; } break; case TWCurveNone: @@ -256,10 +259,10 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve) const { return result; } -Data PrivateKey::sign(const Data& digest, TWCurve curve, int (*canonicalChecker)(uint8_t by, uint8_t sig[64])) const { +Data PrivateKey::sign(const Data& digest, int (*canonicalChecker)(uint8_t by, uint8_t sig[64])) const { Data result; bool success = false; - switch (curve) { + switch (_curve) { case TWCurveSECP256k1: { result.resize(65); success = ecdsa_sign_digest_checked(&secp256k1, key().data(), digest.data(), digest.size(), result.data() + 1, result.data(), canonicalChecker) == 0; @@ -288,6 +291,9 @@ Data PrivateKey::sign(const Data& digest, TWCurve curve, int (*canonicalChecker) } Data PrivateKey::signAsDER(const Data& digest) const { + if (_curve != TWCurveSECP256k1) { + throw std::invalid_argument("DER signature is only supported for SECP256k1"); + } Data sig(64); bool success = ecdsa_sign_digest(&secp256k1, key().data(), digest.data(), sig.data(), nullptr, nullptr) == 0; @@ -304,6 +310,9 @@ Data PrivateKey::signAsDER(const Data& digest) const { } Data PrivateKey::signZilliqa(const Data& message) const { + if (_curve != TWCurveSECP256k1) { + throw std::invalid_argument("Zilliqa signature is only supported for SECP256k1"); + } Data sig(64); bool success = zil_schnorr_sign(&secp256k1, key().data(), message.data(), static_cast(message.size()), sig.data()) == 0; diff --git a/src/PrivateKey.h b/src/PrivateKey.h index c6ab3ddd8dc..ccc6a1125ba 100644 --- a/src/PrivateKey.h +++ b/src/PrivateKey.h @@ -10,6 +10,8 @@ #include #include +#include + namespace TW { class PrivateKey { @@ -41,16 +43,21 @@ class PrivateKey { // obtain private key type used by the curve/coin static TWPrivateKeyType getType(TWCurve curve) noexcept; - /// Initializes a private key with an array of bytes. Size must be exact (normally 32, or 192 for extended) - explicit PrivateKey(const Data& data); + /// Initializes a private key with an array of bytes and a curve. + /// Size of the data must be exact (normally 32, or 192 for extended) + /// Signing functions will throw an exception if the provided curve is different from the one specified. + explicit PrivateKey(const Data& data, TWCurve curve); - /// Initializes a private key from a string of bytes. - explicit PrivateKey(const std::string& data) : PrivateKey(TW::data(data)) {} + /// Initializes a private key from a string of bytes and a curve. + /// Signing functions will throw an exception if the provided curve is different from the one specified. + explicit PrivateKey(const std::string& data, TWCurve curve) : PrivateKey(TW::data(data), curve) {} - /// Initializes a Cardano style key + /// Initializes a Cardano style key with a specified curve. + /// Signing functions will throw an exception if the provided curve is different from the one specified. explicit PrivateKey( const Data& bytes1, const Data& extension1, const Data& chainCode1, - const Data& bytes2, const Data& extension2, const Data& chainCode2); + const Data& bytes2, const Data& extension2, const Data& chainCode2, + TWCurve curve); PrivateKey(const PrivateKey& other) = default; PrivateKey& operator=(const PrivateKey& other) = default; @@ -63,22 +70,30 @@ class PrivateKey { /// Returns the public key for this private key. PublicKey getPublicKey(enum TWPublicKeyType type) const; - /// Signs a digest using the given ECDSA curve. - Data sign(const Data& digest, TWCurve curve) const; + /// Signs a digest using the given ECDSA curve at the time of construction. + /// IF constructed without a curve, an exception will be thrown. + Data sign(const Data& digest) const; /// Signs a digest using the given ECDSA curve and prepends the recovery id (a la graphene) /// Only a sig that passes canonicalChecker is returned - Data sign(const Data& digest, TWCurve curve, int(*canonicalChecker)(uint8_t by, uint8_t sig[64])) const; + Data sign(const Data& digest, int (*canonicalChecker)(uint8_t by, uint8_t sig[64])) const; /// Signs a digest using the given ECDSA curve. The result is encoded with /// DER. + /// If constructed with a curve, an exception will be thrown if the curve does not match SECP256k1. Data signAsDER(const Data& digest) const; /// Signs a digest using given ECDSA curve, returns Zilliqa schnorr signature + /// If constructed with a curve, an exception will be thrown if the curve does not match SECP256k1. Data signZilliqa(const Data& message) const; /// Cleanup contents (fill with 0s), called before destruction void cleanup(); + + /// Returns the curve used by the private key. + TWCurve curve() const { return _curve; } +private: + TWCurve _curve; }; } // namespace TW diff --git a/src/StarkEx/MessageSigner.cpp b/src/StarkEx/MessageSigner.cpp index 967bc0bf7e0..02257e76d55 100644 --- a/src/StarkEx/MessageSigner.cpp +++ b/src/StarkEx/MessageSigner.cpp @@ -9,7 +9,7 @@ namespace TW::StarkEx { std::string MessageSigner::signMessage(const TW::PrivateKey& privateKey, const std::string& message) { auto digest = parse_hex(message, true); - return hex(privateKey.sign(digest, TWCurveStarkex)); + return hex(privateKey.sign(digest)); } bool MessageSigner::verifyMessage(const PublicKey& publicKey, const std::string& message, const std::string& signature) noexcept { diff --git a/src/Stellar/Signer.cpp b/src/Stellar/Signer.cpp index 01ac21aa776..1718ff8b571 100644 --- a/src/Stellar/Signer.cpp +++ b/src/Stellar/Signer.cpp @@ -23,7 +23,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { std::string Signer::sign() const noexcept { - auto key = PrivateKey(Data(_input.private_key().begin(), _input.private_key().end())); + auto key = PrivateKey(Data(_input.private_key().begin(), _input.private_key().end()), TWCurveED25519); auto account = Address(_input.account()); auto encoded = encode(_input); @@ -39,7 +39,7 @@ std::string Signer::sign() const noexcept { auto hash = Hash::sha256(encodedWithHeaders); auto data = Data(hash.begin(), hash.end()); - auto sign = key.sign(data, TWCurveED25519); + auto sign = key.sign(data); auto signature = Data(); signature.insert(signature.end(), encoded.begin(), encoded.end()); diff --git a/src/Tezos/BinaryCoding.cpp b/src/Tezos/BinaryCoding.cpp index 7300157f5ca..6be3edd6442 100644 --- a/src/Tezos/BinaryCoding.cpp +++ b/src/Tezos/BinaryCoding.cpp @@ -60,7 +60,7 @@ PrivateKey parsePrivateKey(const std::string& privateKey) { throw std::invalid_argument("Invalid Public Key"); } append(pk, Data(decoded.begin() + prefix_size, decoded.end())); - return PrivateKey(pk); + return PrivateKey(pk, TWCurveSECP256k1); } } // namespace TW::Tezos diff --git a/src/Tezos/MessageSigner.cpp b/src/Tezos/MessageSigner.cpp index 25234f170ed..fc687d7b1d8 100644 --- a/src/Tezos/MessageSigner.cpp +++ b/src/Tezos/MessageSigner.cpp @@ -38,7 +38,7 @@ std::string MessageSigner::formatMessage(const std::string& message, const std:: } std::string MessageSigner::signMessage(const PrivateKey& privateKey, const std::string& message) { - auto signature = privateKey.sign(Hash::blake2b(parse_hex(message), 32), TWCurveED25519); + auto signature = privateKey.sign(Hash::blake2b(parse_hex(message), 32)); return Base58::encodeCheck(concat(gEdSigPrefix, signature)); } diff --git a/src/Tezos/Signer.cpp b/src/Tezos/Signer.cpp index bd61436b4d7..42ec445cdd0 100644 --- a/src/Tezos/Signer.cpp +++ b/src/Tezos/Signer.cpp @@ -17,7 +17,7 @@ namespace TW::Tezos { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto signer = Signer(); - PrivateKey key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + PrivateKey key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveED25519); Data encoded; if (input.encoded_operations().empty()) { auto operationList = Tezos::OperationList(input.operation_list().branch()); @@ -53,7 +53,7 @@ Data Signer::signData(const PrivateKey& privateKey, const Data& data) { append(watermarkedData, data); Data hash = Hash::blake2b(watermarkedData, 32); - Data signature = privateKey.sign(hash, TWCurve::TWCurveED25519); + Data signature = privateKey.sign(hash); Data signedData = Data(); append(signedData, data); diff --git a/src/Theta/Signer.cpp b/src/Theta/Signer.cpp index 020965d374a..0f25d9503c5 100755 --- a/src/Theta/Signer.cpp +++ b/src/Theta/Signer.cpp @@ -12,7 +12,7 @@ using RLP = TW::Ethereum::RLP; namespace TW::Theta { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto pkFrom = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto pkFrom = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); auto from = Ethereum::Address(pkFrom.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); auto transaction = Transaction( @@ -61,7 +61,7 @@ Data Signer::encode(const Transaction& transaction) const { Data Signer::sign(const PrivateKey& privateKey, const Transaction& transaction) noexcept { auto encoded = encode(transaction); auto hash = Hash::keccak256(encoded); - auto signature = privateKey.sign(hash, TWCurveSECP256k1); + auto signature = privateKey.sign(hash); return signature; } diff --git a/src/Tron/MessageSigner.cpp b/src/Tron/MessageSigner.cpp index 8fe0d8619a6..41411fb8390 100644 --- a/src/Tron/MessageSigner.cpp +++ b/src/Tron/MessageSigner.cpp @@ -14,14 +14,14 @@ namespace TW::Tron { Data generateMessage(const std::string& message) { std::string prefix(1, MessageSigner::TronPrefix); std::stringstream ss; - ss << prefix << MessageSigner::MessagePrefix << message; + ss << prefix << MessageSigner::MessagePrefix << message.length() << message; Data signableMessage = Hash::keccak256(data(ss.str())); return signableMessage; } std::string MessageSigner::signMessage(const PrivateKey& privateKey, const std::string& message) { auto signableMessage = generateMessage(message); - auto data = privateKey.sign(signableMessage, TWCurveSECP256k1); + auto data = privateKey.sign(signableMessage); data[64] += 27; return hex(data); } diff --git a/src/Tron/MessageSigner.h b/src/Tron/MessageSigner.h index e6e7bf2a9a9..ed016f66b4b 100644 --- a/src/Tron/MessageSigner.h +++ b/src/Tron/MessageSigner.h @@ -24,7 +24,7 @@ class MessageSigner { /// \param signature signature to verify the message against /// \return true if the message match the signature, false otherwise static bool verifyMessage(const PublicKey& publicKey, const std::string& message, const std::string& signature) noexcept; - static constexpr auto MessagePrefix = "TRON Signed Message:\n32"; + static constexpr auto MessagePrefix = "TRON Signed Message:\n"; static constexpr std::uint8_t TronPrefix{0x19}; }; diff --git a/src/Tron/Signer.cpp b/src/Tron/Signer.cpp index 72e3783d079..d0acf6706cd 100644 --- a/src/Tron/Signer.cpp +++ b/src/Tron/Signer.cpp @@ -404,9 +404,9 @@ Data serialize(const protocol::Transaction& tx) noexcept { } Proto::SigningOutput signDirect(const Proto::SigningInput& input) { - const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); auto hash = parse_hex(input.txid()); - const auto signature = key.sign(hash, TWCurveSECP256k1); + const auto signature = key.sign(hash); auto output = Proto::SigningOutput(); output.set_signature(signature.data(), signature.size()); output.set_id(input.txid()); @@ -441,8 +441,8 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { const auto hash = Hash::sha256(serialize(tx)); - const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - const auto signature = key.sign(hash, TWCurveSECP256k1); + const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); + const auto signature = key.sign(hash); const auto json = transactionJSON(tx, hash, signature).dump(); diff --git a/src/VeChain/Signer.cpp b/src/VeChain/Signer.cpp index 78ddc8a81e6..de3e864318c 100644 --- a/src/VeChain/Signer.cpp +++ b/src/VeChain/Signer.cpp @@ -11,7 +11,7 @@ using namespace TW; namespace TW::VeChain { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); auto transaction = Transaction(); transaction.chainTag = static_cast(input.chain_tag()); transaction.blockRef = input.block_ref(); @@ -37,7 +37,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { Data Signer::sign(const PrivateKey& privateKey, Transaction& transaction) noexcept { auto encoded = transaction.encode(); auto hash = Hash::blake2b(encoded, 32); - auto signature = privateKey.sign(hash, TWCurveSECP256k1); + auto signature = privateKey.sign(hash); return Data(signature.begin(), signature.end()); } diff --git a/src/Waves/Signer.cpp b/src/Waves/Signer.cpp index 2d4d6955236..a8e11f6e7cf 100644 --- a/src/Waves/Signer.cpp +++ b/src/Waves/Signer.cpp @@ -11,7 +11,7 @@ using namespace TW; namespace TW::Waves { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveCurve25519); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); auto transaction = Transaction(input, publicKey.bytes); @@ -26,7 +26,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { Data Signer::sign(const PrivateKey& privateKey, Transaction& transaction) noexcept { try { auto bytesToSign = transaction.serializeToSign(); - auto signature = privateKey.sign(bytesToSign, TWCurveCurve25519); + auto signature = privateKey.sign(bytesToSign); return signature; } catch (...) { return Data(); diff --git a/src/Zilliqa/Signer.cpp b/src/Zilliqa/Signer.cpp index cf2003ec4f7..6db4930d251 100644 --- a/src/Zilliqa/Signer.cpp +++ b/src/Zilliqa/Signer.cpp @@ -44,7 +44,7 @@ static inline ByteArray* byteArray(const void* data, size_t size) { Data Signer::getPreImage(const Proto::SigningInput& input, Address& address) noexcept { auto internal = ZilliqaMessage::ProtoTransactionCoreInfo(); - const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); if (!Address::decode(input.to(), address)) { // invalid input address return Data(0); @@ -91,7 +91,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto output = Proto::SigningOutput(); Address address; const auto preImage = Signer::getPreImage(input, address); - const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); const auto pubKey = key.getPublicKey(TWPublicKeyTypeSECP256k1); const auto signature = key.signZilliqa(preImage); const auto transaction = input.transaction(); diff --git a/src/interface/TWPrivateKey.cpp b/src/interface/TWPrivateKey.cpp index 37483dca65d..a0dac25a551 100644 --- a/src/interface/TWPrivateKey.cpp +++ b/src/interface/TWPrivateKey.cpp @@ -15,10 +15,10 @@ using namespace TW; -struct TWPrivateKey *TWPrivateKeyCreate() { +struct TWPrivateKey *TWPrivateKeyCreate(enum TWCurve curve) { Data bytes(PrivateKey::_size); random_buffer(bytes.data(), PrivateKey::_size); - if (!PrivateKey::isValid(bytes)) { + if (!PrivateKey::isValid(bytes, curve)) { // Under no circumstance return an invalid private key. We'd rather // crash. This also captures cases where the random generator fails // since we initialize the array to zeros, which is an invalid private @@ -26,21 +26,21 @@ struct TWPrivateKey *TWPrivateKeyCreate() { std::terminate(); } - return new TWPrivateKey{ PrivateKey(std::move(bytes)) }; + return new TWPrivateKey{ PrivateKey(std::move(bytes), curve) }; } -struct TWPrivateKey *_Nullable TWPrivateKeyCreateWithData(TWData *_Nonnull data) { +struct TWPrivateKey *_Nullable TWPrivateKeyCreateWithData(TWData *_Nonnull data, enum TWCurve curve) { auto dataSize = TWDataSize(data); Data bytes(dataSize); TWDataCopyBytes(data, 0, dataSize, bytes.data()); - if (!PrivateKey::isValid(bytes)) { + if (!PrivateKey::isValid(bytes, curve)) { return nullptr; } - return new TWPrivateKey{ PrivateKey(std::move(bytes)) }; + return new TWPrivateKey{ PrivateKey(std::move(bytes), curve) }; } struct TWPrivateKey *_Nullable TWPrivateKeyCreateCopy(struct TWPrivateKey *_Nonnull key) { - return new TWPrivateKey{ PrivateKey(key->impl.bytes) }; + return new TWPrivateKey{ PrivateKey(key->impl.bytes, key->impl.curve()) }; } void TWPrivateKeyDelete(struct TWPrivateKey *_Nonnull pk) { @@ -88,9 +88,9 @@ struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyCurve25519(struct TWPrivate return TWPrivateKeyGetPublicKeyByType(pk, TWPublicKeyTypeCURVE25519); } -TWData *TWPrivateKeySign(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull digest, enum TWCurve curve) { +TWData *TWPrivateKeySign(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull digest) { const auto& d = *reinterpret_cast(digest); - auto result = pk->impl.sign(d, curve); + auto result = pk->impl.sign(d); if (result.empty()) { return nullptr; } else { @@ -98,8 +98,8 @@ TWData *TWPrivateKeySign(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull dige } } -TWData* TWPrivateKeySignAsDER(struct TWPrivateKey* pk, TWData* digest) { - auto& d = *reinterpret_cast(digest); +TWData *TWPrivateKeySignAsDER(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull digest) { + const auto& d = *reinterpret_cast(digest); auto result = pk->impl.signAsDER(d); if (result.empty()) { return nullptr; diff --git a/src/proto/Ethereum.proto b/src/proto/Ethereum.proto index 37949d836fc..60d8a222238 100644 --- a/src/proto/Ethereum.proto +++ b/src/proto/Ethereum.proto @@ -174,6 +174,15 @@ message Access { repeated bytes stored_keys = 2; } +// Smart Contract account type. +enum SCAccountType { + // ERC-4337 compatible smart contract wallet. + // https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/accounts/SimpleAccount.sol + SimpleAccount = 0; + // Biz smart contract (Trust Wallet specific). + Biz4337 = 1; +} + // Input data necessary to create a signed transaction. // Legacy and EIP2718/EIP1559 transactions supported, see TransactionMode. message SigningInput { @@ -222,6 +231,9 @@ message SigningInput { // Optional list of addresses and storage keys that the transaction plans to access. // Used in `TransactionMode::Enveloped` only. repeated Access access_list = 12; + + // Smart contract account type. Used in `TransactionMode::UserOp` only. + SCAccountType user_operation_mode = 14; } // Result containing the signed and encoded transaction. diff --git a/swift/Podfile.lock b/swift/Podfile.lock index cc5798c5639..88093ba1a0d 100644 --- a/swift/Podfile.lock +++ b/swift/Podfile.lock @@ -13,4 +13,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: aac2324ba35cdd5631cb37618cd483887bab9cfd -COCOAPODS: 1.14.3 +COCOAPODS: 1.16.2 diff --git a/swift/Sources/KeyStore.swift b/swift/Sources/KeyStore.swift index 0b6c3368d77..58c8d78e7a0 100644 --- a/swift/Sources/KeyStore.swift +++ b/swift/Sources/KeyStore.swift @@ -138,10 +138,11 @@ public final class KeyStore { return try self.import(mnemonic: mnemonic, name: name, encryptPassword: newPassword, coins: coins) } - guard let privateKey = PrivateKey(data: data) else { + let coin = coins.first ?? .ethereum + guard let privateKey = PrivateKey(data: data, curve: coin.curve) else { throw Error.invalidKey } - return try self.import(privateKey: privateKey, name: name, password: newPassword, coin: coins.first ?? .ethereum) + return try self.import(privateKey: privateKey, name: name, password: newPassword, coin: coin) } private func checkMnemonic(_ data: Data) -> String? { diff --git a/swift/Tests/Addresses/BitcoinAddressTests.swift b/swift/Tests/Addresses/BitcoinAddressTests.swift index da079e80027..56d7bca0b10 100644 --- a/swift/Tests/Addresses/BitcoinAddressTests.swift +++ b/swift/Tests/Addresses/BitcoinAddressTests.swift @@ -44,7 +44,7 @@ class BitcoinAddressTests: XCTestCase { func testFromPrivateKey() { let data = Data(hexString: "f7b5f7a8090c5c93cd2d6d01383c9286b221ea78d8bef3e482f0c5cdde653e68")! - let privateKey = PrivateKey(data: data)! + let privateKey = PrivateKey(data: data, curve: CoinType.bitcoin.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) let address = BitcoinAddress.compatibleAddress(publicKey: publicKey, prefix: CoinType.bitcoin.p2shPrefix) @@ -53,7 +53,7 @@ class BitcoinAddressTests: XCTestCase { func testFromPrivateKeyUncompressed() { let data = Data(hexString: "f7b5f7a8090c5c93cd2d6d01383c9286b221ea78d8bef3e482f0c5cdde653e68")! - let privateKey = PrivateKey(data: data)! + let privateKey = PrivateKey(data: data, curve: CoinType.bitcoin.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: false) let address = BitcoinAddress.compatibleAddress(publicKey: publicKey, prefix: CoinType.bitcoin.p2shPrefix) @@ -62,7 +62,7 @@ class BitcoinAddressTests: XCTestCase { func testFromPrivateKeySegwitAddress() { let data = Data(hexString: "28071bf4e2b0340db41b807ed8a5514139e5d6427ff9d58dbd22b7ed187103a4")! - let privateKey = PrivateKey(data: data)! + let privateKey = PrivateKey(data: data, curve: CoinType.bitcoin.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) let address = BitcoinAddress(publicKey: publicKey, prefix: CoinType.bitcoin.p2pkhPrefix)! @@ -228,7 +228,7 @@ class BitcoinAddressTests: XCTestCase { } func testBitcoinDeriveAddress() { - let privateKey = PrivateKey(data: Data(hexString: "4646464646464646464646464646464646464646464646464646464646464646")!)! + let privateKey = PrivateKey(data: Data(hexString: "4646464646464646464646464646464646464646464646464646464646464646")!, curve: CoinType.bitcoin.curve)! let address = CoinType.bitcoin.deriveAddress(privateKey: privateKey) XCTAssertEqual("bc1qhkfq3zahaqkkzx5mjnamwjsfpq2jk7z00ppggv", address.description) } diff --git a/swift/Tests/Addresses/NEOAddressTests.swift b/swift/Tests/Addresses/NEOAddressTests.swift index fffdc401277..59a5822a457 100644 --- a/swift/Tests/Addresses/NEOAddressTests.swift +++ b/swift/Tests/Addresses/NEOAddressTests.swift @@ -25,7 +25,7 @@ class NEOAddressTests: XCTestCase { } func testFromPrivateKey() { - let privateKey = PrivateKey(data: Data(hexString: "4cbd05e59cbe5faba43bbf5a15fdaf27ad72c232f8d88d987c6b3d4d98300af5")!)! + let privateKey = PrivateKey(data: Data(hexString: "4cbd05e59cbe5faba43bbf5a15fdaf27ad72c232f8d88d987c6b3d4d98300af5")!, curve: CoinType.neo.curve)! let address = AnyAddress(publicKey: privateKey.getPublicKeyNist256p1(), coin: .neo) XCTAssertEqual(address.description, "AH11LGtFk6VU9Z7suuM5eNpho1bAoE5Gbz") } diff --git a/swift/Tests/Addresses/OntologyAddressTests.swift b/swift/Tests/Addresses/OntologyAddressTests.swift index 760465b3ecd..d8b3c1d6b67 100644 --- a/swift/Tests/Addresses/OntologyAddressTests.swift +++ b/swift/Tests/Addresses/OntologyAddressTests.swift @@ -25,7 +25,7 @@ class OntologyAddressTests: XCTestCase { } func testFromPrivateKey() { - let privateKey = PrivateKey(data: Data(hexString: "4cbd05e59cbe5faba43bbf5a15fdaf27ad72c232f8d88d987c6b3d4d98300af5")!)! + let privateKey = PrivateKey(data: Data(hexString: "4cbd05e59cbe5faba43bbf5a15fdaf27ad72c232f8d88d987c6b3d4d98300af5")!, curve: CoinType.ontology.curve)! let address = AnyAddress(publicKey: privateKey.getPublicKeyNist256p1(), coin: .ontology) XCTAssertEqual(address.description, "AH11LGtFk6VU9Z7suuM5eNpho1bAoE5Gbz") } diff --git a/swift/Tests/Addresses/TronAddressTests.swift b/swift/Tests/Addresses/TronAddressTests.swift index cc2b3a6773f..722c33a303b 100644 --- a/swift/Tests/Addresses/TronAddressTests.swift +++ b/swift/Tests/Addresses/TronAddressTests.swift @@ -8,7 +8,7 @@ import XCTest class TronAddressTests: XCTestCase { func testFromPrivateKey() { - let privateKey = PrivateKey(data: Data(hexString: "2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")!)! + let privateKey = PrivateKey(data: Data(hexString: "2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")!, curve: CoinType.tron.curve)! let address = CoinType.tron.deriveAddress(privateKey: privateKey) @@ -16,7 +16,7 @@ class TronAddressTests: XCTestCase { } func testFromPublicKey() { - let privateKey = PrivateKey(data: Data(hexString: "BE88DF1D0BF30A923CB39C3BB953178BAAF3726E8D3CE81E7C8462E046E0D835")!)! + let privateKey = PrivateKey(data: Data(hexString: "BE88DF1D0BF30A923CB39C3BB953178BAAF3726E8D3CE81E7C8462E046E0D835")!, curve: CoinType.tron.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: false) XCTAssertEqual("THRF3GuPnvvPzKoaT8pJex5XHmo8NNbCb3", AnyAddress(publicKey: publicKey, coin: .tron).description) diff --git a/swift/Tests/Blockchains/AcalaEVMTests.swift b/swift/Tests/Blockchains/AcalaEVMTests.swift index dd4577c23a1..af9d42fbf44 100644 --- a/swift/Tests/Blockchains/AcalaEVMTests.swift +++ b/swift/Tests/Blockchains/AcalaEVMTests.swift @@ -7,7 +7,7 @@ import XCTest class AcalaEVMTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0")!)! + let key = PrivateKey(data: Data(hexString: "828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0")!, curve: CoinType.acalaEVM.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: false) let address = AnyAddress(publicKey: pubkey, coin: .acalaEVM) let expected = AnyAddress(string: "0xe32DC46bfBF78D1eada7b0a68C96903e01418D64", coin: .acalaEVM)! diff --git a/swift/Tests/Blockchains/AcalaTests.swift b/swift/Tests/Blockchains/AcalaTests.swift index 4ef108e7f2c..2afc9695041 100644 --- a/swift/Tests/Blockchains/AcalaTests.swift +++ b/swift/Tests/Blockchains/AcalaTests.swift @@ -10,7 +10,7 @@ class AcalaTests: XCTestCase { let genesisHash = Data(hexString: "0xfc41b9bd8ef8fe53d58c7ea67c794c7ec9a73daf05e6d54b14ff6342c99ba64c")! func testAddress() { - let key = PrivateKey(data: Data(hexString: "0x9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0")!)! + let key = PrivateKey(data: Data(hexString: "0x9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0")!, curve: CoinType.acala.curve)! let pubkey = key.getPublicKeyEd25519() let address = AnyAddress(publicKey: pubkey, coin: .acala) let addressFromString = AnyAddress(string: "269ZCS3WLGydTN8ynhyhZfzJrXkePUcdhwgLQs6TWFs5wVL5", coin: .acala)! @@ -21,7 +21,7 @@ class AcalaTests: XCTestCase { func testSigning() { // real key in 1p test - let key = PrivateKey(data: Data(hexString: "9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0")!)! + let key = PrivateKey(data: Data(hexString: "9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0")!, curve: CoinType.acala.curve)! let input = PolkadotSigningInput.with { $0.genesisHash = genesisHash diff --git a/swift/Tests/Blockchains/AgoricTests.swift b/swift/Tests/Blockchains/AgoricTests.swift index a35e0ed4948..d16d35114e0 100644 --- a/swift/Tests/Blockchains/AgoricTests.swift +++ b/swift/Tests/Blockchains/AgoricTests.swift @@ -8,7 +8,7 @@ import XCTest class AgoricTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "037048190544fa57651452f477c096de4f3073e7835cf3845b04602563a73f73")!)! + let key = PrivateKey(data: Data(hexString: "037048190544fa57651452f477c096de4f3073e7835cf3845b04602563a73f73")!, curve: CoinType.agoric.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: true) let address = AnyAddress(publicKey: pubkey, coin: .agoric) let addressFromString = AnyAddress(string: "agoric18zvvgk6j3eq5wd7mqxccgt20gz2w94cy88aek5", coin: .agoric)! @@ -18,7 +18,7 @@ class AgoricTests: XCTestCase { } func testSign() { - let privateKey = PrivateKey(data: Data(hexString: "037048190544fa57651452f477c096de4f3073e7835cf3845b04602563a73f73")!)! + let privateKey = PrivateKey(data: Data(hexString: "037048190544fa57651452f477c096de4f3073e7835cf3845b04602563a73f73")!, curve: CoinType.agoric.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) let fromAddress = AnyAddress(publicKey: publicKey, coin: .agoric) diff --git a/swift/Tests/Blockchains/AionTests.swift b/swift/Tests/Blockchains/AionTests.swift index 89c731de13f..948d4123866 100644 --- a/swift/Tests/Blockchains/AionTests.swift +++ b/swift/Tests/Blockchains/AionTests.swift @@ -8,7 +8,7 @@ import XCTest class AionTests: XCTestCase { func testAddress() { - let privateKey = PrivateKey(data: Data(hexString: "db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9")!)! + let privateKey = PrivateKey(data: Data(hexString: "db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9")!, curve: CoinType.aion.curve)! let publicKey = privateKey.getPublicKeyEd25519() let address = AnyAddress(publicKey: publicKey, coin: .aion) XCTAssertEqual(address.description, "0xa0d2312facea71b740679c926d040c9056a65a4bfa2ddd18ec160064f82909e7") diff --git a/swift/Tests/Blockchains/AlgorandTests.swift b/swift/Tests/Blockchains/AlgorandTests.swift index 7c03fbb1a07..a8e081280a3 100644 --- a/swift/Tests/Blockchains/AlgorandTests.swift +++ b/swift/Tests/Blockchains/AlgorandTests.swift @@ -8,7 +8,7 @@ import XCTest class AlgorandTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "a6c4394041e64fe93d889386d7922af1b9a87f12e433762759608e61434d6cf7")!)! + let key = PrivateKey(data: Data(hexString: "a6c4394041e64fe93d889386d7922af1b9a87f12e433762759608e61434d6cf7")!, curve: CoinType.algorand.curve)! let pubkey = key.getPublicKeyEd25519() let address = AnyAddress(publicKey: pubkey, coin: .algorand) let addressFromString = AnyAddress(string: "ADIYK65L3XR5ODNNCUIQVEET455L56MRKJHRBX5GU4TZI2752QIWK4UL5A", coin: .algorand)! diff --git a/swift/Tests/Blockchains/AvalancheTests.swift b/swift/Tests/Blockchains/AvalancheTests.swift index cc0da61aaae..32ae197680d 100644 --- a/swift/Tests/Blockchains/AvalancheTests.swift +++ b/swift/Tests/Blockchains/AvalancheTests.swift @@ -8,7 +8,7 @@ import XCTest class AvalancheTests: XCTestCase { func testCChainAddress() { - let key = PrivateKey(data: Data(hexString: "98cb077f972feb0481f1d894f272c6a1e3c15e272a1658ff716444f465200070")!)! + let key = PrivateKey(data: Data(hexString: "98cb077f972feb0481f1d894f272c6a1e3c15e272a1658ff716444f465200070")!, curve: CoinType.avalancheCChain.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: false) let address = AnyAddress(publicKey: pubkey, coin: .avalancheCChain) let addressETH = AnyAddress(publicKey: pubkey, coin: .ethereum) diff --git a/swift/Tests/Blockchains/BandChainTests.swift b/swift/Tests/Blockchains/BandChainTests.swift index 60fb141add8..9c450b3da38 100644 --- a/swift/Tests/Blockchains/BandChainTests.swift +++ b/swift/Tests/Blockchains/BandChainTests.swift @@ -6,7 +6,7 @@ import WalletCore import XCTest class BandChainTests: XCTestCase { - let privateKey = PrivateKey(data: Data(hexString: "1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6")!)! + let privateKey = PrivateKey(data: Data(hexString: "1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6")!, curve: CoinType.bandChain.curve)! func testAddress() { let address = CoinType.bandChain.deriveAddress(privateKey: privateKey) diff --git a/swift/Tests/Blockchains/BinanceChainTests.swift b/swift/Tests/Blockchains/BinanceChainTests.swift index 8f16331ee46..69fa9750bd5 100644 --- a/swift/Tests/Blockchains/BinanceChainTests.swift +++ b/swift/Tests/Blockchains/BinanceChainTests.swift @@ -7,7 +7,7 @@ import WalletCore class BinanceChainTests: XCTestCase { - let testKey = PrivateKey(data: Data(hexString: "eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")!)! + let testKey = PrivateKey(data: Data(hexString: "eeba3f6f2db26ced519a3d4c43afff101db957a21d54d25dc7fd235c404d7a5d")!, curve: .secp256k1)! func testAddress() { let publicKey = PublicKey(data: Data(hexString: "0x026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502")!, type: .secp256k1)! @@ -36,7 +36,7 @@ class BinanceChainTests: XCTestCase { } func testSignSendOrder() { - let privateKey = PrivateKey(data: Data(hexString: "95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832")!)! + let privateKey = PrivateKey(data: Data(hexString: "95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832")!, curve: .secp256k1)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) let token = BinanceSendOrder.Token.with { diff --git a/swift/Tests/Blockchains/BinanceSmartChainTests.swift b/swift/Tests/Blockchains/BinanceSmartChainTests.swift index a1d82e098e8..4fdb63ad66d 100644 --- a/swift/Tests/Blockchains/BinanceSmartChainTests.swift +++ b/swift/Tests/Blockchains/BinanceSmartChainTests.swift @@ -8,7 +8,7 @@ import XCTest class BinanceSmartChainTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06")!)! + let key = PrivateKey(data: Data(hexString: "727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06")!, curve: CoinType.smartChain.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: false) let address = AnyAddress(publicKey: pubkey, coin: .smartChain) let expected = AnyAddress(string: "0xf3d468DBb386aaD46E92FF222adDdf872C8CC064", coin: .smartChain)! diff --git a/swift/Tests/Blockchains/BitcoinDiamondTests.swift b/swift/Tests/Blockchains/BitcoinDiamondTests.swift index 1afbbdc18f2..78aecf1ecab 100644 --- a/swift/Tests/Blockchains/BitcoinDiamondTests.swift +++ b/swift/Tests/Blockchains/BitcoinDiamondTests.swift @@ -7,7 +7,7 @@ import XCTest class BitcoinDiamondTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")!)! + let key = PrivateKey(data: Data(hexString: "d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")!, curve: CoinType.bitcoinDiamond.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: true) let address = AnyAddress(publicKey: pubkey, coin: .bitcoinDiamond) let addressFromString = AnyAddress(string: "1G15VvshDxwFTnahZZECJfFwEkq9fP79o8", coin: .bitcoinDiamond)! @@ -17,7 +17,7 @@ class BitcoinDiamondTests: XCTestCase { } func testSign() { - let key = PrivateKey(data: Data(hexString: "d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")!)! + let key = PrivateKey(data: Data(hexString: "d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")!, curve: CoinType.bitcoinDiamond.curve)! let script = BitcoinScript.lockScriptForAddress(address: "1G15VvshDxwFTnahZZECJfFwEkq9fP79o8", coin: .bitcoinDiamond) let utxos = [ diff --git a/swift/Tests/Blockchains/BitcoinTests.swift b/swift/Tests/Blockchains/BitcoinTests.swift index 3ae42d7d925..18b904b6327 100644 --- a/swift/Tests/Blockchains/BitcoinTests.swift +++ b/swift/Tests/Blockchains/BitcoinTests.swift @@ -16,7 +16,7 @@ class BitcoinTransactionSignerTests: XCTestCase { let dustAmount = 546 as Int64 let txId = Data.reverse(hexString: "8ec895b4d30adb01e38471ca1019bfc8c3e5fbd1f28d9e7b5653260d89989008") - let privateKey = PrivateKey(data: privateKeyData)! + let privateKey = PrivateKey(data: privateKeyData, curve: CoinType.bitcoin.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) let utxo0 = BitcoinV2Input.with { @@ -88,7 +88,7 @@ class BitcoinTransactionSignerTests: XCTestCase { // Now spend just created `797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1` commit output. let txIdCommit = Data.reverse(hexString: "797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1") - let privateKey = PrivateKey(data: privateKeyData)! + let privateKey = PrivateKey(data: privateKeyData, curve: CoinType.bitcoin.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) let utxo0 = BitcoinV2Input.with { @@ -152,7 +152,7 @@ class BitcoinTransactionSignerTests: XCTestCase { let txIdReveal = Data.reverse(hexString: "7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca") let txIdForFee = Data.reverse(hexString: "797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1") - let privateKey = PrivateKey(data: privateKeyData)! + let privateKey = PrivateKey(data: privateKeyData, curve: CoinType.bitcoin.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) let bobAddress = "bc1qazgc2zhu2kmy42py0vs8d7yff67l3zgpwfzlpk" @@ -302,7 +302,7 @@ class BitcoinTransactionSignerTests: XCTestCase { func testSignP2SH_P2WPKH() { let address = "3LGoLac9mtCwDy2q8PYyvwL8kMyrCWCYQW" let lockScript = BitcoinScript.lockScriptForAddress(address: address, coin: .bitcoin) - let key = PrivateKey(data: Data(hexString: "e240ef3419d038577e48426c8c37c3c13bec1a0ed3f5270b82e7377bc48699dd")!)! + let key = PrivateKey(data: Data(hexString: "e240ef3419d038577e48426c8c37c3c13bec1a0ed3f5270b82e7377bc48699dd")!, curve: CoinType.bitcoin.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: true) let utxos = [ BitcoinUnspentTransaction.with { @@ -351,7 +351,7 @@ class BitcoinTransactionSignerTests: XCTestCase { // compressed WIF, real key is 5KCr let wif = "L4BeKzm3AHDUMkxLRVKTSVxkp6Hz9FcMQPh18YCKU1uioXfovzwP" let decoded = Base58.decode(string: wif)! - let key = PrivateKey(data: decoded[1 ..< 33])! + let key = PrivateKey(data: decoded[1 ..< 33], curve: CoinType.bitcoin.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: false) // shortcut methods only support compressed public key @@ -420,7 +420,7 @@ class BitcoinTransactionSignerTests: XCTestCase { func testPlanPsbtThorSwap() throws { let privateKeyBytes = Data(hexString: "f00ffbe44c5c2838c13d2778854ac66b75e04eb6054f0241989e223223ad5e55")! - let privateKey = PrivateKey(data: privateKeyBytes)! + let privateKey = PrivateKey(data: privateKeyBytes, curve: CoinType.bitcoin.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) let psbt = Data(hexString: "70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d00000000")! diff --git a/swift/Tests/Blockchains/BitconCashTests.swift b/swift/Tests/Blockchains/BitconCashTests.swift index 406dc3026af..d00008da5a9 100644 --- a/swift/Tests/Blockchains/BitconCashTests.swift +++ b/swift/Tests/Blockchains/BitconCashTests.swift @@ -60,7 +60,7 @@ class BitcoinCashTests: XCTestCase { func testSign() throws { let utxoTxId = "050d00e2e18ef13969606f1ceee290d3f49bd940684ce39898159352952b8ce2" - let privateKey = PrivateKey(data: Data(hexString: "7fdafb9db5bc501f2096e7d13d331dc7a75d9594af3d251313ba8b6200f4e384")!)! + let privateKey = PrivateKey(data: Data(hexString: "7fdafb9db5bc501f2096e7d13d331dc7a75d9594af3d251313ba8b6200f4e384")!, curve: CoinType.bitcoinCash.curve)! let address = CoinType.bitcoinCash.deriveAddress(privateKey: privateKey) let utxo = BitcoinUnspentTransaction.with { $0.outPoint.hash = Data.reverse(hexString: utxoTxId) // reverse of UTXO tx id, Bitcoin internal expects network byte order diff --git a/swift/Tests/Blockchains/BluzelleTests.swift b/swift/Tests/Blockchains/BluzelleTests.swift index 78d32c8843f..c76f86e62de 100644 --- a/swift/Tests/Blockchains/BluzelleTests.swift +++ b/swift/Tests/Blockchains/BluzelleTests.swift @@ -9,7 +9,7 @@ class BluzelleAddressTests: XCTestCase { func testAddressPublicKey() { let privateKeyData = Data(hexString: "1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6")! - let privateKey = PrivateKey(data: privateKeyData)! + let privateKey = PrivateKey(data: privateKeyData, curve: CoinType.bluzelle.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) let expectedAddress = "bluzelle1jf9aaj9myrzsnmpdr7twecnaftzmku2myvn4dg" @@ -86,7 +86,7 @@ class BluzelleSignerTests: XCTestCase { func testSigningMessage() { // Submitted Realworld tx for the following test : https://bigdipper.net.bluzelle.com/transactions/B3A7F30539CCDF72D210BC995FAF65B43F9BE04FA9F8AFAE0EC969660744002F - let privateKey = PrivateKey(data: privateKeyData)! + let privateKey = PrivateKey(data: privateKeyData, curve: CoinType.bluzelle.curve)! let sendCoinsMessage = CosmosMessage.Send.with { $0.fromAddress = myAddress diff --git a/swift/Tests/Blockchains/CardanoTests.swift b/swift/Tests/Blockchains/CardanoTests.swift index 4acef413ba3..ff82eba88cf 100644 --- a/swift/Tests/Blockchains/CardanoTests.swift +++ b/swift/Tests/Blockchains/CardanoTests.swift @@ -7,7 +7,7 @@ import XCTest class CardanoTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b574437aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fae0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276")!)! + let key = PrivateKey(data: Data(hexString: "e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b574437aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fae0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276")!, curve: CoinType.cardano.curve)! let pubkey = key.getPublicKeyEd25519Cardano() let address = AnyAddress(publicKey: pubkey, coin: .cardano) let addressFromString = AnyAddress(string: "addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq", coin: .cardano)! @@ -64,7 +64,7 @@ class CardanoTests: XCTestCase { /// Successfully broadcasted: /// https://cardanoscan.io/transaction/0203ce2c91f59f169a26e9ef91254639d2b7911afac9c7c0ae64539f88ba46a5 func testSignTransferFromLegacy() throws { - let privateKey = PrivateKey(data: Data(hexString: "98f266d1aac660179bc2f456033941238ee6b2beb8ed0f9f34c9902816781f5a9903d1d395d6ab887b65ea5e344ef09b449507c21a75f0ce8c59d0ed1c6764eba7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f4e890ca4eb6bec44bf751b5a843174534af64d6ad1f44e0613db78a7018781f5aa151d2997f52059466b715d8eefab30a78b874ae6ef4931fa58bb21ef8ce2423d46f19d0fbf75afb0b9a24e31d533f4fd74cee3b56e162568e8defe37123afc4")!)! + let privateKey = PrivateKey(data: Data(hexString: "98f266d1aac660179bc2f456033941238ee6b2beb8ed0f9f34c9902816781f5a9903d1d395d6ab887b65ea5e344ef09b449507c21a75f0ce8c59d0ed1c6764eba7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f4e890ca4eb6bec44bf751b5a843174534af64d6ad1f44e0613db78a7018781f5aa151d2997f52059466b715d8eefab30a78b874ae6ef4931fa58bb21ef8ce2423d46f19d0fbf75afb0b9a24e31d533f4fd74cee3b56e162568e8defe37123afc4")!, curve: CoinType.cardano.curve)! let publicKey = privateKey.getPublicKeyEd25519Cardano() let byronAddress = Cardano.getByronAddress(publicKey: publicKey) diff --git a/swift/Tests/Blockchains/ConfluxeSpaceTests.swift b/swift/Tests/Blockchains/ConfluxeSpaceTests.swift index 77751907f35..3d7e8428a92 100644 --- a/swift/Tests/Blockchains/ConfluxeSpaceTests.swift +++ b/swift/Tests/Blockchains/ConfluxeSpaceTests.swift @@ -7,7 +7,7 @@ import XCTest class ConfluxeSpaceTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0")!)! + let key = PrivateKey(data: Data(hexString: "828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0")!, curve: CoinType.confluxeSpace.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: false) let address = AnyAddress(publicKey: pubkey, coin: .confluxeSpace) let expected = AnyAddress(string: "0xe32DC46bfBF78D1eada7b0a68C96903e01418D64", coin: .confluxeSpace)! diff --git a/swift/Tests/Blockchains/CosmosTests.swift b/swift/Tests/Blockchains/CosmosTests.swift index 90cfd33423c..c20aae7ff03 100644 --- a/swift/Tests/Blockchains/CosmosTests.swift +++ b/swift/Tests/Blockchains/CosmosTests.swift @@ -22,7 +22,7 @@ class CosmosAddressTests: XCTestCase { class CosmosSignerTests: XCTestCase { - let privateKey = PrivateKey(data: Data(hexString: "80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005")!)! + let privateKey = PrivateKey(data: Data(hexString: "80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005")!, curve: CoinType.cosmos.curve)! func testSigningTransaction() { let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) @@ -95,7 +95,7 @@ class CosmosSignerTests: XCTestCase { $0.sequence = 5 $0.messages = [message] $0.fee = fee - $0.privateKey = PrivateKey(data: Data(hexString: "c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc")!)!.data + $0.privateKey = PrivateKey(data: Data(hexString: "c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc")!, curve: CoinType.cosmos.curve)!.data } let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) @@ -129,7 +129,7 @@ class CosmosSignerTests: XCTestCase { $0.sequence = 4 $0.messages = [message] $0.fee = fee - $0.privateKey = PrivateKey(data: Data(hexString: "c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc")!)!.data + $0.privateKey = PrivateKey(data: Data(hexString: "c7764249cdf77f8f1d840fa8af431579e5e41cf1af937e1e23afa22f3f4f0ccc")!, curve: CoinType.cosmos.curve)!.data } let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .cosmos) @@ -223,7 +223,7 @@ class CosmosSignerTests: XCTestCase { } func testIbcTransfer() { - let privateKey = PrivateKey(data: Data(hexString: "8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af")!)! + let privateKey = PrivateKey(data: Data(hexString: "8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af")!, curve: CoinType.cosmos.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) let fromAddress = AnyAddress(publicKey: publicKey, coin: .cosmos) diff --git a/swift/Tests/Blockchains/CryptoorgTests.swift b/swift/Tests/Blockchains/CryptoorgTests.swift index 9f781e24982..199aab0da72 100644 --- a/swift/Tests/Blockchains/CryptoorgTests.swift +++ b/swift/Tests/Blockchains/CryptoorgTests.swift @@ -7,7 +7,7 @@ import XCTest class CryptoorgTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e")!)! + let key = PrivateKey(data: Data(hexString: "7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e")!, curve: CoinType.cosmos.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: true) let address = AnyAddress(publicKey: pubkey, coin: .cryptoOrg) let addressFromString = AnyAddress(string: "cro1z53wwe7md6cewz9sqwqzn0aavpaun0gw39h3rd", coin: .cryptoOrg)! @@ -16,7 +16,7 @@ class CryptoorgTests: XCTestCase { XCTAssertEqual(address.description, addressFromString.description) } - let privateKey = PrivateKey(data: Data(hexString: "200e439e39cf1aad465ee3de6166247f914cbc0f823fc2dd48bf16dcd556f39d")!)! + let privateKey = PrivateKey(data: Data(hexString: "200e439e39cf1aad465ee3de6166247f914cbc0f823fc2dd48bf16dcd556f39d")!, curve: CoinType.cosmos.curve)! func testSign() { let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) diff --git a/swift/Tests/Blockchains/DashTests.swift b/swift/Tests/Blockchains/DashTests.swift index 94ebddfccc1..d55d1e09d6b 100644 --- a/swift/Tests/Blockchains/DashTests.swift +++ b/swift/Tests/Blockchains/DashTests.swift @@ -7,7 +7,7 @@ import XCTest class DashAddressTests: XCTestCase { func testAddress() { - let privateKey = PrivateKey(data: Data(hexString: "4b45e94800b9a2c3a45296f8de718bf9577cbe444773c39508d7f957355c759c")!)! + let privateKey = PrivateKey(data: Data(hexString: "4b45e94800b9a2c3a45296f8de718bf9577cbe444773c39508d7f957355c759c")!, curve: CoinType.dash.curve)! let address = CoinType.dash.deriveAddress(privateKey: privateKey) XCTAssertEqual(address, "Xw7HTXGY3TFeA3ZsVuMRrYh96GtwWb4hQb") diff --git a/swift/Tests/Blockchains/DecredTests.swift b/swift/Tests/Blockchains/DecredTests.swift index e13b23dcfad..736b0fda94f 100644 --- a/swift/Tests/Blockchains/DecredTests.swift +++ b/swift/Tests/Blockchains/DecredTests.swift @@ -31,7 +31,7 @@ class DecredTests: XCTestCase { func testSign() { // https://mainnet.decred.org/tx/bcc5228e9d956918984d1853c31d7edcd862f8a7fca20ded114d93f8a74ad32a - let key = PrivateKey(data: Data(hexString: "ba005cd605d8a02e3d5dfd04234cef3a3ee4f76bfbad2722d1fb5af8e12e6764")!)! + let key = PrivateKey(data: Data(hexString: "ba005cd605d8a02e3d5dfd04234cef3a3ee4f76bfbad2722d1fb5af8e12e6764")!, curve: CoinType.decred.curve)! let txHash = Data.reverse(hexString: "5015d14dcfd78998cfa13e0325798a74d95bbe75f167a49467303f70dde9bffd") let utxoAddress = CoinType.decred.deriveAddress(privateKey: key) @@ -73,7 +73,7 @@ class DecredTests: XCTestCase { func testSignV2() { // https://dcrdata.decred.org/tx/0934163f403cf9d256447890fed972e1f8b66309ecd41dec8a4dcfb657906a68 let privateKeyData = Data(hexString: "99ed469e6b7d9f188962940d9d0f9fd8582c6c37e52394348f177ff0526b8a03")! - let privateKey = PrivateKey(data: privateKeyData)! + let privateKey = PrivateKey(data: privateKeyData, curve: CoinType.decred.curve)! let senderAddress = CoinType.decred.deriveAddress(privateKey: privateKey) let toAddress = "Dsofok7qyhDLVRXcTqYdFgmGsUFSiHonbWH" diff --git a/swift/Tests/Blockchains/DydxTests.swift b/swift/Tests/Blockchains/DydxTests.swift index a464c834c8a..2192f355e96 100644 --- a/swift/Tests/Blockchains/DydxTests.swift +++ b/swift/Tests/Blockchains/DydxTests.swift @@ -7,7 +7,7 @@ import XCTest class DydxTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433")!)! + let key = PrivateKey(data: Data(hexString: "a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433")!, curve: CoinType.dydx.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: true) let address = AnyAddress(publicKey: pubkey, coin: .dydx) let addressFromString = AnyAddress(string: "dydx1mry47pkga5tdswtluy0m8teslpalkdq0hc72uz", coin: .dydx)! diff --git a/swift/Tests/Blockchains/ECashTests.swift b/swift/Tests/Blockchains/ECashTests.swift index 7f07d59b0ba..ea7e6b460b1 100644 --- a/swift/Tests/Blockchains/ECashTests.swift +++ b/swift/Tests/Blockchains/ECashTests.swift @@ -60,7 +60,7 @@ class ECashTests: XCTestCase { func testSign() throws { let utxoTxId = "050d00e2e18ef13969606f1ceee290d3f49bd940684ce39898159352952b8ce2" - let privateKey = PrivateKey(data: Data(hexString: "7fdafb9db5bc501f2096e7d13d331dc7a75d9594af3d251313ba8b6200f4e384")!)! + let privateKey = PrivateKey(data: Data(hexString: "7fdafb9db5bc501f2096e7d13d331dc7a75d9594af3d251313ba8b6200f4e384")!, curve: CoinType.ecash.curve)! let address = CoinType.ecash.deriveAddress(privateKey: privateKey) let utxo = BitcoinUnspentTransaction.with { $0.outPoint.hash = Data.reverse(hexString: utxoTxId) // reverse of UTXO tx id, Bitcoin internal expects network byte order diff --git a/swift/Tests/Blockchains/EthereumTests.swift b/swift/Tests/Blockchains/EthereumTests.swift index 9d5e73a326a..f706e6baae7 100644 --- a/swift/Tests/Blockchains/EthereumTests.swift +++ b/swift/Tests/Blockchains/EthereumTests.swift @@ -274,7 +274,7 @@ class EthereumTests: XCTestCase { } func testMessageAndVerifySignerImmutableX() { - let privateKey = PrivateKey(data: Data(hexString: "3b0a61f46fdae924007146eacb6db6642de7a5603ad843ec58e10331d89d4b84")!)! + let privateKey = PrivateKey(data: Data(hexString: "3b0a61f46fdae924007146eacb6db6642de7a5603ad843ec58e10331d89d4b84")!, curve: CoinType.ethereum.curve)! let msg = "Only sign this request if you’ve initiated an action with Immutable X.\n\nFor internal use:\nbd717ba31dca6e0f3f136f7c4197babce5f09a9f25176044c0b3112b1b6017a3" let signature = EthereumMessageSigner.signMessageImmutableX(privateKey: privateKey, message: msg) XCTAssertEqual(signature, "32cd5a58f3419fc5db672e3d57f76199b853eda0856d491b38f557b629b0a0814ace689412bf354a1af81126d2749207dffae8ae8845160f33948a6b787e17ee01") @@ -283,7 +283,7 @@ class EthereumTests: XCTestCase { } func testMessageAndVerifySignerLegacy() { - let privateKey = PrivateKey(data: Data(hexString: "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")!)! + let privateKey = PrivateKey(data: Data(hexString: "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")!, curve: CoinType.ethereum.curve)! let msg = "Foo" let signature = EthereumMessageSigner.signMessage(privateKey: privateKey, message: msg) XCTAssertEqual(signature, "21a779d499957e7fd39392d49a079679009e60e492d9654a148829be43d2490736ec72bc4a5644047d979c3cf4ebe2c1c514044cf436b063cb89fc6676be71101b") @@ -292,7 +292,7 @@ class EthereumTests: XCTestCase { } func testMessageAndVerifySignerEip155() { - let privateKey = PrivateKey(data: Data(hexString: "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")!)! + let privateKey = PrivateKey(data: Data(hexString: "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")!, curve: CoinType.ethereum.curve)! let msg = "Foo" let signature = EthereumMessageSigner.signMessageEip155(privateKey: privateKey, message: msg, chainId: 0) XCTAssertEqual(signature, "21a779d499957e7fd39392d49a079679009e60e492d9654a148829be43d2490736ec72bc4a5644047d979c3cf4ebe2c1c514044cf436b063cb89fc6676be711023") @@ -301,7 +301,7 @@ class EthereumTests: XCTestCase { } func testMessageAndVerifySigner712Legacy() { - let privateKey = PrivateKey(data: Data(hexString: "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")!)! + let privateKey = PrivateKey(data: Data(hexString: "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")!, curve: CoinType.ethereum.curve)! let msg = """ { "types": { @@ -336,7 +336,7 @@ class EthereumTests: XCTestCase { } func testMessageAndVerifySigner712Eip155() { - let privateKey = PrivateKey(data: Data(hexString: "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")!)! + let privateKey = PrivateKey(data: Data(hexString: "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")!, curve: CoinType.ethereum.curve)! let msg = """ { "types": { diff --git a/swift/Tests/Blockchains/EverscaleTests.swift b/swift/Tests/Blockchains/EverscaleTests.swift index 2fc44ddf7b5..838ece5b2f1 100644 --- a/swift/Tests/Blockchains/EverscaleTests.swift +++ b/swift/Tests/Blockchains/EverscaleTests.swift @@ -7,7 +7,7 @@ import XCTest class EverscaleTests: XCTestCase { func testAddressFromPrivateKey() { - let privateKey = PrivateKey(data: Data(hexString: "15d126cb1a84acdbcd1d9c3f6975968c2beb18cc43c95849d4b0226e1c8552aa")!)! + let privateKey = PrivateKey(data: Data(hexString: "15d126cb1a84acdbcd1d9c3f6975968c2beb18cc43c95849d4b0226e1c8552aa")!, curve: CoinType.everscale.curve)! let publicKey = privateKey.getPublicKeyEd25519() let address = AnyAddress(publicKey: publicKey, coin: .everscale) XCTAssertEqual(address.description, "0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04") diff --git a/swift/Tests/Blockchains/FIOTests.swift b/swift/Tests/Blockchains/FIOTests.swift index 4a35e145ea8..bbcbfbe25a4 100644 --- a/swift/Tests/Blockchains/FIOTests.swift +++ b/swift/Tests/Blockchains/FIOTests.swift @@ -21,7 +21,7 @@ class FIOTests: XCTestCase { } func testAddressFromKey() { - let key = PrivateKey(data: Data(hexString: "ea8eb60b7e5868e218f248e032769020b4fea5dcfd02f2992861eaf4fb534854")!)! + let key = PrivateKey(data: Data(hexString: "ea8eb60b7e5868e218f248e032769020b4fea5dcfd02f2992861eaf4fb534854")!, curve: CoinType.fio.curve)! let address = AnyAddress(publicKey: key.getPublicKeySecp256k1(compressed: true), coin: .fio) XCTAssertEqual(address.description, "FIO5kJKNHwctcfUM5XZyiWSqSTM5HTzznJP9F3ZdbhaQAHEVq575o") @@ -29,7 +29,7 @@ class FIOTests: XCTestCase { func testRegisterFioAddress() { let chainId = Data(hexString: "4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77")! - let privateKey = PrivateKey(data: Data(hexString: "ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")!)! + let privateKey = PrivateKey(data: Data(hexString: "ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")!, curve: CoinType.fio.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: false) let address = AnyAddress(publicKey: publicKey, coin: .fio) @@ -66,7 +66,7 @@ class FIOTests: XCTestCase { func testAddPubAddress() { let chainId = Data(hexString: "4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77")! - let privateKey = PrivateKey(data: Data(hexString: "ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")!)! + let privateKey = PrivateKey(data: Data(hexString: "ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")!, curve: CoinType.fio.curve)! let chainParams = FIOChainParams.with { $0.chainID = chainId @@ -105,7 +105,7 @@ class FIOTests: XCTestCase { func testTransfer() { let chainId = Data(hexString: "4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77")! - let privateKey = PrivateKey(data: Data(hexString: "ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")!)! + let privateKey = PrivateKey(data: Data(hexString: "ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")!, curve: CoinType.fio.curve)! let chainParams = FIOChainParams.with { $0.chainID = chainId diff --git a/swift/Tests/Blockchains/FilecoinTests.swift b/swift/Tests/Blockchains/FilecoinTests.swift index 2998bdfec56..35d524742be 100644 --- a/swift/Tests/Blockchains/FilecoinTests.swift +++ b/swift/Tests/Blockchains/FilecoinTests.swift @@ -8,14 +8,14 @@ import WalletCore class FilecoinTests: XCTestCase { func testCreateAddress() { - let privateKey = PrivateKey(data: Data(hexString: "1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe")!)! + let privateKey = PrivateKey(data: Data(hexString: "1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe")!, curve: CoinType.filecoin.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: false) let address = AnyAddress(publicKey: publicKey, coin: .filecoin) XCTAssertEqual(address.description, "f1z4a36sc7mfbv4z3qwutblp2flycdui3baffytbq") } func testCreateDelegatedAddress() { - let privateKey = PrivateKey(data: Data(hexString: "825d2bb32965764a98338139412c7591ed54c951dd65504cd8ddaeaa0fea7b2a")!)! + let privateKey = PrivateKey(data: Data(hexString: "825d2bb32965764a98338139412c7591ed54c951dd65504cd8ddaeaa0fea7b2a")!, curve: CoinType.filecoin.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: false) let address = AnyAddress(publicKey: publicKey, filecoinAddressType: .delegated) XCTAssertEqual(address.description, "f410fvak24cyg3saddajborn6idt7rrtfj2ptauk5pbq") diff --git a/swift/Tests/Blockchains/GroestlcoinTests.swift b/swift/Tests/Blockchains/GroestlcoinTests.swift index 5490f79e1b3..634aaae64af 100644 --- a/swift/Tests/Blockchains/GroestlcoinTests.swift +++ b/swift/Tests/Blockchains/GroestlcoinTests.swift @@ -7,12 +7,12 @@ import XCTest class GroestlcoinTests: XCTestCase { func testAddress() { - let privateKey1 = PrivateKey(data: Data(hexString: "3c3385ddc6fd95ba7282051aeb440bc75820b8c10db5c83c052d7586e3e98e84")!)! + let privateKey1 = PrivateKey(data: Data(hexString: "3c3385ddc6fd95ba7282051aeb440bc75820b8c10db5c83c052d7586e3e98e84")!, curve: CoinType.groestlcoin.curve)! let publicKey1 = privateKey1.getPublicKeySecp256k1(compressed: true) let legacyAddress = GroestlcoinAddress(publicKey: publicKey1, prefix: CoinType.groestlcoin.p2pkhPrefix) XCTAssertEqual(GroestlcoinAddress(string: "Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM")!.description, legacyAddress.description) - let privateKey2 = PrivateKey(data: Data(hexString: "8c59c0a6f433a961109d4fd485c4562f87e0f1ad0ece32e1db406a84c5028391")!)! + let privateKey2 = PrivateKey(data: Data(hexString: "8c59c0a6f433a961109d4fd485c4562f87e0f1ad0ece32e1db406a84c5028391")!, curve: CoinType.groestlcoin.curve)! let publicKey2 = privateKey2.getPublicKeySecp256k1(compressed: true) let bech32Address = SegwitAddress(hrp: .groestlcoin, publicKey: publicKey2) XCTAssertEqual(SegwitAddress(string: "grs1qsjpmsmm4x34wlt6kk4zef9u0jtculguktwgwg4")!.description, bech32Address.description) diff --git a/swift/Tests/Blockchains/IconTests.swift b/swift/Tests/Blockchains/IconTests.swift index 3e19c5d48bb..afe5c0e9706 100644 --- a/swift/Tests/Blockchains/IconTests.swift +++ b/swift/Tests/Blockchains/IconTests.swift @@ -21,7 +21,7 @@ class IconTests: XCTestCase { } func testFromPrivateKey() { - let privateKey = PrivateKey(data: Data(hexString: "94d1a980d5e528067d44bf8a60d646f556e40ca71e17cd4ead2d56f89e4bd20f")!)! + let privateKey = PrivateKey(data: Data(hexString: "94d1a980d5e528067d44bf8a60d646f556e40ca71e17cd4ead2d56f89e4bd20f")!, curve: CoinType.icon.curve)! let address = AnyAddress(publicKey: privateKey.getPublicKeySecp256k1(compressed: false), coin: .icon) XCTAssertEqual(address.description, "hx98c0832ca5bd8e8bf355ca9491888aa9725c2c48") } @@ -32,7 +32,7 @@ class IconTests: XCTestCase { } func testSigning() { - let privateKey = PrivateKey(data: Data(hexString: "2d42994b2f7735bbc93a3e64381864d06747e574aa94655c516f9ad0a74eed79")!)! + let privateKey = PrivateKey(data: Data(hexString: "2d42994b2f7735bbc93a3e64381864d06747e574aa94655c516f9ad0a74eed79")!, curve: CoinType.icon.curve)! let input = IconSigningInput.with { $0.fromAddress = "hxbe258ceb872e08851f1f59694dac2558708ece11" $0.toAddress = "hx5bfdb090f43a808005ffc27c25b213145e80b7cd" diff --git a/swift/Tests/Blockchains/InternetComputerTests.swift b/swift/Tests/Blockchains/InternetComputerTests.swift index e4942450abf..fc90e82dfb3 100644 --- a/swift/Tests/Blockchains/InternetComputerTests.swift +++ b/swift/Tests/Blockchains/InternetComputerTests.swift @@ -11,7 +11,7 @@ class InternetComputerTests: XCTestCase { func testAddress() { // TODO: Check and finalize implementation - let key = PrivateKey(data: Data(hexString: "ee42eaada903e20ef6e5069f0428d552475c1ea7ed940842da6448f6ef9d48e7")!)! + let key = PrivateKey(data: Data(hexString: "ee42eaada903e20ef6e5069f0428d552475c1ea7ed940842da6448f6ef9d48e7")!, curve: CoinType.internetComputer.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: false) let address = AnyAddress(publicKey: pubkey, coin: .internetComputer) let addressFromString = AnyAddress(string: "2f25874478d06cf68b9833524a6390d0ba69c566b02f46626979a3d6a4153211", coin: .internetComputer)! @@ -21,7 +21,7 @@ class InternetComputerTests: XCTestCase { } func testSign() { - let key = PrivateKey(data: Data(hexString: "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be")!)! + let key = PrivateKey(data: Data(hexString: "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be")!, curve: CoinType.internetComputer.curve)! let input = InternetComputerSigningInput.with { $0.privateKey = key.data $0.transaction = InternetComputerTransaction.with { @@ -39,7 +39,7 @@ class InternetComputerTests: XCTestCase { } func testSignWithInvalidToAccountIdentifier() { - let key = PrivateKey(data: Data(hexString: "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be")!)! + let key = PrivateKey(data: Data(hexString: "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be")!, curve: CoinType.internetComputer.curve)! let input = InternetComputerSigningInput.with { $0.privateKey = key.data $0.transaction = InternetComputerTransaction.with { @@ -57,7 +57,7 @@ class InternetComputerTests: XCTestCase { } func testSignWithInvalidAmount() { - let key = PrivateKey(data: Data(hexString: "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be")!)! + let key = PrivateKey(data: Data(hexString: "227102911bb99ce7285a55f952800912b7d22ebeeeee59d77fc33a5d7c7080be")!, curve: CoinType.internetComputer.curve)! let input = InternetComputerSigningInput.with { $0.privateKey = key.data $0.transaction = InternetComputerTransaction.with { diff --git a/swift/Tests/Blockchains/IoTeXTests.swift b/swift/Tests/Blockchains/IoTeXTests.swift index 017df545223..16cd34966a1 100644 --- a/swift/Tests/Blockchains/IoTeXTests.swift +++ b/swift/Tests/Blockchains/IoTeXTests.swift @@ -7,7 +7,7 @@ import WalletCore class IoTeXTests: XCTestCase { func testSign() { - let privateKey = PrivateKey(data: Data(hexString: "0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748")!)! + let privateKey = PrivateKey(data: Data(hexString: "0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748")!, curve: CoinType.ioTeX.curve)! let input = IoTeXSigningInput.with { $0.version = 1 diff --git a/swift/Tests/Blockchains/KavaTests.swift b/swift/Tests/Blockchains/KavaTests.swift index 1b23b5e3632..0d5ab0913ca 100644 --- a/swift/Tests/Blockchains/KavaTests.swift +++ b/swift/Tests/Blockchains/KavaTests.swift @@ -7,7 +7,7 @@ import WalletCore class KavaTests: XCTestCase { - let privateKey = PrivateKey(data: Data(hexString: "1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6")!)! + let privateKey = PrivateKey(data: Data(hexString: "1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6")!, curve: CoinType.kava.curve)! func testAddress() { let address = CoinType.kava.deriveAddress(privateKey: privateKey) diff --git a/swift/Tests/Blockchains/KuCoinCommunityChainTests.swift b/swift/Tests/Blockchains/KuCoinCommunityChainTests.swift index 6faee34cac3..b751ad9656c 100644 --- a/swift/Tests/Blockchains/KuCoinCommunityChainTests.swift +++ b/swift/Tests/Blockchains/KuCoinCommunityChainTests.swift @@ -8,7 +8,7 @@ import XCTest class KuCoinCommunityChainTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "33b85056aabab539bcb68540735ecf054e38bc58b29b751530e2b54ecb4ca564")!)! + let key = PrivateKey(data: Data(hexString: "33b85056aabab539bcb68540735ecf054e38bc58b29b751530e2b54ecb4ca564")!, curve: CoinType.kuCoinCommunityChain.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: false) let address = AnyAddress(publicKey: pubkey, coin: .kuCoinCommunityChain) let addressFromString = AnyAddress(string: "0xE5cA667d795685E9915E5F4b4254ca832eEB398B", coin: .kuCoinCommunityChain)! diff --git a/swift/Tests/Blockchains/KusamaTests.swift b/swift/Tests/Blockchains/KusamaTests.swift index b881a2451fb..0f1d5f1242b 100644 --- a/swift/Tests/Blockchains/KusamaTests.swift +++ b/swift/Tests/Blockchains/KusamaTests.swift @@ -20,7 +20,7 @@ class KusamaTests: XCTestCase { } func testAddress() { - let key = PrivateKey(data: Data(hexString: "0x85fca134b3fe3fd523d8b528608d803890e26c93c86dc3d97b8d59c7b3540c97")!)! + let key = PrivateKey(data: Data(hexString: "0x85fca134b3fe3fd523d8b528608d803890e26c93c86dc3d97b8d59c7b3540c97")!, curve: CoinType.kusama.curve)! let pubkey = key.getPublicKeyEd25519() let address = AnyAddress(publicKey: pubkey, coin: .kusama) let addressFromString = AnyAddress(string: "HewiDTQv92L2bVtkziZC8ASxrFUxr6ajQ62RXAnwQ8FDVmg", coin: .kusama)! diff --git a/swift/Tests/Blockchains/LitecoinTests.swift b/swift/Tests/Blockchains/LitecoinTests.swift index f4087fe1a04..7fb92ca4cdf 100644 --- a/swift/Tests/Blockchains/LitecoinTests.swift +++ b/swift/Tests/Blockchains/LitecoinTests.swift @@ -7,18 +7,20 @@ import XCTest class LitecoinTests: XCTestCase { func testAddress() { - let privateKey1 = PrivateKey(data: Data(hexString: "a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730")!)! + let curve = CoinType.litecoin.curve + + let privateKey1 = PrivateKey(data: Data(hexString: "a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730")!, curve: curve)! let publicKey1 = privateKey1.getPublicKeySecp256k1(compressed: true) let legacyAddress = BitcoinAddress(publicKey: publicKey1, prefix: CoinType.litecoin.p2pkhPrefix)! XCTAssertEqual(BitcoinAddress(string: "LV7LV7Z4bWDEjYkfx9dQo6k6RjGbXsg6hS")!.description, legacyAddress.description) - let privateKey2 = PrivateKey(data: Data(hexString: "f6ee7e6c9bd2f4dc8f0db0dc4679de06c998afc42d825edf7966dd4488b0aa1f")!)! + let privateKey2 = PrivateKey(data: Data(hexString: "f6ee7e6c9bd2f4dc8f0db0dc4679de06c998afc42d825edf7966dd4488b0aa1f")!, curve: curve)! let publicKey2 = privateKey2.getPublicKeySecp256k1(compressed: true) let compatibleAddress = BitcoinAddress.compatibleAddress(publicKey: publicKey2, prefix: CoinType.litecoin.p2shPrefix) XCTAssertEqual(BitcoinAddress(string: "M8eTgzhoFTErAjkGa6cyBomcHfxAprbDgD")!.description, compatibleAddress.description) - let privateKey3 = PrivateKey(data: Data(hexString: "55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625")!)! + let privateKey3 = PrivateKey(data: Data(hexString: "55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625")!, curve: curve)! let publicKey3 = privateKey3.getPublicKeySecp256k1(compressed: true) let bech32Address = SegwitAddress(hrp: .litecoin, publicKey: publicKey3) XCTAssertEqual(SegwitAddress(string: "ltc1qytnqzjknvv03jwfgrsmzt0ycmwqgl0asjnaxwu")!.description, bech32Address.description) diff --git a/swift/Tests/Blockchains/MonacoinTests.swift b/swift/Tests/Blockchains/MonacoinTests.swift index b08d139ab98..8d40a76fe1a 100644 --- a/swift/Tests/Blockchains/MonacoinTests.swift +++ b/swift/Tests/Blockchains/MonacoinTests.swift @@ -7,18 +7,18 @@ import XCTest class MonacoinTests: XCTestCase { func testAddress() { - let privateKey1 = PrivateKey(data: Data(hexString: "a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730")!)! + let privateKey1 = PrivateKey(data: Data(hexString: "a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730")!, curve: CoinType.monacoin.curve)! let publicKey1 = privateKey1.getPublicKeySecp256k1(compressed: true) let legacyAddress = BitcoinAddress(publicKey: publicKey1, prefix: CoinType.monacoin.p2pkhPrefix)! XCTAssertEqual(BitcoinAddress(string: "MHnYTL9e1s8zNR2qzzJ3mMHfgjnUzyMscd")!.description, legacyAddress.description) - let privateKey2 = PrivateKey(data: Data(hexString: "f6ee7e6c9bd2f4dc8f0db0dc4679de06c998afc42d825edf7966dd4488b0aa1f")!)! + let privateKey2 = PrivateKey(data: Data(hexString: "f6ee7e6c9bd2f4dc8f0db0dc4679de06c998afc42d825edf7966dd4488b0aa1f")!, curve: CoinType.monacoin.curve)! let publicKey2 = privateKey2.getPublicKeySecp256k1(compressed: true) let compatibleAddress = BitcoinAddress.compatibleAddress(publicKey: publicKey2, prefix: CoinType.monacoin.p2shPrefix) XCTAssertEqual(BitcoinAddress(string: "P9LUcYCEoMZEFuShhCHZcS8YSCEtGsMQ7u")!.description, compatibleAddress.description) - let privateKey3 = PrivateKey(data: Data(hexString: "55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625")!)! + let privateKey3 = PrivateKey(data: Data(hexString: "55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625")!, curve: CoinType.monacoin.curve)! let publicKey3 = privateKey3.getPublicKeySecp256k1(compressed: true) let bech32Address = SegwitAddress(hrp: .monacoin, publicKey: publicKey3) XCTAssertEqual(SegwitAddress(string: "mona1qytnqzjknvv03jwfgrsmzt0ycmwqgl0asju3qmd")!.description, bech32Address.description) diff --git a/swift/Tests/Blockchains/MultiversXTests.swift b/swift/Tests/Blockchains/MultiversXTests.swift index 2084f8b551d..39556f6081d 100644 --- a/swift/Tests/Blockchains/MultiversXTests.swift +++ b/swift/Tests/Blockchains/MultiversXTests.swift @@ -14,7 +14,7 @@ class MultiversXTests: XCTestCase { let carolBech32 = "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8" func testAddress() { - let key = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + let key = PrivateKey(data: Data(hexString: aliceSeedHex)!, curve: CoinType.multiversX.curve)! let pubkey = key.getPublicKeyEd25519() let address = AnyAddress(publicKey: pubkey, coin: .multiversX) let addressFromString = AnyAddress(string: aliceBech32, coin: .multiversX)! @@ -24,7 +24,7 @@ class MultiversXTests: XCTestCase { } func testSignGenericAction() { - let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!, curve: CoinType.multiversX.curve)! let input = MultiversXSigningInput.with { $0.genericAction = MultiversXGenericAction.with { @@ -52,7 +52,7 @@ class MultiversXTests: XCTestCase { } func testSignGenericActionWithGuardian() { - let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!, curve: CoinType.multiversX.curve)! let input = MultiversXSigningInput.with { $0.genericAction = MultiversXGenericAction.with { @@ -82,7 +82,7 @@ class MultiversXTests: XCTestCase { } func testSignGenericActionWithRelayer() { - let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!, curve: CoinType.multiversX.curve)! let input = MultiversXSigningInput.with { $0.genericAction = MultiversXGenericAction.with { @@ -112,7 +112,7 @@ class MultiversXTests: XCTestCase { func testSignGenericActionUndelegate() { // Successfully broadcasted https://explorer.multiversx.com/transactions/3301ae5a6a77f0ab9ceb5125258f12539a113b0c6787de76a5c5867f2c515d65 - let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!, curve: CoinType.multiversX.curve)! let input = MultiversXSigningInput.with { $0.genericAction = MultiversXGenericAction.with { @@ -141,7 +141,7 @@ class MultiversXTests: XCTestCase { func testSignGenericActionDelegate() { // Successfully broadcasted https://explorer.multiversx.com/transactions/e5007662780f8ed677b37b156007c24bf60b7366000f66ec3525cfa16a4564e7 - let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!, curve: CoinType.multiversX.curve)! let input = MultiversXSigningInput.with { $0.genericAction = MultiversXGenericAction.with { @@ -169,7 +169,7 @@ class MultiversXTests: XCTestCase { } func testSignEGLDTransfer() { - let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!, curve: CoinType.multiversX.curve)! let input = MultiversXSigningInput.with { $0.egldTransfer = MultiversXEGLDTransfer.with { @@ -193,7 +193,7 @@ class MultiversXTests: XCTestCase { } func testSignEGLDTransferWithGuardian() { - let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!, curve: CoinType.multiversX.curve)! let input = MultiversXSigningInput.with { $0.egldTransfer = MultiversXEGLDTransfer.with { @@ -218,7 +218,7 @@ class MultiversXTests: XCTestCase { } func testSignESDTTransfer() { - let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!, curve: CoinType.multiversX.curve)! let input = MultiversXSigningInput.with { $0.esdtTransfer = MultiversXESDTTransfer.with { @@ -244,7 +244,7 @@ class MultiversXTests: XCTestCase { } func testSignESDTNFTTransfer() { - let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)! + let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!, curve: CoinType.multiversX.curve)! let input = MultiversXSigningInput.with { $0.esdtnftTransfer = MultiversXESDTNFTTransfer.with { diff --git a/swift/Tests/Blockchains/NEARTests.swift b/swift/Tests/Blockchains/NEARTests.swift index e145db8c625..bf3fc950dc4 100644 --- a/swift/Tests/Blockchains/NEARTests.swift +++ b/swift/Tests/Blockchains/NEARTests.swift @@ -45,7 +45,7 @@ class NEARTests: XCTestCase { func testSigningTransaction() { // swiftlint:disable:next line_length - let privateKey = PrivateKey(data: Base58.decodeNoCheck(string: "3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv")!.subdata(in: 0..<32))! + let privateKey = PrivateKey(data: Base58.decodeNoCheck(string: "3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv")!.subdata(in: 0..<32), curve: CoinType.near.curve)! let input = NEARSigningInput.with { $0.signerID = "test.near" diff --git a/swift/Tests/Blockchains/NEOTests.swift b/swift/Tests/Blockchains/NEOTests.swift index 21b881831ff..3270ba1e314 100644 --- a/swift/Tests/Blockchains/NEOTests.swift +++ b/swift/Tests/Blockchains/NEOTests.swift @@ -8,7 +8,7 @@ import XCTest class NEOTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "2A9EAB0FEC93CD94FA0A209AC5604602C1F0105FB02EAB398E17B4517C2FFBAB")!)! + let key = PrivateKey(data: Data(hexString: "2A9EAB0FEC93CD94FA0A209AC5604602C1F0105FB02EAB398E17B4517C2FFBAB")!, curve: CoinType.neo.curve)! let pubkey = key.getPublicKeyNist256p1() let address = AnyAddress(publicKey: pubkey, coin: .neo) let addressFromString = AnyAddress(string: "AQCSMB3oSDA1dHPn6GXN6KB4NHmdo1fX41", coin: .neo) diff --git a/swift/Tests/Blockchains/NULSTests.swift b/swift/Tests/Blockchains/NULSTests.swift index 458162f164b..0fe52c5ea7f 100644 --- a/swift/Tests/Blockchains/NULSTests.swift +++ b/swift/Tests/Blockchains/NULSTests.swift @@ -8,7 +8,7 @@ import XCTest class NULSTests: XCTestCase { func testAddress() { - let privateKey = PrivateKey(data: Data(hexString: "0xa1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a")!)! + let privateKey = PrivateKey(data: Data(hexString: "0xa1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a")!, curve: CoinType.nuls.curve)! let pubkey = privateKey.getPublicKeySecp256k1(compressed: true) let address = AnyAddress(publicKey: pubkey, coin: .nuls) let addressFromString = AnyAddress(string: "NULSd6HghWa4CN5qdxqMwYVikQxRZyj57Jn4L", coin: .nuls)! diff --git a/swift/Tests/Blockchains/NativeInjectiveTests.swift b/swift/Tests/Blockchains/NativeInjectiveTests.swift index f8686ff84bb..6e89f972817 100644 --- a/swift/Tests/Blockchains/NativeInjectiveTests.swift +++ b/swift/Tests/Blockchains/NativeInjectiveTests.swift @@ -8,7 +8,7 @@ import XCTest class NativeInjectiveTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "9ee18daf8e463877aaf497282abc216852420101430482a28e246c179e2c5ef1")!)! + let key = PrivateKey(data: Data(hexString: "9ee18daf8e463877aaf497282abc216852420101430482a28e246c179e2c5ef1")!, curve: CoinType.nativeInjective.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: false) let address = AnyAddress(publicKey: pubkey, coin: .nativeInjective) let addressFromString = AnyAddress(string: "inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a", coin: .nativeInjective)! @@ -17,7 +17,7 @@ class NativeInjectiveTests: XCTestCase { } func testSign() { - let privateKey = PrivateKey(data: Data(hexString: "9ee18daf8e463877aaf497282abc216852420101430482a28e246c179e2c5ef1")!)! + let privateKey = PrivateKey(data: Data(hexString: "9ee18daf8e463877aaf497282abc216852420101430482a28e246c179e2c5ef1")!, curve: CoinType.nativeInjective.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: false) let fromAddress = AnyAddress(publicKey: publicKey, coin: .nativeInjective) diff --git a/swift/Tests/Blockchains/NativeZetaChainTests.swift b/swift/Tests/Blockchains/NativeZetaChainTests.swift index 5c5a8650098..bb05234c76c 100644 --- a/swift/Tests/Blockchains/NativeZetaChainTests.swift +++ b/swift/Tests/Blockchains/NativeZetaChainTests.swift @@ -7,7 +7,7 @@ import XCTest class NativeZetaChainTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed")!)! + let key = PrivateKey(data: Data(hexString: "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed")!, curve: CoinType.nativeZetaChain.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: false) let address = AnyAddress(publicKey: pubkey, coin: .nativeZetaChain) let addressFromString = AnyAddress(string: "zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne", coin: .nativeZetaChain)! @@ -16,7 +16,7 @@ class NativeZetaChainTests: XCTestCase { } func testSign() { - let privateKey = PrivateKey(data: Data(hexString: "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed")!)! + let privateKey = PrivateKey(data: Data(hexString: "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed")!, curve: CoinType.nativeZetaChain.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: false) let fromAddress = AnyAddress(publicKey: publicKey, coin: .nativeZetaChain) diff --git a/swift/Tests/Blockchains/NebulasTests.swift b/swift/Tests/Blockchains/NebulasTests.swift index 7cfe7e63747..1549e3719d6 100644 --- a/swift/Tests/Blockchains/NebulasTests.swift +++ b/swift/Tests/Blockchains/NebulasTests.swift @@ -10,7 +10,7 @@ import WalletCore class NebulasTests: XCTestCase { func testAddressFromPublicKey() { - let privateKey = PrivateKey(data: Data(hexString: "d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b")!)! + let privateKey = PrivateKey(data: Data(hexString: "d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b")!, curve: CoinType.nebulas.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: false) let address = AnyAddress(publicKey: publicKey, coin: .nebulas) @@ -28,7 +28,7 @@ class NebulasTests: XCTestCase { $0.amount = Data(hexString: "98a7d9b8314c0000")! //11000000000000000000ULL $0.payload = "" $0.timestamp = Data(hexString: "5cfc84ca")! //1560052938 - $0.privateKey = PrivateKey(data: Data(hexString: "d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b")!)!.data + $0.privateKey = PrivateKey(data: Data(hexString: "d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b")!, curve: CoinType.nebulas.curve)!.data } let output: NebulasSigningOutput = AnySigner.sign(input: input, coin: .nebulas) XCTAssertEqual(output.algorithm, 1) diff --git a/swift/Tests/Blockchains/NervosTests.swift b/swift/Tests/Blockchains/NervosTests.swift index c1716b0775f..8bbd395e8b5 100644 --- a/swift/Tests/Blockchains/NervosTests.swift +++ b/swift/Tests/Blockchains/NervosTests.swift @@ -16,7 +16,7 @@ class NervosTests: XCTestCase { func testAddress() throws { - let key = PrivateKey(data: Data(hexString: "8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb")!)! + let key = PrivateKey(data: Data(hexString: "8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb")!, curve: CoinType.nervos.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: true) let address = AnyAddress(publicKey: pubkey, coin: .nervos) let addressFromString = AnyAddress(string: "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9erg8furras980hksatlslfaktks7epf25", coin: .nervos)! diff --git a/swift/Tests/Blockchains/OasisTests.swift b/swift/Tests/Blockchains/OasisTests.swift index 994d0aff619..dce8b3d1220 100644 --- a/swift/Tests/Blockchains/OasisTests.swift +++ b/swift/Tests/Blockchains/OasisTests.swift @@ -9,7 +9,7 @@ class OasisTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0")!)! + let key = PrivateKey(data: Data(hexString: "4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0")!, curve: CoinType.oasis.curve)! let pubkey = key.getPublicKeyEd25519() let address = AnyAddress(publicKey: pubkey, coin: .oasis) let addressFromString = AnyAddress(string: "oasis1qzawzy5kaa2xgphenf3r0f5enpr3mx5dps559yxm", coin: .oasis)! @@ -19,7 +19,7 @@ class OasisTests: XCTestCase { } func testSign() { - let privateKey = PrivateKey(data: Data(hexString: "0x4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0")!)! + let privateKey = PrivateKey(data: Data(hexString: "0x4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0")!, curve: CoinType.oasis.curve)! let input = OasisSigningInput.with { $0.privateKey = privateKey.data $0.transfer = OasisTransferMessage.with { diff --git a/swift/Tests/Blockchains/OsmosisTests.swift b/swift/Tests/Blockchains/OsmosisTests.swift index 45fc92bc3e8..f59c17174b3 100644 --- a/swift/Tests/Blockchains/OsmosisTests.swift +++ b/swift/Tests/Blockchains/OsmosisTests.swift @@ -7,7 +7,7 @@ import XCTest class OsmosisTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af")!)! + let key = PrivateKey(data: Data(hexString: "8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af")!, curve: CoinType.osmosis.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: true) let address = AnyAddress(publicKey: pubkey, coin: .osmosis) let addressFromString = AnyAddress(string: "osmo1mky69cn8ektwy0845vec9upsdphktxt0en97f5", coin: .osmosis)! @@ -17,7 +17,7 @@ class OsmosisTests: XCTestCase { } func testSigningTransaction() { - let privateKey = PrivateKey(data: Data(hexString: "8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af")!)! + let privateKey = PrivateKey(data: Data(hexString: "8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af")!, curve: CoinType.osmosis.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) let fromAddress = AnyAddress(publicKey: publicKey, coin: .osmosis) diff --git a/swift/Tests/Blockchains/PactusTests.swift b/swift/Tests/Blockchains/PactusTests.swift index 933bdeaebea..9ee073327f9 100644 --- a/swift/Tests/Blockchains/PactusTests.swift +++ b/swift/Tests/Blockchains/PactusTests.swift @@ -10,7 +10,7 @@ class PactusTests: XCTestCase { override func setUp() { super.setUp() - privateKey = PrivateKey(data: Data(hexString: "4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6")!)! + privateKey = PrivateKey(data: Data(hexString: "4e51f1f3721f644ac7a193be7f5e7b8c2abaa3467871daf4eacb5d3af080e5d6")!, curve: CoinType.pactus.curve)! } func testAddress() { diff --git a/swift/Tests/Blockchains/PolkadotTests.swift b/swift/Tests/Blockchains/PolkadotTests.swift index fe1d96c74d8..0197a7b349b 100644 --- a/swift/Tests/Blockchains/PolkadotTests.swift +++ b/swift/Tests/Blockchains/PolkadotTests.swift @@ -22,7 +22,7 @@ class PolkadotTests: XCTestCase { } func testAddress() { - let key = PrivateKey(data: Data(hexString: "0xd65ed4c1a742699b2e20c0c1f1fe780878b1b9f7d387f934fe0a7dc36f1f9008")!)! + let key = PrivateKey(data: Data(hexString: "0xd65ed4c1a742699b2e20c0c1f1fe780878b1b9f7d387f934fe0a7dc36f1f9008")!, curve: CoinType.polkadot.curve)! let pubkey = key.getPublicKeyEd25519() let address = AnyAddress(publicKey: pubkey, coin: .polkadot) let addressFromString = AnyAddress(string: "12twBQPiG5yVSf3jQSBkTAKBKqCShQ5fm33KQhH3Hf6VDoKW", coin: .polkadot)! @@ -138,7 +138,7 @@ class PolkadotTests: XCTestCase { func testChillAndUnbond() { // real key in 1p test - let key = PrivateKey(data: Data(hexString: "298fcced2b497ed48367261d8340f647b3fca2d9415d57c2e3c5ef90482a2266")!)! + let key = PrivateKey(data: Data(hexString: "298fcced2b497ed48367261d8340f647b3fca2d9415d57c2e3c5ef90482a2266")!, curve: CoinType.polkadot.curve)! let input = PolkadotSigningInput.with { $0.genesisHash = genesisHash @@ -164,7 +164,7 @@ class PolkadotTests: XCTestCase { func testAcalaSigning() { // real key in 1p test - let key = PrivateKey(data: Data(hexString: "9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0")!)! + let key = PrivateKey(data: Data(hexString: "9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0")!, curve: CoinType.polkadot.curve)! let acalaGenesisHash = Data(hexString: "0xfc41b9bd8ef8fe53d58c7ea67c794c7ec9a73daf05e6d54b14ff6342c99ba64c")! diff --git a/swift/Tests/Blockchains/PolygonTests.swift b/swift/Tests/Blockchains/PolygonTests.swift index 5c0e06fff09..9493a1c3456 100644 --- a/swift/Tests/Blockchains/PolygonTests.swift +++ b/swift/Tests/Blockchains/PolygonTests.swift @@ -7,11 +7,11 @@ import XCTest class PolygonTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0")!)! + let key = PrivateKey(data: Data(hexString: "828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0")!, curve: CoinType.polygon.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: false) let address = AnyAddress(publicKey: pubkey, coin: .polygon) let expected = AnyAddress(string: "0xe32DC46bfBF78D1eada7b0a68C96903e01418D64", coin: .polygon)! XCTAssertEqual(address.description, expected.description) } -} \ No newline at end of file +} diff --git a/swift/Tests/Blockchains/PolymeshTests.swift b/swift/Tests/Blockchains/PolymeshTests.swift index bbd695266b7..f323ede9b85 100644 --- a/swift/Tests/Blockchains/PolymeshTests.swift +++ b/swift/Tests/Blockchains/PolymeshTests.swift @@ -11,7 +11,7 @@ class PolymeshTests: XCTestCase { let testKey1 = Data(hexString: "0x790a0a01ec2e7c7db4abcaffc92ce70a960ef9ad3021dbe3bf327c1c6343aee4")! func testAddress() { - let key = PrivateKey(data: Data(hexString: "0x790a0a01ec2e7c7db4abcaffc92ce70a960ef9ad3021dbe3bf327c1c6343aee4")!)! + let key = PrivateKey(data: Data(hexString: "0x790a0a01ec2e7c7db4abcaffc92ce70a960ef9ad3021dbe3bf327c1c6343aee4")!, curve: CoinType.polymesh.curve)! let pubkey = key.getPublicKeyEd25519() let address = AnyAddress(publicKey: pubkey, coin: .polymesh) let addressFromString = AnyAddress(string: "2EANwBfNsFu9KV8JsW5sbhF6ft8bzvw5EW1LCrgHhrqtK6Ys", coin: .polymesh)! diff --git a/swift/Tests/Blockchains/QtumTests.swift b/swift/Tests/Blockchains/QtumTests.swift index 1c9fdaf3da1..0b1303a96b2 100644 --- a/swift/Tests/Blockchains/QtumTests.swift +++ b/swift/Tests/Blockchains/QtumTests.swift @@ -7,13 +7,13 @@ import XCTest class QtumTests: XCTestCase { func testAddress() { - let privateKey1 = PrivateKey(data: Data(hexString: "a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730")!)! + let privateKey1 = PrivateKey(data: Data(hexString: "a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730")!, curve: CoinType.qtum.curve)! let publicKey1 = privateKey1.getPublicKeySecp256k1(compressed: true) let legacyAddress = BitcoinAddress(publicKey: publicKey1, prefix: CoinType.qtum.p2pkhPrefix)! XCTAssertEqual(BitcoinAddress(string: "QWVNLCXwhJqzut9YCLxbeMTximr2hmw7Vr")!.description, legacyAddress.description) - let privateKey2 = PrivateKey(data: Data(hexString: "55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625")!)! + let privateKey2 = PrivateKey(data: Data(hexString: "55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625")!, curve: CoinType.qtum.curve)! let publicKey2 = privateKey2.getPublicKeySecp256k1(compressed: true) let bech32Address = SegwitAddress(hrp: .qtum, publicKey: publicKey2) XCTAssertEqual(SegwitAddress(string: "qc1qytnqzjknvv03jwfgrsmzt0ycmwqgl0as6uywkk")!.description, bech32Address.description) diff --git a/swift/Tests/Blockchains/RippleTests.swift b/swift/Tests/Blockchains/RippleTests.swift index d4f9cf22689..d4be1f0d5ee 100644 --- a/swift/Tests/Blockchains/RippleTests.swift +++ b/swift/Tests/Blockchains/RippleTests.swift @@ -7,7 +7,7 @@ import WalletCore class RippleTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "9c3d42d0515f0406ed350ab2abf3eaf761f8907802469b64052ac17e2250ae13")!)! + let key = PrivateKey(data: Data(hexString: "9c3d42d0515f0406ed350ab2abf3eaf761f8907802469b64052ac17e2250ae13")!, curve: CoinType.xrp.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: true) let address = AnyAddress(publicKey: pubkey, coin: .xrp) diff --git a/swift/Tests/Blockchains/ScrollTests.swift b/swift/Tests/Blockchains/ScrollTests.swift index c890debc03e..b63c3ee51e2 100644 --- a/swift/Tests/Blockchains/ScrollTests.swift +++ b/swift/Tests/Blockchains/ScrollTests.swift @@ -7,7 +7,7 @@ import XCTest class ScrollTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0")!)! + let key = PrivateKey(data: Data(hexString: "828c4c48c2cef521f0251920891ed79e871faa24f64f43cde83d07bc99f8dbf0")!, curve: CoinType.scroll.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: false) let address = AnyAddress(publicKey: pubkey, coin: .scroll) let expected = AnyAddress(string: "0xe32DC46bfBF78D1eada7b0a68C96903e01418D64", coin: .scroll)! diff --git a/swift/Tests/Blockchains/SecretTests.swift b/swift/Tests/Blockchains/SecretTests.swift index 2acf4b61247..1af042b0505 100644 --- a/swift/Tests/Blockchains/SecretTests.swift +++ b/swift/Tests/Blockchains/SecretTests.swift @@ -7,7 +7,7 @@ import XCTest class SecretTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "87201512d132ef7a1e57f9e24905fbc24300bd73f676b5716182be5f3e39dada")!)! + let key = PrivateKey(data: Data(hexString: "87201512d132ef7a1e57f9e24905fbc24300bd73f676b5716182be5f3e39dada")!, curve: CoinType.secret.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: true) let address = AnyAddress(publicKey: pubkey, coin: .secret) let addressFromString = AnyAddress(string: "secret18mdrja40gfuftt5yx6tgj0fn5lurplezyp894y", coin: .secret)! @@ -17,7 +17,7 @@ class SecretTests: XCTestCase { } func testSigningTransaction() { - let privateKey = PrivateKey(data: Data(hexString: "87201512d132ef7a1e57f9e24905fbc24300bd73f676b5716182be5f3e39dada")!)! + let privateKey = PrivateKey(data: Data(hexString: "87201512d132ef7a1e57f9e24905fbc24300bd73f676b5716182be5f3e39dada")!, curve: CoinType.secret.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) let fromAddress = AnyAddress(publicKey: publicKey, coin: .secret) diff --git a/swift/Tests/Blockchains/SmartBitcoinCashTests.swift b/swift/Tests/Blockchains/SmartBitcoinCashTests.swift index 5385549a3b8..d478345c99c 100644 --- a/swift/Tests/Blockchains/SmartBitcoinCashTests.swift +++ b/swift/Tests/Blockchains/SmartBitcoinCashTests.swift @@ -8,7 +8,7 @@ import XCTest class SmartBitcoinCashTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "155cbd57319f3d938977b4c18000473eb3c432c4e31b667b63e88559c497d82d")!)! + let key = PrivateKey(data: Data(hexString: "155cbd57319f3d938977b4c18000473eb3c432c4e31b667b63e88559c497d82d")!, curve: CoinType.smartBitcoinCash.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: false) let address = AnyAddress(publicKey: pubkey, coin: .smartBitcoinCash) let addressFromString = AnyAddress(string: "0x8bFC9477684987dcAf0970b9bce5E3D9267C99C0", coin: .smartBitcoinCash)! diff --git a/swift/Tests/Blockchains/SolanaTests.swift b/swift/Tests/Blockchains/SolanaTests.swift index e84ced805eb..e02b9ae8131 100644 --- a/swift/Tests/Blockchains/SolanaTests.swift +++ b/swift/Tests/Blockchains/SolanaTests.swift @@ -10,7 +10,7 @@ class SolanaTests: XCTestCase { let privateKeyData = Data(Base58.decodeNoCheck( string: "A7psj2GW7ZMdY4E5hJq14KMeYg7HFjULSsWSrTXZLvYr")!) func testAddressFromPrivateKey() { - let privateKey = PrivateKey(data: privateKeyData)! + let privateKey = PrivateKey(data: privateKeyData, curve: CoinType.solana.curve)! let publicKey = privateKey.getPublicKeyEd25519() let address = AnyAddress(publicKey: publicKey, coin: .solana) XCTAssertEqual(address.description, "7v91N7iZ9mNicL8WfG6cgSCKyRXydQjLh6UYBWwm6y1Q") diff --git a/swift/Tests/Blockchains/StargazeTests.swift b/swift/Tests/Blockchains/StargazeTests.swift index 16bc5f5c3c0..bc67e73ee2b 100644 --- a/swift/Tests/Blockchains/StargazeTests.swift +++ b/swift/Tests/Blockchains/StargazeTests.swift @@ -7,7 +7,7 @@ import XCTest class StargazeTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433")!)! + let key = PrivateKey(data: Data(hexString: "a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433")!, curve: CoinType.stargaze.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: true) let address = AnyAddress(publicKey: pubkey, coin: .stargaze) let addressFromString = AnyAddress(string: "stars1mry47pkga5tdswtluy0m8teslpalkdq02a8nhy", coin: .stargaze)! @@ -17,7 +17,7 @@ class StargazeTests: XCTestCase { } func testSignCW721() { - let privateKey = PrivateKey(data: Data(hexString: "a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433")!)! + let privateKey = PrivateKey(data: Data(hexString: "a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433")!, curve: CoinType.stargaze.curve)! let txMessage = "{\"transfer_nft\": {\"recipient\": \"stars1kd5q7qejlqz94kpmd9pvr4v2gzgnca3lvt6xnp\",\"token_id\": \"1209\"}}"; let wasmMsg = CosmosMessage.WasmExecuteContractGeneric.with { @@ -63,7 +63,7 @@ class StargazeTests: XCTestCase { } func testSign() { - let privateKey = PrivateKey(data: Data(hexString: "a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433")!)! + let privateKey = PrivateKey(data: Data(hexString: "a498a9ee41af9bab5ef2a8be63d5c970135c3c109e70efc8c56c534e6636b433")!, curve: CoinType.stargaze.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) let fromAddress = AnyAddress(publicKey: publicKey, coin: .stargaze) diff --git a/swift/Tests/Blockchains/StarkExTests.swift b/swift/Tests/Blockchains/StarkExTests.swift index 56dd47f5839..078245d9305 100644 --- a/swift/Tests/Blockchains/StarkExTests.swift +++ b/swift/Tests/Blockchains/StarkExTests.swift @@ -7,7 +7,7 @@ import WalletCore class StarkExTests: XCTestCase { func testMessageAndVerifySigner() { - let privateKey = PrivateKey(data: Data(hexString: "04be51a04e718c202e4dca60c2b72958252024cfc1070c090dd0f170298249de")!)! + let privateKey = PrivateKey(data: Data(hexString: "04be51a04e718c202e4dca60c2b72958252024cfc1070c090dd0f170298249de")!, curve: .starkex)! let msg = "463a2240432264a3aa71a5713f2a4e4c1b9e12bbb56083cd56af6d878217cf" let signature = StarkExMessageSigner.signMessage(privateKey: privateKey, message: msg) XCTAssertEqual(signature, "04cf5f21333dd189ada3c0f2a51430d733501a9b1d5e07905273c1938cfb261e05b6013d74adde403e8953743a338c8d414bb96bf69d2ca1a91a85ed2700a528") diff --git a/swift/Tests/Blockchains/StellarTests.swift b/swift/Tests/Blockchains/StellarTests.swift index 159d1575fdb..d6be712bad8 100644 --- a/swift/Tests/Blockchains/StellarTests.swift +++ b/swift/Tests/Blockchains/StellarTests.swift @@ -8,7 +8,7 @@ import WalletCore class StellarTests: XCTestCase { func testAddressFromPrivateKey() { - let key = PrivateKey(data: Data(hexString: "59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")!)! + let key = PrivateKey(data: Data(hexString: "59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")!, curve: CoinType.stellar.curve)! let pubkey = key.getPublicKeyEd25519() let address = AnyAddress(publicKey: pubkey, coin: .stellar) diff --git a/swift/Tests/Blockchains/SyscoinTests.swift b/swift/Tests/Blockchains/SyscoinTests.swift index 9668e7de5de..7b4cfa53114 100644 --- a/swift/Tests/Blockchains/SyscoinTests.swift +++ b/swift/Tests/Blockchains/SyscoinTests.swift @@ -7,18 +7,18 @@ import XCTest class SyscoinTests: XCTestCase { func testAddress() { - let privateKey1 = PrivateKey(data: Data(hexString: "a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730")!)! + let privateKey1 = PrivateKey(data: Data(hexString: "a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730")!, curve: CoinType.syscoin.curve)! let publicKey1 = privateKey1.getPublicKeySecp256k1(compressed: true) let legacyAddress = BitcoinAddress(publicKey: publicKey1, prefix: CoinType.syscoin.p2pkhPrefix)! XCTAssertEqual(BitcoinAddress(string: "SXBPFk2PFDAP13qyKSdC4yptsJ8kJRAT3T")!.description, legacyAddress.description) - let privateKey2 = PrivateKey(data: Data(hexString: "f6ee7e6c9bd2f4dc8f0db0dc4679de06c998afc42d825edf7966dd4488b0aa1f")!)! + let privateKey2 = PrivateKey(data: Data(hexString: "f6ee7e6c9bd2f4dc8f0db0dc4679de06c998afc42d825edf7966dd4488b0aa1f")!, curve: CoinType.syscoin.curve)! let publicKey2 = privateKey2.getPublicKeySecp256k1(compressed: true) let compatibleAddress = BitcoinAddress.compatibleAddress(publicKey: publicKey2, prefix: CoinType.syscoin.p2shPrefix) XCTAssertEqual(BitcoinAddress(string: "32SKP7HqJLPRNEUNUDddNAXCxyMinjbX8g")!.description, compatibleAddress.description) - let privateKey3 = PrivateKey(data: Data(hexString: "55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625")!)! + let privateKey3 = PrivateKey(data: Data(hexString: "55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625")!, curve: CoinType.syscoin.curve)! let publicKey3 = privateKey3.getPublicKeySecp256k1(compressed: true) let bech32Address = SegwitAddress(hrp: .syscoin, publicKey: publicKey3) XCTAssertEqual(SegwitAddress(string: "sys1qytnqzjknvv03jwfgrsmzt0ycmwqgl0as7lkcf7")!.description, bech32Address.description) diff --git a/swift/Tests/Blockchains/THORChainTests.swift b/swift/Tests/Blockchains/THORChainTests.swift index a0ae0db1d45..5f584ddc446 100644 --- a/swift/Tests/Blockchains/THORChainTests.swift +++ b/swift/Tests/Blockchains/THORChainTests.swift @@ -20,7 +20,7 @@ class THORChainAddressTests: XCTestCase { class THORChainSignerTests: XCTestCase { - let privateKey = PrivateKey(data: Data(hexString: "7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e")!)! + let privateKey = PrivateKey(data: Data(hexString: "7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e")!, curve: CoinType.thorchain.curve)! func testJsonModeSigning() { let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) diff --git a/swift/Tests/Blockchains/TerraClassicTests.swift b/swift/Tests/Blockchains/TerraClassicTests.swift index 66ab6f11816..ceb05b6c020 100644 --- a/swift/Tests/Blockchains/TerraClassicTests.swift +++ b/swift/Tests/Blockchains/TerraClassicTests.swift @@ -7,7 +7,7 @@ import WalletCore class TerraClassicTests: XCTestCase { - let privateKey = PrivateKey(data: Data(hexString: "1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6")!)! + let privateKey = PrivateKey(data: Data(hexString: "1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6")!, curve: CoinType.terra.curve)! func testAddress() { let address = CoinType.terra.deriveAddress(privateKey: privateKey) @@ -452,7 +452,7 @@ class TerraClassicTests: XCTestCase { } func testSigningWasmTerraTransferTxProtobuf() { - let privateKey = PrivateKey(data: Data(hexString: "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616")!)! + let privateKey = PrivateKey(data: Data(hexString: "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616")!, curve: CoinType.terra.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra) @@ -500,9 +500,9 @@ class TerraClassicTests: XCTestCase { } func testSigningWasmTerraGenericProtobuf() { - let privateKey = PrivateKey(data: Data(hexString: "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616")!)! - let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) - let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra) + let privateKey = PrivateKey(data: Data(hexString: "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616")!, curve: CoinType.terra.curve)! + let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra) let wasmGenericMessage = CosmosMessage.WasmTerraExecuteContractGeneric.with { $0.senderAddress = fromAddress.description @@ -549,7 +549,7 @@ class TerraClassicTests: XCTestCase { } func testSigningWasmTerraGenericWithCoins() { - let privateKey = PrivateKey(data: Data(hexString: "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616")!)! + let privateKey = PrivateKey(data: Data(hexString: "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616")!, curve: CoinType.terra.curve)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) let fromAddress = AnyAddress(publicKey: publicKey, coin: .terra) diff --git a/swift/Tests/Blockchains/TerraTests.swift b/swift/Tests/Blockchains/TerraTests.swift index af8b1d0e593..c04caaf49a1 100644 --- a/swift/Tests/Blockchains/TerraTests.swift +++ b/swift/Tests/Blockchains/TerraTests.swift @@ -7,9 +7,9 @@ import WalletCore class TerraTests: XCTestCase { - let privateKey80e8 = PrivateKey(data: Data(hexString: "80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005")!)! // terra1hsk6jryyqjfhp5dhc55tc9jtckygx0ep37hdd2 - let privateKeycf08 = PrivateKey(data: Data(hexString: "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616")!)! // terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf - let privateKey1037 = PrivateKey(data: Data(hexString: "1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6")!)! + let privateKey80e8 = PrivateKey(data: Data(hexString: "80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005")!, curve: CoinType.terra.curve)! // terra1hsk6jryyqjfhp5dhc55tc9jtckygx0ep37hdd2 + let privateKeycf08 = PrivateKey(data: Data(hexString: "cf08ee8493e6f6a53f9721b9045576e80f371c0e36d08fdaf78b27a7afd8e616")!, curve: CoinType.terra.curve)! // terra18wukp84dq227wu4mgh0jm6n9nlnj6rs82pp9wf + let privateKey1037 = PrivateKey(data: Data(hexString: "1037f828ca313f4c9e120316e8e9ff25e17f07fe66ba557d5bc5e2eeb7cba8f6")!, curve: CoinType.terra.curve)! func testAddress() { let address = CoinType.terraV2.deriveAddress(privateKey: privateKey1037) diff --git a/swift/Tests/Blockchains/TezosTests.swift b/swift/Tests/Blockchains/TezosTests.swift index b7374aa2e9d..d53d37f7483 100644 --- a/swift/Tests/Blockchains/TezosTests.swift +++ b/swift/Tests/Blockchains/TezosTests.swift @@ -17,7 +17,7 @@ class TezosTests: XCTestCase { } public func testAddressFromPublicKey() { - let privateKey = PrivateKey(data: Data(hexString: "b177a72743f54ed4bdf51f1b55527c31bcd68c6d2cb2436d76cadd0227c99ff0")!)! + let privateKey = PrivateKey(data: Data(hexString: "b177a72743f54ed4bdf51f1b55527c31bcd68c6d2cb2436d76cadd0227c99ff0")!, curve: CoinType.tezos.curve)! let publicKey = privateKey.getPublicKeyEd25519() let address = tezos.deriveAddressFromPublicKey(publicKey: publicKey) @@ -113,7 +113,7 @@ class TezosTests: XCTestCase { } public func testMessageSignerSignAndVerify() { - let privateKey = PrivateKey(data: Data(hexString: "91b4fb8d7348db2e7de2693f58ce1cceb966fa960739adac1d9dba2cbaa0940a")!)! + let privateKey = PrivateKey(data: Data(hexString: "91b4fb8d7348db2e7de2693f58ce1cceb966fa960739adac1d9dba2cbaa0940a")!, curve: CoinType.tezos.curve)! let msg = "05010000004254657a6f73205369676e6564204d6573736167653a207465737455726c20323032332d30322d30385431303a33363a31382e3435345a2048656c6c6f20576f726c64" let signature = TezosMessageSigner.signMessage(privateKey: privateKey, message: msg) XCTAssertEqual(signature, "edsigu3se2fcEJUCm1aqxjzbHdf7Wsugr4mLaA9YM2UVZ9Yy5meGv87VqHN3mmDeRwApTj1JKDaYjqmLZifSFdWCqBoghqaowwJ") @@ -135,7 +135,7 @@ class TezosTests: XCTestCase { public func testSigning() { let privateKeyData = Data(hexString: "c6377a4cc490dc913fc3f0d9cf67d293a32df4547c46cb7e9e33c3b7b97c64d8")! - let privateKey = PrivateKey(data: privateKeyData)! + let privateKey = PrivateKey(data: privateKeyData, curve: CoinType.tezos.curve)! let publicKey = privateKey.getPublicKeyEd25519() let branch = "BL8euoCWqNCny9AR3AKjnpi38haYMxjei1ZqNHuXMn19JSQnoWp" @@ -201,8 +201,8 @@ class TezosTests: XCTestCase { watermark.append(bytes) let hash = Hash.blake2b(data: watermark, size: 32) - let privateKey = PrivateKey(data: key)! - let signature = privateKey.sign(digest: hash, curve: .ed25519)! + let privateKey = PrivateKey(data: key, curve: .ed25519)! + let signature = privateKey.sign(digest: hash)! var signed = Data() signed.append(bytes) diff --git a/swift/Tests/Blockchains/TheOpenNetworkTests.swift b/swift/Tests/Blockchains/TheOpenNetworkTests.swift index 797ae8334e0..d8d2961cc8f 100644 --- a/swift/Tests/Blockchains/TheOpenNetworkTests.swift +++ b/swift/Tests/Blockchains/TheOpenNetworkTests.swift @@ -8,7 +8,7 @@ import XCTest class TheOpenNetworkTests: XCTestCase { func testAddressFromPrivateKey() { let data = Data(hexString: "63474e5fe9511f1526a50567ce142befc343e71a49b865ac3908f58667319cb8") - let privateKey = PrivateKey(data: data!)! + let privateKey = PrivateKey(data: data!, curve: CoinType.ton.curve)! let publicKey = privateKey.getPublicKeyEd25519() let address = AnyAddress(publicKey: publicKey, coin: .ton) XCTAssertEqual(address.description, "UQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts4DV") @@ -71,7 +71,7 @@ class TheOpenNetworkTests: XCTestCase { // from the following mnemonic: // document shield addict crime broom point story depend suit satisfy test chicken valid tail speak fortune sound drill seek cube cheap body music recipe let privateKeyData = Data(hexString: "112d4e2e700a468f1eae699329202f1ee671d6b665caa2d92dea038cf3868c18")! - let privateKey = PrivateKey(data: privateKeyData)! + let privateKey = PrivateKey(data: privateKeyData, curve: CoinType.ton.curve)! let message = "Hello world" let signature = TONMessageSigner.signMessage(privateKey: privateKey, message: message)! // The following signature has been computed by calling `window.ton.send("ton_personalSign", { data: "Hello world" });`. diff --git a/swift/Tests/Blockchains/ThetaFuelTests.swift b/swift/Tests/Blockchains/ThetaFuelTests.swift index e0c42392f32..ce0ac162714 100644 --- a/swift/Tests/Blockchains/ThetaFuelTests.swift +++ b/swift/Tests/Blockchains/ThetaFuelTests.swift @@ -7,7 +7,7 @@ import XCTest class ThetaFuelTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "4646464646464646464646464646464646464646464646464646464646464646")!)! + let key = PrivateKey(data: Data(hexString: "4646464646464646464646464646464646464646464646464646464646464646")!, curve: CoinType.thetaFuel.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: false) let address = AnyAddress(publicKey: pubkey, coin: .thetaFuel) let expected = AnyAddress(string: "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F", coin: .thetaFuel)! diff --git a/swift/Tests/Blockchains/TronTests.swift b/swift/Tests/Blockchains/TronTests.swift index 9c5c7d6ebea..6f1d36d32a5 100644 --- a/swift/Tests/Blockchains/TronTests.swift +++ b/swift/Tests/Blockchains/TronTests.swift @@ -76,10 +76,10 @@ class TronTests: XCTestCase { } func testMessageAndVerifySigner() { - let privateKey = PrivateKey(data: Data(hexString: "75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a")!)! + let privateKey = PrivateKey(data: Data(hexString: "75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a")!, curve: CoinType.tron.curve)! let msg = "Hello World" let signature = TronMessageSigner.signMessage(privateKey: privateKey, message: msg) - XCTAssertEqual(signature, "9bb6d11ec8a6a3fb686a8f55b123e7ec4e9746a26157f6f9e854dd72f5683b450397a7b0a9653865658de8f9243f877539882891bad30c7286c3bf5622b900471b") + XCTAssertEqual(signature, "bc0753c070cc55693097df11bc11e1a7c4bd5e1a40b9dc94c75568e59bcc9d6b50a7873ef25b469e494490a54de37327b4bc7fc825c81a377b555e34fb7261ba1c") let pubKey = privateKey.getPublicKey(coinType: .tron) XCTAssertTrue(TronMessageSigner.verifyMessage(pubKey: pubKey, message: msg, signature: signature)) } diff --git a/swift/Tests/Blockchains/WavesTests.swift b/swift/Tests/Blockchains/WavesTests.swift index 3f16f7bdfd0..9d6d93ac1db 100644 --- a/swift/Tests/Blockchains/WavesTests.swift +++ b/swift/Tests/Blockchains/WavesTests.swift @@ -9,7 +9,7 @@ class WavesTests: XCTestCase { func testSigner() throws { - let privateKey = PrivateKey(data: Data(hexString: "68b7a9adb4a655b205f43dac413803785921e22cd7c4d05857b203a62621075f")!)! + let privateKey = PrivateKey(data: Data(hexString: "68b7a9adb4a655b205f43dac413803785921e22cd7c4d05857b203a62621075f")!, curve: CoinType.waves.curve)! let transferMessage = WavesTransferMessage.with { $0.amount = 100_000_000 diff --git a/swift/Tests/Blockchains/ZcashTests.swift b/swift/Tests/Blockchains/ZcashTests.swift index 6d201b53ce1..7154fad82e7 100644 --- a/swift/Tests/Blockchains/ZcashTests.swift +++ b/swift/Tests/Blockchains/ZcashTests.swift @@ -80,7 +80,7 @@ class ZcashTests: XCTestCase { let txId = Data.reverse(hexString: "3a19dd44032dfed61bfca5ba5751aab8a107b30609cbd5d70dc5ef09885b6853") let sapplingBranchId = Data(hexString: "bb09b876")! - let privateKey = PrivateKey(data: privateKeyData)! + let privateKey = PrivateKey(data: privateKeyData, curve: CoinType.zcash.curve)! let utxo0 = BitcoinV2Input.with { $0.outPoint = UtxoOutPoint.with { diff --git a/swift/Tests/Blockchains/ZenTests.swift b/swift/Tests/Blockchains/ZenTests.swift index ab7adddffd9..9824b8ca8e7 100644 --- a/swift/Tests/Blockchains/ZenTests.swift +++ b/swift/Tests/Blockchains/ZenTests.swift @@ -7,7 +7,7 @@ import XCTest class ZenTests: XCTestCase { func testAddress() { - let key = PrivateKey(data: Data(hexString: "3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")!)! + let key = PrivateKey(data: Data(hexString: "3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")!, curve: CoinType.zen.curve)! let pubkey = key.getPublicKeySecp256k1(compressed: true) let address = AnyAddress(publicKey: pubkey, coin: .zen) let addressFromString = AnyAddress(string: "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg", coin: .zen)! @@ -17,7 +17,7 @@ class ZenTests: XCTestCase { } func testSign() { - let key = PrivateKey(data: Data(hexString: "3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")!)! + let key = PrivateKey(data: Data(hexString: "3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")!, curve: CoinType.zen.curve)! let blockHash = Data(hexString: "81dc725fd33fada1062323802eefb54d3325d924d4297a692214560400000000")! let blockHeight = Int64(1147624) let utxos = [ diff --git a/swift/Tests/Blockchains/ZilliqaTests.swift b/swift/Tests/Blockchains/ZilliqaTests.swift index 060db939033..0113f3036b4 100644 --- a/swift/Tests/Blockchains/ZilliqaTests.swift +++ b/swift/Tests/Blockchains/ZilliqaTests.swift @@ -8,7 +8,7 @@ import WalletCore class ZilliqaTests: XCTestCase { let coin = CoinType.zilliqa - let privateKey = PrivateKey(data: Data(hexString: "0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748")!)! + let privateKey = PrivateKey(data: Data(hexString: "0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748")!, curve: CoinType.zilliqa.curve)! func testConfig() { XCTAssertEqual(coin.hrp, .zilliqa) diff --git a/swift/Tests/HDWalletTests.swift b/swift/Tests/HDWalletTests.swift index c36a17c62e3..5a1abbff378 100644 --- a/swift/Tests/HDWalletTests.swift +++ b/swift/Tests/HDWalletTests.swift @@ -365,7 +365,7 @@ class HDWalletTests: XCTestCase { let wallet = HDWallet.test let key = wallet.getKeyForCoin(coin: eth) let hash = Data(hexString: "3F891FDA3704F0368DAB65FA81EBE616F4AA2A0854995DA4DC0B59D2CADBD64F")! - let result = key.sign(digest: hash, curve: .secp256k1)! + let result = key.sign(digest: hash)! let publicKey = key.getPublicKeySecp256k1(compressed: false) XCTAssertEqual(result.count, 65) diff --git a/swift/Tests/Keystore/AccountTests.swift b/swift/Tests/Keystore/AccountTests.swift index 772e741ea56..06e04567bfb 100755 --- a/swift/Tests/Keystore/AccountTests.swift +++ b/swift/Tests/Keystore/AccountTests.swift @@ -17,8 +17,8 @@ class AccountTests: XCTestCase { let wallet = Wallet(keyURL: URL(fileURLWithPath: "/"), key: key) let hash = Data(hexString: "3F891FDA3704F0368DAB65FA81EBE616F4AA2A0854995DA4DC0B59D2CADBD64F")! - let privateKey = PrivateKey(data: wallet.key.decryptPrivateKey(password: password)!)! - let result = privateKey.sign(digest: hash, curve: .secp256k1)! + let privateKey = PrivateKey(data: wallet.key.decryptPrivateKey(password: password)!, curve: .secp256k1)! + let result = privateKey.sign(digest: hash)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: false) XCTAssertEqual(result.count, 65) @@ -32,7 +32,7 @@ class AccountTests: XCTestCase { let hash = Data(hexString: "3F891FDA3704F0368DAB65FA81EBE616F4AA2A0854995DA4DC0B59D2CADBD64F")! let hdwallet = wallet.key.wallet(password: password)! let privateKey = hdwallet.getKeyForCoin(coin: .ethereum) - let result = privateKey.sign(digest: hash, curve: .secp256k1)! + let result = privateKey.sign(digest: hash)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: false) XCTAssertEqual(result.count, 65) diff --git a/swift/Tests/Keystore/KeyStoreTests.swift b/swift/Tests/Keystore/KeyStoreTests.swift index 99b2815b825..405dfeac7d3 100755 --- a/swift/Tests/Keystore/KeyStoreTests.swift +++ b/swift/Tests/Keystore/KeyStoreTests.swift @@ -181,7 +181,7 @@ class KeyStoreTests: XCTestCase { XCTAssertNotNil(keyStore.keyWallet) XCTAssertNotNil(storedData) - XCTAssertNotNil(PrivateKey(data: storedData!)) + XCTAssertNotNil(PrivateKey(data: storedData!, curve: CoinType.ethereum.curve)) } func testImportPrivateKeyAES256() throws { @@ -195,17 +195,17 @@ class KeyStoreTests: XCTestCase { XCTAssertNotNil(keyStore.keyWallet) XCTAssertNotNil(storedData) - XCTAssertNotNil(PrivateKey(data: storedData!)) + XCTAssertNotNil(PrivateKey(data: storedData!, curve: CoinType.ethereum.curve)) } func testImportPrivateKey() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) - let privateKey = PrivateKey(data: Data(hexString: "9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c")!)! + let privateKey = PrivateKey(data: Data(hexString: "9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c")!, curve: CoinType.ethereum.curve)! let wallet = try keyStore.import(privateKey: privateKey, name: "name", password: "password", coin: .ethereum) let storedData = wallet.key.decryptPrivateKey(password: Data("password".utf8)) XCTAssertNotNil(storedData) - XCTAssertNotNil(PrivateKey(data: storedData!)) + XCTAssertNotNil(PrivateKey(data: storedData!, curve: CoinType.ethereum.curve)) XCTAssertEqual(wallet.accounts.count, 1) diff --git a/swift/Tests/PrivateKeyTests.swift b/swift/Tests/PrivateKeyTests.swift index 1fd2ce348bb..5219223483a 100644 --- a/swift/Tests/PrivateKeyTests.swift +++ b/swift/Tests/PrivateKeyTests.swift @@ -7,7 +7,7 @@ import XCTest class PrivateKeyTests: XCTestCase { func testCreateNew() { - let privateKey = PrivateKey() + let privateKey = PrivateKey(curve: .secp256k1) XCTAssertEqual(privateKey.data.count, TWPrivateKeySize) XCTAssertTrue(PrivateKey.isValid(data: privateKey.data, curve: .secp256k1)) @@ -15,23 +15,23 @@ class PrivateKeyTests: XCTestCase { } func testCreateFromInvalid() { - let privateKey = PrivateKey(data: Data(hexString: "0xdeadbeef")!) + let privateKey = PrivateKey(data: Data(hexString: "0xdeadbeef")!, curve: .secp256k1) XCTAssertNil(privateKey) } func testStarkKeyCreation() { let data = Data(hexString: "06cf0a8bf113352eb863157a45c5e5567abb34f8d32cddafd2c22aa803f4892c")! XCTAssertTrue(PrivateKey.isValid(data: data, curve: .starkex)) - let privateKey = PrivateKey(data: data)! + let privateKey = PrivateKey(data: data, curve: .starkex)! let pubKey = privateKey.getPublicKeyByType(pubkeyType: .starkex) XCTAssertEqual(pubKey.data.hexString, "02d2bbdc1adaf887b0027cdde2113cfd81c60493aa6dc15d7887ddf1a82bc831") } func testStarkKeySigning() { let data = Data(hexString: "0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79")! - let privateKey = PrivateKey(data: data)! + let privateKey = PrivateKey(data: data, curve: .starkex)! let digest = Data(hexString: "06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76")! - let signature = privateKey.sign(digest: digest, curve: .starkex)! + let signature = privateKey.sign(digest: digest)! XCTAssertEqual(signature.hexString, "061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a") } @@ -43,14 +43,14 @@ class PrivateKeyTests: XCTestCase { } func testPublicKey() { - let privateKey = PrivateKey(data: Data(hexString: "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")!)! + let privateKey = PrivateKey(data: Data(hexString: "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")!, curve: .secp256k1)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: false) XCTAssertEqual(publicKey.data.hexString, "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91") } func testSignSchnorr() { - let privateKey = PrivateKey(data: Data(hexString: "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")!)! + let privateKey = PrivateKey(data: Data(hexString: "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")!, curve: .secp256k1)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) let message = "hello schnorr".data(using: .utf8)! @@ -61,4 +61,12 @@ class PrivateKeyTests: XCTestCase { XCTAssertEqual(sig.hexString, "d166b1ae7892c5ef541461dc12a50214d0681b63d8037cda29a3fe6af8bb973e4ea94624d85bc0010bdc1b38d05198328fae21254adc2bf5feaf2804d54dba55") XCTAssertTrue(verified) } + + func testSignWithoutCurve() { + let data = Data(hexString: "0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79")! + let privateKey = PrivateKey(data: data, curve: .starkex)! + let digest = Data(hexString: "06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76")! + let signature = privateKey.sign(digest: digest)! + XCTAssertEqual(signature.hexString, "061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a") + } } diff --git a/swift/Tests/PublicKeyTests.swift b/swift/Tests/PublicKeyTests.swift index 026935dbb6d..ad0dfe51511 100644 --- a/swift/Tests/PublicKeyTests.swift +++ b/swift/Tests/PublicKeyTests.swift @@ -7,9 +7,9 @@ import XCTest class PublicKeyTests: XCTestCase { - let privateKey = PrivateKey(data: Data(hexString: "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")!)! func testCompressed() { + let privateKey = PrivateKey(data: Data(hexString: "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")!, curve: .secp256k1)! let publicKey = privateKey.getPublicKeySecp256k1(compressed: false) XCTAssertEqual(publicKey.compressed.data.hexString, "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1") } @@ -17,16 +17,20 @@ class PublicKeyTests: XCTestCase { func testVerify() { let message = Hash.sha256(data: "hello".data(using: .utf8)!) - let sig1 = privateKey.sign(digest: message, curve: .ed25519)! - let result1 = privateKey.getPublicKeyEd25519() + + let privateKey1 = PrivateKey(data: Data(hexString: "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")!, curve: .ed25519)! + let sig1 = privateKey1.sign(digest: message)! + let result1 = privateKey1.getPublicKeyEd25519() .verify(signature: sig1, message: message) - let sig2 = privateKey.sign(digest: message, curve: .ed25519Blake2bNano)! - let result2 = privateKey.getPublicKeyEd25519Blake2b() + let privateKey2 = PrivateKey(data: Data(hexString: "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")!, curve: .ed25519Blake2bNano)! + let sig2 = privateKey2.sign(digest: message)! + let result2 = privateKey2.getPublicKeyEd25519Blake2b() .verify(signature: sig2, message: message) - let sig3 = privateKey.sign(digest: message, curve: .secp256k1)! - let result3 = privateKey.getPublicKeySecp256k1(compressed: true) + let privateKey3 = PrivateKey(data: Data(hexString: "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")!, curve: .secp256k1)! + let sig3 = privateKey3.sign(digest: message)! + let result3 = privateKey3.getPublicKeySecp256k1(compressed: true) .verify(signature: sig3, message: message) XCTAssertTrue(result1) diff --git a/tests/chains/Aeternity/SignerTests.cpp b/tests/chains/Aeternity/SignerTests.cpp index 3fe61a03851..c5038f13631 100644 --- a/tests/chains/Aeternity/SignerTests.cpp +++ b/tests/chains/Aeternity/SignerTests.cpp @@ -22,7 +22,7 @@ TEST(AeternitySigner, Sign) { uint64_t nonce = 49; auto transaction = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); - auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"), TWCurveED25519); auto result = Signer::sign(privateKey, transaction); EXPECT_EQ(result.signature(), "sg_VW42qDPP3MMNFAStYaumjZz7mC7BZYpbNa15E57ejqUe7JdQFWCiX65eLNUpGMpt8tSpfgCfkYzcaFppqx7W75CrcWdC8"); @@ -39,7 +39,7 @@ TEST(AeternitySigner, SignTxWithZeroTtl) { uint64_t nonce = 49; auto transaction = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); - auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"), TWCurveED25519); auto result = Signer::sign(privateKey, transaction); EXPECT_EQ(result.signature(), "sg_7qJK868bqEZ5ciC2P3WCKYfhayvKTHvPsz3bdPgpfF3Ky7yNg9f8k22A3gxjjSm9afa6JmP8TJpF4GJkFh2k7gGaog9KS"); @@ -56,7 +56,7 @@ TEST(AeternitySigner, SignTxWithZeroAmount) { uint64_t nonce = 7; auto transaction = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); - auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"), TWCurveED25519); auto result = Signer::sign(privateKey, transaction); EXPECT_EQ(result.signature(), "sg_ShWvujPnyKBT1Ng2X5k6XSchVK8Bq7LYEisPMH11DUoPkXZcooBzqw81j9j5JewoFFpT9xEhUptj1azcLA21ogURYh4Lz"); @@ -73,7 +73,7 @@ TEST(AeternitySigner, SignTxWithZeroNonce) { uint64_t nonce = 0; auto transaction = Transaction(sender_id, recipient_id, amount, fee, payload, ttl, nonce); - auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + auto privateKey = PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"), TWCurveED25519); auto result = Signer::sign(privateKey, transaction); EXPECT_EQ(result.signature(), "sg_MaJc4ptSUhq5kH6mArszDAvu4f7PejyuhmgM6U8GEr8bRUTaSFbdFPx4C6FEYA5v5Lgwu9EToaWnHgR2xkqZ9JjHnaBpA"); diff --git a/tests/chains/Aion/SignerTests.cpp b/tests/chains/Aion/SignerTests.cpp index 84927f7dcee..ed6ba21e40e 100644 --- a/tests/chains/Aion/SignerTests.cpp +++ b/tests/chains/Aion/SignerTests.cpp @@ -14,7 +14,7 @@ TEST(AionSigner, Sign) { auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); auto transaction = Transaction(9, 20000000000, 21000, address, 10000, 155157377101, {}); - auto privateKey = PrivateKey(parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9")); + auto privateKey = PrivateKey(parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9"), TWCurveED25519); Signer::sign(privateKey, transaction); EXPECT_EQ(hex(transaction.signature), "a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a7d3d3386742c2716031b79950cef5fcb49c079a5cab095c8b08915e126b9741389924ba2d5c00036a3b39c2a8562fa0800f1a13a566ce6e027274ce63a41dec07"); @@ -27,7 +27,7 @@ TEST(AionSigner, SignWithData) { auto address = Aion::Address("0xa082c3de528b7807dc27ad66debb16d4cfe4054209398cee619dd95955063d1e"); auto transaction = Transaction(9, 20000000000, 21000, address, 10000, 155157377101, parse_hex("41494f4e0000")); - auto privateKey = PrivateKey(parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9")); + auto privateKey = PrivateKey(parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9"), TWCurveED25519); Signer::sign(privateKey, transaction); EXPECT_EQ(hex(transaction.signature), "a775daa30b33fda3091768f0561c8042ee23cb48a6a3e5d7e8248b13d04a48a736fc2642c2d62900204779aa274dba3b8712eff7a8464aa78ea52b09ece20679fe3f5edf94c84a7e0c5f93213be891bc279af927086f455167f5bc73d3046c0d"); diff --git a/tests/chains/Aion/TransactionCompilerTests.cpp b/tests/chains/Aion/TransactionCompilerTests.cpp index e10e739bec2..1b5afac8aac 100644 --- a/tests/chains/Aion/TransactionCompilerTests.cpp +++ b/tests/chains/Aion/TransactionCompilerTests.cpp @@ -23,7 +23,7 @@ namespace TW::Aion::tests { TEST(AionCompiler, CompileWithSignatures) { /// Step 1: Prepare transaction input (protobuf) auto privateKey = parse_hex("db33ffdf82c7ba903daf68d961d3c23c20471a8ce6b408e52d579fd8add80cc9"); - auto key = PrivateKey(privateKey); + auto key = PrivateKey(privateKey, TWCurveED25519); auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); Proto::SigningInput input; @@ -50,7 +50,7 @@ TEST(AionCompiler, CompileWithSignatures) { EXPECT_EQ(hex(preImageHash), "d4423fe7d233b85c1bf5b1120ec03842e572fb25f3755f7a20bc83addc8c4d85"); // Simulate signature, normally obtained from signature server - const auto signature = key.sign(preImageHash, TWCurveED25519); + const auto signature = key.sign(preImageHash); /// Step 3: Compile transaction info auto outputData = diff --git a/tests/chains/Algorand/AddressTests.cpp b/tests/chains/Algorand/AddressTests.cpp index 993b0e3247c..7d3371042ed 100644 --- a/tests/chains/Algorand/AddressTests.cpp +++ b/tests/chains/Algorand/AddressTests.cpp @@ -29,7 +29,7 @@ TEST(AlgorandAddress, Validation) { } TEST(AlgorandAddress, FromPrivateKey) { - auto privateKey = PrivateKey(parse_hex("526d96fffdbfe787b2f00586298538f9a019e97f6587964dc61aae9ad1d7fa23")); + auto privateKey = PrivateKey(parse_hex("526d96fffdbfe787b2f00586298538f9a019e97f6587964dc61aae9ad1d7fa23"), TWCurveED25519); auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeED25519)); ASSERT_EQ(address.string(), "JBCQYJ2FREG667NAN7BFKH4RFIKPT7CYDQJNW3SNN5Z7F7ILFLKQ346TSU"); } @@ -39,7 +39,7 @@ TEST(AlgorandAddress, FromPublicKey) { auto address = Address(publicKey); ASSERT_EQ(address.string(), "YK2CHL5IWAEV4WXBAVTIXENSCMW3JWW36OFM7RSJBDJUO2QADEP5QYVO5I"); - auto privateKey2 = PrivateKey(parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f")); + auto privateKey2 = PrivateKey(parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"), TWCurveSECP256k1); auto publicKey2 = privateKey2.getPublicKey(TWPublicKeyTypeSECP256k1Extended); EXPECT_ANY_THROW(new Address(publicKey2)); } diff --git a/tests/chains/Algorand/SignerTests.cpp b/tests/chains/Algorand/SignerTests.cpp index cc3736ccd2a..f3a1b359439 100644 --- a/tests/chains/Algorand/SignerTests.cpp +++ b/tests/chains/Algorand/SignerTests.cpp @@ -55,7 +55,7 @@ TEST(AlgorandSigner, EncodeBytes) { } TEST(AlgorandSigner, Sign) { - auto key = PrivateKey(parse_hex("c9d3cc16fecabe2747eab86b81528c6ed8b65efc1d6906d86aabc27187a1fe7c")); + auto key = PrivateKey(parse_hex("c9d3cc16fecabe2747eab86b81528c6ed8b65efc1d6906d86aabc27187a1fe7c"), TWCurveED25519); auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); auto from = Address(publicKey); auto to = Address("UCE2U2JC4O4ZR6W763GUQCG57HQCDZEUJY4J5I6VYY4HQZUJDF7AKZO5GM"); @@ -85,7 +85,7 @@ TEST(AlgorandSigner, Sign) { TEST(AlgorandSigner, SignAssetNFTTransfer) { // Successfully broadcasted: https://allo.info/tx/FFLUH4QKZHG744RIQ2AZNWZUSIIH262KZ4MEWSY4RXMWN5NMOOJA - auto key = PrivateKey(parse_hex("dc6051ffc7b3ec601bde432f6dea34d40fe3855e4181afa0f0524c42194a6da7")); + auto key = PrivateKey(parse_hex("dc6051ffc7b3ec601bde432f6dea34d40fe3855e4181afa0f0524c42194a6da7"), TWCurveED25519); auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); auto from = Address(publicKey); auto to = Address("362T7CSXNLIOBX6J3H2SCPS4LPYFNV6DDWE6G64ZEUJ6SY5OJIR6SB5CVE"); @@ -116,7 +116,7 @@ TEST(AlgorandSigner, SignAssetNFTTransfer) { TEST(AlgorandSigner, SignAsset) { // https://explorer.bitquery.io/algorand_testnet/tx/NJ62HYO2LC222AVLIN2GW5LKIWKLGC7NZLIQ3DUL2RDVRYO2UW7A - auto key = PrivateKey(parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04")); + auto key = PrivateKey(parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04"), TWCurveED25519); auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); auto from = Address(publicKey); auto to = Address("GJIWJSX2EU5RC32LKTDDXWLA2YICBHKE35RV2ZPASXZYKWUWXFLKNFSS4U"); @@ -146,7 +146,7 @@ TEST(AlgorandSigner, SignAsset) { } TEST(AlgorandSigner, SignAssetWithNote) { - auto key = PrivateKey(parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04")); + auto key = PrivateKey(parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04"), TWCurveED25519); auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); auto from = Address(publicKey); auto to = Address("GJIWJSX2EU5RC32LKTDDXWLA2YICBHKE35RV2ZPASXZYKWUWXFLKNFSS4U"); @@ -173,7 +173,7 @@ TEST(AlgorandSigner, SignAssetWithNote) { TEST(AlgorandSigner, SignAssetOptIn) { // https://explorer.bitquery.io/algorand_testnet/tx/47LE2QS4B5N6IFHXOUN2MJUTCOQCHNY6AB3AJYECK4IM2VYKJDKQ - auto key = PrivateKey(parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04")); + auto key = PrivateKey(parse_hex("5a6a3cfe5ff4cc44c19381d15a0d16de2a76ee5c9b9d83b232e38cb5a2c84b04"), TWCurveED25519); auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); auto address = Address(publicKey); Data note; diff --git a/tests/chains/Algorand/TransactionCompilerTests.cpp b/tests/chains/Algorand/TransactionCompilerTests.cpp index e630a7205a3..5df08888b70 100644 --- a/tests/chains/Algorand/TransactionCompilerTests.cpp +++ b/tests/chains/Algorand/TransactionCompilerTests.cpp @@ -23,7 +23,7 @@ namespace TW::Algorand::tests { TEST(AlgorandCompiler, CompileWithSignatures) { /// Step 1: Prepare transaction input (protobuf) auto privateKey = parse_hex("d5b43d706ef0cb641081d45a2ec213b5d8281f439f2425d1af54e2afdaabf55b"); - auto key = PrivateKey(privateKey); + auto key = PrivateKey(privateKey, TWCurveED25519); auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); auto note = parse_hex("68656c6c6f"); auto genesisHash = Base64::decode("wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8="); @@ -52,7 +52,7 @@ TEST(AlgorandCompiler, CompileWithSignatures) { EXPECT_EQ(hex(preImage), "54588aa3616d74cf000000e8d4a51000a3666565ce00040358a26676ce001d9167a367656eac6d61696e6e65742d76312e30a26768c420c061c4d8fc1dbdded2d7604be4568e3f6d041987ac37bde4b620b5ab39248adfa26c76ce001d954fa46e6f7465c40568656c6c6fa3726376c42014560180e9c92da3171aa3c872356e30a8ea7f96c4a74bc1755a68929c94cb8fa3736e64c42061bf060efc02e2887dfffc8ed85268c8c091c013eedf315bc50794d02a8791ada474797065a3706179"); // Simulate signature, normally obtained from signature server - const auto signature = key.sign(preImage, TWCurveED25519); + const auto signature = key.sign(preImage); /// Step 3: Compile transaction info auto outputData = diff --git a/tests/chains/Aptos/AddressTests.cpp b/tests/chains/Aptos/AddressTests.cpp index b94e4222c86..6c7a952b87f 100644 --- a/tests/chains/Aptos/AddressTests.cpp +++ b/tests/chains/Aptos/AddressTests.cpp @@ -38,7 +38,7 @@ TEST(AptosAddress, Invalid) { } TEST(AptosAddress, FromPrivateKey) { - auto privateKey = PrivateKey(parse_hex("088baa019f081d6eab8dff5c447f9ce2f83c1babf3d03686299eaf6a1e89156e")); + auto privateKey = PrivateKey(parse_hex("088baa019f081d6eab8dff5c447f9ce2f83c1babf3d03686299eaf6a1e89156e"), TWCurveED25519); auto pubkey = privateKey.getPublicKey(TWPublicKeyTypeED25519); Entry entry; auto address = entry.deriveAddress(TWCoinTypeAptos, pubkey, TWDerivationDefault, std::monostate{}); diff --git a/tests/chains/Aptos/CompilerTests.cpp b/tests/chains/Aptos/CompilerTests.cpp index ebe0ccc49ae..814e71c6f02 100644 --- a/tests/chains/Aptos/CompilerTests.cpp +++ b/tests/chains/Aptos/CompilerTests.cpp @@ -44,9 +44,9 @@ TEST(AptosCompiler, StandardTransaction) { // Sign the pre-hash data. - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); + auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec"), TWCurveED25519); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; - auto signature = privateKey.sign(actualDataToSign, TWCurveED25519); + auto signature = privateKey.sign(actualDataToSign); EXPECT_EQ(hex(signature), "5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"); // Compile the transaction. @@ -125,9 +125,9 @@ TEST(AptosCompiler, BlindTransactionJson) { // Sign the pre-hash data. - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); + auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec"), TWCurveED25519); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; - auto signature = privateKey.sign(actualDataToSign, TWCurveED25519); + auto signature = privateKey.sign(actualDataToSign); EXPECT_EQ(hex(signature), "42cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b"); // Compile the transaction. diff --git a/tests/chains/Aptos/TWAnySignerTests.cpp b/tests/chains/Aptos/TWAnySignerTests.cpp index b0ad67d6d8c..c1b44907593 100644 --- a/tests/chains/Aptos/TWAnySignerTests.cpp +++ b/tests/chains/Aptos/TWAnySignerTests.cpp @@ -26,7 +26,7 @@ TEST(TWAnySignerAptos, TxSign) { input.set_gas_unit_price(100); input.set_expiration_timestamp_secs(3664390082); input.set_chain_id(33); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); + auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec"), TWCoinTypeCurve(TWCoinTypeAptos)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeAptos); @@ -85,7 +85,7 @@ TEST(TWAnySignerAptos, TxSignWithABI) { "u64", "bool" ])"); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); + auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec"), TWCoinTypeCurve(TWCoinTypeAptos)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeAptos); diff --git a/tests/chains/BinanceSmartChain/SignerTests.cpp b/tests/chains/BinanceSmartChain/SignerTests.cpp index c9196d6b53b..53addde1c4e 100644 --- a/tests/chains/BinanceSmartChain/SignerTests.cpp +++ b/tests/chains/BinanceSmartChain/SignerTests.cpp @@ -60,7 +60,7 @@ TEST(BinanceSmartChain, SignTokenTransfer) { auto tokenContractAddress = "0xed24fc36d5ee211ea25a80239fb8c4cfd80f12ee"; auto dummyAmount = store(uint256_t(0)); // addr: 0xB9F5771C27664bF2282D98E09D7F50cEc7cB01a7 mnemonic: isolate dismiss ... cruel note - auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); + auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904"), TWCoinTypeCurve(TWCoinTypeSmartChain)); input.set_chain_id(chainId.data(), chainId.size()); input.set_nonce(nonce.data(), nonce.size()); diff --git a/tests/chains/BinanceSmartChain/TWAnyAddressTests.cpp b/tests/chains/BinanceSmartChain/TWAnyAddressTests.cpp index 139e6a8368b..13ca90a3794 100644 --- a/tests/chains/BinanceSmartChain/TWAnyAddressTests.cpp +++ b/tests/chains/BinanceSmartChain/TWAnyAddressTests.cpp @@ -13,7 +13,7 @@ using namespace TW; TEST(TWBinanceSmartChain, Address) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06").get(), TWCoinTypeCurve(TWCoinTypeSmartChain))); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), false)); auto string = "0xf3d468DBb386aaD46E92FF222adDdf872C8CC064"; diff --git a/tests/chains/Bitcoin/MessageSignerTests.cpp b/tests/chains/Bitcoin/MessageSignerTests.cpp index d55cded3107..8b04383336f 100644 --- a/tests/chains/Bitcoin/MessageSignerTests.cpp +++ b/tests/chains/Bitcoin/MessageSignerTests.cpp @@ -21,7 +21,7 @@ namespace TW::Bitcoin::MessageSignerTests { -const auto gPrivateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); +const auto gPrivateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCoinTypeCurve(TWCoinTypeBitcoin)); TEST(BitcoinMessageSigner, VerifyMessage) { EXPECT_TRUE(MessageSigner::verifyMessage( @@ -152,7 +152,7 @@ TEST(TWBitcoinMessageSigner, VerifyMessage) { TEST(TWBitcoinMessageSigner, SignAndVerify) { const auto privKeyData = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get(), TWCoinTypeCurve(TWCoinTypeBitcoin))); const auto address = STRING("19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X"); const auto message = STRING("test signature"); diff --git a/tests/chains/Bitcoin/TWAnyAddressTests.cpp b/tests/chains/Bitcoin/TWAnyAddressTests.cpp index f482783a01b..62c271c25b8 100644 --- a/tests/chains/Bitcoin/TWAnyAddressTests.cpp +++ b/tests/chains/Bitcoin/TWAnyAddressTests.cpp @@ -15,7 +15,7 @@ namespace TW::Bitcoin::tests { void testBitcoinAddressFromPublicKeyDerivation(const char* privateKey, TWDerivation derivation, const char* expectedAddr) { const auto data = DATA(privateKey); - const auto privkey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(data.get())); + const auto privkey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(data.get(), TWCoinTypeCurve(TWCoinTypeBitcoin))); const auto pubkey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyByType(privkey.get(), TWPublicKeyTypeSECP256k1)); const auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKeyDerivation(pubkey.get(), TWCoinTypeBitcoin, derivation)); const auto addrDescription = WRAPS(TWAnyAddressDescription(addr.get())); diff --git a/tests/chains/Bitcoin/TWBitcoinPsbtTests.cpp b/tests/chains/Bitcoin/TWBitcoinPsbtTests.cpp index 83ef3a6c6ba..57bb5cf23be 100644 --- a/tests/chains/Bitcoin/TWBitcoinPsbtTests.cpp +++ b/tests/chains/Bitcoin/TWBitcoinPsbtTests.cpp @@ -12,7 +12,7 @@ namespace TW::Bitcoin::PsbtTests { -const auto gPrivateKey = PrivateKey(parse_hex("f00ffbe44c5c2838c13d2778854ac66b75e04eb6054f0241989e223223ad5e55")); +const auto gPrivateKey = PrivateKey(parse_hex("f00ffbe44c5c2838c13d2778854ac66b75e04eb6054f0241989e223223ad5e55"), TWCoinTypeCurve(TWCoinTypeBitcoin)); const auto gPsbt = parse_hex("70736274ff0100bc0200000001147010db5fbcf619067c1090fec65c131443fbc80fb4aaeebe940e44206098c60000000000ffffffff0360ea000000000000160014f22a703617035ef7f490743d50f26ae08c30d0a70000000000000000426a403d3a474149412e41544f4d3a636f736d6f7331737377797a666d743675396a373437773537753438746778646575393573757a666c6d7175753a303a743a35303e12000000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d000000000001011f6603010000000000160014b139199ec796f36fc42e637f42da8e3e6720aa9d00000000"); TEST(TWBitcoinPsbt, SignThorSwap) { diff --git a/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp b/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp index c54bd302026..f8accd9173b 100644 --- a/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp +++ b/tests/chains/Bitcoin/TWBitcoinSigningTests.cpp @@ -46,7 +46,7 @@ SigningInput buildInputP2PKH(bool omitKey = false) { input.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"; input.coinType = TWCoinTypeBitcoin; - auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); + auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"), TWCurveSECP256k1); auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash0 = Hash::ripemd(Hash::sha256(pubKey0.bytes)); assert(hex(utxoPubkeyHash0) == "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); @@ -54,7 +54,7 @@ SigningInput buildInputP2PKH(bool omitKey = false) { input.privateKeys.push_back(utxoKey0); } - auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"), TWCurveSECP256k1); auto pubKey1 = utxoKey1.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash1 = Hash::ripemd(Hash::sha256(pubKey1.bytes)); assert(hex(utxoPubkeyHash1) == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); @@ -84,7 +84,7 @@ SigningInput buildInputP2PKH(bool omitKey = false) { } TEST(BitcoinSigning, SpendMinimumAmountP2WPKH) { - auto myPrivateKey = PrivateKey(parse_hex("9ea2172511ed73ae0096be8e593c3b75631700edaf729f1abbae607314a20e35")); + auto myPrivateKey = PrivateKey(parse_hex("9ea2172511ed73ae0096be8e593c3b75631700edaf729f1abbae607314a20e35"), TWCurveSECP256k1); auto myPublicKey = myPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(myPublicKey.bytes)); @@ -378,7 +378,7 @@ TEST(BitcoinSigning, SignBRC20TransferCommitV2) { auto forFeeAmount = fullAmount - brcInscribeAmount - minerFee; auto txId = parse_hex("089098890d2653567b9e8df2d1fbe5c3c8bf1910ca7184e301db0ad3b495c88e"); - PrivateKey key(privateKey); + PrivateKey key(privateKey, TWCurveSECP256k1); auto pubKey = key.getPublicKey(TWPublicKeyTypeSECP256k1); TW::BitcoinV2::Proto::SigningInput signing; @@ -474,7 +474,7 @@ TEST(BitcoinSigning, SignPlanTransactionWithDustAmount) { // If the change amount is less than "dust", there should not be a change output. TEST(BitcoinSigning, SignPlanTransactionNoChange) { - const auto myPrivateKey = PrivateKey(parse_hex("9ea2172511ed73ae0096be8e593c3b75631700edaf729f1abbae607314a20e35")); + const auto myPrivateKey = PrivateKey(parse_hex("9ea2172511ed73ae0096be8e593c3b75631700edaf729f1abbae607314a20e35"), TWCurveSECP256k1); auto myPublicKey = myPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(myPublicKey.bytes)); auto redeemScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); @@ -560,7 +560,7 @@ TEST(BitcoinSigning, SignPlanTransactionNoChange) { // Not enough funds to send requested amount after UTXO dust filtering. TEST(BitcoinSigning, SignPlanTransactionNotSufficientAfterDustFiltering) { - const auto myPrivateKey = PrivateKey(parse_hex("9ea2172511ed73ae0096be8e593c3b75631700edaf729f1abbae607314a20e35")); + const auto myPrivateKey = PrivateKey(parse_hex("9ea2172511ed73ae0096be8e593c3b75631700edaf729f1abbae607314a20e35"), TWCurveSECP256k1); auto myPublicKey = myPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(myPublicKey.bytes)); auto redeemScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); @@ -628,7 +628,7 @@ TEST(BitcoinSigning, SignPlanTransactionNotSufficientAfterDustFiltering) { // Deposit 0.0001 BTC from bc1q2sphzvc2uqmxqte2w9dd4gzy4sy9vvfv0me9ke to 0xa8491D40d4F71A752cA41DA0516AEd80c33a1B56 on ZETA mainnet. // https://www.zetachain.com/docs/developers/omnichain/bitcoin/#example-1-deposit-btc-into-an-account-in-zevm TEST(BitcoinSigning, SignDepositBtcToZetaChain) { - const auto myPrivateKey = PrivateKey(parse_hex("428d66be0b5a620f126a00fa67637222ce3dc9badfe5c605189520760810cfac")); + const auto myPrivateKey = PrivateKey(parse_hex("428d66be0b5a620f126a00fa67637222ce3dc9badfe5c605189520760810cfac"), TWCurveSECP256k1); auto myPublicKey = myPrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(myPublicKey.bytes)); auto redeemScript = Script::buildPayToWitnessPublicKeyHash(utxoPubkeyHash); @@ -795,7 +795,7 @@ TEST(BitcoinSigning, SignP2WPKH_Bip143) { input.changeAddress = "16TZ8J6Q5iZKBWizWzFAYnrsaox5Z5aBRV"; const auto hash0 = parse_hex("fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f"); - const auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); + const auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"), TWCurveSECP256k1); const auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_EQ(hex(pubKey0.bytes), "03c9f4836b9a4f77fc0d81f7bcb01b7f1b35916864b9476c241ce9fc198bd25432"); @@ -806,7 +806,7 @@ TEST(BitcoinSigning, SignP2WPKH_Bip143) { input.privateKeys.push_back(utxoKey0); const auto hash1 = parse_hex("ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a"); - const auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + const auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"), TWCurveSECP256k1); const auto pubKey1 = utxoKey1.getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_EQ(hex(pubKey1.bytes), "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357"); const auto utxoPubkeyHash1 = Hash::ripemd(Hash::sha256(pubKey1.bytes)); @@ -882,13 +882,13 @@ SigningInput buildInputP2WPKH(int64_t amount, TWBitcoinSigHashType hashType, int input.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"; input.coinType = TWCoinTypeBitcoin; - auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); + auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"), TWCurveSECP256k1); auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash0 = Hash::ripemd(Hash::sha256(pubKey0.bytes)); assert(hex(utxoPubkeyHash0) == "b7cd046b6d522a3d61dbcb5235c0e9cc97265457"); input.privateKeys.push_back(utxoKey0); - auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"), TWCurveSECP256k1); auto pubKey1 = utxoKey1.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash1 = Hash::ripemd(Hash::sha256(pubKey1.bytes)); assert(hex(utxoPubkeyHash1) == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); @@ -1105,10 +1105,10 @@ SigningInput buildInputP2WSH(enum TWBitcoinSigHashType hashType, bool omitScript input.dustCalculator = std::make_shared(50); if (!omitKeys) { - auto utxoKey0 = PrivateKey(parse_hex("ed00a0841cd53aedf89b0c616742d1d2a930f8ae2b0fb514765a17bb62c7521a")); + auto utxoKey0 = PrivateKey(parse_hex("ed00a0841cd53aedf89b0c616742d1d2a930f8ae2b0fb514765a17bb62c7521a"), TWCurveSECP256k1); input.privateKeys.push_back(utxoKey0); - auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"), TWCurveSECP256k1); input.privateKeys.push_back(utxoKey1); } @@ -1372,7 +1372,7 @@ SigningInput buildInputP2SH_P2WPKH(bool omitScript = false, bool omitKeys = fals input.changeAddress = "1FQc5LdgGHMHEN9nwkjmz6tWkxhPpxBvBU"; input.coinType = TWCoinTypeBitcoin; - auto utxoKey0 = PrivateKey(parse_hex("eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf")); + auto utxoKey0 = PrivateKey(parse_hex("eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf"), TWCurveSECP256k1); auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey0.bytes)); assert(hex(utxoPubkeyHash) == "79091972186c449eb1ded22b78e40d009bdf0089"); @@ -1532,17 +1532,17 @@ TEST(BitcoinSigning, SignP2SH_P2WSH) { input.changeAddress = "1Bd1VA2bnLjoBk4ook3H19tZWETk8s6Ym5"; auto key0 = parse_hex("730fff80e1413068a05b57d6a58261f07551163369787f349438ea38ca80fac6"); - input.privateKeys.push_back(PrivateKey(key0)); + input.privateKeys.push_back(PrivateKey(key0, TWCurveSECP256k1)); auto key1 = parse_hex("11fa3d25a17cbc22b29c44a484ba552b5a53149d106d3d853e22fdd05a2d8bb3"); - input.privateKeys.push_back(PrivateKey(key1)); + input.privateKeys.push_back(PrivateKey(key1, TWCurveSECP256k1)); auto key2 = parse_hex("77bf4141a87d55bdd7f3cd0bdccf6e9e642935fec45f2f30047be7b799120661"); - input.privateKeys.push_back(PrivateKey(key2)); + input.privateKeys.push_back(PrivateKey(key2, TWCurveSECP256k1)); auto key3 = parse_hex("14af36970f5025ea3e8b5542c0f8ebe7763e674838d08808896b63c3351ffe49"); - input.privateKeys.push_back(PrivateKey(key3)); + input.privateKeys.push_back(PrivateKey(key3, TWCurveSECP256k1)); auto key4 = parse_hex("fe9a95c19eef81dde2b95c1284ef39be497d128e2aa46916fb02d552485e0323"); - input.privateKeys.push_back(PrivateKey(key4)); + input.privateKeys.push_back(PrivateKey(key4, TWCurveSECP256k1)); auto key5 = parse_hex("428a7aee9f0c2af0cd19af3cf1c78149951ea528726989b2e83e4778d2c3f890"); - input.privateKeys.push_back(PrivateKey(key5)); + input.privateKeys.push_back(PrivateKey(key5, TWCurveSECP256k1)); auto redeemScript = Script::buildPayToWitnessScriptHash(parse_hex("a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54")); auto scriptHash = Hash::ripemd(Hash::sha256(redeemScript.bytes)); @@ -1657,10 +1657,10 @@ TEST(BitcoinSigning, Sign_NegativeInvalidAddress) { input.toAddress = "THIS-IS-NOT-A-BITCOIN-ADDRESS"; input.changeAddress = "THIS-IS-NOT-A-BITCOIN-ADDRESS-EITHER"; - auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866")); + auto utxoKey0 = PrivateKey(parse_hex("bbc27228ddcb9209d7fd6f36b02f7dfa6252af40bb2f1cbc7a557da8027ff866"), TWCurveSECP256k1); input.privateKeys.push_back(utxoKey0); - auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + auto utxoKey1 = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"), TWCurveSECP256k1); input.privateKeys.push_back(utxoKey1); auto scriptPub1 = Script(parse_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1")); @@ -1738,7 +1738,7 @@ TEST(BitcoinSigning, Plan_10input_MaxAmount) { EXPECT_TRUE(verifyPlan(plan, {1'000'000, 1'010'000, 1'020'000, 1'030'000, 1'040'000, 1'050'000, 1'060'000, 1'070'000, 1'080'000, 1'090'000}, 10'449'278, 722)); // Extend input with keys, reuse plan, Sign - auto privKey = PrivateKey(parse_hex(ownPrivateKey)); + auto privKey = PrivateKey(parse_hex(ownPrivateKey), TWCurveSECP256k1); input.privateKeys.push_back(privKey); input.plan = plan; @@ -1771,7 +1771,7 @@ TEST(BitcoinSigning, Sign_LitecoinReal_a85f) { input.toAddress = "ltc1q0dvup9kzplv6yulzgzzxkge8d35axkq4n45hum"; input.changeAddress = ownAddress; - auto privKey = PrivateKey(parse_hex(ownPrivateKey)); + auto privKey = PrivateKey(parse_hex(ownPrivateKey), TWCurveSECP256k1); input.privateKeys.push_back(privKey); auto utxo0Script = Script::lockScriptForAddress(ownAddress, coin); @@ -1861,7 +1861,7 @@ TEST(BitcoinSigning, PlanAndSign_LitecoinReal_8435) { EXPECT_TRUE(verifyPlan(plan, {3'899'774}, 1'200'000, 141)); // Extend input with keys and plan, for Sign - auto privKey = PrivateKey(parse_hex(ownPrivateKey)); + auto privKey = PrivateKey(parse_hex(ownPrivateKey), TWCurveSECP256k1); input.privateKeys.push_back(privKey); input.plan = plan; @@ -1946,7 +1946,7 @@ TEST(BitcoinSigning, Sign_ManyUtxos_400) { EXPECT_TRUE(verifyPlan(plan, subset, 300'000, 4'561)); // Extend input with keys, reuse plan, Sign - auto privKey = PrivateKey(parse_hex(ownPrivateKey)); + auto privKey = PrivateKey(parse_hex(ownPrivateKey), TWCurveSECP256k1); input.privateKeys.push_back(privKey); input.plan = plan; @@ -2015,7 +2015,7 @@ TEST(BitcoinSigning, Sign_ManyUtxos_2000) { EXPECT_TRUE(verifyPlan(plan, subset, 2'000'000, 40'943)); // Extend input with keys, reuse plan, Sign - auto privKey = PrivateKey(parse_hex(ownPrivateKey)); + auto privKey = PrivateKey(parse_hex(ownPrivateKey), TWCurveSECP256k1); input.privateKeys.push_back(privKey); input.plan = plan; @@ -2075,7 +2075,7 @@ TEST(BitcoinSigning, EncodeThreeOutput) { // add signature - auto privkey = PrivateKey(parse_hex(ownPrivateKey)); + auto privkey = PrivateKey(parse_hex(ownPrivateKey), TWCurveSECP256k1); auto pubkey = PrivateKey(privkey).getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_EQ(hex(pubkey.bytes), "036739829f2cfec79cfe6aaf1c22ecb7d4867dfd8ab4deb7121b36a00ab646caed"); @@ -2123,7 +2123,7 @@ TEST(BitcoinSigning, EncodeThreeOutput) { TEST(BitcoinSigning, RedeemExtendedPubkeyUTXO) { auto wif = "L4BeKzm3AHDUMkxLRVKTSVxkp6Hz9FcMQPh18YCKU1uioXfovzwP"; auto decoded = Base58::decodeCheck(wif); - auto key = PrivateKey(Data(decoded.begin() + 1, decoded.begin() + 33)); + auto key = PrivateKey(Data(decoded.begin() + 1, decoded.begin() + 33), TWCurveSECP256k1); auto pubkey = key.getPublicKey(TWPublicKeyTypeSECP256k1Extended); auto hash = Hash::sha256ripemd(pubkey.bytes.data(), pubkey.bytes.size()); @@ -2191,7 +2191,7 @@ TEST(BitcoinSigning, SignP2TR_5df51e) { input.changeAddress = ownAddress; input.coinType = coin; - auto utxoKey0 = PrivateKey(parse_hex(privateKey)); + auto utxoKey0 = PrivateKey(parse_hex(privateKey), TWCurveSECP256k1); auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_EQ(hex(pubKey0.bytes), "021e582a887bd94d648a9267143eb600449a8d59a0db0653740b1378067a6d0cee"); EXPECT_EQ(SegwitAddress(pubKey0, "bc").string(), ownAddress); @@ -2313,7 +2313,7 @@ TEST(BitcoinSigning, Build_OpReturn_THORChainSwap_eb4c) { } TEST(BitcoinSigning, Sign_OpReturn_THORChainSwap) { - PrivateKey privateKey = PrivateKey(parse_hex("6bd4096fa6f08bd3af2b437244ba0ca2d35045c5233b8d6796df37e61e974de5")); + PrivateKey privateKey = PrivateKey(parse_hex("6bd4096fa6f08bd3af2b437244ba0ca2d35045c5233b8d6796df37e61e974de5"), TWCurveSECP256k1); PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); auto ownAddress = SegwitAddress(publicKey, "bc"); auto ownAddressString = ownAddress.string(); diff --git a/tests/chains/Bitcoin/TransactionCompilerTests.cpp b/tests/chains/Bitcoin/TransactionCompilerTests.cpp index fd1aeb639c6..0c9c0b1af34 100644 --- a/tests/chains/Bitcoin/TransactionCompilerTests.cpp +++ b/tests/chains/Bitcoin/TransactionCompilerTests.cpp @@ -249,9 +249,9 @@ TEST(BitcoinCompiler, CompileWithSignatures) { // 2 private keys are needed (despite >2 UTXOs) auto key0 = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); auto key1 = parse_hex("7878787878787878787878787878787878787878787878787878787878787878"); - EXPECT_EQ(hex(PrivateKey(key0).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + EXPECT_EQ(hex(PrivateKey(key0, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), hex(inPubKey0)); - EXPECT_EQ(hex(PrivateKey(key1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + EXPECT_EQ(hex(PrivateKey(key1, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), hex(inPubKey1)); *input.add_private_key() = std::string(key0.begin(), key0.end()); *input.add_private_key() = std::string(key1.begin(), key1.end()); @@ -299,7 +299,7 @@ TEST(BitcoinCompiler, CompileWithSignaturesV2) { Bitcoin::Proto::SigningInput inputLegacy; auto& input = *inputLegacy.mutable_signing_v2(); - const PrivateKey alicePrivateKey(parse_hex("56429688a1a6b00b90ccd22a0de0a376b6569d8684022ae92229a28478bfb657")); + const PrivateKey alicePrivateKey(parse_hex("56429688a1a6b00b90ccd22a0de0a376b6569d8684022ae92229a28478bfb657"), TWCurveSECP256k1); const auto alicePublicKey = alicePrivateKey.getPublicKey(TWPublicKeyTypeSECP256k1); const auto bobPublicKey = parse_hex("037ed9a436e11ec4947ac4b7823787e24ba73180f1edd2857bff19c9f4d62b65bf"); @@ -351,7 +351,7 @@ TEST(BitcoinCompiler, CompileWithSignaturesV2) { // Step 3: Simulate signature, normally obtained from signature server - const auto sig0 = alicePrivateKey.sign(data(sighash0.sighash()), TWCurveSECP256k1); + const auto sig0 = alicePrivateKey.sign(data(sighash0.sighash())); EXPECT_EQ(hex(sig0), "78eda020d4b86fcb3af78ef919912e6d79b81164dbbb0b0b96da6ac58a2de4b11a5fd8d48734d5a02371c4b5ee551a69dca3842edbf577d863cf8ae9fdbbd45900"); // Step 4: Compile transaction info diff --git a/tests/chains/Bitcoin/TxComparisonHelper.cpp b/tests/chains/Bitcoin/TxComparisonHelper.cpp index f674bcaaff7..3996815dd7c 100644 --- a/tests/chains/Bitcoin/TxComparisonHelper.cpp +++ b/tests/chains/Bitcoin/TxComparisonHelper.cpp @@ -48,7 +48,7 @@ SigningInput buildSigningInput(Amount amount, int byteFee, const UTXOs& utxos, b input.dustCalculator = std::make_shared(coin); if (!omitPrivateKey) { - auto utxoKey = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9")); + auto utxoKey = PrivateKey(parse_hex("619c335025c7f4012e556c2a58b2506e30b8511b53ade95ea316fd8c3286feb9"), TWCurveSECP256k1); auto pubKey = utxoKey.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey.bytes)); assert(hex(utxoPubkeyHash) == "1d0f172a0ecb48aee1be1f2687d2963ae33f71a1"); diff --git a/tests/chains/BitcoinCash/TWBitcoinCashTests.cpp b/tests/chains/BitcoinCash/TWBitcoinCashTests.cpp index d29e118c648..c2afdd8be9d 100644 --- a/tests/chains/BitcoinCash/TWBitcoinCashTests.cpp +++ b/tests/chains/BitcoinCash/TWBitcoinCashTests.cpp @@ -60,7 +60,7 @@ TEST(BitcoinCash, InvalidAddress) { } TEST(BitcoinCash, LegacyToCashAddr) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("28071bf4e2b0340db41b807ed8a5514139e5d6427ff9d58dbd22b7ed187103a4").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("28071bf4e2b0340db41b807ed8a5514139e5d6427ff9d58dbd22b7ed187103a4").get(), TWCoinTypeCurve(TWCoinTypeBitcoinCash))); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), 0)); auto addressString = WRAPS(TWBitcoinAddressDescription(address.get())); diff --git a/tests/chains/BitcoinDiamond/SignerTests.cpp b/tests/chains/BitcoinDiamond/SignerTests.cpp index 2b0a239d726..bf0916d7617 100644 --- a/tests/chains/BitcoinDiamond/SignerTests.cpp +++ b/tests/chains/BitcoinDiamond/SignerTests.cpp @@ -45,7 +45,7 @@ TEST(BitcoinDiamondSigner, Sign) { utxo0->set_amount(27615); auto utxoKey0 = - PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")); + PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee"), TWCoinTypeCurve(TWCoinTypeBitcoinDiamond)); auto utxoAddr0 = TW::deriveAddress(TWCoinTypeBitcoinDiamond, utxoKey0); ASSERT_EQ(utxoAddr0, "1G15VvshDxwFTnahZZECJfFwEkq9fP79o8"); auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeBitcoinDiamond); @@ -102,7 +102,7 @@ TEST(BitcoinDiamondSigner, SignSegwit) { utxo0->set_amount(27615); auto utxoKey0 = - PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")); + PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee"), TWCoinTypeCurve(TWCoinTypeBitcoinDiamond)); auto utxoAddr0 = TW::deriveAddress(TWCoinTypeBitcoinDiamond, utxoKey0, TWDerivationBitcoinSegwit); ASSERT_EQ(utxoAddr0, "bcd1q5jx6gcuxeefvejk30r0fqrr37psnpscslrrd0y"); @@ -160,7 +160,7 @@ TEST(BitcoinDiamondSigner, SignAnyoneCanPay) { utxo0->set_amount(27615); auto utxoKey0 = - PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")); + PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee"), TWCoinTypeCurve(TWCoinTypeBitcoinDiamond)); auto utxoAddr0 = TW::deriveAddress(TWCoinTypeBitcoinDiamond, utxoKey0, TWDerivationBitcoinSegwit); ASSERT_EQ(utxoAddr0, "bcd1q5jx6gcuxeefvejk30r0fqrr37psnpscslrrd0y"); diff --git a/tests/chains/BitcoinDiamond/TWAnySignerTests.cpp b/tests/chains/BitcoinDiamond/TWAnySignerTests.cpp index f1ab62557f2..f71410173e6 100644 --- a/tests/chains/BitcoinDiamond/TWAnySignerTests.cpp +++ b/tests/chains/BitcoinDiamond/TWAnySignerTests.cpp @@ -45,7 +45,7 @@ TEST(TWAnySignerBitcoinDiamond, Sign) { auto script0 = parse_hex("76a914a48da46386ce52cccad178de900c71f06130c31088ac"); utxo0->set_script(script0.data(), (int)script0.size()); - auto utxoKey0 = PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee")); + auto utxoKey0 = PrivateKey(parse_hex("d2b9f2846d3adcead910ee0124a3ba7ae29e8a4729787d27f9bea1f532928eee"), TWCoinTypeCurve(TWCoinTypeBitcoinDiamond)); input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); Bitcoin::Proto::TransactionPlan plan; diff --git a/tests/chains/BitcoinGold/TWAddressTests.cpp b/tests/chains/BitcoinGold/TWAddressTests.cpp index ec7372aee34..67d006b5b63 100644 --- a/tests/chains/BitcoinGold/TWAddressTests.cpp +++ b/tests/chains/BitcoinGold/TWAddressTests.cpp @@ -24,7 +24,7 @@ TEST(TWBitcoinGoldAddress, Valid) { } TEST(TWBitcoinGoldAddress, PubkeyToAddress) { - const auto privateKey = PrivateKey(parse_hex(PRIVATE_KEY)); + const auto privateKey = PrivateKey(parse_hex(PRIVATE_KEY), TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); /// construct with public key diff --git a/tests/chains/Cardano/AddressTests.cpp b/tests/chains/Cardano/AddressTests.cpp index c42ea1b5402..d2abeb123bb 100644 --- a/tests/chains/Cardano/AddressTests.cpp +++ b/tests/chains/Cardano/AddressTests.cpp @@ -324,7 +324,7 @@ TEST(CardanoAddress, FromPrivateKeyV3) { parse_hex("d809b1b4b4c74734037f76aace501730a3fe2fca30b5102df99ad3f7c0103e48"), parse_hex("d54cde47e9041b31f3e6873d700d83f7a937bea746dadfa2c5b0a6a92502356c"), parse_hex("69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000"), - dummyKey, dummyKey, dummyKey); + dummyKey, dummyKey, dummyKey, TWCurveED25519); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); EXPECT_ANY_THROW(new AddressV3(publicKey)); } @@ -409,7 +409,7 @@ TEST(CardanoAddress, FromPrivateKeyV2) { parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), - dummyKey, dummyKey, dummyKey + dummyKey, dummyKey, dummyKey, TWCurveED25519ExtendedCardano ); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); ASSERT_EQ(hex(publicKey.bytes), @@ -426,7 +426,7 @@ TEST(CardanoAddress, FromPrivateKeyV2) { parse_hex("a089c9423100960440ccd5b7adbd202d1ab1993a7bb30fc88b287d94016df247"), parse_hex("da86a87f08fb15de1431a6c0ccd5ebf51c3bee81f7eaf714801bbbe4d903154a"), parse_hex("e513fa1290da1d22e83a41f17eed72d4489483b561fff36b9555ffdb91c430e2"), - dummyKey, dummyKey, dummyKey + dummyKey, dummyKey, dummyKey, TWCurveED25519ExtendedCardano ); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); ASSERT_EQ(hex(publicKey.bytes), @@ -443,7 +443,7 @@ TEST(CardanoAddress, FromPrivateKeyV2) { parse_hex("d809b1b4b4c74734037f76aace501730a3fe2fca30b5102df99ad3f7c0103e48"), parse_hex("d54cde47e9041b31f3e6873d700d83f7a937bea746dadfa2c5b0a6a92502356c"), parse_hex("69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000"), - dummyKey, dummyKey, dummyKey + dummyKey, dummyKey, dummyKey, TWCurveED25519ExtendedCardano ); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); ASSERT_EQ(hex(publicKey.bytes), @@ -460,7 +460,7 @@ TEST(CardanoAddress, FromPrivateKeyV2) { parse_hex("d809b1b4b4c74734037f76aace501730a3fe2fca30b5102df99ad3f7c0103e48"), parse_hex("d54cde47e9041b31f3e6873d700d83f7a937bea746dadfa2c5b0a6a92502356c"), parse_hex("69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000"), - dummyKey, dummyKey, dummyKey); + dummyKey, dummyKey, dummyKey, TWCurveED25519); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); EXPECT_ANY_THROW(new AddressV2(publicKey)); } @@ -472,14 +472,14 @@ TEST(CardanoAddress, PrivateKeyExtended) { parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), parse_hex("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff"), parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), - dummyKey, dummyKey, dummyKey + dummyKey, dummyKey, dummyKey, TWCurveED25519ExtendedCardano ); auto publicKeyExt = privateKeyExt.getPublicKey(TWPublicKeyTypeED25519Cardano); ASSERT_EQ(128ul, publicKeyExt.bytes.size()); // Non-extended: both are 32 bytes. auto privateKeyNonext = PrivateKey( - parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744")); + parse_hex("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744"), TWCurveED25519); auto publicKeyNonext = privateKeyNonext.getPublicKey(TWPublicKeyTypeED25519); ASSERT_EQ(32ul, publicKeyNonext.bytes.size()); } diff --git a/tests/chains/Cardano/SigningTests.cpp b/tests/chains/Cardano/SigningTests.cpp index 83be54b7926..6856feec9bf 100644 --- a/tests/chains/Cardano/SigningTests.cpp +++ b/tests/chains/Cardano/SigningTests.cpp @@ -143,7 +143,7 @@ TEST(CardanoSigning, SendNft) { utxo3->set_address(fromAddress); utxo3->set_amount(2000000); - PrivateKey privKey(parse_hex(fromAddressPrivKey)); + PrivateKey privKey(parse_hex(fromAddressPrivKey), TWCurveED25519ExtendedCardano); input.add_private_key(privKey.bytes.data(), privKey.bytes.size()); // Set an output info. @@ -615,7 +615,7 @@ TEST(CardanoSigning, SignTransferFromLegacy) { const auto privateKeyData = parse_hex("98f266d1aac660179bc2f456033941238ee6b2beb8ed0f9f34c9902816781f5a9903d1d395d6ab887b65ea5e344ef09b449507c21a75f0ce8c59d0ed1c6764eba7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f4e890ca4eb6bec44bf751b5a843174534af64d6ad1f44e0613db78a7018781f5aa151d2997f52059466b715d8eefab30a78b874ae6ef4931fa58bb21ef8ce2423d46f19d0fbf75afb0b9a24e31d533f4fd74cee3b56e162568e8defe37123afc4"); { - const auto privKey = PrivateKey(privateKeyData); + const auto privKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano); const auto pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519Cardano); const auto addr = AddressV2(pubKey); EXPECT_EQ(addr.string(), "Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8"); @@ -1012,7 +1012,9 @@ TEST(CardanoSigning, SignMessageWithKey) { "69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000" "1111111111111111111111111111111111111111111111111111111111111111" "1111111111111111111111111111111111111111111111111111111111111111" - "1111111111111111111111111111111111111111111111111111111111111111")); + "1111111111111111111111111111111111111111111111111111111111111111"), + TWCurveED25519ExtendedCardano + ); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); EXPECT_EQ(hex(publicKey.bytes), @@ -1024,7 +1026,7 @@ TEST(CardanoSigning, SignMessageWithKey) { const auto sampleMessageStr = "Hello world"; const auto sampleMessage = data(sampleMessageStr); - const auto signature = privateKey.sign(sampleMessage, TWCurveED25519ExtendedCardano); + const auto signature = privateKey.sign(sampleMessage); const auto sampleRightSignature = "1096ddcfb2ad21a4c0d861ef3fabe18841e8de88105b0d8e36430d7992c588634ead4100c32b2800b31b65e014d54a8238bdda63118d829bf0bcf1b631e86f0e"; EXPECT_EQ(hex(signature), sampleRightSignature); diff --git a/tests/chains/Cardano/StakingTests.cpp b/tests/chains/Cardano/StakingTests.cpp index 45e9e7d13f9..1fed5d1ff81 100644 --- a/tests/chains/Cardano/StakingTests.cpp +++ b/tests/chains/Cardano/StakingTests.cpp @@ -29,7 +29,7 @@ const auto poolIdNufi = "7d7ac07a2f2a25b7a4db868a40720621c4939cf6aefbb9a11464f1a TEST(CardanoStaking, RegisterStakingKey) { const auto privateKeyData = parse_hex(privateKeyTest1); - const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).getPublicKey(TWPublicKeyTypeED25519Cardano); const auto ownAddress = AddressV3(publicKey).string(); EXPECT_EQ(ownAddress, ownAddress1); const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); @@ -76,7 +76,7 @@ TEST(CardanoStaking, RegisterStakingKey) { TEST(CardanoStaking, DeregisterStakingKey) { const auto privateKeyData = parse_hex(privateKeyTest1); - const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).getPublicKey(TWPublicKeyTypeED25519Cardano); const auto ownAddress = AddressV3(publicKey).string(); EXPECT_EQ(ownAddress, ownAddress1); const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); @@ -123,7 +123,7 @@ TEST(CardanoStaking, DeregisterStakingKey) { TEST(CardanoStaking, Redelegate) { const auto privateKeyData = parse_hex(privateKeyTest1); - const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).getPublicKey(TWPublicKeyTypeED25519Cardano); const auto ownAddress = AddressV3(publicKey).string(); EXPECT_EQ(ownAddress, ownAddress1); const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); @@ -171,7 +171,7 @@ TEST(CardanoStaking, Redelegate) { TEST(CardanoStaking, RegisterAndDelegate_similar53339b) { const auto privateKeyData = parse_hex(privateKeyTest1); - const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).getPublicKey(TWPublicKeyTypeED25519Cardano); const auto ownAddress = AddressV3(publicKey).string(); EXPECT_EQ(ownAddress, ownAddress1); const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); @@ -265,7 +265,7 @@ TEST(CardanoStaking, RegisterAndDelegate_similar53339b) { TEST(CardanoStaking, Withdraw_similarf48098) { const auto privateKeyData = parse_hex(privateKeyTest1); - const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).getPublicKey(TWPublicKeyTypeED25519Cardano); const auto ownAddress = AddressV3(publicKey).string(); EXPECT_EQ(ownAddress, ownAddress1); const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); diff --git a/tests/chains/Cardano/TWCardanoAddressTests.cpp b/tests/chains/Cardano/TWCardanoAddressTests.cpp index d18caf17f49..5ff83f710c3 100644 --- a/tests/chains/Cardano/TWCardanoAddressTests.cpp +++ b/tests/chains/Cardano/TWCardanoAddressTests.cpp @@ -19,7 +19,7 @@ TEST(TWCardano, AddressFromPublicKey) { auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA( "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" "639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7bed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a" - ).get())); + ).get(), TWCoinTypeCurve(TWCoinTypeCardano))); ASSERT_NE(nullptr, privateKey.get()); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519Cardano(privateKey.get())); ASSERT_NE(nullptr, publicKey.get()); diff --git a/tests/chains/Cardano/VoteDelegationTests.cpp b/tests/chains/Cardano/VoteDelegationTests.cpp index 74700e2cb53..4befdedf809 100644 --- a/tests/chains/Cardano/VoteDelegationTests.cpp +++ b/tests/chains/Cardano/VoteDelegationTests.cpp @@ -25,7 +25,7 @@ const auto dRepAddressCIP105 = "drep13d6sxkyz6st9h65qqrzd8ukpywhr8swe9f6357qntgj TEST(CardanoVoteDelegation, DelegateToSpecificDRepCIP129) { const auto privateKeyData = parse_hex(privateKeyTest1); - const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).getPublicKey(TWPublicKeyTypeED25519Cardano); const auto ownAddress = AddressV3(publicKey).string(); EXPECT_EQ(ownAddress, ownAddress1); const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); @@ -82,7 +82,7 @@ TEST(CardanoVoteDelegation, DelegateToSpecificDRepCIP129) { TEST(CardanoVoteDelegation, DelegateToSpecificDRepCIP105) { const auto privateKeyData = parse_hex(privateKeyTest1); - const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).getPublicKey(TWPublicKeyTypeED25519Cardano); const auto ownAddress = AddressV3(publicKey).string(); EXPECT_EQ(ownAddress, ownAddress1); const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); @@ -139,7 +139,7 @@ TEST(CardanoVoteDelegation, DelegateToSpecificDRepCIP105) { TEST(CardanoVoteDelegation, DelegateToAlwaysAbstain) { const auto privateKeyData = parse_hex(privateKeyTest1); - const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).getPublicKey(TWPublicKeyTypeED25519Cardano); const auto ownAddress = AddressV3(publicKey).string(); EXPECT_EQ(ownAddress, ownAddress1); const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); @@ -195,7 +195,7 @@ TEST(CardanoVoteDelegation, DelegateToAlwaysAbstain) { TEST(CardanoVoteDelegation, DelegateToNoConfidence) { const auto privateKeyData = parse_hex(privateKeyTest1); - const auto publicKey = PrivateKey(privateKeyData).getPublicKey(TWPublicKeyTypeED25519Cardano); + const auto publicKey = PrivateKey(privateKeyData, TWCurveED25519ExtendedCardano).getPublicKey(TWPublicKeyTypeED25519Cardano); const auto ownAddress = AddressV3(publicKey).string(); EXPECT_EQ(ownAddress, ownAddress1); const auto stakingAddress = AddressV3(publicKey).getStakingAddress(); diff --git a/tests/chains/Cosmos/AddressTests.cpp b/tests/chains/Cosmos/AddressTests.cpp index 97034a9b590..87032b6ec58 100644 --- a/tests/chains/Cosmos/AddressTests.cpp +++ b/tests/chains/Cosmos/AddressTests.cpp @@ -25,7 +25,7 @@ TEST(CosmosAddress, Invalid) { } TEST(CosmosAddress, Cosmos_FromPublicKey) { - auto privateKey = PrivateKey(parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005")); + auto privateKey = PrivateKey(parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"), TWCurveSECP256k1); auto publicKeyData = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ(hex(publicKeyData.bytes), "0257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc5"); @@ -56,7 +56,7 @@ TEST(CosmosAddress, Cosmos_Invalid) { } TEST(CosmosAddress, ThorFromPublicKey) { - auto privateKey = PrivateKey(parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e")); + auto privateKey = PrivateKey(parse_hex("7105512f0c020a1dd759e14b865ec0125f59ac31e34d7a2807a228ed50cb343e"), TWCurveSECP256k1); auto publicKeyData = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ(hex(publicKeyData.bytes), "03ed997e396cf4292f5fce5a42bba41599ccd5d96e313154a7c9ea7049de317c77"); diff --git a/tests/chains/Cosmos/CosmosTestHelpers.h b/tests/chains/Cosmos/CosmosTestHelpers.h index 50e81c4459f..650d9884289 100644 --- a/tests/chains/Cosmos/CosmosTestHelpers.h +++ b/tests/chains/Cosmos/CosmosTestHelpers.h @@ -68,7 +68,7 @@ namespace TW::Cosmos::tests::internal { } static inline void testCreateFromPrivKey(const CosmosAddressParameters& addressParameters) { - auto privateKey = PrivateKey(parse_hex(addressParameters.privKey)); + auto privateKey = PrivateKey(parse_hex(addressParameters.privKey), TWCoinTypeCurve(addressParameters.coinType)); auto address = Address(addressParameters.coinType, privateKey.getPublicKey(addressParameters.publicKeyType)); ASSERT_EQ(address.string(), addressParameters.address); } diff --git a/tests/chains/Cosmos/NativeInjective/TransactionCompilerTests.cpp b/tests/chains/Cosmos/NativeInjective/TransactionCompilerTests.cpp index 2bf1a12785a..c71cc623d9a 100644 --- a/tests/chains/Cosmos/NativeInjective/TransactionCompilerTests.cpp +++ b/tests/chains/Cosmos/NativeInjective/TransactionCompilerTests.cpp @@ -19,7 +19,7 @@ TEST(NativeInjectiveCompiler, CompileWithSignatures) { TW::Cosmos::Proto::SigningInput input; PrivateKey privateKey = - PrivateKey(parse_hex("727513ec3c54eb6fae24f2ff756bbc4c89b82945c6538bbd173613ae3de719d3")); + PrivateKey(parse_hex("727513ec3c54eb6fae24f2ff756bbc4c89b82945c6538bbd173613ae3de719d3"), TWCoinTypeCurve(coin)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); /// Step 1: Prepare transaction input (protobuf) diff --git a/tests/chains/Cosmos/SignerTests.cpp b/tests/chains/Cosmos/SignerTests.cpp index 6e5570004d4..57f39e57293 100644 --- a/tests/chains/Cosmos/SignerTests.cpp +++ b/tests/chains/Cosmos/SignerTests.cpp @@ -240,7 +240,7 @@ TEST(CosmosSigner, SignIbcTransferProtobuf_817101) { amountOfFee->set_amount("12500"); auto privateKey = parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af"); - EXPECT_EQ(Cosmos::Address(TWCoinTypeCosmos, PrivateKey(privateKey).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx"); + EXPECT_EQ(Cosmos::Address(TWCoinTypeCosmos, PrivateKey(privateKey, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx"); input.set_private_key(privateKey.data(), privateKey.size()); auto output = Proto::SigningOutput(); diff --git a/tests/chains/Cosmos/THORChain/SwapTests.cpp b/tests/chains/Cosmos/THORChain/SwapTests.cpp index 44b11f6d686..b0f8f9d9d83 100644 --- a/tests/chains/Cosmos/THORChain/SwapTests.cpp +++ b/tests/chains/Cosmos/THORChain/SwapTests.cpp @@ -90,7 +90,7 @@ TEST(THORChainSwap, SwapBtcEth) { // set few fields before signing tx.set_byte_fee(20); - EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); + EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); tx.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); auto& utxo = *tx.add_utxo(); Data utxoHash = parse_hex("1234000000000000000000000000000000000000000000000000000000005678"); @@ -350,7 +350,7 @@ TEST(THORChainSwap, SwapBtcBnb) { // set few fields before signing tx.set_byte_fee(80); - EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); + EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); tx.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); auto& utxo = *tx.add_utxo(); Data utxoHash = parse_hex("8eae5c3a4c75058d4e3facd5d72f18a40672bcd3d1f35ebf3094bd6c78da48eb"); @@ -801,7 +801,7 @@ TEST(THORChainSwap, SwapBnbEth) { EXPECT_EQ(hex(TW::data(tx.private_key())), ""); // set private key and few other fields - EXPECT_EQ(TW::deriveAddress(TWCoinTypeBinance, PrivateKey(TestKey1Bnb)), Address1Bnb); + EXPECT_EQ(TW::deriveAddress(TWCoinTypeBinance, PrivateKey(TestKey1Bnb, TWCoinTypeCurve(TWCoinTypeBinance))), Address1Bnb); tx.set_private_key(TestKey1Bnb.data(), TestKey1Bnb.size()); tx.set_chain_id("Binance-Chain-Tigris"); tx.set_account_number(1902570); @@ -849,7 +849,7 @@ TEST(THORChainSwap, SwapBnbRune) { EXPECT_EQ(hex(TW::data(tx.private_key())), ""); // set private key and few other fields - EXPECT_EQ(TW::deriveAddress(TWCoinTypeBinance, PrivateKey(TestKey1Bnb)), Address1Bnb); + EXPECT_EQ(TW::deriveAddress(TWCoinTypeBinance, PrivateKey(TestKey1Bnb, TWCoinTypeCurve(TWCoinTypeBinance))), Address1Bnb); tx.set_private_key(TestKey1Bnb.data(), TestKey1Bnb.size()); tx.set_chain_id("Binance-Chain-Tigris"); tx.set_account_number(1902570); @@ -901,7 +901,7 @@ TEST(THORChainSwap, SwapBusdTokenBnb) { // set private key and few other fields const Data privateKey = parse_hex("412c379cccf9d792238f0a8bd923604e00c2be11ea1de715945f6a849796362a"); - EXPECT_EQ(Binance::Address(PrivateKey(privateKey).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "bnb1gddl87crh47wzynjx3c6pmcclzk7txlkm74x28"); + EXPECT_EQ(Binance::Address(PrivateKey(privateKey, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "bnb1gddl87crh47wzynjx3c6pmcclzk7txlkm74x28"); tx.set_private_key(privateKey.data(), privateKey.size()); tx.set_chain_id("Binance-Chain-Tigris"); tx.set_account_number(7320332); @@ -951,7 +951,7 @@ TEST(THORChainSwap, SwapBnbBnbToken) { // set private key and few other fields const Data privateKey = parse_hex("bcf8b072560dda05122c99390def2c385ec400e1a93df0657a85cf6b57a715da"); - EXPECT_EQ(Binance::Address(PrivateKey(privateKey).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx"); + EXPECT_EQ(Binance::Address(PrivateKey(privateKey, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1)).string(), "bnb1us47wdhfx08ch97zdueh3x3u5murfrx30jecrx"); tx.set_private_key(privateKey.data(), privateKey.size()); tx.set_chain_id("Binance-Chain-Tigris"); tx.set_account_number(1902570); @@ -1004,7 +1004,7 @@ TEST(THORChainSwap, SwapBtcEthWithAffFee) { // set few fields before signing tx.set_byte_fee(20); - EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); + EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); tx.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); auto& utxo = *tx.add_utxo(); Data utxoHash = parse_hex("1234000000000000000000000000000000000000000000000000000000005678"); @@ -1122,7 +1122,7 @@ TEST(THORChainSwap, SwapBtcNegativeMemoTooLong) { // set few fields before signing tx.set_byte_fee(20); - EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); + EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); tx.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); auto& utxo = *tx.add_utxo(); Data utxoHash = parse_hex("1234000000000000000000000000000000000000000000000000000000005678"); diff --git a/tests/chains/Cosmos/THORChain/TWSwapTests.cpp b/tests/chains/Cosmos/THORChain/TWSwapTests.cpp index 9492770c08f..1aabc6655f9 100644 --- a/tests/chains/Cosmos/THORChain/TWSwapTests.cpp +++ b/tests/chains/Cosmos/THORChain/TWSwapTests.cpp @@ -81,7 +81,7 @@ TEST(TWTHORChainSwap, SwapBtcToEth) { // sign tx input for signed full tx // set few fields before signing txInput.set_byte_fee(20); - EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); + EXPECT_EQ(Bitcoin::SegwitAddress(PrivateKey(TestKey1Btc, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1), "bc").string(), Address1Btc); txInput.add_private_key(TestKey1Btc.data(), TestKey1Btc.size()); auto& utxo = *txInput.add_utxo(); Data utxoHash = parse_hex("1234000000000000000000000000000000000000000000000000000000005678"); diff --git a/tests/chains/Cosmos/TransactionCompilerTests.cpp b/tests/chains/Cosmos/TransactionCompilerTests.cpp index e55e966a13f..f70e77a427a 100644 --- a/tests/chains/Cosmos/TransactionCompilerTests.cpp +++ b/tests/chains/Cosmos/TransactionCompilerTests.cpp @@ -25,7 +25,7 @@ TEST(CosmosCompiler, CompileWithSignatures) { TW::Cosmos::Proto::SigningInput input; PrivateKey privateKey = - PrivateKey(parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af")); + PrivateKey(parse_hex("8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af"), TWCoinTypeCurve(coin)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); /// Step 1: Prepare transaction input (protobuf) diff --git a/tests/chains/Decred/AddressTests.cpp b/tests/chains/Decred/AddressTests.cpp index 51c855ce811..0d8884dbee3 100644 --- a/tests/chains/Decred/AddressTests.cpp +++ b/tests/chains/Decred/AddressTests.cpp @@ -19,7 +19,7 @@ TEST(DecredAddress, FromPublicKey) { ASSERT_EQ(address.string(), "DsmcYVbP1Nmag2H4AS17UTvmWXmGeA7nLDx"); } { - const auto privateKey = PrivateKey(parse_hex("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a")); + const auto privateKey = PrivateKey(parse_hex("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a"), TWCurveED25519); const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeED25519)); EXPECT_ANY_THROW(new Address(publicKey)); } diff --git a/tests/chains/Decred/SignerTests.cpp b/tests/chains/Decred/SignerTests.cpp index e698910b17c..5e34583badf 100644 --- a/tests/chains/Decred/SignerTests.cpp +++ b/tests/chains/Decred/SignerTests.cpp @@ -19,7 +19,7 @@ namespace TW::Decred::tests { // clang-format off TEST(DecredSigner, SignP2PKH) { - const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"), TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); @@ -121,7 +121,7 @@ TEST(DecredSigner, SignP2PKH) { } TEST(DecredSigner, SignP2PK) { - const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"), TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); @@ -203,7 +203,7 @@ TEST(DecredSigner, SignP2PK) { } TEST(DecredSigner, SignP2SH) { - const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"), TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); @@ -309,7 +309,7 @@ TEST(DecredSigner, SignP2SH) { } TEST(DecredSigner, SignNegativeNoUtxo) { - const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"), TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); @@ -364,7 +364,7 @@ TEST(DecredSigner, SignNegativeNoUtxo) { } TEST(DecredSigner, SignP2PKH_NoPlan) { - const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c")); + const auto privateKey = PrivateKey(parse_hex("22a47fa09a223f2aa079edf85a7c2d4f8720ee63e502ee2869afab7de234b80c"), TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); const auto keyhash = Hash::ripemd(Hash::blake256(publicKey.bytes)); diff --git a/tests/chains/Decred/TransactionCompilerTests.cpp b/tests/chains/Decred/TransactionCompilerTests.cpp index 23d49b69bf1..1c9921bebc2 100644 --- a/tests/chains/Decred/TransactionCompilerTests.cpp +++ b/tests/chains/Decred/TransactionCompilerTests.cpp @@ -78,7 +78,7 @@ TEST(DecredCompiler, CompileWithSignatures) { "9e4305478d1a69ee5c89a2e234d1cf270798d447d5db983b8fc3c817afddec34"); // compile - auto publicKey = PrivateKey(utxoKey).getPublicKey(TWPublicKeyTypeSECP256k1); + auto publicKey = PrivateKey(utxoKey, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1); auto signature = parse_hex("304402206ee887c9239e5fff0048674bdfff2a8cfbeec6cd4a3ccebcc12fac44b24cc5ac0220718f7c760818fde18bc5ba8457d43d5a145cc4cf13d2a5557cba9107e9f4558d"); auto outputData = TransactionCompiler::compileWithSignatures(coin, txInputData, {signature}, {publicKey.bytes}); diff --git a/tests/chains/ECash/TWECashTests.cpp b/tests/chains/ECash/TWECashTests.cpp index bd9f5738b3b..1ca9d6ff98e 100644 --- a/tests/chains/ECash/TWECashTests.cpp +++ b/tests/chains/ECash/TWECashTests.cpp @@ -57,7 +57,7 @@ TEST(ECash, InvalidAddress) { } TEST(ECash, LegacyToECashAddr) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("28071bf4e2b0340db41b807ed8a5514139e5d6427ff9d58dbd22b7ed187103a4").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("28071bf4e2b0340db41b807ed8a5514139e5d6427ff9d58dbd22b7ed187103a4").get(), TWCoinTypeCurve(TWCoinTypeECash))); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), 0)); auto addressString = WRAPS(TWBitcoinAddressDescription(address.get())); diff --git a/tests/chains/EOS/AddressTests.cpp b/tests/chains/EOS/AddressTests.cpp index 28aed7426a4..7bb2594660a 100644 --- a/tests/chains/EOS/AddressTests.cpp +++ b/tests/chains/EOS/AddressTests.cpp @@ -53,7 +53,7 @@ TEST(EOSAddress, FromPrivateKey) { "PUB_K1_6enPVMggisfqVVRZ1tj47d9UeHK46CBssoCmAz6sLDMBdtZk78"}; for (int i = 0; i < 4; i++) { - const auto privateKey = PrivateKey(parse_hex(privArray[i])); + const auto privateKey = PrivateKey(parse_hex(privArray[i]), privTypes[i] == Type::ModernR1 ? TWCurveNIST256p1 : TWCurveSECP256k1); const auto publicKey = PublicKey(privateKey.getPublicKey(privTypes[i] == Type::ModernR1 ? TWPublicKeyTypeNIST256p1 : TWPublicKeyTypeSECP256k1)); const auto address = Address(publicKey, privTypes[i]); diff --git a/tests/chains/EOS/TransactionCompilerTests.cpp b/tests/chains/EOS/TransactionCompilerTests.cpp index bb4bb23f72b..be5b2233d36 100644 --- a/tests/chains/EOS/TransactionCompilerTests.cpp +++ b/tests/chains/EOS/TransactionCompilerTests.cpp @@ -67,7 +67,7 @@ TEST(EOSCompiler, CompileWithSignatures) { EXPECT_EQ(hex(preImageHash), "14fc3299ee3e1113096bf1869dfa14c04a7ffdedd8ebdabf530683e4cfcd726c"); // Simulate signature, normally obtained from signature server - const PublicKey publicKey = PrivateKey(key).getPublicKey(TWPublicKeyTypeNIST256p1); + const PublicKey publicKey = PrivateKey(key, TWCurveNIST256p1).getPublicKey(TWPublicKeyTypeNIST256p1); const auto signature = parse_hex("1f6c4efceb5a6dadab271fd7e2153d97d22690938475b23f017cf9ec29e20d25725e90e541e130daa83c38fc4c933725f05837422c3f4a51f8c1d07208c8fd5e0b"); // data("SIG_K1_K9RdLC7DEDWjTfR64GU8BtDHcAjzR1ntcT651JMcfHNTpdsvDrUwfyzF1FkvL9fxEi2UCtGJZ9zYoNbJoMF1fbU64cRiJ7"); /// Step 3: Compile transaction info diff --git a/tests/chains/EOS/TransactionTests.cpp b/tests/chains/EOS/TransactionTests.cpp index 013db25ed5a..2f3bc7a350a 100644 --- a/tests/chains/EOS/TransactionTests.cpp +++ b/tests/chains/EOS/TransactionTests.cpp @@ -55,7 +55,7 @@ TEST(EOSTransaction, Serialization) { // make transaction invalid and see if signing succeeds tx.maxNetUsageWords = UINT32_MAX; - ASSERT_THROW(signer.sign(PrivateKey(Hash::sha256(std::string("A"))), Type::ModernK1, tx), std::invalid_argument); + ASSERT_THROW(signer.sign(PrivateKey(Hash::sha256(std::string("A")), TWCurveSECP256k1), Type::ModernK1, tx), std::invalid_argument); referenceBlockId = parse_hex("000067d6f6a7e7799a1f3d487439a679f8cf95f1c986f35c0d2fa320f51a7144"); referenceBlockTime = 1554209118; @@ -77,7 +77,7 @@ TEST(EOSTransaction, Serialization) { // verify k1 sigs for (int i = 0; i < 5; i++) { - PrivateKey pk(Hash::sha256(std::string(i + 1, 'A'))); + PrivateKey pk(Hash::sha256(std::string(i + 1, 'A')), TWCurveSECP256k1); ASSERT_NO_THROW(signer.sign(pk, Type::ModernK1, tx2)); ASSERT_EQ( @@ -87,7 +87,7 @@ TEST(EOSTransaction, Serialization) { // verify r1 sigs for (int i = 0; i < 5; i++) { - PrivateKey pk(Hash::sha256(std::string(i + 1, 'A'))); + PrivateKey pk(Hash::sha256(std::string(i + 1, 'A')), TWCurveNIST256p1); ASSERT_NO_THROW(signer.sign(pk, Type::ModernR1, tx2)); ASSERT_EQ( diff --git a/tests/chains/Ethereum/AddressTests.cpp b/tests/chains/Ethereum/AddressTests.cpp index b2b320cf82e..63bee37c017 100644 --- a/tests/chains/Ethereum/AddressTests.cpp +++ b/tests/chains/Ethereum/AddressTests.cpp @@ -42,7 +42,7 @@ TEST(EthereumAddress, String) { } TEST(EthereumAddress, FromPrivateKey) { - const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); const auto address = Address(publicKey); diff --git a/tests/chains/Ethereum/EthereumMessageSignerTests.cpp b/tests/chains/Ethereum/EthereumMessageSignerTests.cpp index 6165fe32e47..524db7230f8 100644 --- a/tests/chains/Ethereum/EthereumMessageSignerTests.cpp +++ b/tests/chains/Ethereum/EthereumMessageSignerTests.cpp @@ -22,7 +22,7 @@ std::string load_file(const std::string& path) { namespace TW::Ethereum { TEST(EthereumEip712, SignMessageAndVerifyLegacy) { - PrivateKey ethKey(parse_hex("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")); + PrivateKey ethKey(parse_hex("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d"), TWCurveSECP256k1); auto msg = R"( { "types": { @@ -56,7 +56,7 @@ namespace TW::Ethereum { } TEST(EthereumEip712, SignMessageAndVerifyEip155) { - PrivateKey ethKey(parse_hex("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")); + PrivateKey ethKey(parse_hex("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d"), TWCurveSECP256k1); auto msg = R"( { "types": { @@ -90,7 +90,7 @@ namespace TW::Ethereum { } TEST(EthereumEip712, SignMessageAndVerifyInvalidEip155) { - PrivateKey ethKey(parse_hex("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")); + PrivateKey ethKey(parse_hex("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d"), TWCurveSECP256k1); auto msg = R"( { "types": { @@ -122,7 +122,7 @@ namespace TW::Ethereum { } TEST(EthereumEip191, SignMessageAndVerifyLegacy) { - PrivateKey ethKey(parse_hex("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")); + PrivateKey ethKey(parse_hex("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d"), TWCurveSECP256k1); auto msg = "Foo"; auto signature = Ethereum::MessageSigner::signMessage(ethKey, msg, MessageType::Legacy); ASSERT_EQ(signature, "21a779d499957e7fd39392d49a079679009e60e492d9654a148829be43d2490736ec72bc4a5644047d979c3cf4ebe2c1c514044cf436b063cb89fc6676be71101b"); @@ -131,7 +131,7 @@ namespace TW::Ethereum { } TEST(EthereumEip191, SignMessageAndVerifyEip155) { - PrivateKey ethKey(parse_hex("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d")); + PrivateKey ethKey(parse_hex("03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d"), TWCurveSECP256k1); auto msg = "Foo"; auto signature = Ethereum::MessageSigner::signMessage(ethKey, msg, MessageType::Eip155, 0); ASSERT_EQ(signature, "21a779d499957e7fd39392d49a079679009e60e492d9654a148829be43d2490736ec72bc4a5644047d979c3cf4ebe2c1c514044cf436b063cb89fc6676be711023"); @@ -140,7 +140,7 @@ namespace TW::Ethereum { } TEST(EthereumEip191, SignMessageAndVerifyImmutableX) { - PrivateKey ethKey(parse_hex("3b0a61f46fdae924007146eacb6db6642de7a5603ad843ec58e10331d89d4b84")); + PrivateKey ethKey(parse_hex("3b0a61f46fdae924007146eacb6db6642de7a5603ad843ec58e10331d89d4b84"), TWCurveSECP256k1); auto msg = "Only sign this request if you’ve initiated an action with Immutable X.\n\nFor internal use:\nbd717ba31dca6e0f3f136f7c4197babce5f09a9f25176044c0b3112b1b6017a3"; auto signature = Ethereum::MessageSigner::signMessage(ethKey, msg, MessageType::ImmutableX); ASSERT_EQ(signature, "32cd5a58f3419fc5db672e3d57f76199b853eda0856d491b38f557b629b0a0814ace689412bf354a1af81126d2749207dffae8ae8845160f33948a6b787e17ee01"); @@ -150,7 +150,7 @@ namespace TW::Ethereum { TEST(TWEthereumMessageSigner, SignAndVerifyImmutableX) { const auto privKeyData = "3b0a61f46fdae924007146eacb6db6642de7a5603ad843ec58e10331d89d4b84"; - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get(), TWCoinTypeCurve(TWCoinTypeEthereum))); const auto message = STRING("Only sign this request if you’ve initiated an action with Immutable X.\n\nFor internal use:\nbd717ba31dca6e0f3f136f7c4197babce5f09a9f25176044c0b3112b1b6017a3"); const auto pubKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeEthereum)); @@ -161,7 +161,7 @@ namespace TW::Ethereum { TEST(TWEthereumMessageSigner, SignAndVerifyLegacy) { const auto privKeyData = "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d"; - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get(), TWCoinTypeCurve(TWCoinTypeEthereum))); const auto message = STRING("Foo"); const auto pubKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeEthereum)); @@ -172,7 +172,7 @@ namespace TW::Ethereum { TEST(TWEthereumMessageSigner, SignAndVerifyEip155) { const auto privKeyData = "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d"; - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get(), TWCoinTypeCurve(TWCoinTypeEthereum))); const auto message = STRING("Foo"); const auto pubKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeEthereum)); @@ -183,7 +183,7 @@ namespace TW::Ethereum { TEST(TWEthereumEip712, SignMessageAndVerifyLegacy) { const auto privKeyData = "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d"; - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get(), TWCoinTypeCurve(TWCoinTypeEthereum))); auto msg = STRING(R"( { "types": { @@ -218,7 +218,7 @@ namespace TW::Ethereum { TEST(TWEthereumEip712, SignMessageAndVerifyEip155) { const auto privKeyData = "03a9ca895dca1623c7dfd69693f7b4111f5d819d2e145536e0b03c136025a25d"; - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get(), TWCoinTypeCurve(TWCoinTypeEthereum))); auto msg = STRING(R"( { "types": { @@ -259,7 +259,7 @@ namespace TW::Ethereum { auto typeData = load_file(path); const auto privKeyData = "9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0"; - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get(), TWCoinTypeCurve(TWCoinTypeEthereum))); auto msg = STRING(typeData.c_str()); auto expected = "cb3a4684a991014a387a04a85b59227ebb79567c2025addcb296b4ca856e9f810d3b526f2a0d0fad6ad1b126b3b9516f8b3be020a7cca9c03ce3cf47f4199b6d1b"; const auto signature = WRAPS(TWEthereumMessageSignerSignTypedMessage(privateKey.get(), msg.get())); @@ -269,7 +269,7 @@ namespace TW::Ethereum { // Test `TWEthereumMessageSignerSignTypedMessageEip155` where `domain.chainId` is a base10 decimal string. // Generated by using https://metamask.github.io/test-dapp/ TEST(EthereumEip712, SignMessageAndVerifyEip155ChainIdString) { - PrivateKey ethKey(parse_hex("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0")); + PrivateKey ethKey(parse_hex("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0"), TWCurveSECP256k1); // 5600 auto chainId = 0x15e0; auto msg = R"( diff --git a/tests/chains/Ethereum/TWAnySignerTests.cpp b/tests/chains/Ethereum/TWAnySignerTests.cpp index f1ffacc0a1e..5e0b9fb8c1a 100644 --- a/tests/chains/Ethereum/TWAnySignerTests.cpp +++ b/tests/chains/Ethereum/TWAnySignerTests.cpp @@ -491,7 +491,7 @@ TEST(TWAnySignerEthereum, StakeRocketPool) { auto maxInclusionFeePerGas = store(uint256_t(1000000000)); auto toAddress = "0x2cac916b2a963bf162f076c0a8a4a8200bcfbfb4"; auto key = parse_hex("9f56448d33de406db1561aae15fce64bdf0e9706ff15c45d4409e8fcbfd1a498"); - const auto pk = PrivateKey(key); + const auto pk = PrivateKey(key, TWCurveSECP256k1); // 0.01 ETH auto valueData = store(uint256_t(10000000000000000)); @@ -540,7 +540,7 @@ TEST(TWAnySignerEthereum, UnstakeRocketPool) { auto maxInclusionFeePerGas = store(uint256_t(1000000000)); auto toAddress = "0xae78736Cd615f374D3085123A210448E74Fc6393"; auto key = parse_hex("9f56448d33de406db1561aae15fce64bdf0e9706ff15c45d4409e8fcbfd1a498"); - const auto pk = PrivateKey(key); + const auto pk = PrivateKey(key, TWCurveSECP256k1); auto valueData = store(uint256_t(0)); diff --git a/tests/chains/Everscale/AddressTests.cpp b/tests/chains/Everscale/AddressTests.cpp index f984140bd74..16bb5a983b8 100644 --- a/tests/chains/Everscale/AddressTests.cpp +++ b/tests/chains/Everscale/AddressTests.cpp @@ -36,7 +36,7 @@ TEST(EverscaleAddress, FromString) { } TEST(EverscaleAddress, FromPrivateKey) { - auto privateKey = PrivateKey(parse_hex("5b59e0372d19b6355c73fa8cc708fa3301ae2ec21bb6277e8b79d386ccb7846f")); + auto privateKey = PrivateKey(parse_hex("5b59e0372d19b6355c73fa8cc708fa3301ae2ec21bb6277e8b79d386ccb7846f"), TWCurveED25519); auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeED25519), WorkchainType::Basechain); ASSERT_EQ(address.string(), "0:269fee242eb410786abe1777a14785c8bbeb1e34100c7570e17698b36ad66fb0"); } diff --git a/tests/chains/Evmos/TransactionCompilerTests.cpp b/tests/chains/Evmos/TransactionCompilerTests.cpp index 47a700bdf94..bd2dcd051ab 100644 --- a/tests/chains/Evmos/TransactionCompilerTests.cpp +++ b/tests/chains/Evmos/TransactionCompilerTests.cpp @@ -19,7 +19,7 @@ TEST(EvmosCompiler, CompileWithSignatures) { TW::Cosmos::Proto::SigningInput input; PrivateKey privateKey = - PrivateKey(parse_hex("727513ec3c54eb6fae24f2ff756bbc4c89b82945c6538bbd173613ae3de719d3")); + PrivateKey(parse_hex("727513ec3c54eb6fae24f2ff756bbc4c89b82945c6538bbd173613ae3de719d3"), TWCoinTypeCurve(coin)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); /// Step 1: Prepare transaction input (protobuf) diff --git a/tests/chains/FIO/AddressTests.cpp b/tests/chains/FIO/AddressTests.cpp index ffc99746168..c052f97bd2c 100644 --- a/tests/chains/FIO/AddressTests.cpp +++ b/tests/chains/FIO/AddressTests.cpp @@ -47,7 +47,7 @@ TEST(FIOAddress, FromStringInvalid) { } TEST(FIOAddress, FromPublicKey) { - auto key = PrivateKey(parse_hex("ea8eb60b7e5868e218f248e032769020b4fea5dcfd02f2992861eaf4fb534854")); + auto key = PrivateKey(parse_hex("ea8eb60b7e5868e218f248e032769020b4fea5dcfd02f2992861eaf4fb534854"), TWCurveSECP256k1); auto publicKey = key.getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_EQ(hex(publicKey.bytes), "0271195c66ec2799e436757a70cd8431d4b17733a097b18a5f7f1b6b085978ff0f"); auto address = Address(publicKey); diff --git a/tests/chains/FIO/EncryptionTests.cpp b/tests/chains/FIO/EncryptionTests.cpp index db8341d9eb4..81974e2b4a7 100644 --- a/tests/chains/FIO/EncryptionTests.cpp +++ b/tests/chains/FIO/EncryptionTests.cpp @@ -111,21 +111,21 @@ TEST(FIOEncryption, getSharedSecret) { // tests extracted from https://github.com/fioprotocol/fiojs/blob/master/src/tests/encryption-fio.test.ts // See also https://github.com/fioprotocol/fiojs/blob/master/docs/message_encryption.md { - const PrivateKey privateKey(parse_hex("2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90")); + const PrivateKey privateKey(parse_hex("2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90"), TWCurveSECP256k1); const PublicKey publicKey(parse_hex("024edfcf9dfe6c0b5c83d1ab3f78d1b39a46ebac6798e08e19761f5ed89ec83c10"), TWPublicKeyTypeSECP256k1); Data secret = Encryption::getSharedSecret(privateKey, publicKey); EXPECT_EQ(secret.size(), 64ul); EXPECT_EQ(hex(secret), "a71b4ec5a9577926a1d2aa1d9d99327fd3b68f6a1ea597200a0d890bd3331df300a2d49fec0b2b3e6969ce9263c5d6cf47c191c1ef149373ecc9f0d98116b598"); } { - const PrivateKey privateKey(parse_hex("81b637d8fcd2c6da6359e6963113a1170de795e4b725b84d1e0b4cfd9ec58ce9")); + const PrivateKey privateKey(parse_hex("81b637d8fcd2c6da6359e6963113a1170de795e4b725b84d1e0b4cfd9ec58ce9"), TWCurveSECP256k1); const PublicKey publicKey(parse_hex("039997a497d964fc1a62885b05a51166a65a90df00492c8d7cf61d6accf54803be"), TWPublicKeyTypeSECP256k1); Data secret = Encryption::getSharedSecret(privateKey, publicKey); EXPECT_EQ(secret.size(), 64ul); EXPECT_EQ(hex(secret), "a71b4ec5a9577926a1d2aa1d9d99327fd3b68f6a1ea597200a0d890bd3331df300a2d49fec0b2b3e6969ce9263c5d6cf47c191c1ef149373ecc9f0d98116b598"); } { - const PrivateKey privateKey(parse_hex("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")); + const PrivateKey privateKey(parse_hex("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"), TWCurveSECP256k1); const PublicKey publicKey(parse_hex("03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd"), TWPublicKeyTypeSECP256k1); Data secret = Encryption::getSharedSecret(privateKey, publicKey); EXPECT_EQ(secret.size(), 64ul); @@ -136,7 +136,7 @@ TEST(FIOEncryption, getSharedSecret) { TEST(FIOEncryption, encryptAndEncode) { // tests extracted from https://github.com/fioprotocol/fiojs/blob/master/src/tests/encryption-fio.test.ts { - const PrivateKey privateKey(parse_hex("2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90")); + const PrivateKey privateKey(parse_hex("2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90"), TWCurveSECP256k1); const PublicKey publicKey(parse_hex("024edfcf9dfe6c0b5c83d1ab3f78d1b39a46ebac6798e08e19761f5ed89ec83c10"), TWPublicKeyTypeSECP256k1); const Data message = parse_hex("0b70757273652e616c69636501310a66696f2e7265716f6274000000"); const Data iv = parse_hex("f300888ca4f512cebdc0020ff0f7224c"); @@ -146,7 +146,7 @@ TEST(FIOEncryption, encryptAndEncode) { EXPECT_EQ(encoded, "8wCIjKT1Es69wAIP8PciTA2ymExK2a+xJinwGoxqdjKLveF0BWVdxOPLMNrScplvsd6o5mLmQL4ZPiXUEUepBMVxtmSnOBq0HvBiRIrB4gU="); } { - const PrivateKey privateKey(parse_hex("81b637d8fcd2c6da6359e6963113a1170de795e4b725b84d1e0b4cfd9ec58ce9")); + const PrivateKey privateKey(parse_hex("81b637d8fcd2c6da6359e6963113a1170de795e4b725b84d1e0b4cfd9ec58ce9"), TWCurveSECP256k1); const PublicKey publicKey(parse_hex("039997a497d964fc1a62885b05a51166a65a90df00492c8d7cf61d6accf54803be"), TWPublicKeyTypeSECP256k1); const Data message = parse_hex("0b70757273652e616c69636501310a66696f2e7265716f6274000000"); const Data iv = parse_hex("f300888ca4f512cebdc0020ff0f7224c"); @@ -158,11 +158,11 @@ TEST(FIOEncryption, encryptAndEncode) { } TEST(FIOEncryption, encryptEncodeDecodeDecrypt) { - const PrivateKey privateKeyAlice(parse_hex("2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90")); + const PrivateKey privateKeyAlice(parse_hex("2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90"), TWCurveSECP256k1); const PublicKey publicKeyAlice = privateKeyAlice.getPublicKey(TWPublicKeyTypeSECP256k1); const Address addressAlice(publicKeyAlice); EXPECT_EQ(addressAlice.string(), "FIO7zsqi7QUAjTAdyynd6DVe8uv4K8gCTRHnAoMN9w9CA1xLCTDVv"); - const PrivateKey privateKeyBob(parse_hex("81b637d8fcd2c6da6359e6963113a1170de795e4b725b84d1e0b4cfd9ec58ce9")); + const PrivateKey privateKeyBob(parse_hex("81b637d8fcd2c6da6359e6963113a1170de795e4b725b84d1e0b4cfd9ec58ce9"), TWCurveSECP256k1); const PublicKey publicKeyBob = privateKeyBob.getPublicKey(TWPublicKeyTypeSECP256k1); const Address addressBob(publicKeyBob); EXPECT_EQ(addressBob.string(), "FIO5VE6Dgy9FUmd1mFotXwF88HkQN1KysCWLPqpVnDMjRvGRi1YrM"); diff --git a/tests/chains/FIO/SignerTests.cpp b/tests/chains/FIO/SignerTests.cpp index a3104e15afc..194a0e3a7e6 100644 --- a/tests/chains/FIO/SignerTests.cpp +++ b/tests/chains/FIO/SignerTests.cpp @@ -9,6 +9,7 @@ #include "Base58.h" #include "Hash.h" #include "HexCoding.h" +#include "TestUtilities.h" #include @@ -24,7 +25,7 @@ TEST(FIOSigner, SignEncode) { TEST(FIOSigner, SignInternals) { // 5KEDWtAUJcFX6Vz38WXsAQAv2geNqT7UaZC8gYu9kTuryr3qkri FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf - PrivateKey pk = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")); + PrivateKey pk = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035"), TWCoinTypeCurve(TWCoinTypeFIO)); { Data pk2 = parse_hex("80"); append(pk2, pk.bytes); @@ -77,7 +78,7 @@ TEST(FIOSigner, Actor) { TEST(FIOSigner, compile) { const Data chainId = parse_hex("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77"); // 5KEDWtAUJcFX6Vz38WXsAQAv2geNqT7UaZC8gYu9kTuryr3qkri FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf - const PrivateKey privKeyBA = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")); + const PrivateKey privKeyBA = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035"), TWCoinTypeCurve(TWCoinTypeFIO)); const PublicKey pubKey6M = privKeyBA.getPublicKey(TWPublicKeyTypeSECP256k1); const Address addr6M(pubKey6M); Proto::SigningInput input; diff --git a/tests/chains/FIO/TWFIOTests.cpp b/tests/chains/FIO/TWFIOTests.cpp index 1c4f35285b3..311e6015e68 100644 --- a/tests/chains/FIO/TWFIOTests.cpp +++ b/tests/chains/FIO/TWFIOTests.cpp @@ -19,7 +19,7 @@ namespace TW::FIO::TWFIOTests { using namespace std; TEST(TWFIO, Address) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035").get(), TWCoinTypeCurve(TWCoinTypeFIO))); ASSERT_NE(nullptr, privateKey.get()); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), false)); ASSERT_NE(nullptr, publicKey.get()); @@ -39,7 +39,7 @@ TEST(TWFIO, Address) { const Data gChainId = parse_hex("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77"); const Data gChainIdMainnet = parse_hex("21dcae42c0182200e93f954a074011f9048a7624c6fe81d3c9541a614a88bd1c"); // 5KEDWtAUJcFX6Vz38WXsAQAv2geNqT7UaZC8gYu9kTuryr3qkri FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf -const PrivateKey privKeyBA = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")); +const PrivateKey privKeyBA = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035"), TWCoinTypeCurve(TWCoinTypeFIO)); const PublicKey pubKey6M = privKeyBA.getPublicKey(TWPublicKeyTypeSECP256k1); const Address addr6M(pubKey6M); diff --git a/tests/chains/FIO/TransactionBuilderTests.cpp b/tests/chains/FIO/TransactionBuilderTests.cpp index 16f16f92b06..556eca7bfd1 100644 --- a/tests/chains/FIO/TransactionBuilderTests.cpp +++ b/tests/chains/FIO/TransactionBuilderTests.cpp @@ -10,6 +10,7 @@ #include "BinaryCoding.h" #include "HexCoding.h" +#include "TestUtilities.h" #include #include @@ -19,7 +20,7 @@ using namespace std; const Data chainId = parse_hex("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77"); // 5KEDWtAUJcFX6Vz38WXsAQAv2geNqT7UaZC8gYu9kTuryr3qkri FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf -const PrivateKey gPrivKeyBA = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")); +const PrivateKey gPrivKeyBA = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035"), TWCoinTypeCurve(TWCoinTypeFIO)); const PublicKey gPubKey6MA = gPrivKeyBA.getPublicKey(TWPublicKeyTypeSECP256k1); const Address gAddr6M(gPubKey6MA); diff --git a/tests/chains/FIO/TransactionCompilerTests.cpp b/tests/chains/FIO/TransactionCompilerTests.cpp index f3eaecbc159..635cee64a51 100644 --- a/tests/chains/FIO/TransactionCompilerTests.cpp +++ b/tests/chains/FIO/TransactionCompilerTests.cpp @@ -32,7 +32,7 @@ using namespace std; TEST(FIOCompiler, CompileWithSignatures) { Data chainId = parse_hex("4e46572250454b796d7296eec9e8896327ea82dd40f2cd74cf1b1d8ba90bcd77"); // 5KEDWtAUJcFX6Vz38WXsAQAv2geNqT7UaZC8gYu9kTuryr3qkri FIO6m1fMdTpRkRBnedvYshXCxLFiC5suRU8KDfx8xxtXp2hntxpnf - PrivateKey privateKey = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035")); + PrivateKey privateKey = PrivateKey(parse_hex("ba0828d5734b65e3bcc2c51c93dfc26dd71bd666cc0273adee77d73d9a322035"), TWCoinTypeCurve(TWCoinTypeFIO)); PublicKey pubKeyA = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); Address addrA(pubKeyA); const auto coin = TWCoinTypeFIO; diff --git a/tests/chains/Filecoin/SignerTests.cpp b/tests/chains/Filecoin/SignerTests.cpp index 8eccd8f6f76..7d0852c9e8b 100644 --- a/tests/chains/Filecoin/SignerTests.cpp +++ b/tests/chains/Filecoin/SignerTests.cpp @@ -6,6 +6,7 @@ #include "Filecoin/Signer.h" #include "HexCoding.h" #include "PrivateKey.h" +#include "TestUtilities.h" #include @@ -13,7 +14,7 @@ namespace TW::Filecoin { TEST(FilecoinSigner, DerivePublicKey) { const PrivateKey privateKey( - parse_hex("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe")); + parse_hex("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe"), TWCoinTypeCurve(TWCoinTypeFilecoin)); const PublicKey publicKey((privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended))); const Address address = Address::secp256k1Address(publicKey); ASSERT_EQ(address.string(), "f1z4a36sc7mfbv4z3qwutblp2flycdui3baffytbq"); @@ -21,7 +22,7 @@ TEST(FilecoinSigner, DerivePublicKey) { TEST(FilecoinSigner, Sign) { const PrivateKey privateKey( - parse_hex("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe")); + parse_hex("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe"), TWCoinTypeCurve(TWCoinTypeFilecoin)); const PublicKey publicKey((privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended))); const Address fromAddress = Address::secp256k1Address(publicKey); const Address toAddress("f1rletqqhinhagw6nxjcr4kbfws25thgt7owzuruy"); diff --git a/tests/chains/Filecoin/TransactionCompilerTests.cpp b/tests/chains/Filecoin/TransactionCompilerTests.cpp index f04883c127d..13277eb9393 100644 --- a/tests/chains/Filecoin/TransactionCompilerTests.cpp +++ b/tests/chains/Filecoin/TransactionCompilerTests.cpp @@ -26,7 +26,7 @@ TEST(FilecoinCompiler, CompileWithSignatures) { /// Step 1: Prepare transaction input (protobuf) Filecoin::Proto::SigningInput input; auto privateKey = parse_hex("1d969865e189957b9824bd34f26d5cbf357fda1a6d844cbf0c9ab1ed93fa7dbe"); - auto key = PrivateKey(privateKey); + auto key = PrivateKey(privateKey, TWCurveSECP256k1); auto publicKey = key.getPublicKey(TWPublicKeyTypeSECP256k1Extended); auto toAddress = "f3um6uo3qt5of54xjbx3hsxbw5mbsc6auxzrvfxekn5bv3duewqyn2tg5rhrlx73qahzzpkhuj7a34iq7oifsq"; @@ -60,7 +60,7 @@ TEST(FilecoinCompiler, CompileWithSignatures) { EXPECT_EQ(hex(preImageHash), "8368c0f622b2c529c7fa147d75aa02aaa7fc13fc4847d4dc57e7a5c59048aafe"); // Simulate signature, normally obtained from signature server - const auto signature = key.sign(preImageHash, TWCurveSECP256k1); + const auto signature = key.sign(preImageHash); /// Step 3: Compile transaction info auto outputData = diff --git a/tests/chains/Filecoin/TransactionTests.cpp b/tests/chains/Filecoin/TransactionTests.cpp index 82650f13499..73db00acad1 100644 --- a/tests/chains/Filecoin/TransactionTests.cpp +++ b/tests/chains/Filecoin/TransactionTests.cpp @@ -7,6 +7,8 @@ #include "HexCoding.h" #include "PrivateKey.h" +#include + #include namespace TW::Filecoin { @@ -23,7 +25,7 @@ TEST(FilecoinTransaction, EncodeBigInt) { TEST(FilecoinTransaction, Serialize) { const PrivateKey privateKey( - parse_hex("2f0f1d2c8de955c7c3fb4d9cae02539fadcb13fa998ccd9a1e871bed95f1941e")); + parse_hex("2f0f1d2c8de955c7c3fb4d9cae02539fadcb13fa998ccd9a1e871bed95f1941e"), TWCoinTypeCurve(TWCoinTypeFilecoin)); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); const Address fromAddress = Address::secp256k1Address(publicKey); const Address toAddress("f1hvadvq4rd2pyayrigjx2nbqz2nvemqouslw4wxi"); diff --git a/tests/chains/Firo/TWFiroAddressTests.cpp b/tests/chains/Firo/TWFiroAddressTests.cpp index 9054b6100c5..3bb59cc65b8 100644 --- a/tests/chains/Firo/TWFiroAddressTests.cpp +++ b/tests/chains/Firo/TWFiroAddressTests.cpp @@ -18,7 +18,7 @@ #include TEST(TWZCoin, Address) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get(), TWCoinTypeCurve(TWCoinTypeFiro))); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeFiro))); auto addressString = WRAPS(TWBitcoinAddressDescription(address.get())); diff --git a/tests/chains/Greenfield/TransactionCompilerTests.cpp b/tests/chains/Greenfield/TransactionCompilerTests.cpp index 9766bbcaa0c..ddfbe747e4f 100644 --- a/tests/chains/Greenfield/TransactionCompilerTests.cpp +++ b/tests/chains/Greenfield/TransactionCompilerTests.cpp @@ -23,7 +23,7 @@ TEST(GreenfieldCompiler, PreHashCompile) { // Successfully broadcasted https://greenfieldscan.com/tx/0x9f895cf2dd64fb1f428cefcf2a6585a813c3540fc9fe1ef42db1da2cb1df55ab auto privateKeyData = parse_hex("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0"); - PrivateKey privateKey(privateKeyData); + PrivateKey privateKey(privateKeyData, TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); Proto::SigningInput input; @@ -60,7 +60,7 @@ TEST(GreenfieldCompiler, PreHashCompile) { // Step 2: Sign "remotely" - auto signature = privateKey.sign(data(preOutput.data_hash()), TWCurveSECP256k1); + auto signature = privateKey.sign(data(preOutput.data_hash())); EXPECT_EQ(hex(signature), "cb3a4684a991014a387a04a85b59227ebb79567c2025addcb296b4ca856e9f810d3b526f2a0d0fad6ad1b126b3b9516f8b3be020a7cca9c03ce3cf47f4199b6d00"); diff --git a/tests/chains/Groestlcoin/AddressTests.cpp b/tests/chains/Groestlcoin/AddressTests.cpp index 8fcce675012..1087102d79d 100644 --- a/tests/chains/Groestlcoin/AddressTests.cpp +++ b/tests/chains/Groestlcoin/AddressTests.cpp @@ -18,7 +18,7 @@ TEST(GroestlcoinAddress, FromPublicKey) { const auto address = Address(publicKey, 36); ASSERT_EQ(address.string(), "Fj62rBJi8LvbmWu2jzkaUX1NFXLEqDLoZM"); - const auto privateKey = PrivateKey(parse_hex("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a")); + const auto privateKey = PrivateKey(parse_hex("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a"), TWCoinTypeCurve(TWCoinTypeGroestlcoin)); const auto publicKey2 = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeED25519)); EXPECT_ANY_THROW(new Address(publicKey2, 36)); } diff --git a/tests/chains/Groestlcoin/TWGroestlcoinSigningTests.cpp b/tests/chains/Groestlcoin/TWGroestlcoinSigningTests.cpp index 128edf79144..eef9548fc4a 100644 --- a/tests/chains/Groestlcoin/TWGroestlcoinSigningTests.cpp +++ b/tests/chains/Groestlcoin/TWGroestlcoinSigningTests.cpp @@ -133,7 +133,7 @@ TEST(GroestlcoinSigning, SignP2SH_P2WPKH) { input.set_change_address("grs1qw4teyraux2s77nhjdwh9ar8rl9dt7zww8r6lne"); // TX input - auto utxoKey0 = PrivateKey(parse_hex("302fc195a8fc96c5a581471e67e4c1ac2efda252f76ad5c77a53764c70d58f91")); + auto utxoKey0 = PrivateKey(parse_hex("302fc195a8fc96c5a581471e67e4c1ac2efda252f76ad5c77a53764c70d58f91"), TWCoinTypeCurve(TWCoinTypeGroestlcoin)); auto pubKey0 = utxoKey0.getPublicKey(TWPublicKeyTypeSECP256k1); auto utxoPubkeyHash = Hash::ripemd(Hash::sha256(pubKey0.bytes)); EXPECT_EQ(hex(utxoPubkeyHash), "2fc7d70acef142d1f7b5ef2f20b1a9b759797674"); diff --git a/tests/chains/Groestlcoin/TWGroestlcoinTests.cpp b/tests/chains/Groestlcoin/TWGroestlcoinTests.cpp index bb186cfd48f..18332219c78 100644 --- a/tests/chains/Groestlcoin/TWGroestlcoinTests.cpp +++ b/tests/chains/Groestlcoin/TWGroestlcoinTests.cpp @@ -15,7 +15,7 @@ #include TEST(Groestlcoin, Address) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("3c3385ddc6fd95ba7282051aeb440bc75820b8c10db5c83c052d7586e3e98e84").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("3c3385ddc6fd95ba7282051aeb440bc75820b8c10db5c83c052d7586e3e98e84").get(), TWCoinTypeCurve(TWCoinTypeGroestlcoin))); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); auto address = WRAP(TWGroestlcoinAddress, TWGroestlcoinAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeGroestlcoin))); auto addressString = WRAPS(TWGroestlcoinAddressDescription(address.get())); diff --git a/tests/chains/Groestlcoin/TransactionCompilerTests.cpp b/tests/chains/Groestlcoin/TransactionCompilerTests.cpp index dfe513977cf..fbc8c99062f 100644 --- a/tests/chains/Groestlcoin/TransactionCompilerTests.cpp +++ b/tests/chains/Groestlcoin/TransactionCompilerTests.cpp @@ -77,7 +77,7 @@ TEST(GroestlcoinCompiler, CompileWithSignatures) { "0fb3da786ad1028574f0b40ff1446515eb85cacccff3f3d0459e191b660597b3"); // compile - auto publicKey = PrivateKey(utxoKey0).getPublicKey(TWPublicKeyTypeSECP256k1); + auto publicKey = PrivateKey(utxoKey0, TWCoinTypeCurve(coin)).getPublicKey(TWPublicKeyTypeSECP256k1); auto signature = parse_hex("304402202163ab98b028aa13563f0de00b785d6df81df5eac0b7c91d23f5be7ea674aa3702202bf6cd7055c6f8f697ce045b1a4f9b997cf6e5761a661d27696ac34064479d19"); { const Data outputData = diff --git a/tests/chains/Harmony/AddressTests.cpp b/tests/chains/Harmony/AddressTests.cpp index 3d7786b76be..6366bfbc882 100644 --- a/tests/chains/Harmony/AddressTests.cpp +++ b/tests/chains/Harmony/AddressTests.cpp @@ -5,6 +5,7 @@ #include "Harmony/Address.h" #include "HexCoding.h" #include "PrivateKey.h" +#include "TestUtilities.h" #include @@ -38,7 +39,7 @@ TEST(HarmonyAddress, InvalidHarmonyAddress) { TEST(HarmonyAddress, FromPublicKey) { const auto privateKey = - PrivateKey(parse_hex("e2f88b4974ae763ca1c2db49218802c2e441293a09eaa9ab681779e05d1b7b94")); + PrivateKey(parse_hex("e2f88b4974ae763ca1c2db49218802c2e441293a09eaa9ab681779e05d1b7b94"), TWCoinTypeCurve(TWCoinTypeHarmony)); const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); const auto address = Address(publicKey); ASSERT_EQ(address.string(), "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe"); diff --git a/tests/chains/Harmony/SignerTests.cpp b/tests/chains/Harmony/SignerTests.cpp index dc917bf26b5..97205319f02 100644 --- a/tests/chains/Harmony/SignerTests.cpp +++ b/tests/chains/Harmony/SignerTests.cpp @@ -4,6 +4,7 @@ #include +#include "TestUtilities.h" #include "Ethereum/RLP.h" #include "Harmony/Address.h" #include "Harmony/Signer.h" @@ -54,7 +55,7 @@ TEST(HarmonySigner, RLPEncodingAndHashAssumeLocalNet) { TEST(HarmonySigner, SignAssumeLocalNet) { auto key = - PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e"), TWCoinTypeCurve(TWCoinTypeHarmony)); auto signer = SignerExposed(LOCAL_NET); uint256_t v("0x28"); @@ -77,7 +78,7 @@ TEST(HarmonySigner, SignProtoBufAssumeLocalNet) { trasactionMsg->set_to_address(TEST_RECEIVER.string()); const auto privateKey = - PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e"), TWCoinTypeCurve(TWCoinTypeHarmony)); auto payload = parse_hex(""); trasactionMsg->set_payload(payload.data(), payload.size()); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); @@ -119,7 +120,7 @@ TEST(HarmonySigner, SignOverProtoBufAssumeMainNet) { auto trasactionMsg = input.mutable_transaction_message(); trasactionMsg->set_to_address(TEST_RECEIVER.string()); const auto privateKey = - PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e"), TWCoinTypeCurve(TWCoinTypeHarmony)); auto payload = parse_hex(""); trasactionMsg->set_payload(payload.data(), payload.size()); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); @@ -166,7 +167,7 @@ TEST(HarmonySigner, BuildSigningOutput) { auto trasactionMsg = input.mutable_transaction_message(); trasactionMsg->set_to_address(TEST_RECEIVER.string()); const auto privateKey = - PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e"), TWCoinTypeCurve(TWCoinTypeHarmony)); auto payload = parse_hex(""); trasactionMsg->set_payload(payload.data(), payload.size()); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); @@ -216,7 +217,7 @@ TEST(HarmonySigner, BuildUnsignedTxBytes) { auto trasactionMsg = input.mutable_transaction_message(); trasactionMsg->set_to_address(TEST_RECEIVER.string()); const auto privateKey = - PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e"), TWCoinTypeCurve(TWCoinTypeHarmony)); auto payload = parse_hex(""); trasactionMsg->set_payload(payload.data(), payload.size()); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); @@ -254,7 +255,7 @@ TEST(HarmonySigner, BuildUnsignedStakingTxBytes) { auto input = Proto::SigningInput(); auto stakingMsg = input.mutable_staking_message(); const auto privateKey = - PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e"), TWCoinTypeCurve(TWCoinTypeHarmony)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); auto value = store(MAIN_NET); diff --git a/tests/chains/Harmony/StakingTests.cpp b/tests/chains/Harmony/StakingTests.cpp index bbcce90bb7d..e54112a936e 100644 --- a/tests/chains/Harmony/StakingTests.cpp +++ b/tests/chains/Harmony/StakingTests.cpp @@ -20,7 +20,7 @@ static bool testAccountDecodeResult = Address::decode("one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9", TEST_ACCOUNT); static auto PRIVATE_KEY = - PrivateKey(parse_hex("4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48")); + PrivateKey(parse_hex("4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48"), TWCoinTypeCurve(TWCoinTypeHarmony)); TEST(HarmonyStaking, SignCreateValidator) { auto input = Proto::SigningInput(); diff --git a/tests/chains/Harmony/TWHarmonyStakingTests.cpp b/tests/chains/Harmony/TWHarmonyStakingTests.cpp index 5c0283c81f0..3db381203e3 100644 --- a/tests/chains/Harmony/TWHarmonyStakingTests.cpp +++ b/tests/chains/Harmony/TWHarmonyStakingTests.cpp @@ -20,7 +20,7 @@ namespace TW::Harmony::tests { static auto TEST_ACCOUNT = "one1a0x3d6xpmr6f8wsyaxd9v36pytvp48zckswvv9"; static auto PRIVATE_KEY = - PrivateKey(parse_hex("4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48")); + PrivateKey(parse_hex("4edef2c24995d15b0e25cbd152fb0e2c05d3b79b9c2afd134e6f59f91bf99e48"), TWCoinTypeCurve(TWCoinTypeHarmony)); TEST(TWHarmonyStakingSigner, CreateValidator) { auto input = Proto::SigningInput(); diff --git a/tests/chains/Harmony/TransactionCompilerTests.cpp b/tests/chains/Harmony/TransactionCompilerTests.cpp index 5cc34ad9765..86b6caf3db6 100644 --- a/tests/chains/Harmony/TransactionCompilerTests.cpp +++ b/tests/chains/Harmony/TransactionCompilerTests.cpp @@ -71,7 +71,7 @@ TEST(HarmonyCompiler, CompileWithSignatures) { ASSERT_EQ(hex(preImage), expectedPreImage); ASSERT_EQ(hex(preImageHash), expectedPreImageHash); - const auto privateKey = PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e")); + const auto privateKey = PrivateKey(parse_hex("b578822c5c718e510f67a9e291e9c6efdaf753f406020f55223b940e1ddb282e"), TWCoinTypeCurve(TWCoinTypeHarmony)); Data signature = parse_hex("43824f50bf4b16ebe1020114de16e3579bdb5f3dcaa26117de87a73b5414b72550506609fd60e3cb565b1f9bae0952d37f3a6c6be262380f7f18cbda5216f34300"); const PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); diff --git a/tests/chains/Hedera/SignerTests.cpp b/tests/chains/Hedera/SignerTests.cpp index b1b4220226a..c66178986e6 100644 --- a/tests/chains/Hedera/SignerTests.cpp +++ b/tests/chains/Hedera/SignerTests.cpp @@ -10,6 +10,7 @@ #include "HexCoding.h" #include "PrivateKey.h" #include "PublicKey.h" +#include "TestUtilities.h" #include @@ -18,7 +19,7 @@ namespace TW::Hedera::tests { TEST(HederaSigner, Sign) { // Successfully broadcasted: https://hashscan.io/testnet/transaction/0.0.48694347-1667222879-749068449?t=1667222891.440398729&p=1 Proto::SigningInput input; - auto privateKey = PrivateKey(parse_hex("e87a5584c0173263e138db689fdb2a7389025aaae7cb1a18a1017d76012130e8")); + auto privateKey = PrivateKey(parse_hex("e87a5584c0173263e138db689fdb2a7389025aaae7cb1a18a1017d76012130e8"), TWCoinTypeCurve(TWCoinTypeHedera)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); auto* body = input.mutable_body(); @@ -43,7 +44,7 @@ TEST(HederaSigner, Sign) { TEST(HederaSigner, SignWithMemo) { // Successfully broadcasted: https://hashscan.io/testnet/transaction/0.0.48694347-1667227300-854561449?t=1667227312.554926003 Proto::SigningInput input; - auto privateKey = PrivateKey(parse_hex("e87a5584c0173263e138db689fdb2a7389025aaae7cb1a18a1017d76012130e8")); + auto privateKey = PrivateKey(parse_hex("e87a5584c0173263e138db689fdb2a7389025aaae7cb1a18a1017d76012130e8"), TWCoinTypeCurve(TWCoinTypeHedera)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); auto* body = input.mutable_body(); @@ -68,7 +69,7 @@ TEST(HederaSigner, SignWithMemo) { TEST(HederaSigner, SignWithMemoMainnet) { // Successfully broadcasted: https://hashscan.io/mainnet/transaction/0.0.1377988-1667566445-926176449?t=1667566457.533804616 Proto::SigningInput input; - auto privateKey = PrivateKey(parse_hex("650c5120cbdc6244e3d10001eb27eea4dd3f80c331b3b6969fa434797d4edd50")); + auto privateKey = PrivateKey(parse_hex("650c5120cbdc6244e3d10001eb27eea4dd3f80c331b3b6969fa434797d4edd50"), TWCoinTypeCurve(TWCoinTypeHedera)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); auto* body = input.mutable_body(); diff --git a/tests/chains/Hedera/TWAnySignerTests.cpp b/tests/chains/Hedera/TWAnySignerTests.cpp index 0e2d900604f..02e880b2f12 100644 --- a/tests/chains/Hedera/TWAnySignerTests.cpp +++ b/tests/chains/Hedera/TWAnySignerTests.cpp @@ -14,7 +14,7 @@ namespace TW::Hedera::tests { TEST(TWAnySignerHedera, Sign) { // Successfully broadcasted: https://hashscan.io/testnet/transaction/0.0.48694347-1667222879-749068449?t=1667222891.440398729&p=1 Proto::SigningInput input; - auto privateKey = PrivateKey(parse_hex("e87a5584c0173263e138db689fdb2a7389025aaae7cb1a18a1017d76012130e8")); + auto privateKey = PrivateKey(parse_hex("e87a5584c0173263e138db689fdb2a7389025aaae7cb1a18a1017d76012130e8"), TWCoinTypeCurve(TWCoinTypeHedera)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); auto* body = input.mutable_body(); diff --git a/tests/chains/ICON/AddressTests.cpp b/tests/chains/ICON/AddressTests.cpp index 2d8229fac8f..4ace2694acb 100644 --- a/tests/chains/ICON/AddressTests.cpp +++ b/tests/chains/ICON/AddressTests.cpp @@ -5,6 +5,7 @@ #include "HexCoding.h" #include "Icon/Address.h" #include "PrivateKey.h" +#include "TestUtilities.h" #include @@ -32,7 +33,7 @@ TEST(IconAddress, String) { } TEST(IconAddress, FromPrivateKey) { - const auto privateKey = PrivateKey(parse_hex("94d1a980d5e528067d44bf8a60d646f556e40ca71e17cd4ead2d56f89e4bd20f")); + const auto privateKey = PrivateKey(parse_hex("94d1a980d5e528067d44bf8a60d646f556e40ca71e17cd4ead2d56f89e4bd20f"), TWCoinTypeCurve(TWCoinTypeICON)); const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); const auto address = Address(publicKey, TypeAddress); diff --git a/tests/chains/ICON/TransactionCompilerTests.cpp b/tests/chains/ICON/TransactionCompilerTests.cpp index a47dc54d2be..ae01ed8efa6 100644 --- a/tests/chains/ICON/TransactionCompilerTests.cpp +++ b/tests/chains/ICON/TransactionCompilerTests.cpp @@ -49,9 +49,9 @@ TEST(IconCompiler, CompileWithSignatures) { EXPECT_EQ(hex(preImageHash),"f0c68a4f588233d722fff7b5a738ffa6b56ad4cb62ad6bc9fb3e5facb0c25059"); auto key = parse_hex("2d42994b2f7735bbc93a3e64381864d06747e574aa94655c516f9ad0a74eed79"); - const auto privateKey = PrivateKey(key); + const auto privateKey = PrivateKey(key, TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWCoinTypePublicKeyType(coin)); - const auto signature = privateKey.sign(parse_hex(hex(preImageHash)), TWCurveSECP256k1); + const auto signature = privateKey.sign(parse_hex(hex(preImageHash))); const Data outputData = TransactionCompiler::compileWithSignatures(coin, protoInputData, {signature}, {publicKey.bytes}); Icon::Proto::SigningOutput output; diff --git a/tests/chains/IOST/TransactionCompilerTests.cpp b/tests/chains/IOST/TransactionCompilerTests.cpp index 9db3d3e0d84..0da9e6da5b6 100644 --- a/tests/chains/IOST/TransactionCompilerTests.cpp +++ b/tests/chains/IOST/TransactionCompilerTests.cpp @@ -26,7 +26,7 @@ TEST(IostCompiler, CompileWithSignatures) { /// Step 1: Prepare transaction input (protobuf) const auto privKeyBytes = Base58::decode( "4TQwN7wWXg26ByuU5WkUPErd5v6PD6HsDuULyGNJgpS979wXF7jRU8NKviJs5boHrRKbLMomKycbek4NyDy6cLb8"); - const auto pkFrom = PrivateKey(Data(privKeyBytes.begin(), privKeyBytes.begin() + 32)); + const auto pkFrom = PrivateKey(Data(privKeyBytes.begin(), privKeyBytes.begin() + 32), TWCoinTypeCurve(coin)); const auto publicKey = pkFrom.getPublicKey(TWPublicKeyTypeED25519); TW::IOST::Proto::SigningInput input; input.set_transfer_memo(""); diff --git a/tests/chains/ImmutableX/StarkKeyTests.cpp b/tests/chains/ImmutableX/StarkKeyTests.cpp index 6f9efca154e..bdc6bd366dd 100644 --- a/tests/chains/ImmutableX/StarkKeyTests.cpp +++ b/tests/chains/ImmutableX/StarkKeyTests.cpp @@ -54,7 +54,7 @@ TEST(ImmutableX, GetPrivateKeyFromSignature) { TEST(ImmutableX, GetPublicKeyFromPrivateKey) { auto privKeyData = parse_hex("058ab7989d625b1a690400dcbe6e070627adedceff7bd196e58d4791026a8afe", true); - PrivateKey privKey(privKeyData); + PrivateKey privKey(privKeyData, TWCurveStarkex); auto pubKey = privKey.getPublicKey(TWPublicKeyTypeStarkex); auto pubKeyHex = hexEncoded(pubKey.bytes); ASSERT_EQ(pubKeyHex, "0x02a4c7332c55d6c1c510d24272d1db82878f2302f05b53bcc38695ed5f78fffd"); @@ -62,9 +62,9 @@ TEST(ImmutableX, GetPublicKeyFromPrivateKey) { TEST(ImmutableX, SimpleSign) { auto privKeyBytes = parse_hex("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79"); - PrivateKey privKey(privKeyBytes); + PrivateKey privKey(privKeyBytes, TWCurveStarkex); auto digest = parse_hex("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76"); - auto signature = hex(privKey.sign(digest, TWCurve::TWCurveStarkex)); + auto signature = hex(privKey.sign(digest)); auto expectedSignature = "061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a"; ASSERT_EQ(signature.size(), 128ULL); ASSERT_EQ(signature.substr(0, 64), "061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f"); diff --git a/tests/chains/IoTeX/AddressTests.cpp b/tests/chains/IoTeX/AddressTests.cpp index fd94363fdff..92960150ec7 100644 --- a/tests/chains/IoTeX/AddressTests.cpp +++ b/tests/chains/IoTeX/AddressTests.cpp @@ -4,6 +4,7 @@ #include +#include "TestUtilities.h" #include "HexCoding.h" #include "PrivateKey.h" #include "PublicKey.h" @@ -35,7 +36,7 @@ TEST(IoTeXAddress, FromString) { } TEST(IoTeXAddress, FromPrivateKey) { - const auto privateKey = PrivateKey(parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f")); + const auto privateKey = PrivateKey(parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"), TWCoinTypeCurve(TWCoinTypeIoTeX)); const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); const auto address = Address(publicKey); ASSERT_EQ(address.string(), "io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j"); diff --git a/tests/chains/IoTeX/SignerTests.cpp b/tests/chains/IoTeX/SignerTests.cpp index bb743500c66..7e4951cbab7 100644 --- a/tests/chains/IoTeX/SignerTests.cpp +++ b/tests/chains/IoTeX/SignerTests.cpp @@ -4,12 +4,14 @@ #include -#include "HexCoding.h" #include "Hash.h" +#include "HexCoding.h" #include "IoTeX/Address.h" #include "IoTeX/Signer.h" #include "proto/IoTeX.pb.h" +#include + namespace TW::IoTeX { TEST(IoTeXSigner, Sign) { @@ -84,7 +86,7 @@ TEST(IoTeXSigner, Compile) { ASSERT_EQ(hex(sig), checkSig); //build compile - auto k = PrivateKey(key); + auto k = PrivateKey(key, TWCoinTypeCurve(TWCoinTypeIoTeX)); PublicKey pk = k.getPublicKey(TWPublicKeyTypeSECP256k1Extended); //merge hash data and signature auto output = signer.compile(input, sig, pk); diff --git a/tests/chains/IoTeX/TransactionCompilerTests.cpp b/tests/chains/IoTeX/TransactionCompilerTests.cpp index 4c25d7e5431..d7b12a6017a 100644 --- a/tests/chains/IoTeX/TransactionCompilerTests.cpp +++ b/tests/chains/IoTeX/TransactionCompilerTests.cpp @@ -43,10 +43,10 @@ TEST(TransactionCompiler, IoTeXCompileWithSignatures) { "0d1d3799c4700a0b1be21a41de4be56ce74dce8e526590f5b5f947385b00947c4c2ead014429aa706a2470055c" "56c7e57d1b119b487765d59b21bcdeafac25108f6929a14f9edf4b2309534501"; - const auto prkey0 = PrivateKey(privateKey0); + const auto prkey0 = PrivateKey(privateKey0, TWCoinTypeCurve(TWCoinTypeIoTeX)); const PublicKey pbkey0 = prkey0.getPublicKey(TWPublicKeyTypeSECP256k1Extended); - const auto prkey1 = PrivateKey(privateKey1); + const auto prkey1 = PrivateKey(privateKey1, TWCoinTypeCurve(TWCoinTypeIoTeX)); const PublicKey pbkey1 = prkey1.getPublicKey(TWPublicKeyTypeSECP256k1Extended); /// Step 1: Prepare transaction input (protobuf) @@ -101,7 +101,7 @@ TEST(TransactionCompiler, IoTeXCompileWithSignatures) { // keys were not used anywhere up to this point. TW::IoTeX::Proto::SigningInput signingInput; ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); - EXPECT_EQ(hex(PrivateKey(privateKey0).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + EXPECT_EQ(hex(PrivateKey(privateKey0, TWCoinTypeCurve(TWCoinTypeIoTeX)).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), hex(pubKey0)); signingInput.set_privatekey(prkey0.bytes.data(), prkey0.bytes.size()); TW::IoTeX::Proto::SigningOutput output; @@ -113,7 +113,7 @@ TEST(TransactionCompiler, IoTeXCompileWithSignatures) { { // more signatures TW::IoTeX::Proto::SigningInput signingInput; ASSERT_TRUE(signingInput.ParseFromArray(inputStrData.data(), (int)inputStrData.size())); - EXPECT_EQ(hex(PrivateKey(privateKey1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + EXPECT_EQ(hex(PrivateKey(privateKey1, TWCoinTypeCurve(TWCoinTypeIoTeX)).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), hex(pubKey1)); signingInput.set_privatekey(prkey1.bytes.data(), prkey1.bytes.size()); TW::IoTeX::Proto::SigningOutput output; diff --git a/tests/chains/Kusama/TWAnyAddressTests.cpp b/tests/chains/Kusama/TWAnyAddressTests.cpp index 426c15989de..c7491934fa5 100644 --- a/tests/chains/Kusama/TWAnyAddressTests.cpp +++ b/tests/chains/Kusama/TWAnyAddressTests.cpp @@ -35,7 +35,7 @@ TEST(KusamaAddress, Validation) { TEST(KusamaAddress, FromPrivateKey) { // from subkey: tiny escape drive pupil flavor endless love walk gadget match filter luxury - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("0xa21981f3bb990c40837df44df639541ff57c5e600f9eb4ac00ed8d1f718364e5").get())); + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("0xa21981f3bb990c40837df44df639541ff57c5e600f9eb4ac00ed8d1f718364e5").get(), TWCoinTypeCurve(TWCoinTypeKusama))); const auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeKusama)); const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeKusama)); const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); diff --git a/tests/chains/Litecoin/TWLitecoinTests.cpp b/tests/chains/Litecoin/TWLitecoinTests.cpp index f85584a571a..a299be82d05 100644 --- a/tests/chains/Litecoin/TWLitecoinTests.cpp +++ b/tests/chains/Litecoin/TWLitecoinTests.cpp @@ -16,7 +16,7 @@ namespace TW::Litecoin::tests { TEST(Litecoin, LegacyAddress) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get(), TWCoinTypeCurve(TWCoinTypeLitecoin))); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeLitecoin))); auto addressString = WRAPS(TWBitcoinAddressDescription(address.get())); @@ -24,7 +24,7 @@ TEST(Litecoin, LegacyAddress) { } TEST(Litecoin, Address) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625").get(), TWCoinTypeCurve(TWCoinTypeLitecoin))); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeLitecoin)); auto string = WRAPS(TWAnyAddressDescription(address.get())); diff --git a/tests/chains/Monacoin/TWMonacoinAddressTests.cpp b/tests/chains/Monacoin/TWMonacoinAddressTests.cpp index a4ef2fa7fca..96adfcbf806 100644 --- a/tests/chains/Monacoin/TWMonacoinAddressTests.cpp +++ b/tests/chains/Monacoin/TWMonacoinAddressTests.cpp @@ -15,7 +15,7 @@ #include TEST(Monacoin, LegacyAddress) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get(), TWCoinTypeCurve(TWCoinTypeMonacoin))); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeMonacoin))); auto addressString = WRAPS(TWBitcoinAddressDescription(address.get())); @@ -23,7 +23,7 @@ TEST(Monacoin, LegacyAddress) { } TEST(Monacoin, Address) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625").get(), TWCoinTypeCurve(TWCoinTypeMonacoin))); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); auto address = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithPublicKey(TWHRPMonacoin, publicKey.get())); auto string = WRAPS(TWSegwitAddressDescription(address.get())); diff --git a/tests/chains/MultiversX/AddressTests.cpp b/tests/chains/MultiversX/AddressTests.cpp index 2135f4678bd..e0d36af87b6 100644 --- a/tests/chains/MultiversX/AddressTests.cpp +++ b/tests/chains/MultiversX/AddressTests.cpp @@ -47,11 +47,11 @@ TEST(MultiversXAddress, FromData) { } TEST(MultiversXAddress, FromPrivateKey) { - auto aliceKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto aliceKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); auto alice = Address(aliceKey.getPublicKey(TWPublicKeyTypeED25519)); ASSERT_EQ(ALICE_BECH32, alice.string()); - auto bobKey = PrivateKey(parse_hex(BOB_SEED_HEX)); + auto bobKey = PrivateKey(parse_hex(BOB_SEED_HEX), TWCurveED25519); auto bob = Address(bobKey.getPublicKey(TWPublicKeyTypeED25519)); ASSERT_EQ(BOB_BECH32, bob.string()); } diff --git a/tests/chains/MultiversX/SignerTests.cpp b/tests/chains/MultiversX/SignerTests.cpp index 09a7ce4e1ad..3554757be70 100644 --- a/tests/chains/MultiversX/SignerTests.cpp +++ b/tests/chains/MultiversX/SignerTests.cpp @@ -22,7 +22,7 @@ namespace TW::MultiversX::tests { TEST(MultiversXSigner, SignGenericAction) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); @@ -59,7 +59,7 @@ TEST(MultiversXSigner, SignGenericAction) { TEST(MultiversXSigner, SignGenericActionUnDelegate) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(6); @@ -96,7 +96,7 @@ TEST(MultiversXSigner, SignGenericActionUnDelegate) { TEST(MultiversXSigner, SignGenericActionRedelegateRewards) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); @@ -132,7 +132,7 @@ TEST(MultiversXSigner, SignGenericActionRedelegateRewards) { TEST(MultiversXSigner, SignGenericActionClaimRewards) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); @@ -168,7 +168,7 @@ TEST(MultiversXSigner, SignGenericActionClaimRewards) { TEST(MultiversXSigner, SignGenericActionWithdrawStake) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); @@ -205,7 +205,7 @@ TEST(MultiversXSigner, SignGenericActionWithdrawStake) { TEST(MultiversXSigner, SignGenericActionDelegate) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(1); @@ -259,7 +259,7 @@ TEST(MultiversXSigner, SignGenericActionJSON) { "chainId": "1" })"; - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); auto encoded = Signer::signJSON(input, privateKey.bytes); nlohmann::json expected = R"( { @@ -280,7 +280,7 @@ TEST(MultiversXSigner, SignGenericActionJSON) { TEST(MultiversXSigner, SignWithoutData) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(0); @@ -332,7 +332,7 @@ TEST(MultiversXSigner, SignJSONWithoutData) { "chainId": "1" })"; - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); auto encoded = Signer::signJSON(input, privateKey.bytes); nlohmann::json expected = R"( { @@ -354,7 +354,7 @@ TEST(MultiversXSigner, SignWithUsernames) { // https://github.com/multiversx/mx-chain-go/blob/master/examples/construction_test.go, scenario "TestConstructTransaction_Usernames". auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(89); @@ -394,7 +394,7 @@ TEST(MultiversXSigner, SignWithUsernames) { TEST(MultiversXSigner, SignWithOptions) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(89); @@ -434,7 +434,7 @@ TEST(MultiversXSigner, SignWithOptions) { TEST(MultiversXSigner, SignEGLDTransfer) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_egld_transfer()->mutable_accounts()->set_sender_nonce(7); @@ -465,7 +465,7 @@ TEST(MultiversXSigner, SignEGLDTransfer) { TEST(MultiversXSigner, SignESDTTransfer) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_esdt_transfer()->mutable_accounts()->set_sender_nonce(7); @@ -500,7 +500,7 @@ TEST(MultiversXSigner, SignESDTTransfer) { TEST(MultiversXSigner, SignESDTNFTTransfer) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_esdtnft_transfer()->mutable_accounts()->set_sender_nonce(7); @@ -536,7 +536,7 @@ TEST(MultiversXSigner, SignESDTNFTTransfer) { TEST(MultiversXSigner, SignGenericActionWithGuardian) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(42); @@ -576,7 +576,7 @@ TEST(MultiversXSigner, SignGenericActionWithGuardian) { TEST(MultiversXSigner, SignEGLDTransferWithGuardian) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_egld_transfer()->mutable_accounts()->set_sender_nonce(7); @@ -610,7 +610,7 @@ TEST(MultiversXSigner, SignEGLDTransferWithGuardian) { TEST(MultiversXSigner, SignGenericActionWithRelayer) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(42); @@ -648,7 +648,7 @@ TEST(MultiversXSigner, SignGenericActionWithRelayer) { TEST(MultiversXSigner, SignEGLDTransferWithRelayer) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_egld_transfer()->mutable_accounts()->set_sender_nonce(7); @@ -706,10 +706,10 @@ TEST(ElrondSigner, buildSigningOutput) { input.set_gas_price(1000000000); input.set_gas_limit(50000); input.set_chain_id("1"); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); auto unsignedTxBytes = Signer::buildUnsignedTxBytes(input); - auto signature = privateKey.sign(unsignedTxBytes, TWCurveED25519); + auto signature = privateKey.sign(unsignedTxBytes); auto output = Signer::buildSigningOutput(input, signature); std::string expectedSignatureHex = "e8647dae8b16e034d518a1a860c6a6c38d16192d0f1362833e62424f424e5da660770dff45f4b951d9cc58bfb9d14559c977d443449bfc4b8783ff9c84065700"; diff --git a/tests/chains/MultiversX/TWAnySignerTests.cpp b/tests/chains/MultiversX/TWAnySignerTests.cpp index 1b1311b1382..359b8622407 100644 --- a/tests/chains/MultiversX/TWAnySignerTests.cpp +++ b/tests/chains/MultiversX/TWAnySignerTests.cpp @@ -16,7 +16,7 @@ namespace TW::MultiversX::tests { TEST(TWAnySignerMultiversX, Sign) { auto input = Proto::SigningInput(); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.mutable_generic_action()->mutable_accounts()->set_sender_nonce(7); diff --git a/tests/chains/MultiversX/TransactionCompilerTests.cpp b/tests/chains/MultiversX/TransactionCompilerTests.cpp index 438add4c4c6..9df91a44432 100644 --- a/tests/chains/MultiversX/TransactionCompilerTests.cpp +++ b/tests/chains/MultiversX/TransactionCompilerTests.cpp @@ -50,7 +50,7 @@ TEST(MultiversXCompiler, CompileGenericActionWithSignatures) { auto preImage = data(preSigningOutput.data()); auto preImageHash = data(preSigningOutput.data_hash()); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); const PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); Data signature = parse_hex("4f0eb7dca9177f1849bc98b856ab4b3238a666abb3369b4fc0faba429b5c91c46b06893e841a8f411aa199c78cc456514abe39948108baf83a7be0b3fae9d70a"); // Verify signature (pubkey & hash & signature) @@ -123,7 +123,7 @@ TEST(MultiversXCompiler, CompileEGLDTransferWithSignatures) { auto preImage = data(preSigningOutput.data()); auto preImageHash = data(preSigningOutput.data_hash()); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); const PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); Data signature = parse_hex("e55ad0642c7d47806410c12b1c93eb6250ccb76f711bbf82c5963bf59b5cdfe291d8b083b75de526f20457eede0c8a1dacf65c2c0034d47560c3bab5319c4006"); // Verify signature (pubkey & hash & signature) @@ -195,7 +195,7 @@ TEST(MultiversXCompiler, CompileESDTTransferWithSignatures) { auto preImage = data(preSigningOutput.data()); auto preImageHash = data(preSigningOutput.data_hash()); - auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX)); + auto privateKey = PrivateKey(parse_hex(ALICE_SEED_HEX), TWCurveED25519); const PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); Data signature = parse_hex("99314010bca2251e52f3a8c2efae2f02b81cb27c83edbaf553e7fb771ffbe69e99ac6304bdc8477ff6727e6e6a47b3d5e17c5537859e21d06a81ec8d632ad100"); // Verify signature (pubkey & hash & signature) diff --git a/tests/chains/NEAR/AddressTests.cpp b/tests/chains/NEAR/AddressTests.cpp index 7ae6b77a671..1ced8e7702d 100644 --- a/tests/chains/NEAR/AddressTests.cpp +++ b/tests/chains/NEAR/AddressTests.cpp @@ -2,9 +2,11 @@ // // Copyright © 2017 Trust Wallet. -#include "NEAR/Address.h" #include "Base58.h" +#include "NEAR/Address.h" #include "PrivateKey.h" + +#include #include #include @@ -38,7 +40,7 @@ TEST(NEARAddress, FromString) { TEST(NEARAddress, FromPrivateKey) { auto fullKey = Base58::decode("3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv"); - auto key = PrivateKey(Data(fullKey.begin(), fullKey.begin() + 32)); + auto key = PrivateKey(Data(fullKey.begin(), fullKey.begin() + 32), TWCoinTypeCurve(TWCoinTypeNEAR)); auto publicKey = key.getPublicKey(TWPublicKeyTypeED25519); auto address = Address(publicKey); diff --git a/tests/chains/NEO/AddressTests.cpp b/tests/chains/NEO/AddressTests.cpp index af24375ebfe..4a6ff91f007 100644 --- a/tests/chains/NEO/AddressTests.cpp +++ b/tests/chains/NEO/AddressTests.cpp @@ -63,7 +63,7 @@ TEST(NEOAddress, Invalid) { } TEST(NEOAddress, FromPrivateKey) { - auto key = PrivateKey(parse_hex("0x2A9EAB0FEC93CD94FA0A209AC5604602C1F0105FB02EAB398E17B4517C2FFBAB")); + auto key = PrivateKey(parse_hex("0x2A9EAB0FEC93CD94FA0A209AC5604602C1F0105FB02EAB398E17B4517C2FFBAB"), TWCurveNIST256p1); auto publicKey = key.getPublicKey(TWPublicKeyTypeNIST256p1); auto address = Address(publicKey); ASSERT_EQ(address.string(), "AQCSMB3oSDA1dHPn6GXN6KB4NHmdo1fX41"); diff --git a/tests/chains/NEO/SignerTests.cpp b/tests/chains/NEO/SignerTests.cpp index 55a214e09ae..e47d188cfef 100644 --- a/tests/chains/NEO/SignerTests.cpp +++ b/tests/chains/NEO/SignerTests.cpp @@ -17,7 +17,7 @@ using namespace std; TEST(NEOSigner, FromPublicPrivateKey) { auto hexPrvKey = "4646464646464646464646464646464646464646464646464646464646464646"; auto hexPubKey = "031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486"; - auto signer = Signer(PrivateKey(parse_hex(hexPrvKey))); + auto signer = Signer(PrivateKey(parse_hex(hexPrvKey), TWCurveNIST256p1)); auto prvKey = signer.getPrivateKey(); auto pubKey = signer.getPublicKey(); @@ -31,7 +31,7 @@ TEST(NEOSigner, FromPublicPrivateKey) { } TEST(NEOSigner, SigningData) { - auto signer = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); + auto signer = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"), TWCurveNIST256p1)); auto verScript = "ba7908ddfe5a1177f2c9d3fa1d3dc71c9c289a3325b3bdd977e20c50136959ed02d1411efa5e8b897d970ef7e2325e6c0a3fdee4eb421223f0d86e455879a9ad"; auto invocationScript = string("401642b3d538e138f34b32330e381a7fe3f5151fcf958f2030991e72e2e25043143e4a1ebd239634efba279c96fa0ab04a15aa15179d73a7ef5a886ac8a06af484401642b3d538e138f34b32330e381a7fe3f5151fcf958f2030991e72e2e25043143e4a1ebd239634efba279c96fa0ab04a15aa15179d73a7ef5a886ac8a06af484401642b3d538e138f34b32330e381a7fe3f5151fcf958f2030991e72e2e25043143e4a1ebd239634efba279c96fa0ab04a15aa15179d73a7ef5a886ac8a06af484"); invocationScript = string(invocationScript.rbegin(), invocationScript.rend()); @@ -42,7 +42,7 @@ TEST(NEOSigner, SigningData) { TEST(NEOAccount, validity) { auto hexPrvKey = "4646464646464646464646464646464646464646464646464646464646464646"; auto hexPubKey = "031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486"; - auto signer = Signer(PrivateKey(parse_hex(hexPrvKey))); + auto signer = Signer(PrivateKey(parse_hex(hexPrvKey), TWCurveNIST256p1)); auto prvKey = signer.getPrivateKey(); auto pubKey = signer.getPublicKey(); EXPECT_EQ(hexPrvKey, hex(prvKey.bytes)); @@ -50,7 +50,7 @@ TEST(NEOAccount, validity) { } TEST(NEOSigner, SigningTransaction) { - auto privateKey = PrivateKey(parse_hex("F18B2F726000E86B4950EBEA7BFF151F69635951BC4A31C44F28EE6AF7AEC128")); + auto privateKey = PrivateKey(parse_hex("F18B2F726000E86B4950EBEA7BFF151F69635951BC4A31C44F28EE6AF7AEC128"), TWCurveNIST256p1); auto signer = Signer(privateKey); auto transaction = Transaction(); transaction.type = TransactionType::TT_ContractTransaction; diff --git a/tests/chains/NEO/TransactionCompilerTests.cpp b/tests/chains/NEO/TransactionCompilerTests.cpp index 121919c7eda..39e97ef60b6 100644 --- a/tests/chains/NEO/TransactionCompilerTests.cpp +++ b/tests/chains/NEO/TransactionCompilerTests.cpp @@ -34,7 +34,7 @@ TEST(NEOCompiler, CompileWithSignatures) { TW::NEO::Proto::SigningInput input; auto privateKey = - PrivateKey(parse_hex("F18B2F726000E86B4950EBEA7BFF151F69635951BC4A31C44F28EE6AF7AEC128")); + PrivateKey(parse_hex("F18B2F726000E86B4950EBEA7BFF151F69635951BC4A31C44F28EE6AF7AEC128"), TWCoinTypeCurve(coin)); auto publicKey = privateKey.getPublicKey(publicKeyType(coin)); input.set_gas_asset_id(GAS_ASSET_ID); input.set_gas_change_address("AdtSLMBqACP4jv8tRWwyweXGpyGG46eMXV"); diff --git a/tests/chains/NULS/AddressTests.cpp b/tests/chains/NULS/AddressTests.cpp index 542d939d183..6e4d13f8f69 100644 --- a/tests/chains/NULS/AddressTests.cpp +++ b/tests/chains/NULS/AddressTests.cpp @@ -5,6 +5,8 @@ #include "NULS/Address.h" #include "HexCoding.h" #include "PrivateKey.h" +#include "TestUtilities.h" + #include namespace TW::NULS::tests { @@ -41,7 +43,7 @@ TEST(NULSAddress, FromString) { TEST(NULSAddress, FromPrivateKey) { const auto privateKey = - PrivateKey(parse_hex("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a")); + PrivateKey(parse_hex("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a"), TWCoinTypeCurve(TWCoinTypeNULS)); const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); const auto address = Address(publicKey, true); ASSERT_EQ(address.string(), "NULSd6HghWa4CN5qdxqMwYVikQxRZyj57Jn4L"); @@ -58,7 +60,7 @@ TEST(NULSAddress, FromCompressedPublicKey) { } TEST(NULSAddress, FromPrivateKey33) { - const auto privateKey = PrivateKey(parse_hex("d77580833f0b3c35b7114c23d6b66790d726c308baf237ec8c369152f2c08d27")); + const auto privateKey = PrivateKey(parse_hex("d77580833f0b3c35b7114c23d6b66790d726c308baf237ec8c369152f2c08d27"), TWCoinTypeCurve(TWCoinTypeNULS)); const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); const auto address = Address(publicKey, true); diff --git a/tests/chains/NULS/TWAnySignerTests.cpp b/tests/chains/NULS/TWAnySignerTests.cpp index d82048ada7a..fa2031bfded 100644 --- a/tests/chains/NULS/TWAnySignerTests.cpp +++ b/tests/chains/NULS/TWAnySignerTests.cpp @@ -91,7 +91,7 @@ TEST(TWAnySigner, SignWithFeePayer) { parse_hex("0x48c91cd24a27a1cdc791022ff39316444229db1c466b3b1841b40c919dee3002"); auto feePayerPrivateKey = parse_hex("0x9401fd554cb700777e57b05338f9ff47597add8b23ce9f1c8e041e9b4e2116b6"); - auto pk = PrivateKey(privateKey); + auto pk = PrivateKey(privateKey, TWCoinTypeCurve(TWCoinTypeNULS)); auto amount = store(uint256_t(100000)); auto balance = store(uint256_t(1000000)); auto feePayerBalance = store(uint256_t(1000000)); diff --git a/tests/chains/Nano/SignerTests.cpp b/tests/chains/Nano/SignerTests.cpp index c28645d0794..359a643c59b 100644 --- a/tests/chains/Nano/SignerTests.cpp +++ b/tests/chains/Nano/SignerTests.cpp @@ -14,7 +14,7 @@ using namespace TW; namespace TW::Nano::tests { TEST(NanoSigner, sign1) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); auto input = Proto::SigningInput(); @@ -44,7 +44,7 @@ TEST(NanoSigner, sign1) { } TEST(NanoSigner, sign2) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto parentBlock = parse_hex("f9a323153daefe041efb94d69b9669c882c935530ed953bbe8a665dfedda9696"); auto input = Proto::SigningInput(); @@ -61,7 +61,7 @@ TEST(NanoSigner, sign2) { } TEST(NanoSigner, sign3) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto parentBlock = parse_hex("2568bf76336f7a415ca236dab97c1df9de951ca057a2e79df1322e647a259e7b"); const auto linkBlock = parse_hex("d7384845d2ae530b45a5dd50ee50757f988329f652781767af3f1bc2322f52b9"); @@ -92,7 +92,7 @@ TEST(NanoSigner, sign3) { } TEST(NanoSigner, sign4) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto parentBlock = parse_hex("1ca240212838d053ecaa9dceee598c52a6080067edecaeede3319eb0b7db6525"); auto input = Proto::SigningInput(); @@ -110,7 +110,7 @@ TEST(NanoSigner, sign4) { } TEST(NanoSigner, signInvalid1) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); // Missing link_block auto input = Proto::SigningInput(); @@ -122,7 +122,7 @@ TEST(NanoSigner, signInvalid1) { } TEST(NanoSigner, signInvalid2) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); // Missing representative @@ -135,7 +135,7 @@ TEST(NanoSigner, signInvalid2) { } TEST(NanoSigner, signInvalid3) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); // Missing balance @@ -148,7 +148,7 @@ TEST(NanoSigner, signInvalid3) { } TEST(NanoSigner, signInvalid4) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); // Account first block cannot be 0 balance @@ -162,7 +162,7 @@ TEST(NanoSigner, signInvalid4) { } TEST(NanoSigner, signInvalid5) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); // First block must use link_block not link_recipient auto input = Proto::SigningInput(); @@ -175,7 +175,7 @@ TEST(NanoSigner, signInvalid5) { } TEST(NanoSigner, signInvalid6) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); // Invalid representative value @@ -189,7 +189,7 @@ TEST(NanoSigner, signInvalid6) { } TEST(NanoSigner, signInvalid7) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto parentBlock = parse_hex("f9a323153daefe041efb94d69b9669c882c935530ed953bbe8a665dfedda9696"); auto input = Proto::SigningInput(); @@ -203,7 +203,7 @@ TEST(NanoSigner, signInvalid7) { } TEST(NanoSigner, buildUnsignedTxBytes) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b); const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); @@ -218,7 +218,7 @@ TEST(NanoSigner, buildUnsignedTxBytes) { } TEST(NanoSigner, buildSigningOutput) { - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b); const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); diff --git a/tests/chains/Nano/TransactionCompilerTests.cpp b/tests/chains/Nano/TransactionCompilerTests.cpp index 88ec1cda1ca..422a846c9dc 100644 --- a/tests/chains/Nano/TransactionCompilerTests.cpp +++ b/tests/chains/Nano/TransactionCompilerTests.cpp @@ -28,7 +28,7 @@ namespace TW::Nano::tests { TEST(NanoCompiler, CompileWithSignatures) { /// Step 1 : Prepare transaction input(protobuf) auto coin = TWCoinTypeNano; - const auto privateKey = PrivateKey(parse_hex(kPrivateKey)); + const auto privateKey = PrivateKey(parse_hex(kPrivateKey), TWCurveED25519Blake2bNano); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b); const auto linkBlock = parse_hex("491fca2c69a84607d374aaf1f6acd3ce70744c5be0721b5ed394653e85233507"); diff --git a/tests/chains/Nebl/AddressTests.cpp b/tests/chains/Nebl/AddressTests.cpp index 932f9dfd9b4..3a64c458302 100644 --- a/tests/chains/Nebl/AddressTests.cpp +++ b/tests/chains/Nebl/AddressTests.cpp @@ -25,7 +25,7 @@ TEST(NeblAddress, Invalid) { } TEST(NeblAddress, FromPrivateKey) { - auto privateKey = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + auto privateKey = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9"), TWCoinTypeCurve(TWCoinTypeNebl)); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); auto address = Address(publicKey, TWCoinTypeP2pkhPrefix(TWCoinTypeNebl)); ASSERT_EQ(address.string(), "NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc"); diff --git a/tests/chains/Nebl/SignerTests.cpp b/tests/chains/Nebl/SignerTests.cpp index eb8cad61749..637e83bff97 100644 --- a/tests/chains/Nebl/SignerTests.cpp +++ b/tests/chains/Nebl/SignerTests.cpp @@ -44,7 +44,7 @@ TEST(NeblSigner, Sign) { utxo0->mutable_out_point()->set_sequence(4294967294); utxo0->set_amount(2500000000); - auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9"), TWCoinTypeCurve(TWCoinTypeNebl)); auto script0 = Bitcoin::Script::lockScriptForAddress("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc", TWCoinTypeNebl); ASSERT_EQ(hex(script0.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); utxo0->set_script(script0.bytes.data(), script0.bytes.size()); @@ -106,7 +106,7 @@ TEST(NeblSigner, SignAnyoneCanPay) { ASSERT_EQ(hex(script1.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); utxo1->set_script(script1.bytes.data(), script1.bytes.size()); - auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9"), TWCoinTypeCurve(TWCoinTypeNebl)); input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); auto plan = TransactionBuilder::plan(input); diff --git a/tests/chains/Nebl/TWAnySignerTests.cpp b/tests/chains/Nebl/TWAnySignerTests.cpp index 745d6ed792c..e420d8684b4 100644 --- a/tests/chains/Nebl/TWAnySignerTests.cpp +++ b/tests/chains/Nebl/TWAnySignerTests.cpp @@ -44,7 +44,7 @@ TEST(TWAnySignerNebl, Sign) { utxo0->mutable_out_point()->set_sequence(4294967294); utxo0->set_amount(2500000000); - auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9"), TWCoinTypeCurve(TWCoinTypeNebl)); auto script0 = Bitcoin::Script::lockScriptForAddress("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc", TWCoinTypeNebl); ASSERT_EQ(hex(script0.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); utxo0->set_script(script0.bytes.data(), script0.bytes.size()); diff --git a/tests/chains/Nebl/TransactionBuilderTests.cpp b/tests/chains/Nebl/TransactionBuilderTests.cpp index 09bdcb8218d..03762bc2146 100644 --- a/tests/chains/Nebl/TransactionBuilderTests.cpp +++ b/tests/chains/Nebl/TransactionBuilderTests.cpp @@ -39,7 +39,7 @@ TEST(NeblTransactionBuilder, BuildWithTime) { utxo0->mutable_out_point()->set_sequence(4294967294); utxo0->set_amount(2500000000); - auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9"), TWCoinTypeCurve(TWCoinTypeNebl)); auto script0 = Bitcoin::Script::lockScriptForAddress("NboLGGKWtK5eXzaah5GVpXju9jCcoMi4cc", TWCoinTypeNebl); ASSERT_EQ(hex(script0.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); utxo0->set_script(script0.bytes.data(), script0.bytes.size()); diff --git a/tests/chains/Nebl/TransactionCompilerTests.cpp b/tests/chains/Nebl/TransactionCompilerTests.cpp index 30669f9ad36..6cfdb58ae3a 100644 --- a/tests/chains/Nebl/TransactionCompilerTests.cpp +++ b/tests/chains/Nebl/TransactionCompilerTests.cpp @@ -54,7 +54,7 @@ TEST(NeblCompiler, CompileWithSignatures) { ASSERT_EQ(hex(script0.bytes), "76a914ae40b2142aba5ddd10f74d9440bfda8a36cbad5b88ac"); utxo0->set_script(script0.bytes.data(), script0.bytes.size()); - auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9")); + auto utxoKey0 = PrivateKey(parse_hex("4222aae79af41eade7b07ce6fd44d926ea8e3f95e51a06e85f8bdec89680cbd9"), TWCoinTypeCurve(TWCoinTypeNebl)); input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); EXPECT_EQ(input.utxo_size(), 1); diff --git a/tests/chains/Nebulas/AddressTests.cpp b/tests/chains/Nebulas/AddressTests.cpp index 8efe6395086..8a0b1f81ef2 100644 --- a/tests/chains/Nebulas/AddressTests.cpp +++ b/tests/chains/Nebulas/AddressTests.cpp @@ -5,6 +5,8 @@ #include "Nebulas/Address.h" #include "HexCoding.h" #include "PrivateKey.h" +#include "TestUtilities.h" + #include namespace TW::Nebulas::tests { @@ -40,7 +42,7 @@ TEST(NebulasAddress, Data) { } TEST(NebulasAddress, FromPrivateKey) { - const auto privateKey = PrivateKey(parse_hex("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b")); + const auto privateKey = PrivateKey(parse_hex("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b"), TWCoinTypeCurve(TWCoinTypeNebulas)); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); const auto address = Address(publicKey); ASSERT_EQ(address.string(), "n1V5bB2tbaM3FUiL4eRwpBLgEredS5C2wLY"); diff --git a/tests/chains/Nebulas/TWNebulasAddressTests.cpp b/tests/chains/Nebulas/TWNebulasAddressTests.cpp index e41a4e54e09..b7b2f8e92db 100644 --- a/tests/chains/Nebulas/TWNebulasAddressTests.cpp +++ b/tests/chains/Nebulas/TWNebulasAddressTests.cpp @@ -12,7 +12,7 @@ TEST(Nebulas, Address) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b").get(), TWCoinTypeCurve(TWCoinTypeNebulas))); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), false)); auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeNebulas)); auto addressString = WRAPS(TWAnyAddressDescription(address.get())); diff --git a/tests/chains/Nebulas/TransactionTests.cpp b/tests/chains/Nebulas/TransactionTests.cpp index d937cb73734..8e8c107a549 100644 --- a/tests/chains/Nebulas/TransactionTests.cpp +++ b/tests/chains/Nebulas/TransactionTests.cpp @@ -28,7 +28,7 @@ TEST(NebulasTransaction, serialize) { /* timestamp: */ 1560052938, /* payload: */ std::string()); - const auto privateKey = PrivateKey(parse_hex("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b")); + const auto privateKey = PrivateKey(parse_hex("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b"), TWCurveSECP256k1); auto signer = Signer(1); signer.sign(privateKey, transaction); transaction.serializeToRaw(); @@ -49,7 +49,7 @@ TEST(NebulasTransaction, binaryPayload) { /* timestamp: */ 1560052938, /* payload: */ std::string("{\"binary\":\"test\"}")); - const auto privateKey = PrivateKey(parse_hex("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b")); + const auto privateKey = PrivateKey(parse_hex("d2fd0ec9f6268fc8d1f563e3e976436936708bdf0dc60c66f35890f5967a8d2b"), TWCurveSECP256k1); auto signer = Signer(1); signer.sign(privateKey, transaction); ASSERT_EQ(TW::Base64::encode(transaction.raw), "CiB1Oqj7bxLQMHEoNyg/vFHmsTrGdkpTf/5qFDkYPB3bkxIaGVefwtw23wEobqA40/7aIwQHghETxH4r+50aGhlXf89CeLWgHFjKu9/6tn4KNbelsMDAIIi2IhAAAAAAAAAAAJin2bgxTAAAKAcwyony5wU6PQoGYmluYXJ5EjN7IkRhdGEiOnsiZGF0YSI6WzExNiwxMDEsMTE1LDExNl0sInR5cGUiOiJCdWZmZXIifX1AAUoQAAAAAAAAAAAAAAAAAA9CQFIQAAAAAAAAAAAAAAAAAAMNQFgBYkGHXq+JWPaEyeB19bqL3QB5jyM961WLq7PMTpnGM4iLtBjCkngjS81kgPM2TE4qKDcpzqjum/NccrZtUPQLGk0MAQ=="); diff --git a/tests/chains/Nervos/AddressTests.cpp b/tests/chains/Nervos/AddressTests.cpp index 4ff4d1246d5..bf757ca9543 100644 --- a/tests/chains/Nervos/AddressTests.cpp +++ b/tests/chains/Nervos/AddressTests.cpp @@ -30,7 +30,7 @@ TEST(NervosAddress, Invalid) { TEST(NervosAddress, FromPrivateKey) { auto privateKey = - PrivateKey(parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb")); + PrivateKey(parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"), TWCoinTypeCurve(TWCoinTypeNervos)); auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); ASSERT_EQ(address.string(), "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqwyk5x9er" "g8furras980hksatlslfaktks7epf25"); @@ -60,7 +60,7 @@ TEST(NervosAddress, FromString) { TEST(TWNervosAddress, AddressFromPublicKey) { auto privateKey = - PrivateKey(parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb")); + PrivateKey(parse_hex("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb"), TWCoinTypeCurve(TWCoinTypeNervos)); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ(publicKey.bytes.size(), 33ul); auto address = Address(publicKey); diff --git a/tests/chains/Nervos/SignerTests.cpp b/tests/chains/Nervos/SignerTests.cpp index af862b5f84a..0a0f99b7a4b 100644 --- a/tests/chains/Nervos/SignerTests.cpp +++ b/tests/chains/Nervos/SignerTests.cpp @@ -23,7 +23,7 @@ std::vector getPrivateKeys(Proto::SigningInput& input) { std::vector privateKeys; privateKeys.reserve(input.private_key_size()); for (auto&& privateKey : input.private_key()) { - privateKeys.emplace_back(privateKey); + privateKeys.emplace_back(privateKey, TWCoinTypeCurve(TWCoinTypeNervos)); } return privateKeys; } diff --git a/tests/chains/Nervos/TWAnyAddressTests.cpp b/tests/chains/Nervos/TWAnyAddressTests.cpp index be1c5f92e01..3116a8c57bd 100644 --- a/tests/chains/Nervos/TWAnyAddressTests.cpp +++ b/tests/chains/Nervos/TWAnyAddressTests.cpp @@ -19,7 +19,7 @@ TEST(TWAnyAddressNervos, AddressFromPublicKey) { auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData( - DATA("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb").get())); + DATA("8a2a726c44e46d1efaa0f9c2a8efed932f0e96d6050b914fde762ee285e61feb").get(), TWCoinTypeCurve(TWCoinTypeNervos))); ASSERT_NE(nullptr, privateKey.get()); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); ASSERT_NE(nullptr, publicKey.get()); diff --git a/tests/chains/Nimiq/SignerTests.cpp b/tests/chains/Nimiq/SignerTests.cpp index d8a56bdb9cd..cdcd691351d 100644 --- a/tests/chains/Nimiq/SignerTests.cpp +++ b/tests/chains/Nimiq/SignerTests.cpp @@ -7,20 +7,21 @@ #include "Nimiq/Address.h" #include "Nimiq/Signer.h" #include "Nimiq/Transaction.h" +#include "TestUtilities.h" #include namespace TW::Nimiq { TEST(NimiqSigner, DerivePublicKey) { - const PrivateKey privateKey(parse_hex("e3cc33575834add098f8487123cd4bca543ee859b3e8cfe624e7e6a97202b756")); + const PrivateKey privateKey(parse_hex("e3cc33575834add098f8487123cd4bca543ee859b3e8cfe624e7e6a97202b756"), TWCoinTypeCurve(TWCoinTypeNimiq)); const PublicKey publicKey((privateKey.getPublicKey(TWPublicKeyTypeED25519))); const auto address = Address(publicKey); ASSERT_EQ(address.string(), "NQ27 GBAY EVHP HK5X 6JHV JGFJ 5M3H BF4Y G7GD"); } TEST(NimiqSigner, Sign) { - const PrivateKey privateKey(parse_hex("e3cc33575834add098f8487123cd4bca543ee859b3e8cfe624e7e6a97202b756")); + const PrivateKey privateKey(parse_hex("e3cc33575834add098f8487123cd4bca543ee859b3e8cfe624e7e6a97202b756"), TWCoinTypeCurve(TWCoinTypeNimiq)); const auto pubkey = privateKey.getPublicKey(TWPublicKeyTypeED25519); std::array pubkeyBytes; std::copy(pubkey.bytes.begin(), pubkey.bytes.end(), pubkeyBytes.data()); diff --git a/tests/chains/Nimiq/TransactionTests.cpp b/tests/chains/Nimiq/TransactionTests.cpp index cd6eaeb2b22..8a58a97dc29 100644 --- a/tests/chains/Nimiq/TransactionTests.cpp +++ b/tests/chains/Nimiq/TransactionTests.cpp @@ -6,13 +6,14 @@ #include "PrivateKey.h" #include "Nimiq/Address.h" #include "Nimiq/Transaction.h" +#include "TestUtilities.h" #include namespace TW::Nimiq { TEST(NimiqTransaction, PreImage) { - const PrivateKey privateKey(parse_hex("e3cc33575834add098f8487123cd4bca543ee859b3e8cfe624e7e6a97202b756")); + const PrivateKey privateKey(parse_hex("e3cc33575834add098f8487123cd4bca543ee859b3e8cfe624e7e6a97202b756"), TWCoinTypeCurve(TWCoinTypeNimiq)); const auto pubkey = privateKey.getPublicKey(TWPublicKeyTypeED25519); std::array pubkeyBytes; std::copy(pubkey.bytes.begin(), pubkey.bytes.end(), pubkeyBytes.data()); @@ -30,7 +31,7 @@ TEST(NimiqTransaction, PreImage) { } TEST(NimiqTransaction, Serialize) { - const PrivateKey privateKey(parse_hex("e3cc33575834add098f8487123cd4bca543ee859b3e8cfe624e7e6a97202b756")); + const PrivateKey privateKey(parse_hex("e3cc33575834add098f8487123cd4bca543ee859b3e8cfe624e7e6a97202b756"), TWCoinTypeCurve(TWCoinTypeNimiq)); const auto pubkey = privateKey.getPublicKey(TWPublicKeyTypeED25519); std::array pubkeyBytes; std::copy(pubkey.bytes.begin(), pubkey.bytes.end(), pubkeyBytes.data()); diff --git a/tests/chains/Oasis/AddressTests.cpp b/tests/chains/Oasis/AddressTests.cpp index a85dd59c24f..9396d9eb3ed 100644 --- a/tests/chains/Oasis/AddressTests.cpp +++ b/tests/chains/Oasis/AddressTests.cpp @@ -6,6 +6,8 @@ #include "Oasis/Address.h" #include "PrivateKey.h" #include "PublicKey.h" +#include "TestUtilities.h" + #include #include @@ -43,7 +45,7 @@ TEST(OasisAddress, FromWrongData) { } TEST(OasisAddress, FromPrivateKey) { - auto privateKey = PrivateKey(parse_hex("4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0")); + auto privateKey = PrivateKey(parse_hex("4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0"), TWCoinTypeCurve(TWCoinTypeOasis)); auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeED25519)); ASSERT_EQ(address.string(), "oasis1qzawzy5kaa2xgphenf3r0f5enpr3mx5dps559yxm"); } diff --git a/tests/chains/Oasis/TransactionCompilerTests.cpp b/tests/chains/Oasis/TransactionCompilerTests.cpp index 18edd6b58e9..2190f36df97 100644 --- a/tests/chains/Oasis/TransactionCompilerTests.cpp +++ b/tests/chains/Oasis/TransactionCompilerTests.cpp @@ -22,7 +22,7 @@ namespace TW::Oasis::tests { TEST(OasisCompiler, CompileWithSignatures) { const auto coin = TWCoinTypeOasis; /// Step 1: Prepare transaction input (protobuf) - auto privateKey = PrivateKey(parse_hex("4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0")); + auto privateKey = PrivateKey(parse_hex("4f8b5676990b00e23d9904a92deb8d8f428ff289c8939926358f1d20537c21a0"), TWCoinTypeCurve(TWCoinTypeOasis)); auto publicKey = privateKey.getPublicKey(publicKeyType(coin)); auto input = TW::Oasis::Proto::SigningInput(); diff --git a/tests/chains/Ontology/AccountTests.cpp b/tests/chains/Ontology/AccountTests.cpp index 1917eea454f..3693920515e 100644 --- a/tests/chains/Ontology/AccountTests.cpp +++ b/tests/chains/Ontology/AccountTests.cpp @@ -5,6 +5,7 @@ #include "Hash.h" #include "HexCoding.h" #include "PrivateKey.h" +#include "TestUtilities.h" #include "Ontology/Signer.h" @@ -16,7 +17,7 @@ namespace TW::Ontology::tests { TEST(OntologyAccount, validity) { auto hexPrvKey = "4646464646464646464646464646464646464646464646464646464646464646"; auto hexPubKey = "031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486"; - auto signer = Signer(PrivateKey(parse_hex(hexPrvKey))); + auto signer = Signer(PrivateKey(parse_hex(hexPrvKey), TWCoinTypeCurve(TWCoinTypeOntology))); auto prvKey = signer.getPrivateKey(); auto pubKey = signer.getPublicKey(); EXPECT_EQ(hexPrvKey, hex(prvKey.bytes)); diff --git a/tests/chains/Ontology/AddressTests.cpp b/tests/chains/Ontology/AddressTests.cpp index 559c5ebe5b0..b544cc726a0 100644 --- a/tests/chains/Ontology/AddressTests.cpp +++ b/tests/chains/Ontology/AddressTests.cpp @@ -4,7 +4,7 @@ #include "HexCoding.h" #include "PublicKey.h" - +#include "TestUtilities.h" #include "Ontology/Address.h" #include "Ontology/Signer.h" @@ -34,9 +34,9 @@ TEST(OntologyAddress, fromString) { } TEST(OntologyAddress, fromMultiPubKeys) { - auto signer1 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); - auto signer2 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"))); - auto signer3 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464658"))); + auto signer1 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"), TWCoinTypeCurve(TWCoinTypeOntology))); + auto signer2 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"), TWCoinTypeCurve(TWCoinTypeOntology))); + auto signer3 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464658"), TWCoinTypeCurve(TWCoinTypeOntology))); std::vector pubKeys{signer1.getPublicKey().bytes, signer2.getPublicKey().bytes, signer3.getPublicKey().bytes}; uint8_t m = 2; auto multiAddress = Address(m, pubKeys); diff --git a/tests/chains/Ontology/Oep4Tests.cpp b/tests/chains/Ontology/Oep4Tests.cpp index 6c43b61be89..5228c46a43a 100644 --- a/tests/chains/Ontology/Oep4Tests.cpp +++ b/tests/chains/Ontology/Oep4Tests.cpp @@ -6,6 +6,7 @@ #include "Ontology/Oep4.h" #include "Ontology/Signer.h" +#include "TestUtilities.h" #include #include @@ -71,8 +72,8 @@ TEST(OntologyOep4, addressHack) { auto ownerbin = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); auto payerbin = parse_hex("4646464646464646464646464646464646464646464646464646464646464652"); - PrivateKey owner(ownerbin); - PrivateKey payer(payerbin); + PrivateKey owner(ownerbin, TWCoinTypeCurve(TWCoinTypeOntology)); + PrivateKey payer(payerbin, TWCoinTypeCurve(TWCoinTypeOntology)); auto pubKey = owner.getPublicKey(TWPublicKeyTypeNIST256p1); Address addr(pubKey); @@ -85,10 +86,10 @@ TEST(OntologyOep4, addressHack) { } TEST(OntologyOep4, transfer) { - PrivateKey fromPrivate(parse_hex("4646464646464646464646464646464646464646464646464646464646464652")); + PrivateKey fromPrivate(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"), TWCoinTypeCurve(TWCoinTypeOntology)); Signer from(fromPrivate); - PrivateKey payerPrivate(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + PrivateKey payerPrivate(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"), TWCoinTypeCurve(TWCoinTypeOntology)); Signer payer(payerPrivate); auto toAddress = Address("AVY6LfvxauVQAVHDV9hC3ZCv7cQqzfDotH"); @@ -114,10 +115,10 @@ TEST(OntologyOep4, transfer) { TEST(OntologyOep4, transferMainnet) { auto from = Signer( - PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"))); + PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"), TWCoinTypeCurve(TWCoinTypeOntology))); auto payer = Signer( - PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); + PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"), TWCoinTypeCurve(TWCoinTypeOntology))); auto toAddress = Address("AUJJhwRNi4RsNfvuexLETxXEb6szu9D5Ad"); diff --git a/tests/chains/Ontology/OngTests.cpp b/tests/chains/Ontology/OngTests.cpp index 1aa603bebf9..4b5cb6f1f78 100644 --- a/tests/chains/Ontology/OngTests.cpp +++ b/tests/chains/Ontology/OngTests.cpp @@ -6,6 +6,8 @@ #include "Ontology/Ong.h" +#include + #include #include @@ -33,10 +35,10 @@ TEST(OntologyOng, balanceOf) { } TEST(OntologyOng, transfer) { - PrivateKey privateKey1(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + PrivateKey privateKey1(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"), TWCoinTypeCurve(TWCoinTypeOntology)); Signer signer1(privateKey1); - PrivateKey privateKey2(parse_hex("4646464646464646464646464646464646464646464646464646464646464652")); + PrivateKey privateKey2(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"), TWCoinTypeCurve(TWCoinTypeOntology)); Signer signer2(privateKey2); Address toAddress("Af1n2cZHhMZumNqKgw9sfCNoTWu9de4NDn"); @@ -72,10 +74,10 @@ TEST(OntologyOng, transfer) { } TEST(OntologyOng, withdraw) { - PrivateKey privateKey1(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + PrivateKey privateKey1(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"), TWCoinTypeCurve(TWCoinTypeOntology)); Signer signer1(privateKey1); - PrivateKey privateKey2(parse_hex("4646464646464646464646464646464646464646464646464646464646464652")); + PrivateKey privateKey2(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"), TWCoinTypeCurve(TWCoinTypeOntology)); Signer signer2(privateKey2); uint32_t nonce = 0; diff --git a/tests/chains/Ontology/OntTests.cpp b/tests/chains/Ontology/OntTests.cpp index 4d499ddbdcc..221f350fbe8 100644 --- a/tests/chains/Ontology/OntTests.cpp +++ b/tests/chains/Ontology/OntTests.cpp @@ -5,8 +5,10 @@ #include "HexCoding.h" #include "Ontology/Ont.h" -#include +#include + #include +#include namespace TW::Ontology::tests { @@ -32,10 +34,10 @@ TEST(OntologyOnt, queryBalance) { } TEST(OntologyOnt, transfer) { - PrivateKey privateKey1(parse_hex("4646464646464646464646464646464646464646464646464646464646464646")); + PrivateKey privateKey1(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"), TWCoinTypeCurve(TWCoinTypeOntology)); Signer signer1(privateKey1); - PrivateKey privateKey2(parse_hex("4646464646464646464646464646464646464646464646464646464646464652")); + PrivateKey privateKey2(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"), TWCoinTypeCurve(TWCoinTypeOntology)); Signer signer2(privateKey2); auto toAddress = Address("Af1n2cZHhMZumNqKgw9sfCNoTWu9de4NDn"); diff --git a/tests/chains/Ontology/TransactionTests.cpp b/tests/chains/Ontology/TransactionTests.cpp index 43def521309..13d84d6da9e 100644 --- a/tests/chains/Ontology/TransactionTests.cpp +++ b/tests/chains/Ontology/TransactionTests.cpp @@ -8,6 +8,7 @@ #include "Ontology/ParamsBuilder.h" #include "Ontology/Signer.h" #include "Ontology/Transaction.h" +#include "TestUtilities.h" #include @@ -37,7 +38,7 @@ TEST(OntologyTransaction, validity) { "f66a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f" "6e746f6c6f67792e4e61746976652e496e766f6b650000"; EXPECT_EQ(hexTx, hex(tx.serialize())); - auto signer1 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"))); + auto signer1 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464646"), TWCoinTypeCurve(TWCoinTypeOntology))); signer1.sign(tx); hexTx = "00d1e3388d5c5802000000000000e09304000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c6" @@ -48,7 +49,7 @@ TEST(OntologyTransaction, validity) { "21031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac"; EXPECT_EQ(520ul, hex(tx.serialize()).length()); EXPECT_EQ(hexTx.substr(0, 20), hex(tx.serialize()).substr(0, 20)); - auto signer2 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"))); + auto signer2 = Signer(PrivateKey(parse_hex("4646464646464646464646464646464646464646464646464646464646464652"), TWCoinTypeCurve(TWCoinTypeOntology))); signer2.addSign(tx); auto result = tx.serialize(); auto verifyPosition1 = diff --git a/tests/chains/Pactus/AddressTests.cpp b/tests/chains/Pactus/AddressTests.cpp index c3bcb0a260d..2710e27c21c 100644 --- a/tests/chains/Pactus/AddressTests.cpp +++ b/tests/chains/Pactus/AddressTests.cpp @@ -22,7 +22,7 @@ TEST(PactusAddress, AddressData) { } TEST(PactusAddress, FromPrivateKey) { - auto privateKey = PrivateKey(parse_hex("2134ae97465505dfd5a1fd05a8a0f146209c601eb3f1b0363b4cfe4b47ba1ab4")); + auto privateKey = PrivateKey(parse_hex("2134ae97465505dfd5a1fd05a8a0f146209c601eb3f1b0363b4cfe4b47ba1ab4"), TWCoinTypeCurve(TWCoinTypePactus)); auto pubkey = privateKey.getPublicKey(TWPublicKeyTypeED25519); Entry entry; auto address = entry.deriveAddress(TWCoinTypePactus, pubkey, TWDerivationDefault, std::monostate{}); diff --git a/tests/chains/Pactus/CompilerTests.cpp b/tests/chains/Pactus/CompilerTests.cpp index 0d4a902e2d9..c2ca6f52103 100644 --- a/tests/chains/Pactus/CompilerTests.cpp +++ b/tests/chains/Pactus/CompilerTests.cpp @@ -31,9 +31,9 @@ TEST(PactusCompiler, CompileAndSign) { EXPECT_EQ(hex(actualDataToSign), testCase.dataToSign); // Sign the pre-hash data. - auto privateKey = PrivateKey(parse_hex(PRIVATE_KEY_HEX)); + auto privateKey = PrivateKey(parse_hex(PRIVATE_KEY_HEX), TWCurveED25519); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; - auto signature = privateKey.sign(actualDataToSign, TWCurveED25519); + auto signature = privateKey.sign(actualDataToSign); EXPECT_EQ(hex(signature), testCase.signature); // Compile the transaction. diff --git a/tests/chains/Pactus/SignerTests.cpp b/tests/chains/Pactus/SignerTests.cpp index cbb469310c0..f0d965b4f21 100644 --- a/tests/chains/Pactus/SignerTests.cpp +++ b/tests/chains/Pactus/SignerTests.cpp @@ -19,7 +19,7 @@ TEST(PactusSigner, Sign) { for (const auto& testCase : TEST_CASES) { auto input = testCase.createSigningInput(); - auto privateKey = PrivateKey(parse_hex(PRIVATE_KEY_HEX)); + auto privateKey = PrivateKey(parse_hex(PRIVATE_KEY_HEX), TWCoinTypeCurve(TWCoinTypePactus)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); TW::Pactus::Proto::SigningOutput output; diff --git a/tests/chains/Polkadot/TWAnyAddressTests.cpp b/tests/chains/Polkadot/TWAnyAddressTests.cpp index 2a18ba00c41..d8eb016e016 100644 --- a/tests/chains/Polkadot/TWAnyAddressTests.cpp +++ b/tests/chains/Polkadot/TWAnyAddressTests.cpp @@ -43,7 +43,7 @@ TEST(PolkadotAddress, Validation) { TEST(PolkadotAddress, FromPrivateKey) { // subkey phrase `chief menu kingdom stereo hope hazard into island bag trick egg route` - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("0x612d82bc053d1b4729057688ecb1ebf62745d817ddd9b595bc822f5f2ba0e41a").get())); + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("0x612d82bc053d1b4729057688ecb1ebf62745d817ddd9b595bc822f5f2ba0e41a").get(), TWCoinTypeCurve(TWCoinTypePolkadot))); const auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypePolkadot)); const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypePolkadot)); const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); diff --git a/tests/chains/Polkadot/TWAnySignerTests.cpp b/tests/chains/Polkadot/TWAnySignerTests.cpp index 7c6328d4d98..de697f185db 100644 --- a/tests/chains/Polkadot/TWAnySignerTests.cpp +++ b/tests/chains/Polkadot/TWAnySignerTests.cpp @@ -26,9 +26,9 @@ uint32_t kusamaPrefix = ss58Prefix(TWCoinTypeKusama); uint32_t astarPrefix = 5; uint32_t parallelPrefix = 172; -auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); +auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115"), TWCoinTypeCurve(TWCoinTypePolkadot)); auto privateKeyThrow2Data = DATA("70a794d4f1019c3ce002f33062f45029c4f930a56b3d20ec477f7668c6bbc37f"); -auto privateKeyThrow2 = TWPrivateKeyCreateWithData(privateKeyThrow2Data.get()); +auto privateKeyThrow2 = TWPrivateKeyCreateWithData(privateKeyThrow2Data.get(), TWCoinTypeCurve(TWCoinTypePolkadot)); auto addressThrow2 = "14Ztd3KJDaB9xyJtRkREtSZDdhLSbm7UUKt8Z7AwSv7q85G2"; auto genesisHash = parse_hex("91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"); diff --git a/tests/chains/Polymesh/TWAnyAddressTests.cpp b/tests/chains/Polymesh/TWAnyAddressTests.cpp index 1602ff3e1a0..eac9a1942cf 100644 --- a/tests/chains/Polymesh/TWAnyAddressTests.cpp +++ b/tests/chains/Polymesh/TWAnyAddressTests.cpp @@ -54,7 +54,7 @@ TEST(PolymeshAddress, Validation) { TEST(PolymeshAddress, FromPrivateKey) { // subkey phrase `chief menu kingdom stereo hope hazard into island bag trick egg route` - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("0x612d82bc053d1b4729057688ecb1ebf62745d817ddd9b595bc822f5f2ba0e41a").get())); + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("0x612d82bc053d1b4729057688ecb1ebf62745d817ddd9b595bc822f5f2ba0e41a").get(), TWCoinTypeCurve(TWCoinTypePolymesh))); const auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypePolymesh)); const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypePolymesh)); const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); diff --git a/tests/chains/Qtum/TWQtumAddressTests.cpp b/tests/chains/Qtum/TWQtumAddressTests.cpp index cf95e3bc12a..20a48ef2813 100644 --- a/tests/chains/Qtum/TWQtumAddressTests.cpp +++ b/tests/chains/Qtum/TWQtumAddressTests.cpp @@ -15,7 +15,7 @@ #include TEST(Qtum, LegacyAddress) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get(), TWCoinTypeCurve(TWCoinTypeQtum))); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeQtum))); auto addressString = WRAPS(TWBitcoinAddressDescription(address.get())); @@ -23,7 +23,7 @@ TEST(Qtum, LegacyAddress) { } TEST(Qtum, Address) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625").get(), TWCoinTypeCurve(TWCoinTypeQtum))); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); auto address = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithPublicKey(TWHRPQtum, publicKey.get())); auto string = WRAPS(TWSegwitAddressDescription(address.get())); diff --git a/tests/chains/Solana/AddressTests.cpp b/tests/chains/Solana/AddressTests.cpp index 6e040826979..c4c2de8cef9 100644 --- a/tests/chains/Solana/AddressTests.cpp +++ b/tests/chains/Solana/AddressTests.cpp @@ -6,6 +6,7 @@ #include "HexCoding.h" #include "PrivateKey.h" #include "Solana/Address.h" +#include "TestUtilities.h" #include @@ -22,7 +23,7 @@ TEST(SolanaAddress, FromPublicKey) { ASSERT_EQ(addressString, address.string()); } { - const auto privateKey = PrivateKey(parse_hex("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a")); + const auto privateKey = PrivateKey(parse_hex("a1269039e4ffdf43687852d7247a295f0b5bc55e6dda031cffaa3295ca0a9d7a"), TWCoinTypeCurve(TWCoinTypeSolana)); const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); EXPECT_ANY_THROW(new Address(publicKey)); } diff --git a/tests/chains/StarkEx/MessageSignerTests.cpp b/tests/chains/StarkEx/MessageSignerTests.cpp index d8140ecead3..a9bf919ed43 100644 --- a/tests/chains/StarkEx/MessageSignerTests.cpp +++ b/tests/chains/StarkEx/MessageSignerTests.cpp @@ -12,7 +12,7 @@ namespace TW::StarkEx::tests { TEST(StarkExMessageSigner, SignAndVerify) { - PrivateKey starkPrivKey(parse_hex("04be51a04e718c202e4dca60c2b72958252024cfc1070c090dd0f170298249de", true)); + PrivateKey starkPrivKey(parse_hex("04be51a04e718c202e4dca60c2b72958252024cfc1070c090dd0f170298249de", true), TWCurveStarkex); auto starkPubKey = starkPrivKey.getPublicKey(TWPublicKeyTypeStarkex); auto starkMsg = "463a2240432264a3aa71a5713f2a4e4c1b9e12bbb56083cd56af6d878217cf"; auto starkSignature = StarkEx::MessageSigner::signMessage(starkPrivKey, starkMsg); @@ -22,7 +22,7 @@ TEST(StarkExMessageSigner, SignAndVerify) { TEST(TWStarkExMessageSigner, SignAndVerify) { const auto privKeyData = "04be51a04e718c202e4dca60c2b72958252024cfc1070c090dd0f170298249de"; - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get(), TWCurveStarkex)); const auto message = STRING("463a2240432264a3aa71a5713f2a4e4c1b9e12bbb56083cd56af6d878217cf"); const auto pubKey = TWPrivateKeyGetPublicKeyByType(privateKey.get(), TWPublicKeyTypeStarkex); diff --git a/tests/chains/Stellar/AddressTests.cpp b/tests/chains/Stellar/AddressTests.cpp index b4c84772273..8aeeff383bf 100644 --- a/tests/chains/Stellar/AddressTests.cpp +++ b/tests/chains/Stellar/AddressTests.cpp @@ -6,6 +6,7 @@ #include "HexCoding.h" #include "PrivateKey.h" #include "Stellar/Address.h" +#include "TestUtilities.h" #include @@ -20,7 +21,7 @@ TEST(StellarAddress, FromPublicKey) { auto str = hex(address.bytes); ASSERT_EQ(string("GAB6EDWGWSRZUYUYCWXAFQFBHE5ZEJPDXCIMVZC3LH2C7IU35FTI2NOQ"), address.string()); - const auto privateKey = PrivateKey(parse_hex("94d1a980d5e528067d44bf8a60d646f556e40ca71e17cd4ead2d56f89e4bd20f")); + const auto privateKey = PrivateKey(parse_hex("94d1a980d5e528067d44bf8a60d646f556e40ca71e17cd4ead2d56f89e4bd20f"), TWCoinTypeCurve(TWCoinTypeStellar)); const auto publicKey2 = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended)); EXPECT_ANY_THROW(new Address(publicKey2)); } diff --git a/tests/chains/Stellar/TWAnySignerTests.cpp b/tests/chains/Stellar/TWAnySignerTests.cpp index fe8728b1de4..fbecc9bcfc2 100644 --- a/tests/chains/Stellar/TWAnySignerTests.cpp +++ b/tests/chains/Stellar/TWAnySignerTests.cpp @@ -37,7 +37,7 @@ TEST(TWAnySingerStellar, Sign_Payment) { TEST(TWAnySingerStellar, Sign_Payment_66b5) { auto key = parse_hex("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9"); - PrivateKey privKey = PrivateKey(key); + PrivateKey privKey = PrivateKey(key, TWCoinTypeCurve(TWCoinTypeStellar)); PublicKey pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519); Address addr = Address(pubKey); EXPECT_EQ(addr.string(), "GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); @@ -60,7 +60,7 @@ TEST(TWAnySingerStellar, Sign_Payment_66b5) { TEST(TWAnySingerStellar, Sign_Payment_Asset_ea50) { auto key = parse_hex("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9"); - PrivateKey privKey = PrivateKey(key); + PrivateKey privKey = PrivateKey(key, TWCoinTypeCurve(TWCoinTypeStellar)); PublicKey pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519); Address addr = Address(pubKey); EXPECT_EQ(addr.string(), "GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); @@ -85,7 +85,7 @@ TEST(TWAnySingerStellar, Sign_Payment_Asset_ea50) { TEST(TWAnySingerStellar, Sign_Change_Trust_ad9c) { auto key = parse_hex("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9"); - PrivateKey privKey = PrivateKey(key); + PrivateKey privKey = PrivateKey(key, TWCoinTypeCurve(TWCoinTypeStellar)); PublicKey pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519); Address addr = Address(pubKey); EXPECT_EQ(addr.string(), "GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); @@ -109,7 +109,7 @@ TEST(TWAnySingerStellar, Sign_Change_Trust_ad9c) { TEST(TWAnySingerStellar, Sign_Change_Trust_2) { auto key = parse_hex("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9"); - PrivateKey privKey = PrivateKey(key); + PrivateKey privKey = PrivateKey(key, TWCoinTypeCurve(TWCoinTypeStellar)); PublicKey pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519); Address addr = Address(pubKey); EXPECT_EQ(addr.string(), "GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); @@ -132,7 +132,7 @@ TEST(TWAnySingerStellar, Sign_Change_Trust_2) { TEST(TWAnySingerStellar, Sign_Create_Claimable_Balance_1f1f84) { auto key = parse_hex("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9"); - PrivateKey privKey = PrivateKey(key); + PrivateKey privKey = PrivateKey(key, TWCoinTypeCurve(TWCoinTypeStellar)); PublicKey pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519); Address addr = Address(pubKey); EXPECT_EQ(addr.string(), "GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); @@ -158,7 +158,7 @@ TEST(TWAnySingerStellar, Sign_Create_Claimable_Balance_1f1f84) { TEST(TWAnySingerStellar, Sign_Claim_Claimable_Balance_c1fb3c) { auto key = parse_hex("3c0635f8638605aed6e461cf3fa2d508dd895df1a1655ff92c79bfbeaf88d4b9"); - PrivateKey privKey = PrivateKey(key); + PrivateKey privKey = PrivateKey(key, TWCoinTypeCurve(TWCoinTypeStellar)); PublicKey pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519); Address addr = Address(pubKey); EXPECT_EQ(addr.string(), "GDFEKJIFKUZP26SESUHZONAUJZMBSODVN2XBYN4KAGNHB7LX2OIXLPUL"); diff --git a/tests/chains/Stellar/TransactionCompilerTests.cpp b/tests/chains/Stellar/TransactionCompilerTests.cpp index 578877ba49c..9647d465b53 100644 --- a/tests/chains/Stellar/TransactionCompilerTests.cpp +++ b/tests/chains/Stellar/TransactionCompilerTests.cpp @@ -25,7 +25,7 @@ TEST(StellarCompiler, CompileWithSignatures) { /// Step 1: Prepare transaction input (protobuf) TW::Stellar::Proto::SigningInput input; auto privateKey = - PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); + PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722"), TWCoinTypeCurve(TWCoinTypeStellar)); auto publicKey = privateKey.getPublicKey(::publicKeyType(coin)); input.set_passphrase(TWStellarPassphrase_Stellar); diff --git a/tests/chains/Stellar/TransactionTests.cpp b/tests/chains/Stellar/TransactionTests.cpp index 1803cb1605f..e950671eb26 100644 --- a/tests/chains/Stellar/TransactionTests.cpp +++ b/tests/chains/Stellar/TransactionTests.cpp @@ -37,7 +37,7 @@ TEST(StellarTransaction, sign) { } TEST(StellarTransaction, signWithMemoText) { - auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); + auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722"), TWCoinTypeCurve(TWCoinTypeStellar)); auto input = TW::Stellar::Proto::SigningInput(); input.set_passphrase(TWStellarPassphrase_Stellar); input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); @@ -57,7 +57,7 @@ TEST(StellarTransaction, signWithMemoText) { } TEST(StellarTransaction, signWithMemoHash) { - auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); + auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722"), TWCoinTypeCurve(TWCoinTypeStellar)); auto input = TW::Stellar::Proto::SigningInput(); input.set_passphrase(TWStellarPassphrase_Stellar); input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); @@ -78,7 +78,7 @@ TEST(StellarTransaction, signWithMemoHash) { } TEST(StellarTransaction, signWithMemoReturn) { - auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); + auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722"), TWCoinTypeCurve(TWCoinTypeStellar)); auto input = TW::Stellar::Proto::SigningInput(); input.set_passphrase(TWStellarPassphrase_Stellar); input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); @@ -99,7 +99,7 @@ TEST(StellarTransaction, signWithMemoReturn) { } TEST(StellarTransaction, signWithMemoID) { - auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); + auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722"), TWCoinTypeCurve(TWCoinTypeStellar)); auto input = TW::Stellar::Proto::SigningInput(); input.set_passphrase(TWStellarPassphrase_Stellar); input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); @@ -119,7 +119,7 @@ TEST(StellarTransaction, signWithMemoID) { } TEST(StellarTransaction, signAcreateAccount) { - auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722")); + auto privateKey = PrivateKey(parse_hex("59a313f46ef1c23a9e4f71cea10fc0c56a2a6bb8a4b9ea3d5348823e5a478722"), TWCoinTypeCurve(TWCoinTypeStellar)); auto input = TW::Stellar::Proto::SigningInput(); input.set_passphrase(TWStellarPassphrase_Stellar); input.set_account("GAE2SZV4VLGBAPRYRFV2VY7YYLYGYIP5I7OU7BSP6DJT7GAZ35OKFDYI"); diff --git a/tests/chains/Stratis/TWStratisTests.cpp b/tests/chains/Stratis/TWStratisTests.cpp index 6d8dbf1f95b..d5a26660eea 100644 --- a/tests/chains/Stratis/TWStratisTests.cpp +++ b/tests/chains/Stratis/TWStratisTests.cpp @@ -14,7 +14,7 @@ #include TEST(Stratis, LegacyAddress) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get(), TWCoinTypeCurve(TWCoinTypeStratis))); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeStratis))); auto addressString = WRAPS(TWBitcoinAddressDescription(address.get())); @@ -22,7 +22,7 @@ TEST(Stratis, LegacyAddress) { } TEST(Stratis, Address) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625").get(), TWCoinTypeCurve(TWCoinTypeStratis))); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeStratis)); auto string = WRAPS(TWAnyAddressDescription(address.get())); diff --git a/tests/chains/Sui/CompilerTests.cpp b/tests/chains/Sui/CompilerTests.cpp index b496546ff43..df31fe10a41 100644 --- a/tests/chains/Sui/CompilerTests.cpp +++ b/tests/chains/Sui/CompilerTests.cpp @@ -32,9 +32,9 @@ TEST(SuiCompiler, PreHashAndCompile) { EXPECT_EQ(data(preSigningOutput.data()), expectedData); EXPECT_EQ(data(preSigningOutput.data_hash()), expectedHash); - auto privateKey = PrivateKey(parse_hex("3823dce5288ab55dd1c00d97e91933c613417fdb282a0b8b01a7f5f5a533b266")); + auto privateKey = PrivateKey(parse_hex("3823dce5288ab55dd1c00d97e91933c613417fdb282a0b8b01a7f5f5a533b266"), TWCurveED25519); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; - auto signature = privateKey.sign(expectedHash, TWCurveED25519); + auto signature = privateKey.sign(expectedHash); auto outputData = TransactionCompiler::compileWithSignatures(TWCoinTypeSui, inputStrData, {signature}, {publicKey}); Proto::SigningOutput output; diff --git a/tests/chains/Sui/SignerTests.cpp b/tests/chains/Sui/SignerTests.cpp index ce801ca9113..f2f66305022 100644 --- a/tests/chains/Sui/SignerTests.cpp +++ b/tests/chains/Sui/SignerTests.cpp @@ -17,7 +17,7 @@ TEST(SuiSigner, Transfer) { Proto::SigningInput input; auto txMsg = "AAACAAgQJwAAAAAAAAAgJZ/4B0q0Jcu0ifI24Y4I8D8aeFa998eih3vWT3OLUBUCAgABAQAAAQEDAAAAAAEBANV1rX8Y6UhGKlz2mPVk7zlKdSpx/sYkk6+KBVwBLA1QAQbywsjB2JZN8QGdZhbpcFcZvrq9kx2idVy5SM635olk7AIAAAAAAAAgYEVuxmf1zRBGdoDr+VDtMpIFF12s2Ua7I2ru1XyGF8/Vda1/GOlIRipc9pj1ZO85SnUqcf7GJJOvigVcASwNUAEAAAAAAAAA0AcAAAAAAAAA"; input.mutable_sign_direct_message()->set_unsigned_tx_msg(txMsg); - auto privateKey = PrivateKey(parse_hex("3823dce5288ab55dd1c00d97e91933c613417fdb282a0b8b01a7f5f5a533b266")); + auto privateKey = PrivateKey(parse_hex("3823dce5288ab55dd1c00d97e91933c613417fdb282a0b8b01a7f5f5a533b266"), TWCoinTypeCurve(TWCoinTypeSui)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); Proto::SigningOutput output; diff --git a/tests/chains/Syscoin/TWSyscoinTests.cpp b/tests/chains/Syscoin/TWSyscoinTests.cpp index 77cc8390314..cabdcc847de 100644 --- a/tests/chains/Syscoin/TWSyscoinTests.cpp +++ b/tests/chains/Syscoin/TWSyscoinTests.cpp @@ -14,7 +14,7 @@ #include TEST(Syscoin, LegacyAddress) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get(), TWCoinTypeCurve(TWCoinTypeSyscoin))); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeSyscoin))); auto addressString = WRAPS(TWBitcoinAddressDescription(address.get())); @@ -22,7 +22,7 @@ TEST(Syscoin, LegacyAddress) { } TEST(Syscoin, Address) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625").get(), TWCoinTypeCurve(TWCoinTypeSyscoin))); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeSyscoin)); auto string = WRAPS(TWAnyAddressDescription(address.get())); diff --git a/tests/chains/Tezos/ForgingTests.cpp b/tests/chains/Tezos/ForgingTests.cpp index 95ffe14dd78..259ea77d070 100644 --- a/tests/chains/Tezos/ForgingTests.cpp +++ b/tests/chains/Tezos/ForgingTests.cpp @@ -100,7 +100,7 @@ TEST(Forging, forge_tz3) { TEST(Forging, ForgeED25519PublicKey) { auto expected = "00311f002e899cdd9a52d96cb8be18ea2bbab867c505da2b44ce10906f511cff95"; - auto privateKey = PrivateKey(parse_hex("c6377a4cc490dc913fc3f0d9cf67d293a32df4547c46cb7e9e33c3b7b97c64d8")); + auto privateKey = PrivateKey(parse_hex("c6377a4cc490dc913fc3f0d9cf67d293a32df4547c46cb7e9e33c3b7b97c64d8"), TWCoinTypeCurve(TWCoinTypeTezos)); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); auto output = forgePublicKey(publicKey); @@ -134,7 +134,7 @@ TEST(Forging, ForgeMichelsonFA12) { TEST(Forging, ForgeSECP256k1PublicKey) { auto expected = "0102b4ac9056d20c52ac11b0d7e83715dd3eac851cfc9cb64b8546d9ea0d4bb3bdfe"; - auto privateKey = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")); + auto privateKey = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a"), TWCoinTypeCurve(TWCoinTypeTezos)); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); auto output = forgePublicKey(publicKey); diff --git a/tests/chains/Tezos/MessageSignerTests.cpp b/tests/chains/Tezos/MessageSignerTests.cpp index 3f38133d55a..3e085896d47 100644 --- a/tests/chains/Tezos/MessageSignerTests.cpp +++ b/tests/chains/Tezos/MessageSignerTests.cpp @@ -24,7 +24,7 @@ TEST(TezosMessageSigner, formatMessage) { TEST(TezosMessageSigner, SignMessage) { auto payload = Tezos::MessageSigner::inputToPayload("Tezos Signed Message: testUrl 2023-02-08T10:36:18.454Z Hello World"); - PrivateKey privKey(parse_hex("91b4fb8d7348db2e7de2693f58ce1cceb966fa960739adac1d9dba2cbaa0940a")); + PrivateKey privKey(parse_hex("91b4fb8d7348db2e7de2693f58ce1cceb966fa960739adac1d9dba2cbaa0940a"), TWCoinTypeCurve(TWCoinTypeTezos)); auto result = Tezos::MessageSigner::signMessage(privKey, payload); auto expected = "edsigu3se2fcEJUCm1aqxjzbHdf7Wsugr4mLaA9YM2UVZ9Yy5meGv87VqHN3mmDeRwApTj1JKDaYjqmLZifSFdWCqBoghqaowwJ"; ASSERT_EQ(result, expected); @@ -48,7 +48,7 @@ TEST(TWTezosMessageSigner, inputToPayload) { TEST(TWTezosMessageSigner, SignAndVerify) { const auto privKeyData = "91b4fb8d7348db2e7de2693f58ce1cceb966fa960739adac1d9dba2cbaa0940a"; - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get(), TWCoinTypeCurve(TWCoinTypeTezos))); const auto message = STRING("05010000004254657a6f73205369676e6564204d6573736167653a207465737455726c20323032332d30322d30385431303a33363a31382e3435345a2048656c6c6f20576f726c64"); const auto pubKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeTezos)); diff --git a/tests/chains/Tezos/SignerTests.cpp b/tests/chains/Tezos/SignerTests.cpp index 2f63e20c9e8..56f046c2f8a 100644 --- a/tests/chains/Tezos/SignerTests.cpp +++ b/tests/chains/Tezos/SignerTests.cpp @@ -22,7 +22,7 @@ TEST(TezosSigner, SignString) { append(expected, bytesToSign); append(expected, expectedSignature); - auto key = PrivateKey(parse_hex("0x2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f")); + auto key = PrivateKey(parse_hex("0x2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f"), TWCurveED25519); auto signedBytes = Signer().signData(key, bytesToSign); ASSERT_EQ(signedBytes, expected); @@ -77,7 +77,7 @@ TEST(TezosSigner, SignOperationList) { op_list.addOperation(delegateOperation); auto decodedPrivateKey = Base58::decodeCheck("edsk4bMQMM6HYtMazF3m7mYhQ6KQ1WCEcBuRwh6DTtdnoqAvC3nPCc"); - auto key = PrivateKey(Data(decodedPrivateKey.begin() + 4, decodedPrivateKey.end())); + auto key = PrivateKey(Data(decodedPrivateKey.begin() + 4, decodedPrivateKey.end()), TWCurveED25519); std::string expectedForgedBytesToSign = hex(op_list.forge(key)); std::string expectedSignature = "871693145f2dc72861ff6816e7ac3ce93c57611ac09a4c657a5a35270fa57153334c14cd8cae94ee228b6ef52f0e3f10948721e666318bc54b6c455404b11e03"; diff --git a/tests/chains/Tezos/TransactionCompilerTests.cpp b/tests/chains/Tezos/TransactionCompilerTests.cpp index 56f3ec7a008..fe1fae2c44b 100644 --- a/tests/chains/Tezos/TransactionCompilerTests.cpp +++ b/tests/chains/Tezos/TransactionCompilerTests.cpp @@ -24,7 +24,7 @@ TEST(TezosCompiler, CompileWithSignatures) { /// Step 1: Prepare transaction input (protobuf) auto privateKey = - PrivateKey(parse_hex("2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f")); + PrivateKey(parse_hex("2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6f"), TWCoinTypeCurve(coin)); auto publicKey = privateKey.getPublicKey(::publicKeyType(coin)); auto revealKey = parse_hex("311f002e899cdd9a52d96cb8be18ea2bbab867c505da2b44ce10906f511cff95"); diff --git a/tests/chains/TheOpenNetwork/TWTONMessageSignerTests.cpp b/tests/chains/TheOpenNetwork/TWTONMessageSignerTests.cpp index b331c31f4d8..8d0d79f266a 100644 --- a/tests/chains/TheOpenNetwork/TWTONMessageSignerTests.cpp +++ b/tests/chains/TheOpenNetwork/TWTONMessageSignerTests.cpp @@ -11,7 +11,7 @@ namespace TW::TheOpenNetwork::tests { TEST(TWTONMessageSigner, SignMessage) { const auto privateKeyBytes = DATA("112d4e2e700a468f1eae699329202f1ee671d6b665caa2d92dea038cf3868c18"); - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(privateKeyBytes.get())); + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(privateKeyBytes.get(), TWCoinTypeCurve(TWCoinTypeTON))); const auto message = STRING("Hello world"); const auto signature = WRAPS(TWTONMessageSignerSignMessage(privateKey.get(), message.get())); diff --git a/tests/chains/Theta/SignerTests.cpp b/tests/chains/Theta/SignerTests.cpp index 295ee5fc98d..778a21cb3de 100644 --- a/tests/chains/Theta/SignerTests.cpp +++ b/tests/chains/Theta/SignerTests.cpp @@ -13,7 +13,7 @@ using boost::multiprecision::uint256_t; TEST(Signer, Sign) { const auto pkFrom = - PrivateKey(parse_hex("0x93a90ea508331dfdf27fb79757d4250b4e84954927ba0073cd67454ac432c737")); + PrivateKey(parse_hex("0x93a90ea508331dfdf27fb79757d4250b4e84954927ba0073cd67454ac432c737"), TWCurveSECP256k1); const auto from = Ethereum::Address("0x2E833968E5bB786Ae419c4d13189fB081Cc43bab"); const auto to = Ethereum::Address("0x9F1233798E905E173560071255140b4A8aBd3Ec6"); auto transaction = Transaction(from, to, 10, 20, 1); diff --git a/tests/chains/Theta/TransactionCompilerTests.cpp b/tests/chains/Theta/TransactionCompilerTests.cpp index fd35e22f406..3b44fe54e45 100644 --- a/tests/chains/Theta/TransactionCompilerTests.cpp +++ b/tests/chains/Theta/TransactionCompilerTests.cpp @@ -24,7 +24,7 @@ TEST(ThetaCompiler, CompileWithSignatures) { const auto coin = TWCoinTypeTheta; /// Step 1: Prepare transaction input (protobuf) const auto pkFrom = - PrivateKey(parse_hex("0x93a90ea508331dfdf27fb79757d4250b4e84954927ba0073cd67454ac432c737")); + PrivateKey(parse_hex("0x93a90ea508331dfdf27fb79757d4250b4e84954927ba0073cd67454ac432c737"), TWCoinTypeCurve(coin)); const auto publicKey = pkFrom.getPublicKey(TWPublicKeyTypeSECP256k1Extended); TW::Theta::Proto::SigningInput input; input.set_chain_id("privatenet"); diff --git a/tests/chains/Tron/AddressTests.cpp b/tests/chains/Tron/AddressTests.cpp index 7489812ddca..4b3cdf165bd 100644 --- a/tests/chains/Tron/AddressTests.cpp +++ b/tests/chains/Tron/AddressTests.cpp @@ -5,23 +5,24 @@ #include "HexCoding.h" #include "PrivateKey.h" #include "Tron/Address.h" +#include "TestUtilities.h" #include namespace TW::Tron { TEST(TronAddress, FromPublicKey) { - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCoinTypeCurve(TWCoinTypeTron)); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); const auto address = Address(publicKey); ASSERT_EQ(address.string(), "TJRyWwFs9wTFGZg3JbrVriFbNfCug5tDeC"); - const auto privateKey2 = PrivateKey(parse_hex("BE88DF1D0BF30A923CB39C3BB953178BAAF3726E8D3CE81E7C8462E046E0D835")); + const auto privateKey2 = PrivateKey(parse_hex("BE88DF1D0BF30A923CB39C3BB953178BAAF3726E8D3CE81E7C8462E046E0D835"), TWCoinTypeCurve(TWCoinTypeTron)); const auto publicKey2 = privateKey2.getPublicKey(TWPublicKeyTypeSECP256k1Extended); const auto address2 = Address(publicKey2); ASSERT_EQ(address2.string(), "THRF3GuPnvvPzKoaT8pJex5XHmo8NNbCb3"); - const auto privateKey3 = PrivateKey(parse_hex("BE88DF1D0BF30A923CB39C3BB953178BAAF3726E8D3CE81E7C8462E046E0D835")); + const auto privateKey3 = PrivateKey(parse_hex("BE88DF1D0BF30A923CB39C3BB953178BAAF3726E8D3CE81E7C8462E046E0D835"), TWCoinTypeCurve(TWCoinTypeTron)); const auto publicKey3 = privateKey3.getPublicKey(TWPublicKeyTypeED25519); EXPECT_ANY_THROW(new Address(publicKey3)); } diff --git a/tests/chains/Tron/SerializationTests.cpp b/tests/chains/Tron/SerializationTests.cpp index ef321093f73..7b7ece7d625 100644 --- a/tests/chains/Tron/SerializationTests.cpp +++ b/tests/chains/Tron/SerializationTests.cpp @@ -7,6 +7,7 @@ #include "PrivateKey.h" #include "HexCoding.h" #include "uint256.h" +#include "TestUtilities.h" #include @@ -35,7 +36,7 @@ namespace TW::Tron { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(3); - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); @@ -67,7 +68,7 @@ namespace TW::Tron { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(3); - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); @@ -101,7 +102,7 @@ namespace TW::Tron { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(3); - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); @@ -135,7 +136,7 @@ namespace TW::Tron { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(3); - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); @@ -166,7 +167,7 @@ namespace TW::Tron { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(3); - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); @@ -197,7 +198,7 @@ namespace TW::Tron { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(3); - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); diff --git a/tests/chains/Tron/SignerTests.cpp b/tests/chains/Tron/SignerTests.cpp index b62ac222b73..84d5c0bdd0b 100644 --- a/tests/chains/Tron/SignerTests.cpp +++ b/tests/chains/Tron/SignerTests.cpp @@ -9,6 +9,7 @@ #include "uint256.h" #include "proto/Tron.pb.h" #include "Tron/Signer.h" +#include "TestUtilities.h" #include @@ -16,7 +17,7 @@ namespace TW::Tron { TEST(TronSigner, SignDirectTransferAsset) { auto input = Proto::SigningInput(); - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); input.set_txid("546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb"); const auto output = Signer::sign(input); @@ -48,7 +49,7 @@ TEST(TronSigner, SignTransferAsset) { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(3); - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); @@ -80,7 +81,7 @@ TEST(TronSigner, SignTransfer) { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(3); - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); @@ -114,7 +115,7 @@ TEST(TronSigner, SignTransferWithMemo) { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(30); - const auto privateKey = PrivateKey(parse_hex("7c2108a30f6f69f8dce72a7df897eabadfe9810eee6976b43bdf8c0b0d35337d")); + const auto privateKey = PrivateKey(parse_hex("7c2108a30f6f69f8dce72a7df897eabadfe9810eee6976b43bdf8c0b0d35337d"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); @@ -147,7 +148,7 @@ TEST(TronSigner, SignFreezeBalanceV2) { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(26); - const auto privateKey = PrivateKey(parse_hex("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a")); + const auto privateKey = PrivateKey(parse_hex("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); @@ -178,7 +179,7 @@ TEST(TronSigner, WithdrawExpireUnfreezeContract) { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(27); - const auto privateKey = PrivateKey(parse_hex("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a")); + const auto privateKey = PrivateKey(parse_hex("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); @@ -211,7 +212,7 @@ TEST(TronSigner, SignUnFreezeBalanceV2) { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(26); - const auto privateKey = PrivateKey(parse_hex("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a")); + const auto privateKey = PrivateKey(parse_hex("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); @@ -245,7 +246,7 @@ TEST(TronSigner, DelegateResourceContract) { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(26); - const auto privateKey = PrivateKey(parse_hex("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a")); + const auto privateKey = PrivateKey(parse_hex("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); @@ -279,7 +280,7 @@ TEST(TronSigner, UnDelegateResourceContract) { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(26); - const auto privateKey = PrivateKey(parse_hex("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a")); + const auto privateKey = PrivateKey(parse_hex("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); @@ -313,7 +314,7 @@ TEST(TronSigner, SignFreezeBalance) { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(3); - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); @@ -345,7 +346,7 @@ TEST(TronSigner, SignUnFreezeBalance) { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(3); - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); @@ -375,7 +376,7 @@ TEST(TronSigner, SignUnFreezeAsset) { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(3); - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); @@ -405,7 +406,7 @@ TEST(TronSigner, SignWithdrawBalance) { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(3); - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); @@ -438,7 +439,7 @@ TEST(TronSigner, SignVoteAsset) { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(3); - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); @@ -473,7 +474,7 @@ TEST(TronSigner, SignVoteWitness) { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(3); - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); @@ -508,7 +509,7 @@ TEST(TronSigner, SignTriggerSmartContract) { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(3); - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); @@ -540,7 +541,7 @@ TEST(TronSigner, SignTransferTrc20Contract) { blockHeader.set_witness_address(witnessAddress.data(), witnessAddress.size()); blockHeader.set_version(3); - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCoinTypeCurve(TWCoinTypeTron)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); const auto output = Signer::sign(input); diff --git a/tests/chains/Tron/TransactionCompilerTests.cpp b/tests/chains/Tron/TransactionCompilerTests.cpp index 0962f4bda07..839f190cba8 100644 --- a/tests/chains/Tron/TransactionCompilerTests.cpp +++ b/tests/chains/Tron/TransactionCompilerTests.cpp @@ -21,7 +21,7 @@ using namespace TW; TEST(TronCompiler, CompileWithSignatures) { const auto privateKey = - PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCoinTypeCurve(TWCoinTypeTron)); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); const auto coin = TWCoinTypeTron; /// Step 1: Prepare transaction input (protobuf) diff --git a/tests/chains/Tron/TronMessageSignerTests.cpp b/tests/chains/Tron/TronMessageSignerTests.cpp index 4b0940a5db6..1ca2d3b039e 100644 --- a/tests/chains/Tron/TronMessageSignerTests.cpp +++ b/tests/chains/Tron/TronMessageSignerTests.cpp @@ -12,22 +12,34 @@ namespace TW::Tron { TEST(TronMessageSigner, SignMessageAndVerify) { - PrivateKey tronKey(parse_hex("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a")); + PrivateKey tronKey(parse_hex("75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a"), TWCoinTypeCurve(TWCoinTypeTron)); auto msg = "Hello World"; auto signature = Tron::MessageSigner::signMessage(tronKey, msg); - ASSERT_EQ(signature, "9bb6d11ec8a6a3fb686a8f55b123e7ec4e9746a26157f6f9e854dd72f5683b450397a7b0a9653865658de8f9243f877539882891bad30c7286c3bf5622b900471b"); auto pubKey = tronKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); + + ASSERT_EQ(signature, "bc0753c070cc55693097df11bc11e1a7c4bd5e1a40b9dc94c75568e59bcc9d6b50a7873ef25b469e494490a54de37327b4bc7fc825c81a377b555e34fb7261ba1c"); ASSERT_TRUE(Tron::MessageSigner::verifyMessage(pubKey, msg, signature)); + + auto msg2 = "A much longer message to test the signing and verification process"; + auto signature2 = Tron::MessageSigner::signMessage(tronKey, msg2); + + ASSERT_EQ(signature2, "93aee5f753cf889e0749c74dd0c5996cce889883ae079e09ede462e16d65d06a4f43d1ed2745e9f3c1690695628269bd58f057a4a93953cc50e66b4a05bc0f451b"); + ASSERT_TRUE(Tron::MessageSigner::verifyMessage(pubKey, msg2, signature2)); } TEST(TWTronMessageSigner, SignAndVerifyLegacy) { const auto privKeyData = "75065f100e38d3f3b4c5c4235834ba8216de62272a4f03532c44b31a5734360a"; - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get())); + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(privKeyData).get(), TWCoinTypeCurve(TWCoinTypeTron))); const auto message = STRING("Hello World"); const auto pubKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeTron)); const auto signature = WRAPS(TWTronMessageSignerSignMessage(privateKey.get(), message.get())); - EXPECT_EQ(std::string(TWStringUTF8Bytes(signature.get())), "9bb6d11ec8a6a3fb686a8f55b123e7ec4e9746a26157f6f9e854dd72f5683b450397a7b0a9653865658de8f9243f877539882891bad30c7286c3bf5622b900471b"); + EXPECT_EQ(std::string(TWStringUTF8Bytes(signature.get())), "bc0753c070cc55693097df11bc11e1a7c4bd5e1a40b9dc94c75568e59bcc9d6b50a7873ef25b469e494490a54de37327b4bc7fc825c81a377b555e34fb7261ba1c"); EXPECT_TRUE(TWTronMessageSignerVerifyMessage(pubKey.get(), message.get(), signature.get())); + + const auto message2 = STRING("A much longer message to test the signing and verification process"); + const auto signature2 = WRAPS(TWTronMessageSignerSignMessage(privateKey.get(), message2.get())); + EXPECT_EQ(std::string(TWStringUTF8Bytes(signature2.get())), "93aee5f753cf889e0749c74dd0c5996cce889883ae079e09ede462e16d65d06a4f43d1ed2745e9f3c1690695628269bd58f057a4a93953cc50e66b4a05bc0f451b"); + EXPECT_TRUE(TWTronMessageSignerVerifyMessage(pubKey.get(), message2.get(), signature2.get())); } } diff --git a/tests/chains/VeChain/SignerTests.cpp b/tests/chains/VeChain/SignerTests.cpp index 1317cc17ee4..5b8d0135fee 100644 --- a/tests/chains/VeChain/SignerTests.cpp +++ b/tests/chains/VeChain/SignerTests.cpp @@ -23,7 +23,7 @@ TEST(Signer, Sign) { transaction.gas = 21000; transaction.nonce = 1; - auto key = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); + auto key = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"), TWCurveSECP256k1); auto signature = Signer::sign(key, transaction); ASSERT_EQ(hex(signature), "3181b1094150f8e4f51f370b805cc9c5b107504145b9e316e846d5e5dbeedb5c1c2b5d217f197a105983dfaad6a198414d5731c7447493cb6b5169907d73dbe101"); diff --git a/tests/chains/VeChain/TransactionCompilerTests.cpp b/tests/chains/VeChain/TransactionCompilerTests.cpp index 6a9f9ab9c90..e5e54512ef5 100644 --- a/tests/chains/VeChain/TransactionCompilerTests.cpp +++ b/tests/chains/VeChain/TransactionCompilerTests.cpp @@ -25,7 +25,7 @@ TEST(VechainCompiler, CompileWithSignatures) { /// Step 1: Prepare transaction input (protobuf) TW::VeChain::Proto::SigningInput input; PrivateKey privateKey = - PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); + PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"), TWCoinTypeCurve(TWCoinTypeVeChain)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); auto publicKey = privateKey.getPublicKey(publicKeyType(coin)); diff --git a/tests/chains/Verge/AddressTests.cpp b/tests/chains/Verge/AddressTests.cpp index 5185d3bd005..25e96d2a243 100644 --- a/tests/chains/Verge/AddressTests.cpp +++ b/tests/chains/Verge/AddressTests.cpp @@ -26,7 +26,7 @@ TEST(VergeAddress, Invalid) { } TEST(VergeAddress, FromPrivateKey) { - auto privateKey = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a")); + auto privateKey = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a"), TWCoinTypeCurve(TWCoinTypeVerge)); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); auto address = Address(publicKey, TWCoinTypeP2pkhPrefix(TWCoinTypeVerge)); ASSERT_EQ(address.string(), "DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E"); diff --git a/tests/chains/Verge/SignerTests.cpp b/tests/chains/Verge/SignerTests.cpp index 14ff7bb0720..5cfa19dc02a 100644 --- a/tests/chains/Verge/SignerTests.cpp +++ b/tests/chains/Verge/SignerTests.cpp @@ -44,7 +44,7 @@ TEST(VergeSigner, Sign) { utxo0->mutable_out_point()->set_sequence(4294967294); utxo0->set_amount(2500000000); - auto utxoKey0 = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a")); + auto utxoKey0 = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a"), TWCoinTypeCurve(TWCoinTypeVerge)); auto script0 = Bitcoin::Script::lockScriptForAddress("DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E", TWCoinTypeVerge); ASSERT_EQ(hex(script0.bytes), "76a914e4839a523f120882d11eb3dda13a18e11fdcbd4a88ac"); utxo0->set_script(script0.bytes.data(), script0.bytes.size()); @@ -106,7 +106,7 @@ TEST(VergeSigner, SignAnyoneCanPay) { ASSERT_EQ(hex(script1.bytes), "76a914e4839a523f120882d11eb3dda13a18e11fdcbd4a88ac"); utxo1->set_script(script1.bytes.data(), script1.bytes.size()); - auto utxoKey0 = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a")); + auto utxoKey0 = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a"), TWCoinTypeCurve(TWCoinTypeVerge)); input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); auto plan = TransactionBuilder::plan(input); @@ -156,7 +156,7 @@ TEST(VergeSigner, SignSegwit) { EXPECT_EQ(hex(script0.bytes), "0014e4839a523f120882d11eb3dda13a18e11fdcbd4a"); utxo0->set_script(script0.bytes.data(), script0.bytes.size()); - auto utxoKey0 = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a")); + auto utxoKey0 = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a"), TWCoinTypeCurve(TWCoinTypeVerge)); input.add_private_key(utxoKey0.bytes.data(), utxoKey0.bytes.size()); auto plan = TransactionBuilder::plan(input); diff --git a/tests/chains/Verge/TWAnySignerTests.cpp b/tests/chains/Verge/TWAnySignerTests.cpp index 7d65a5a792d..7423968307d 100644 --- a/tests/chains/Verge/TWAnySignerTests.cpp +++ b/tests/chains/Verge/TWAnySignerTests.cpp @@ -44,7 +44,7 @@ TEST(TWAnySignerVerge, Sign) { utxo0->mutable_out_point()->set_sequence(4294967294); utxo0->set_amount(2500000000); - auto utxoKey0 = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a")); + auto utxoKey0 = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a"), TWCoinTypeCurve(TWCoinTypeVerge)); auto script0 = Bitcoin::Script::lockScriptForAddress("DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E", TWCoinTypeVerge); ASSERT_EQ(hex(script0.bytes), "76a914e4839a523f120882d11eb3dda13a18e11fdcbd4a88ac"); utxo0->set_script(script0.bytes.data(), script0.bytes.size()); diff --git a/tests/chains/Verge/TransactionBuilderTests.cpp b/tests/chains/Verge/TransactionBuilderTests.cpp index dd0b84e1c94..dee08abbca6 100644 --- a/tests/chains/Verge/TransactionBuilderTests.cpp +++ b/tests/chains/Verge/TransactionBuilderTests.cpp @@ -39,7 +39,7 @@ TEST(VergeTransactionBuilder, BuildWithTime) { utxo0->mutable_out_point()->set_sequence(4294967294); utxo0->set_amount(2500000000); - auto utxoKey0 = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a")); + auto utxoKey0 = PrivateKey(parse_hex("693dfe6f3ed717573eb10c24ebe5eb592fa3c239245cd499c487eb7b8ea7ed3a"), TWCoinTypeCurve(TWCoinTypeVerge)); auto script0 = Bitcoin::Script::lockScriptForAddress("DRyNFvJaybnF22UfMS6NR1Qav3mqxPj86E", TWCoinTypeVerge); ASSERT_EQ(hex(script0.bytes), "76a914e4839a523f120882d11eb3dda13a18e11fdcbd4a88ac"); utxo0->set_script(script0.bytes.data(), script0.bytes.size()); diff --git a/tests/chains/Viacoin/TWViacoinAddressTests.cpp b/tests/chains/Viacoin/TWViacoinAddressTests.cpp index 996afe1fe8b..3f902297c88 100644 --- a/tests/chains/Viacoin/TWViacoinAddressTests.cpp +++ b/tests/chains/Viacoin/TWViacoinAddressTests.cpp @@ -15,7 +15,7 @@ #include TEST(Viacoin, LegacyAddress) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("a22ddec5c567b4488bb00f69b6146c50da2ee883e2c096db098726394d585730").get(), TWCoinTypeCurve(TWCoinTypeViacoin))); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); auto address = WRAP(TWBitcoinAddress, TWBitcoinAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeP2pkhPrefix(TWCoinTypeViacoin))); auto addressString = WRAPS(TWBitcoinAddressDescription(address.get())); @@ -23,7 +23,7 @@ TEST(Viacoin, LegacyAddress) { } TEST(Viacoin, Address) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("55f9cbb0376c422946fa28397c1219933ac60b312ede41bfacaf701ecd546625").get(), TWCoinTypeCurve(TWCoinTypeViacoin))); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); auto address = WRAP(TWSegwitAddress, TWSegwitAddressCreateWithPublicKey(TWHRPViacoin, publicKey.get())); auto string = WRAPS(TWSegwitAddressDescription(address.get())); diff --git a/tests/chains/WAX/TWAnySignerTests.cpp b/tests/chains/WAX/TWAnySignerTests.cpp index f81d7663dec..414ea7bfef6 100644 --- a/tests/chains/WAX/TWAnySignerTests.cpp +++ b/tests/chains/WAX/TWAnySignerTests.cpp @@ -21,7 +21,7 @@ TEST(TWAnySignerWAX, Sign) { const auto refBlock = parse_hex("0cffaeda15039f3468398c5b4295d220fcc217f7cf96030c3729773097c6bd76"); const auto key = parse_hex("d30d185a296b9591d648cb92fe0aa8f8a42de30ed9d2a21da9e7f69c67e8e355"); - const auto pubKey = PublicKey(PrivateKey(key).getPublicKey(TWPublicKeyTypeSECP256k1)); + const auto pubKey = PublicKey(PrivateKey(key, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1)); const auto address = Address(pubKey); EXPECT_EQ(address.string(), "EOS7rC6zYUjuxWkiokZTrwwHqwFvZ15Qdrn5WNxMKVXtHiDDmBWog"); diff --git a/tests/chains/Waves/AddressTests.cpp b/tests/chains/Waves/AddressTests.cpp index 22de9778b55..781b94a3838 100644 --- a/tests/chains/Waves/AddressTests.cpp +++ b/tests/chains/Waves/AddressTests.cpp @@ -24,7 +24,7 @@ TEST(WavesAddress, SecureHash) { TEST(WavesAddress, FromPrivateKey) { const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"), TWCoinTypeCurve(TWCoinTypeWaves)); const auto publicKeyEd25519 = privateKey.getPublicKey(TWPublicKeyTypeED25519); ASSERT_EQ(hex(Data(publicKeyEd25519.bytes.begin(), publicKeyEd25519.bytes.end())), "ff84c4bfc095df25b01e48807715856d95af93d88c5b57f30cb0ce567ca4ced6"); diff --git a/tests/chains/Waves/LeaseTests.cpp b/tests/chains/Waves/LeaseTests.cpp index 588f7f99db7..ab012c1497d 100644 --- a/tests/chains/Waves/LeaseTests.cpp +++ b/tests/chains/Waves/LeaseTests.cpp @@ -21,7 +21,7 @@ namespace TW::Waves::tests { TEST(WavesLease, serialize) { const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"), TWCurveCurve25519); auto input = Proto::SigningInput(); input.set_timestamp(int64_t(1526646497465)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); @@ -42,7 +42,7 @@ TEST(WavesLease, serialize) { TEST(WavesLease, CancelSerialize) { const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"), TWCurveCurve25519); auto input = Proto::SigningInput(); input.set_timestamp(int64_t(1568831000826)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); @@ -62,7 +62,7 @@ TEST(WavesLease, CancelSerialize) { TEST(WavesLease, jsonSerialize) { const auto privateKey = PrivateKey(parse_hex( - "9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + "9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"), TWCurveCurve25519); const auto publicKeyCurve25519 = privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); auto input = Proto::SigningInput(); @@ -102,7 +102,7 @@ TEST(WavesLease, jsonSerialize) { TEST(WavesLease, jsonCancelSerialize) { const auto privateKey = PrivateKey(parse_hex( - "9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + "9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"), TWCurveCurve25519); const auto publicKeyCurve25519 = privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); auto input = Proto::SigningInput(); diff --git a/tests/chains/Waves/SignerTests.cpp b/tests/chains/Waves/SignerTests.cpp index bbc63fad0c5..c4e6f61e263 100644 --- a/tests/chains/Waves/SignerTests.cpp +++ b/tests/chains/Waves/SignerTests.cpp @@ -16,7 +16,7 @@ namespace TW::Waves::tests { TEST(WavesSigner, SignTransaction) { const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"), TWCurveCurve25519); const auto publicKeyCurve25519 = privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); ASSERT_EQ(hex(Data(publicKeyCurve25519.bytes.begin(), publicKeyCurve25519.bytes.end())), "559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d"); diff --git a/tests/chains/Waves/TransactionTests.cpp b/tests/chains/Waves/TransactionTests.cpp index a71ca9c9069..5597f37259b 100644 --- a/tests/chains/Waves/TransactionTests.cpp +++ b/tests/chains/Waves/TransactionTests.cpp @@ -19,7 +19,7 @@ using namespace std; TEST(WavesTransaction, serialize) { const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"), TWCurveCurve25519); auto input = Proto::SigningInput(); input.set_timestamp(int64_t(1526641218066)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); @@ -67,7 +67,7 @@ TEST(WavesTransaction, serialize) { TEST(WavesTransaction, failedSerialize) { // 141 bytes attachment const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"), TWCurveCurve25519); auto input = Proto::SigningInput(); input.set_timestamp(int64_t(1526641218066)); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); @@ -91,7 +91,7 @@ TEST(WavesTransaction, failedSerialize) { TEST(WavesTransaction, jsonSerialize) { const auto privateKey = - PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); + PrivateKey(parse_hex("9864a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a"), TWCurveCurve25519); const auto publicKeyCurve25519 = privateKey.getPublicKey(TWPublicKeyTypeCURVE25519); ASSERT_EQ(hex(Data(publicKeyCurve25519.bytes.begin(), publicKeyCurve25519.bytes.end())), "559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d"); diff --git a/tests/chains/XRP/TransactionCompilerTests.cpp b/tests/chains/XRP/TransactionCompilerTests.cpp index 7bcf106884a..099dcd27fda 100644 --- a/tests/chains/XRP/TransactionCompilerTests.cpp +++ b/tests/chains/XRP/TransactionCompilerTests.cpp @@ -24,7 +24,7 @@ TEST(RippleCompiler, CompileRippleWithSignatures) { /// Step 1: Prepare transaction input (protobuf) auto key = parse_hex("acf1bbf6264e699da0cc65d17ac03fcca6ded1522d19529df7762db46097ff9f"); auto input = TW::Ripple::Proto::SigningInput(); - auto privateKey = TW::PrivateKey(key); + auto privateKey = TW::PrivateKey(key, TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); input.mutable_op_payment()->set_amount(1000000); @@ -46,7 +46,7 @@ TEST(RippleCompiler, CompileRippleWithSignatures) { auto preImageHash = preSigningOutput.data_hash(); EXPECT_EQ(hex(preImageHash), "86ef78df7a4aad29e6b3730f7965c1bd5ccd2439426cb738d7c494a64cfaf4af"); // Simulate signature, normally obtained from signature server - const auto signature = privateKey.sign(TW::data(preImageHash), TWCurveSECP256k1); + const auto signature = privateKey.sign(TW::data(preImageHash)); // Verify signature (pubkey & hash & signature) EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); diff --git a/tests/chains/Zcash/AddressTests.cpp b/tests/chains/Zcash/AddressTests.cpp index 32ea339ed5f..a29a988ce7a 100644 --- a/tests/chains/Zcash/AddressTests.cpp +++ b/tests/chains/Zcash/AddressTests.cpp @@ -11,7 +11,7 @@ namespace TW::Zcash { TEST(ZcashAddress, FromPrivateKey) { - const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54")); + const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"), TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); const auto address = TAddress(publicKey); @@ -21,7 +21,7 @@ TEST(ZcashAddress, FromPrivateKey) { } TEST(ZcashAddress, FromPublicKey) { - const auto privateKey = PrivateKey(parse_hex("BE88DF1D0BF30A923CB39C3BB953178BAAF3726E8D3CE81E7C8462E046E0D835")); + const auto privateKey = PrivateKey(parse_hex("BE88DF1D0BF30A923CB39C3BB953178BAAF3726E8D3CE81E7C8462E046E0D835"), TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); const auto address = TAddress(publicKey); diff --git a/tests/chains/Zcash/TWZcashAddressTests.cpp b/tests/chains/Zcash/TWZcashAddressTests.cpp index 4b1cfd3dee6..6301eef9894 100644 --- a/tests/chains/Zcash/TWZcashAddressTests.cpp +++ b/tests/chains/Zcash/TWZcashAddressTests.cpp @@ -17,7 +17,7 @@ #include TEST(TWZcash, TransparentAddress) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("987919d988ef94e678bce254c932e7a7a76744b2c008467448406d4246513132").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("987919d988ef94e678bce254c932e7a7a76744b2c008467448406d4246513132").get(), TWCoinTypeCurve(TWCoinTypeZcash))); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeZcash)); auto addressString = WRAPS(TWAnyAddressDescription(address.get())); diff --git a/tests/chains/Zcash/TWZcashTransactionTests.cpp b/tests/chains/Zcash/TWZcashTransactionTests.cpp index 243f9554639..7126da30ce1 100644 --- a/tests/chains/Zcash/TWZcashTransactionTests.cpp +++ b/tests/chains/Zcash/TWZcashTransactionTests.cpp @@ -181,7 +181,7 @@ TEST(TWZcashTransaction, BlossomSigning) { utxo0->set_amount(27615); // real key 1p "m/44'/133'/0'/0/14" - auto utxoKey0 = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); + auto utxoKey0 = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"), TWCurveSECP256k1); auto utxoAddr0 = TW::deriveAddress(TWCoinTypeZcash, utxoKey0); auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeZcash); utxo0->set_script(script0.bytes.data(), script0.bytes.size()); diff --git a/tests/chains/Zcash/TransactionCompilerTests.cpp b/tests/chains/Zcash/TransactionCompilerTests.cpp index 6e92a26d50d..183b26e6233 100644 --- a/tests/chains/Zcash/TransactionCompilerTests.cpp +++ b/tests/chains/Zcash/TransactionCompilerTests.cpp @@ -48,7 +48,7 @@ TEST(ZcashCompiler, CompileWithSignatures) { utxo0->set_amount(27615); // real key 1p "m/44'/133'/0'/0/14" - auto utxoKey0 = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); + auto utxoKey0 = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"), TWCurveSECP256k1); auto utxoAddr0 = TW::deriveAddress(TWCoinTypeZcash, utxoKey0); auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeZcash); utxo0->set_script(script0.bytes.data(), script0.bytes.size()); diff --git a/tests/chains/Zelcash/TWZelcashAddressTests.cpp b/tests/chains/Zelcash/TWZelcashAddressTests.cpp index f247625c93d..deb07d608c6 100644 --- a/tests/chains/Zelcash/TWZelcashAddressTests.cpp +++ b/tests/chains/Zelcash/TWZelcashAddressTests.cpp @@ -14,7 +14,7 @@ #include TEST(TWZelcash, TransparentAddress) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("987919d988ef94e678bce254c932e7a7a76744b2c008467448406d4246513132").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("987919d988ef94e678bce254c932e7a7a76744b2c008467448406d4246513132").get(), TWCoinTypeCurve(TWCoinTypeZelcash))); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeZelcash)); auto addressString = WRAPS(TWAnyAddressDescription(address.get())); diff --git a/tests/chains/Zen/AddressTests.cpp b/tests/chains/Zen/AddressTests.cpp index bef8a87317a..c0f4595c24f 100644 --- a/tests/chains/Zen/AddressTests.cpp +++ b/tests/chains/Zen/AddressTests.cpp @@ -23,7 +23,7 @@ TEST(ZenAddress, Invalid) { } TEST(ZenAddress, FromPrivateKey) { - auto privateKey = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")); + auto privateKey = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a"), TWCurveSECP256k1); auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); auto address = Address(pubKey); ASSERT_EQ(address.string(), "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); diff --git a/tests/chains/Zen/SignerTests.cpp b/tests/chains/Zen/SignerTests.cpp index 8b054461429..5aa159115c0 100644 --- a/tests/chains/Zen/SignerTests.cpp +++ b/tests/chains/Zen/SignerTests.cpp @@ -46,7 +46,7 @@ TEST(ZenSigner, Sign) { utxo0->mutable_out_point()->set_sequence(UINT32_MAX); utxo0->set_amount(17600); - auto utxoKey0 = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")); + auto utxoKey0 = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a"), TWCoinTypeCurve(TWCoinTypeZen)); auto utxoAddr0 = TW::deriveAddress(TWCoinTypeZen, utxoKey0); ASSERT_EQ(utxoAddr0, "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeZen, blockHash, blockHeight); diff --git a/tests/chains/Zen/TWAnySignerTests.cpp b/tests/chains/Zen/TWAnySignerTests.cpp index 55a83993273..5fe8e1bad12 100644 --- a/tests/chains/Zen/TWAnySignerTests.cpp +++ b/tests/chains/Zen/TWAnySignerTests.cpp @@ -46,7 +46,7 @@ TEST(TWAnySignerZen, Sign) { utxo0->mutable_out_point()->set_sequence(UINT32_MAX); utxo0->set_amount(17600); - auto utxoKey0 = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")); + auto utxoKey0 = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a"), TWCoinTypeCurve(TWCoinTypeZen)); auto utxoAddr0 = TW::deriveAddress(TWCoinTypeZen, utxoKey0); ASSERT_EQ(utxoAddr0, "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeZen, blockHash, blockHeight); diff --git a/tests/chains/Zen/TransactionBuilderTests.cpp b/tests/chains/Zen/TransactionBuilderTests.cpp index 8ba691be1b6..96d50a75a3c 100644 --- a/tests/chains/Zen/TransactionBuilderTests.cpp +++ b/tests/chains/Zen/TransactionBuilderTests.cpp @@ -52,7 +52,7 @@ TEST(ZenTransactionBuilder, Build) { utxo0->mutable_out_point()->set_sequence(UINT32_MAX); utxo0->set_amount(17600); - auto utxoKey0 = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a")); + auto utxoKey0 = PrivateKey(parse_hex("3a8e0a528f62f4ca2c77744c8a571def2845079b50105a9f7ef6b1b823def67a"), TWCoinTypeCurve(TWCoinTypeZen)); auto utxoAddr0 = TW::deriveAddress(TWCoinTypeZen, utxoKey0); ASSERT_EQ(utxoAddr0, "znk19H1wcARcCa7TM6zgmJUbWoWWtZ8k5cg"); auto script0 = Bitcoin::Script::lockScriptForAddress(utxoAddr0, TWCoinTypeZen, blockHash, blockHeight); diff --git a/tests/chains/Zilliqa/AddressTests.cpp b/tests/chains/Zilliqa/AddressTests.cpp index db26af859b7..26861fb8ca7 100644 --- a/tests/chains/Zilliqa/AddressTests.cpp +++ b/tests/chains/Zilliqa/AddressTests.cpp @@ -15,7 +15,7 @@ namespace TW::Zilliqa::tests { TEST(ZilliqaAddress, FromPrivateKey) { const auto privateKey = - PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6")); + PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6"), TWCurveSECP256k1); const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); const auto address = Address(publicKey); auto expectedAddress = "zil1j8xae6lggm8y63m3y2r7aefu797ze7mhzulnqg"; diff --git a/tests/chains/Zilliqa/SignatureTests.cpp b/tests/chains/Zilliqa/SignatureTests.cpp index bea022cd485..a629ac7e72e 100644 --- a/tests/chains/Zilliqa/SignatureTests.cpp +++ b/tests/chains/Zilliqa/SignatureTests.cpp @@ -14,7 +14,7 @@ using namespace TW; TEST(ZilliqaSignature, Signing) { auto keyData = WRAPD(TWDataCreateWithHexString(STRING("0xafeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get())); - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(keyData.get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(keyData.get(), TWCoinTypeCurve(TWCoinTypeZilliqa))); auto pubKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); auto message = "hello schnorr"; diff --git a/tests/chains/Zilliqa/SignerTests.cpp b/tests/chains/Zilliqa/SignerTests.cpp index f8e0440307d..dec0e6f67fe 100644 --- a/tests/chains/Zilliqa/SignerTests.cpp +++ b/tests/chains/Zilliqa/SignerTests.cpp @@ -14,7 +14,7 @@ namespace TW::Zilliqa::tests { TEST(ZilliqaSigner, PreImage) { - auto privateKey = PrivateKey(parse_hex("0E891B9DFF485000C7D1DC22ECF3A583CC50328684321D61947A86E57CF6C638")); + auto privateKey = PrivateKey(parse_hex("0E891B9DFF485000C7D1DC22ECF3A583CC50328684321D61947A86E57CF6C638"), TWCurveSECP256k1); auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ(hex(pubKey.bytes), "034ae47910d58b9bde819c3cffa8de4441955508db00aa2540db8e6bf6e99abc1b"); @@ -46,7 +46,7 @@ TEST(ZilliqaSigner, PreImage) { } TEST(ZilliqaSigner, Signing) { - auto privateKey = PrivateKey(parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748")); + auto privateKey = PrivateKey(parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748"), TWCurveSECP256k1); auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); // 1 ZIL @@ -76,7 +76,7 @@ TEST(ZilliqaSigner, Signing) { TEST(ZilliqaSigner, SigningData) { // https://viewblock.io/zilliqa/tx/0x6228b3d7e69fc3481b84fd00e892cec359a41654f58948ff7b1b932396b00ad9 - auto privateKey = PrivateKey(parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748")); + auto privateKey = PrivateKey(parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748"), TWCurveSECP256k1); auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); // 10 ZIL diff --git a/tests/common/Bech32AddressTests.cpp b/tests/common/Bech32AddressTests.cpp index 11d8ec18b81..27a54f35d4e 100644 --- a/tests/common/Bech32AddressTests.cpp +++ b/tests/common/Bech32AddressTests.cpp @@ -99,33 +99,33 @@ TEST(Bech32Address, FromKeyHash) { TEST(Bech32Address, FromPublicKey) { { - auto privateKey = PrivateKey(parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832")); + auto privateKey = PrivateKey(parse_hex("95949f757db1f57ca94a5dff23314accbe7abee89597bf6a3c7382c84d7eb832"), TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ(hex(publicKey.bytes), "026a35920088d98c3888ca68c53dfc93f4564602606cbb87f0fe5ee533db38e502"); auto address = Bech32Address("bnb", Hash::HasherSha256ripemd, publicKey); ASSERT_EQ("bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", address.string()); } { - auto privateKey = PrivateKey(parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005")); + auto privateKey = PrivateKey(parse_hex("80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"), TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ(hex(publicKey.bytes), "0257286ec3f37d33557bbbaa000b27744ac9023aa9967cae75a181d1ff91fa9dc5"); auto address = Bech32Address("cosmos", Hash::HasherSha256ripemd, publicKey); ASSERT_EQ(address.string(), "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02"); } { - auto privateKey = PrivateKey(parse_hex("e2f88b4974ae763ca1c2db49218802c2e441293a09eaa9ab681779e05d1b7b94")); + auto privateKey = PrivateKey(parse_hex("e2f88b4974ae763ca1c2db49218802c2e441293a09eaa9ab681779e05d1b7b94"), TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); auto address = Bech32Address("one", Hash::HasherKeccak256, publicKey); ASSERT_EQ(address.string(), "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe"); } { - auto privateKey = PrivateKey(parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f")); + auto privateKey = PrivateKey(parse_hex("0806c458b262edd333a191e92f561aff338211ee3e18ab315a074a2d82aa343f"), TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); auto address = Bech32Address("io", Hash::HasherKeccak256, publicKey); ASSERT_EQ(address.string(), "io187wzp08vnhjjpkydnr97qlh8kh0dpkkytfam8j"); } { - const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6")); + const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6"), TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ(hex(publicKey.bytes), "02b65744e8bd0ba7666468abaff2aeb862c88a25ed605e0153100aa8f2661c1c3d"); const auto address = Bech32Address("zil", Hash::HasherSha256, publicKey); @@ -136,7 +136,7 @@ TEST(Bech32Address, FromPublicKey) { // From same public key, but different hashes: different results TEST(Bech32Address, Hashes) { - const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6")); + const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6"), TWCurveSECP256k1); auto publicKey1 = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ("02b65744e8bd0ba7666468abaff2aeb862c88a25ed605e0153100aa8f2661c1c3d", hex(publicKey1.bytes)); @@ -158,7 +158,7 @@ TEST(Bech32Address, Hashes) { // From same public key, but different prefixes: different results (checksum) TEST(Bech32Address, Prefixes) { - const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6")); + const auto privateKey = PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6"), TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); ASSERT_EQ("02b65744e8bd0ba7666468abaff2aeb862c88a25ed605e0153100aa8f2661c1c3d", hex(publicKey.bytes)); diff --git a/tests/common/CoinAddressDerivationTests.cpp b/tests/common/CoinAddressDerivationTests.cpp index fd35b42420c..3e722a0f481 100644 --- a/tests/common/CoinAddressDerivationTests.cpp +++ b/tests/common/CoinAddressDerivationTests.cpp @@ -14,8 +14,8 @@ namespace TW { TEST(Coin, DeriveAddress) { auto dummyKeyData = parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"); - const auto privateKey = PrivateKey(dummyKeyData); - const auto privateKeyExt = PrivateKey(dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData); + const auto privateKey = PrivateKey(dummyKeyData, TWCurveSECP256k1); + const auto privateKeyExt = PrivateKey(dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData, TWCurveSECP256k1); const auto coins = TW::getCoinTypes(); for (auto& c : coins) { diff --git a/tests/common/HDWallet/HDWalletInternalTests.cpp b/tests/common/HDWallet/HDWalletInternalTests.cpp index 19e8c45868f..6b0554a65d3 100644 --- a/tests/common/HDWallet/HDWalletInternalTests.cpp +++ b/tests/common/HDWallet/HDWalletInternalTests.cpp @@ -31,7 +31,7 @@ std::string nodeToHexString(const HDNode& node) { } Data publicKeyFromPrivateKey(const Data& privateKey) { - return PrivateKey(privateKey).getPublicKey(TWPublicKeyTypeSECP256k1).bytes; + return PrivateKey(privateKey, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes; } TEST(HDWalletInternal, SquareDerivationRoutes) { diff --git a/tests/common/PrivateKeyTests.cpp b/tests/common/PrivateKeyTests.cpp index fa9ec36aea3..aaf8d1c0923 100644 --- a/tests/common/PrivateKeyTests.cpp +++ b/tests/common/PrivateKeyTests.cpp @@ -15,17 +15,16 @@ using namespace TW; using namespace std; namespace TW::tests { - TEST(PrivateKey, CreateValid) { Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); EXPECT_TRUE(PrivateKey::isValid(privKeyData, TWCurveSECP256k1)); - auto privateKey = PrivateKey(privKeyData); + auto privateKey = PrivateKey(privKeyData, TWCurveSECP256k1); EXPECT_EQ(hex(privKeyData), hex(privateKey.bytes)); } string TestInvalid(const Data& privKeyData) { try { - auto privateKey = PrivateKey(privKeyData); + auto privateKey = PrivateKey(privKeyData, TWCurveSECP256k1); return hex(privateKey.bytes); } catch (invalid_argument& ex) { // expected exception @@ -58,7 +57,7 @@ TEST(PrivateKey, InvalidSECP256k1) { string TestInvalidExtended(const Data& data, const Data& ext, const Data& chainCode, const Data& data2, const Data& ext2, const Data& chainCode2) { try { - auto privateKey = PrivateKey(data, ext, chainCode, data2, ext2, chainCode2); + auto privateKey = PrivateKey(data, ext, chainCode, data2, ext2, chainCode2, TWCurveSECP256k1); return hex(privateKey.bytes); } catch (invalid_argument& ex) { // expected exception @@ -117,26 +116,29 @@ TEST(PrivateKey, Valid) { TEST(PrivateKey, PublicKey) { Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(privKeyData); { + const auto privateKey = PrivateKey(privKeyData, TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); EXPECT_EQ( "4870d56d074c50e891506d78faa4fb69ca039cc5f131eb491e166b975880e867", hex(publicKey.bytes)); } { + const auto privateKey = PrivateKey(privKeyData, TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_EQ( "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1", hex(publicKey.bytes)); } { + const auto privateKey = PrivateKey(privKeyData, TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); EXPECT_EQ( "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91", hex(publicKey.bytes)); } { + const auto privateKey = PrivateKey(privKeyData, TWCurveNIST256p1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1Extended); EXPECT_EQ( "046d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab918b4fe46ccbf56701fb210d67d91c5779468f6b3fdc7a63692b9b62543f47ae", @@ -146,7 +148,7 @@ TEST(PrivateKey, PublicKey) { TEST(PrivateKey, Cleanup) { Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = new PrivateKey(privKeyData); + auto privateKey = new PrivateKey(privKeyData, TWCurveSECP256k1); auto ptr = privateKey->bytes.data(); ASSERT_EQ(hex(privKeyData), hex(data(ptr, 32))); @@ -172,7 +174,7 @@ TEST(PrivateKey, GetType) { TEST(PrivateKey, PrivateKeyExtended) { // Non-extended: both keys are 32 bytes. auto privateKeyNonext = PrivateKey(parse_hex( - "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveED25519); EXPECT_EQ("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", hex(privateKeyNonext.bytes)); auto publicKeyNonext = privateKeyNonext.getPublicKey(TWPublicKeyTypeED25519); EXPECT_EQ(32ul, publicKeyNonext.bytes.size()); @@ -185,7 +187,7 @@ TEST(PrivateKey, PrivateKeyExtended) { "d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7b" "ed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a"; // Extended keys: private key is 2x3x32 bytes, public key is 2x64 bytes - auto privateKeyExt = PrivateKey(parse_hex(fullkey)); + auto privateKeyExt = PrivateKey(parse_hex(fullkey), TWCurveED25519ExtendedCardano); EXPECT_EQ(fullkey, hex(privateKeyExt.bytes)); EXPECT_EQ("b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744", hex(privateKeyExt.key())); EXPECT_EQ("309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71eff", hex(privateKeyExt.extension())); @@ -204,14 +206,16 @@ TEST(PrivateKey, PrivateKeyExtended) { parse_hex("bf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4"), parse_hex("639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05"), parse_hex("d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7b"), - parse_hex("ed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a")); + parse_hex("ed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a"), + TWCurveED25519ExtendedCardano + ); EXPECT_EQ(fullkey, hex(privateKeyExt.bytes)); } TEST(PrivateKey, PrivateKeyExtendedError) { // TWPublicKeyTypeED25519Cardano pubkey with non-extended private: error auto privateKeyNonext = PrivateKey(parse_hex( - "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveED25519ExtendedCardano); try { auto publicKeyError = privateKeyNonext.getPublicKey(TWPublicKeyTypeED25519Cardano); } catch (invalid_argument& ex) { @@ -223,10 +227,10 @@ TEST(PrivateKey, PrivateKeyExtendedError) { TEST(PrivateKey, SignSECP256k1) { Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(privKeyData); + auto privateKey = PrivateKey(privKeyData, TWCurveSECP256k1); Data messageData = TW::data("hello"); Data hash = Hash::keccak256(messageData); - Data actual = privateKey.sign(hash, TWCurveSECP256k1); + Data actual = privateKey.sign(hash); EXPECT_EQ( "8720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de901", @@ -236,10 +240,12 @@ TEST(PrivateKey, SignSECP256k1) { TEST(PrivateKey, SignExtended) { const auto privateKeyExt = PrivateKey(parse_hex( "b0884d248cb301edd1b34cf626ba6d880bb3ae8fd91b4696446999dc4f0b5744309941d56938e943980d11643c535e046653ca6f498c014b88f2ad9fd6e71effbf36a8fa9f5e11eb7a852c41e185e3969d518e66e6893c81d3fc7227009952d4" - "639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7bed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a")); + "639aadd8b6499ae39b78018b79255fbd8f585cbda9cbb9e907a72af86afb7a05d41a57c2dec9a6a19d6bf3b1fa784f334f3a0048d25ccb7b78a7b44066f9ba7bed7f28be986cbe06819165f2ee41b403678a098961013cf4a2f3e9ea61fb6c1a"), + TWCurveED25519ExtendedCardano + ); Data messageData = TW::data("hello"); Data hash = Hash::keccak256(messageData); - Data actual = privateKeyExt.sign(hash, TWCurveED25519ExtendedCardano); + Data actual = privateKeyExt.sign(hash); EXPECT_EQ( "375df53b6a4931dcf41e062b1c64288ed4ff3307f862d5c1b1c71964ce3b14c99422d0fdfeb2807e9900a26d491d5e8a874c24f98eec141ed694d7a433a90f08", @@ -247,7 +253,7 @@ TEST(PrivateKey, SignExtended) { } TEST(PrivateKey, SignSchnorr) { - const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); const Data messageData = TW::data("hello schnorr"); const Data digest = Hash::sha256(messageData); const auto signature = privateKey.signZilliqa(digest); @@ -257,10 +263,10 @@ TEST(PrivateKey, SignSchnorr) { TEST(PrivateKey, SignNIST256p1) { Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(privKeyData); + auto privateKey = PrivateKey(privKeyData, TWCurveNIST256p1); Data messageData = TW::data("hello"); Data hash = Hash::keccak256(messageData); - Data actual = privateKey.sign(hash, TWCurveNIST256p1); + Data actual = privateKey.sign(hash); EXPECT_EQ( "8859e63a0c0cc2fc7f788d7e78406157b288faa6f76f76d37c4cd1534e8d83c468f9fd6ca7dde378df594625dcde98559389569e039282275e3d87c26e36447401", @@ -275,8 +281,8 @@ TEST(PrivateKey, SignNIST256p1VerifyLegacy) { Data msg(32); random_buffer(msg.data(), 32); - PrivateKey privateKey(secret); - auto signature = privateKey.sign(msg, TWCurveNIST256p1); + PrivateKey privateKey(secret, TWCurveNIST256p1); + auto signature = privateKey.sign(msg); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1); EXPECT_TRUE(TrezorCrypto::verifyNist256p1Signature(publicKey.bytes, signature, msg)) @@ -292,10 +298,10 @@ int isCanonical([[maybe_unused]] uint8_t by, [[maybe_unused]] uint8_t sig[64]) { TEST(PrivateKey, SignCanonicalSECP256k1) { Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(privKeyData); + auto privateKey = PrivateKey(privKeyData, TWCurveSECP256k1); Data messageData = TW::data("hello"); Data hash = Hash::keccak256(messageData); - Data actual = privateKey.sign(hash, TWCurveSECP256k1, isCanonical); + Data actual = privateKey.sign(hash, isCanonical); EXPECT_EQ( "208720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de9", @@ -304,18 +310,20 @@ TEST(PrivateKey, SignCanonicalSECP256k1) { TEST(PrivateKey, SignShortDigest) { Data privKeyData = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(privKeyData); Data shortDigest = TW::data("12345"); { - Data actual = privateKey.sign(shortDigest, TWCurveSECP256k1); + auto privateKey = PrivateKey(privKeyData, TWCurveSECP256k1); + Data actual = privateKey.sign(shortDigest); EXPECT_EQ(actual.size(), 0ul); } { - Data actual = privateKey.sign(shortDigest, TWCurveNIST256p1); + auto privateKey = PrivateKey(privKeyData, TWCurveNIST256p1); + Data actual = privateKey.sign(shortDigest); EXPECT_EQ(actual.size(), 0ul); } { - Data actual = privateKey.sign(shortDigest, TWCurveSECP256k1, isCanonical); + auto privateKey = PrivateKey(privKeyData, TWCurveSECP256k1); + Data actual = privateKey.sign(shortDigest, isCanonical); EXPECT_EQ(actual.size(), 0ul); } } diff --git a/tests/common/PublicKeyTests.cpp b/tests/common/PublicKeyTests.cpp index d73c1c1ac8a..d69e1bc7952 100644 --- a/tests/common/PublicKeyTests.cpp +++ b/tests/common/PublicKeyTests.cpp @@ -15,7 +15,7 @@ using namespace TW; TEST(PublicKeyTests, CreateFromPrivateSecp256k1) { const Data key = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(key); + auto privateKey = PrivateKey(key, TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_EQ(publicKey.bytes.size(), 33ul); EXPECT_EQ(hex(publicKey.bytes), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); @@ -43,7 +43,7 @@ TEST(PublicKeyTests, CreateBlake) { const auto privateKeyHex = "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"; const auto publicKeyKeyHex = "b689ab808542e13f3d2ec56fe1efe43a1660dcadc73ce489fde7df98dd8ce5d9"; { - auto publicKey = PrivateKey(parse_hex(privateKeyHex)).getPublicKey(TWPublicKeyTypeED25519Blake2b); + auto publicKey = PrivateKey(parse_hex(privateKeyHex), TWCurveED25519Blake2bNano).getPublicKey(TWPublicKeyTypeED25519Blake2b); EXPECT_EQ(hex(publicKey.bytes), publicKeyKeyHex); EXPECT_EQ(publicKey.bytes.size(), 32ul); } @@ -55,7 +55,7 @@ TEST(PublicKeyTests, CreateBlake) { TEST(PublicKeyTests, CompressedExtended) { const Data key = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(key); + auto privateKey = PrivateKey(key, TWCurveSECP256k1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_EQ(publicKey.type, TWPublicKeyTypeSECP256k1); EXPECT_EQ(publicKey.bytes.size(), 33ul); @@ -92,7 +92,7 @@ TEST(PublicKeyTests, CompressedExtended) { TEST(PublicKeyTests, CompressedExtendedNist) { const Data key = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(key); + auto privateKey = PrivateKey(key, TWCurveNIST256p1); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1); EXPECT_EQ(publicKey.type, TWPublicKeyTypeNIST256p1); EXPECT_EQ(publicKey.bytes.size(), 33ul); @@ -129,7 +129,7 @@ TEST(PublicKeyTests, CompressedExtendedNist) { TEST(PublicKeyTests, CompressedExtendedED25519) { const Data key = parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"); - auto privateKey = PrivateKey(key); + auto privateKey = PrivateKey(key, TWCurveED25519); auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); EXPECT_EQ(publicKey.type, TWPublicKeyTypeED25519); EXPECT_EQ(publicKey.bytes.size(), 32ul); @@ -155,32 +155,34 @@ TEST(PublicKeyTests, IsValidWrongType) { } TEST(PublicKeyTests, Verify) { - const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); - const char* message = "Hello"; const Data messageData = TW::data(message); const Data digest = Hash::sha256(messageData); { - const auto signature = privateKey.sign(digest, TWCurveSECP256k1); + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); + const auto signature = privateKey.sign(digest); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); EXPECT_TRUE(publicKey.verify(signature, digest)); EXPECT_EQ(hex(signature), "0f5d5a9e5fc4b82a625312f3be5d3e8ad017d882de86c72c92fcefa924e894c12071772a14201a3a0debf381b5e8dea39fadb9bcabdc02ee71ab018f55bf717f01"); } { - const auto signature = privateKey.sign(digest, TWCurveED25519); + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveED25519); + const auto signature = privateKey.sign(digest); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); EXPECT_TRUE(publicKey.verify(signature, digest)); EXPECT_EQ(hex(signature), "42848abf2641a731e18b8a1fb80eff341a5acebdc56faeccdcbadb960aef775192842fccec344679446daa4d02d264259c8f9aa364164ebe0ebea218581e2e03"); } { - const auto signature = privateKey.sign(digest, TWCurveED25519Blake2bNano); + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveED25519Blake2bNano); + const auto signature = privateKey.sign(digest); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Blake2b); EXPECT_TRUE(publicKey.verify(signature, digest)); EXPECT_EQ(hex(signature), "5c1473944cd0234ebc5a91b2966b9e707a33b936dadd149417a2e53b6b3fc97bef17b767b1690708c74d7b4c8fe48703fd44a6ef59d4cc5b9f88ba992db0a003"); } { - const auto signature = privateKey.sign(digest, TWCurveNIST256p1); + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveNIST256p1); + const auto signature = privateKey.sign(digest); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1Extended); EXPECT_TRUE(publicKey.verify(signature, digest)); EXPECT_EQ(hex(signature), "2e4655831f0c60729583595c103bf0d862af6313e4326f03f512682106c792822f5a9cd21e7d4a3316c2d337e5eee649b09c34f7b4407344f0d32e8d33167d8901"); @@ -200,7 +202,7 @@ TEST(PublicKeyTests, ED25519_malleability) { } TEST(PublicKeyTests, VerifyAsDER) { - const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); const char* message = "Hello"; const Data messageData = TW::data(message); @@ -224,19 +226,19 @@ TEST(PublicKeyTests, VerifyAsDER) { } TEST(PublicKeyTests, VerifyEd25519Extended) { - const auto privateKey = PrivateKey(parse_hex("e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b574437aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fae0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276")); + const auto privateKey = PrivateKey(parse_hex("e8c8c5b2df13f3abed4e6b1609c808e08ff959d7e6fc3d849e3f2880550b574437aa559095324d78459b9bb2da069da32337e1cc5da78f48e1bd084670107f3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26fae0d152bb611cb9ff34e945e4ff627e6fba81da687a601a879759cd76530b5744424db69a75edd4780a5fbc05d1a3c84ac4166ff8e424808481dd8e77627ce5f5bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276"), TWCurveED25519ExtendedCardano); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); const auto message = TW::data("Hello"); const auto digest = Hash::sha256(message); - const auto signature = privateKey.sign(digest, TWCurveED25519ExtendedCardano); + const auto signature = privateKey.sign(digest); const auto valid = publicKey.verify(signature, digest); EXPECT_TRUE(valid); } TEST(PublicKeyTests, VerifySchnorr) { - const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); const auto privateKey = PrivateKey(key); const Data messageData = TW::data("hello schnorr"); @@ -249,7 +251,7 @@ TEST(PublicKeyTests, VerifySchnorr) { } TEST(PublicKeyTests, VerifySchnorrWrongType) { - const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); const auto privateKey = PrivateKey(key); const Data messageData = TW::data("hello schnorr"); @@ -284,13 +286,13 @@ TEST(PublicKeyTests, RecoverRaw) { } TEST(PublicKeyTests, SignAndRecoverRaw) { - const auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); + const auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904"), TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); EXPECT_EQ(hex(publicKey.bytes), "0463ade8ebc212b85e7e4278dc3dcb4f9cc18aab912ef5d302b5d1940e772e9e1a9213522efddad487bbd5dd7907e8e776f918e9a5e4cb51893724e9fe76792a4f"); const auto message = parse_hex("6468eb103d51c9a683b51818fdb73390151c9973831d2cfb4e9587ad54273155"); // sign - const auto signature = privateKey.sign(message, TWCurveSECP256k1); + const auto signature = privateKey.sign(message); EXPECT_EQ(hex(signature), "92c336138f7d0231fe9422bb30ee9ef10bf222761fe9e04442e3a11e88880c646487026011dae03dc281bc21c7d7ede5c2226d197befb813a4ecad686b559e5800"); // revocer @@ -322,7 +324,7 @@ TEST(PublicKeyTests, Recover) { "0456d8089137b1fd0d890f8c7d4a04d0fd4520a30b19518ee87bd168ea12ed8090329274c4c6c0d9df04515776f2741eeffc30235d596065d718c3973e19711ad0"); } - const auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904")); + const auto privateKey = PrivateKey(parse_hex("4f96ed80e9a7555a6f74b3d658afdd9c756b0a40d4ca30c42c2039eb449bb904"), TWCurveSECP256k1); const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended); EXPECT_EQ(hex(publicKey.bytes), "0463ade8ebc212b85e7e4278dc3dcb4f9cc18aab912ef5d302b5d1940e772e9e1a9213522efddad487bbd5dd7907e8e776f918e9a5e4cb51893724e9fe76792a4f"); { diff --git a/tests/interface/TWPrivateKeyTests.cpp b/tests/interface/TWPrivateKeyTests.cpp index 3c4882247ec..63102e99543 100644 --- a/tests/interface/TWPrivateKeyTests.cpp +++ b/tests/interface/TWPrivateKeyTests.cpp @@ -17,7 +17,7 @@ const auto key1Hex = "22667b69166481c9f334756f49c8dddfd72c6bcdd68a7386886e97a82f741130"; TEST(TWPrivateKeyTests, Create) { - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(key1Hex).get())); + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(key1Hex).get(), TWCurveSECP256k1)); ASSERT_TRUE(privateKey.get() != nullptr); const auto data = WRAPD(TWPrivateKeyData(privateKey.get())); @@ -25,17 +25,17 @@ TEST(TWPrivateKeyTests, Create) { } TEST(TWPrivateKeyTests, CreateNewRandom) { - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreate()); + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreate(TWCurveSECP256k1)); ASSERT_TRUE(privateKey.get() != nullptr); } TEST(TWPrivateKeyTests, CreateInvalid) { - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("deadbeef").get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("deadbeef").get(), TWCurveSECP256k1)); ASSERT_EQ(privateKey.get(), nullptr); } TEST(TWPrivateKeyTests, CreateCopy) { - const auto privateKey1 = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(key1Hex).get())); + const auto privateKey1 = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA(key1Hex).get(), TWCurveSECP256k1)); ASSERT_TRUE(privateKey1.get() != nullptr); const auto privateKey2 = WRAP(TWPrivateKey, TWPrivateKeyCreateCopy(privateKey1.get())); ASSERT_TRUE(privateKey2.get() != nullptr); @@ -44,7 +44,7 @@ TEST(TWPrivateKeyTests, CreateCopy) { TEST(TWPrivateKeyTests, AllZeros) { auto bytes = TW::Data(32); auto data = WRAPD(TWDataCreateWithBytes(bytes.data(), bytes.size())); - auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(data.get())); + auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(data.get(), TWCurveSECP256k1)); ASSERT_EQ(privateKey.get(), nullptr); } @@ -68,20 +68,23 @@ TEST(TWPrivateKeyTests, IsValid) { } TEST(TWPrivateKeyTests, PublicKey) { - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get())); { + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get(), TWCurveSECP256k1)); const auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), false)); ASSERT_EQ(TW::hex(publicKey.get()->impl.bytes), "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91"); } { + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get(), TWCurveNIST256p1)); const auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyNist256p1(privateKey.get())); ASSERT_EQ(TW::hex(publicKey.get()->impl.bytes), "026d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab"); } { + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get(), TWCurveCurve25519)); const auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyCurve25519(privateKey.get())); ASSERT_EQ(TW::hex(publicKey.get()->impl.bytes), "686cfce9108566dd43fc6aa75e31f9a9f319c9e9c04d6ad0a52505b86bc17c3a"); } { + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get(), TWCoinTypeCurve(TWCoinTypeEthereum))); const auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeEthereum)); ASSERT_EQ(TW::hex(publicKey.get()->impl.bytes), "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91"); @@ -91,30 +94,32 @@ TEST(TWPrivateKeyTests, PublicKey) { ASSERT_EQ(TW::hex(publicKey.get()->impl.bytes), TW::hex(publicKeyByType.get()->impl.bytes)); } { + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get(), TWCoinTypeCurve(TWCoinTypeNEO))); const auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeNEO)); ASSERT_EQ(TW::hex(publicKey.get()->impl.bytes), "026d786ab8fda678cf50f71d13641049a393b325063b8c0d4e5070de48a2caf9ab"); } { + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get(), TWCoinTypeCurve(TWCoinTypeWaves))); const auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypeWaves)); ASSERT_EQ(TW::hex(publicKey.get()->impl.bytes), "686cfce9108566dd43fc6aa75e31f9a9f319c9e9c04d6ad0a52505b86bc17c3a"); } } TEST(TWPrivateKeyTests, Sign) { - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get())); + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get(), TWCurveSECP256k1)); const auto message = "hello"; const auto data = WRAPD(TWDataCreateWithBytes((uint8_t *)message, strlen(message))); const auto hash = WRAPD(TWHashKeccak256(data.get())); - const auto actual = WRAPD(TWPrivateKeySign(privateKey.get(), hash.get(), TWCurveSECP256k1)); + const auto actual = WRAPD(TWPrivateKeySign(privateKey.get(), hash.get())); ASSERT_EQ(TW::hex(*((TW::Data*)actual.get())), "8720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de901"); } TEST(TWPrivateKeyTests, SignAsDER) { - const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get())); + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get(), TWCurveSECP256k1)); const auto message = "hello"; const auto data = WRAPD(TWDataCreateWithBytes((uint8_t *)message, strlen(message))); diff --git a/tests/interface/TWPublicKeyTests.cpp b/tests/interface/TWPublicKeyTests.cpp index 16b707e0a95..4832a854b71 100644 --- a/tests/interface/TWPublicKeyTests.cpp +++ b/tests/interface/TWPublicKeyTests.cpp @@ -25,7 +25,7 @@ TEST(TWPublicKeyTests, Create) { } TEST(TWPublicKeyTests, CreateFromPrivateSecp256k1) { - const PrivateKey key(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const PrivateKey key(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); @@ -43,7 +43,7 @@ TEST(TWPublicKeyTests, CreateInvalid) { } TEST(TWPublicKeyTests, CompressedExtended) { - const PrivateKey key(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const PrivateKey key(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveCurve25519); const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); EXPECT_EQ(TWPublicKeyKeyType(publicKey.get()), TWPublicKeyTypeSECP256k1); @@ -66,21 +66,21 @@ TEST(TWPublicKeyTests, CompressedExtended) { } TEST(TWPublicKeyTests, Verify) { - const PrivateKey key(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const PrivateKey key(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); const char* message = "Hello"; auto messageData = WRAPD(TWDataCreateWithBytes((const uint8_t*)message, strlen(message))); auto digest = WRAPD(TWHashKeccak256(messageData.get())); - auto signature = WRAPD(TWPrivateKeySign(privateKey.get(), digest.get(), TWCurveSECP256k1)); + auto signature = WRAPD(TWPrivateKeySign(privateKey.get(), digest.get())); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), false)); ASSERT_TRUE(TWPublicKeyVerify(publicKey.get(), signature.get(), digest.get())); } TEST(TWPublicKeyTests, VerifyAsDER) { - const PrivateKey key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); + const PrivateKey key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); const char* message = "Hello"; @@ -97,18 +97,19 @@ TEST(TWPublicKeyTests, VerifyAsDER) { } TEST(TWPublicKeyTests, VerifyEd25519) { - const PrivateKey key(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")); - const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); - const char* message = "Hello"; auto messageData = WRAPD(TWDataCreateWithBytes((const uint8_t*)message, strlen(message))); auto digest = WRAPD(TWHashSHA256(messageData.get())); - auto signature = WRAPD(TWPrivateKeySign(privateKey.get(), digest.get(), TWCurveED25519)); - auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(privateKey.get())); + const PrivateKey key1(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveED25519); + const auto privateKey1 = WRAP(TWPrivateKey, new TWPrivateKey{ key1 }); + auto signature = WRAPD(TWPrivateKeySign(privateKey1.get(), digest.get())); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519(privateKey1.get())); - auto signature2 = WRAPD(TWPrivateKeySign(privateKey.get(), digest.get(), TWCurveED25519Blake2bNano)); - auto publicKey2 = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519Blake2b(privateKey.get())); + const PrivateKey key2(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveED25519Blake2bNano); + const auto privateKey2 = WRAP(TWPrivateKey, new TWPrivateKey{ key2 }); + auto signature2 = WRAPD(TWPrivateKeySign(privateKey2.get(), digest.get())); + auto publicKey2 = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyEd25519Blake2b(privateKey2.get())); ASSERT_TRUE(TWPublicKeyVerify(publicKey.get(), signature.get(), digest.get())); ASSERT_TRUE(TWPublicKeyVerify(publicKey2.get(), signature2.get(), digest.get())); diff --git a/tests/interface/TWTransactionCompilerTests.cpp b/tests/interface/TWTransactionCompilerTests.cpp index 806de4959da..eb52f41d2d5 100644 --- a/tests/interface/TWTransactionCompilerTests.cpp +++ b/tests/interface/TWTransactionCompilerTests.cpp @@ -431,9 +431,9 @@ TEST(TWTransactionCompiler, ExternalSignatureSignBitcoin) { // 2 private keys are needed (despite >2 UTXOs) auto key0 = parse_hex("4646464646464646464646464646464646464646464646464646464646464646"); auto key1 = parse_hex("7878787878787878787878787878787878787878787878787878787878787878"); - EXPECT_EQ(hex(PrivateKey(key0).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + EXPECT_EQ(hex(PrivateKey(key0, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), hex(inPubKey0)); - EXPECT_EQ(hex(PrivateKey(key1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), + EXPECT_EQ(hex(PrivateKey(key1, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes), hex(inPubKey1)); *input.add_private_key() = std::string(key0.begin(), key0.end()); *input.add_private_key() = std::string(key1.begin(), key1.end()); diff --git a/walletconsole/lib/Address.cpp b/walletconsole/lib/Address.cpp index 166efc215cc..804b575309e 100644 --- a/walletconsole/lib/Address.cpp +++ b/walletconsole/lib/Address.cpp @@ -46,7 +46,7 @@ bool Address::addrPri(const string& coinid, const string& prikey_in, string& res return false; } auto ctype = (TWCoinType)coin.c; - PrivateKey priKey = PrivateKey(priDat); + PrivateKey priKey = PrivateKey(priDat, TWCoinTypeCurve(ctype)); res = TW::deriveAddress(ctype, priKey); return true; } diff --git a/walletconsole/lib/Keys.cpp b/walletconsole/lib/Keys.cpp index b30e90b98f8..7545b1e4f0c 100644 --- a/walletconsole/lib/Keys.cpp +++ b/walletconsole/lib/Keys.cpp @@ -52,7 +52,7 @@ bool Keys::pubPri(const string& coinid, const string& p, string& res) { Data privDat; try { privDat = parse_hex(p); - auto priv = PrivateKey(privDat); + auto priv = PrivateKey(privDat, TWCoinTypeCurve(TWCoinType(coin.c))); auto pub = priv.getPublicKey((TWPublicKeyType)coin.pubKeyType); res = hex(pub.bytes); _out << "Public key created, type " << (int)coin.pubKeyType << ", length " << pub.bytes.size() << endl; diff --git a/wasm/tests/Blockchain/Aptos.test.ts b/wasm/tests/Blockchain/Aptos.test.ts index 98ccaf9ce27..3b5eab56e33 100644 --- a/wasm/tests/Blockchain/Aptos.test.ts +++ b/wasm/tests/Blockchain/Aptos.test.ts @@ -10,7 +10,7 @@ import Long = require("long"); describe("Aptos", () => { it("test sign aptos", () => { - const { PrivateKey, HexCoding, AnySigner, AnyAddress, CoinType } = globalThis.core; + const { PrivateKey, HexCoding, AnySigner, AnyAddress, CoinType, CoinTypeExt } = globalThis.core; const txDataInput = TW.Aptos.Proto.SigningInput.create({ chainId: 33, sender: "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", @@ -26,6 +26,7 @@ describe("Aptos", () => { HexCoding.decode( "0x5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", ), + CoinTypeExt.curve(CoinType.aptos) ).data(), }); const input = TW.Aptos.Proto.SigningInput.encode(txDataInput).finish(); diff --git a/wasm/tests/Blockchain/Bitcoin.test.ts b/wasm/tests/Blockchain/Bitcoin.test.ts index f839f31b8f3..81be2ab5656 100644 --- a/wasm/tests/Blockchain/Bitcoin.test.ts +++ b/wasm/tests/Blockchain/Bitcoin.test.ts @@ -16,14 +16,14 @@ describe("Bitcoin", () => { // Transfer from P2TR to P2WPKH address. // Successfully broadcasted: https://mempool.space/tx/a9c63dfe54f6ff462155d966a54226c456b3e43b52a9abe55d7fa87d6564c6e4 it("test Bitcoin sign P2TR", () => { - const { AnySigner, BitcoinSigHashType, PrivateKey, HexCoding, CoinType } = globalThis.core; + const { AnySigner, BitcoinSigHashType, PrivateKey, HexCoding, CoinType, CoinTypeExt } = globalThis.core; const Proto = TW.BitcoinV2.Proto; const privateKeyData = HexCoding.decode("7fa638b0df495b2968ae6dc7011c4db08c86df16c91aa71a77ee6a222954e5bb"); const dustAmount = new Long(546); const utxoTxId = HexCoding.decode("75ed78f0ae2bad924065d2357ef01184ceee2181c44e03337746512be9371a82").reverse(); - const privateKey = PrivateKey.createWithData(privateKeyData); + const privateKey = PrivateKey.createWithData(privateKeyData, CoinTypeExt.curve(CoinType.bitcoin)); const publicKey = privateKey.getPublicKeySecp256k1(true); const utxo0 = Proto.Input.create({ @@ -94,7 +94,7 @@ describe("Bitcoin", () => { // Successfully broadcasted: https://www.blockchain.com/explorer/transactions/btc/3e3576eb02667fac284a5ecfcb25768969680cc4c597784602d0a33ba7c654b7 it("test Bitcoin sign BRC20 Transfer", () => { - const { AnySigner, BitcoinSigHashType, PrivateKey, HexCoding, CoinType } = globalThis.core; + const { AnySigner, BitcoinSigHashType, PrivateKey, HexCoding, CoinType, CoinTypeExt } = globalThis.core; const Proto = TW.BitcoinV2.Proto; const privateKeyData = HexCoding.decode("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"); @@ -102,7 +102,7 @@ describe("Bitcoin", () => { const txIdInscription = HexCoding.decode("7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca").reverse(); const txIdForFees = HexCoding.decode("797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1").reverse(); - const privateKey = PrivateKey.createWithData(privateKeyData); + const privateKey = PrivateKey.createWithData(privateKeyData, CoinTypeExt.curve(CoinType.bitcoin)); const publicKey = privateKey.getPublicKeySecp256k1(true); const bobAddress = "bc1qazgc2zhu2kmy42py0vs8d7yff67l3zgpwfzlpk"; @@ -188,14 +188,14 @@ describe("Bitcoin", () => { // Successfully broadcasted: https://www.blockchain.com/explorer/transactions/btc/797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1 it("test Bitcoin sign BRC20 Commit", () => { - const { AnySigner, BitcoinSigHashType, PrivateKey, HexCoding, CoinType } = globalThis.core; + const { AnySigner, BitcoinSigHashType, PrivateKey, HexCoding, CoinType, CoinTypeExt } = globalThis.core; const Proto = TW.BitcoinV2.Proto; const privateKeyData = HexCoding.decode("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"); const dustAmount = new Long(546); const txId = HexCoding.decode("8ec895b4d30adb01e38471ca1019bfc8c3e5fbd1f28d9e7b5653260d89989008").reverse(); - const privateKey = PrivateKey.createWithData(privateKeyData); + const privateKey = PrivateKey.createWithData(privateKeyData, CoinTypeExt.curve(CoinType.bitcoin)); const publicKey = privateKey.getPublicKeySecp256k1(true); const utxo0 = Proto.Input.create({ @@ -270,14 +270,14 @@ describe("Bitcoin", () => { // Successfully broadcasted: https://www.blockchain.com/explorer/transactions/btc/7046dc2689a27e143ea2ad1039710885147e9485ab6453fa7e87464aa7dd3eca it("test Bitcoin sign BRC20 Reveal", () => { - const { AnySigner, BitcoinSigHashType, PrivateKey, HexCoding, CoinType } = globalThis.core; + const { AnySigner, BitcoinSigHashType, PrivateKey, HexCoding, CoinType, CoinTypeExt } = globalThis.core; const Proto = TW.BitcoinV2.Proto; const privateKeyData = HexCoding.decode("e253373989199da27c48680e3a3fc0f648d50f9a727ef17a7fe6a4dc3b159129"); const dustAmount = new Long(546); const txIdCommit = HexCoding.decode("797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1").reverse(); - const privateKey = PrivateKey.createWithData(privateKeyData); + const privateKey = PrivateKey.createWithData(privateKeyData, CoinTypeExt.curve(CoinType.bitcoin)); const publicKey = privateKey.getPublicKeySecp256k1(true); // Now spend just created `797d17d47ae66e598341f9dfdea020b04d4017dcf9cc33f0e51f7a6082171fb1` commit output. diff --git a/wasm/tests/Blockchain/Ethereum.test.ts b/wasm/tests/Blockchain/Ethereum.test.ts index 357e8d306f3..381b3279c0a 100644 --- a/wasm/tests/Blockchain/Ethereum.test.ts +++ b/wasm/tests/Blockchain/Ethereum.test.ts @@ -16,7 +16,7 @@ describe("Ethereum", () => { assert.isTrue(PrivateKey.isValid(data, Curve.secp256k1)); - const key = PrivateKey.createWithData(data); + const key = PrivateKey.createWithData(data, Curve.secp256k1); const pubKey = key.getPublicKeySecp256k1(false); assert.equal( @@ -104,9 +104,9 @@ describe("Ethereum", () => { assert.equal(HexCoding.encode(hash), "0x1da44b586eb0729ff70a73c326926f6ed5a25f5b056e7f47fbc6e58d86871655"); - var key = PrivateKey.createWithData(HexCoding.decode("1fcb84974220eb76e619d7208e1446ae9c0f755e97fb220a8f61c7dc03a0dfce")); + var key = PrivateKey.createWithData(HexCoding.decode("1fcb84974220eb76e619d7208e1446ae9c0f755e97fb220a8f61c7dc03a0dfce"), Curve.secp256k1); - const signature = key.sign(hash, Curve.secp256k1); + const signature = key.sign(hash); assert.equal(HexCoding.encode(signature), "0x58156c371347613642e94b66abc4ced8e36011fb3233f5372371aa5ad321671b1a10c0b88f47ce543fd4c455761f5fbf8f61d050f57dcba986640011da794a9000"); @@ -116,7 +116,7 @@ describe("Ethereum", () => { it("test signing EIP712 message", () => { const { EthereumAbi, HexCoding, Hash, PrivateKey, Curve } = globalThis.core;; - const key = PrivateKey.createWithData(Hash.keccak256(Buffer.from("cow"))); + const key = PrivateKey.createWithData(Hash.keccak256(Buffer.from("cow")), Curve.secp256k1); const message = { types: { EIP712Domain: [ @@ -156,7 +156,7 @@ describe("Ethereum", () => { }; const hash = EthereumAbi.encodeTyped(JSON.stringify(message)); - const signature = key.sign(hash, Curve.secp256k1); + const signature = key.sign(hash); assert.equal(HexCoding.encode(hash), "0xbe609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2"); assert.equal(HexCoding.encode(signature), "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b9156201"); diff --git a/wasm/tests/Blockchain/Hedera.test.ts b/wasm/tests/Blockchain/Hedera.test.ts index e9dbbe0c881..11efc80bef4 100644 --- a/wasm/tests/Blockchain/Hedera.test.ts +++ b/wasm/tests/Blockchain/Hedera.test.ts @@ -20,7 +20,7 @@ describe("Hedera", () => { }); it("test sign simple transfer Hedera", () => { - const { PrivateKey, HexCoding, AnySigner, AnyAddress, CoinType } = globalThis.core; + const { PrivateKey, HexCoding, AnySigner, AnyAddress, CoinType, CoinTypeExt } = globalThis.core; const transferMsg = TW.Hedera.Proto.TransferMessage.create({ from: "0.0.48694347", to: "0.0.48462050", @@ -49,6 +49,7 @@ describe("Hedera", () => { HexCoding.decode( "0xe87a5584c0173263e138db689fdb2a7389025aaae7cb1a18a1017d76012130e8", ), + CoinTypeExt.curve(CoinType.hedera) ).data(), body: transactionBody }); diff --git a/wasm/tests/Blockchain/InternetComputer.test.ts b/wasm/tests/Blockchain/InternetComputer.test.ts index 210a06961e0..abb1a3da1d7 100644 --- a/wasm/tests/Blockchain/InternetComputer.test.ts +++ b/wasm/tests/Blockchain/InternetComputer.test.ts @@ -11,12 +11,12 @@ import Long = require("long"); describe("InternetComputer", () => { it("test address", () => { - const { PrivateKey, HexCoding, AnyAddress, CoinType, Curve } = globalThis.core; + const { PrivateKey, HexCoding, AnyAddress, CoinType, Curve, CoinTypeExt } = globalThis.core; const privateKeyBytes = HexCoding.decode("ee42eaada903e20ef6e5069f0428d552475c1ea7ed940842da6448f6ef9d48e7"); assert.isTrue(PrivateKey.isValid(privateKeyBytes, Curve.secp256k1)); - const privateKey = PrivateKey.createWithData(privateKeyBytes); + const privateKey = PrivateKey.createWithData(privateKeyBytes, CoinTypeExt.curve(CoinType.hedera)); const publicKey = privateKey.getPublicKeySecp256k1(false); assert.equal( diff --git a/wasm/tests/Blockchain/Ripple.test.ts b/wasm/tests/Blockchain/Ripple.test.ts index 323ae28dd1e..20a069344ae 100644 --- a/wasm/tests/Blockchain/Ripple.test.ts +++ b/wasm/tests/Blockchain/Ripple.test.ts @@ -9,10 +9,11 @@ import Long = require("long"); describe("Ripple", () => { it("test sign XRP payment", () => { - const {PrivateKey, HexCoding, AnySigner, CoinType} = globalThis.core; + const {PrivateKey, HexCoding, AnySigner, CoinType, CoinTypeExt} = globalThis.core; const privateKey = PrivateKey.createWithData( - HexCoding.decode("0xa5576c0f63da10e584568c8d134569ff44017b0a249eb70657127ae04f38cc77") + HexCoding.decode("0xa5576c0f63da10e584568c8d134569ff44017b0a249eb70657127ae04f38cc77"), + CoinTypeExt.curve(CoinType.xrp) ); const txDataInput = TW.Ripple.Proto.SigningInput.create({ fee: new Long(10), diff --git a/wasm/tests/Blockchain/TheOpenNetwork.test.ts b/wasm/tests/Blockchain/TheOpenNetwork.test.ts index b96cb551675..bc06e3291fa 100644 --- a/wasm/tests/Blockchain/TheOpenNetwork.test.ts +++ b/wasm/tests/Blockchain/TheOpenNetwork.test.ts @@ -10,9 +10,9 @@ import Long = require("long"); describe("TheOpenNetwork", () => { it("test address from private key TheOpenNetwork", () => { - const { PrivateKey, HexCoding, AnyAddress, CoinType, Curve } = globalThis.core; + const { PrivateKey, HexCoding, AnyAddress, CoinType, Curve, CoinTypeExt } = globalThis.core; let data = HexCoding.decode("63474e5fe9511f1526a50567ce142befc343e71a49b865ac3908f58667319cb8"); - let privateKey = PrivateKey.createWithData(data); + let privateKey = PrivateKey.createWithData(data, CoinTypeExt.curve(CoinType.ton)); assert.isTrue(PrivateKey.isValid(data, Curve.ed25519)); @@ -66,7 +66,7 @@ describe("TheOpenNetwork", () => { }); it("test sign TheOpenNetwork", () => { - const { PrivateKey, HexCoding, CoinType, AnySigner } = globalThis.core; + const { PrivateKey, HexCoding, CoinType, AnySigner, CoinTypeExt } = globalThis.core; let privateKeyData = HexCoding.decode("c38f49de2fb13223a9e7d37d5d0ffbdd89a5eb7c8b0ee4d1c299f2cefe7dc4a0"); @@ -79,7 +79,7 @@ describe("TheOpenNetwork", () => { let input = TW.TheOpenNetwork.Proto.SigningInput.create({ messages: [transfer], - privateKey: PrivateKey.createWithData(privateKeyData).data(), + privateKey: PrivateKey.createWithData(privateKeyData, CoinTypeExt.curve(CoinType.ton)).data(), sequenceNumber: 6, expireAt: 1671132440, walletVersion: TW.TheOpenNetwork.Proto.WalletVersion.WALLET_V4_R2, @@ -96,7 +96,7 @@ describe("TheOpenNetwork", () => { }); it("test jetton transfer TheOpenNetwork", () => { - const { PrivateKey, HexCoding, CoinType, AnySigner } = globalThis.core; + const { PrivateKey, HexCoding, CoinType, AnySigner, CoinTypeExt } = globalThis.core; let privateKeyData = HexCoding.decode("c054900a527538c1b4325688a421c0469b171c29f23a62da216e90b0df2412ee"); @@ -118,7 +118,7 @@ describe("TheOpenNetwork", () => { let input = TW.TheOpenNetwork.Proto.SigningInput.create({ messages: [transfer], - privateKey: PrivateKey.createWithData(privateKeyData).data(), + privateKey: PrivateKey.createWithData(privateKeyData, CoinTypeExt.curve(CoinType.ton)).data(), sequenceNumber: 1, expireAt: 1787693046, walletVersion: TW.TheOpenNetwork.Proto.WalletVersion.WALLET_V4_R2, diff --git a/wasm/tests/CoinType.test.ts b/wasm/tests/CoinType.test.ts index 75a0bed0408..a0b593afbcd 100644 --- a/wasm/tests/CoinType.test.ts +++ b/wasm/tests/CoinType.test.ts @@ -33,7 +33,7 @@ describe("CoinType", () => { const { CoinType, CoinTypeExt, PrivateKey, HexCoding } = globalThis.core; const data = HexCoding.decode("8778cc93c6596387e751d2dc693bbd93e434bd233bc5b68a826c56131821cb63"); - const key = PrivateKey.createWithData(data); + const key = PrivateKey.createWithData(data, CoinTypeExt.curve(CoinType.solana)); const addr = CoinTypeExt.deriveAddress(CoinType.solana, key); assert.equal(addr, "7v91N7iZ9mNicL8WfG6cgSCKyRXydQjLh6UYBWwm6y1Q") }); diff --git a/wasm/tests/PrivateKey.test.ts b/wasm/tests/PrivateKey.test.ts new file mode 100644 index 00000000000..67e15295a03 --- /dev/null +++ b/wasm/tests/PrivateKey.test.ts @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import "mocha"; +import { assert } from "chai"; + +describe("PrivateKey", () => { + + it("test sign without curve", () => { + const { PrivateKey, HexCoding, Curve } = globalThis.core; + + const data = HexCoding.decode("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79"); + + const key = PrivateKey.createWithData(data, Curve.starkex); + const digest = HexCoding.decode("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76"); + const signature = key.sign(digest); + + assert.equal( + HexCoding.encode(signature), + "0x061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a" + ); + + key.delete(); + }); +}); From 36f71ecef3166d422ef9c8aefa84a253f80511b9 Mon Sep 17 00:00:00 2001 From: gupnik Date: Fri, 28 Mar 2025 15:47:47 +0530 Subject: [PATCH 14/23] Merge master into dev (#4337) * feat(eip7702): Add Biz Smart Contract Account Type (#4319) * fix(eip7702): Add `UserOperationMode` * Add `erc4337.biz_account.abi.json` ABI * fix(eip7702): Add `test_barz_transfer_erc7702_eoa` test * fix(eip7702): Fix `Biz.execute4337Ops()` * fix(eip7702): Minor changes * fix(eip7702): Rename `UserOperationMode` to `SCAccountType` * fix: tron message sign (#4326) * Adds ability to specify the curve while constructing Private Key (#4324) * Adds ability to specify the curve while constructing Private Key * Adds signing functions without a curve * Migrates to new API * Use TWCoinTypeCurve * Adds Curve --------- Co-authored-by: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Co-authored-by: Yeferson Licet <111311418+y3fers0n@users.noreply.github.com> --- tests/common/PrivateKeyTests.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/common/PrivateKeyTests.cpp b/tests/common/PrivateKeyTests.cpp index aaf8d1c0923..74cc7a9cf51 100644 --- a/tests/common/PrivateKeyTests.cpp +++ b/tests/common/PrivateKeyTests.cpp @@ -328,4 +328,5 @@ TEST(PrivateKey, SignShortDigest) { } } + } // namespace TW::tests From 2afdd7f2a7768cf9f7e5f16d782f59ee9c215838 Mon Sep 17 00:00:00 2001 From: gupnik Date: Wed, 2 Apr 2025 13:34:25 +0530 Subject: [PATCH 15/23] Merge master into dev again to fix conflicts (#4339) * feat(eip7702): Add Biz Smart Contract Account Type (#4319) * fix(eip7702): Add `UserOperationMode` * Add `erc4337.biz_account.abi.json` ABI * fix(eip7702): Add `test_barz_transfer_erc7702_eoa` test * fix(eip7702): Fix `Biz.execute4337Ops()` * fix(eip7702): Minor changes * fix(eip7702): Rename `UserOperationMode` to `SCAccountType` * fix: tron message sign (#4326) * Adds ability to specify the curve while constructing Private Key (#4324) * Adds ability to specify the curve while constructing Private Key * Adds signing functions without a curve * Migrates to new API * Use TWCoinTypeCurve * Adds Curve * feat(eip7702): Add `SetCode` transaction type (#4336) * fix(eip7702): Add `SetCode` transaction type * fix(eip7702): Add `Biz.executeBatch` function call * Add `AuthorizationSigner` * fix(eip7702): Fix Authorization list RLP encoding * fix(eip7702): Add `Biz.execute` and `Biz.executeBatch` tests * fix(eip7702): Add android test * [CI] Trigger CI * feat(biz): Adjust `Barz.getEncodedHash` according to the latest changes in Biz contract (#4342) * fix(biz): Adjust `Barz.getEncodedHash` according to the latest Biz changes * fix(biz): Adjust Android test * chore(dependencies): Update `gtest` to 1.16.0 (#4343) * [ETH]: Makes factory and paymaster optional while serialising UserOpV07 (#4345) * Uses updated API --------- Co-authored-by: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Co-authored-by: Yeferson Licet <111311418+y3fers0n@users.noreply.github.com> --- .../core/app/blockchains/ethereum/TestBarz.kt | 80 ++++++++- include/TrustWalletCore/TWBarz.h | 24 ++- rust/tw_evm/src/abi/prebuild/biz.rs | 115 ++++++++++++ rust/tw_evm/src/abi/prebuild/erc4337.rs | 83 +-------- rust/tw_evm/src/abi/prebuild/mod.rs | 11 ++ .../src/modules/authorization_signer.rs | 91 ++++++++++ rust/tw_evm/src/modules/mod.rs | 1 + rust/tw_evm/src/modules/tx_builder.rs | 143 +++++++++++++-- rust/tw_evm/src/rlp/impls.rs | 10 ++ .../src/transaction/authorization_list.rs | 95 ++++++++++ rust/tw_evm/src/transaction/mod.rs | 2 + .../src/transaction/transaction_eip7702.rs | 165 ++++++++++++++++++ rust/tw_evm/src/transaction/user_operation.rs | 3 +- .../src/transaction/user_operation_v0_7.rs | 16 +- rust/tw_evm/tests/barz.rs | 141 ++++++++++++++- src/Ethereum/Barz.cpp | 46 +++-- src/Ethereum/Barz.h | 17 +- src/interface/TWBarz.cpp | 27 ++- src/proto/Ethereum.proto | 19 +- tests/CMakeLists.txt | 2 +- tests/chains/Ethereum/BarzTests.cpp | 29 +-- tools/dependencies-version | 2 +- tools/download-dependencies | 4 +- tools/install-dependencies | 2 +- 24 files changed, 958 insertions(+), 170 deletions(-) create mode 100644 rust/tw_evm/src/abi/prebuild/biz.rs create mode 100644 rust/tw_evm/src/modules/authorization_signer.rs create mode 100644 rust/tw_evm/src/transaction/authorization_list.rs create mode 100644 rust/tw_evm/src/transaction/transaction_eip7702.rs diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt index 64e421d5559..8e22f9a4c88 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt @@ -227,6 +227,63 @@ class TestBarz { assertEquals(output.encoded.toStringUtf8(), "{\"callData\":\"0x47e1da2a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b126600000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b12660000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"88673\",\"initCode\":\"0x\",\"maxFeePerGas\":\"10000000000\",\"maxPriorityFeePerGas\":\"10000000000\",\"nonce\":\"3\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"56060\",\"sender\":\"0x1E6c542ebC7c960c6A155A9094DB838cEf842cf5\",\"signature\":\"0x0747b665fe9f3a52407f95a35ac3e76de37c9b89483ae440431244e89a77985f47df712c7364c1a299a5ef62d0b79a2cf4ed63d01772275dd61f72bd1ad5afce1c\",\"verificationGasLimit\":\"522180\"}") } + // https://bscscan.com/tx/0x425eb17a8e1dee2fcee8352a772d83cbb069c2e03f2c5d9d00da3b3ef66ce48b + @Test + fun testSignEip7702EoaBatched() { + val transferFunc1 = EthereumAbiFunction("transfer") + transferFunc1.addParamAddress("0x2EF648D7C03412B832726fd4683E2625deA047Ba".toHexByteArray(), false) + // 100_000_000_000_000 + transferFunc1.addParamUInt256("0x5af3107a4000".toHexByteArray(), false) + val transferPayload1 = EthereumAbi.encode(transferFunc1) + + val transferFunc2 = EthereumAbiFunction("transfer") + transferFunc2.addParamAddress("0x95dc01ebd10b6dccf1cc329af1a3f73806117c2e".toHexByteArray(), false) + // 500_000_000_000_000 + transferFunc2.addParamUInt256("0x1c6bf52634000".toHexByteArray(), false) + val transferPayload2 = EthereumAbi.encode(transferFunc2) + + val signingInput = Ethereum.SigningInput.newBuilder() + signingInput.apply { + privateKey = ByteString.copyFrom(PrivateKey("0xe148e40f06ee3ba316cdb2571f33486cf879c0ffd2b279ce9f9a88c41ce962e7".toHexByteArray(), CoinType.ETHEREUM.curve()).data()) + chainId = ByteString.copyFrom("0x38".toHexByteArray()) + nonce = ByteString.copyFrom("0x12".toHexByteArray()) + txMode = TransactionMode.SetCode + + gasLimit = ByteString.copyFrom("0x186a0".toHexByteArray()) + maxFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) + maxInclusionFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) + + transaction = Ethereum.Transaction.newBuilder().apply { + batch = Ethereum.Transaction.Batch.newBuilder().apply { + addAllCalls(listOf( + Ethereum.Transaction.Batch.BatchedCall.newBuilder().apply { + // TWT + address = "0x4B0F1812e5Df2A09796481Ff14017e6005508003" + amount = ByteString.copyFrom("0x00".toHexByteArray()) + payload = ByteString.copyFrom(transferPayload1) + }.build(), + Ethereum.Transaction.Batch.BatchedCall.newBuilder().apply { + // TWT + address = "0x4B0F1812e5Df2A09796481Ff14017e6005508003" + amount = ByteString.copyFrom("0x00".toHexByteArray()) + payload = ByteString.copyFrom(transferPayload2) + }.build() + )) + }.build() + }.build() + + userOperationMode = Ethereum.SCAccountType.Biz + eip7702Authority = Ethereum.Authority.newBuilder().apply { + address = "0x117BC8454756456A0f83dbd130Bb94D793D3F3F7" + }.build() + } + + val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) + + assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x00b2d13719df301927ddcbdad5b6bc6214f2007c6408df883c9ea483b45e6f44") + assertEquals(Numeric.toHexString(output.encoded.toByteArray()), "0x04f9030f3812843b9aca00843b9aca00830186a0945132829820b44dc3e8586cec926a16fca0a5608480b9024434fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000002ef648d7c03412b832726fd4683e2625dea047ba00000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000001c6bf5263400000000000000000000000000000000000000000000000000000000000c0f85cf85a3894117bc8454756456a0f83dbd130bb94d793d3f3f71380a0073afc661c158a2dccf4183f87e1e4d62b4d406af418cfd69959368ec9bec2a6a064292fd61d4d16b840470a86fc4f7a89413f9126d897f2268eb76a1d887c6d7a01a0e8bcbd96323c9d3e67b74366b2f43299100996d9e8874a6fd87186ac8f580d4ca07c25b4f0619af77fb953e8f0e4372bfbee62616ad419697516108eeb9bcebb28") + } + @Test fun testAuthorizationHash() { val chainId = "0x01".toHexByteArray() @@ -307,24 +364,35 @@ class TestBarz { Numeric.toHexString(output.preHash.toByteArray()) ) - val version = "v0.1.0" + val codeAddress = "0x2e234DAe75C793f67A35089C9d99245E1C58470b" + val codeName = "Biz" + val codeVersion = "v1.0.0" val typeHash = "0x4f51e7a567f083a31264743067875fc6a7ae45c32c5bd71f6a998c4625b13867" - val domainSeparatorHash = "0x293ce8821a350a49f08b53d14e10112c36c7fbf3b8eb7078497893f3ea477f6b" + val domainSeparatorHash = "0xd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472" val hash = "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb" - val encodedHash = WCBarz.getEncodedHash(chainIdByteArray, wallet, version, typeHash, domainSeparatorHash, hash) + val encodedHash = WCBarz.getEncodedHash( + chainIdByteArray, + codeAddress, + codeName, + codeVersion, + typeHash, + domainSeparatorHash, + wallet, + hash + ) assertEquals( - "0x59ebb8c4e48c115eeaf2ea7d3a0802754462761c5019df8d2a38effb226191d5", + "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635", Numeric.toHexString(encodedHash) ) val privateKey = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8" val signedHash = WCBarz.getSignedHash( - "0x59ebb8c4e48c115eeaf2ea7d3a0802754462761c5019df8d2a38effb226191d5", + "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635", privateKey ) assertEquals( - "0x34a7792a140f52358925a57bca8ea936d70133b285396040ac0507597ed5c70a3148964ba1e0b32b8f59fbd9c098a4ec2b9ae5e5739ce4aeccae0f73279d50da1b", + "0xa29e460720e4b539f593d1a407827d9608cccc2c18b7af7b3689094dca8a016755bca072ffe39bc62285b65aff8f271f20798a421acf18bb2a7be8dbe0eb05f81c", Numeric.toHexString(signedHash) ) } diff --git a/include/TrustWalletCore/TWBarz.h b/include/TrustWalletCore/TWBarz.h index 6c3f2585af7..cda18a1a478 100644 --- a/include/TrustWalletCore/TWBarz.h +++ b/include/TrustWalletCore/TWBarz.h @@ -78,15 +78,25 @@ TWString *_Nonnull TWBarzSignAuthorization(TWData* _Nonnull chainId, TWString* _ /// Returns the encoded hash of the user operation /// -/// \param chainId The chainId of the network -/// \param wallet The address of the wallet -/// \param version The version of the wallet -/// \param typeHash The type hash of the transaction -/// \param domainSeparatorHash The domain separator hash of the wallet -/// \param hash The hash of the user operation +/// \param chainId The chainId of the network. +/// \param codeAddress The address of the Biz Smart Contract. +/// \param codeName The name of the Biz Smart Contract. +/// \param codeVersion The version of the Biz Smart Contract. +/// \param typeHash The type hash of the transaction. +/// \param domainSeparatorHash The domain separator hash of the wallet. +/// \param sender The address of the UserOperation sender. +/// \param userOpHash The hash of the user operation. /// \return The encoded hash of the user operation TW_EXPORT_STATIC_METHOD -TWData *_Nonnull TWBarzGetEncodedHash(TWData* _Nonnull chainId, TWString* _Nonnull wallet, TWString* _Nonnull version, TWString* _Nonnull typeHash, TWString* _Nonnull domainSeparatorHash, TWString* _Nonnull hash); +TWData *_Nonnull TWBarzGetEncodedHash( + TWData* _Nonnull chainId, + TWString* _Nonnull codeAddress, + TWString* _Nonnull codeName, + TWString* _Nonnull codeVersion, + TWString* _Nonnull typeHash, + TWString* _Nonnull domainSeparatorHash, + TWString* _Nonnull sender, + TWString* _Nonnull userOpHash); /// Signs a message using the private key /// diff --git a/rust/tw_evm/src/abi/prebuild/biz.rs b/rust/tw_evm/src/abi/prebuild/biz.rs new file mode 100644 index 00000000000..36edfa95e68 --- /dev/null +++ b/rust/tw_evm/src/abi/prebuild/biz.rs @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::abi::contract::Contract; +use crate::abi::function::Function; +use crate::abi::param_token::NamedToken; +use crate::abi::param_type::ParamType; +use crate::abi::prebuild::ExecuteArgs; +use crate::abi::token::Token; +use crate::abi::{AbiError, AbiErrorKind, AbiResult}; +use lazy_static::lazy_static; +use tw_coin_entry::error::prelude::*; +use tw_memory::Data; + +const ERC4337_BIZ_ACCOUNT_ABI: &str = include_str!("resource/erc4337.biz_account.abi.json"); + +lazy_static! { + static ref ERC4337_BIZ_ACCOUNT: Contract = + serde_json::from_str(ERC4337_BIZ_ACCOUNT_ABI).unwrap(); +} + +pub struct BizAccount; + +impl BizAccount { + pub fn encode_execute(args: ExecuteArgs) -> AbiResult { + let func = ERC4337_BIZ_ACCOUNT.function("execute")?; + func.encode_input(&[ + Token::Address(args.to), + Token::u256(args.value), + Token::Bytes(args.data), + ]) + } + + pub fn encode_execute_4337_op(args: ExecuteArgs) -> AbiResult { + let func = ERC4337_BIZ_ACCOUNT.function("execute4337Op")?; + func.encode_input(&[ + Token::Address(args.to), + Token::u256(args.value), + Token::Bytes(args.data), + ]) + } + + pub fn encode_execute_batch(args: I) -> AbiResult + where + I: IntoIterator, + { + let func = ERC4337_BIZ_ACCOUNT.function("executeBatch")?; + encode_batch(func, args) + } + + pub fn encode_execute_4337_ops(args: I) -> AbiResult + where + I: IntoIterator, + { + let func = ERC4337_BIZ_ACCOUNT.function("execute4337Ops")?; + encode_batch(func, args) + } +} + +fn encode_batch(function: &Function, args: I) -> AbiResult +where + I: IntoIterator, +{ + // `tuple[]`, where each item is a tuple of (address, uint256, bytes). + let array_param = function + .inputs + .first() + .or_tw_err(AbiErrorKind::Error_internal) + .context("'Biz.execute4337Ops()' should contain only one argument")?; + + let ParamType::Array { + kind: array_elem_type, + } = array_param.kind.clone() + else { + return AbiError::err(AbiErrorKind::Error_internal).with_context(|| { + format!( + "'Biz.execute4337Ops()' input argument should be an array, found: {:?}", + array_param.kind + ) + }); + }; + + let ParamType::Tuple { + params: tuple_params, + } = array_elem_type.as_ref() + else { + return AbiError::err(AbiErrorKind::Error_internal).with_context(|| { + format!( + "'Biz.execute4337Ops()' input argument should be an array of tuples, found: {array_elem_type:?}", + ) + }); + }; + + if tuple_params.len() != 3 { + return AbiError::err(AbiErrorKind::Error_internal).with_context(|| { + format!( + "'Biz.execute4337Ops()' input argument should be an array of tuples with 3 elements, found: {}", tuple_params.len() + ) + }); + } + + let array_tokens = args + .into_iter() + .map(|call| Token::Tuple { + params: vec![ + NamedToken::with_param_and_token(&tuple_params[0], Token::Address(call.to)), + NamedToken::with_param_and_token(&tuple_params[1], Token::u256(call.value)), + NamedToken::with_param_and_token(&tuple_params[2], Token::Bytes(call.data)), + ], + }) + .collect(); + + function.encode_input(&[Token::array(*array_elem_type, array_tokens)]) +} diff --git a/rust/tw_evm/src/abi/prebuild/erc4337.rs b/rust/tw_evm/src/abi/prebuild/erc4337.rs index 3dd1b74ac10..71329edfc58 100644 --- a/rust/tw_evm/src/abi/prebuild/erc4337.rs +++ b/rust/tw_evm/src/abi/prebuild/erc4337.rs @@ -3,32 +3,20 @@ // Copyright © 2017 Trust Wallet. use crate::abi::contract::Contract; -use crate::abi::param_token::NamedToken; use crate::abi::param_type::ParamType; +use crate::abi::prebuild::ExecuteArgs; use crate::abi::token::Token; -use crate::abi::{AbiError, AbiErrorKind, AbiResult}; -use crate::address::Address; +use crate::abi::AbiResult; use lazy_static::lazy_static; -use tw_coin_entry::error::prelude::{OrTWError, ResultContext}; use tw_memory::Data; -use tw_number::U256; /// Generated via https://remix.ethereum.org /// https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/samples/SimpleAccount.sol const ERC4337_SIMPLE_ACCOUNT_ABI: &str = include_str!("resource/erc4337.simple_account.abi.json"); -const ERC4337_BIZ_ACCOUNT_ABI: &str = include_str!("resource/erc4337.biz_account.abi.json"); lazy_static! { static ref ERC4337_SIMPLE_ACCOUNT: Contract = serde_json::from_str(ERC4337_SIMPLE_ACCOUNT_ABI).unwrap(); - static ref ERC4337_BIZ_ACCOUNT: Contract = - serde_json::from_str(ERC4337_BIZ_ACCOUNT_ABI).unwrap(); -} - -pub struct ExecuteArgs { - pub to: Address, - pub value: U256, - pub data: Data, } pub struct Erc4337SimpleAccount; @@ -43,15 +31,6 @@ impl Erc4337SimpleAccount { ]) } - pub fn encode_execute_4337_op(args: ExecuteArgs) -> AbiResult { - let func = ERC4337_BIZ_ACCOUNT.function("execute4337Op")?; - func.encode_input(&[ - Token::Address(args.to), - Token::u256(args.value), - Token::Bytes(args.data), - ]) - } - pub fn encode_execute_batch(args: I) -> AbiResult where I: IntoIterator, @@ -80,62 +59,4 @@ impl Erc4337SimpleAccount { Token::array(ParamType::Bytes, datas), ]) } - - pub fn encode_execute_4337_ops(args: I) -> AbiResult - where - I: IntoIterator, - { - let func = ERC4337_BIZ_ACCOUNT.function("execute4337Ops")?; - - // `tuple[]`, where each item is a tuple of (address, uint256, bytes). - let array_param = func - .inputs - .first() - .or_tw_err(AbiErrorKind::Error_internal) - .context("'Biz.execute4337Ops()' should contain only one argument")?; - - let ParamType::Array { - kind: array_elem_type, - } = array_param.kind.clone() - else { - return AbiError::err(AbiErrorKind::Error_internal).with_context(|| { - format!( - "'Biz.execute4337Ops()' input argument should be an array, found: {:?}", - array_param.kind - ) - }); - }; - - let ParamType::Tuple { - params: tuple_params, - } = array_elem_type.as_ref() - else { - return AbiError::err(AbiErrorKind::Error_internal).with_context(|| { - format!( - "'Biz.execute4337Ops()' input argument should be an array of tuples, found: {array_elem_type:?}", - ) - }); - }; - - if tuple_params.len() != 3 { - return AbiError::err(AbiErrorKind::Error_internal).with_context(|| { - format!( - "'Biz.execute4337Ops()' input argument should be an array of tuples with 3 elements, found: {}", tuple_params.len() - ) - }); - } - - let array_tokens = args - .into_iter() - .map(|call| Token::Tuple { - params: vec![ - NamedToken::with_param_and_token(&tuple_params[0], Token::Address(call.to)), - NamedToken::with_param_and_token(&tuple_params[1], Token::u256(call.value)), - NamedToken::with_param_and_token(&tuple_params[2], Token::Bytes(call.data)), - ], - }) - .collect(); - - func.encode_input(&[Token::array(*array_elem_type, array_tokens)]) - } } diff --git a/rust/tw_evm/src/abi/prebuild/mod.rs b/rust/tw_evm/src/abi/prebuild/mod.rs index 134b2430973..5eeb9ab85a1 100644 --- a/rust/tw_evm/src/abi/prebuild/mod.rs +++ b/rust/tw_evm/src/abi/prebuild/mod.rs @@ -2,7 +2,18 @@ // // Copyright © 2017 Trust Wallet. +use crate::address::Address; +use tw_memory::Data; +use tw_number::U256; + +pub mod biz; pub mod erc1155; pub mod erc20; pub mod erc4337; pub mod erc721; + +pub struct ExecuteArgs { + pub to: Address, + pub value: U256, + pub data: Data, +} diff --git a/rust/tw_evm/src/modules/authorization_signer.rs b/rust/tw_evm/src/modules/authorization_signer.rs new file mode 100644 index 00000000000..86d77fde04e --- /dev/null +++ b/rust/tw_evm/src/modules/authorization_signer.rs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::rlp::list::RlpList; +use crate::transaction::authorization_list::{Authorization, SignedAuthorization}; +use std::iter; +use tw_coin_entry::error::prelude::*; +use tw_hash::sha3::keccak256; +use tw_hash::H256; +use tw_keypair::ecdsa::secp256k1::PrivateKey; +use tw_keypair::traits::SigningKeyTrait; +use tw_number::U256; + +pub const EIP_7702_MAGIC_PREFIX: u8 = 0x05; + +pub struct AuthorizationSigner; + +impl AuthorizationSigner { + pub fn sign( + private_key: &PrivateKey, + authorization: Authorization, + ) -> SigningResult { + let hash = Self::get_authorization_hash(&authorization); + let signature = private_key.sign(hash)?; + + Ok(SignedAuthorization { + authorization, + y_parity: signature.v(), + r: U256::from_big_endian(signature.r()), + s: U256::from_big_endian(signature.s()), + }) + } + + fn get_authorization_hash(authorization: &Authorization) -> H256 { + let mut list = RlpList::new(); + list.append(&authorization.chain_id) + .append(&authorization.address) + .append(&authorization.nonce); + + let to_hash: Vec<_> = iter::once(EIP_7702_MAGIC_PREFIX) + .chain(list.finish()) + .collect(); + H256::try_from(keccak256(&to_hash).as_slice()) + .expect("keccak256 must return exactly 32 bytes") + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::address::Address; + use std::str::FromStr; + use tw_encoding::hex::ToHex; + + #[test] + fn test_get_authorization_hash() { + let authorization = Authorization { + chain_id: U256::from(1_u32), + address: Address::from_str("0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1").unwrap(), + nonce: U256::from(1_u32), + }; + assert_eq!( + AuthorizationSigner::get_authorization_hash(&authorization).to_hex(), + "3ae543b2fa103a39a6985d964a67caed05f6b9bb2430ad6d498cda743fe911d9" + ); + } + + #[test] + fn test_sign_authorization() { + let authorization = Authorization { + chain_id: U256::from(1_u32), + address: Address::from_str("0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1").unwrap(), + nonce: U256::from(1_u32), + }; + let private_key = PrivateKey::try_from( + "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8", + ) + .unwrap(); + let signed_authorization = AuthorizationSigner::sign(&private_key, authorization).unwrap(); + assert_eq!( + signed_authorization.r.to_big_endian().to_hex(), + "2c39f2f64441dd38c364ee175dc6b9a87f34ca330bce968f6c8e22811e3bb710" + ); + assert_eq!( + signed_authorization.s.to_big_endian().to_hex(), + "5f1bcde93dee4b214e60bc0e63babcc13e4fecb8a23c4098fd89844762aa012c" + ); + assert_eq!(signed_authorization.y_parity, 1); + } +} diff --git a/rust/tw_evm/src/modules/mod.rs b/rust/tw_evm/src/modules/mod.rs index fb246766ac1..6653db1f41f 100644 --- a/rust/tw_evm/src/modules/mod.rs +++ b/rust/tw_evm/src/modules/mod.rs @@ -3,6 +3,7 @@ // Copyright © 2017 Trust Wallet. pub mod abi_encoder; +pub mod authorization_signer; pub mod compiler; pub mod message_signer; pub mod rlp_encoder; diff --git a/rust/tw_evm/src/modules/tx_builder.rs b/rust/tw_evm/src/modules/tx_builder.rs index 516f375024b..d246fc7d2b2 100644 --- a/rust/tw_evm/src/modules/tx_builder.rs +++ b/rust/tw_evm/src/modules/tx_builder.rs @@ -3,14 +3,19 @@ // Copyright © 2017 Trust Wallet. use crate::abi::abi_to_signing_error; +use crate::abi::prebuild::biz::BizAccount; use crate::abi::prebuild::erc1155::Erc1155; use crate::abi::prebuild::erc20::Erc20; -use crate::abi::prebuild::erc4337::{Erc4337SimpleAccount, ExecuteArgs}; +use crate::abi::prebuild::erc4337::Erc4337SimpleAccount; use crate::abi::prebuild::erc721::Erc721; +use crate::abi::prebuild::ExecuteArgs; use crate::address::{Address, EvmAddress}; use crate::evm_context::EvmContext; +use crate::modules::authorization_signer::AuthorizationSigner; use crate::transaction::access_list::{Access, AccessList}; +use crate::transaction::authorization_list::{Authorization, AuthorizationList}; use crate::transaction::transaction_eip1559::TransactionEip1559; +use crate::transaction::transaction_eip7702::TransactionEip7702; use crate::transaction::transaction_non_typed::TransactionNonTyped; use crate::transaction::user_operation::UserOperation; use crate::transaction::user_operation_v0_7::UserOperationV0_7; @@ -19,6 +24,7 @@ use std::marker::PhantomData; use std::str::FromStr; use tw_coin_entry::error::prelude::*; use tw_hash::H256; +use tw_keypair::ecdsa::secp256k1; use tw_memory::Data; use tw_number::U256; use tw_proto::Common::Proto::SigningError as CommonError; @@ -136,26 +142,23 @@ impl TxBuilder { (amount, payload, to_address) }, Tx::batch(ref batch) => { - if input.tx_mode != TxMode::UserOp { - return SigningError::err(SigningErrorType::Error_invalid_params) - .context("Transaction batch can be used in User Operation mode only"); - } - // Payload should match ERC4337 standard. let calls: Vec<_> = batch .calls .iter() .map(Self::erc4337_execute_call_from_proto) .collect::, _>>()?; - let user_op_payload = match input.user_operation_mode { - SCAccountType::SimpleAccount => { - Erc4337SimpleAccount::encode_execute_batch(calls) - }, - SCAccountType::Biz4337 => Erc4337SimpleAccount::encode_execute_4337_ops(calls), - } - .map_err(abi_to_signing_error)?; + let execute_payload = Self::encode_execute_batch(input.user_operation_mode, calls)?; - return Self::user_operation_from_proto(input, user_op_payload); + return match input.tx_mode { + TxMode::UserOp => Self::user_operation_from_proto(input, execute_payload), + TxMode::SetCode => { + Self::transaction_eip7702_from_proto(input, U256::zero(), execute_payload) + }, + _ => SigningError::err(SigningErrorType::Error_invalid_params).context( + "Transaction batch can be used in `UserOp` or `SetCode` modes only", + ), + }; }, Tx::None => { return SigningError::err(SigningErrorType::Error_invalid_params) @@ -180,13 +183,22 @@ impl TxBuilder { data: payload, }; - let user_op_payload = match input.user_operation_mode { - SCAccountType::SimpleAccount => Erc4337SimpleAccount::encode_execute(args), - SCAccountType::Biz4337 => Erc4337SimpleAccount::encode_execute_4337_op(args), - } - .map_err(abi_to_signing_error)?; + let user_op_payload = Self::encode_execute(input.user_operation_mode, args)?; Self::user_operation_from_proto(input, user_op_payload)? }, + TxMode::SetCode => { + let to = to + .or_tw_err(SigningErrorType::Error_invalid_address) + .context("No contract/destination address specified")?; + let args = ExecuteArgs { + to, + value: eth_amount, + data: payload, + }; + + let execute_payload = Self::encode_execute(input.user_operation_mode, args)?; + Self::transaction_eip7702_from_proto(input, eth_amount, execute_payload)? + }, }; Ok(tx) } @@ -294,6 +306,76 @@ impl TxBuilder { }) } + #[inline] + fn transaction_eip7702_from_proto( + input: &Proto::SigningInput, + eth_amount: U256, + payload: Data, + ) -> SigningResult> { + let signer_key = secp256k1::PrivateKey::try_from(input.private_key.as_ref()) + .into_tw() + .context("Sender's private key must be provided to generate an EIP-7702 transaction")?; + let signer = Address::with_secp256k1_pubkey(&signer_key.public()); + + let nonce = U256::from_big_endian_slice(&input.nonce) + .into_tw() + .context("Invalid nonce")?; + + let gas_limit = U256::from_big_endian_slice(&input.gas_limit) + .into_tw() + .context("Invalid gas limit")?; + + let max_inclusion_fee_per_gas = + U256::from_big_endian_slice(&input.max_inclusion_fee_per_gas) + .into_tw() + .context("Invalid max inclusion fee per gas")?; + + let max_fee_per_gas = U256::from_big_endian_slice(&input.max_fee_per_gas) + .into_tw() + .context("Invalid max fee per gas")?; + + let access_list = + Self::parse_access_list(&input.access_list).context("Invalid access list")?; + + let authority: Address = input + .eip7702_authority + .as_ref() + .or_tw_err(SigningErrorType::Error_invalid_params) + .context("'eip7702Authority' must be provided for `SetCode` transaction")? + .address + // Parse `Address` + .parse() + .into_tw() + .context("Invalid authority address")?; + + let chain_id = U256::from_big_endian_slice(&input.chain_id) + .into_tw() + .context("Invalid chain ID")?; + + let authorization = Authorization { + chain_id, + address: authority, + // `authorization.nonce` must be incremented by 1 over `transaction.nonce`. + nonce: nonce + 1, + }; + let signed_authorization = AuthorizationSigner::sign(&signer_key, authorization)?; + let authorization_list = AuthorizationList::from(vec![signed_authorization]); + + Ok(TransactionEip7702 { + nonce, + max_inclusion_fee_per_gas, + max_fee_per_gas, + gas_limit, + // EIP-7702 transaction calls a smart contract function of the authorized address. + to: Some(signer), + amount: eth_amount, + payload, + access_list, + authorization_list, + } + .into_boxed()) + } + fn user_operation_v0_6_from_proto( input: &Proto::SigningInput, user_op: &Proto::UserOperation, @@ -435,6 +517,29 @@ impl TxBuilder { }) } + #[inline] + fn encode_execute(account_type: SCAccountType, args: ExecuteArgs) -> SigningResult { + match account_type { + SCAccountType::SimpleAccount => Erc4337SimpleAccount::encode_execute(args), + SCAccountType::Biz4337 => BizAccount::encode_execute_4337_op(args), + SCAccountType::Biz => BizAccount::encode_execute(args), + } + .map_err(abi_to_signing_error) + } + + #[inline] + fn encode_execute_batch( + account_type: SCAccountType, + calls: Vec, + ) -> SigningResult { + match account_type { + SCAccountType::SimpleAccount => Erc4337SimpleAccount::encode_execute_batch(calls), + SCAccountType::Biz4337 => BizAccount::encode_execute_4337_ops(calls), + SCAccountType::Biz => BizAccount::encode_execute_batch(calls), + } + .map_err(abi_to_signing_error) + } + fn parse_address(addr: &str) -> SigningResult
{ Context::Address::from_str(addr) .map(Context::Address::into) diff --git a/rust/tw_evm/src/rlp/impls.rs b/rust/tw_evm/src/rlp/impls.rs index 371a3a7b47e..c12afd27079 100644 --- a/rust/tw_evm/src/rlp/impls.rs +++ b/rust/tw_evm/src/rlp/impls.rs @@ -8,6 +8,16 @@ use crate::rlp::RlpEncode; use tw_hash::H256; use tw_number::U256; +impl RlpEncode for u8 { + fn rlp_append(&self, buf: &mut RlpBuffer) { + if *self == 0 { + buf.append_data(&[]) + } else { + buf.append_data(&[*self]) + } + } +} + impl RlpEncode for U256 { fn rlp_append(&self, buf: &mut RlpBuffer) { buf.append_data(&self.to_big_endian_compact()) diff --git a/rust/tw_evm/src/transaction/authorization_list.rs b/rust/tw_evm/src/transaction/authorization_list.rs new file mode 100644 index 00000000000..ea652d8a716 --- /dev/null +++ b/rust/tw_evm/src/transaction/authorization_list.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::Address; +use crate::rlp::buffer::RlpBuffer; +use crate::rlp::RlpEncode; +use tw_number::U256; + +/// Authorization for 7702 txn support. +pub struct Authorization { + /// The chain ID of the authorization. + pub chain_id: U256, + /// The address of the authorization. + pub address: Address, + /// The nonce for the authorization. + pub nonce: U256, +} + +/// Signed authorization for 7702 txn support. +pub struct SignedAuthorization { + pub authorization: Authorization, + /// y-parity of the signature. + pub y_parity: u8, + /// r part of the signature. + pub r: U256, + /// s part of the signature. + pub s: U256, +} + +impl RlpEncode for SignedAuthorization { + fn rlp_append(&self, buf: &mut RlpBuffer) { + buf.begin_list(); + + self.authorization.chain_id.rlp_append(buf); + self.authorization.address.rlp_append(buf); + self.authorization.nonce.rlp_append(buf); + self.y_parity.rlp_append(buf); + self.r.rlp_append(buf); + self.s.rlp_append(buf); + + buf.finalize_list(); + } +} + +/// [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930) access list. +#[derive(Default)] +pub struct AuthorizationList(Vec); + +impl From> for AuthorizationList { + fn from(value: Vec) -> Self { + AuthorizationList(value) + } +} + +impl RlpEncode for AuthorizationList { + fn rlp_append(&self, buf: &mut RlpBuffer) { + buf.begin_list(); + + for access in self.0.iter() { + access.rlp_append(buf); + } + + buf.finalize_list(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + use tw_encoding::hex::ToHex; + + #[test] + fn test_encode_signed_authorization() { + let authorization = SignedAuthorization { + authorization: Authorization { + chain_id: U256::from(123_u32), + address: Address::from_str("0x0101010101010101010101010101010101010101").unwrap(), + nonce: U256::from(321_u32), + }, + y_parity: 3, + r: U256::from(222_u32), + s: U256::from(333_u32), + }; + + let mut buf = RlpBuffer::new(); + authorization.rlp_append(&mut buf); + let encoded = buf.finish(); + assert_eq!( + encoded.to_hex(), + "df7b9401010101010101010101010101010101010101018201410381de82014d" + ); + } +} diff --git a/rust/tw_evm/src/transaction/mod.rs b/rust/tw_evm/src/transaction/mod.rs index c3d63b789d8..6dba302df49 100644 --- a/rust/tw_evm/src/transaction/mod.rs +++ b/rust/tw_evm/src/transaction/mod.rs @@ -17,8 +17,10 @@ use tw_memory::Data; use tw_number::U256; pub mod access_list; +pub mod authorization_list; pub mod signature; pub mod transaction_eip1559; +pub mod transaction_eip7702; pub mod transaction_non_typed; pub mod user_operation; pub mod user_operation_v0_7; diff --git a/rust/tw_evm/src/transaction/transaction_eip7702.rs b/rust/tw_evm/src/transaction/transaction_eip7702.rs new file mode 100644 index 00000000000..9833bc056b2 --- /dev/null +++ b/rust/tw_evm/src/transaction/transaction_eip7702.rs @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::address::Address; +use crate::rlp::list::RlpList; +use crate::transaction::access_list::AccessList; +use crate::transaction::authorization_list::AuthorizationList; +use crate::transaction::signature::{EthSignature, Signature}; +use crate::transaction::{SignedTransaction, TransactionCommon, UnsignedTransaction}; +use tw_coin_entry::error::prelude::*; +use tw_keypair::ecdsa::secp256k1; +use tw_memory::Data; +use tw_number::U256; + +const EIP7702_TX_TYPE: u8 = 0x04; + +/// EIP7702 transaction. +pub struct TransactionEip7702 { + pub nonce: U256, + pub max_inclusion_fee_per_gas: U256, + pub max_fee_per_gas: U256, + pub gas_limit: U256, + pub to: Option
, + pub amount: U256, + pub payload: Data, + pub access_list: AccessList, + pub authorization_list: AuthorizationList, +} + +impl TransactionCommon for TransactionEip7702 { + #[inline] + fn payload(&self) -> Data { + self.payload.clone() + } +} + +impl UnsignedTransaction for TransactionEip7702 { + type SignedTransaction = SignedTransactionEip7702; + + #[inline] + fn encode(&self, chain_id: U256) -> Data { + encode_transaction(self, chain_id, None) + } + + #[inline] + fn try_into_signed( + self, + signature: secp256k1::Signature, + chain_id: U256, + ) -> SigningResult { + Ok(SignedTransactionEip7702 { + unsigned: self, + signature: Signature::new(signature), + chain_id, + }) + } +} + +pub struct SignedTransactionEip7702 { + unsigned: TransactionEip7702, + signature: Signature, + chain_id: U256, +} + +impl TransactionCommon for SignedTransactionEip7702 { + #[inline] + fn payload(&self) -> Data { + self.unsigned.payload.clone() + } +} + +impl SignedTransaction for SignedTransactionEip7702 { + type Signature = Signature; + + #[inline] + fn encode(&self) -> Data { + encode_transaction(&self.unsigned, self.chain_id, Some(&self.signature)) + } + + #[inline] + fn signature(&self) -> &Self::Signature { + &self.signature + } +} + +fn encode_transaction( + tx: &TransactionEip7702, + chain_id: U256, + signature: Option<&Signature>, +) -> Data { + let mut list = RlpList::new(); + list.append(&chain_id) + .append(&tx.nonce) + .append(&tx.max_inclusion_fee_per_gas) + .append(&tx.max_fee_per_gas) + .append(&tx.gas_limit) + .append(&tx.to) + .append(&tx.amount) + .append(tx.payload.as_slice()) + .append(&tx.access_list) + .append(&tx.authorization_list); + + if let Some(signature) = signature { + list.append(&signature.v()); + list.append(&signature.r()); + list.append(&signature.s()); + } + + let tx_encoded = list.finish(); + + let mut envelope = Vec::with_capacity(tx_encoded.len() + 1); + envelope.push(EIP7702_TX_TYPE); + envelope.extend_from_slice(tx_encoded.as_slice()); + envelope +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::transaction::authorization_list::{Authorization, SignedAuthorization}; + use std::str::FromStr; + use tw_encoding::hex::{DecodeHex, ToHex}; + use tw_hash::H256; + + #[test] + fn test_encode_transaction_eip7702() { + let authorization = SignedAuthorization { + authorization: Authorization { + chain_id: U256::from(6_u32), + address: Address::from_str("0x0202020202020202020202020202020202020202").unwrap(), + nonce: U256::from(2_u32), + }, + y_parity: 0, + r: U256::from_str("0x42556c4f2a3f4e4e639cca524d1da70e60881417d4643e5382ed110a52719eaf") + .unwrap(), + s: U256::from_str("0x172f591a2a763d0bd6b13d042d8c5eb66e87f129c9dc77ada66b6041012db2b3") + .unwrap(), + }; + + let tx = TransactionEip7702 { + nonce: U256::from(1_u32), + max_inclusion_fee_per_gas: U256::from(2_u32), + max_fee_per_gas: U256::from(3_u32), + gas_limit: U256::from(4_u32), + to: Some(Address::from_str("0x0101010101010101010101010101010101010101").unwrap()), + amount: U256::from(5_u32), + payload: "0x1234".decode_hex().unwrap(), + access_list: AccessList::default(), + authorization_list: AuthorizationList::from(vec![authorization]), + }; + + let r = H256::from("d93fc9ae934d4f72db91cb149e7e84b50ca83b5a8a7b873b0fdb009546e3af47"); + let s = H256::from("786bfaf31af61eea6471dbb1bec7d94f73fb90887e4f04d0e9b85676c47ab02a"); + let v = 0x00; + let signature = secp256k1::Signature::try_from_parts(r, s, v).unwrap(); + let chain_id = U256::from(56_u32); + let signed_tx = tx.try_into_signed(signature, chain_id).unwrap(); + + assert_eq!( + signed_tx.encode().to_hex(), + "04f8c0380102030494010101010101010101010101010101010101010105821234c0f85cf85a069402020202020202020202020202020202020202020280a042556c4f2a3f4e4e639cca524d1da70e60881417d4643e5382ed110a52719eafa0172f591a2a763d0bd6b13d042d8c5eb66e87f129c9dc77ada66b6041012db2b380a0d93fc9ae934d4f72db91cb149e7e84b50ca83b5a8a7b873b0fdb009546e3af47a0786bfaf31af61eea6471dbb1bec7d94f73fb90887e4f04d0e9b85676c47ab02a" + ); + } +} diff --git a/rust/tw_evm/src/transaction/user_operation.rs b/rust/tw_evm/src/transaction/user_operation.rs index 3f55658bb57..bf16935fcd1 100644 --- a/rust/tw_evm/src/transaction/user_operation.rs +++ b/rust/tw_evm/src/transaction/user_operation.rs @@ -162,7 +162,8 @@ struct SignedUserOperationSerde { #[cfg(test)] mod tests { use super::*; - use crate::abi::prebuild::erc4337::{Erc4337SimpleAccount, ExecuteArgs}; + use crate::abi::prebuild::erc4337::Erc4337SimpleAccount; + use crate::abi::prebuild::ExecuteArgs; #[test] fn test_encode_user_operation() { diff --git a/rust/tw_evm/src/transaction/user_operation_v0_7.rs b/rust/tw_evm/src/transaction/user_operation_v0_7.rs index ccf1b08369f..bfe9f1a0183 100644 --- a/rust/tw_evm/src/transaction/user_operation_v0_7.rs +++ b/rust/tw_evm/src/transaction/user_operation_v0_7.rs @@ -188,11 +188,7 @@ impl SignedTransaction for SignedUserOperationV0_7 { let tx = SignedUserOperationV0_7Serde { sender: self.unsigned.sender.to_string(), nonce: self.unsigned.nonce.to_string(), - factory: self - .unsigned - .factory - .map(|addr| addr.to_string()) - .unwrap_or_default(), + factory: self.unsigned.factory.map(|addr| addr.to_string()), factory_data: hex::encode(&self.unsigned.factory_data, prefix), call_data: hex::encode(&self.unsigned.call_data, prefix), call_data_gas_limit: self.unsigned.call_data_gas_limit.to_string(), @@ -200,11 +196,7 @@ impl SignedTransaction for SignedUserOperationV0_7 { pre_verification_gas: self.unsigned.pre_verification_gas.to_string(), max_fee_per_gas: self.unsigned.max_fee_per_gas.to_string(), max_priority_fee_per_gas: self.unsigned.max_priority_fee_per_gas.to_string(), - paymaster: self - .unsigned - .paymaster - .map(|addr| addr.to_string()) - .unwrap_or_default(), + paymaster: self.unsigned.paymaster.map(|addr| addr.to_string()), paymaster_verification_gas_limit: self .unsigned .paymaster_verification_gas_limit @@ -229,7 +221,7 @@ impl SignedTransaction for SignedUserOperationV0_7 { struct SignedUserOperationV0_7Serde { sender: String, nonce: String, - factory: String, + factory: Option, factory_data: String, call_data: String, call_data_gas_limit: String, @@ -237,7 +229,7 @@ struct SignedUserOperationV0_7Serde { pre_verification_gas: String, max_fee_per_gas: String, max_priority_fee_per_gas: String, - paymaster: String, + paymaster: Option, paymaster_verification_gas_limit: String, paymaster_post_op_gas_limit: String, paymaster_data: String, diff --git a/rust/tw_evm/tests/barz.rs b/rust/tw_evm/tests/barz.rs index b795d34607b..4b8d1ee782d 100644 --- a/rust/tw_evm/tests/barz.rs +++ b/rust/tw_evm/tests/barz.rs @@ -5,7 +5,7 @@ use std::borrow::Cow; use tw_coin_entry::error::prelude::*; use tw_encoding::hex; -use tw_encoding::hex::DecodeHex; +use tw_encoding::hex::{DecodeHex, ToHex}; use tw_evm::abi::prebuild::erc20::Erc20; use tw_evm::address::Address; use tw_evm::evm_context::StandardEvmContext; @@ -249,7 +249,7 @@ fn test_barz_transfer_account_not_deployed_v0_7() { } #[test] -fn test_barz_transfer_erc7702_eoa() { +fn test_barz_transfer_erc4337_eoa() { let private_key = hex::decode("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483").unwrap(); @@ -306,7 +306,7 @@ fn test_barz_transfer_erc7702_eoa() { } #[test] -fn test_barz_transfer_erc7702_eoa_batch() { +fn test_barz_transfer_erc4337_eoa_batch() { let private_key = hex::decode("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483").unwrap(); @@ -391,3 +391,138 @@ fn test_barz_transfer_erc7702_eoa_batch() { let user_op: serde_json::Value = serde_json::from_slice(&output.encoded).unwrap(); assert_eq!(user_op["callData"], "0x26da7d880000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000b0086171ac7b6bd4d046580bca6d6a4b0835c2320000000000000000000000000000000000000000000000000002540befbfbd000000000000000000000000000000000000000000000000000000000000000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b1266000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000"); } + +#[test] +fn test_barz_transfer_erc7702_eoa() { + let private_key = + hex::decode("0xe148e40f06ee3ba316cdb2571f33486cf879c0ffd2b279ce9f9a88c41ce962e7").unwrap(); + + let erc20_transfer = Proto::mod_Transaction::ERC20Transfer { + to: "0x95dc01ebd10b6dccf1cc329af1a3f73806117c2e".into(), + amount: U256::encode_be_compact(500_000_000_000_000_u64), + }; + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(56_u64), + nonce: U256::encode_be_compact(16_u64), + tx_mode: Proto::TransactionMode::SetCode, + gas_limit: U256::from(100_000_u128).to_big_endian_compact().into(), + max_fee_per_gas: U256::from(1_000_000_000_u128) + .to_big_endian_compact() + .into(), + max_inclusion_fee_per_gas: U256::from(1_000_000_000_u128) + .to_big_endian_compact() + .into(), + private_key: private_key.into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::erc20_transfer( + erc20_transfer, + ), + }), + to_address: "0x4B0F1812e5Df2A09796481Ff14017e6005508003".into(), + user_operation_mode: Proto::SCAccountType::Biz, + eip7702_authority: Some(Proto::Authority { + address: "0x117BC8454756456A0f83dbd130Bb94D793D3F3F7".into(), + }), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!( + output.error, + SigningErrorType::OK, + "{}", + output.error_message + ); + + assert_eq!( + output.pre_hash.to_hex(), + "8917c03bdd4be922d2163448902eb4f9be4c1fb427641d10f72331e839b00dce" + ); + // Successfully broadcasted transaction: + // https://bscscan.com/tx/0x723c6265ded49520372b4e04d66290fc946f12a48375ee0b1f01165ebe85f0e1 + assert_eq!( + output.encoded.to_hex(), + "04f901ae3810843b9aca00843b9aca00830186a0945132829820b44dc3e8586cec926a16fca0a5608480b8e4b61d27f60000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000001c6bf5263400000000000000000000000000000000000000000000000000000000000c0f85cf85a3894117bc8454756456a0f83dbd130bb94d793d3f3f71180a0f435b376e77a6baff416c53d83992ff53d65846cb1a21686d6743dceee5e7c21a03a9eff368ecc02f1126facd76e8ae5003528ff48ddec3302ad52b06828e992f001a0303774c304ef92095bddf85dba08ea6c7d31d89adf974fe4bcf68c80aee0200aa0669244d097856a4c91433219ab9530650f7012c6118b537d193ca82de05acaac" + ); +} + +#[test] +fn test_barz_transfer_erc7702_eoa_batch() { + let private_key = + hex::decode("0xe148e40f06ee3ba316cdb2571f33486cf879c0ffd2b279ce9f9a88c41ce962e7").unwrap(); + + let mut calls = Vec::with_capacity(2); + + // ERC20 transfer #1. + { + let recipient = Address::from("0x2EF648D7C03412B832726fd4683E2625deA047Ba"); + // 0.0001 TWT + let amount = U256::from(100_000_000_000_000_u64); + let payload = Erc20::transfer(recipient, amount).unwrap(); + + calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + // TWT + address: "0x4B0F1812e5Df2A09796481Ff14017e6005508003".into(), + amount: Cow::default(), + payload: payload.into(), + }); + } + + // ERC20 transfer #2. + { + let recipient = Address::from("0x95dc01ebd10b6dccf1cc329af1a3f73806117c2e"); + // 0.0005 TWT + let amount = U256::from(500_000_000_000_000_u64); + let payload = Erc20::transfer(recipient, amount).unwrap(); + + calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + // TWT + address: "0x4B0F1812e5Df2A09796481Ff14017e6005508003".into(), + amount: Cow::default(), + payload: payload.into(), + }); + } + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(56_u64), + nonce: U256::encode_be_compact(18_u64), + tx_mode: Proto::TransactionMode::SetCode, + gas_limit: U256::from(100_000_u128).to_big_endian_compact().into(), + max_fee_per_gas: U256::from(1_000_000_000_u128) + .to_big_endian_compact() + .into(), + max_inclusion_fee_per_gas: U256::from(1_000_000_000_u128) + .to_big_endian_compact() + .into(), + private_key: private_key.into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::batch( + Proto::mod_Transaction::Batch { calls }, + ), + }), + user_operation_mode: Proto::SCAccountType::Biz, + eip7702_authority: Some(Proto::Authority { + address: "0x117BC8454756456A0f83dbd130Bb94D793D3F3F7".into(), + }), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!( + output.error, + SigningErrorType::OK, + "{}", + output.error_message + ); + + assert_eq!( + output.pre_hash.to_hex(), + "00b2d13719df301927ddcbdad5b6bc6214f2007c6408df883c9ea483b45e6f44" + ); + // Successfully broadcasted transaction: + // https://bscscan.com/tx/0x425eb17a8e1dee2fcee8352a772d83cbb069c2e03f2c5d9d00da3b3ef66ce48b + assert_eq!( + output.encoded.to_hex(), + "04f9030f3812843b9aca00843b9aca00830186a0945132829820b44dc3e8586cec926a16fca0a5608480b9024434fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000002ef648d7c03412b832726fd4683e2625dea047ba00000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000001c6bf5263400000000000000000000000000000000000000000000000000000000000c0f85cf85a3894117bc8454756456a0f83dbd130bb94d793d3f3f71380a0073afc661c158a2dccf4183f87e1e4d62b4d406af418cfd69959368ec9bec2a6a064292fd61d4d16b840470a86fc4f7a89413f9126d897f2268eb76a1d887c6d7a01a0e8bcbd96323c9d3e67b74366b2f43299100996d9e8874a6fd87186ac8f580d4ca07c25b4f0619af77fb953e8f0e4372bfbee62616ad419697516108eeb9bcebb28" + ); +} diff --git a/src/Ethereum/Barz.cpp b/src/Ethereum/Barz.cpp index 54af0c466dc..b9b29eedd7e 100644 --- a/src/Ethereum/Barz.cpp +++ b/src/Ethereum/Barz.cpp @@ -270,7 +270,7 @@ std::vector getRSVY(const Data& hash, const std::string& privateKey) { std::string signAuthorization(const Data& chainId, const std::string& contractAddress, const Data& nonce, const std::string& privateKey) { auto authorizationHash = getAuthorizationHash(chainId, contractAddress, nonce); auto rsvy = getRSVY(authorizationHash, privateKey); - + nlohmann::json jsonObj; jsonObj["chainId"] = hexEncoded(chainId); jsonObj["address"] = contractAddress; @@ -278,33 +278,43 @@ std::string signAuthorization(const Data& chainId, const std::string& contractAd jsonObj["yParity"] = hexEncoded(rsvy[3]); jsonObj["r"] = hexEncoded(rsvy[0]); jsonObj["s"] = hexEncoded(rsvy[1]); - + return jsonObj.dump(); } -Data getEncodedHash(const Data& chainId, const std::string& wallet, const std::string& version, const std::string& typeHash, const std::string& domainSeparatorHash, const std::string& hash) { - // Create domain separator: keccak256(abi.encode(BIZ_DOMAIN_SEPARATOR_HASH, block.chainid, wallet, "v0.1.0")) +Data getEncodedHash( + const Data& chainId, + const std::string& codeAddress, + const std::string& codeName, + const std::string& codeVersion, + const std::string& typeHash, + const std::string& domainSeparatorHash, + const std::string& sender, + const std::string& userOpHash) +{ + Data codeAddressBytes32(12, 0); + append(codeAddressBytes32, parse_hex(codeAddress)); + + // Create domain separator: keccak256(abi.encode(BIZ_DOMAIN_SEPARATOR_HASH, "Biz", "v1.0.0", block.chainid, wallet, _addressToBytes32(singleton))) auto domainSeparator = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { std::make_shared(parse_hex(domainSeparatorHash)), + std::make_shared(Hash::keccak256(codeName)), + std::make_shared(Hash::keccak256(codeVersion)), std::make_shared(chainId), - std::make_shared(wallet), - std::make_shared(version) + std::make_shared(sender), + std::make_shared(codeAddressBytes32), }); if (!domainSeparator.has_value()) { return {}; } Data domainSeparatorEncodedHash = Hash::keccak256(domainSeparator.value()); - - // Create message hash: keccak256(abi.encode(typeHash, keccak256(abi.encode(hash)))) - Data encodedHash; - Ethereum::ABI::ValueEncoder::encodeBytes(parse_hex(hash), encodedHash); - Data innerHash = Hash::keccak256(encodedHash); - - Data messageData; - Ethereum::ABI::ValueEncoder::encodeBytes(parse_hex(typeHash), messageData); - Ethereum::ABI::ValueEncoder::encodeBytes(innerHash, messageData); - Data messageHash = Hash::keccak256(messageData); - + + // Create message hash: keccak256(abi.encode(typeHash, hash)) + Data messageToHash; + append(messageToHash, parse_hex(typeHash)); + append(messageToHash, parse_hex(userOpHash)); + Data messageHash = Hash::keccak256(messageToHash); + // Final hash: keccak256(abi.encodePacked("\x19\x01", domainSeparator, messageHash)) Data encoded; append(encoded, parse_hex("0x1901")); @@ -315,7 +325,7 @@ Data getEncodedHash(const Data& chainId, const std::string& wallet, const std::s Data getSignedHash(const std::string& hash, const std::string& privateKey) { auto rsvy = getRSVY(parse_hex(hash), privateKey); - + Data result; append(result, rsvy[0]); append(result, rsvy[1]); diff --git a/src/Ethereum/Barz.h b/src/Ethereum/Barz.h index d6c2907d6dd..445cbd529aa 100644 --- a/src/Ethereum/Barz.h +++ b/src/Ethereum/Barz.h @@ -17,7 +17,20 @@ Data getFormattedSignature(const Data& signature, const Data challenge, const Da Data getPrefixedMsgHash(const Data msgHash, const std::string& barzAddress, const uint32_t chainId); Data getDiamondCutCode(const Proto::DiamondCutInput& input); // action should be one of 0, 1, 2. 0 = Add, 1 = Remove, 2 = Replace Data getAuthorizationHash(const Data& chainId, const std::string& contractAddress, const Data& nonce); -std::string signAuthorization(const Data& chainId, const std::string& contractAddress, const Data& nonce, const std::string& privateKey); -Data getEncodedHash(const Data& chainId, const std::string& wallet, const std::string& version, const std::string& typeHash, const std::string& domainSeparatorHash, const std::string& hash); +std::string signAuthorization( + const Data& chainId, + const std::string& contractAddress, + const Data& nonce, + const std::string& privateKey); +Data getEncodedHash( + const Data& chainId, + const std::string& codeAddress, + const std::string& codeName, + const std::string& codeVersion, + const std::string& typeHash, + const std::string& domainSeparatorHash, + const std::string& sender, + const std::string& userOpHash); Data getSignedHash(const std::string& hash, const std::string& privateKey); + } diff --git a/src/interface/TWBarz.cpp b/src/interface/TWBarz.cpp index 0c6719e84eb..1220d65beab 100644 --- a/src/interface/TWBarz.cpp +++ b/src/interface/TWBarz.cpp @@ -76,14 +76,31 @@ TWString *_Nonnull TWBarzSignAuthorization(TWData* _Nonnull chainId, TWString* _ return TWStringCreateWithUTF8Bytes(signedAuthorization.c_str()); } -TWData *_Nonnull TWBarzGetEncodedHash(TWData* _Nonnull chainId, TWString* _Nonnull wallet, TWString* _Nonnull version, TWString* _Nonnull typeHash, TWString* _Nonnull domainSeparatorHash, TWString* _Nonnull hash) { +TWData *_Nonnull TWBarzGetEncodedHash( + TWData* _Nonnull chainId, + TWString* _Nonnull codeAddress, + TWString* _Nonnull codeName, + TWString* _Nonnull codeVersion, + TWString* _Nonnull typeHash, + TWString* _Nonnull domainSeparatorHash, + TWString* _Nonnull sender, + TWString* _Nonnull userOpHash) { const auto& chainIdData = *reinterpret_cast(chainId); - const auto& walletStr = *reinterpret_cast(wallet); - const auto& versionStr = *reinterpret_cast(version); + const auto& codeAddressStr = *reinterpret_cast(codeAddress); + const auto& codeNameStr = *reinterpret_cast(codeName); + const auto& codeVersionStr = *reinterpret_cast(codeVersion); const auto& typeHashStr = *reinterpret_cast(typeHash); const auto& domainSeparatorHashStr = *reinterpret_cast(domainSeparatorHash); - const auto& hashStr = *reinterpret_cast(hash); - const auto encodedHash = TW::Barz::getEncodedHash(chainIdData, walletStr, versionStr, typeHashStr, domainSeparatorHashStr, hashStr); + const auto& senderStr = *reinterpret_cast(sender); + const auto& userOpHashStr = *reinterpret_cast(userOpHash); + const auto encodedHash = TW::Barz::getEncodedHash( + chainIdData, + codeAddressStr, + codeNameStr, + codeVersionStr, typeHashStr, + domainSeparatorHashStr, + senderStr, + userOpHashStr); return TWDataCreateWithData(&encodedHash); } diff --git a/src/proto/Ethereum.proto b/src/proto/Ethereum.proto index 60d8a222238..f2e51c469a9 100644 --- a/src/proto/Ethereum.proto +++ b/src/proto/Ethereum.proto @@ -110,6 +110,11 @@ enum TransactionMode { // EIP4337-compatible UserOperation UserOp = 2; + + // EIP-7702 transaction (with type 0x4); allows to set the code of a contract for an EOA. + // Note that `SetCode` transaction extends `Enveloped` transaction. + // https://eips.ethereum.org/EIPS/eip-7702 + SetCode = 4; } // ERC-4337 structure that describes a transaction to be sent on behalf of a user @@ -174,13 +179,21 @@ message Access { repeated bytes stored_keys = 2; } +// [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) authority. +message Authority { + // Address to be authorized, a smart contract address. + string address = 2; +} + // Smart Contract account type. enum SCAccountType { // ERC-4337 compatible smart contract wallet. // https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/accounts/SimpleAccount.sol SimpleAccount = 0; - // Biz smart contract (Trust Wallet specific). + // Biz smart contract (Trust Wallet specific) through ERC-4337 EntryPoint. Biz4337 = 1; + // Biz smart contract (Trust Wallet specific) directly through ERC-7702. + Biz = 2; } // Input data necessary to create a signed transaction. @@ -234,6 +247,10 @@ message SigningInput { // Smart contract account type. Used in `TransactionMode::UserOp` only. SCAccountType user_operation_mode = 14; + + // A smart contract to which we’re delegating to. + // Currently, we support delegation to only one authority at a time. + Authority eip7702_authority = 15; } // Result containing the signed and encoded transaction. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 288f2842ba8..87ec8f86384 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,7 +10,7 @@ set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # Add googletest directly to our build. This defines # the gtest and gtest_main targets. -add_subdirectory(${CMAKE_SOURCE_DIR}/build/local/src/gtest/googletest-release-1.11.0 +add_subdirectory(${CMAKE_SOURCE_DIR}/build/local/src/gtest/googletest-1.16.0 ${CMAKE_CURRENT_BINARY_DIR}/googletest-build EXCLUDE_FROM_ALL) diff --git a/tests/chains/Ethereum/BarzTests.cpp b/tests/chains/Ethereum/BarzTests.cpp index 4eb2adae5fb..bb3fb750c89 100644 --- a/tests/chains/Ethereum/BarzTests.cpp +++ b/tests/chains/Ethereum/BarzTests.cpp @@ -446,24 +446,33 @@ TEST(Barz, SignAuthorization) { TEST(Barz, GetEncodedHash) { { const auto chainId = store(uint256_t(31337), 32); - std::cout << "chainId: " << hexEncoded(chainId) << std::endl; - const auto wallet = "0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029"; - const auto version = "v0.1.0"; + const auto codeAddress = "0x2e234DAe75C793f67A35089C9d99245E1C58470b"; + const auto codeName = "Biz"; + const auto codeVersion = "v1.0.0"; const auto typeHash = "0x4f51e7a567f083a31264743067875fc6a7ae45c32c5bd71f6a998c4625b13867"; - const auto domainSeparatorHash = "0x293ce8821a350a49f08b53d14e10112c36c7fbf3b8eb7078497893f3ea477f6b"; - const auto hash = "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb"; - - const auto& encodedHash = Barz::getEncodedHash(chainId, wallet, version, typeHash, domainSeparatorHash, hash); - ASSERT_EQ(hexEncoded(encodedHash), "0x59ebb8c4e48c115eeaf2ea7d3a0802754462761c5019df8d2a38effb226191d5"); + const auto domainSeparatorHash = "0xd87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472"; + const auto sender = "0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029"; + const auto userOpHash = "0xf177858c1c500e51f38ffe937bed7e4d3a8678725900be4682d3ce04d97071eb"; + + const auto& encodedHash = Barz::getEncodedHash( + chainId, + codeAddress, + codeName, + codeVersion, + typeHash, + domainSeparatorHash, + sender, + userOpHash); + ASSERT_EQ(hexEncoded(encodedHash), "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635"); } } TEST(Barz, GetSignedHash) { { - const auto hash = "0x59ebb8c4e48c115eeaf2ea7d3a0802754462761c5019df8d2a38effb226191d5"; + const auto hash = "0xc63891abc38f7a991f89ad7cb6d7e53543627b0536c3f5e545b736756c971635"; const auto privateKey = "0x947dd69af402e7f48da1b845dfc1df6be593d01a0d8274bd03ec56712e7164e8"; const auto signedHash = Barz::getSignedHash(hash, privateKey); - ASSERT_EQ(hexEncoded(signedHash), "0x34a7792a140f52358925a57bca8ea936d70133b285396040ac0507597ed5c70a3148964ba1e0b32b8f59fbd9c098a4ec2b9ae5e5739ce4aeccae0f73279d50da1b"); + ASSERT_EQ(hexEncoded(signedHash), "0xa29e460720e4b539f593d1a407827d9608cccc2c18b7af7b3689094dca8a016755bca072ffe39bc62285b65aff8f271f20798a421acf18bb2a7be8dbe0eb05f81c"); } } diff --git a/tools/dependencies-version b/tools/dependencies-version index 90a9a778a6e..66d095d4f1a 100755 --- a/tools/dependencies-version +++ b/tools/dependencies-version @@ -1,6 +1,6 @@ #!/bin/bash -export GTEST_VERSION=1.11.0 +export GTEST_VERSION=1.16.0 export CHECK_VERSION=0.15.2 export JSON_VERSION=3.11.3 export PROTOBUF_VERSION=3.19.2 diff --git a/tools/download-dependencies b/tools/download-dependencies index bd138ca5904..2d674ab60c1 100755 --- a/tools/download-dependencies +++ b/tools/download-dependencies @@ -15,9 +15,9 @@ function download_gtest() { mkdir -p "$GTEST_DIR" cd "$GTEST_DIR" if [ ! -f release-$GTEST_VERSION.tar.gz ]; then - curl -fSsOL https://github.com/google/googletest/archive/release-$GTEST_VERSION.tar.gz + curl -fSsOL https://github.com/google/googletest/releases/download/v$GTEST_VERSION/googletest-$GTEST_VERSION.tar.gz fi - tar xzf release-$GTEST_VERSION.tar.gz + tar xzf googletest-$GTEST_VERSION.tar.gz } function download_libcheck() { diff --git a/tools/install-dependencies b/tools/install-dependencies index 3ad3d77a54d..acdcf8e56eb 100755 --- a/tools/install-dependencies +++ b/tools/install-dependencies @@ -31,7 +31,7 @@ function download_dependencies() { function build_gtest() { # Build gtest GTEST_DIR="$ROOT/build/local/src/gtest" - cd ${GTEST_DIR}/googletest-release-$GTEST_VERSION + cd ${GTEST_DIR}/googletest-$GTEST_VERSION $CMAKE -DCMAKE_INSTALL_PREFIX:PATH=$PREFIX -H. $MAKE -j4 $MAKE install From 6b46c47079371125da24e0c96035851cfea711fc Mon Sep 17 00:00:00 2001 From: gupnik Date: Fri, 4 Apr 2025 17:34:55 +0530 Subject: [PATCH 16/23] Migrates PrivateKey and PublicKey implementation to Rust (#4347) * Migrates PrivateKey and PublicKey implementation to Rust * Migrates PublicKey zilliqa schnorr to rust * Migrates Zillqa Private Key to rust * Removes todo * Makes clippy happy * Minor fix * Adds Rust Tests * Fixes address santization issue * Fixes doctests * Adds Zilliqa Schnorr as a separate curve and remove separate signing/verifying APIs for zillqa * FMT * Addresses review comments * Fixes C++ Tests * Derive ZeroizeOnDrop trait on Zillqa Private Key * Cleanup public key data * Adds Tests * FMT * Use CByteArrayWrapper * Addresses review comments * Addresses review comment * Removes doc test * Only allow valid Cardano keys and make TWHDWalletGetMasterKey return an optional --- .../app/blockchains/neo/TestsNEOSigner.kt | 7 +- .../ontology/TestOntologySigning.kt | 14 +- codegen-v2/src/codegen/cpp/code_gen.rs | 4 +- include/TrustWalletCore/TWCurve.h | 2 + include/TrustWalletCore/TWHDWallet.h | 4 +- include/TrustWalletCore/TWPrivateKey.h | 15 +- include/TrustWalletCore/TWPublicKey.h | 9 - include/TrustWalletCore/TWPublicKeyType.h | 2 + registry.json | 4 +- .../src/test_utils/address_utils.rs | 5 +- rust/tw_bech32_address/src/lib.rs | 5 +- rust/tw_coin_entry/src/coin_context.rs | 5 +- rust/tw_coin_entry/src/coin_entry_ext.rs | 2 +- rust/tw_coin_entry/src/error/impl_from.rs | 5 +- .../src/test_utils/test_context.rs | 9 +- rust/tw_coin_registry/src/coin_context.rs | 7 +- rust/tw_coin_registry/src/registry.rs | 3 +- .../src/private_key/secp256k1.rs | 6 +- rust/tw_keypair/src/ecdsa/canonical.rs | 32 +++ rust/tw_keypair/src/ecdsa/mod.rs | 2 +- rust/tw_keypair/src/ecdsa/signature.rs | 20 ++ rust/tw_keypair/src/ffi/privkey.rs | 70 ++++- rust/tw_keypair/src/ffi/pubkey.rs | 122 ++++++-- rust/tw_keypair/src/lib.rs | 4 + .../src/test_utils/tw_private_key_helper.rs | 11 +- rust/tw_keypair/src/tw/mod.rs | 37 ++- rust/tw_keypair/src/tw/private.rs | 93 +++++- rust/tw_keypair/src/tw/public.rs | 85 +++++- rust/tw_keypair/src/zilliqa_schnorr/mod.rs | 95 +++++++ .../tw_keypair/src/zilliqa_schnorr/private.rs | 200 +++++++++++++ rust/tw_keypair/src/zilliqa_schnorr/public.rs | 119 ++++++++ .../src/zilliqa_schnorr/signature.rs | 37 +++ .../tw_keypair/tests/private_key_ffi_tests.rs | 131 +++++---- rust/tw_keypair/tests/public_key_ffi_tests.rs | 219 +++++++++++++- .../tests/tw_keypair_starkex_tests.rs | 6 +- .../tests/chains/ton/ton_message_signer.rs | 3 +- .../tests/coin_address_derivation_test.rs | 9 +- src/EOS/Signer.cpp | 2 +- src/EOS/Signer.h | 2 +- src/FIO/Signer.cpp | 2 +- src/FIO/Signer.h | 2 +- src/HDWallet.cpp | 3 +- src/PrivateKey.cpp | 267 ++---------------- src/PrivateKey.h | 7 +- src/PublicKey.cpp | 207 +++----------- src/PublicKey.h | 16 +- src/Zilliqa/Signer.cpp | 6 +- src/interface/TWHDWallet.cpp | 5 +- src/interface/TWPrivateKey.cpp | 19 +- src/interface/TWPublicKey.cpp | 6 - swift/Tests/Blockchains/NEOTests.swift | 6 +- swift/Tests/Blockchains/OntologyTests.swift | 10 +- swift/Tests/HDWalletTests.swift | 2 +- swift/Tests/PrivateKeyTests.swift | 8 +- tests/chains/Cardano/AddressTests.cpp | 22 +- tests/chains/EOS/TransactionTests.cpp | 6 +- tests/chains/NEO/AddressTests.cpp | 2 +- tests/chains/NEO/SignerTests.cpp | 4 +- tests/chains/NEO/TWAnySignerTests.cpp | 10 +- tests/chains/NEO/TransactionCompilerTests.cpp | 21 +- tests/chains/Ontology/Oep4Tests.cpp | 4 +- tests/chains/Ontology/OngTests.cpp | 35 +-- tests/chains/Ontology/OntTests.cpp | 18 +- tests/chains/Ontology/TWAnySignerTests.cpp | 39 +-- tests/chains/Solana/AddressTests.cpp | 8 +- tests/chains/Zilliqa/AddressTests.cpp | 4 +- tests/chains/Zilliqa/SignatureTests.cpp | 6 +- tests/chains/Zilliqa/SignerTests.cpp | 14 +- tests/common/CoinAddressDerivationTests.cpp | 6 +- tests/common/PrivateKeyTests.cpp | 12 +- tests/common/PublicKeyTests.cpp | 28 +- tests/interface/TWCoinTypeTests.cpp | 2 +- tests/interface/TWHDWalletTests.cpp | 6 +- tests/interface/TWPublicKeyTests.cpp | 11 +- 74 files changed, 1424 insertions(+), 807 deletions(-) create mode 100644 rust/tw_keypair/src/zilliqa_schnorr/mod.rs create mode 100644 rust/tw_keypair/src/zilliqa_schnorr/private.rs create mode 100644 rust/tw_keypair/src/zilliqa_schnorr/public.rs create mode 100644 rust/tw_keypair/src/zilliqa_schnorr/signature.rs diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/neo/TestsNEOSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/neo/TestsNEOSigner.kt index eb40bbf1892..272b5ad0a8e 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/neo/TestsNEOSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/neo/TestsNEOSigner.kt @@ -97,12 +97,7 @@ class TestNEOSigner { // https://testnet-explorer.o3.network/transactions/0x7b138c753c24f474d0f70af30a9d79756e0ee9c1f38c12ed07fbdf6fc5132eaf assertEquals( - "8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf122956bc88746dc666759a2d67f120fe3ce1659f916d22a91e0b02421d3bddbd1232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac", + "8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf1dd6a943678b9239a98a65d2980edf01beed0a0b4904573f31309a6a128a54980232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac", hex) - - // TODO uncomment when nist256p1 Rust implementation is enabled. - // assertEquals( - // "8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf1dd6a943678b9239a98a65d2980edf01beed0a0b4904573f31309a6a128a54980232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac", - // hex) } } diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ontology/TestOntologySigning.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ontology/TestOntologySigning.kt index 2bf777966fb..cea59e9659b 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ontology/TestOntologySigning.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ontology/TestOntologySigning.kt @@ -74,13 +74,8 @@ class TestOntologySigning { val hex = Numeric.toHexString(result, 0, result.size, false) assertEquals( - "00d102d45c8bf401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140301766d925382a6ebb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb7cff28ddaf7f1048822c0ca21a0c4926323a2497875b963f3b8cbd3717aa6e7c2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac414038466b25ac49a22ba8c301328ef049a61711b257987e85e25d63e0444a14e860305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + "00d102d45c8bf401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140301766d925382a6ebb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb8300d7215080efb87dd3f35de5f3b6d98aacd6161fbc0845b82d0d8be4b8b6d52321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac414038466b25ac49a22ba8c301328ef049a61711b257987e85e25d63e0444a14e860305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", hex) - - // TODO uncomment when nist256p1 Rust implementation is enabled. - // assertEquals( - // "00d102d45c8bf401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140301766d925382a6ebb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb8300d7215080efb87dd3f35de5f3b6d98aacd6161fbc0845b82d0d8be4b8b6d52321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac414038466b25ac49a22ba8c301328ef049a61711b257987e85e25d63e0444a14e860305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", - // hex) } @Test @@ -103,13 +98,8 @@ class TestOntologySigning { val hex = Numeric.toHexString(result, 0, result.size, false) assertEquals( - "00d19d3182a8f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140e27e935b87855efad62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f73b8ab199a4d757b4c7b9ed46c4ff8cfa8aefaa90b7fb6485e358034448cba752321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac4140450047b2efb384129a16ec4c707790e9379b978cc7085170071d8d7c5c037d743b078bd4e21bb4404c0182a32ee05260e22454dffb34dacccf458dfbee6d32db232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", + "00d19d3182a8f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140e27e935b87855efad62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f8c4754e565b28a85b384612b93b00730143800049b97e83c95844a8eb7d66adc2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac4140450047b2efb384129a16ec4c707790e9379b978cc7085170071d8d7c5c037d74c4f8742a1de44bc0b3fe7d5cd11fad9edac2a5cdabe2c3b824743cc70df5f276232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", hex) - - // TODO uncomment when nist256p1 Rust implementation is enabled. - // assertEquals( - // "00d19d3182a8f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140e27e935b87855efad62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f8c4754e565b28a85b384612b93b00730143800049b97e83c95844a8eb7d66adc2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac4140450047b2efb384129a16ec4c707790e9379b978cc7085170071d8d7c5c037d74c4f8742a1de44bc0b3fe7d5cd11fad9edac2a5cdabe2c3b824743cc70df5f276232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", - // hex) } } diff --git a/codegen-v2/src/codegen/cpp/code_gen.rs b/codegen-v2/src/codegen/cpp/code_gen.rs index 1186ffdf855..486583c0f61 100644 --- a/codegen-v2/src/codegen/cpp/code_gen.rs +++ b/codegen-v2/src/codegen/cpp/code_gen.rs @@ -400,7 +400,7 @@ fn generate_conversion_code_with_var_name(tw_type: TWType, name: &str) -> Result writeln!( &mut conversion_code, "\tauto &{name}PrivateKey = *reinterpret_cast({name});\n\ - \tauto* {name}RustRaw = Rust::tw_private_key_create_with_data({name}PrivateKey.bytes.data(), {name}PrivateKey.bytes.size());\n\ + \tauto* {name}RustRaw = Rust::tw_private_key_create_with_data({name}PrivateKey.bytes.data(), {name}PrivateKey.bytes.size(), static_cast({name}PrivateKey.curve()));\n\ \tconst auto {name}RustPrivateKey = Rust::wrapTWPrivateKey({name}RustRaw);" ) .map_err(|e| BadFormat(e.to_string()))?; @@ -413,7 +413,7 @@ fn generate_conversion_code_with_var_name(tw_type: TWType, name: &str) -> Result "\tstd::shared_ptr {name}RustPrivateKey;\n\ \tif ({name} != nullptr) {{\n\ \t\tconst auto& {name}PrivateKey = {name};\n\ - \t\tauto* {name}RustRaw = Rust::tw_private_key_create_with_data({name}PrivateKey->impl.bytes.data(), {name}PrivateKey->impl.bytes.size());\n\ + \t\tauto* {name}RustRaw = Rust::tw_private_key_create_with_data({name}PrivateKey->impl.bytes.data(), {name}PrivateKey->impl.bytes.size(), static_cast({name}PrivateKey->impl.curve()));\n\ \t\t{name}RustPrivateKey = Rust::wrapTWPrivateKey({name}RustRaw);\n\ \t}}" ) diff --git a/include/TrustWalletCore/TWCurve.h b/include/TrustWalletCore/TWCurve.h index 3b7f2b003bd..80940124d2b 100644 --- a/include/TrustWalletCore/TWCurve.h +++ b/include/TrustWalletCore/TWCurve.h @@ -18,6 +18,8 @@ enum TWCurve { TWCurveNIST256p1 /* "nist256p1" */, TWCurveED25519ExtendedCardano /* "ed25519-cardano-seed" */, TWCurveStarkex /* "starkex" */, + TWCurveSchnorr /* "schnorr" */, + TWCurveZILLIQASchnorr /* "zilliqa-schnorr" */, TWCurveNone }; diff --git a/include/TrustWalletCore/TWHDWallet.h b/include/TrustWalletCore/TWHDWallet.h index 9e902a55587..dae14812dcc 100644 --- a/include/TrustWalletCore/TWHDWallet.h +++ b/include/TrustWalletCore/TWHDWallet.h @@ -96,9 +96,9 @@ TWData* _Nonnull TWHDWalletEntropy(struct TWHDWallet* _Nonnull wallet); /// \param wallet non-null TWHDWallet /// \param curve a curve /// \note Returned object needs to be deleted with \TWPrivateKeyDelete -/// \return Non-null corresponding private key +/// \return Corresponding private key. Returns null for `TWCurveED25519ExtendedCardano`. TW_EXPORT_METHOD -struct TWPrivateKey* _Nonnull TWHDWalletGetMasterKey(struct TWHDWallet* _Nonnull wallet, enum TWCurve curve); +struct TWPrivateKey* _Nullable TWHDWalletGetMasterKey(struct TWHDWallet* _Nonnull wallet, enum TWCurve curve); /// Generates the default private key for the specified coin, using default derivation. /// diff --git a/include/TrustWalletCore/TWPrivateKey.h b/include/TrustWalletCore/TWPrivateKey.h index e0beebb04dd..462d44ca67a 100644 --- a/include/TrustWalletCore/TWPrivateKey.h +++ b/include/TrustWalletCore/TWPrivateKey.h @@ -123,6 +123,13 @@ struct TWPublicKey* _Nonnull TWPrivateKeyGetPublicKeyEd25519Cardano(struct TWPri TW_EXPORT_METHOD struct TWPublicKey* _Nonnull TWPrivateKeyGetPublicKeyCurve25519(struct TWPrivateKey* _Nonnull pk); +/// Returns the Zilliqa Schnorr public key associated with the given private key +/// +/// \param pk Non-null pointer to the private key +/// \return Non-null pointer to the corresponding public key +TW_EXPORT_METHOD +struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyZilliqaSchnorr(struct TWPrivateKey *_Nonnull pk); + /// Signs a digest using ECDSA /// /// \param pk Non-null pointer to a Private key @@ -139,12 +146,4 @@ TWData* _Nullable TWPrivateKeySign(struct TWPrivateKey* _Nonnull pk, TWData* _No TW_EXPORT_METHOD TWData* _Nullable TWPrivateKeySignAsDER(struct TWPrivateKey* _Nonnull pk, TWData* _Nonnull digest); -/// Signs a digest using ECDSA and Zilliqa schnorr signature scheme. -/// -/// \param pk Non-null pointer to a Private key -/// \param message Non-null message -/// \return Signature as a Non-null block of data -TW_EXPORT_METHOD -TWData* _Nullable TWPrivateKeySignZilliqaSchnorr(struct TWPrivateKey* _Nonnull pk, TWData* _Nonnull message); - TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWPublicKey.h b/include/TrustWalletCore/TWPublicKey.h index 7187ac564af..a003d24f781 100644 --- a/include/TrustWalletCore/TWPublicKey.h +++ b/include/TrustWalletCore/TWPublicKey.h @@ -87,15 +87,6 @@ bool TWPublicKeyVerify(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signatu TW_EXPORT_METHOD bool TWPublicKeyVerifyAsDER(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *_Nonnull message); -/// Verify a Zilliqa schnorr signature with a signature and message. -/// -/// \param pk Non-null pointer to a public key -/// \param signature Non-null pointer to a block of data corresponding to the signature -/// \param message Non-null pointer to a block of data corresponding to the message -/// \return true if the signature and the message belongs to the given public key, false otherwise -TW_EXPORT_METHOD -bool TWPublicKeyVerifyZilliqaSchnorr(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *_Nonnull message); - /// Give the public key type (eliptic) of a given public key /// /// \param publicKey Non-null pointer to a public key diff --git a/include/TrustWalletCore/TWPublicKeyType.h b/include/TrustWalletCore/TWPublicKeyType.h index f175fc8c471..99f10b68fc3 100644 --- a/include/TrustWalletCore/TWPublicKeyType.h +++ b/include/TrustWalletCore/TWPublicKeyType.h @@ -20,6 +20,8 @@ enum TWPublicKeyType { TWPublicKeyTypeCURVE25519 = 6, TWPublicKeyTypeED25519Cardano = 7, TWPublicKeyTypeStarkex = 8, + TWPublicKeyTypeSchnorr = 9, + TWPublicKeyTypeZILLIQASchnorr = 10, }; TW_EXTERN_C_END diff --git a/registry.json b/registry.json index e53e559ab61..dd65ffc741e 100644 --- a/registry.json +++ b/registry.json @@ -1892,8 +1892,8 @@ "path": "m/44'/313'/0'/0/0" } ], - "curve": "secp256k1", - "publicKeyType": "secp256k1", + "curve": "zilliqaSchnorr", + "publicKeyType": "zilliqaSchnorr", "hrp": "zil", "explorer": { "url": "https://viewblock.io", diff --git a/rust/tw_any_coin/src/test_utils/address_utils.rs b/rust/tw_any_coin/src/test_utils/address_utils.rs index 7d7b9cff373..36cf926ee3d 100644 --- a/rust/tw_any_coin/src/test_utils/address_utils.rs +++ b/rust/tw_any_coin/src/test_utils/address_utils.rs @@ -49,7 +49,7 @@ pub fn test_address_derive_with_derivation( let public_key = match key { KeyType::PrivateKey(key) => { - let private_key = TWPrivateKeyHelper::with_hex(key); + let private_key = TWPrivateKeyHelper::with_hex(key, coin_item.curve.to_raw()); TWPublicKeyHelper::wrap(unsafe { tw_private_key_get_public_key_by_type( private_key.ptr(), @@ -156,7 +156,8 @@ pub struct AddressCreateBech32WithPublicKey<'a> { } pub fn test_address_create_bech32_with_public_key(input: AddressCreateBech32WithPublicKey<'_>) { - let private_key = TWPrivateKeyHelper::with_hex(input.private_key); + let coin_item = get_coin_item(input.coin).unwrap(); + let private_key = TWPrivateKeyHelper::with_hex(input.private_key, coin_item.curve.to_raw()); let public_key = TWPublicKeyHelper::wrap(unsafe { tw_private_key_get_public_key_by_type(private_key.ptr(), input.public_key_type as u32) }); diff --git a/rust/tw_bech32_address/src/lib.rs b/rust/tw_bech32_address/src/lib.rs index ed35f0ba8f9..cce8a7235bf 100644 --- a/rust/tw_bech32_address/src/lib.rs +++ b/rust/tw_bech32_address/src/lib.rs @@ -202,7 +202,7 @@ impl Serialize for Bech32Address { mod tests { use super::*; use tw_encoding::hex::{DecodeHex, ToHex}; - + use tw_keypair::tw::Curve; struct FromPublicKeyTestInput<'a> { hrp: &'a str, private_key: &'a str, @@ -212,7 +212,8 @@ mod tests { } fn test_from_public_key(input: FromPublicKeyTestInput<'_>) { - let private_key = PrivateKey::new(input.private_key.decode_hex().unwrap()).unwrap(); + let private_key = + PrivateKey::new(input.private_key.decode_hex().unwrap(), Curve::Secp256k1).unwrap(); let public_key = private_key .get_public_key_by_type(input.public_key_type) .unwrap(); diff --git a/rust/tw_coin_entry/src/coin_context.rs b/rust/tw_coin_entry/src/coin_context.rs index bc80004caae..b2bfa82b42b 100644 --- a/rust/tw_coin_entry/src/coin_context.rs +++ b/rust/tw_coin_entry/src/coin_context.rs @@ -4,13 +4,16 @@ use crate::derivation::DerivationWithPath; use tw_hash::hasher::Hasher; -use tw_keypair::tw::PublicKeyType; +use tw_keypair::tw::{Curve, PublicKeyType}; /// Extend the trait with methods required for blockchain additions. pub trait CoinContext { /// Necessary chain property. fn public_key_type(&self) -> PublicKeyType; + /// Returns the curve for the coin. + fn curve(&self) -> Curve; + /// Optional chain property. fn address_hasher(&self) -> Option; diff --git a/rust/tw_coin_entry/src/coin_entry_ext.rs b/rust/tw_coin_entry/src/coin_entry_ext.rs index 05976df99ef..97e6cee6ab9 100644 --- a/rust/tw_coin_entry/src/coin_entry_ext.rs +++ b/rust/tw_coin_entry/src/coin_entry_ext.rs @@ -179,7 +179,7 @@ where return TWError::err(SigningErrorType::Error_not_supported); }; - let private_key = PrivateKey::new(private_key)?; + let private_key = PrivateKey::new(private_key, coin.curve())?; json_signer.sign_json(coin, input_json, &private_key) } diff --git a/rust/tw_coin_entry/src/error/impl_from.rs b/rust/tw_coin_entry/src/error/impl_from.rs index 12d95dddd82..7c060a8bdea 100644 --- a/rust/tw_coin_entry/src/error/impl_from.rs +++ b/rust/tw_coin_entry/src/error/impl_from.rs @@ -45,7 +45,10 @@ impl From for SigningError { | KeyPairError::InvalidSignature | KeyPairError::InvalidSignMessage | KeyPairError::SignatureVerifyError - | KeyPairError::InvalidEncryptedMessage => { + | KeyPairError::InvalidEncryptedMessage + | KeyPairError::InvalidMessage + | KeyPairError::InvalidRecId + | KeyPairError::UnsupportedCurve => { TWError::new(SigningErrorType::Error_invalid_params) }, KeyPairError::SigningError => TWError::new(SigningErrorType::Error_signing), diff --git a/rust/tw_coin_entry/src/test_utils/test_context.rs b/rust/tw_coin_entry/src/test_utils/test_context.rs index 5aaedceb221..22168d1793f 100644 --- a/rust/tw_coin_entry/src/test_utils/test_context.rs +++ b/rust/tw_coin_entry/src/test_utils/test_context.rs @@ -5,12 +5,13 @@ use crate::coin_context::CoinContext; use crate::derivation::DerivationWithPath; use tw_hash::hasher::Hasher; -use tw_keypair::tw::PublicKeyType; +use tw_keypair::tw::{Curve, PublicKeyType}; /// Test coin context that panics on any `CoinContext` method call. #[derive(Default)] pub struct TestCoinContext { pub public_key_type: Option, + pub curve: Option, pub address_hasher: Option, pub hrp: Option, pub p2pkh: Option, @@ -35,6 +36,12 @@ impl CoinContext for TestCoinContext { .expect("EmptyCoinContext::public_key_type was not set") } + fn curve(&self) -> Curve { + self.curve + .clone() + .expect("EmptyCoinContext::curve was not set") + } + fn address_hasher(&self) -> Option { self.address_hasher } diff --git a/rust/tw_coin_registry/src/coin_context.rs b/rust/tw_coin_registry/src/coin_context.rs index 9f38d9cd28b..4dc39680b99 100644 --- a/rust/tw_coin_registry/src/coin_context.rs +++ b/rust/tw_coin_registry/src/coin_context.rs @@ -6,7 +6,7 @@ use crate::registry::CoinItem; use tw_coin_entry::coin_context::CoinContext; use tw_coin_entry::derivation::DerivationWithPath; use tw_hash::hasher::Hasher; -use tw_keypair::tw::PublicKeyType; +use tw_keypair::tw::{Curve, PublicKeyType}; pub struct CoinRegistryContext { item: &'static CoinItem, @@ -25,6 +25,11 @@ impl CoinContext for CoinRegistryContext { self.item.public_key_type } + #[inline] + fn curve(&self) -> Curve { + self.item.curve.clone() + } + #[inline] fn address_hasher(&self) -> Option { self.item.address_hasher diff --git a/rust/tw_coin_registry/src/registry.rs b/rust/tw_coin_registry/src/registry.rs index 48a270a45e9..e75f0314794 100644 --- a/rust/tw_coin_registry/src/registry.rs +++ b/rust/tw_coin_registry/src/registry.rs @@ -10,7 +10,7 @@ use serde::Deserialize; use std::collections::HashMap; use tw_coin_entry::derivation::DerivationWithPath; use tw_hash::hasher::Hasher; -use tw_keypair::tw::PublicKeyType; +use tw_keypair::tw::{Curve, PublicKeyType}; type RegistryMap = HashMap; @@ -32,6 +32,7 @@ pub struct CoinItem { pub blockchain: BlockchainType, pub derivation: Vec, pub public_key_type: PublicKeyType, + pub curve: Curve, pub address_hasher: Option, pub hrp: Option, pub p2pkh_prefix: Option, diff --git a/rust/tw_cosmos_sdk/src/private_key/secp256k1.rs b/rust/tw_cosmos_sdk/src/private_key/secp256k1.rs index 655b80d5a51..d942c214d01 100644 --- a/rust/tw_cosmos_sdk/src/private_key/secp256k1.rs +++ b/rust/tw_cosmos_sdk/src/private_key/secp256k1.rs @@ -21,14 +21,12 @@ impl<'a> TryFrom<&'a [u8]> for Secp256PrivateKey { type Error = KeyPairError; fn try_from(value: &'a [u8]) -> Result { - tw::PrivateKey::new(value.to_vec()).map(Secp256PrivateKey) + tw::PrivateKey::new(value.to_vec(), Curve::Secp256k1).map(Secp256PrivateKey) } } impl CosmosPrivateKey for Secp256PrivateKey { fn sign_tx_hash(&self, hash: &[u8]) -> SigningResult { - self.0 - .sign(hash, Curve::Secp256k1) - .map_err(SigningError::from) + self.0.sign(hash).map_err(SigningError::from) } } diff --git a/rust/tw_keypair/src/ecdsa/canonical.rs b/rust/tw_keypair/src/ecdsa/canonical.rs index e335ee5c7de..f40da41b18a 100644 --- a/rust/tw_keypair/src/ecdsa/canonical.rs +++ b/rust/tw_keypair/src/ecdsa/canonical.rs @@ -24,6 +24,7 @@ use k256::sha2::digest::{FixedOutput, FixedOutputReset}; use k256::sha2::Digest; use rfc6979::{ByteArray, HmacDrbg}; use tw_hash::H256; +use tw_memory::ffi::c_byte_array::CByteArray; type CurveDigest = ::Digest; @@ -67,6 +68,37 @@ where Err(KeyPairError::SigningError) } +pub(crate) fn sign_canonical_ffi( + secret: &SigningKey, + hash_to_sign: H256, + canonical_checker: Option i32>, + transform_result: F, +) -> KeyPairResult> +where + F: FnOnce(Signature) -> Vec, + Scalar: SignPrimitive, + ::Uint: FieldBytesEncoding, +{ + let checker = move |sig: &Signature| { + if let Some(checker_fn) = canonical_checker { + // Convert signature to bytes and pass to the C function + let sig_bytes = sig.to_bytes(); + let sig_array = CByteArray::from(sig_bytes[..64].to_vec()); + + // Call the C function and check if it returns a positive value + unsafe { checker_fn(sig_bytes[64], sig_array.data()) > 0 } + } else { + // If no checker provided, accept all signatures + true + } + }; + + let mut sig = sign_with_canonical(secret, hash_to_sign, checker).map(transform_result)?; + // graphene adds 31 to the recovery id + sig[0] += 31; + Ok(sig) +} + fn ct_eq>(a: &ByteArray, b: &ByteArray) -> Choice { let mut ret = Choice::from(1); diff --git a/rust/tw_keypair/src/ecdsa/mod.rs b/rust/tw_keypair/src/ecdsa/mod.rs index dae3dd5bb7e..11f89c68564 100644 --- a/rust/tw_keypair/src/ecdsa/mod.rs +++ b/rust/tw_keypair/src/ecdsa/mod.rs @@ -8,7 +8,7 @@ use ecdsa::elliptic_curve::CurveArithmetic; use ecdsa::hazmat::DigestPrimitive; use ecdsa::PrimeCurve; -mod canonical; +pub mod canonical; pub mod der; pub mod nist256p1; pub mod secp256k1; diff --git a/rust/tw_keypair/src/ecdsa/signature.rs b/rust/tw_keypair/src/ecdsa/signature.rs index 0cb06a45a65..b70a14e698b 100644 --- a/rust/tw_keypair/src/ecdsa/signature.rs +++ b/rust/tw_keypair/src/ecdsa/signature.rs @@ -33,6 +33,16 @@ impl Signature { Signature { signature, v } } + pub(crate) fn new_from_bytes(sig: &[u8], rec_id: u8) -> KeyPairResult { + if sig.len() + 1 < Self::len() { + return Err(KeyPairError::InvalidSignature); + } + + let v = ecdsa::RecoveryId::from_byte(rec_id).ok_or(KeyPairError::InvalidSignature)?; + let signature = Self::signature_from_slices(&sig[Self::R_RANGE], &sig[Self::S_RANGE])?; + Ok(Self::new(signature, v)) + } + /// Returns the number of bytes for a serialized signature representation. pub const fn len() -> usize { Self::LEN @@ -90,6 +100,16 @@ impl Signature { dest } + pub fn vrs(&self) -> H520 { + let (r, s) = self.signature.split_bytes(); + + let mut dest = H520::default(); + dest[0] = self.v.to_byte(); + dest[1..33].copy_from_slice(r.as_slice()); + dest[33..65].copy_from_slice(s.as_slice()); + dest + } + pub fn to_der(&self) -> KeyPairResult { der::Signature::new(self.r(), self.s()) } diff --git a/rust/tw_keypair/src/ffi/privkey.rs b/rust/tw_keypair/src/ffi/privkey.rs index 49436e1a2cb..2c4232d38b6 100644 --- a/rust/tw_keypair/src/ffi/privkey.rs +++ b/rust/tw_keypair/src/ffi/privkey.rs @@ -31,11 +31,13 @@ impl AsRef for TWPrivateKey { pub unsafe extern "C" fn tw_private_key_create_with_data( input: *const u8, input_len: usize, + curve: u32, ) -> *mut TWPrivateKey { + let curve = try_or_else!(Curve::from_raw(curve), std::ptr::null_mut); let bytes_ref = CByteArrayRef::new(input, input_len); let bytes = try_or_else!(bytes_ref.to_vec(), std::ptr::null_mut); - PrivateKey::new(bytes) + PrivateKey::new(bytes, curve) .map(|private| TWPrivateKey(private).into_ptr()) // Return null if the private key is invalid. .unwrap_or_else(|_| std::ptr::null_mut()) @@ -72,6 +74,16 @@ pub unsafe extern "C" fn tw_private_key_size(data: *const TWPrivateKey) -> usize .unwrap_or_default() } +/// Returns the raw data of a given private-key. +/// +/// \param key *non-null* pointer to a private key. +/// \return C-compatible result with a C-compatible byte array. +#[no_mangle] +pub unsafe extern "C" fn tw_private_key_data(key: *mut TWPrivateKey) -> CByteArray { + let private = try_or_else!(TWPrivateKey::from_ptr_as_ref(key), CByteArray::default); + CByteArray::from(private.0.bytes().to_vec()) +} + /// Determines if the given private key is valid or not. /// /// \param key *non-null* byte array. @@ -101,9 +113,7 @@ pub unsafe extern "C" fn tw_private_key_sign( key: *mut TWPrivateKey, message: *const u8, message_len: usize, - curve: u32, ) -> CByteArray { - let curve = try_or_else!(Curve::from_raw(curve), CByteArray::default); let private = try_or_else!(TWPrivateKey::from_ptr_as_ref(key), CByteArray::default); let message_to_sign = try_or_else!( CByteArrayRef::new(message, message_len).as_slice(), @@ -111,7 +121,7 @@ pub unsafe extern "C" fn tw_private_key_sign( ); // Return an empty signature if an error occurs. - let sig = private.0.sign(message_to_sign, curve).unwrap_or_default(); + let sig = private.0.sign(message_to_sign).unwrap_or_default(); CByteArray::from(sig) } @@ -134,6 +144,58 @@ pub unsafe extern "C" fn tw_private_key_get_public_key_by_type( .unwrap_or_else(|_| std::ptr::null_mut()) } +/// Signs a digest using ECDSA with a canonical checker function. +/// +/// \param key *non-null* pointer to a Private key +/// \param digest *non-null* byte array containing the digest to sign. +/// \param digest_len the length of the `digest` array. +/// \param canonical_checker function pointer to check if signature is canonical. +/// \return Signature as a C-compatible result with a C-compatible byte array. +#[no_mangle] +pub unsafe extern "C" fn tw_private_key_sign_canonical( + key: *mut TWPrivateKey, + digest: *const u8, + digest_len: usize, + canonical_checker: Option i32>, +) -> CByteArray { + let private = try_or_else!(TWPrivateKey::from_ptr_as_ref(key), CByteArray::default); + let digest_to_sign = try_or_else!( + CByteArrayRef::new(digest, digest_len).as_slice(), + CByteArray::default + ); + + // Return an empty signature if an error occurs. + let sig = private + .0 + .sign_canonical(digest_to_sign, canonical_checker) + .unwrap_or_default(); + CByteArray::from(sig) +} + +/// Signs a digest using ECDSA as DER. +/// +/// \param key *non-null* pointer to a Private key +/// \param digest *non-null* byte array containing the digest to sign. +/// \param digest_len the length of the `digest` array. +/// \return Signature as a C-compatible result with a C-compatible byte array. +/// \note This function is only available for SECP256k1. +#[no_mangle] +pub unsafe extern "C" fn tw_private_key_sign_as_der( + key: *mut TWPrivateKey, + digest: *const u8, + digest_len: usize, +) -> CByteArray { + let private = try_or_else!(TWPrivateKey::from_ptr_as_ref(key), CByteArray::default); + let digest_to_sign = try_or_else!( + CByteArrayRef::new(digest, digest_len).as_slice(), + CByteArray::default + ); + + // Return an empty signature if an error occurs. + let sig = private.0.sign_as_der(digest_to_sign).unwrap_or_default(); + CByteArray::from(sig) +} + // #[no_mangle] // pub unsafe extern "C" fn tw_private_key_get_shared_key( // key: *mut TWPrivateKey, diff --git a/rust/tw_keypair/src/ffi/pubkey.rs b/rust/tw_keypair/src/ffi/pubkey.rs index 7f05f5f0fe9..1a3f9d2b239 100644 --- a/rust/tw_keypair/src/ffi/pubkey.rs +++ b/rust/tw_keypair/src/ffi/pubkey.rs @@ -41,6 +41,23 @@ pub unsafe extern "C" fn tw_public_key_create_with_data( .unwrap_or_else(|_| std::ptr::null_mut()) } +/// Determines if the given public key is valid or not. +/// +/// \param key *non-null* byte array. +/// \param key_len the length of the `key` array. +/// \param curve Eliptic curve of the public key. +/// \return true if the public key is valid, false otherwise. +#[no_mangle] +pub unsafe extern "C" fn tw_public_key_is_valid( + key: *const u8, + key_len: usize, + pubkey_type: u32, +) -> bool { + let pubkey_type = try_or_false!(PublicKeyType::from_raw(pubkey_type)); + let pub_key_bytes = try_or_false!(CByteArrayRef::new(key, key_len).to_vec()); + PublicKey::is_valid(pub_key_bytes, pubkey_type) +} + /// Delete the given public key. /// /// \param key *non-null* pointer to public key. @@ -82,21 +99,90 @@ pub unsafe extern "C" fn tw_public_key_data(key: *mut TWPublicKey) -> CByteArray CByteArray::from(public.0.to_bytes()) } -// #[no_mangle] -// pub unsafe extern "C" fn tw_public_key_is_valid( -// pubkey: *const u8, -// pubkey_len: usize, -// pubkey_type: u32, -// ) -> bool { -// let ty = match TWPublicKeyType::from_raw(pubkey_type) { -// Some(ty) => ty, -// None => return false, -// }; -// -// let pubkey_slice = match CByteArrayRef::new(pubkey, pubkey_len).as_slice() { -// Some(pubkey) => pubkey, -// None => return false, -// }; -// -// TWPublicKey::is_valid(pubkey_slice, ty) -// } +/// Returns the type of a given public-key. +/// +/// \param key *non-null* pointer to a public key. +/// \return C-compatible result with a C-compatible byte array. +#[no_mangle] +pub unsafe extern "C" fn tw_public_key_type(key: *mut TWPublicKey) -> u32 { + let public = try_or_else!(TWPublicKey::from_ptr_as_ref(key), || 0); + public.0.public_key_type() as u32 +} + +/// Returns the compressed data of a given public-key. +/// +/// \param key *non-null* pointer to a public key. +/// \return C-compatible result with a C-compatible byte array. +#[no_mangle] +pub unsafe extern "C" fn tw_public_key_compressed(key: *mut TWPublicKey) -> *mut TWPublicKey { + let public = try_or_else!(TWPublicKey::from_ptr_as_ref(key), std::ptr::null_mut); + public + .0 + .compressed() + .map(|public| TWPublicKey(public).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Returns the extended data of a given public-key. +/// +/// \param key *non-null* pointer to a public key. +/// \return C-compatible result with a C-compatible byte array. +#[no_mangle] +pub unsafe extern "C" fn tw_public_key_extended(key: *mut TWPublicKey) -> *mut TWPublicKey { + let public = try_or_else!(TWPublicKey::from_ptr_as_ref(key), std::ptr::null_mut); + public + .0 + .extended() + .map(|public| TWPublicKey(public).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Recover a public key from a signature and a message. +/// +/// \param sig *non-null* pointer to a block of data corresponding to the signature. +/// \param sig_len the length of the `sig` array. +/// \param msg *non-null* pointer to a block of data corresponding to the message. +/// \param msg_len the length of the `msg` array. +/// \return C-compatible result with a C-compatible pointer to the public key. +#[no_mangle] +pub unsafe extern "C" fn tw_public_key_recover_from_signature( + sig: *const u8, + sig_len: usize, + msg: *const u8, + msg_len: usize, + rec_id: u8, +) -> *mut TWPublicKey { + let sig = try_or_else!( + CByteArrayRef::new(sig, sig_len).as_slice(), + std::ptr::null_mut + ); + let msg = try_or_else!( + CByteArrayRef::new(msg, msg_len).as_slice(), + std::ptr::null_mut + ); + PublicKey::recover_from_signature(sig, msg, rec_id) + .map(|public| TWPublicKey(public).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Verify a signature as DER-encoded ECDSA signature. +/// +/// \param key *non-null* pointer to a public key. +/// \param sig *non-null* pointer to a block of data corresponding to the signature. +/// \param sig_len the length of the `sig` array. +/// \param msg *non-null* pointer to a block of data corresponding to the message. +/// \param msg_len the length of the `msg` array. +/// \return true if the signature and the message belongs to the given public key, otherwise false. +#[no_mangle] +pub unsafe extern "C" fn tw_public_key_verify_as_der( + key: *mut TWPublicKey, + sig: *const u8, + sig_len: usize, + msg: *const u8, + msg_len: usize, +) -> bool { + let public = try_or_false!(TWPublicKey::from_ptr_as_ref(key)); + let sig = try_or_false!(CByteArrayRef::new(sig, sig_len).as_slice()); + let msg = try_or_false!(CByteArrayRef::new(msg, msg_len).as_slice()); + public.0.verify_as_der(sig, msg) +} diff --git a/rust/tw_keypair/src/lib.rs b/rust/tw_keypair/src/lib.rs index 9a51090213d..0f185cfb57d 100644 --- a/rust/tw_keypair/src/lib.rs +++ b/rust/tw_keypair/src/lib.rs @@ -51,6 +51,7 @@ pub mod schnorr; pub mod starkex; pub mod traits; pub mod tw; +pub mod zilliqa_schnorr; #[cfg(feature = "test-utils")] pub mod test_utils; @@ -62,9 +63,12 @@ pub enum KeyPairError { InvalidSecretKey, InvalidPublicKey, InvalidSignature, + InvalidRecId, InvalidSignMessage, InvalidEncryptedMessage, SignatureVerifyError, SigningError, InternalError, + InvalidMessage, + UnsupportedCurve, } diff --git a/rust/tw_keypair/src/test_utils/tw_private_key_helper.rs b/rust/tw_keypair/src/test_utils/tw_private_key_helper.rs index 6c3c3bfc629..4250a64d4f4 100644 --- a/rust/tw_keypair/src/test_utils/tw_private_key_helper.rs +++ b/rust/tw_keypair/src/test_utils/tw_private_key_helper.rs @@ -12,16 +12,17 @@ pub struct TWPrivateKeyHelper { } impl TWPrivateKeyHelper { - pub fn with_bytes>(bytes: T) -> TWPrivateKeyHelper { + pub fn with_bytes>(bytes: T, curve: u32) -> TWPrivateKeyHelper { let priv_key_raw = CByteArray::from(bytes.into()); - let ptr = - unsafe { tw_private_key_create_with_data(priv_key_raw.data(), priv_key_raw.size()) }; + let ptr = unsafe { + tw_private_key_create_with_data(priv_key_raw.data(), priv_key_raw.size(), curve) + }; TWPrivateKeyHelper { ptr } } - pub fn with_hex(hex: &str) -> TWPrivateKeyHelper { + pub fn with_hex(hex: &str, curve: u32) -> TWPrivateKeyHelper { let priv_key_data = hex::decode(hex).unwrap(); - TWPrivateKeyHelper::with_bytes(priv_key_data) + TWPrivateKeyHelper::with_bytes(priv_key_data, curve) } pub fn ptr(&self) -> *mut TWPrivateKey { diff --git a/rust/tw_keypair/src/tw/mod.rs b/rust/tw_keypair/src/tw/mod.rs index 3e590dd0ed2..251d2d278f7 100644 --- a/rust/tw_keypair/src/tw/mod.rs +++ b/rust/tw_keypair/src/tw/mod.rs @@ -9,23 +9,34 @@ mod public; pub use private::PrivateKey; pub use public::PublicKey; +use zeroize::ZeroizeOnDrop; pub type Signature = Vec; #[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Deserialize, ZeroizeOnDrop)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub enum Curve { + #[serde(rename = "secp256k1")] Secp256k1 = 0, + #[serde(rename = "ed25519")] Ed25519 = 1, + #[serde(rename = "ed25519Blake2bNano")] Ed25519Blake2bNano = 2, /// Waves blockchain specific `curve25519`. + #[serde(rename = "curve25519Waves")] Curve25519Waves = 3, + #[serde(rename = "nist256p1")] Nist256p1 = 4, /// Cardano blockchain specific `ed25519` extended key. + #[serde(rename = "ed25519ExtendedCardano")] Ed25519ExtendedCardano = 5, + #[serde(rename = "starkex")] Starkex = 6, + #[serde(rename = "schnorr")] Schnorr = 7, + #[serde(rename = "zilliqaSchnorr")] + ZilliqaSchnorr = 8, } impl Curve { @@ -40,9 +51,24 @@ impl Curve { 5 => Some(Curve::Ed25519ExtendedCardano), 6 => Some(Curve::Starkex), 7 => Some(Curve::Schnorr), + 8 => Some(Curve::ZilliqaSchnorr), _ => None, } } + + pub fn to_raw(&self) -> u32 { + match self { + Curve::Secp256k1 => 0, + Curve::Ed25519 => 1, + Curve::Ed25519Blake2bNano => 2, + Curve::Curve25519Waves => 3, + Curve::Nist256p1 => 4, + Curve::Ed25519ExtendedCardano => 5, + Curve::Starkex => 6, + Curve::Schnorr => 7, + Curve::ZilliqaSchnorr => 8, + } + } } #[repr(C)] @@ -72,6 +98,8 @@ pub enum PublicKeyType { Starkex = 8, #[serde(rename = "schnorr")] Schnorr = 9, + #[serde(rename = "zilliqaSchnorr")] + ZilliqaSchnorr = 10, } impl PublicKeyType { @@ -88,6 +116,7 @@ impl PublicKeyType { 7 => Some(PublicKeyType::Ed25519ExtendedCardano), 8 => Some(PublicKeyType::Starkex), 9 => Some(PublicKeyType::Schnorr), + 10 => Some(PublicKeyType::ZilliqaSchnorr), _ => None, } } @@ -108,7 +137,8 @@ mod tests { (5, Some(Curve::Ed25519ExtendedCardano)), (6, Some(Curve::Starkex)), (7, Some(Curve::Schnorr)), - (8, None), + (8, Some(Curve::ZilliqaSchnorr)), + (9, None), ]; for (raw, expected) in tests { assert_eq!(Curve::from_raw(raw), expected); @@ -128,7 +158,8 @@ mod tests { (7, Some(PublicKeyType::Ed25519ExtendedCardano)), (8, Some(PublicKeyType::Starkex)), (9, Some(PublicKeyType::Schnorr)), - (10, None), + (10, Some(PublicKeyType::ZilliqaSchnorr)), + (11, None), ]; for (raw, expected) in tests { assert_eq!(PublicKeyType::from_raw(raw), expected); diff --git a/rust/tw_keypair/src/tw/private.rs b/rust/tw_keypair/src/tw/private.rs index 08cf8c18e80..8d44e3a8c92 100644 --- a/rust/tw_keypair/src/tw/private.rs +++ b/rust/tw_keypair/src/tw/private.rs @@ -2,13 +2,15 @@ // // Copyright © 2017 Trust Wallet. +use crate::ecdsa::canonical::sign_canonical_ffi; use crate::ecdsa::{nist256p1, secp256k1}; use crate::schnorr; use crate::traits::SigningKeyTrait; use crate::tw::{Curve, PublicKey, PublicKeyType}; +use crate::zilliqa_schnorr; use crate::{ed25519, starkex, KeyPairError, KeyPairResult}; use std::ops::Range; -use tw_hash::H256; +use tw_hash::{Hash, H256}; use tw_misc::traits::ToBytesVec; use zeroize::ZeroizeOnDrop; @@ -18,23 +20,24 @@ use zeroize::ZeroizeOnDrop; #[derive(ZeroizeOnDrop)] pub struct PrivateKey { bytes: Vec, + curve: Curve, } /// cbindgen:ignore impl PrivateKey { /// The number of bytes in a private key. - const SIZE: usize = 32; + pub(crate) const SIZE: usize = 32; const CARDANO_SIZE: usize = ed25519::cardano::ExtendedPrivateKey::LEN; const KEY_RANGE: Range = 0..Self::SIZE; const EXTENDED_CARDANO_RANGE: Range = 0..Self::CARDANO_SIZE; /// Validates the given `bytes` secret and creates a private key. - pub fn new(bytes: Vec) -> KeyPairResult { - if !Self::is_valid_general(&bytes) { + pub fn new(bytes: Vec, curve: Curve) -> KeyPairResult { + if !Self::is_valid_general(&bytes, &curve) { return Err(KeyPairError::InvalidSecretKey); } - Ok(PrivateKey { bytes }) + Ok(PrivateKey { bytes, curve }) } pub fn bytes(&self) -> &[u8] { @@ -59,9 +62,15 @@ impl PrivateKey { Ok(&self.bytes[Self::EXTENDED_CARDANO_RANGE]) } - /// Checks if the given `bytes` secret is valid in general (without a concrete curve). - pub fn is_valid_general(bytes: &[u8]) -> bool { - if bytes.len() != Self::SIZE && bytes.len() != Self::CARDANO_SIZE { + /// Checks if the given `bytes` secret is valid in general for the given `curve`. + pub fn is_valid_general(bytes: &[u8], curve: &Curve) -> bool { + let expected_len = if curve == &Curve::Ed25519ExtendedCardano { + Self::CARDANO_SIZE + } else { + Self::SIZE + }; + + if bytes.len() != expected_len { return false; } // Check for zero address. @@ -70,7 +79,7 @@ impl PrivateKey { /// Checks if the given `bytes` secret is valid. pub fn is_valid(bytes: &[u8], curve: Curve) -> bool { - if !Self::is_valid_general(bytes) { + if !Self::is_valid_general(bytes, &curve) { return false; } match curve { @@ -91,11 +100,14 @@ impl PrivateKey { }, Curve::Starkex => starkex::PrivateKey::try_from(&bytes[Self::KEY_RANGE]).is_ok(), Curve::Schnorr => schnorr::PrivateKey::try_from(&bytes[Self::KEY_RANGE]).is_ok(), + Curve::ZilliqaSchnorr => { + zilliqa_schnorr::PrivateKey::try_from(&bytes[Self::KEY_RANGE]).is_ok() + }, } } /// Signs a `message` with using the given elliptic curve. - pub fn sign(&self, message: &[u8], curve: Curve) -> KeyPairResult> { + pub fn sign(&self, message: &[u8]) -> KeyPairResult> { fn sign_impl(signing_key: Key, message: &[u8]) -> KeyPairResult> where Key: SigningKeyTrait, @@ -105,7 +117,7 @@ impl PrivateKey { signing_key.sign(hash_to_sign).map(|sig| sig.to_vec()) } - match curve { + match self.curve { Curve::Secp256k1 => sign_impl(self.to_secp256k1_privkey()?, message), Curve::Ed25519 => sign_impl(self.to_ed25519()?, message), Curve::Ed25519Blake2bNano => sign_impl(self.to_ed25519_blake2b()?, message), @@ -116,6 +128,38 @@ impl PrivateKey { }, Curve::Starkex => sign_impl(self.to_starkex_privkey()?, message), Curve::Schnorr => sign_impl(self.to_schnorr_privkey()?, message), + Curve::ZilliqaSchnorr => sign_impl(self.to_zilliqa_schnorr_privkey()?, message), + } + } + + pub fn sign_canonical( + &self, + message: &[u8], + canonical_checker: Option i32>, + ) -> KeyPairResult> { + let message_hash = + Hash::<32>::try_from(message).map_err(|_| KeyPairError::InvalidSignMessage)?; + + match self.curve { + Curve::Secp256k1 => { + let private_key = self.to_secp256k1_privkey()?; + sign_canonical_ffi( + &private_key.secret, + message_hash, + canonical_checker, + |sig| sig.vrs().to_vec(), + ) + }, + Curve::Nist256p1 => { + let private_key = self.to_nist256p1_privkey()?; + sign_canonical_ffi( + &private_key.secret, + message_hash, + canonical_checker, + |sig| sig.vrs().to_vec(), + ) + }, + _ => Err(KeyPairError::UnsupportedCurve), } } @@ -164,6 +208,10 @@ impl PrivateKey { let privkey = self.to_schnorr_privkey()?; Ok(PublicKey::Schnorr(privkey.public())) }, + PublicKeyType::ZilliqaSchnorr => { + let privkey = self.to_zilliqa_schnorr_privkey()?; + Ok(PublicKey::ZilliqaSchnorr(privkey.public())) + }, } } @@ -206,4 +254,27 @@ impl PrivateKey { fn to_schnorr_privkey(&self) -> KeyPairResult { schnorr::PrivateKey::try_from(self.key().as_slice()) } + + /// Tries to convert [`PrivateKey::key`] to [`zilliqa_schnorr::PrivateKey`]. + fn to_zilliqa_schnorr_privkey(&self) -> KeyPairResult { + zilliqa_schnorr::PrivateKey::try_from(self.key().as_slice()) + } + + /// Signs a digest using ECDSA as DER. + pub fn sign_as_der(&self, digest: &[u8]) -> KeyPairResult> { + match self.curve { + Curve::Secp256k1 => { + let private_key = self.to_secp256k1_privkey()?; + let hash_to_sign = + H256::try_from(digest).map_err(|_| KeyPairError::InvalidSignMessage)?; + let sig = private_key + .sign(hash_to_sign) + .map_err(|_| KeyPairError::InvalidSignature)?; + let der_sig = crate::ecdsa::der::Signature::new(sig.r(), sig.s()) + .map_err(|_| KeyPairError::InvalidSignature)?; + Ok(der_sig.der_bytes()) + }, + _ => Err(KeyPairError::UnsupportedCurve), + } + } } diff --git a/rust/tw_keypair/src/tw/public.rs b/rust/tw_keypair/src/tw/public.rs index 2ab54f8820a..3494abbd2a1 100644 --- a/rust/tw_keypair/src/tw/public.rs +++ b/rust/tw_keypair/src/tw/public.rs @@ -4,12 +4,16 @@ use crate::ecdsa::{nist256p1, secp256k1}; use crate::schnorr; -use crate::traits::VerifyingKeyTrait; +use crate::traits::{SigningKeyTrait, VerifyingKeyTrait}; use crate::tw::PublicKeyType; +use crate::zilliqa_schnorr; use crate::{ed25519, starkex, KeyPairError, KeyPairResult}; +use tw_hash::H256; use tw_misc::traits::ToBytesVec; use tw_misc::try_or_false; +use super::PrivateKey; + /// Represents a public key that can be used to verify signatures and messages. #[derive(Clone)] #[non_exhaustive] @@ -24,6 +28,7 @@ pub enum PublicKey { Ed25519ExtendedCardano(Box), Starkex(starkex::PublicKey), Schnorr(schnorr::PublicKey), + ZilliqaSchnorr(zilliqa_schnorr::PublicKey), } impl PublicKey { @@ -54,6 +59,12 @@ impl PublicKey { let pubkey = ed25519::sha512::PublicKey::try_from(bytes.as_slice())?; Ok(PublicKey::Ed25519(pubkey)) }, + PublicKeyType::Ed25519 + if ed25519::sha512::PublicKey::LEN + 1 == bytes.len() && bytes[0] == 0x01 => + { + let pubkey = ed25519::sha512::PublicKey::try_from(&bytes[1..])?; + Ok(PublicKey::Ed25519(pubkey)) + }, PublicKeyType::Ed25519Blake2b if ed25519::blake2b::PublicKey::LEN == bytes.len() => { let pubkey = ed25519::blake2b::PublicKey::try_from(bytes.as_slice())?; Ok(PublicKey::Ed25519Blake2b(pubkey)) @@ -76,6 +87,10 @@ impl PublicKey { let pubkey = schnorr::PublicKey::try_from(bytes.as_slice())?; Ok(PublicKey::Schnorr(pubkey)) }, + PublicKeyType::ZilliqaSchnorr => { + let pubkey = zilliqa_schnorr::PublicKey::try_from(bytes.as_slice())?; + Ok(PublicKey::ZilliqaSchnorr(pubkey)) + }, _ => Err(KeyPairError::InvalidPublicKey), } } @@ -114,6 +129,7 @@ impl PublicKey { }, PublicKey::Starkex(stark) => verify_impl(stark, sig, message), PublicKey::Schnorr(schnorr) => verify_impl(schnorr, sig, message), + PublicKey::ZilliqaSchnorr(zilliqa) => verify_impl(zilliqa, sig, message), } } @@ -130,6 +146,7 @@ impl PublicKey { PublicKey::Ed25519ExtendedCardano(cardano) => cardano.to_vec(), PublicKey::Starkex(stark) => stark.to_vec(), PublicKey::Schnorr(schnorr) => schnorr.to_vec(), + PublicKey::ZilliqaSchnorr(zilliqa) => zilliqa.to_vec(), } } @@ -171,6 +188,72 @@ impl PublicKey { PublicKey::Ed25519ExtendedCardano(_) => PublicKeyType::Ed25519ExtendedCardano, PublicKey::Starkex(_) => PublicKeyType::Starkex, PublicKey::Schnorr(_) => PublicKeyType::Schnorr, + PublicKey::ZilliqaSchnorr(_) => PublicKeyType::ZilliqaSchnorr, + } + } + + pub fn compressed(&self) -> KeyPairResult { + match self { + PublicKey::Secp256k1Extended(secp) => { + let bytes = secp.compressed().to_vec(); + PublicKey::new(bytes, PublicKeyType::Secp256k1) + }, + PublicKey::Nist256p1Extended(nist) => { + let bytes = nist.compressed().to_vec(); + PublicKey::new(bytes, PublicKeyType::Nist256p1) + }, + _ => Ok(self.clone()), + } + } + + pub fn extended(&self) -> KeyPairResult { + match self { + PublicKey::Secp256k1(secp) => { + let bytes = secp.uncompressed().to_vec(); + PublicKey::new(bytes, PublicKeyType::Secp256k1Extended) + }, + PublicKey::Nist256p1(nist) => { + let bytes = nist.uncompressed().to_vec(); + PublicKey::new(bytes, PublicKeyType::Nist256p1Extended) + }, + _ => Ok(self.clone()), + } + } + + pub fn recover_from_signature( + sig: &[u8], + message: &[u8], + rec_id: u8, + ) -> KeyPairResult { + if sig.len() < 2 * PrivateKey::SIZE { + return Err(KeyPairError::InvalidSignature); + } + if rec_id >= 4 { + return Err(KeyPairError::InvalidRecId); + } + if message.len() < PrivateKey::SIZE { + return Err(KeyPairError::InvalidMessage); + } + + let verify_sig = + ::Signature::new_from_bytes(sig, rec_id) + .map_err(|_| KeyPairError::InvalidSignature)?; + let message = H256::try_from(message).map_err(|_| KeyPairError::InvalidMessage)?; + let pubkey = secp256k1::PublicKey::recover(verify_sig, message)?; + Ok(PublicKey::Secp256k1Extended(pubkey)) + } + + pub fn verify_as_der(&self, sig: &[u8], message: &[u8]) -> bool { + match self { + PublicKey::Secp256k1(secp) | PublicKey::Secp256k1Extended(secp) => { + let der_sig = try_or_false!(crate::ecdsa::der::Signature::from_bytes(sig)); + let verify_sig = try_or_false!(secp256k1::VerifySignature::try_from( + der_sig.to_bytes().as_slice() + )); + let message = try_or_false!(H256::try_from(message)); + secp.verify(verify_sig, message) + }, + _ => false, } } } diff --git a/rust/tw_keypair/src/zilliqa_schnorr/mod.rs b/rust/tw_keypair/src/zilliqa_schnorr/mod.rs new file mode 100644 index 00000000000..c89022d56df --- /dev/null +++ b/rust/tw_keypair/src/zilliqa_schnorr/mod.rs @@ -0,0 +1,95 @@ +mod private; +mod public; +mod signature; + +pub use private::PrivateKey; +pub use public::PublicKey; +pub use signature::Signature; + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::{SigningKeyTrait, VerifyingKeyTrait}; + use crate::zilliqa_schnorr::public::VerifySignature; + use std::str::FromStr; + use tw_encoding::hex; + use tw_hash::sha2::sha256; + use tw_hash::sha3::keccak256; + use tw_hash::H256; + use tw_misc::traits::ToBytesVec; + + #[test] + fn test_public_key() { + let private_key = PrivateKey::from_str( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ) + .unwrap(); + + let public_key = private_key.public(); + assert_eq!( + hex::encode(public_key.to_vec(), false), + "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1" + ); + } + + #[test] + fn test_sign_zilliqa() { + let private_key = PrivateKey::from_str( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ) + .unwrap(); + + let message = "hello schnorr"; + let message_data = message.as_bytes(); + let digest = sha256(message_data); + + let signature = private_key.sign(digest.to_vec()).unwrap(); + + assert_eq!( + hex::encode(signature.to_vec(), false), + "b8118ccb99563fe014279c957b0a9d563c1666e00367e9896fe541765246964f64a53052513da4e6dc20fdaf69ef0d95b4ca51c87ad3478986cf053c2dd0b853" + ); + } + + #[test] + fn test_verify_zilliqa() { + let private_key = PrivateKey::from_str( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ) + .unwrap(); + + let public_key = private_key.public(); + + let message = "hello schnorr"; + let message_data = message.as_bytes(); + let digest = sha256(message_data); + + let signature = private_key.sign(digest.to_vec()).unwrap(); + + assert_eq!( + hex::encode(signature.to_vec(), false), + "b8118ccb99563fe014279c957b0a9d563c1666e00367e9896fe541765246964f64a53052513da4e6dc20fdaf69ef0d95b4ca51c87ad3478986cf053c2dd0b853" + ); + + let is_valid = public_key.verify(signature, digest.to_vec()); + assert!(is_valid); + } + + #[test] + fn test_verify_invalid() { + let private_key = PrivateKey::from_str( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ) + .unwrap(); + + let signature_bytes = hex::decode("b8118ccb99563fe014279c957b0a9d563c1666e00367e9896fe541765246964f64a53052513da4e6dc20fdaf69ef0d95b4ca51c87ad3478986cf053c2dd0b853").unwrap(); + let verify_sig = VerifySignature::try_from(signature_bytes.as_slice()).unwrap(); + + let hash_to_sign = keccak256(b"hello"); + let hash_to_sign = H256::try_from(hash_to_sign.as_slice()).unwrap(); + + assert!(!private_key + .public() + .verify(verify_sig, hash_to_sign.to_vec())); + } +} diff --git a/rust/tw_keypair/src/zilliqa_schnorr/private.rs b/rust/tw_keypair/src/zilliqa_schnorr/private.rs new file mode 100644 index 00000000000..71749063c34 --- /dev/null +++ b/rust/tw_keypair/src/zilliqa_schnorr/private.rs @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use super::PublicKey; +use super::Signature; +use crate::ecdsa::canonical::generate_k; +use crate::KeyPairResult; +use crate::{traits::SigningKeyTrait, KeyPairError}; +use ecdsa::elliptic_curve::PrimeField; +use ecdsa::hazmat::{bits2field, DigestPrimitive}; +use k256::elliptic_curve::bigint::ArrayEncoding; +use k256::elliptic_curve::Curve; +use k256::Secp256k1; +use k256::{ + elliptic_curve::{ops::Reduce, sec1::ToEncodedPoint}, + AffinePoint, Scalar, U256, +}; +use rfc6979::HmacDrbg; +use secp256k1::rand; +use sha2::{Digest, Sha256}; +use std::ops::Deref; +use std::str::FromStr; +use tw_encoding::hex; +use zeroize::ZeroizeOnDrop; + +type CurveDigest = ::Digest; + +#[derive(Debug, Clone, ZeroizeOnDrop)] +/// secp256k1 (K-256) secret key. +pub struct PrivateKey(k256::SecretKey); + +impl PrivateKey { + /// Generates a random private key. + /// + /// # Example + /// ``` + /// use tw_keypair::zilliqa_schnorr::PrivateKey; + /// let private_key = PrivateKey::create_random(); + /// ``` + pub fn create_random() -> Self { + Self(k256::SecretKey::random(&mut rand::thread_rng())) + } + + /// Constructs a private key from a raw secret key. + pub fn from_slice(slice: &[u8]) -> Result { + Ok(Self( + k256::SecretKey::from_slice(slice).map_err(|_| KeyPairError::InvalidSecretKey)?, + )) + } + + /// Returns corresponding public key of the private key + pub fn public(&self) -> PublicKey { + PublicKey::new(self.0.public_key()) + } + + // Taken from https://github.com/Zilliqa/zilliqa-rs/blob/24a0e882bcab634b6e776d94709c1760841023d4/src/crypto/schnorr.rs#L20 + fn sign_inner(&self, k: Scalar, message: &[u8]) -> Option { + let public_key = self.public(); + + // 2. Compute the commitment Q = kG, where G is the base point. + let q = AffinePoint::GENERATOR * k; + + // 3. Compute the challenge r = H(Q, kpub, m) + let mut hasher = Sha256::new(); + hasher.update(q.to_encoded_point(true).to_bytes()); + hasher.update(public_key.to_encoded_point(true).to_bytes()); + hasher.update(message); + let r = >::reduce_bytes(&hasher.finalize()); + + // 4. If r = 0 mod(order), goto 1 + if r.is_zero().into() { + return None; + } + + // 5. Compute s = k - r*kpriv mod(order) + let s: Scalar = k - r.mul(&self.0.as_scalar_primitive().into()); + + // 6. If s = 0 goto 1. + if s.is_zero().into() { + return None; + } + + // 7. Signature on m is (r, s) + k256::ecdsa::Signature::from_scalars(r.to_bytes(), s.to_bytes()) + .map(|signature| Signature { signature }) + .ok() + } +} + +impl TryFrom<&[u8]> for PrivateKey { + type Error = KeyPairError; + + fn try_from(bytes: &[u8]) -> Result { + Self::from_slice(bytes) + } +} + +impl FromStr for PrivateKey { + type Err = KeyPairError; + + fn from_str(secret_key: &str) -> Result { + let bytes = hex::decode(secret_key).map_err(|_| KeyPairError::InvalidSecretKey)?; + Ok(Self( + k256::SecretKey::from_slice(&bytes).map_err(|_| KeyPairError::InvalidSecretKey)?, + )) + } +} + +impl Deref for PrivateKey { + type Target = k256::SecretKey; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl SigningKeyTrait for PrivateKey { + type SigningMessage = Vec; + type Signature = Signature; + + // Taken from https://github.com/Zilliqa/zilliqa-rs/blob/24a0e882bcab634b6e776d94709c1760841023d4/src/crypto/schnorr.rs#L20 + fn sign(&self, message: Self::SigningMessage) -> KeyPairResult { + let priv_scalar = self.0.as_scalar_primitive(); + let entropy_input = &priv_scalar.to_bytes(); + + let message_hash = sha2::Sha256::digest(message.as_slice()); + let nonce = bits2field::(message_hash.as_slice()) + .map_err(|_| KeyPairError::InvalidSignMessage)?; + let n = Secp256k1::ORDER.to_be_byte_array(); + let additional_data = &[]; + + let mut hmac_drbg = + HmacDrbg::>::new(entropy_input, &nonce, additional_data); + + for _ in 0..10000 { + let k = generate_k(&mut hmac_drbg, &n); + let k_scalar = Scalar::from_repr(k).unwrap(); + + if let Some(signature) = self.sign_inner(k_scalar, message.as_slice()) { + return Ok(signature); + } + } + + Err(KeyPairError::SigningError) + } +} + +#[cfg(test)] +mod tests { + use super::{PrivateKey, PublicKey}; + use crate::traits::VerifyingKeyTrait; + use k256::{elliptic_curve::PrimeField, FieldBytes, Scalar}; + use tw_encoding::hex; + + #[test] + fn signing() { + // From https://github.com/Zilliqa/zilliqa-js/blob/226b371eaac78ed80e7b40b93189b6a97086bdf5/packages/zilliqa-js-crypto/test/schnorr.spec.ts#L23. + let cases = [ + ( + "A7F1D92A82C8D8FE434D98558CE2B347171198542F112D0558F56BD68807999248336241F30D23E55F30D1C8ED610C4B0235398184B814A29CB45A672ACAE548E9C5F1B0C4158AE59B4D39F6F7E8A105D3FEEDA5D5F3D9E45BFA6CC351E220AE0CE106986D61FF34A11E19FD3650E9B7818FC33A1E0FC02C44557AC8AB50C9B2DEB2F6B5E24C4FDD9F8867BDCE1FF261008E7897970E346207D75E47A158298E5BA2F56246869CC42E362A02731264E60687EF5309D108534F51F8658FB4F080B7CB19EE9AEBD718CC4FA27C8C37DFC1ADA5D133D13ABE03F021E9B1B78CCBD82F7FF2B38C6D48D01E481B2D4FAF7171805FD7F2D39EF4C4F19B9496E81DAB8193B3737E1B27D9C43957166441B93515E8F03C95D8E8CE1E1864FAAD68DDFC5932130109390B0F1FE5CA716805F8362E98DCCAADC86ADBED25801A9A9DCFA6264319DDAFE83A89C51F3C6D199D38DE10E660C37BE872C3F2B31660DE8BC95902B9103262CDB941F77376F5D3DBB7A3D5A387797FC4819A035ECA704CEDB37110EE7F206B0C8805AAEBF4963E7C4708CE8D4E092366E71792A8A3B2BBCDEE321B3E15380C541EF0930888969F7457AFE18588826A419D58311C1784B5484EECDB393F6A0ACA11B91DF0866B500B8DEE501FD7EB9BCE09A17D74124B4605ADFC0777BED9816D8D7E8488544A18D8045CB3283B0A752B881B5F500FADB59010E63D", + "039E43C9810E6CC09F46AAD38E716DAE3191629534967DC457D3A687D2E2CDDC6A", + "0F494B8312E8D257E51730C78F8FE3B47B6840C59AAAEC7C2EBE404A2DE8B25A", + "532B2267C4A3054F380B3357339BDFB379E88366FE61B42ACA05F69BC3F6F54E", + "3AF3D288E830E96FF8ED0769F45ABDA774CD989E2AE32EF9E985C8505F14FF98", + "E191EB14A70B5B53ADA45AFFF4A04578F5D8BB2B1C8A22985EA159B53826CDE7", + ), + ( + "1B664F8BDA2DBF33CB6BE21C8EB3ECA9D9D5BF144C08E9577ED0D1E5E560875109B340980580473DBC2E689A3BE838E77A0A3348FE960EC9BF81DA36F1868CA5D24788FA4C0C778BF0D12314285495636516CF40861B3D737FD35DBB591C5B5D25916EB1D86176B14E0E67D2D03957F0CF6C87834BF328540588360BA7C7C5F88541634FB7BADE5F94FF671D1FEBDCBDA116D2DA779038ED7679896C29198B2657B58C50EA054F644F4129C8BA8D8D544B727633DD40754398046796E038626FEF9237CE5B615BC08677EE5ABFBD85F73F7F8868CB1B5FBA4C1309F16061AA133821FBE2A758D2BBE6AA040A940D41B7D3B869CEE945150AA4A40E6FF719EEC24B2681CD5CE06B50273436584066046656D5EFED7315759189D68815DDB9E5F8D7FD53B6EC096616A773B9421F6704CED36EF4E484BA0C6C5A4855C71C33A54AC82BE803E5CFD175779FC444B7E6AA9001EEFABEBC0CF99754887C7B0A27AFDDC415F8A02C5AF1EFEA26AD1E5D92B1E29A8FAF5B2186C3094F4A137BCFAA65D7B274214DB64C86F3085B24938E1832FB310A6F064181E298D23062ABC817BA173023C8C04C5C3A1ECBF4AF72372B381FF69865C8F0E3C70B931C45A7419B3C441842EBFACC3D070AC3B433CD120B6E85B72DADCF40B23B173C34F6BE1B1901F6621F1497B085CF8E999D986EF8FF3A889A0238979983A8686F69E10EF9249A87", + "0245DC2911EDC02F2774E0A40FBEB0112EA60BF513F9EC50889D59FC94C97EC18F", + "8D566BB87EF69FFDA622E0A59FBAAFE57F486CE65844343A5D9B97DE9C4F619A", + "948AFFFF6E068CA2F2757BFD6085D6E4C3084B038E5533C5927ECB19EA0D329C", + "DFEE66E2C4799E73F0F778126A23032608408C27C2E7B3FA45A626BB9BDEB53C", + "75445CC9DBFE4E7BC64E020FA22CACFA4C40D5AA84DD6AEF661564FCA9746C40", + ), + ( + "3444C8501F19A8A78670F748FA401C4020AE086D7157A3837EC721DEF0D6E095928C5B78ED9B95560CE33D5B22778BE66DCEF2D21878D481DFF41A4DEDCAFDCAEAB4BD78629D7EC40FD26F1DD954CA84A3B53B84E9903056E840837A1390F37BB8ADE799DAC1E465D811916547EB4B6A163082E9833634A1224C54F681B8DC70A792C0CB4671D4970CCC80E2168CE920CC8FA07B1F90E9898D16019913ED5B8EE8A8DE7AB6F7895601FD20E49FD73E6F5D24C0D97E67871539F0E4E32CCB6677AFF03356D1F3790945E94039E51A63B3C840B74E3053D95CA71C0D3AC20A9065828D30AB5BFB6188A8F291FB1EB4E1EED03E2F5F558C00D8E3084120DEEB8BFE908429B36A896A45D624E79372CC18DF37DB2D20C9726D4FEF7BECF220138B53BC54C2DA461A9955AFF33F2F93DD96464BF3E883FC5750BDBE79BC2F82427F41DE42659AC4B111D7CEF8085003469DF8C9D3541480C6841707CE4C8F3D003AF982AD35C2733D0FA3B1EE52A6DAB36203D99AEC179A565B5050F480235C3BC560AA28EF5DD5525BFA254E584A86FDBD4BCC5B56551BAD00255CB72F806D7F3C533321B0864007AFBA4E0FF9638517FA8D788F52766F3A28C57C428BFDD4234AA760CE8044DF1E1FBA58E8B1D9C5A79D2AC4592FC31702F7E83351D2160C09C5CEA554F2C93A61C040E225612DF2B550900B097E18638350E3BA15C9AD53CE1861", + "02237627FE7374061FBD80AEA842DCE76D9206F0DDC7B319F3B30FA75DBD4F009A", + "009755F442D66585A10B80A49850C77764AD029D1BEA73F4DA45AB331306E6E5", + "2D78C77B736AD0A00FDF60695C01E96520656C13DC890A5B864672C6CED1C49A", + "4B73D4D919D7B4DEF330391899EA02023851CABE044E34E18EAE3E10588CECCD", + "D5DE85C4BDEA5910DC36AEF5660774D65291322C1E87FDA0D00C864E8C5FED29", + ), + ]; + for (message, public_key, secret_key, k, r, s) in cases { + let k = + Scalar::from_repr(FieldBytes::clone_from_slice(&hex::decode(k).unwrap())).unwrap(); + let message = hex::decode(message).unwrap(); + let secret_key = secret_key.parse::().unwrap(); + let public_key = public_key.parse::().unwrap(); + + let signature = secret_key.sign_inner(k, &message).unwrap(); + + assert_eq!(signature.signature.r().to_string(), r); + assert_eq!(signature.signature.s().to_string(), s); + + assert!(public_key.verify(signature, message)); + } + } +} diff --git a/rust/tw_keypair/src/zilliqa_schnorr/public.rs b/rust/tw_keypair/src/zilliqa_schnorr/public.rs new file mode 100644 index 00000000000..6d22bc03588 --- /dev/null +++ b/rust/tw_keypair/src/zilliqa_schnorr/public.rs @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::traits::VerifyingKeyTrait; +use crate::KeyPairError; +use k256::{ + elliptic_curve::{ops::Reduce, sec1::ToEncodedPoint, Group}, + AffinePoint, Scalar, U256, +}; +use sha2::{Digest, Sha256}; +use std::fmt::Display; +use std::ops::Deref; +use std::str::FromStr; +use tw_encoding::hex; +use tw_misc::traits::ToBytesVec; + +pub type VerifySignature = super::Signature; + +/// secp256k1 (K-256) public key. +#[derive(Debug, Clone)] +pub struct PublicKey(k256::PublicKey); + +impl PublicKey { + /// Creates a new public key. + pub fn new(pk: k256::PublicKey) -> Self { + Self(pk) + } +} + +impl TryFrom<&[u8]> for PublicKey { + type Error = KeyPairError; + + fn try_from(bytes: &[u8]) -> Result { + Ok(Self( + k256::PublicKey::from_sec1_bytes(bytes).map_err(|_| KeyPairError::InvalidPublicKey)?, + )) + } +} + +impl FromStr for PublicKey { + type Err = KeyPairError; + + /// Parse a string into a public key + /// + /// # Example + /// ``` + /// use tw_keypair::zilliqa_schnorr::PublicKey; + /// let public_key: PublicKey = "03bfad0f0b53cff5213b5947f3ddd66acee8906aba3610c111915aecc84092e052" + /// .parse() + /// .unwrap(); + /// assert_eq!( + /// public_key.to_string(), + /// "03bfad0f0b53cff5213b5947f3ddd66acee8906aba3610c111915aecc84092e052" + /// ); + /// ``` + fn from_str(public_key: &str) -> Result { + let bytes = hex::decode(public_key).map_err(|_| KeyPairError::InvalidPublicKey)?; + Ok(Self( + k256::PublicKey::from_sec1_bytes(&bytes).map_err(|_| KeyPairError::InvalidPublicKey)?, + )) + } +} + +impl Deref for PublicKey { + type Target = k256::PublicKey; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Display for PublicKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + hex::encode(self.to_sec1_bytes(), false).to_lowercase() + ) + } +} + +impl ToBytesVec for PublicKey { + fn to_vec(&self) -> Vec { + self.0.to_sec1_bytes().to_vec() + } +} + +impl VerifyingKeyTrait for PublicKey { + type SigningMessage = Vec; + type VerifySignature = VerifySignature; + + // Taken from https://github.com/Zilliqa/zilliqa-rs/blob/24a0e882bcab634b6e776d94709c1760841023d4/src/crypto/schnorr.rs#L50 + fn verify(&self, signature: Self::VerifySignature, message: Self::SigningMessage) -> bool { + let (r, s) = signature.split_scalars(); + + // 2. Compute Q = sG + r*kpub + let q = (AffinePoint::GENERATOR * *s) + (*self.as_affine() * *r); + + // 3. If Q = 0 (the neutral point), return 0; + if q.is_identity().into() { + return false; + } + + // 4. r' = H(Q, kpub, m) + let mut hasher = Sha256::new(); + hasher.update(q.to_encoded_point(true).to_bytes()); + hasher.update(self.to_encoded_point(true).to_bytes()); + hasher.update(message); + let r_dash = >::reduce_bytes(&hasher.finalize()); + + // 5. Return r' == r + if r_dash != *r { + return false; + } + + true + } +} diff --git a/rust/tw_keypair/src/zilliqa_schnorr/signature.rs b/rust/tw_keypair/src/zilliqa_schnorr/signature.rs new file mode 100644 index 00000000000..ce5158fd737 --- /dev/null +++ b/rust/tw_keypair/src/zilliqa_schnorr/signature.rs @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::KeyPairError; +use k256::NonZeroScalar; +use tw_misc::traits::ToBytesVec; + +pub struct Signature { + pub(crate) signature: k256::ecdsa::Signature, +} + +impl Signature { + pub fn new(signature: k256::ecdsa::Signature) -> Self { + Self { signature } + } + + pub(crate) fn split_scalars(&self) -> (NonZeroScalar, NonZeroScalar) { + self.signature.split_scalars() + } +} + +impl<'a> TryFrom<&'a [u8]> for Signature { + type Error = KeyPairError; + + fn try_from(sig: &'a [u8]) -> Result { + let signature = + k256::ecdsa::Signature::from_slice(sig).map_err(|_| KeyPairError::InvalidSignature)?; + Ok(Signature::new(signature)) + } +} + +impl ToBytesVec for Signature { + fn to_vec(&self) -> Vec { + self.signature.to_bytes().to_vec() + } +} diff --git a/rust/tw_keypair/tests/private_key_ffi_tests.rs b/rust/tw_keypair/tests/private_key_ffi_tests.rs index 8a03928ecfd..68def0d326b 100644 --- a/rust/tw_keypair/tests/private_key_ffi_tests.rs +++ b/rust/tw_keypair/tests/private_key_ffi_tests.rs @@ -8,7 +8,7 @@ use tw_hash::sha3::keccak256; use tw_hash::H256; use tw_keypair::ffi::privkey::{ tw_private_key_create_with_data, tw_private_key_get_public_key_by_type, - tw_private_key_is_valid, tw_private_key_sign, + tw_private_key_is_valid, tw_private_key_sign, tw_private_key_sign_as_der, }; use tw_keypair::ffi::pubkey::{tw_public_key_data, tw_public_key_delete, tw_public_key_verify}; use tw_keypair::test_utils::tw_private_key_helper::TWPrivateKeyHelper; @@ -17,18 +17,11 @@ use tw_keypair::tw::{Curve, PublicKeyType}; use tw_memory::ffi::c_byte_array::CByteArray; fn test_sign(curve: Curve, secret: &str, msg: &str, expected_sign: &str) { - let tw_privkey = TWPrivateKeyHelper::with_hex(secret); + let tw_privkey = TWPrivateKeyHelper::with_hex(secret, curve.to_raw()); let msg = hex::decode(msg).unwrap(); let msg_raw = CByteArray::from(msg); - let actual = unsafe { - tw_private_key_sign( - tw_privkey.ptr(), - msg_raw.data(), - msg_raw.size(), - curve as u32, - ) - .into_vec() - }; + let actual = + unsafe { tw_private_key_sign(tw_privkey.ptr(), msg_raw.data(), msg_raw.size()).into_vec() }; let expected = hex::decode(expected_sign).unwrap(); assert_eq!(actual, expected); } @@ -37,23 +30,25 @@ fn test_sign(curve: Curve, secret: &str, msg: &str, expected_sign: &str) { fn test_tw_private_key_create() { let tw_privkey = TWPrivateKeyHelper::with_hex( "ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a", + Curve::Secp256k1.to_raw(), ); assert!(!tw_privkey.is_null()); // Invalid hex. - let tw_privkey = TWPrivateKeyHelper::with_bytes(*b"123"); + let tw_privkey = TWPrivateKeyHelper::with_bytes(*b"123", Curve::Secp256k1.to_raw()); assert!(tw_privkey.is_null()); // Zero private key. let tw_privkey = TWPrivateKeyHelper::with_hex( "0000000000000000000000000000000000000000000000000000000000000000", + Curve::Secp256k1.to_raw(), ); assert!(tw_privkey.is_null()); } #[test] fn test_tw_private_key_delete_null() { - unsafe { tw_private_key_create_with_data(std::ptr::null_mut(), 0) }; + unsafe { tw_private_key_create_with_data(std::ptr::null_mut(), 0, Curve::Secp256k1.to_raw()) }; } #[test] @@ -117,17 +112,12 @@ fn test_tw_private_key_sign_starkex() { fn test_tw_private_key_sign_invalid_hash() { let tw_privkey = TWPrivateKeyHelper::with_hex( "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + Curve::Secp256k1.to_raw(), ); let hash = hex::decode("0xf86a808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000808080").unwrap(); let hash_raw = CByteArray::from(hash); let actual = unsafe { - tw_private_key_sign( - tw_privkey.ptr(), - hash_raw.data(), - hash_raw.size(), - Curve::Secp256k1 as u32, - ) - .into_vec() + tw_private_key_sign(tw_privkey.ptr(), hash_raw.data(), hash_raw.size()).into_vec() }; assert!(actual.is_empty()); } @@ -136,16 +126,9 @@ fn test_tw_private_key_sign_invalid_hash() { fn test_tw_private_key_sign_null_hash() { let tw_privkey = TWPrivateKeyHelper::with_hex( "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + Curve::Secp256k1.to_raw(), ); - let actual = unsafe { - tw_private_key_sign( - tw_privkey.ptr(), - std::ptr::null(), - 0, - Curve::Secp256k1 as u32, - ) - .into_vec() - }; + let actual = unsafe { tw_private_key_sign(tw_privkey.ptr(), std::ptr::null(), 0).into_vec() }; assert!(actual.is_empty()); } @@ -164,6 +147,7 @@ fn test_tw_private_key_get_public_key_by_type() { let tw_privkey = TWPrivateKeyHelper::with_hex( "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + Curve::Secp256k1.to_raw(), ); assert!(!tw_privkey.is_null()); @@ -181,31 +165,25 @@ fn test_tw_private_key_get_public_key_by_type() { #[test] fn test_tw_private_key_is_valid() { - fn is_valid(privkey_bytes: Vec) -> bool { + fn is_valid(privkey_bytes: Vec, curve: Curve) -> bool { let privkey_raw = CByteArray::from(privkey_bytes); - unsafe { - tw_private_key_is_valid( - privkey_raw.data(), - privkey_raw.size(), - Curve::Secp256k1 as u32, - ) - } + unsafe { tw_private_key_is_valid(privkey_raw.data(), privkey_raw.size(), curve.to_raw()) } } // Non-zero private key. let privkey_bytes = H256::from("0000000000000000000000000000000000000000000000000000000000000001"); - assert!(is_valid(privkey_bytes.into_vec())); + assert!(is_valid(privkey_bytes.into_vec(), Curve::Secp256k1)); // Cardano private key. let privkey_bytes = hex::decode("089b68e458861be0c44bf9f7967f05cc91e51ede86dc679448a3566990b7785bd48c330875b1e0d03caaed0e67cecc42075dce1c7a13b1c49240508848ac82f603391c68824881ae3fc23a56a1a75ada3b96382db502e37564e84a5413cfaf1290dbd508e5ec71afaea98da2df1533c22ef02a26bb87b31907d0b2738fb7785b38d53aa68fc01230784c9209b2b2a2faf28491b3b1f1d221e63e704bbd0403c4154425dfbb01a2c5c042da411703603f89af89e57faae2946e2a5c18b1c5ca0e").unwrap(); - assert!(is_valid(privkey_bytes)); + assert!(is_valid(privkey_bytes, Curve::Ed25519ExtendedCardano)); // Zero private key. let privkey_bytes = H256::from("0000000000000000000000000000000000000000000000000000000000000000"); - assert!(!is_valid(privkey_bytes.into_vec())); + assert!(!is_valid(privkey_bytes.into_vec(), Curve::Secp256k1)); } // `schnorr` generates unique signatures based on auxiliary random. @@ -214,18 +192,11 @@ fn test_tw_private_key_sign_schnorr() { let secret = "0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79"; let msg = "99b7098e8150cde90f3ec00280815d3069f81c7cdb6d83bbe2b897b1afbe7cd6"; - let tw_privkey = TWPrivateKeyHelper::with_hex(secret); + let tw_privkey = TWPrivateKeyHelper::with_hex(secret, Curve::Schnorr.to_raw()); let msg = hex::decode(msg).unwrap(); let msg_raw = CByteArray::from(msg); - let signature = unsafe { - tw_private_key_sign( - tw_privkey.ptr(), - msg_raw.data(), - msg_raw.size(), - Curve::Schnorr as u32, - ) - .into_vec() - }; + let signature = + unsafe { tw_private_key_sign(tw_privkey.ptr(), msg_raw.data(), msg_raw.size()).into_vec() }; let signature_data = CByteArray::from(signature); @@ -247,3 +218,63 @@ fn test_tw_private_key_sign_schnorr() { }; assert!(is_valid, "Error verifying a schnorr signature"); } + +#[test] +fn test_tw_private_key_sign_as_der() { + let tw_privkey = TWPrivateKeyHelper::with_hex( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + Curve::Secp256k1.to_raw(), + ); + assert!(!tw_privkey.is_null()); + + let message = "hello"; + let message_data = message.as_bytes(); + let digest = keccak256(message_data); + let digest_raw = CByteArray::from(digest.to_vec()); + + let signature = unsafe { + tw_private_key_sign_as_der(tw_privkey.ptr(), digest_raw.data(), digest_raw.size()) + .into_vec() + }; + + assert_eq!( + hex::encode(signature, false), + "30450221008720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba6202204d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de9" + ); +} + +#[test] +fn test_tw_private_key_sign_zilliqa() { + let tw_privkey = TWPrivateKeyHelper::with_hex( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + Curve::ZilliqaSchnorr.to_raw(), + ); + assert!(!tw_privkey.is_null()); + + let message = "hello schnorr"; + let message_data = message.as_bytes(); + let digest = sha256(message_data); + let digest_raw = CByteArray::from(digest.to_vec()); + + let signature = unsafe { + tw_private_key_sign(tw_privkey.ptr(), digest_raw.data(), digest_raw.size()).into_vec() + }; + + assert_eq!( + hex::encode(signature, false), + "b8118ccb99563fe014279c957b0a9d563c1666e00367e9896fe541765246964f64a53052513da4e6dc20fdaf69ef0d95b4ca51c87ad3478986cf053c2dd0b853" + ); +} + +#[test] +fn test_private_key_is_valid() { + let private_key_hex = "0x4646464646464646464646464646464646464646464646464646464646464646"; + let bytes = hex::decode(private_key_hex).unwrap(); + let bytes_ptr = bytes.as_ptr(); + let bytes_len = bytes.len(); + + let is_valid = + unsafe { tw_private_key_is_valid(bytes_ptr, bytes_len, Curve::Secp256k1.to_raw()) }; + + assert!(is_valid); +} diff --git a/rust/tw_keypair/tests/public_key_ffi_tests.rs b/rust/tw_keypair/tests/public_key_ffi_tests.rs index 9195ed66f5d..cc4b4fdbb21 100644 --- a/rust/tw_keypair/tests/public_key_ffi_tests.rs +++ b/rust/tw_keypair/tests/public_key_ffi_tests.rs @@ -5,9 +5,17 @@ use tw_encoding::hex; use tw_hash::sha2::sha256; use tw_hash::sha3::keccak256; -use tw_keypair::ffi::pubkey::{tw_public_key_delete, tw_public_key_verify}; +use tw_keypair::ffi::privkey::{ + tw_private_key_get_public_key_by_type, tw_private_key_sign, tw_private_key_sign_as_der, +}; +use tw_keypair::ffi::pubkey::{ + tw_public_key_compressed, tw_public_key_data, tw_public_key_delete, tw_public_key_extended, + tw_public_key_is_valid, tw_public_key_recover_from_signature, tw_public_key_type, + tw_public_key_verify, tw_public_key_verify_as_der, +}; +use tw_keypair::test_utils::tw_private_key_helper::TWPrivateKeyHelper; use tw_keypair::test_utils::tw_public_key_helper::TWPublicKeyHelper; -use tw_keypair::tw::PublicKeyType; +use tw_keypair::tw::{Curve, PublicKeyType}; use tw_memory::ffi::c_byte_array::CByteArray; fn test_verify(ty: PublicKeyType, public: &str, msg: &str, sign: &str) { @@ -38,6 +46,8 @@ fn test_tw_public_key_create_by_type() { PublicKeyType::Secp256k1, ); assert!(!tw_public.is_null()); + let ty = unsafe { tw_public_key_type(tw_public.ptr()) }; + assert_eq!(ty, PublicKeyType::Secp256k1 as u32); // Compressed pubkey with '03' prefix. let tw_public = TWPublicKeyHelper::with_hex( @@ -45,12 +55,16 @@ fn test_tw_public_key_create_by_type() { PublicKeyType::Secp256k1, ); assert!(!tw_public.is_null()); + let ty = unsafe { tw_public_key_type(tw_public.ptr()) }; + assert_eq!(ty, PublicKeyType::Secp256k1 as u32); let tw_public = TWPublicKeyHelper::with_hex( "0499c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c166b489a4b7c491e7688e6ebea3a71fc3a1a48d60f98d5ce84c93b65e423fde91", PublicKeyType::Secp256k1Extended, ); assert!(!tw_public.is_null()); + let ty = unsafe { tw_public_key_type(tw_public.ptr()) }; + assert_eq!(ty, PublicKeyType::Secp256k1Extended as u32); // Pass an extended pubkey, but Secp256k1 type. let tw_public = TWPublicKeyHelper::with_hex( @@ -58,6 +72,8 @@ fn test_tw_public_key_create_by_type() { PublicKeyType::Secp256k1, ); assert!(tw_public.is_null()); + let ty = unsafe { tw_public_key_type(tw_public.ptr()) }; + assert_eq!(ty, PublicKeyType::Secp256k1 as u32); // Pass a compressed pubkey, but Secp256k1Extended type. let tw_public = TWPublicKeyHelper::with_hex( @@ -65,6 +81,8 @@ fn test_tw_public_key_create_by_type() { PublicKeyType::Secp256k1Extended, ); assert!(tw_public.is_null()); + let ty = unsafe { tw_public_key_type(tw_public.ptr()) }; + assert_eq!(ty, PublicKeyType::Secp256k1 as u32); } #[test] @@ -120,3 +138,200 @@ fn test_tw_public_key_verify_ed25519_extended_cardano() { let sign = "375df53b6a4931dcf41e062b1c64288ed4ff3307f862d5c1b1c71964ce3b14c99422d0fdfeb2807e9900a26d491d5e8a874c24f98eec141ed694d7a433a90f08"; test_verify(PublicKeyType::Ed25519ExtendedCardano, public, &msg, sign); } + +#[test] +fn test_tw_public_key_compressed_extended() { + let tw_privkey = TWPrivateKeyHelper::with_hex( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + Curve::Secp256k1.to_raw(), + ); + assert!(!tw_privkey.is_null()); + + let tw_pubkey = unsafe { + TWPublicKeyHelper::wrap(tw_private_key_get_public_key_by_type( + tw_privkey.ptr(), + PublicKeyType::Secp256k1 as u32, + )) + }; + assert!(!tw_pubkey.is_null()); + + unsafe { + assert_eq!( + tw_public_key_type(tw_pubkey.ptr()), + PublicKeyType::Secp256k1 as u32 + ); + } + + let tw_extended = unsafe { TWPublicKeyHelper::wrap(tw_public_key_extended(tw_pubkey.ptr())) }; + assert!(!tw_extended.is_null()); + + unsafe { + assert_eq!( + tw_public_key_type(tw_extended.ptr()), + PublicKeyType::Secp256k1Extended as u32 + ); + } + + let tw_compressed = + unsafe { TWPublicKeyHelper::wrap(tw_public_key_compressed(tw_extended.ptr())) }; + assert!(!tw_compressed.is_null()); + + unsafe { + assert_eq!( + tw_public_key_type(tw_compressed.ptr()), + PublicKeyType::Secp256k1 as u32 + ); + } +} + +#[test] +fn test_tw_public_key_recover() { + let message = + hex::decode("de4e9524586d6fce45667f9ff12f661e79870c4105fa0fb58af976619bb11432").unwrap(); + let signature = hex::decode("00000000000000000000000000000000000000000000000000000000000000020123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef80").unwrap(); + + let message_raw = CByteArray::from(message); + let signature_raw = CByteArray::from(signature); + + let tw_public_key = unsafe { + TWPublicKeyHelper::wrap(tw_public_key_recover_from_signature( + signature_raw.data(), + signature_raw.size(), + message_raw.data(), + message_raw.size(), + 0x01, + )) + }; + + assert!(!tw_public_key.is_null()); + + unsafe { + assert_eq!( + tw_public_key_type(tw_public_key.ptr()), + PublicKeyType::Secp256k1Extended as u32 + ); + + let public_key_data = tw_public_key_data(tw_public_key.ptr()); + let public_key_hex = hex::encode(public_key_data.into_vec(), false); + + assert_eq!( + public_key_hex, + "0456d8089137b1fd0d890f8c7d4a04d0fd4520a30b19518ee87bd168ea12ed8090329274c4c6c0d9df04515776f2741eeffc30235d596065d718c3973e19711ad0" + ); + } +} + +#[test] +fn test_tw_public_key_verify_as_der() { + let tw_privkey = TWPrivateKeyHelper::with_hex( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + Curve::Secp256k1.to_raw(), + ); + assert!(!tw_privkey.is_null()); + + let message = "Hello"; + let message_data = message.as_bytes(); + let digest = keccak256(message_data); + let digest_raw = CByteArray::from(digest.to_vec()); + + // Sign the digest using DER format + let signature = unsafe { + tw_private_key_sign_as_der(tw_privkey.ptr(), digest_raw.data(), digest_raw.size()) + .into_vec() + }; + let signature_raw = CByteArray::from(signature); + + // Get the public key + let tw_public_key = unsafe { + TWPublicKeyHelper::wrap(tw_private_key_get_public_key_by_type( + tw_privkey.ptr(), + PublicKeyType::Secp256k1Extended as u32, + )) + }; + + // Verify the DER signature + let is_valid_der = unsafe { + tw_public_key_verify_as_der( + tw_public_key.ptr(), + signature_raw.data(), + signature_raw.size(), + digest_raw.data(), + digest_raw.size(), + ) + }; + assert!(is_valid_der, "DER signature verification failed"); + + // Regular verification should fail with DER signature + let is_valid_regular = unsafe { + tw_public_key_verify( + tw_public_key.ptr(), + signature_raw.data(), + signature_raw.size(), + digest_raw.data(), + digest_raw.size(), + ) + }; + assert!( + !is_valid_regular, + "Regular verification should fail with DER signature" + ); +} + +#[test] +fn test_tw_public_key_verify_zilliqa() { + let tw_privkey = TWPrivateKeyHelper::with_hex( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + Curve::ZilliqaSchnorr.to_raw(), + ); + assert!(!tw_privkey.is_null()); + + let message = "hello schnorr"; + let message_data = message.as_bytes(); + let digest = sha256(message_data); + let digest_raw = CByteArray::from(digest.to_vec()); + + // Sign the digest using Zilliqa format + let signature = unsafe { + tw_private_key_sign(tw_privkey.ptr(), digest_raw.data(), digest_raw.size()).into_vec() + }; + let signature_raw = CByteArray::from(signature.clone()); + + // Get the public key + let tw_public_key = unsafe { + TWPublicKeyHelper::wrap(tw_private_key_get_public_key_by_type( + tw_privkey.ptr(), + PublicKeyType::ZilliqaSchnorr as u32, + )) + }; + + // Verify the Zilliqa signature + let is_valid = unsafe { + tw_public_key_verify( + tw_public_key.ptr(), + signature_raw.data(), + signature_raw.size(), + digest_raw.data(), + digest_raw.size(), + ) + }; + assert!(is_valid, "Zilliqa signature verification failed"); + + // Check the expected signature + assert_eq!( + hex::encode(signature, false), + "b8118ccb99563fe014279c957b0a9d563c1666e00367e9896fe541765246964f64a53052513da4e6dc20fdaf69ef0d95b4ca51c87ad3478986cf053c2dd0b853" + ); +} + +#[test] +fn test_public_key_is_valid() { + let public_key_hex = "0xbeff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908"; + let bytes = hex::decode(public_key_hex).unwrap(); + let bytes_ptr = bytes.as_ptr(); + let bytes_len = bytes.len(); + + let is_valid = + unsafe { tw_public_key_is_valid(bytes_ptr, bytes_len, PublicKeyType::Ed25519 as u32) }; + + assert!(is_valid); +} diff --git a/rust/tw_keypair/tests/tw_keypair_starkex_tests.rs b/rust/tw_keypair/tests/tw_keypair_starkex_tests.rs index 9fe872ad797..411f46b8eef 100644 --- a/rust/tw_keypair/tests/tw_keypair_starkex_tests.rs +++ b/rust/tw_keypair/tests/tw_keypair_starkex_tests.rs @@ -13,7 +13,7 @@ fn test_starkex_tw_private_key() { let pubkey_bytes = hex::decode("02a4c7332c55d6c1c510d24272d1db82878f2302f05b53bcc38695ed5f78fffd").unwrap(); - let privkey = PrivateKey::new(privkey_bytes.clone()).unwrap(); + let privkey = PrivateKey::new(privkey_bytes.clone(), Curve::Starkex).unwrap(); assert_eq!(privkey.key().into_vec(), privkey_bytes); let public = privkey @@ -29,8 +29,8 @@ fn test_starkex_tw_private_key_sign() { let hash_to_sign = hex::decode("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76").unwrap(); - let privkey = PrivateKey::new(privkey_bytes).unwrap(); - let actual = privkey.sign(&hash_to_sign, Curve::Starkex).unwrap(); + let privkey = PrivateKey::new(privkey_bytes.clone(), Curve::Starkex).unwrap(); + let actual = privkey.sign(&hash_to_sign).unwrap(); let expected = H512::from("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a"); assert_eq!(actual, expected.into_vec()); } diff --git a/rust/tw_tests/tests/chains/ton/ton_message_signer.rs b/rust/tw_tests/tests/chains/ton/ton_message_signer.rs index 523188947fa..3a0b4265985 100644 --- a/rust/tw_tests/tests/chains/ton/ton_message_signer.rs +++ b/rust/tw_tests/tests/chains/ton/ton_message_signer.rs @@ -2,7 +2,7 @@ // // Copyright © 2017 Trust Wallet. -use tw_keypair::test_utils::tw_private_key_helper::TWPrivateKeyHelper; +use tw_keypair::{test_utils::tw_private_key_helper::TWPrivateKeyHelper, tw::Curve}; use tw_memory::test_utils::tw_string_helper::TWStringHelper; use wallet_core_rs::ffi::ton::message_signer::tw_ton_message_signer_sign_message; @@ -10,6 +10,7 @@ use wallet_core_rs::ffi::ton::message_signer::tw_ton_message_signer_sign_message fn test_ton_wallet_create_state_init() { let private_key = TWPrivateKeyHelper::with_hex( "112d4e2e700a468f1eae699329202f1ee671d6b665caa2d92dea038cf3868c18", + Curve::Ed25519.to_raw(), ); assert!(!private_key.is_null()); let message = TWStringHelper::create("Hello world"); diff --git a/rust/tw_tests/tests/coin_address_derivation_test.rs b/rust/tw_tests/tests/coin_address_derivation_test.rs index 026b260128c..068bbdaeb06 100644 --- a/rust/tw_tests/tests/coin_address_derivation_test.rs +++ b/rust/tw_tests/tests/coin_address_derivation_test.rs @@ -16,13 +16,14 @@ use tw_memory::test_utils::tw_string_helper::TWStringHelper; #[test] fn test_coin_address_derivation() { - let private_key = TWPrivateKeyHelper::with_hex( - "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", - ); - for coin in CoinType::iter() { let coin_item = get_coin_item(coin).unwrap(); + let private_key = TWPrivateKeyHelper::with_hex( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + coin_item.curve.to_raw(), + ); + // Skip unsupported blockchains. if !coin_item.blockchain.is_supported() { continue; diff --git a/src/EOS/Signer.cpp b/src/EOS/Signer.cpp index 1c1286e17c4..a9fe3dbc608 100644 --- a/src/EOS/Signer.cpp +++ b/src/EOS/Signer.cpp @@ -88,7 +88,7 @@ TW::Data Signer::serializeTx(const Transaction& transaction) const noexcept { } // canonical check for EOS -int Signer::isCanonical([[maybe_unused]] uint8_t by, uint8_t sig[64]) { +int Signer::isCanonical([[maybe_unused]] uint8_t by, const uint8_t sig[64]) { // clang-format off return !(sig[0] & 0x80) && !(sig[0] == 0 && !(sig[1] & 0x80)) diff --git a/src/EOS/Signer.h b/src/EOS/Signer.h index 0eef641f9a2..5667515ec84 100644 --- a/src/EOS/Signer.h +++ b/src/EOS/Signer.h @@ -36,7 +36,7 @@ class Signer { /// Serialize the transaction. Data serializeTx(const Transaction& transaction) const noexcept; - static int isCanonical(uint8_t by, uint8_t sig[64]); + static int isCanonical(uint8_t by, const uint8_t sig[64]); Transaction buildTx(const Proto::SigningInput& input) const; Data buildUnsignedTx(const Proto::SigningInput& input) noexcept; diff --git a/src/FIO/Signer.cpp b/src/FIO/Signer.cpp index 566af04f1ec..571f62912a5 100644 --- a/src/FIO/Signer.cpp +++ b/src/FIO/Signer.cpp @@ -62,7 +62,7 @@ bool Signer::verify(const PublicKey& pubKey, const Data& data, const Data& signa } // canonical check for FIO, both R and S length is 32 -int Signer::isCanonical([[maybe_unused]] uint8_t by, uint8_t sig[64]) { +int Signer::isCanonical([[maybe_unused]] uint8_t by, const uint8_t sig[64]) { return !(sig[0] & 0x80) && !(sig[0] == 0 && !(sig[1] & 0x80)) && !(sig[32] & 0x80) diff --git a/src/FIO/Signer.h b/src/FIO/Signer.h index 72b3d7b7098..e18494ad815 100644 --- a/src/FIO/Signer.h +++ b/src/FIO/Signer.h @@ -34,7 +34,7 @@ class Signer { /// Verify a signature, used in testing static bool verify(const PublicKey& pubKey, const Data& data, const Data& signature); - static int isCanonical(uint8_t by, uint8_t sig[64]); + static int isCanonical(uint8_t by, const uint8_t sig[64]); }; } // namespace TW::FIO diff --git a/src/HDWallet.cpp b/src/HDWallet.cpp index a329f6634da..4b50eb42726 100644 --- a/src/HDWallet.cpp +++ b/src/HDWallet.cpp @@ -196,7 +196,7 @@ PrivateKey HDWallet::getKeyByCurve(TWCurve curve, const DerivationPath auto data = Data(node.private_key, node.private_key + PrivateKey::_size); TW::memzero(&node); if (curve == TWCurveStarkex) { - return ImmutableX::getPrivateKeyFromEthPrivKey(PrivateKey(data, curve)); + return ImmutableX::getPrivateKeyFromEthPrivKey(PrivateKey(data, TWCurveSECP256k1)); } return PrivateKey(data, curve); } @@ -384,6 +384,7 @@ const char* curveName(TWCurve curve) { switch (curve) { case TWCurveStarkex: case TWCurveSECP256k1: + case TWCurveZILLIQASchnorr: return SECP256K1_NAME; case TWCurveED25519: return ED25519_NAME; diff --git a/src/PrivateKey.cpp b/src/PrivateKey.cpp index be4c8dc4f4b..1195535c832 100644 --- a/src/PrivateKey.cpp +++ b/src/PrivateKey.cpp @@ -7,52 +7,12 @@ #include "HexCoding.h" #include "PublicKey.h" -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include #include using namespace TW; -Data rust_get_public_from_private(const Data& key, TWPublicKeyType public_type) { - auto* privkey = Rust::tw_private_key_create_with_data(key.data(), key.size()); - if (privkey == nullptr) { - return {}; - } - Data toReturn; - - auto* pubkey = Rust::tw_private_key_get_public_key_by_type(privkey, static_cast(public_type)); - if (pubkey == nullptr) { - Rust::tw_private_key_delete(privkey); - return {}; - } - - Rust::CByteArrayWrapper res = Rust::tw_public_key_data(pubkey); - - Rust::tw_public_key_delete(pubkey); - Rust::tw_private_key_delete(privkey); - return res.data; -} - -Data rust_private_key_sign(const Data& key, const Data& hash, TWCurve curve) { - auto* priv = Rust::tw_private_key_create_with_data(key.data(), key.size()); - if (priv == nullptr) { - return {}; - } - Rust::CByteArrayWrapper res = Rust::tw_private_key_sign(priv, hash.data(), hash.size(), static_cast(curve)); - Rust::tw_private_key_delete(priv); - return res.data; -} - bool PrivateKey::isValid(const Data& data) { // Check length if (data.size() != _size && data.size() != cardanoKeySize) { @@ -70,39 +30,7 @@ bool PrivateKey::isValid(const Data& data) { } bool PrivateKey::isValid(const Data& data, TWCurve curve) { - // check size - bool valid = isValid(data); - if (!valid) { - return false; - } - - const ecdsa_curve* ec_curve = nullptr; - switch (curve) { - case TWCurveSECP256k1: - ec_curve = &secp256k1; - break; - case TWCurveNIST256p1: - ec_curve = &nist256p1; - break; - case TWCurveED25519: - case TWCurveED25519Blake2bNano: - case TWCurveED25519ExtendedCardano: - case TWCurveCurve25519: - case TWCurveNone: - default: - break; - } - - if (ec_curve != nullptr) { - bignum256 k; - bn_read_be(data.data(), &k); - if (!bn_is_less(&k, &ec_curve->order)) { - memzero(&k, sizeof(k)); - return false; - }; - } - - return true; + return Rust::tw_private_key_is_valid(data.data(), data.size(), static_cast(curve)); } TWPrivateKeyType PrivateKey::getType(TWCurve curve) noexcept { @@ -120,6 +48,11 @@ PrivateKey::PrivateKey(const Data& data, TWCurve curve) { } bytes = data; _curve = curve; + auto* privkey = Rust::tw_private_key_create_with_data(data.data(), data.size(), static_cast(curve)); + if (privkey == nullptr) { + throw std::invalid_argument("Invalid private key"); + } + _impl = Rust::wrapTWPrivateKey(privkey); } PrivateKey::PrivateKey( @@ -137,191 +70,37 @@ PrivateKey::PrivateKey( append(bytes, extension2); append(bytes, chainCode2); _curve = curve; -} - -PublicKey PrivateKey::getPublicKey(TWPublicKeyType type) const { - Data result; - switch (type) { - case TWPublicKeyTypeSECP256k1: - result.resize(PublicKey::secp256k1Size); - ecdsa_get_public_key33(&secp256k1, key().data(), result.data()); - break; - case TWPublicKeyTypeSECP256k1Extended: - result.resize(PublicKey::secp256k1ExtendedSize); - ecdsa_get_public_key65(&secp256k1, key().data(), result.data()); - break; - case TWPublicKeyTypeNIST256p1: - result.resize(PublicKey::secp256k1Size); - ecdsa_get_public_key33(&nist256p1, key().data(), result.data()); - break; - case TWPublicKeyTypeNIST256p1Extended: - result.resize(PublicKey::secp256k1ExtendedSize); - ecdsa_get_public_key65(&nist256p1, key().data(), result.data()); - break; - case TWPublicKeyTypeED25519: - result.resize(PublicKey::ed25519Size); - ed25519_publickey(key().data(), result.data()); - break; - case TWPublicKeyTypeED25519Blake2b: - result.resize(PublicKey::ed25519Size); - ed25519_publickey_blake2b(key().data(), result.data()); - break; - case TWPublicKeyTypeED25519Cardano: { - // must be double extended key - if (bytes.size() != cardanoKeySize) { - throw std::invalid_argument("Invalid extended key"); - } - Data pubKey(PublicKey::ed25519Size); - - // first key - ed25519_publickey_ext(key().data(), pubKey.data()); - append(result, pubKey); - // copy chainCode - append(result, chainCode()); - - // second key - ed25519_publickey_ext(secondKey().data(), pubKey.data()); - append(result, pubKey); - append(result, secondChainCode()); - } break; - - case TWPublicKeyTypeCURVE25519: { - result.resize(PublicKey::ed25519Size); - PublicKey ed25519PublicKey = getPublicKey(TWPublicKeyTypeED25519); - ed25519_pk_to_curve25519(result.data(), ed25519PublicKey.bytes.data()); - break; - } - - case TWPublicKeyTypeStarkex: { - result = rust_get_public_from_private(this->bytes, type); - break; - } + auto* privkey = Rust::tw_private_key_create_with_data(bytes.data(), bytes.size(), static_cast(curve)); + if (privkey == nullptr) { + throw std::invalid_argument("Invalid private key"); } - return PublicKey(result, type); + _impl = Rust::wrapTWPrivateKey(privkey); } -int ecdsa_sign_digest_checked(const ecdsa_curve* curve, const uint8_t* priv_key, const uint8_t* digest, size_t digest_size, uint8_t* sig, uint8_t* pby, int (*is_canonical)(uint8_t by, uint8_t sig[64])) { - if (digest_size < 32) { - return -1; +PublicKey PrivateKey::getPublicKey(TWPublicKeyType type) const { + auto* pubkey = Rust::tw_private_key_get_public_key_by_type(_impl.get(), static_cast(type)); + if (pubkey == nullptr) { + return PublicKey(Data(), type); } - assert(digest_size >= 32); - return ecdsa_sign_digest(curve, priv_key, digest, sig, pby, is_canonical); + return PublicKey(Rust::wrapTWPublicKey(pubkey)); } Data PrivateKey::sign(const Data& digest) const { - Data result; - bool success = false; - switch (_curve) { - case TWCurveSECP256k1: { - result.resize(65); - success = ecdsa_sign_digest_checked(&secp256k1, key().data(), digest.data(), digest.size(), result.data(), result.data() + 64, nullptr) == 0; - } break; - case TWCurveED25519: { - result.resize(64); - ed25519_sign(digest.data(), digest.size(), key().data(), result.data()); - success = true; - } break; - case TWCurveED25519Blake2bNano: { - result.resize(64); - ed25519_sign_blake2b(digest.data(), digest.size(), key().data(), result.data()); - success = true; - } break; - case TWCurveED25519ExtendedCardano: { - result.resize(64); - ed25519_sign_ext(digest.data(), digest.size(), key().data(), extension().data(), result.data()); - success = true; - } break; - case TWCurveCurve25519: { - result.resize(64); - const auto publicKey = getPublicKey(TWPublicKeyTypeED25519); - ed25519_sign(digest.data(), digest.size(), key().data(), result.data()); - const auto sign_bit = publicKey.bytes[31] & 0x80; - result[63] = result[63] & 127; - result[63] |= sign_bit; - success = true; - } break; - case TWCurveNIST256p1: { - result.resize(65); - success = ecdsa_sign_digest_checked(&nist256p1, key().data(), digest.data(), digest.size(), result.data(), result.data() + 64, nullptr) == 0; - } break; - case TWCurveStarkex: { - result = rust_private_key_sign(key(), digest, _curve); - success = result.size() == 64; - } break; - case TWCurveNone: - default: - break; - } - - if (!success) { - return {}; - } - return result; + Rust::CByteArrayWrapper res = Rust::tw_private_key_sign(_impl.get(), digest.data(), digest.size()); + return res.data; } -Data PrivateKey::sign(const Data& digest, int (*canonicalChecker)(uint8_t by, uint8_t sig[64])) const { - Data result; - bool success = false; - switch (_curve) { - case TWCurveSECP256k1: { - result.resize(65); - success = ecdsa_sign_digest_checked(&secp256k1, key().data(), digest.data(), digest.size(), result.data() + 1, result.data(), canonicalChecker) == 0; - } break; - case TWCurveED25519: // not supported - case TWCurveED25519Blake2bNano: // not supported - case TWCurveED25519ExtendedCardano: // not supported - case TWCurveCurve25519: // not supported - break; - case TWCurveNIST256p1: { - result.resize(65); - success = ecdsa_sign_digest_checked(&nist256p1, key().data(), digest.data(), digest.size(), result.data() + 1, result.data(), canonicalChecker) == 0; - } break; - case TWCurveNone: - default: - break; - } - - if (!success) { - return {}; - } - - // graphene adds 31 to the recovery id - result[0] += 31; - return result; +Data PrivateKey::sign(const Data& digest, int (*canonicalChecker)(uint8_t by, const uint8_t sig[64])) const { + Rust::CByteArrayWrapper res = Rust::tw_private_key_sign_canonical(_impl.get(), digest.data(), digest.size(), canonicalChecker); + return res.data; } Data PrivateKey::signAsDER(const Data& digest) const { - if (_curve != TWCurveSECP256k1) { - throw std::invalid_argument("DER signature is only supported for SECP256k1"); - } - Data sig(64); - bool success = - ecdsa_sign_digest(&secp256k1, key().data(), digest.data(), sig.data(), nullptr, nullptr) == 0; - if (!success) { - return {}; - } - - Data resultBytes(72); - size_t size = ecdsa_sig_to_der(sig.data(), resultBytes.data()); - - auto result = Data{}; - std::copy(resultBytes.begin(), resultBytes.begin() + size, std::back_inserter(result)); - return result; -} - -Data PrivateKey::signZilliqa(const Data& message) const { - if (_curve != TWCurveSECP256k1) { - throw std::invalid_argument("Zilliqa signature is only supported for SECP256k1"); - } - Data sig(64); - bool success = zil_schnorr_sign(&secp256k1, key().data(), message.data(), static_cast(message.size()), sig.data()) == 0; - - if (!success) { - return {}; - } - return sig; + Rust::CByteArrayWrapper res = Rust::tw_private_key_sign_as_der(_impl.get(), digest.data(), digest.size()); + return res.data; } void PrivateKey::cleanup() { memzero(bytes.data(), bytes.size()); + _impl = nullptr; } diff --git a/src/PrivateKey.h b/src/PrivateKey.h index ccc6a1125ba..34f6a07d469 100644 --- a/src/PrivateKey.h +++ b/src/PrivateKey.h @@ -76,17 +76,13 @@ class PrivateKey { /// Signs a digest using the given ECDSA curve and prepends the recovery id (a la graphene) /// Only a sig that passes canonicalChecker is returned - Data sign(const Data& digest, int (*canonicalChecker)(uint8_t by, uint8_t sig[64])) const; + Data sign(const Data& digest, int (*canonicalChecker)(uint8_t by, const uint8_t sig[64])) const; /// Signs a digest using the given ECDSA curve. The result is encoded with /// DER. /// If constructed with a curve, an exception will be thrown if the curve does not match SECP256k1. Data signAsDER(const Data& digest) const; - /// Signs a digest using given ECDSA curve, returns Zilliqa schnorr signature - /// If constructed with a curve, an exception will be thrown if the curve does not match SECP256k1. - Data signZilliqa(const Data& message) const; - /// Cleanup contents (fill with 0s), called before destruction void cleanup(); @@ -94,6 +90,7 @@ class PrivateKey { TWCurve curve() const { return _curve; } private: TWCurve _curve; + std::shared_ptr _impl; }; } // namespace TW diff --git a/src/PublicKey.cpp b/src/PublicKey.cpp index d9789e6bc06..5be44c9bc78 100644 --- a/src/PublicKey.cpp +++ b/src/PublicKey.cpp @@ -6,15 +6,9 @@ #include "PrivateKey.h" #include "Data.h" #include "rust/bindgen/WalletCoreRSBindgen.h" +#include "rust/Wrapper.h" -#include -#include -#include -#include -#include -#include -#include -#include +#include #include @@ -23,29 +17,7 @@ namespace TW { /// Determines if a collection of bytes makes a valid public key of the /// given type. bool PublicKey::isValid(const Data& data, enum TWPublicKeyType type) { - const auto size = data.size(); - if (size == 0) { - return false; - } - switch (type) { - case TWPublicKeyTypeED25519: - return size == ed25519Size || (size == ed25519Size + 1 && data[0] == 0x01); - case TWPublicKeyTypeCURVE25519: - case TWPublicKeyTypeED25519Blake2b: - return size == ed25519Size; - case TWPublicKeyTypeED25519Cardano: - return size == cardanoKeySize; - case TWPublicKeyTypeSECP256k1: - case TWPublicKeyTypeNIST256p1: - return size == secp256k1Size && (data[0] == 0x02 || data[0] == 0x03); - case TWPublicKeyTypeSECP256k1Extended: - case TWPublicKeyTypeNIST256p1Extended: - return size == secp256k1ExtendedSize && data[0] == 0x04; - case TWPublicKeyTypeStarkex: - return size == starkexSize; - default: - return false; - } + return Rust::tw_public_key_is_valid(data.data(), data.size(), static_cast(type)); } /// Initializes a public key with a collection of bytes. @@ -56,79 +28,36 @@ PublicKey::PublicKey(const Data& data, enum TWPublicKeyType type) if (!isValid(data, type)) { throw std::invalid_argument("Invalid public key data"); } - switch (type) { - case TWPublicKeyTypeStarkex: - case TWPublicKeyTypeSECP256k1: - case TWPublicKeyTypeNIST256p1: - case TWPublicKeyTypeSECP256k1Extended: - case TWPublicKeyTypeNIST256p1Extended: - bytes.reserve(data.size()); - std::copy(std::begin(data), std::end(data), std::back_inserter(bytes)); - break; - - case TWPublicKeyTypeED25519: - case TWPublicKeyTypeCURVE25519: - bytes.reserve(ed25519Size); - if (data.size() == ed25519Size + 1) { - std::copy(std::begin(data) + 1, std::end(data), std::back_inserter(bytes)); - } else { - std::copy(std::begin(data), std::end(data), std::back_inserter(bytes)); - } - break; - case TWPublicKeyTypeED25519Blake2b: - bytes.reserve(ed25519Size); - assert(data.size() == ed25519Size); // ensured by isValid() above - std::copy(std::begin(data), std::end(data), std::back_inserter(bytes)); - break; - case TWPublicKeyTypeED25519Cardano: - bytes.reserve(cardanoKeySize); - std::copy(std::begin(data), std::end(data), std::back_inserter(bytes)); + auto* pubkey = Rust::tw_public_key_create_with_data(data.data(), data.size(), static_cast(type)); + if (pubkey == nullptr) { + throw std::invalid_argument("Invalid public key"); } + _impl = Rust::wrapTWPublicKey(pubkey); + Rust::CByteArrayWrapper pubkeyData = Rust::tw_public_key_data(_impl.get()); + bytes = pubkeyData.data; +} + +PublicKey::PublicKey(std::shared_ptr _impl) + : _impl(_impl) { + Rust::CByteArrayWrapper pubkeyData = Rust::tw_public_key_data(_impl.get()); + bytes = pubkeyData.data; + type = static_cast(Rust::tw_public_key_type(_impl.get())); } PublicKey PublicKey::compressed() const { - if (type != TWPublicKeyTypeSECP256k1Extended && type != TWPublicKeyTypeNIST256p1Extended) { + auto compressedPubKey = Rust::tw_public_key_compressed(_impl.get()); + if (compressedPubKey == nullptr) { return *this; } - - Data newBytes(secp256k1Size); - assert(bytes.size() >= 65); - newBytes[0] = 0x02 | (bytes[64] & 0x01); - - assert(type == TWPublicKeyTypeSECP256k1Extended || type == TWPublicKeyTypeNIST256p1Extended); - switch (type) { - case TWPublicKeyTypeSECP256k1Extended: - std::copy(bytes.begin() + 1, bytes.begin() + secp256k1Size, newBytes.begin() + 1); - return PublicKey(newBytes, TWPublicKeyTypeSECP256k1); - - case TWPublicKeyTypeNIST256p1Extended: - default: - std::copy(bytes.begin() + 1, bytes.begin() + secp256k1Size, newBytes.begin() + 1); - return PublicKey(newBytes, TWPublicKeyTypeNIST256p1); - } + return PublicKey(Rust::wrapTWPublicKey(compressedPubKey)); } PublicKey PublicKey::extended() const { - Data newBytes(secp256k1ExtendedSize); - switch (type) { - case TWPublicKeyTypeSECP256k1: - ecdsa_uncompress_pubkey(&secp256k1, bytes.data(), newBytes.data()); - return PublicKey(newBytes, TWPublicKeyTypeSECP256k1Extended); - case TWPublicKeyTypeSECP256k1Extended: - return *this; - case TWPublicKeyTypeNIST256p1: - ecdsa_uncompress_pubkey(&nist256p1, bytes.data(), newBytes.data()); - return PublicKey(newBytes, TWPublicKeyTypeNIST256p1Extended); - case TWPublicKeyTypeNIST256p1Extended: - return *this; - case TWPublicKeyTypeED25519: - case TWPublicKeyTypeCURVE25519: - case TWPublicKeyTypeED25519Blake2b: - case TWPublicKeyTypeED25519Cardano: - return *this; - default: + auto extendedPubKey = Rust::tw_public_key_extended(_impl.get()); + if (extendedPubKey == nullptr) { return *this; } + return PublicKey(Rust::wrapTWPublicKey(extendedPubKey)); } bool rust_public_key_verify(const Data& key, TWPublicKeyType type, const Data& sig, const Data& msgHash) { @@ -142,73 +71,11 @@ bool rust_public_key_verify(const Data& key, TWPublicKeyType type, const Data& s } bool PublicKey::verify(const Data& signature, const Data& message) const { - switch (type) { - case TWPublicKeyTypeSECP256k1: - case TWPublicKeyTypeSECP256k1Extended: - return ecdsa_verify_digest(&secp256k1, bytes.data(), signature.data(), message.data()) == 0; - case TWPublicKeyTypeNIST256p1: - case TWPublicKeyTypeNIST256p1Extended: - return ecdsa_verify_digest(&nist256p1, bytes.data(), signature.data(), message.data()) == 0; - case TWPublicKeyTypeED25519: - return ed25519_sign_open(message.data(), message.size(), bytes.data(), signature.data()) == 0; - case TWPublicKeyTypeED25519Blake2b: - return ed25519_sign_open_blake2b(message.data(), message.size(), bytes.data(), signature.data()) == 0; - case TWPublicKeyTypeED25519Cardano: { - const auto key = subData(bytes, 0, ed25519Size); - return ed25519_sign_open(message.data(), message.size(), key.data(), signature.data()) == 0; - } - case TWPublicKeyTypeCURVE25519: { - auto ed25519PublicKey = Data(); - ed25519PublicKey.resize(PublicKey::ed25519Size); - curve25519_pk_to_ed25519(ed25519PublicKey.data(), bytes.data()); - - ed25519PublicKey[31] &= 0x7F; - ed25519PublicKey[31] |= signature[63] & 0x80; - - // remove sign bit - auto verifyBuffer = Data(); - append(verifyBuffer, signature); - verifyBuffer[63] &= 127; - return ed25519_sign_open(message.data(), message.size(), ed25519PublicKey.data(), verifyBuffer.data()) == 0; - } - case TWPublicKeyTypeStarkex: - return rust_public_key_verify(bytes, type, signature, message); - default: - throw std::logic_error("Not yet implemented"); - } + return Rust::tw_public_key_verify(_impl.get(), signature.data(), signature.size(), message.data(), message.size()); } bool PublicKey::verifyAsDER(const Data& signature, const Data& message) const { - switch (type) { - case TWPublicKeyTypeSECP256k1: - case TWPublicKeyTypeSECP256k1Extended: { - Data sig(64); - int ret = ecdsa_sig_from_der(signature.data(), signature.size(), sig.data()); - if (ret) { - return false; - } - return ecdsa_verify_digest(&secp256k1, bytes.data(), sig.data(), message.data()) == 0; - } - - default: - return false; - } -} - -bool PublicKey::verifyZilliqa(const Data& signature, const Data& message) const { - switch (type) { - case TWPublicKeyTypeSECP256k1: - case TWPublicKeyTypeSECP256k1Extended: - return zil_schnorr_verify(&secp256k1, bytes.data(), signature.data(), message.data(), static_cast(message.size())) == 0; - case TWPublicKeyTypeNIST256p1: - case TWPublicKeyTypeNIST256p1Extended: - case TWPublicKeyTypeED25519: - case TWPublicKeyTypeED25519Blake2b: - case TWPublicKeyTypeED25519Cardano: - case TWPublicKeyTypeCURVE25519: - default: - return false; - } + return Rust::tw_public_key_verify_as_der(_impl.get(), signature.data(), signature.size(), message.data(), message.size()); } Data PublicKey::hash(const Data& prefix, Hash::Hasher hasher, bool skipTypeByte) const { @@ -223,20 +90,11 @@ Data PublicKey::hash(const Data& prefix, Hash::Hasher hasher, bool skipTypeByte) } PublicKey PublicKey::recoverRaw(const Data& signatureRS, byte recId, const Data& messageDigest) { - if (signatureRS.size() < 2 * PrivateKey::_size) { - throw std::invalid_argument("signature too short"); - } - if (recId >= 4) { - throw std::invalid_argument("Invalid recId (>=4)"); - } - if (messageDigest.size() < PrivateKey::_size) { - throw std::invalid_argument("digest too short"); - } - TW::Data result(secp256k1SignatureSize); - if (auto ret = ecdsa_recover_pub_from_sig(&secp256k1, result.data(), signatureRS.data(), messageDigest.data(), recId); ret != 0) { - throw std::invalid_argument("recover failed " + std::to_string(ret)); + auto* pubkey = Rust::tw_public_key_recover_from_signature(signatureRS.data(), signatureRS.size(), messageDigest.data(), messageDigest.size(), recId); + if (pubkey == nullptr) { + throw std::invalid_argument("Recover failed"); } - return PublicKey(result, TWPublicKeyTypeSECP256k1Extended); + return PublicKey(Rust::wrapTWPublicKey(pubkey)); } PublicKey PublicKey::recover(const Data& signature, const Data& messageDigest) { @@ -255,9 +113,12 @@ bool PublicKey::isValidED25519() const { if (type != TWPublicKeyTypeED25519) { return false; } - assert(bytes.size() == ed25519Size); - ge25519 r; - return ge25519_unpack_negative_vartime(&r, bytes.data()) != 0; + return _impl != nullptr; +} + +void PublicKey::cleanup() { + memzero(bytes.data(), bytes.size()); + _impl = nullptr; } } // namespace TW diff --git a/src/PublicKey.h b/src/PublicKey.h index 6a1e3d17855..5304d614d45 100644 --- a/src/PublicKey.h +++ b/src/PublicKey.h @@ -8,6 +8,7 @@ #include "Hash.h" #include +#include "rust/Wrapper.h" #include #include @@ -55,6 +56,11 @@ class PublicKey { /// \throws std::invalid_argument if the data is not a valid public key. explicit PublicKey(const Data& data, enum TWPublicKeyType type); + /// Initializes a public key with a Rust implementation. + explicit PublicKey(std::shared_ptr impl); + + virtual ~PublicKey() { cleanup(); } + /// Determines if this is a compressed public key. bool isCompressed() const { return type != TWPublicKeyTypeSECP256k1Extended && type != TWPublicKeyTypeNIST256p1Extended; @@ -71,10 +77,7 @@ class PublicKey { /// Verifies a signature in DER format. bool verifyAsDER(const Data& signature, const Data& message) const; - - /// Verifies a Zilliqa schnorr signature for the provided message. - bool verifyZilliqa(const Data& signature, const Data& message) const; - + /// Computes the public key hash. /// /// The public key hash is computed by applying the hasher to the public key @@ -97,6 +100,11 @@ class PublicKey { /// Check if this key makes a valid ED25519 key (it is on the curve) bool isValidED25519() const; + + /// Cleanup contents (fill with 0s), called before destruction + void cleanup(); +private: + std::shared_ptr _impl; }; inline bool operator==(const PublicKey& lhs, const PublicKey& rhs) { diff --git a/src/Zilliqa/Signer.cpp b/src/Zilliqa/Signer.cpp index 6db4930d251..cb6c295fb28 100644 --- a/src/Zilliqa/Signer.cpp +++ b/src/Zilliqa/Signer.cpp @@ -91,9 +91,9 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto output = Proto::SigningOutput(); Address address; const auto preImage = Signer::getPreImage(input, address); - const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1); - const auto pubKey = key.getPublicKey(TWPublicKeyTypeSECP256k1); - const auto signature = key.signZilliqa(preImage); + const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveZILLIQASchnorr); + const auto pubKey = key.getPublicKey(TWPublicKeyTypeZILLIQASchnorr); + const auto signature = key.sign(preImage); const auto transaction = input.transaction(); // build json diff --git a/src/interface/TWHDWallet.cpp b/src/interface/TWHDWallet.cpp index bce0e819a6b..da690924693 100644 --- a/src/interface/TWHDWallet.cpp +++ b/src/interface/TWHDWallet.cpp @@ -60,7 +60,10 @@ TWData *_Nonnull TWHDWalletEntropy(struct TWHDWallet *_Nonnull wallet) { return TWDataCreateWithBytes(wallet->impl.getEntropy().data(), wallet->impl.getEntropy().size()); } -struct TWPrivateKey *_Nonnull TWHDWalletGetMasterKey(struct TWHDWallet *_Nonnull wallet, TWCurve curve) { +struct TWPrivateKey *_Nullable TWHDWalletGetMasterKey(struct TWHDWallet *_Nonnull wallet, TWCurve curve) { + if (curve == TWCurveED25519ExtendedCardano) { + return nullptr; + } return new TWPrivateKey{ wallet->impl.getMasterKey(curve) }; } diff --git a/src/interface/TWPrivateKey.cpp b/src/interface/TWPrivateKey.cpp index a0dac25a551..9ca55d6b419 100644 --- a/src/interface/TWPrivateKey.cpp +++ b/src/interface/TWPrivateKey.cpp @@ -88,6 +88,14 @@ struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyCurve25519(struct TWPrivate return TWPrivateKeyGetPublicKeyByType(pk, TWPublicKeyTypeCURVE25519); } +struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeySchnorr(struct TWPrivateKey *_Nonnull pk) { + return TWPrivateKeyGetPublicKeyByType(pk, TWPublicKeyTypeSchnorr); +} + +struct TWPublicKey *_Nonnull TWPrivateKeyGetPublicKeyZilliqaSchnorr(struct TWPrivateKey *_Nonnull pk) { + return TWPrivateKeyGetPublicKeyByType(pk, TWPublicKeyTypeZILLIQASchnorr); +} + TWData *TWPrivateKeySign(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull digest) { const auto& d = *reinterpret_cast(digest); auto result = pk->impl.sign(d); @@ -108,17 +116,6 @@ TWData *TWPrivateKeySignAsDER(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull } } -TWData *TWPrivateKeySignZilliqaSchnorr(struct TWPrivateKey *_Nonnull pk, TWData *_Nonnull message) { - const auto& msg = *reinterpret_cast(message); - auto result = pk->impl.signZilliqa(msg); - - if (result.empty()) { - return nullptr; - } else { - return TWDataCreateWithBytes(result.data(), result.size()); - } -} - struct TWPublicKey* TWPrivateKeyGetPublicKey(struct TWPrivateKey* pk, enum TWCoinType coinType) { return TWPrivateKeyGetPublicKeyByType(pk, TWCoinTypePublicKeyType(coinType)); } diff --git a/src/interface/TWPublicKey.cpp b/src/interface/TWPublicKey.cpp index 6459b947a05..913504b1f0b 100644 --- a/src/interface/TWPublicKey.cpp +++ b/src/interface/TWPublicKey.cpp @@ -58,12 +58,6 @@ bool TWPublicKeyVerifyAsDER(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull si return pk->impl.verifyAsDER(s, m); } -bool TWPublicKeyVerifyZilliqaSchnorr(struct TWPublicKey *_Nonnull pk, TWData *_Nonnull signature, TWData *_Nonnull message) { - const auto& s = *reinterpret_cast(signature); - const auto& m = *reinterpret_cast(message); - return pk->impl.verifyZilliqa(s, m); -} - enum TWPublicKeyType TWPublicKeyKeyType(struct TWPublicKey *_Nonnull publicKey) { return publicKey->impl.type; } diff --git a/swift/Tests/Blockchains/NEOTests.swift b/swift/Tests/Blockchains/NEOTests.swift index 3270ba1e314..cf020762b0b 100644 --- a/swift/Tests/Blockchains/NEOTests.swift +++ b/swift/Tests/Blockchains/NEOTests.swift @@ -94,11 +94,7 @@ class NEOTests: XCTestCase { // https://testnet-explorer.o3.network/transactions/0x7b138c753c24f474d0f70af30a9d79756e0ee9c1f38c12ed07fbdf6fc5132eaf - XCTAssertEqual("8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf122956bc88746dc666759a2d67f120fe3ce1659f916d22a91e0b02421d3bddbd1232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac", + XCTAssertEqual("8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf1dd6a943678b9239a98a65d2980edf01beed0a0b4904573f31309a6a128a54980232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac", result) - - // TODO uncomment when nist256p1 Rust implementation is enabled. - // XCTAssertEqual("8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf1dd6a943678b9239a98a65d2980edf01beed0a0b4904573f31309a6a128a54980232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac", - // result) } } diff --git a/swift/Tests/Blockchains/OntologyTests.swift b/swift/Tests/Blockchains/OntologyTests.swift index a81c6744980..cdf1b2adda1 100644 --- a/swift/Tests/Blockchains/OntologyTests.swift +++ b/swift/Tests/Blockchains/OntologyTests.swift @@ -52,10 +52,7 @@ class OntologyTests: XCTestCase { let output: OntologySigningOutput = AnySigner.sign(input: input, coin: .ontology) let result = output.encoded.hexString - XCTAssertEqual("00d102d45c8bf401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140301766d925382a6ebb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb7cff28ddaf7f1048822c0ca21a0c4926323a2497875b963f3b8cbd3717aa6e7c2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac414038466b25ac49a22ba8c301328ef049a61711b257987e85e25d63e0444a14e860305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", result) - - // TODO uncomment when nist256p1 Rust implementation is enabled. - // XCTAssertEqual("00d102d45c8bf401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140301766d925382a6ebb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb8300d7215080efb87dd3f35de5f3b6d98aacd6161fbc0845b82d0d8be4b8b6d52321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac414038466b25ac49a22ba8c301328ef049a61711b257987e85e25d63e0444a14e860305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", result) + XCTAssertEqual("00d102d45c8bf401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140301766d925382a6ebb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb8300d7215080efb87dd3f35de5f3b6d98aacd6161fbc0845b82d0d8be4b8b6d52321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac414038466b25ac49a22ba8c301328ef049a61711b257987e85e25d63e0444a14e860305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", result) } func testSignOngTransfer() { @@ -75,10 +72,7 @@ class OntologyTests: XCTestCase { let output: OntologySigningOutput = AnySigner.sign(input: input, coin: .ontology) let result = output.encoded.hexString - XCTAssertEqual("00d19d3182a8f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140e27e935b87855efad62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f73b8ab199a4d757b4c7b9ed46c4ff8cfa8aefaa90b7fb6485e358034448cba752321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac4140450047b2efb384129a16ec4c707790e9379b978cc7085170071d8d7c5c037d743b078bd4e21bb4404c0182a32ee05260e22454dffb34dacccf458dfbee6d32db232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", result) - - // TODO uncomment when nist256p1 Rust implementation is enabled. - // XCTAssertEqual("00d19d3182a8f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140e27e935b87855efad62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f8c4754e565b28a85b384612b93b00730143800049b97e83c95844a8eb7d66adc2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac4140450047b2efb384129a16ec4c707790e9379b978cc7085170071d8d7c5c037d74c4f8742a1de44bc0b3fe7d5cd11fad9edac2a5cdabe2c3b824743cc70df5f276232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", result) + XCTAssertEqual("00d19d3182a8f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df67100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94abac41aaf3ead76586a7cc8516a7cc86c51c1087472616e736665721400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140e27e935b87855efad62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f8c4754e565b28a85b384612b93b00730143800049b97e83c95844a8eb7d66adc2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac4140450047b2efb384129a16ec4c707790e9379b978cc7085170071d8d7c5c037d74c4f8742a1de44bc0b3fe7d5cd11fad9edac2a5cdabe2c3b824743cc70df5f276232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", result) } } diff --git a/swift/Tests/HDWalletTests.swift b/swift/Tests/HDWalletTests.swift index 5a1abbff378..9a3fe8541e2 100644 --- a/swift/Tests/HDWalletTests.swift +++ b/swift/Tests/HDWalletTests.swift @@ -75,7 +75,7 @@ class HDWalletTests: XCTestCase { func testMasterKey() { let wallet = HDWallet(mnemonic: "tiny escape drive pupil flavor endless love walk gadget match filter luxury", passphrase: "")! XCTAssertEqual(wallet.seed.hexString, "d430216f5b506dfd281d6ff6e92150d205868923df00774bc301e5ffdc2f4d1ad38a602017ddea6fc7d6315345d8b9cadbd8213ed2ffce5dfc550fa918665eb8") - let masterKey = wallet.getMasterKey(curve: Curve.secp256k1) + let masterKey = wallet.getMasterKey(curve: Curve.secp256k1)! XCTAssertEqual(masterKey.data.hexString, "e120fc1ef9d193a851926ebd937c3985dc2c4e642fb3d0832317884d5f18f3b3") } diff --git a/swift/Tests/PrivateKeyTests.swift b/swift/Tests/PrivateKeyTests.swift index 5219223483a..4d6a5050821 100644 --- a/swift/Tests/PrivateKeyTests.swift +++ b/swift/Tests/PrivateKeyTests.swift @@ -50,13 +50,13 @@ class PrivateKeyTests: XCTestCase { } func testSignSchnorr() { - let privateKey = PrivateKey(data: Data(hexString: "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")!, curve: .secp256k1)! - let publicKey = privateKey.getPublicKeySecp256k1(compressed: true) + let privateKey = PrivateKey(data: Data(hexString: "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5")!, curve: .zilliqaschnorr)! + let publicKey = privateKey.getPublicKeyZilliqaSchnorr() let message = "hello schnorr".data(using: .utf8)! - let sig = privateKey.signZilliqaSchnorr(message: message)! - let verified = publicKey.verifyZilliqaSchnorr(signature: sig, message: message) + let sig = privateKey.sign(digest: message)! + let verified = publicKey.verify(signature: sig, message: message) XCTAssertEqual(sig.hexString, "d166b1ae7892c5ef541461dc12a50214d0681b63d8037cda29a3fe6af8bb973e4ea94624d85bc0010bdc1b38d05198328fae21254adc2bf5feaf2804d54dba55") XCTAssertTrue(verified) diff --git a/tests/chains/Cardano/AddressTests.cpp b/tests/chains/Cardano/AddressTests.cpp index d2abeb123bb..8c042b46543 100644 --- a/tests/chains/Cardano/AddressTests.cpp +++ b/tests/chains/Cardano/AddressTests.cpp @@ -144,16 +144,6 @@ TEST(CardanoAddress, MnemonicToAddressV3) { // check entropy EXPECT_EQ("30a6f50aeb58ff7699b822d63e0ef27aeff17d9f", hex(wallet.getEntropy())); - { - PrivateKey masterPrivKey = wallet.getMasterKey(TWCurve::TWCurveED25519ExtendedCardano); - PrivateKey masterPrivKeyExt = wallet.getMasterKeyExtension(TWCurve::TWCurveED25519ExtendedCardano); - // the two together matches first half of keypair - ASSERT_EQ("a018cd746e128a0be0782b228c275473205445c33b9000a33dd5668b430b5744", hex(masterPrivKey.bytes)); - ASSERT_EQ("26877cfe435fddda02409b839b7386f3738f10a30b95a225f4b720ee71d2505b", hex(masterPrivKeyExt.bytes)); - - PublicKey masterPublicKey = masterPrivKey.getPublicKey(TWPublicKeyTypeED25519); - ASSERT_EQ("3aecb95953edd0b16db20366097ddedcb3512fe36193473c5fca2af774d44739", hex(masterPublicKey.bytes)); - } { string addr = wallet.deriveAddress(TWCoinType::TWCoinTypeCardano); EXPECT_EQ("addr1qxxe304qg9py8hyyqu8evfj4wln7dnms943wsugpdzzsxnkvvjljtzuwxvx0pnwelkcruy95ujkq3aw6rl0vvg32x35qc92xkq", addr); @@ -320,13 +310,11 @@ TEST(CardanoAddress, FromDataV3_Base) { TEST(CardanoAddress, FromPrivateKeyV3) { { // from cardano-crypto.js test - auto privateKey = PrivateKey( + EXPECT_ANY_THROW(PrivateKey( parse_hex("d809b1b4b4c74734037f76aace501730a3fe2fca30b5102df99ad3f7c0103e48"), parse_hex("d54cde47e9041b31f3e6873d700d83f7a937bea746dadfa2c5b0a6a92502356c"), parse_hex("69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000"), - dummyKey, dummyKey, dummyKey, TWCurveED25519); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); - EXPECT_ANY_THROW(new AddressV3(publicKey)); + dummyKey, dummyKey, dummyKey, TWCurveED25519)); } } @@ -456,13 +444,11 @@ TEST(CardanoAddress, FromPrivateKeyV2) { } { // from cardano-crypto.js test - auto privateKey = PrivateKey( + EXPECT_ANY_THROW(PrivateKey( parse_hex("d809b1b4b4c74734037f76aace501730a3fe2fca30b5102df99ad3f7c0103e48"), parse_hex("d54cde47e9041b31f3e6873d700d83f7a937bea746dadfa2c5b0a6a92502356c"), parse_hex("69272d81c376382b8a87c21370a7ae9618df8da708d1a9490939ec54ebe43000"), - dummyKey, dummyKey, dummyKey, TWCurveED25519); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); - EXPECT_ANY_THROW(new AddressV2(publicKey)); + dummyKey, dummyKey, dummyKey, TWCurveED25519)); } } diff --git a/tests/chains/EOS/TransactionTests.cpp b/tests/chains/EOS/TransactionTests.cpp index 2f3bc7a350a..8cb6bb50017 100644 --- a/tests/chains/EOS/TransactionTests.cpp +++ b/tests/chains/EOS/TransactionTests.cpp @@ -24,9 +24,9 @@ static std::string k1Sigs[5]{ static std::string r1Sigs[5]{ "SIG_R1_KC4qBxibZpXLESzN37F46Jv8w7dQtyPDeRmeFs8Z4DmEFhr3FACLkjcbSLkVN7acBt4eb6zUa9N76UfJncr4htxCSe7huZ", - "SIG_R1_KaWNQefJReykjKUsS51caChJRgeywUoeuucFReyKY1SPNveSoFFVgxT3jEzW56ZLtpN7qGgekoSNsKs1BzzyZ9snhCALcG", - "SIG_R1_KarN7JJxHeKgRJLmscWzCsdDpfdktWrBGJLvVFN7RYZpSeNHBsqNV7dKqxkvKtbhsqHukLKw1EQNTjcUcxUD6hUTVvNWcP", - "SIG_R1_KvHdcwEDW8RQWEPTA3BoK9RVZqtAwKqVvYQN9Wz44XUrzjrNyRkpc7vguqc8q6FLMJBUUen59hyXM3BuLvvrp2x4S6m1o8", + "SIG_R1_K11q2EFGPGXwAXMxFSoDUeEpSvXAdxLcHe9mFkQtJTZNBmaGtgKGvTAPN9eFg91Ub7Jf7tCuPkakQbGWGwE6KjA2Eez1zR", + "SIG_R1_K1MpistvFFsrrWDrgzJb7KAjquVwazP8e2tSKLogBzgoGp65dDhzf4unVunQnzQMX92ZA9CdRV8TdDRmjW9o2pdaG62G4P", + "SIG_R1_KLo6EWpBTjyawSGXyQyQDay1b5kMdoNTJGwsycRcpybqpwjhTntLi6XZpZWbu3P6JF9XmEBqDPPXhwJNGnu9YJ7x8Deuwd", "SIG_R1_KZB6ivprUS1zwGxMxZQJ7UvWk4Tpvoo6WiFKUPJuUHj4Es39ejiFVoD7ZB6MfSJxAaRPvnAF39hnApwzFAM8Erxmj3Suvm"}; TEST(EOSTransaction, Serialization) { diff --git a/tests/chains/NEO/AddressTests.cpp b/tests/chains/NEO/AddressTests.cpp index 4a6ff91f007..17dc11b03df 100644 --- a/tests/chains/NEO/AddressTests.cpp +++ b/tests/chains/NEO/AddressTests.cpp @@ -15,7 +15,7 @@ using namespace TW; namespace TW::NEO::tests { TEST(NEOAddress, FromPublicKey) { - const auto publicKey = PublicKey(parse_hex("0222b2277d039d67f4197a638dd5a1d99c290b17aa8c4a16ccee5165fe612de66a"), TWPublicKeyTypeSECP256k1); + const auto publicKey = PublicKey(parse_hex("0222b2277d039d67f4197a638dd5a1d99c290b17aa8c4a16ccee5165fe612de66a"), TWPublicKeyTypeNIST256p1); const auto address = Address(publicKey); EXPECT_EQ(string("AKmrAHRD9ZDUnu4m3vWWonpsojo4vgSuqp"), address.string()); } diff --git a/tests/chains/NEO/SignerTests.cpp b/tests/chains/NEO/SignerTests.cpp index e47d188cfef..e53f0a4f92d 100644 --- a/tests/chains/NEO/SignerTests.cpp +++ b/tests/chains/NEO/SignerTests.cpp @@ -81,9 +81,7 @@ TEST(NEOSigner, SigningTransaction) { signer.sign(transaction); auto signedTx = transaction.serialize(); - EXPECT_EQ(hex(signedTx), "800000019c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de0100029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500fcbbc414000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac0141405046619c8e20e1fdeec92ce95f3019f6e7cc057294eb16b2d5e55c105bf32eb27e1fc01c1858576228f1fef8c0945a8ad69688e52a4ed19f5b85f5eff7e961d7232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); - // TODO uncomment when nist256p1 Rust implementation is enabled. - // EXPECT_EQ(hex(signedTx), "800000019c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de0100029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500fcbbc414000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac0141405046619c8e20e1fdeec92ce95f3019f6e7cc057294eb16b2d5e55c105bf32eb281e03fe2e7a7a89ed70e01073f6ba574e65071c87cc8cce59833d4d30479c37a232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); + EXPECT_EQ(hex(signedTx), "800000019c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de0100029b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500fcbbc414000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac0141405046619c8e20e1fdeec92ce95f3019f6e7cc057294eb16b2d5e55c105bf32eb281e03fe2e7a7a89ed70e01073f6ba574e65071c87cc8cce59833d4d30479c37a232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); } } // namespace TW::NEO::tests diff --git a/tests/chains/NEO/TWAnySignerTests.cpp b/tests/chains/NEO/TWAnySignerTests.cpp index e600d17ad3f..461912180ac 100644 --- a/tests/chains/NEO/TWAnySignerTests.cpp +++ b/tests/chains/NEO/TWAnySignerTests.cpp @@ -166,9 +166,7 @@ TEST(TWAnySignerNEO, Sign) { ANY_SIGN(input, TWCoinTypeNEO); // https://testnet-explorer.o3.network/transactions/0x7b138c753c24f474d0f70af30a9d79756e0ee9c1f38c12ed07fbdf6fc5132eaf - ASSERT_EQ(hex(output.encoded()), "8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf122956bc88746dc666759a2d67f120fe3ce1659f916d22a91e0b02421d3bddbd1232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); - // TODO uncomment when nist256p1 Rust implementation is enabled. - // ASSERT_EQ(hex(output.encoded()), "8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf1dd6a943678b9239a98a65d2980edf01beed0a0b4904573f31309a6a128a54980232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); + ASSERT_EQ(hex(output.encoded()), "8000001efb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00039b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50083064905000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ace72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c605013cf0617000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac014140dc261ac093a87640441bf0c3ad4a55ec727932b9175f600618bb5275f31aacf1dd6a943678b9239a98a65d2980edf01beed0a0b4904573f31309a6a128a54980232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); } TEST(TWAnySignerNEO, Plan) { @@ -186,10 +184,8 @@ TEST(TWAnySignerNEO, SignMultiOutput) { Proto::SigningInput input = createInputWithMultiOutput(); Proto::SigningOutput output; ANY_SIGN(input, TWCoinTypeNEO); - - ASSERT_EQ(hex(output.encoded()), "80000023fb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00e05bdf39f3b8c377a9801cb3eaa38eefe8abdd176642d5f2aab3b8217588a7e50000ebf6778b3fc3523ebc0acdbdeb29202135737ac0ac47cd814da0d63cdde955140000eeadb902cc55ac4954c1f9551ae6695c2364e0dfd0013857b5115208601271da0000ef7b431058f36524a668a7497c88ad45cc8b2c5b20891ad53d1071d3fe6c48040000f49e21b21a0c87edad0d96673223f47ad3560490613510650edb42a45570f2a50000049b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500c2eb0b00000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5e069f3d21c000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac01414057b786872d667a637ff44412e3ccf50933a9c30609016ecbcaec8b9d80f2b0e26eb0cf111674ff0802a42357671867b11c334807c40146419825eed8c45a6eed232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); - // TODO uncomment when nist256p1 Rust implementation is enabled. - // ASSERT_EQ(hex(output.encoded()), "80000023fb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00e05bdf39f3b8c377a9801cb3eaa38eefe8abdd176642d5f2aab3b8217588a7e50000ebf6778b3fc3523ebc0acdbdeb29202135737ac0ac47cd814da0d63cdde955140000eeadb902cc55ac4954c1f9551ae6695c2364e0dfd0013857b5115208601271da0000ef7b431058f36524a668a7497c88ad45cc8b2c5b20891ad53d1071d3fe6c48040000f49e21b21a0c87edad0d96673223f47ad3560490613510650edb42a45570f2a50000049b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500c2eb0b00000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5e069f3d21c000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac01414057b786872d667a637ff44412e3ccf50933a9c30609016ecbcaec8b9d80f2b0e2914f30ede98b00f8fd5bdca898e7984ea0b3b2a5e31658435b93dbea3808b664232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); + + ASSERT_EQ(hex(output.encoded()), "80000023fb50cb3be3e08917b308a1dbdb2408109394560ec67518af43035d8c260815c601000bd791a26120eef181d8162bd6cb7495dee1299aa67bb796dcd4a03769f9b24e00000bea299e6a243c9379c3e8884c9176b1456b3017611772b2fadc55d10901ee3f000026c413526bbd45cca355683db9f39d6864a7e298f481f2cdeefe8b578ccea96e00002b2647616d4f4143700f8e862aa8427efd7fa9998fe040e23ed877d2cbd35af700003159b899275e2f0e0b1314acddc7e1ec5948598fca40a9733e2b448fe9344705000036509c8a487005aa8e16663613d2d767461ee2f8dc4f678cc22f9148d4420c8b0000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040100385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040200385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040300385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040400385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040500385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040600385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040700385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040800385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f04090040ec871088beb680f5b149767dbb0b8ac7ec1a1c5836e177606b6200e6bc83cf00004e393bd89d886ae116ed9e6b49be427b21f7247d896923265e68dfa82b57d79b00005b99bf2caacf20bfc9cd51b3d3472499383c803c2d781d00f1e2dd970325eeb4000062ac42685ef8b856291bb0264fcb767b00706a15243326775f61a159a29c01e100006f011d435ef43c066689d1222f4eada1d4590ebaaa190960ac26a5acf29d37bd00007dea63ea47a6c9e8318f3b19a0df5ccb3a348f54a176736afa7b9b3b843f4c160000925e50254e8056bfd540f3d45f171dbab504f5b191070ee7af1e16764ac7ce4a00009677a6869128961a1a3b17e609e915d2d9a29ceaab5689dccb841ca729665c8900009692e4e512eb2e04b10042bcc28910140b2229ede40291b0e1a0c3c44381825400009dc6c119d0f4bacb1b1e9faffcba33581729c1915a2f1147ce7a6fc8abe4455300009f6b635afee02b5db0c93a5b1bfcace34a18c78d76c73b7bf90d21d4d0193ec80000b11bbb613e36b2bcc6c3a76c888c6c139957a1b7091dab26ce88b65c3fb056340000385bfb24fe7f6f5dd28e9836e8220c8ac766efcc4e04082bd9d982ccd6738f040a00e05bdf39f3b8c377a9801cb3eaa38eefe8abdd176642d5f2aab3b8217588a7e50000ebf6778b3fc3523ebc0acdbdeb29202135737ac0ac47cd814da0d63cdde955140000eeadb902cc55ac4954c1f9551ae6695c2364e0dfd0013857b5115208601271da0000ef7b431058f36524a668a7497c88ad45cc8b2c5b20891ad53d1071d3fe6c48040000f49e21b21a0c87edad0d96673223f47ad3560490613510650edb42a45570f2a50000049b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500ba1dd205000000ea610aa6db39bd8c8556c9569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500c2eb0b00000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5e069f3d21c000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac01414057b786872d667a637ff44412e3ccf50933a9c30609016ecbcaec8b9d80f2b0e2914f30ede98b00f8fd5bdca898e7984ea0b3b2a5e31658435b93dbea3808b664232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"); } TEST(TWAnySignerNEO, PlanMultiOutput) { diff --git a/tests/chains/NEO/TransactionCompilerTests.cpp b/tests/chains/NEO/TransactionCompilerTests.cpp index 39e97ef60b6..3a00568449b 100644 --- a/tests/chains/NEO/TransactionCompilerTests.cpp +++ b/tests/chains/NEO/TransactionCompilerTests.cpp @@ -71,32 +71,19 @@ TEST(NEOCompiler, CompileWithSignatures) { // Simulate signature, normally obtained from signature server const auto publicKeyData = publicKey.bytes; const auto signature = - parse_hex("5046619c8e20e1fdeec92ce95f3019f6e7cc057294eb16b2d5e55c105bf32eb27e1fc01c18585762" - "28f1fef8c0945a8ad69688e52a4ed19f5b85f5eff7e961d7"); - // TODO uncomment when nist256p1 Rust implementation is enabled. - // const auto signature = - // parse_hex("5046619c8e20e1fdeec92ce95f3019f6e7cc057294eb16b2d5e55c105bf32eb281e03fe2e7a7a89e" - // "d70e01073f6ba574e65071c87cc8cce59833d4d30479c37a"); + parse_hex("5046619c8e20e1fdeec92ce95f3019f6e7cc057294eb16b2d5e55c105bf32eb281e03fe2e7a7a89e" + "d70e01073f6ba574e65071c87cc8cce59833d4d30479c37a"); // Verify signature (pubkey & hash & signature) EXPECT_TRUE(publicKey.verify(signature, TW::data(preImageHash))); - /// Step 3: Compile transaction info auto expectedTx = "800000019c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de0100029b7cffdaa674" "beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000ea610aa6db39bd8c8556c9" "569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500fcbbc4" "14000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac0141405046619c8e20e1fdeec92ce95f3019f6e7cc" - "057294eb16b2d5e55c105bf32eb27e1fc01c1858576228f1fef8c0945a8ad69688e52a4ed19f5b85f5eff7e961" - "d7232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"; - // TODO uncomment when nist256p1 Rust implementation is enabled. - // auto expectedTx = - // "800000019c85b39cd5677e2bfd6bf8a711e8da93a2f1d172b2a52c6ca87757a4bccc24de0100029b7cffdaa674" - // "beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500e1f50500000000ea610aa6db39bd8c8556c9" - // "569d94b5e5a5d0ad199b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc500fcbbc4" - // "14000000f2908c7efc0c9e43ffa7e79170ba37e501e1b4ac0141405046619c8e20e1fdeec92ce95f3019f6e7cc" - // "057294eb16b2d5e55c105bf32eb281e03fe2e7a7a89ed70e01073f6ba574e65071c87cc8cce59833d4d30479c3" - // "7a232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"; + "057294eb16b2d5e55c105bf32eb281e03fe2e7a7a89ed70e01073f6ba574e65071c87cc8cce59833d4d30479c3" + "7a232102a41c2aea8568864b106553729d32b1317ec463aa23e7a3521455d95992e17a7aac"; auto outputData = TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKeyData}); diff --git a/tests/chains/Ontology/Oep4Tests.cpp b/tests/chains/Ontology/Oep4Tests.cpp index 5228c46a43a..7ebf934c1af 100644 --- a/tests/chains/Ontology/Oep4Tests.cpp +++ b/tests/chains/Ontology/Oep4Tests.cpp @@ -108,9 +108,7 @@ TEST(OntologyOep4, transfer) { auto rawTx = hex(rawTxBytes); // Transaction Hex tab // https://explorer.ont.io/testnet/tx/710266b8d497e794ecd47e01e269e4aeb6f4ff2b01eaeafc4cd371e062b13757 - EXPECT_EQ("00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e9001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff00024140bd2923854d7b84b97a107bb3cddf18c8e3dddd2f36b41a1f5f5b23366484daa22871cfb819923fe01e9cb1e9ed16baa2b05c2feb76bcbe2ec125f72701c5e965232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41406d638653597774ce45812ea2653250806b657b32b7c6ad3e027ddeba91e9a9da4bb5dacd23dafba868cb31bacb38b4a6ff2607682a426c1dc09b05a1e158d6cd2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac", rawTx); - // TODO uncomment when nist256p1 Rust implementation is enabled. - // EXPECT_EQ(rawTx, "00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e9001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff00024140bd2923854d7b84b97a107bb3cddf18c8e3dddd2f36b41a1f5f5b23366484daa2d78e3046e66dc020e1634e1612e9455d0c8acac2305ae0563293d39bfa9d3bec232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41406d638653597774ce45812ea2653250806b657b32b7c6ad3e027ddeba91e9a9dab44a2531dc2504589734ce4534c74b58bdc0f3457cd53267331ec5211b0a4e842321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac"); + EXPECT_EQ(rawTx, "00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e9001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff00024140bd2923854d7b84b97a107bb3cddf18c8e3dddd2f36b41a1f5f5b23366484daa2d78e3046e66dc020e1634e1612e9455d0c8acac2305ae0563293d39bfa9d3bec232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41406d638653597774ce45812ea2653250806b657b32b7c6ad3e027ddeba91e9a9dab44a2531dc2504589734ce4534c74b58bdc0f3457cd53267331ec5211b0a4e842321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac"); } TEST(OntologyOep4, transferMainnet) { diff --git a/tests/chains/Ontology/OngTests.cpp b/tests/chains/Ontology/OngTests.cpp index 4b5cb6f1f78..e06bf3f6a02 100644 --- a/tests/chains/Ontology/OngTests.cpp +++ b/tests/chains/Ontology/OngTests.cpp @@ -53,24 +53,12 @@ TEST(OntologyOng, transfer) { "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" "00000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140ac3edf2d00540f9c" - "2f3b24878936b409c995c425ab5edf247c5b0d812a50df293ff63e173bac71a6cd0772ff78415c46ac64" - "f625cbc06fe90ccdecf9a94319c42321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" + "2f3b24878936b409c995c425ab5edf247c5b0d812a50df29c009c1e7c4538e5a32f88d0087bea3b91082" + "0487db572e9be6ebddc953200b8d2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" "47125f927b7486ac41406fea9f12b125d7f65a94774e765a796428b3c6c4c46b0470624b9a1cef4ff420" - "488828f308c263b35287363e51add8cd49136eb57a397c6ade95df80d9a16282232103d9fd62df332403" + "b777d70bf73d9c4dad78c9c1ae52273273d38bf82cde221a1523eb4222c1c2cf232103d9fd62df332403" "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", rawTxHex); - - // TODO uncomment when nist256p1 Rust implementation is enabled. - // EXPECT_EQ("00d100000000f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" - // "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" - // "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" - // "00000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140ac3edf2d00540f9c" - // "2f3b24878936b409c995c425ab5edf247c5b0d812a50df29c009c1e7c4538e5a32f88d0087bea3b91082" - // "0487db572e9be6ebddc953200b8d2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" - // "47125f927b7486ac41406fea9f12b125d7f65a94774e765a796428b3c6c4c46b0470624b9a1cef4ff420" - // "b777d70bf73d9c4dad78c9c1ae52273273d38bf82cde221a1523eb4222c1c2cf232103d9fd62df332403" - // "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", - // rawTxHex); } TEST(OntologyOng, withdraw) { @@ -92,25 +80,12 @@ TEST(OntologyOng, withdraw) { "6b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc81400000000000000000000000000000000000000" "016a7cc814fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc8516a7cc86c0c7472616e7366657246726f" "6d1400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f" - "6b6500024140b8b859055c744a89ef4d4f6ae7a58e0a99fef2eb0f6cf09d740b56cf4c7c14ab64e00c28de9b1f" - "28921cbd62e6bcd6d452ab9871f8f5d2288812ff322ee2f4af2321031bec1250aa8f78275f99a6663688f31085" + "6b6500024140b8b859055c744a89ef4d4f6ae7a58e0a99fef2eb0f6cf09d740b56cf4c7c14ab9b1ff3d62164e0" + "d86de3429d1943292b6a3b623bae21cc5c6ba6cb90cd8030a22321031bec1250aa8f78275f99a6663688f31085" "848d0ed92f1203e447125f927b7486ac41406413b060329e133cd13709c361ccd90b3944477cf3937f1459313f" "0ea6435f6f2b1335192a5d1b346fd431e8af912bfa4e1a23ad7d0ab7fc5b808655af5c9043232103d9fd62df33" "2403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", rawTxHex); - - // TODO uncomment when nist256p1 Rust implementation is enabled. - // EXPECT_EQ( - // "00d100000000f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df68b00c6" - // "6b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc81400000000000000000000000000000000000000" - // "016a7cc814fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc8516a7cc86c0c7472616e7366657246726f" - // "6d1400000000000000000000000000000000000000020068164f6e746f6c6f67792e4e61746976652e496e766f" - // "6b6500024140b8b859055c744a89ef4d4f6ae7a58e0a99fef2eb0f6cf09d740b56cf4c7c14ab9b1ff3d62164e0" - // "d86de3429d1943292b6a3b623bae21cc5c6ba6cb90cd8030a22321031bec1250aa8f78275f99a6663688f31085" - // "848d0ed92f1203e447125f927b7486ac41406413b060329e133cd13709c361ccd90b3944477cf3937f1459313f" - // "0ea6435f6f2b1335192a5d1b346fd431e8af912bfa4e1a23ad7d0ab7fc5b808655af5c9043232103d9fd62df33" - // "2403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", - // rawTxHex); } } // namespace TW::Ontology::tests diff --git a/tests/chains/Ontology/OntTests.cpp b/tests/chains/Ontology/OntTests.cpp index 221f350fbe8..f5ea71f4220 100644 --- a/tests/chains/Ontology/OntTests.cpp +++ b/tests/chains/Ontology/OntTests.cpp @@ -51,24 +51,12 @@ TEST(OntologyOnt, transfer) { "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" "00000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b65000241407531e7d5bb9ae138" - "862585a65c26d624f1a7a61011298809d9ed9cf60d10a4504067dee9d549a836b480c4e48904e28f9b42" - "dd5fa14376cbb1ef27d931eaea552321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" + "862585a65c26d624f1a7a61011298809d9ed9cf60d10a450bf9821152ab657ca4b7f3b1b76fb1d7021a4" + "1d4e05d427b941caa2e9ca783afc2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" "47125f927b7486ac4140bcc6df81d7f2f3143f152c446643ac5bf7910ef90046be8c89818264a11d360d" - "0576d7b092fabafd0913a67ccf8b2f8e3d2bd708f768c2bb67e2d2f759805608232103d9fd62df332403" + "fa89284e6d054503f6ec59833074d0717fbb23a4afaedbc98bd6f7cba2e2cf49232103d9fd62df332403" "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", rawTxHex); - - // TODO uncomment when nist256p1 Rust implementation is enabled. - // EXPECT_EQ("00d100000000f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" - // "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" - // "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" - // "00000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b65000241407531e7d5bb9ae138" - // "862585a65c26d624f1a7a61011298809d9ed9cf60d10a450bf9821152ab657ca4b7f3b1b76fb1d7021a4" - // "1d4e05d427b941caa2e9ca783afc2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" - // "47125f927b7486ac4140bcc6df81d7f2f3143f152c446643ac5bf7910ef90046be8c89818264a11d360d" - // "fa89284e6d054503f6ec59833074d0717fbb23a4afaedbc98bd6f7cba2e2cf49232103d9fd62df332403" - // "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", - // rawTxHex); } // Successfully broadcasted: https://explorer.ont.io/tx/785af64758886099b995e2aed914c56b15ab63c6e0c5acf42b66f3bbc3e95f98 diff --git a/tests/chains/Ontology/TWAnySignerTests.cpp b/tests/chains/Ontology/TWAnySignerTests.cpp index 3d9754490aa..4d33fee8ab9 100644 --- a/tests/chains/Ontology/TWAnySignerTests.cpp +++ b/tests/chains/Ontology/TWAnySignerTests.cpp @@ -79,24 +79,12 @@ TEST(TWAnySingerOntology, OntTransfer) { "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" "00000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140301766d925382a6e" - "bb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb7cff28ddaf7f1048822c0ca21a0c4926323a" - "2497875b963f3b8cbd3717aa6e7c2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" + "bb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb8300d7215080efb87dd3f35de5f3b6d98aac" + "d6161fbc0845b82d0d8be4b8b6d52321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" "47125f927b7486ac414038466b25ac49a22ba8c301328ef049a61711b257987e85e25d63e0444a14e860" "305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403" "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", hex(output.encoded())); - - // TODO uncomment when nist256p1 Rust implementation is enabled. - // EXPECT_EQ("00d102d45c8bf401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" - // "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" - // "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" - // "00000000010068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140301766d925382a6e" - // "bb2ebeb18d3741954c9370dcf6d9c45b34ce7b18bc42dcdb8300d7215080efb87dd3f35de5f3b6d98aac" - // "d6161fbc0845b82d0d8be4b8b6d52321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" - // "47125f927b7486ac414038466b25ac49a22ba8c301328ef049a61711b257987e85e25d63e0444a14e860" - // "305a4cd3bb6ea2fe80fd293abb3c592e679c42c546cbf3baa051a07b28b374a6232103d9fd62df332403" - // "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", - // hex(output.encoded())); } TEST(TWAnySingerOntology, OngDecimals) { @@ -161,24 +149,12 @@ TEST(TWAnySingerOntology, OngTransfer) { "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" "00000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140e27e935b87855efa" - "d62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f73b8ab199a4d757b4c7b9ed46c4ff8cfa8ae" - "faa90b7fb6485e358034448cba752321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" + "d62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f8c4754e565b28a85b384612b93b007301438" + "00049b97e83c95844a8eb7d66adc2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" "47125f927b7486ac4140450047b2efb384129a16ec4c707790e9379b978cc7085170071d8d7c5c037d74" - "3b078bd4e21bb4404c0182a32ee05260e22454dffb34dacccf458dfbee6d32db232103d9fd62df332403" + "c4f8742a1de44bc0b3fe7d5cd11fad9edac2a5cdabe2c3b824743cc70df5f276232103d9fd62df332403" "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", hex(output.encoded())); - - // TODO uncomment when nist256p1 Rust implementation is enabled. - // EXPECT_EQ("00d19d3182a8f401000000000000204e00000000000057e9d1a61f9aafa798b6c7fbeae35639681d7df6" - // "7100c66b14fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a6a7cc814feec06b79ed299ea06fcb94aba" - // "c41aaf3ead76586a7cc8516a7cc86c51c1087472616e7366657214000000000000000000000000000000" - // "00000000020068164f6e746f6c6f67792e4e61746976652e496e766f6b6500024140e27e935b87855efa" - // "d62bb76b21c7b591f445f867eff86f888ca6ee1870ecd80f8c4754e565b28a85b384612b93b007301438" - // "00049b97e83c95844a8eb7d66adc2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e4" - // "47125f927b7486ac4140450047b2efb384129a16ec4c707790e9379b978cc7085170071d8d7c5c037d74" - // "c4f8742a1de44bc0b3fe7d5cd11fad9edac2a5cdabe2c3b824743cc70df5f276232103d9fd62df332403" - // "d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac", - // hex(output.encoded())); } TEST(TWAnySingerOntology, OngWithdraw) { @@ -259,10 +235,7 @@ TEST(TWAnySingerOntology, Oep4Transfer) { auto rawTx = data(output.encoded()); auto rawTxHex = hex(rawTx); - EXPECT_EQ("00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e9001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff00024140bd2923854d7b84b97a107bb3cddf18c8e3dddd2f36b41a1f5f5b23366484daa22871cfb819923fe01e9cb1e9ed16baa2b05c2feb76bcbe2ec125f72701c5e965232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41406d638653597774ce45812ea2653250806b657b32b7c6ad3e027ddeba91e9a9da4bb5dacd23dafba868cb31bacb38b4a6ff2607682a426c1dc09b05a1e158d6cd2321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac", hex(output.encoded())); - - // TODO uncomment when nist256p1 Rust implementation is enabled. - // EXPECT_EQ("00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e9001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff00024140bd2923854d7b84b97a107bb3cddf18c8e3dddd2f36b41a1f5f5b23366484daa2d78e3046e66dc020e1634e1612e9455d0c8acac2305ae0563293d39bfa9d3bec232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41406d638653597774ce45812ea2653250806b657b32b7c6ad3e027ddeba91e9a9dab44a2531dc2504589734ce4534c74b58bdc0f3457cd53267331ec5211b0a4e842321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac", rawTxHex); + EXPECT_EQ("00d134120000c40900000000000050c3000000000000fbacc8214765d457c8e3f2b5a1d3c4981a2e9d2a4d02e9001496f688657b95be51c11a87b51adfda4ab69e9cbb1457e9d1a61f9aafa798b6c7fbeae35639681d7df653c1087472616e736665726733def739225d0f93dd2aed457d7b1fd074ec31ff00024140bd2923854d7b84b97a107bb3cddf18c8e3dddd2f36b41a1f5f5b23366484daa2d78e3046e66dc020e1634e1612e9455d0c8acac2305ae0563293d39bfa9d3bec232103d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1eeac41406d638653597774ce45812ea2653250806b657b32b7c6ad3e027ddeba91e9a9dab44a2531dc2504589734ce4534c74b58bdc0f3457cd53267331ec5211b0a4e842321031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486ac", rawTxHex); } TEST(TWAnySingerOntology, Oep4TokenBalanceOf) { diff --git a/tests/chains/Solana/AddressTests.cpp b/tests/chains/Solana/AddressTests.cpp index c4c2de8cef9..65960797cc0 100644 --- a/tests/chains/Solana/AddressTests.cpp +++ b/tests/chains/Solana/AddressTests.cpp @@ -60,10 +60,10 @@ TEST(SolanaAddress, isValidOnCurve) { EXPECT_TRUE(PublicKey(Base58::decode("68io7dTfyeWua1wD1YcCMka4y5iiChceaFRCBjqCM5PK"), TWPublicKeyTypeED25519).isValidED25519()); EXPECT_TRUE(PublicKey(Base58::decode("Dra34QLFCjxnk8tUNcBwxs6pgb5spF4oseQYF2xn7ABZ"), TWPublicKeyTypeED25519).isValidED25519()); // negative case - EXPECT_FALSE(PublicKey(Base58::decode("6X4X1Ae24mkoWeCEpktevySVG9jzeCufut5vtUW3wFrD"), TWPublicKeyTypeED25519).isValidED25519()); - EXPECT_FALSE(PublicKey(Base58::decode("EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"), TWPublicKeyTypeED25519).isValidED25519()); - EXPECT_FALSE(PublicKey(Base58::decode("ANVCrmRw7Ww7rTFfMbrjApSPXEEcZpBa6YEiBdf98pAf"), TWPublicKeyTypeED25519).isValidED25519()); - EXPECT_FALSE(PublicKey(Base58::decode("AbygL37RheNZv327cMvZPqKYLLkZ6wqWYexRxgNiZyeP"), TWPublicKeyTypeED25519).isValidED25519()); + EXPECT_FALSE(PublicKey::isValid(Base58::decode("6X4X1Ae24mkoWeCEpktevySVG9jzeCufut5vtUW3wFrD"), TWPublicKeyTypeED25519)); + EXPECT_FALSE(PublicKey::isValid(Base58::decode("EDNd1ycsydWYwVmrYZvqYazFqwk1QjBgAUKFjBoz1jKP"), TWPublicKeyTypeED25519)); + EXPECT_FALSE(PublicKey::isValid(Base58::decode("ANVCrmRw7Ww7rTFfMbrjApSPXEEcZpBa6YEiBdf98pAf"), TWPublicKeyTypeED25519)); + EXPECT_FALSE(PublicKey::isValid(Base58::decode("AbygL37RheNZv327cMvZPqKYLLkZ6wqWYexRxgNiZyeP"), TWPublicKeyTypeED25519)); } } // namespace TW::Solana::tests diff --git a/tests/chains/Zilliqa/AddressTests.cpp b/tests/chains/Zilliqa/AddressTests.cpp index 26861fb8ca7..cd64513a77a 100644 --- a/tests/chains/Zilliqa/AddressTests.cpp +++ b/tests/chains/Zilliqa/AddressTests.cpp @@ -15,8 +15,8 @@ namespace TW::Zilliqa::tests { TEST(ZilliqaAddress, FromPrivateKey) { const auto privateKey = - PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6"), TWCurveSECP256k1); - const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeSECP256k1)); + PrivateKey(parse_hex("3382266517e2ebe6df51faf4bfe612236ad46fb8bd59ac982a223b045e080ac6"), TWCurveZILLIQASchnorr); + const auto publicKey = PublicKey(privateKey.getPublicKey(TWPublicKeyTypeZILLIQASchnorr)); const auto address = Address(publicKey); auto expectedAddress = "zil1j8xae6lggm8y63m3y2r7aefu797ze7mhzulnqg"; diff --git a/tests/chains/Zilliqa/SignatureTests.cpp b/tests/chains/Zilliqa/SignatureTests.cpp index a629ac7e72e..8fbcba85e47 100644 --- a/tests/chains/Zilliqa/SignatureTests.cpp +++ b/tests/chains/Zilliqa/SignatureTests.cpp @@ -15,13 +15,13 @@ using namespace TW; TEST(ZilliqaSignature, Signing) { auto keyData = WRAPD(TWDataCreateWithHexString(STRING("0xafeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5").get())); auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(keyData.get(), TWCoinTypeCurve(TWCoinTypeZilliqa))); - auto pubKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); + auto pubKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyZilliqaSchnorr(privateKey.get())); auto message = "hello schnorr"; auto messageData = WRAPD(TWDataCreateWithBytes((uint8_t *)message, strnlen(message, 13))); - auto signatureData = WRAPD(TWPrivateKeySignZilliqaSchnorr(privateKey.get(), messageData.get())); + auto signatureData = WRAPD(TWPrivateKeySign(privateKey.get(), messageData.get())); auto signature = data(TWDataBytes(signatureData.get()), TWDataSize(signatureData.get())); - ASSERT_TRUE(TWPublicKeyVerifyZilliqaSchnorr(pubKey.get(), signatureData.get(), messageData.get())); + ASSERT_TRUE(TWPublicKeyVerify(pubKey.get(), signatureData.get(), messageData.get())); EXPECT_EQ(hex(signature), "d166b1ae7892c5ef541461dc12a50214d0681b63d8037cda29a3fe6af8bb973e4ea94624d85bc0010bdc1b38d05198328fae21254adc2bf5feaf2804d54dba55"); } diff --git a/tests/chains/Zilliqa/SignerTests.cpp b/tests/chains/Zilliqa/SignerTests.cpp index dec0e6f67fe..132f6dcc5ed 100644 --- a/tests/chains/Zilliqa/SignerTests.cpp +++ b/tests/chains/Zilliqa/SignerTests.cpp @@ -14,8 +14,8 @@ namespace TW::Zilliqa::tests { TEST(ZilliqaSigner, PreImage) { - auto privateKey = PrivateKey(parse_hex("0E891B9DFF485000C7D1DC22ECF3A583CC50328684321D61947A86E57CF6C638"), TWCurveSECP256k1); - auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto privateKey = PrivateKey(parse_hex("0E891B9DFF485000C7D1DC22ECF3A583CC50328684321D61947A86E57CF6C638"), TWCurveZILLIQASchnorr); + auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeZILLIQASchnorr); ASSERT_EQ(hex(pubKey.bytes), "034ae47910d58b9bde819c3cffa8de4441955508db00aa2540db8e6bf6e99abc1b"); auto amount = uint256_t(15000000000000); @@ -42,12 +42,12 @@ TEST(ZilliqaSigner, PreImage) { ASSERT_EQ(hex(preImage), "0881800410041a149ca91eb535fb92fda5094110fdaeb752edb9b03922230a21034ae47910d58b9bde819c3cffa8de4441955508db00aa2540db8e6bf6e99abc1b2a120a10000000000000000000000da475abf00032120a100000000000000000000000003b9aca003801"); - ASSERT_TRUE(pubKey.verifyZilliqa(Data(signature.begin(), signature.end()), preImage)); + ASSERT_TRUE(pubKey.verify(Data(signature.begin(), signature.end()), preImage)); } TEST(ZilliqaSigner, Signing) { - auto privateKey = PrivateKey(parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748"), TWCurveSECP256k1); - auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto privateKey = PrivateKey(parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748"), TWCurveZILLIQASchnorr); + auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeZILLIQASchnorr); // 1 ZIL auto amount = uint256_t(1000000000000); @@ -76,8 +76,8 @@ TEST(ZilliqaSigner, Signing) { TEST(ZilliqaSigner, SigningData) { // https://viewblock.io/zilliqa/tx/0x6228b3d7e69fc3481b84fd00e892cec359a41654f58948ff7b1b932396b00ad9 - auto privateKey = PrivateKey(parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748"), TWCurveSECP256k1); - auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); + auto privateKey = PrivateKey(parse_hex("0x68ffa8ec149ce50da647166036555f73d57f662eb420e154621e5f24f6cf9748"), TWCurveZILLIQASchnorr); + auto pubKey = privateKey.getPublicKey(TWPublicKeyTypeZILLIQASchnorr); // 10 ZIL auto amount = uint256_t(10000000000000); diff --git a/tests/common/CoinAddressDerivationTests.cpp b/tests/common/CoinAddressDerivationTests.cpp index 3e722a0f481..705706ddc97 100644 --- a/tests/common/CoinAddressDerivationTests.cpp +++ b/tests/common/CoinAddressDerivationTests.cpp @@ -15,7 +15,6 @@ namespace TW { TEST(Coin, DeriveAddress) { auto dummyKeyData = parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"); const auto privateKey = PrivateKey(dummyKeyData, TWCurveSECP256k1); - const auto privateKeyExt = PrivateKey(dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData, TWCurveSECP256k1); const auto coins = TW::getCoinTypes(); for (auto& c : coins) { @@ -25,11 +24,12 @@ TEST(Coin, DeriveAddress) { address = TW::deriveAddress(c, privateKey); break; - case TWCoinTypeCardano: - case TWCoinTypeNEO: + case TWCoinTypeCardano: { + const auto privateKeyExt = PrivateKey(dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData, dummyKeyData, TWCurveED25519ExtendedCardano); address = TW::deriveAddress(c, privateKeyExt); break; } + } switch (c) { // Ethereum and ... diff --git a/tests/common/PrivateKeyTests.cpp b/tests/common/PrivateKeyTests.cpp index 74cc7a9cf51..db420b8ee8a 100644 --- a/tests/common/PrivateKeyTests.cpp +++ b/tests/common/PrivateKeyTests.cpp @@ -213,11 +213,9 @@ TEST(PrivateKey, PrivateKeyExtended) { } TEST(PrivateKey, PrivateKeyExtendedError) { - // TWPublicKeyTypeED25519Cardano pubkey with non-extended private: error - auto privateKeyNonext = PrivateKey(parse_hex( - "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveED25519ExtendedCardano); try { - auto publicKeyError = privateKeyNonext.getPublicKey(TWPublicKeyTypeED25519Cardano); + auto privateKeyNonext = PrivateKey(parse_hex( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveED25519ExtendedCardano); } catch (invalid_argument& ex) { // expected exception return; @@ -253,10 +251,10 @@ TEST(PrivateKey, SignExtended) { } TEST(PrivateKey, SignSchnorr) { - const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); + const auto privateKey = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveZILLIQASchnorr); const Data messageData = TW::data("hello schnorr"); const Data digest = Hash::sha256(messageData); - const auto signature = privateKey.signZilliqa(digest); + const auto signature = privateKey.sign(digest); EXPECT_EQ(hex(signature), "b8118ccb99563fe014279c957b0a9d563c1666e00367e9896fe541765246964f64a53052513da4e6dc20fdaf69ef0d95b4ca51c87ad3478986cf053c2dd0b853"); } @@ -292,7 +290,7 @@ TEST(PrivateKey, SignNIST256p1VerifyLegacy) { } } -int isCanonical([[maybe_unused]] uint8_t by, [[maybe_unused]] uint8_t sig[64]) { +int isCanonical([[maybe_unused]] uint8_t by, [[maybe_unused]] const uint8_t sig[64]) { return 1; } diff --git a/tests/common/PublicKeyTests.cpp b/tests/common/PublicKeyTests.cpp index d69e1bc7952..c1278e59dbb 100644 --- a/tests/common/PublicKeyTests.cpp +++ b/tests/common/PublicKeyTests.cpp @@ -238,28 +238,28 @@ TEST(PublicKeyTests, VerifyEd25519Extended) { } TEST(PublicKeyTests, VerifySchnorr) { - const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); + const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveZILLIQASchnorr); const auto privateKey = PrivateKey(key); const Data messageData = TW::data("hello schnorr"); const Data digest = Hash::sha256(messageData); - const auto signature = privateKey.signZilliqa(digest); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1); - EXPECT_TRUE(publicKey.verifyZilliqa(signature, digest)); + const auto signature = privateKey.sign(digest); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeZILLIQASchnorr); + EXPECT_TRUE(publicKey.verify(signature, digest)); EXPECT_EQ(hex(signature), "b8118ccb99563fe014279c957b0a9d563c1666e00367e9896fe541765246964f64a53052513da4e6dc20fdaf69ef0d95b4ca51c87ad3478986cf053c2dd0b853"); } TEST(PublicKeyTests, VerifySchnorrWrongType) { - const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveSECP256k1); + const auto key = PrivateKey(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveZILLIQASchnorr); const auto privateKey = PrivateKey(key); const Data messageData = TW::data("hello schnorr"); const Data digest = Hash::sha256(messageData); - const auto signature = privateKey.signZilliqa(digest); - const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1); - EXPECT_FALSE(publicKey.verifyZilliqa(signature, digest)); + const auto signature = privateKey.sign(digest); + const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSchnorr); + EXPECT_FALSE(publicKey.verify(signature, digest)); } TEST(PublicKeyTests, RecoverRaw) { @@ -305,13 +305,13 @@ TEST(PublicKeyTests, RecoverRawNegative) { const auto message = parse_hex("de4e9524586d6fce45667f9ff12f661e79870c4105fa0fb58af976619bb11432"); const auto signature = parse_hex("00000000000000000000000000000000000000000000000000000000000000020123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); // recid >= 4 - EXPECT_EXCEPTION(PublicKey::recoverRaw(signature, 4ul, message), "Invalid recId"); + EXPECT_EXCEPTION(PublicKey::recoverRaw(signature, 4ul, message), "Recover failed"); // signature too short EXPECT_EXCEPTION(PublicKey::recoverRaw(parse_hex("00000000000000000000000000000000000000000000000000000000000000020123456789abcdef0123456789abcdef0123456789abcdef0123456789abcd"), 1ul, message), - "signature too short"); + "Recover failed"); // Digest too short EXPECT_EXCEPTION(PublicKey::recoverRaw(signature, 1ul, parse_hex("de4e9524586d6fce45667f9ff12f661e79870c4105fa0fb58af976619bb114")), - "digest too short"); + "Recover failed"); } TEST(PublicKeyTests, Recover) { @@ -355,10 +355,8 @@ TEST(PublicKeyTests, isValidED25519) { EXPECT_TRUE(PublicKey::isValid(parse_hex("01beff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908"), TWPublicKeyTypeED25519)); EXPECT_TRUE(PublicKey(parse_hex("01beff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908"), TWPublicKeyTypeED25519).isValidED25519()); // Following 32 bytes are not valid public keys (not on the curve) - EXPECT_TRUE(PublicKey::isValid(parse_hex("8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"), TWPublicKeyTypeED25519)); - EXPECT_FALSE(PublicKey(parse_hex("8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"), TWPublicKeyTypeED25519).isValidED25519()); - EXPECT_TRUE(PublicKey::isValid(parse_hex("51fdd5feae59d7dcbf5ebea99c05593ebee302577a5486ceac706ed568aa1e0e"), TWPublicKeyTypeED25519)); - EXPECT_FALSE(PublicKey(parse_hex("51fdd5feae59d7dcbf5ebea99c05593ebee302577a5486ceac706ed568aa1e0e"), TWPublicKeyTypeED25519).isValidED25519()); + EXPECT_FALSE(PublicKey::isValid(parse_hex("8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"), TWPublicKeyTypeED25519)); + EXPECT_FALSE(PublicKey::isValid(parse_hex("51fdd5feae59d7dcbf5ebea99c05593ebee302577a5486ceac706ed568aa1e0e"), TWPublicKeyTypeED25519)); // invalid input size/format EXPECT_FALSE(PublicKey::isValid(parse_hex("1234"), TWPublicKeyTypeED25519)); EXPECT_FALSE(PublicKey::isValid(parse_hex("beff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa5279"), TWPublicKeyTypeED25519)); diff --git a/tests/interface/TWCoinTypeTests.cpp b/tests/interface/TWCoinTypeTests.cpp index b7547ae34a9..a3faf369ff4 100644 --- a/tests/interface/TWCoinTypeTests.cpp +++ b/tests/interface/TWCoinTypeTests.cpp @@ -93,7 +93,7 @@ TEST(TWCoinType, TWPublicKeyType) { ASSERT_EQ(TWPublicKeyTypeSECP256k1Extended, TWCoinTypePublicKeyType(TWCoinTypeIoTeX)); ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeViacoin)); ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeQtum)); - ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeZilliqa)); + ASSERT_EQ(TWPublicKeyTypeZILLIQASchnorr, TWCoinTypePublicKeyType(TWCoinTypeZilliqa)); ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeTerra)); ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeMonacoin)); ASSERT_EQ(TWPublicKeyTypeSECP256k1, TWCoinTypePublicKeyType(TWCoinTypeKava)); diff --git a/tests/interface/TWHDWalletTests.cpp b/tests/interface/TWHDWalletTests.cpp index cff8a206c30..4f03674358e 100644 --- a/tests/interface/TWHDWalletTests.cpp +++ b/tests/interface/TWHDWalletTests.cpp @@ -258,7 +258,7 @@ TEST(HDWallet, DeriveDoge) { TEST(HDWallet, DeriveZilliqa) { auto wallet = WRAP(TWHDWallet, TWHDWalletCreateWithMnemonic(gWords.get(), gPassphrase.get())); auto key = WRAP(TWPrivateKey, TWHDWalletGetKeyForCoin(wallet.get(), TWCoinTypeZilliqa)); - auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(key.get(), true)); + auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeyZilliqaSchnorr(key.get())); auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); assertHexEqual(publicKeyData, "0262746d4988c63b9972c63272461e9fa080d4dfa2a1fda3dd01285620c0a60c22"); @@ -442,12 +442,12 @@ TEST(HDWallet, PublicKeyFromExtended_Ethereum) { } TEST(HDWallet, PublicKeyFromExtended_NIST256p1) { - const auto xpub = STRING("xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj"); + const auto xpub = STRING("xpub6C14EL78ogJs6aQEpzMdPa6oUdo7dUFdScW7ckC6MXxUAN7zDiEP6pGQphtcjS2cv4Nusp4i9CVJPmtuGmp1RKN3pCvUPWkDFcoMHCHERTA"); const auto xpubAddr = WRAP(TWPublicKey, TWHDWalletGetPublicKeyFromExtended(xpub.get(), TWCoinTypeNEO, STRING("m/44'/888'/0'/0/0").get())); // Neo ASSERT_NE(xpubAddr.get(), nullptr); auto data = WRAPD(TWPublicKeyData(xpubAddr.get())); ASSERT_NE(data.get(), nullptr); - assertHexEqual(data, "03774c910fcf07fa96886ea794f0d5caed9afe30b44b83f7e213bb92930e7df4bd"); + assertHexEqual(data, "023c73be53bc3bbbacf6af57850efd294f07f1d8e324f8bb88df9274a188eac4b0"); } TEST(HDWallet, PublicKeyFromExtended_Negative) { diff --git a/tests/interface/TWPublicKeyTests.cpp b/tests/interface/TWPublicKeyTests.cpp index 4832a854b71..99e4c8c4462 100644 --- a/tests/interface/TWPublicKeyTests.cpp +++ b/tests/interface/TWPublicKeyTests.cpp @@ -33,7 +33,7 @@ TEST(TWPublicKeyTests, CreateFromPrivateSecp256k1) { auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); EXPECT_EQ(hex(*((Data*)(publicKeyData.get()))), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); EXPECT_EQ(*((std::string*)(WRAPS(TWPublicKeyDescription(publicKey.get())).get())), "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"); - EXPECT_TRUE(TWPublicKeyIsValid(publicKey.get(), TWPublicKeyTypeSECP256k1)); + EXPECT_TRUE(TWPublicKeyIsValid(publicKeyData.get(), TWPublicKeyTypeSECP256k1)); EXPECT_TRUE(TWPublicKeyIsCompressed(publicKey.get())); } @@ -46,23 +46,26 @@ TEST(TWPublicKeyTests, CompressedExtended) { const PrivateKey key(parse_hex("afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5"), TWCurveCurve25519); const auto privateKey = WRAP(TWPrivateKey, new TWPrivateKey{ key }); auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKeySecp256k1(privateKey.get(), true)); + auto publicKeyData = WRAPD(TWPublicKeyData(publicKey.get())); EXPECT_EQ(TWPublicKeyKeyType(publicKey.get()), TWPublicKeyTypeSECP256k1); EXPECT_EQ(publicKey.get()->impl.bytes.size(), 33ul); EXPECT_EQ(TWPublicKeyIsCompressed(publicKey.get()), true); - EXPECT_TRUE(TWPublicKeyIsValid(publicKey.get(), TWPublicKeyTypeSECP256k1)); + EXPECT_TRUE(TWPublicKeyIsValid(publicKeyData.get(), TWPublicKeyTypeSECP256k1)); auto extended = WRAP(TWPublicKey, TWPublicKeyUncompressed(publicKey.get())); + auto extendedData = WRAPD(TWPublicKeyData(extended.get())); EXPECT_EQ(TWPublicKeyKeyType(extended.get()), TWPublicKeyTypeSECP256k1Extended); EXPECT_EQ(extended.get()->impl.bytes.size(), 65ul); EXPECT_EQ(TWPublicKeyIsCompressed(extended.get()), false); - EXPECT_TRUE(TWPublicKeyIsValid(extended.get(), TWPublicKeyTypeSECP256k1Extended)); + EXPECT_TRUE(TWPublicKeyIsValid(extendedData.get(), TWPublicKeyTypeSECP256k1Extended)); auto compressed = WRAP(TWPublicKey, TWPublicKeyCompressed(extended.get())); + auto compressedData = WRAPD(TWPublicKeyData(publicKey.get())); //EXPECT_TRUE(compressed == publicKey.get()); EXPECT_EQ(TWPublicKeyKeyType(compressed.get()), TWPublicKeyTypeSECP256k1); EXPECT_EQ(compressed.get()->impl.bytes.size(), 33ul); EXPECT_EQ(TWPublicKeyIsCompressed(compressed.get()), true); - EXPECT_TRUE(TWPublicKeyIsValid(compressed.get(), TWPublicKeyTypeSECP256k1)); + EXPECT_TRUE(TWPublicKeyIsValid(compressedData.get(), TWPublicKeyTypeSECP256k1)); } TEST(TWPublicKeyTests, Verify) { From 504d5f385105ecfa36431d643a6d4f4af68b350b Mon Sep 17 00:00:00 2001 From: gupnik Date: Wed, 9 Apr 2025 13:33:30 +0530 Subject: [PATCH 17/23] Merge master to dev (#4358) * feat(eip7702): Add Biz Smart Contract Account Type (#4319) * fix(eip7702): Add `UserOperationMode` * Add `erc4337.biz_account.abi.json` ABI * fix(eip7702): Add `test_barz_transfer_erc7702_eoa` test * fix(eip7702): Fix `Biz.execute4337Ops()` * fix(eip7702): Minor changes * fix(eip7702): Rename `UserOperationMode` to `SCAccountType` * fix: tron message sign (#4326) * Adds ability to specify the curve while constructing Private Key (#4324) * Adds ability to specify the curve while constructing Private Key * Adds signing functions without a curve * Migrates to new API * Use TWCoinTypeCurve * Adds Curve * feat(eip7702): Add `SetCode` transaction type (#4336) * fix(eip7702): Add `SetCode` transaction type * fix(eip7702): Add `Biz.executeBatch` function call * Add `AuthorizationSigner` * fix(eip7702): Fix Authorization list RLP encoding * fix(eip7702): Add `Biz.execute` and `Biz.executeBatch` tests * fix(eip7702): Add android test * [CI] Trigger CI * feat(biz): Adjust `Barz.getEncodedHash` according to the latest changes in Biz contract (#4342) * fix(biz): Adjust `Barz.getEncodedHash` according to the latest Biz changes * fix(biz): Adjust Android test * chore(dependencies): Update `gtest` to 1.16.0 (#4343) * [ETH]: Makes factory and paymaster optional while serialising UserOpV07 (#4345) * feat(biz): Allow to call `Biz.execute` when EOA is delegated already (#4351) * fix(biz): Allow to call `Biz.execute` when EOA is delegated already * feature(biz): Adopt C++ tests * feat(biz): Add and fix android tests * feat(biz): Fix ios tests * chore(aa): Rename `Execute` and `Batch` to `AAExecute` and `AABatch` correspondingly * chore(scw): Rename `AABatch` and `AAExecute` to `SCWalletBatch` and `SCWalletExecute` * chore(uov7): Serialize UserOperation numbers as hex 0x prefixed (#4353) --------- Co-authored-by: Sergei Boiko <127754187+satoshiotomakan@users.noreply.github.com> Co-authored-by: Yeferson Licet <111311418+y3fers0n@users.noreply.github.com> --- .../core/app/blockchains/ethereum/TestBarz.kt | 72 +++++- rust/tw_encoding/src/hex.rs | 25 ++ rust/tw_evm/Cargo.toml | 2 +- rust/tw_evm/src/modules/tx_builder.rs | 243 ++++++++++++------ rust/tw_evm/src/signature.rs | 11 +- .../src/transaction/user_operation_v0_7.rs | 98 +++---- rust/tw_evm/tests/barz.rs | 241 +++++++++++++---- rust/tw_number/Cargo.toml | 1 + rust/tw_number/src/lib.rs | 1 + rust/tw_number/src/serde.rs | 15 ++ rust/tw_number/src/u256.rs | 12 + src/proto/Ethereum.proto | 30 ++- swift/Tests/BarzTests.swift | 24 +- tests/chains/Ethereum/BarzTests.cpp | 6 +- 14 files changed, 576 insertions(+), 205 deletions(-) create mode 100644 rust/tw_number/src/serde.rs diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt index 8e22f9a4c88..7fec2add476 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestBarz.kt @@ -117,8 +117,12 @@ class TestBarz { }.build() transaction = Ethereum.Transaction.newBuilder().apply { - transfer = Ethereum.Transaction.Transfer.newBuilder().apply { - amount = ByteString.copyFrom("0x2386f26fc10000".toHexByteArray()) + scwExecute = Ethereum.Transaction.SCWalletExecute.newBuilder().apply { + transaction = Ethereum.Transaction.newBuilder().apply { + transfer = Ethereum.Transaction.Transfer.newBuilder().apply { + amount = ByteString.copyFrom("0x2386f26fc10000".toHexByteArray()) + }.build() + }.build() }.build() }.build() } @@ -159,8 +163,12 @@ class TestBarz { }.build() transaction = Ethereum.Transaction.newBuilder().apply { - transfer = Ethereum.Transaction.Transfer.newBuilder().apply { - amount = ByteString.copyFrom("0x2386f26fc10000".toHexByteArray()) + scwExecute = Ethereum.Transaction.SCWalletExecute.newBuilder().apply { + transaction = Ethereum.Transaction.newBuilder().apply { + transfer = Ethereum.Transaction.Transfer.newBuilder().apply { + amount = ByteString.copyFrom("0x2386f26fc10000".toHexByteArray()) + }.build() + }.build() }.build() }.build() } @@ -204,14 +212,14 @@ class TestBarz { }.build() transaction = Ethereum.Transaction.newBuilder().apply { - batch = Ethereum.Transaction.Batch.newBuilder().apply { + scwBatch = Ethereum.Transaction.SCWalletBatch.newBuilder().apply { addAllCalls(listOf( - Ethereum.Transaction.Batch.BatchedCall.newBuilder().apply { + Ethereum.Transaction.SCWalletBatch.BatchedCall.newBuilder().apply { address = "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266" amount = ByteString.copyFrom("0x00".toHexByteArray()) payload = ByteString.copyFrom(approveCall) }.build(), - Ethereum.Transaction.Batch.BatchedCall.newBuilder().apply { + Ethereum.Transaction.SCWalletBatch.BatchedCall.newBuilder().apply { address = "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266" amount = ByteString.copyFrom("0x00".toHexByteArray()) payload = ByteString.copyFrom(transferCall) @@ -254,15 +262,16 @@ class TestBarz { maxInclusionFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) transaction = Ethereum.Transaction.newBuilder().apply { - batch = Ethereum.Transaction.Batch.newBuilder().apply { + scwBatch = Ethereum.Transaction.SCWalletBatch.newBuilder().apply { + walletType = Ethereum.SCWalletType.Biz addAllCalls(listOf( - Ethereum.Transaction.Batch.BatchedCall.newBuilder().apply { + Ethereum.Transaction.SCWalletBatch.BatchedCall.newBuilder().apply { // TWT address = "0x4B0F1812e5Df2A09796481Ff14017e6005508003" amount = ByteString.copyFrom("0x00".toHexByteArray()) payload = ByteString.copyFrom(transferPayload1) }.build(), - Ethereum.Transaction.Batch.BatchedCall.newBuilder().apply { + Ethereum.Transaction.SCWalletBatch.BatchedCall.newBuilder().apply { // TWT address = "0x4B0F1812e5Df2A09796481Ff14017e6005508003" amount = ByteString.copyFrom("0x00".toHexByteArray()) @@ -272,7 +281,6 @@ class TestBarz { }.build() }.build() - userOperationMode = Ethereum.SCAccountType.Biz eip7702Authority = Ethereum.Authority.newBuilder().apply { address = "0x117BC8454756456A0f83dbd130Bb94D793D3F3F7" }.build() @@ -284,6 +292,42 @@ class TestBarz { assertEquals(Numeric.toHexString(output.encoded.toByteArray()), "0x04f9030f3812843b9aca00843b9aca00830186a0945132829820b44dc3e8586cec926a16fca0a5608480b9024434fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000002ef648d7c03412b832726fd4683e2625dea047ba00000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000001c6bf5263400000000000000000000000000000000000000000000000000000000000c0f85cf85a3894117bc8454756456a0f83dbd130bb94d793d3f3f71380a0073afc661c158a2dccf4183f87e1e4d62b4d406af418cfd69959368ec9bec2a6a064292fd61d4d16b840470a86fc4f7a89413f9126d897f2268eb76a1d887c6d7a01a0e8bcbd96323c9d3e67b74366b2f43299100996d9e8874a6fd87186ac8f580d4ca07c25b4f0619af77fb953e8f0e4372bfbee62616ad419697516108eeb9bcebb28") } + // https://bscscan.com/tx/0x6f8b2c8d50e8bb543d7124703b75d9e495832116a1a61afabf40b9b0ac43c980 + @Test + fun testSignEnvelopedBiz() { + val signingInput = Ethereum.SigningInput.newBuilder() + signingInput.apply { + privateKey = ByteString.copyFrom(PrivateKey("0xe762e91cc4889a9fce79b2d2ffc079f86c48331f57b2cd16a33bee060fe448e1".toHexByteArray(), CoinType.ETHEREUM.curve()).data()) + chainId = ByteString.copyFrom("0x38".toHexByteArray()) + nonce = ByteString.copyFrom("0x02".toHexByteArray()) + txMode = TransactionMode.Enveloped + + gasLimit = ByteString.copyFrom("0x186a0".toHexByteArray()) + maxFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) + maxInclusionFeePerGas = ByteString.copyFrom("0x3b9aca00".toHexByteArray()) + + transaction = Ethereum.Transaction.newBuilder().apply { + scwExecute = Ethereum.Transaction.SCWalletExecute.newBuilder().apply { + walletType = Ethereum.SCWalletType.Biz + transaction = Ethereum.Transaction.newBuilder().apply { + erc20Transfer = Ethereum.Transaction.ERC20Transfer.newBuilder().apply { + to = "0x95dc01ebd10b6dccf1cc329af1a3f73806117c2e" + amount = ByteString.copyFrom("0xb5e620f48000".toHexByteArray()) + }.build() + }.build() + }.build() + }.build() + + // TWT token. + toAddress = "0x4B0F1812e5Df2A09796481Ff14017e6005508003" + } + + val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) + + assertEquals(Numeric.toHexString(output.preHash.toByteArray()), "0x60260356568ae70838bd80085b971e1e4ebe42046688fd8511a268986e522121") + assertEquals(Numeric.toHexString(output.encoded.toByteArray()), "0x02f901503802843b9aca00843b9aca00830186a0946e860086bba8fdeafb553815af0f09a854cc887a80b8e4b61d27f60000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000000b5e620f4800000000000000000000000000000000000000000000000000000000000c080a0fb45762a262f4c32090576e9de087482d25cd00b6ea2522eb7d5a40f435acdbaa0151dbd48a4f4bf06080313775fe32ececd68869d721518a92bf292e4a84322f9") + } + @Test fun testAuthorizationHash() { val chainId = "0x01".toHexByteArray() @@ -351,7 +395,11 @@ class TestBarz { toAddress = "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9" transaction = Ethereum.Transaction.newBuilder().apply { - this.transfer = transfer + scwExecute = Ethereum.Transaction.SCWalletExecute.newBuilder().apply { + transaction = Ethereum.Transaction.newBuilder().apply { + this.transfer = transfer + }.build() + }.build() }.build() userOperationV07 = userOpV07 diff --git a/rust/tw_encoding/src/hex.rs b/rust/tw_encoding/src/hex.rs index c7c5e5d6610..8eee855bf2d 100644 --- a/rust/tw_encoding/src/hex.rs +++ b/rust/tw_encoding/src/hex.rs @@ -135,6 +135,31 @@ pub mod as_hex { } } +pub mod as_hex_prefixed { + use crate::hex::encode; + use serde::{Deserializer, Serialize, Serializer}; + use std::fmt; + + /// Serializes the `value` as a `0x` prefixed hex. + pub fn serialize(value: &T, serializer: S) -> Result + where + T: AsRef<[u8]>, + S: Serializer, + { + encode(value, true).serialize(serializer) + } + + pub fn deserialize<'de, D, T, E>(deserializer: D) -> Result + where + D: Deserializer<'de>, + T: for<'a> TryFrom<&'a [u8], Error = E>, + E: fmt::Debug, + { + // `as_hex::deserialize` handles the prefix already. + super::as_hex::deserialize(deserializer) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/rust/tw_evm/Cargo.toml b/rust/tw_evm/Cargo.toml index 857a8f1bd5a..7e264f6ae05 100644 --- a/rust/tw_evm/Cargo.toml +++ b/rust/tw_evm/Cargo.toml @@ -15,7 +15,7 @@ tw_hash = { path = "../tw_hash" } tw_keypair = { path = "../tw_keypair" } tw_memory = { path = "../tw_memory" } tw_misc = { path = "../tw_misc" } -tw_number = { path = "../tw_number" } +tw_number = { path = "../tw_number", features = ["serde"] } tw_proto = { path = "../tw_proto" } [dev-dependencies] diff --git a/rust/tw_evm/src/modules/tx_builder.rs b/rust/tw_evm/src/modules/tx_builder.rs index d246fc7d2b2..ae884b653a4 100644 --- a/rust/tw_evm/src/modules/tx_builder.rs +++ b/rust/tw_evm/src/modules/tx_builder.rs @@ -31,9 +31,31 @@ use tw_proto::Common::Proto::SigningError as CommonError; use tw_proto::Ethereum::Proto; use Proto::mod_SigningInput::OneOfuser_operation_oneof as UserOp; use Proto::mod_Transaction::OneOftransaction_oneof as Tx; -use Proto::SCAccountType; +use Proto::SCWalletType; use Proto::TransactionMode as TxMode; +pub struct TransactionParts { + pub eth_amount: U256, + pub data: Data, + pub to: Option
, +} + +impl TryFrom for ExecuteArgs { + type Error = SigningError; + + fn try_from(value: TransactionParts) -> Result { + let to = value + .to + .or_tw_err(SigningErrorType::Error_invalid_params) + .context("Error creating a Smart Contract Wallet execute call - no destination address specified")?; + Ok(ExecuteArgs { + to, + value: value.eth_amount, + data: value.data, + }) + } +} + pub struct TxBuilder { _phantom: PhantomData, } @@ -47,16 +69,43 @@ impl TxBuilder { .context("No transaction specified"); }; - let (eth_amount, payload, to) = match transaction.transaction_oneof { + let TransactionParts { + eth_amount, + data, + to, + } = Self::handle_transaction_type(input, &transaction.transaction_oneof)?; + + let tx = match input.tx_mode { + TxMode::Legacy => { + Self::transaction_non_typed_from_proto(input, eth_amount, data, to)?.into_boxed() + }, + TxMode::Enveloped => { + Self::transaction_eip1559_from_proto(input, eth_amount, data, to)?.into_boxed() + }, + TxMode::UserOp => Self::user_operation_from_proto(input, data)?, + TxMode::SetCode => Self::transaction_eip7702_from_proto(input, eth_amount, data, to)?, + }; + Ok(tx) + } + + fn handle_transaction_type( + input: &Proto::SigningInput, + transaction: &Tx, + ) -> SigningResult { + match transaction { Tx::transfer(ref transfer) => { - let amount = U256::from_big_endian_slice(&transfer.amount) + let eth_amount = U256::from_big_endian_slice(&transfer.amount) .into_tw() .context("Invalid amount")?; let to_address = Self::parse_address(&input.to_address) .context("Invalid destination address")?; - (amount, transfer.data.to_vec(), Some(to_address)) + Ok(TransactionParts { + eth_amount, + data: transfer.data.to_vec(), + to: Some(to_address), + }) }, Tx::erc20_transfer(ref erc20_transfer) => { let token_to_address = Self::parse_address(&erc20_transfer.to) @@ -69,9 +118,14 @@ impl TxBuilder { let contract_address = Self::parse_address(&input.to_address).context("Invalid Contract address")?; - let payload = Erc20::transfer(token_to_address, token_amount) + let data = Erc20::transfer(token_to_address, token_amount) .map_err(abi_to_signing_error)?; - (U256::zero(), payload, Some(contract_address)) + + Ok(TransactionParts { + eth_amount: U256::zero(), + data, + to: Some(contract_address), + }) }, Tx::erc20_approve(ref erc20_approve) => { let spender = Self::parse_address(&erc20_approve.spender) @@ -84,9 +138,13 @@ impl TxBuilder { let contract_address = Self::parse_address(&input.to_address).context("Invalid Contract address")?; - let payload = - Erc20::approve(spender, token_amount).map_err(abi_to_signing_error)?; - (U256::zero(), payload, Some(contract_address)) + let data = Erc20::approve(spender, token_amount).map_err(abi_to_signing_error)?; + + Ok(TransactionParts { + eth_amount: U256::zero(), + data, + to: Some(contract_address), + }) }, Tx::erc721_transfer(ref erc721_transfer) => { let from = @@ -101,9 +159,14 @@ impl TxBuilder { let contract_address = Self::parse_address(&input.to_address).context("Invalid Contract address")?; - let payload = Erc721::encode_transfer_from(from, token_to_address, token_id) + let data = Erc721::encode_transfer_from(from, token_to_address, token_id) .map_err(abi_to_signing_error)?; - (U256::zero(), payload, Some(contract_address)) + + Ok(TransactionParts { + eth_amount: U256::zero(), + data, + to: Some(contract_address), + }) }, Tx::erc1155_transfer(ref erc1155_transfer) => { let from = Self::parse_address(&erc1155_transfer.from) @@ -124,83 +187,70 @@ impl TxBuilder { let contract_address = Self::parse_address(&input.to_address).context("Invalid Contract address")?; - let payload = Erc1155::encode_safe_transfer_from(from, to, token_id, value, data) + let data = Erc1155::encode_safe_transfer_from(from, to, token_id, value, data) .map_err(abi_to_signing_error)?; - (U256::zero(), payload, Some(contract_address)) + + Ok(TransactionParts { + eth_amount: U256::zero(), + data, + to: Some(contract_address), + }) }, Tx::contract_generic(ref contract_generic) => { - let amount = U256::from_big_endian_slice(&contract_generic.amount) + let eth_amount = U256::from_big_endian_slice(&contract_generic.amount) .into_tw() .context("Invalid amount")?; - let payload = contract_generic.data.to_vec(); + let data = contract_generic.data.to_vec(); // `to_address` can be omitted for the generic contract call. // For example, on creating a new smart contract. - let to_address = Self::parse_address_optional(&input.to_address) + let to = Self::parse_address_optional(&input.to_address) .context("Invalid destination address")?; - (amount, payload, to_address) + Ok(TransactionParts { + eth_amount, + data, + to, + }) }, - Tx::batch(ref batch) => { + Tx::scw_batch(ref batch) => { // Payload should match ERC4337 standard. let calls: Vec<_> = batch .calls .iter() .map(Self::erc4337_execute_call_from_proto) .collect::, _>>()?; - let execute_payload = Self::encode_execute_batch(input.user_operation_mode, calls)?; - - return match input.tx_mode { - TxMode::UserOp => Self::user_operation_from_proto(input, execute_payload), - TxMode::SetCode => { - Self::transaction_eip7702_from_proto(input, U256::zero(), execute_payload) - }, - _ => SigningError::err(SigningErrorType::Error_invalid_params).context( - "Transaction batch can be used in `UserOp` or `SetCode` modes only", - ), - }; - }, - Tx::None => { - return SigningError::err(SigningErrorType::Error_invalid_params) - .context("No transaction specified") - }, - }; + let execute_payload = Self::encode_execute_batch(batch.wallet_type, calls)?; + let to = Self::sc_tx_destination(input, batch.wallet_type)?; - let tx = match input.tx_mode { - TxMode::Legacy => { - Self::transaction_non_typed_from_proto(input, eth_amount, payload, to)?.into_boxed() - }, - TxMode::Enveloped => { - Self::transaction_eip1559_from_proto(input, eth_amount, payload, to)?.into_boxed() - }, - TxMode::UserOp => { - let to = to - .or_tw_err(SigningErrorType::Error_invalid_address) - .context("No contract/destination address specified")?; - let args = ExecuteArgs { + Ok(TransactionParts { + eth_amount: U256::zero(), + data: execute_payload, to, - value: eth_amount, - data: payload, - }; - - let user_op_payload = Self::encode_execute(input.user_operation_mode, args)?; - Self::user_operation_from_proto(input, user_op_payload)? + }) }, - TxMode::SetCode => { - let to = to - .or_tw_err(SigningErrorType::Error_invalid_address) - .context("No contract/destination address specified")?; - let args = ExecuteArgs { + Tx::scw_execute(ref execute) => { + let inner_transaction = execute + .transaction + .as_ref() + .or_tw_err(SigningErrorType::Error_invalid_params) + .context("`Execute.transaction` must be provided")?; + + let execute_args = + Self::handle_transaction_type(input, &inner_transaction.transaction_oneof)? + .try_into()?; + let execute_call_payload = Self::encode_execute(execute.wallet_type, execute_args)?; + + let to = Self::sc_tx_destination(input, execute.wallet_type)?; + Ok(TransactionParts { + eth_amount: U256::zero(), + data: execute_call_payload, to, - value: eth_amount, - data: payload, - }; - - let execute_payload = Self::encode_execute(input.user_operation_mode, args)?; - Self::transaction_eip7702_from_proto(input, eth_amount, execute_payload)? + }) }, - }; - Ok(tx) + Tx::None => SigningError::err(SigningErrorType::Error_invalid_params) + .context("No transaction specified"), + } } #[inline] @@ -224,7 +274,7 @@ impl TxBuilder { #[inline] fn erc4337_execute_call_from_proto( - call: &Proto::mod_Transaction::mod_Batch::BatchedCall, + call: &Proto::mod_Transaction::mod_SCWalletBatch::BatchedCall, ) -> SigningResult { let to = Self::parse_address(&call.address) .context("Invalid 'BatchedCall' destination address")?; @@ -311,11 +361,17 @@ impl TxBuilder { input: &Proto::SigningInput, eth_amount: U256, payload: Data, + to_address: Option
, ) -> SigningResult> { let signer_key = secp256k1::PrivateKey::try_from(input.private_key.as_ref()) .into_tw() .context("Sender's private key must be provided to generate an EIP-7702 transaction")?; let signer = Address::with_secp256k1_pubkey(&signer_key.public()); + if to_address != Some(signer) { + return SigningError::err(SigningErrorType::Error_invalid_params).context( + "Unexpected 'accountAddress'. Expected to be the same as the signer address", + ); + } let nonce = U256::from_big_endian_slice(&input.nonce) .into_tw() @@ -517,25 +573,50 @@ impl TxBuilder { }) } + /// Returns a destination address of the Smart Contract Wallet transaction. + /// Returns: + /// - `Ok(Some(address))` when generating a transaction calling a function of the account itself through EIP-7702 authorized code. + /// - `Ok(None)` when generating a UserOperation. + /// - `Err(e)` when the account type is not supported for the given transaction mode. + #[inline] + fn sc_tx_destination( + input: &Proto::SigningInput, + wallet_type: SCWalletType, + ) -> SigningResult> { + match (input.tx_mode, wallet_type) { + // Destination address is not used when generating UserOperation. + (TxMode::UserOp, SCWalletType::SimpleAccount | SCWalletType::Biz4337) => Ok(None), + (TxMode::UserOp, _) => SigningError::err(SigningErrorType::Error_invalid_params) + .context("Biz account cannot be used in UserOperation flow"), + (TxMode::Legacy | TxMode::Enveloped | TxMode::SetCode, SCWalletType::Biz) => { + Self::signer_address(input).map(Some) + }, + (TxMode::Legacy | TxMode::Enveloped | TxMode::SetCode, _) => SigningError::err( + SigningErrorType::Error_invalid_params, + ) + .context("Biz account can only be used in Legacy/Enveloped/SetCode transactions flow"), + } + } + #[inline] - fn encode_execute(account_type: SCAccountType, args: ExecuteArgs) -> SigningResult { - match account_type { - SCAccountType::SimpleAccount => Erc4337SimpleAccount::encode_execute(args), - SCAccountType::Biz4337 => BizAccount::encode_execute_4337_op(args), - SCAccountType::Biz => BizAccount::encode_execute(args), + fn encode_execute(wallet_type: SCWalletType, args: ExecuteArgs) -> SigningResult { + match wallet_type { + SCWalletType::SimpleAccount => Erc4337SimpleAccount::encode_execute(args), + SCWalletType::Biz4337 => BizAccount::encode_execute_4337_op(args), + SCWalletType::Biz => BizAccount::encode_execute(args), } .map_err(abi_to_signing_error) } #[inline] fn encode_execute_batch( - account_type: SCAccountType, + wallet_type: SCWalletType, calls: Vec, ) -> SigningResult { - match account_type { - SCAccountType::SimpleAccount => Erc4337SimpleAccount::encode_execute_batch(calls), - SCAccountType::Biz4337 => BizAccount::encode_execute_4337_ops(calls), - SCAccountType::Biz => BizAccount::encode_execute_batch(calls), + match wallet_type { + SCWalletType::SimpleAccount => Erc4337SimpleAccount::encode_execute_batch(calls), + SCWalletType::Biz4337 => BizAccount::encode_execute_4337_ops(calls), + SCWalletType::Biz => BizAccount::encode_execute_batch(calls), } .map_err(abi_to_signing_error) } @@ -575,4 +656,12 @@ impl TxBuilder { } Ok(access) } + + fn signer_address(input: &Proto::SigningInput) -> SigningResult
{ + let signer_key = secp256k1::PrivateKey::try_from(input.private_key.as_ref()) + .into_tw() + .context("Sender's private key must be provided to generate an EIP-7702 transaction")?; + let signer = Address::with_secp256k1_pubkey(&signer_key.public()); + Ok(signer) + } } diff --git a/rust/tw_evm/src/signature.rs b/rust/tw_evm/src/signature.rs index c3097c197a8..4b4cb2d64b3 100644 --- a/rust/tw_evm/src/signature.rs +++ b/rust/tw_evm/src/signature.rs @@ -3,7 +3,7 @@ // Copyright © 2017 Trust Wallet. use std::ops::BitXor; -use tw_number::{NumberResult, U256}; +use tw_number::{NumberError, NumberResult, U256}; /// EIP155 Eth encoding of V, of the form 27+v, or 35+chainID*2+v. /// cbindgin:ignore @@ -19,12 +19,19 @@ pub fn replay_protection(chain_id: U256, v: u8) -> NumberResult { } } -/// Embeds `chain_id` in `v` param, for replay protection, legacy. +/// Embeds legacy protection into `v` param. #[inline] pub fn legacy_replay_protection(v: u8) -> NumberResult { U256::from(v).checked_add(ETHEREUM_SIGNATURE_V_OFFSET) } +/// Embeds legacy protection into `v` param. +#[inline] +pub fn legacy_replay_protection_u8(v: u8) -> NumberResult { + v.checked_add(ETHEREUM_SIGNATURE_V_OFFSET) + .ok_or(NumberError::IntegerOverflow) +} + /// Embeds `chain_id` in `v` param, for replay protection, EIP155. #[inline] pub fn eip155_replay_protection(chain_id: U256, v: u8) -> NumberResult { diff --git a/rust/tw_evm/src/transaction/user_operation_v0_7.rs b/rust/tw_evm/src/transaction/user_operation_v0_7.rs index bfe9f1a0183..96e1f8624cd 100644 --- a/rust/tw_evm/src/transaction/user_operation_v0_7.rs +++ b/rust/tw_evm/src/transaction/user_operation_v0_7.rs @@ -6,14 +6,17 @@ use crate::abi::encode::encode_tokens; use crate::abi::non_empty_array::NonEmptyBytes; use crate::abi::token::Token; use crate::address::Address; +use crate::signature::legacy_replay_protection_u8; use crate::transaction::signature::Signature; use crate::transaction::{SignedTransaction, TransactionCommon, UnsignedTransaction}; -use serde::Serialize; +use serde::ser::Error as SerError; +use serde::{Serialize, Serializer}; use tw_coin_entry::error::prelude::*; -use tw_encoding::hex; +use tw_encoding::hex::{self, as_hex_prefixed}; use tw_hash::sha3::keccak256; use tw_hash::H256; use tw_memory::Data; +use tw_number::serde::as_u256_hex; use tw_number::U256; pub struct PackedUserOperation { @@ -115,22 +118,45 @@ impl PackedUserOperation { } } +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] pub struct UserOperationV0_7 { pub sender: Address, + #[serde(serialize_with = "U256::as_hex")] pub nonce: U256, + + #[serde(skip_serializing_if = "Option::is_none")] pub factory: Option
, + #[serde(skip_serializing_if = "Data::is_empty")] + #[serde(with = "as_hex_prefixed")] pub factory_data: Data, + + #[serde(with = "as_hex_prefixed")] pub call_data: Data, + + #[serde(serialize_with = "as_u256_hex")] pub call_data_gas_limit: u128, + #[serde(serialize_with = "as_u256_hex")] pub verification_gas_limit: u128, + #[serde(serialize_with = "U256::as_hex")] pub pre_verification_gas: U256, + + #[serde(serialize_with = "as_u256_hex")] pub max_fee_per_gas: u128, + #[serde(serialize_with = "as_u256_hex")] pub max_priority_fee_per_gas: u128, + + #[serde(skip_serializing_if = "Option::is_none")] pub paymaster: Option
, + #[serde(serialize_with = "as_u256_hex")] pub paymaster_verification_gas_limit: u128, + #[serde(serialize_with = "as_u256_hex")] pub paymaster_post_op_gas_limit: u128, + #[serde(skip_serializing_if = "Data::is_empty")] + #[serde(with = "as_hex_prefixed")] pub paymaster_data: Data, + #[serde(skip)] pub entry_point: Address, } @@ -165,8 +191,12 @@ impl UnsignedTransaction for UserOperationV0_7 { } } +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] pub struct SignedUserOperationV0_7 { + #[serde(flatten)] unsigned: UserOperationV0_7, + #[serde(serialize_with = "serialize_signature_with_legacy_replay_protect")] signature: Signature, } @@ -181,31 +211,7 @@ impl SignedTransaction for SignedUserOperationV0_7 { type Signature = Signature; fn encode(&self) -> Data { - let mut signature = self.signature.to_rsv_bytes(); - signature[64] += 27; - - let prefix = true; - let tx = SignedUserOperationV0_7Serde { - sender: self.unsigned.sender.to_string(), - nonce: self.unsigned.nonce.to_string(), - factory: self.unsigned.factory.map(|addr| addr.to_string()), - factory_data: hex::encode(&self.unsigned.factory_data, prefix), - call_data: hex::encode(&self.unsigned.call_data, prefix), - call_data_gas_limit: self.unsigned.call_data_gas_limit.to_string(), - verification_gas_limit: self.unsigned.verification_gas_limit.to_string(), - pre_verification_gas: self.unsigned.pre_verification_gas.to_string(), - max_fee_per_gas: self.unsigned.max_fee_per_gas.to_string(), - max_priority_fee_per_gas: self.unsigned.max_priority_fee_per_gas.to_string(), - paymaster: self.unsigned.paymaster.map(|addr| addr.to_string()), - paymaster_verification_gas_limit: self - .unsigned - .paymaster_verification_gas_limit - .to_string(), - paymaster_post_op_gas_limit: self.unsigned.paymaster_post_op_gas_limit.to_string(), - paymaster_data: hex::encode(&self.unsigned.paymaster_data, prefix), - signature: hex::encode(signature.as_slice(), prefix), - }; - serde_json::to_string(&tx) + serde_json::to_string(self) .expect("Simple structure should never fail on serialization") .into_bytes() } @@ -216,26 +222,6 @@ impl SignedTransaction for SignedUserOperationV0_7 { } } -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -struct SignedUserOperationV0_7Serde { - sender: String, - nonce: String, - factory: Option, - factory_data: String, - call_data: String, - call_data_gas_limit: String, - verification_gas_limit: String, - pre_verification_gas: String, - max_fee_per_gas: String, - max_priority_fee_per_gas: String, - paymaster: Option, - paymaster_verification_gas_limit: String, - paymaster_post_op_gas_limit: String, - paymaster_data: String, - signature: String, -} - fn concat_u128_be(a: u128, b: u128) -> [u8; 32] { let a = a.to_be_bytes(); let b = b.to_be_bytes(); @@ -248,6 +234,22 @@ fn concat_u128_be(a: u128, b: u128) -> [u8; 32] { }) } +pub fn serialize_signature_with_legacy_replay_protect( + signature: &Signature, + serializer: S, +) -> Result +where + S: Serializer, +{ + let prefix = true; + let mut rsv = signature.to_rsv_bytes(); + rsv[64] = + legacy_replay_protection_u8(rsv[64]).map_err(|e| SerError::custom(format!("{e:?}")))?; + + let hex_str = hex::encode(rsv, prefix); + serializer.serialize_str(&hex_str) +} + #[cfg(test)] mod tests { use super::*; @@ -310,7 +312,7 @@ mod tests { nonce: U256::from(0u64), init_code: "f471789937856d80e589f5996cf8b0511ddd9de4f471789937856d80e589f5996cf8b0511ddd9de4".decode_hex().unwrap(), call_data: "00".decode_hex().unwrap(), - account_gas_limits:concat_u128_be(100000u128, 100000u128).to_vec(), + account_gas_limits: concat_u128_be(100000u128, 100000u128).to_vec(), pre_verification_gas: U256::from(1000000u64), gas_fees: concat_u128_be(100000u128, 100000u128).to_vec(), paymaster_and_data: "f62849f9a0b5bf2913b396098f7c7019b51a820a0000000000000000000000000001869f00000000000000000000000000015b3800000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b".decode_hex().unwrap(), diff --git a/rust/tw_evm/tests/barz.rs b/rust/tw_evm/tests/barz.rs index 4b8d1ee782d..27006d1a13c 100644 --- a/rust/tw_evm/tests/barz.rs +++ b/rust/tw_evm/tests/barz.rs @@ -13,6 +13,20 @@ use tw_evm::modules::signer::Signer; use tw_misc::traits::ToBytesVec; use tw_number::U256; use tw_proto::Ethereum::Proto; +use tw_proto::Ethereum::Proto::mod_Transaction::OneOftransaction_oneof as TransactionType; + +fn execute(tx: TransactionType, wallet_type: Proto::SCWalletType) -> Proto::Transaction { + Proto::Transaction { + transaction_oneof: TransactionType::scw_execute(Box::new( + Proto::mod_Transaction::SCWalletExecute { + transaction: Some(Box::new(Proto::Transaction { + transaction_oneof: tx, + })), + wallet_type, + }, + )), + } +} // https://testnet.bscscan.com/tx/0x43fc13dfdf06bbb09da8ce070953753764f1e43782d0c8b621946d8b45749419 #[test] @@ -42,9 +56,10 @@ fn test_barz_transfer_account_deployed() { max_inclusion_fee_per_gas: U256::encode_be_compact(0x1_a339_c9e9), to_address: "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9".into(), private_key: private_key.into(), - transaction: Some(Proto::Transaction { - transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), - }), + transaction: Some(execute( + TransactionType::transfer(transfer), + Proto::SCWalletType::SimpleAccount, + )), user_operation_oneof: Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation( user_op, ), @@ -97,9 +112,10 @@ fn test_barz_transfer_account_not_deployed() { max_inclusion_fee_per_gas: U256::encode_be_compact(0x1_a339_c9e9), to_address: "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9".into(), private_key: private_key.into(), - transaction: Some(Proto::Transaction { - transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), - }), + transaction: Some(execute( + TransactionType::transfer(transfer), + Proto::SCWalletType::SimpleAccount, + )), user_operation_oneof: Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation( user_op, ), @@ -136,7 +152,7 @@ fn test_barz_batched_account_deployed() { let amount = U256::from(0x8AC7_2304_89E8_0000_u64); let payload = Erc20::approve(spender, amount).unwrap(); - calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + calls.push(Proto::mod_Transaction::mod_SCWalletBatch::BatchedCall { address: contract_address.into(), amount: Cow::default(), payload: payload.into(), @@ -149,7 +165,7 @@ fn test_barz_batched_account_deployed() { let amount = U256::from(0x8AC7_2304_89E8_0000_u64); let payload = Erc20::transfer(recipient, amount).unwrap(); - calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + calls.push(Proto::mod_Transaction::mod_SCWalletBatch::BatchedCall { address: contract_address.into(), amount: Cow::default(), payload: payload.into(), @@ -175,9 +191,10 @@ fn test_barz_batched_account_deployed() { to_address: contract_address.into(), private_key: private_key.into(), transaction: Some(Proto::Transaction { - transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::batch( - Proto::mod_Transaction::Batch { calls }, - ), + transaction_oneof: TransactionType::scw_batch(Proto::mod_Transaction::SCWalletBatch { + calls, + wallet_type: Proto::SCWalletType::SimpleAccount, + }), }), user_operation_oneof: Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation( user_op, @@ -230,9 +247,10 @@ fn test_barz_transfer_account_not_deployed_v0_7() { max_inclusion_fee_per_gas: U256::from(100000u128).to_big_endian_compact().into(), to_address: "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9".into(), private_key: private_key.into(), - transaction: Some(Proto::Transaction { - transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), - }), + transaction: Some(execute( + TransactionType::transfer(transfer), + Proto::SCWalletType::SimpleAccount, + )), user_operation_oneof: Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation_v0_7(user_op), ..Proto::SigningInput::default() @@ -249,7 +267,7 @@ fn test_barz_transfer_account_not_deployed_v0_7() { } #[test] -fn test_barz_transfer_erc4337_eoa() { +fn test_biz4337_transfer() { let private_key = hex::decode("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483").unwrap(); @@ -281,14 +299,12 @@ fn test_barz_transfer_erc4337_eoa() { // USDT token. to_address: "0xdac17f958d2ee523a2206206994597c13d831ec7".into(), private_key: private_key.into(), - transaction: Some(Proto::Transaction { - transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::erc20_approve( - approve, - ), - }), + transaction: Some(execute( + TransactionType::erc20_approve(approve), + Proto::SCWalletType::Biz4337, + )), user_operation_oneof: Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation_v0_7(user_op), - user_operation_mode: Proto::SCAccountType::Biz4337, ..Proto::SigningInput::default() }; @@ -301,12 +317,13 @@ fn test_barz_transfer_erc4337_eoa() { "68109b9caf49f7971b689307c9a77ceec46e4b8fa88421c4276dd846f782d92c" ); - let user_op: serde_json::Value = serde_json::from_slice(&output.encoded).unwrap(); - assert_eq!(user_op["callData"], "0x76276c82000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000b0086171ac7b6bd4d046580bca6d6a4b0835c2320000000000000000000000000000000000000000000000000002540befbfbd0000000000000000000000000000000000000000000000000000000000"); + let expected = r#"{"sender":"0x2EF648D7C03412B832726fd4683E2625deA047Ba","nonce":"0x00","callData":"0x76276c82000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000b0086171ac7b6bd4d046580bca6d6a4b0835c2320000000000000000000000000000000000000000000000000002540befbfbd0000000000000000000000000000000000000000000000000000000000","callDataGasLimit":"0x0186a0","verificationGasLimit":"0x0186a0","preVerificationGas":"0x0f4240","maxFeePerGas":"0x0186a0","maxPriorityFeePerGas":"0x0186a0","paymaster":"0xb0086171AC7b6BD4D046580bca6d6A4b0835c232","paymasterVerificationGasLimit":"0x01869f","paymasterPostOpGasLimit":"0x015b38","paymasterData":"0x00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b","signature":"0xf6b1f7ad22bcc68ca292bc10d15e82e0eab8c75c1a04f9750e7cff1418d38d9c6c115c510e3f47eb802103d62f88fa7d4a3b2e24e2ddbe7ee68153920ab3f6cc1b"}"#; + let actual = String::from_utf8(output.encoded.to_vec()).unwrap(); + assert_eq!(actual, expected); } #[test] -fn test_barz_transfer_erc4337_eoa_batch() { +fn test_biz4337_transfer_batch() { let private_key = hex::decode("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483").unwrap(); @@ -333,7 +350,7 @@ fn test_barz_transfer_erc4337_eoa_batch() { let amount = U256::from(655_360_197_115_136_u64); let payload = Erc20::approve(recipient, amount).unwrap(); - calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + calls.push(Proto::mod_Transaction::mod_SCWalletBatch::BatchedCall { // USDT address: "0xdac17f958d2ee523a2206206994597c13d831ec7".into(), amount: Cow::default(), @@ -347,7 +364,7 @@ fn test_barz_transfer_erc4337_eoa_batch() { let amount = U256::from(0x8AC7_2304_89E8_0000_u64); let payload = Erc20::transfer(recipient, amount).unwrap(); - calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + calls.push(Proto::mod_Transaction::mod_SCWalletBatch::BatchedCall { address: "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266".into(), amount: Cow::default(), payload: payload.into(), @@ -365,13 +382,13 @@ fn test_barz_transfer_erc4337_eoa_batch() { to_address: "0xdac17f958d2ee523a2206206994597c13d831ec7".into(), private_key: private_key.into(), transaction: Some(Proto::Transaction { - transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::batch( - Proto::mod_Transaction::Batch { calls }, - ), + transaction_oneof: TransactionType::scw_batch(Proto::mod_Transaction::SCWalletBatch { + calls, + wallet_type: Proto::SCWalletType::Biz4337, + }), }), user_operation_oneof: Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation_v0_7(user_op), - user_operation_mode: Proto::SCAccountType::Biz4337, ..Proto::SigningInput::default() }; @@ -388,12 +405,13 @@ fn test_barz_transfer_erc4337_eoa_batch() { "f6340068891dc3eb78959993151c421dde23982b3a1407c0dbbd62c2c22c3cb8" ); - let user_op: serde_json::Value = serde_json::from_slice(&output.encoded).unwrap(); - assert_eq!(user_op["callData"], "0x26da7d880000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000b0086171ac7b6bd4d046580bca6d6a4b0835c2320000000000000000000000000000000000000000000000000002540befbfbd000000000000000000000000000000000000000000000000000000000000000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b1266000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000"); + let expected = r#"{"sender":"0x2EF648D7C03412B832726fd4683E2625deA047Ba","nonce":"0x00","callData":"0x26da7d880000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000b0086171ac7b6bd4d046580bca6d6a4b0835c2320000000000000000000000000000000000000000000000000002540befbfbd000000000000000000000000000000000000000000000000000000000000000000000000000000000003bbb5660b8687c2aa453a0e42dcb6e0732b1266000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000","callDataGasLimit":"0x0186a0","verificationGasLimit":"0x0186a0","preVerificationGas":"0x0f4240","maxFeePerGas":"0x0186a0","maxPriorityFeePerGas":"0x0186a0","paymaster":"0xb0086171AC7b6BD4D046580bca6d6A4b0835c232","paymasterVerificationGasLimit":"0x01869f","paymasterPostOpGasLimit":"0x015b38","paymasterData":"0x00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b","signature":"0x21ab0bdcd1441aef3e4046a922bab3636d0c74011c1b055c55ad9f39ae9b4dac59bcbf3bc1ff31b367a83360edfc8e9652f1a5c8b07eb76fe5a426835682d6721c"}"#; + let actual = String::from_utf8(output.encoded.to_vec()).unwrap(); + assert_eq!(actual, expected); } #[test] -fn test_barz_transfer_erc7702_eoa() { +fn test_biz_eip7702_transfer() { let private_key = hex::decode("0xe148e40f06ee3ba316cdb2571f33486cf879c0ffd2b279ce9f9a88c41ce962e7").unwrap(); @@ -413,13 +431,12 @@ fn test_barz_transfer_erc7702_eoa() { .to_big_endian_compact() .into(), private_key: private_key.into(), - transaction: Some(Proto::Transaction { - transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::erc20_transfer( - erc20_transfer, - ), - }), + transaction: Some(execute( + TransactionType::erc20_transfer(erc20_transfer), + Proto::SCWalletType::Biz, + )), + // TWT token. to_address: "0x4B0F1812e5Df2A09796481Ff14017e6005508003".into(), - user_operation_mode: Proto::SCAccountType::Biz, eip7702_authority: Some(Proto::Authority { address: "0x117BC8454756456A0f83dbd130Bb94D793D3F3F7".into(), }), @@ -447,7 +464,7 @@ fn test_barz_transfer_erc7702_eoa() { } #[test] -fn test_barz_transfer_erc7702_eoa_batch() { +fn test_biz_eip7702_transfer_batch() { let private_key = hex::decode("0xe148e40f06ee3ba316cdb2571f33486cf879c0ffd2b279ce9f9a88c41ce962e7").unwrap(); @@ -460,7 +477,7 @@ fn test_barz_transfer_erc7702_eoa_batch() { let amount = U256::from(100_000_000_000_000_u64); let payload = Erc20::transfer(recipient, amount).unwrap(); - calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + calls.push(Proto::mod_Transaction::mod_SCWalletBatch::BatchedCall { // TWT address: "0x4B0F1812e5Df2A09796481Ff14017e6005508003".into(), amount: Cow::default(), @@ -475,7 +492,7 @@ fn test_barz_transfer_erc7702_eoa_batch() { let amount = U256::from(500_000_000_000_000_u64); let payload = Erc20::transfer(recipient, amount).unwrap(); - calls.push(Proto::mod_Transaction::mod_Batch::BatchedCall { + calls.push(Proto::mod_Transaction::mod_SCWalletBatch::BatchedCall { // TWT address: "0x4B0F1812e5Df2A09796481Ff14017e6005508003".into(), amount: Cow::default(), @@ -496,11 +513,11 @@ fn test_barz_transfer_erc7702_eoa_batch() { .into(), private_key: private_key.into(), transaction: Some(Proto::Transaction { - transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::batch( - Proto::mod_Transaction::Batch { calls }, - ), + transaction_oneof: TransactionType::scw_batch(Proto::mod_Transaction::SCWalletBatch { + calls, + wallet_type: Proto::SCWalletType::Biz, + }), }), - user_operation_mode: Proto::SCAccountType::Biz, eip7702_authority: Some(Proto::Authority { address: "0x117BC8454756456A0f83dbd130Bb94D793D3F3F7".into(), }), @@ -526,3 +543,135 @@ fn test_barz_transfer_erc7702_eoa_batch() { "04f9030f3812843b9aca00843b9aca00830186a0945132829820b44dc3e8586cec926a16fca0a5608480b9024434fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000002ef648d7c03412b832726fd4683e2625dea047ba00000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000001c6bf5263400000000000000000000000000000000000000000000000000000000000c0f85cf85a3894117bc8454756456a0f83dbd130bb94d793d3f3f71380a0073afc661c158a2dccf4183f87e1e4d62b4d406af418cfd69959368ec9bec2a6a064292fd61d4d16b840470a86fc4f7a89413f9126d897f2268eb76a1d887c6d7a01a0e8bcbd96323c9d3e67b74366b2f43299100996d9e8874a6fd87186ac8f580d4ca07c25b4f0619af77fb953e8f0e4372bfbee62616ad419697516108eeb9bcebb28" ); } + +#[test] +fn test_biz_eip1559_transfer() { + // 0x6E860086BbA8fdEafB553815aF0F09a854cC887a + let private_key = + hex::decode("0xe762e91cc4889a9fce79b2d2ffc079f86c48331f57b2cd16a33bee060fe448e1").unwrap(); + + let erc20_transfer = Proto::mod_Transaction::ERC20Transfer { + to: "0x95dc01ebd10b6dccf1cc329af1a3f73806117c2e".into(), + amount: U256::encode_be_compact(200_000_000_000_000_u64), + }; + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(56_u64), + nonce: U256::encode_be_compact(2_u64), + tx_mode: Proto::TransactionMode::Enveloped, + gas_limit: U256::from(100_000_u128).to_big_endian_compact().into(), + max_fee_per_gas: U256::from(1_000_000_000_u128) + .to_big_endian_compact() + .into(), + max_inclusion_fee_per_gas: U256::from(1_000_000_000_u128) + .to_big_endian_compact() + .into(), + private_key: private_key.into(), + transaction: Some(execute( + TransactionType::erc20_transfer(erc20_transfer), + Proto::SCWalletType::Biz, + )), + // TWT token. + to_address: "0x4B0F1812e5Df2A09796481Ff14017e6005508003".into(), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!( + output.error, + SigningErrorType::OK, + "{}", + output.error_message + ); + + assert_eq!( + output.pre_hash.to_hex(), + "60260356568ae70838bd80085b971e1e4ebe42046688fd8511a268986e522121" + ); + // Successfully broadcasted transaction: + // https://bscscan.com/tx/0x6f8b2c8d50e8bb543d7124703b75d9e495832116a1a61afabf40b9b0ac43c980 + assert_eq!( + output.encoded.to_hex(), + "02f901503802843b9aca00843b9aca00830186a0946e860086bba8fdeafb553815af0f09a854cc887a80b8e4b61d27f60000000000000000000000004b0f1812e5df2a09796481ff14017e6005508003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb00000000000000000000000095dc01ebd10b6dccf1cc329af1a3f73806117c2e0000000000000000000000000000000000000000000000000000b5e620f4800000000000000000000000000000000000000000000000000000000000c080a0fb45762a262f4c32090576e9de087482d25cd00b6ea2522eb7d5a40f435acdbaa0151dbd48a4f4bf06080313775fe32ececd68869d721518a92bf292e4a84322f9" + ); +} + +#[test] +fn test_biz_eip1559_transfer_with_incorrect_wallet_type_error() { + // 0x6E860086BbA8fdEafB553815aF0F09a854cC887a + let private_key = + hex::decode("0xe762e91cc4889a9fce79b2d2ffc079f86c48331f57b2cd16a33bee060fe448e1").unwrap(); + + let erc20_transfer = Proto::mod_Transaction::ERC20Transfer { + to: "0x95dc01ebd10b6dccf1cc329af1a3f73806117c2e".into(), + amount: U256::encode_be_compact(200_000_000_000_000_u64), + }; + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(56_u64), + nonce: U256::encode_be_compact(2_u64), + tx_mode: Proto::TransactionMode::Enveloped, + gas_limit: U256::from(100_000_u128).to_big_endian_compact().into(), + max_fee_per_gas: U256::from(1_000_000_000_u128) + .to_big_endian_compact() + .into(), + max_inclusion_fee_per_gas: U256::from(1_000_000_000_u128) + .to_big_endian_compact() + .into(), + private_key: private_key.into(), + transaction: Some(execute( + TransactionType::erc20_transfer(erc20_transfer), + // Biz4337 account cannot be used in Legacy/Enveloped/SetCode transaction flow. + Proto::SCWalletType::Biz4337, + )), + // TWT token. + to_address: "0x4B0F1812e5Df2A09796481Ff14017e6005508003".into(), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::Error_invalid_params,); +} + +#[test] +fn test_user_operation_transfer_with_incorrect_wallet_type_error() { + let private_key = + hex::decode("0x3c90badc15c4d35733769093d3733501e92e7f16e101df284cee9a310d36c483").unwrap(); + + let transfer = Proto::mod_Transaction::Transfer { + amount: U256::encode_be_compact(0x23_86f2_6fc1_0000), + data: Cow::default(), + }; + let user_op = Proto::UserOperationV0_7 { + entry_point: "0x0000000071727De22E5E9d8BAf0edAc6f37da032".into(), + sender: "0x174a240e5147D02dE4d7724D5D3E1c1bF11cE029".into(), + pre_verification_gas: U256::from(1000000u64).to_big_endian_compact().into(), + verification_gas_limit: U256::from(100000u128).to_big_endian_compact().into(), + factory: "0xf471789937856d80e589f5996cf8b0511ddd9de4".into(), + factory_data: "f471789937856d80e589f5996cf8b0511ddd9de4".decode_hex().unwrap().into(), + paymaster: "0xf62849f9a0b5bf2913b396098f7c7019b51a820a".into(), + paymaster_verification_gas_limit: U256::from(99999u128).to_big_endian_compact().into(), + paymaster_post_op_gas_limit: U256::from(88888u128).to_big_endian_compact().into(), + paymaster_data: "00000000000b0000000000002e234dae75c793f67a35089c9d99245e1c58470b00000000000000000000000000000000000000000000000000000000000186a0072f35038bcacc31bcdeda87c1d9857703a26fb70a053f6e87da5a4e7a1e1f3c4b09fbe2dbff98e7a87ebb45a635234f4b79eff3225d07560039c7764291c97e1b".decode_hex().unwrap().into(), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(31337u64), + nonce: U256::encode_be_compact(0u64), + tx_mode: Proto::TransactionMode::UserOp, + gas_limit: U256::from(100000u128).to_big_endian_compact().into(), + max_fee_per_gas: U256::from(100000u128).to_big_endian_compact().into(), + max_inclusion_fee_per_gas: U256::from(100000u128).to_big_endian_compact().into(), + to_address: "0x61061fCAE11fD5461535e134EfF67A98CFFF44E9".into(), + private_key: private_key.into(), + transaction: Some(execute( + TransactionType::transfer(transfer), + // Biz account cannot be used in UserOperation flow. + Proto::SCWalletType::Biz, + )), + user_operation_oneof: + Proto::mod_SigningInput::OneOfuser_operation_oneof::user_operation_v0_7(user_op), + ..Proto::SigningInput::default() + }; + + let output = Signer::::sign_proto(input); + assert_eq!(output.error, SigningErrorType::Error_invalid_params); +} diff --git a/rust/tw_number/Cargo.toml b/rust/tw_number/Cargo.toml index 6f32da2d9b1..46f87781236 100644 --- a/rust/tw_number/Cargo.toml +++ b/rust/tw_number/Cargo.toml @@ -13,6 +13,7 @@ arbitrary = { version = "1", features = ["derive"], optional = true } lazy_static = "1.4.0" primitive-types = "0.10.1" serde = { version = "1.0", features = ["derive"], optional = true } +tw_encoding = { path = "../tw_encoding" } tw_hash = { path = "../tw_hash" } tw_memory = { path = "../tw_memory" } diff --git a/rust/tw_number/src/lib.rs b/rust/tw_number/src/lib.rs index 0c8189ba13d..5242eccabb2 100644 --- a/rust/tw_number/src/lib.rs +++ b/rust/tw_number/src/lib.rs @@ -3,6 +3,7 @@ // Copyright © 2017 Trust Wallet. mod i256; +pub mod serde; mod sign; mod u256; diff --git a/rust/tw_number/src/serde.rs b/rust/tw_number/src/serde.rs new file mode 100644 index 00000000000..59394df9736 --- /dev/null +++ b/rust/tw_number/src/serde.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::U256; +use serde::Serializer; + +pub fn as_u256_hex(num: &T, serializer: S) -> Result +where + S: Serializer, + T: Into + Copy, +{ + let num_u256: U256 = (*num).into(); + num_u256.as_hex(serializer) +} diff --git a/rust/tw_number/src/u256.rs b/rust/tw_number/src/u256.rs index e6136b8ce29..2e32094b274 100644 --- a/rust/tw_number/src/u256.rs +++ b/rust/tw_number/src/u256.rs @@ -236,6 +236,7 @@ mod impl_serde { use serde::de::Error as DeError; use serde::{Deserialize, Deserializer, Serializer}; use std::str::FromStr; + use tw_encoding::hex; impl U256 { pub fn as_decimal_str(&self, serializer: S) -> Result @@ -259,6 +260,17 @@ mod impl_serde { { crate::serde_common::from_num_or_decimal_str::<'de, U256, u64, D>(deserializer) } + + pub fn as_hex(&self, serializer: S) -> Result + where + S: Serializer, + { + let prefix = true; + let min_bytes_len = 1; + + let hex_str = hex::encode(self.to_big_endian_compact_min_len(min_bytes_len), prefix); + serializer.serialize_str(&hex_str) + } } } diff --git a/src/proto/Ethereum.proto b/src/proto/Ethereum.proto index f2e51c469a9..c1ce8b6fdb6 100644 --- a/src/proto/Ethereum.proto +++ b/src/proto/Ethereum.proto @@ -72,8 +72,8 @@ message Transaction { bytes data = 2; } - // Batched transaction for ERC-4337 wallets - message Batch { + // Batch transaction to a Smart Contract Wallet (ERC-4337 and ERC-7702). + message SCWalletBatch { message BatchedCall { // Recipient addresses. string address = 1; @@ -85,7 +85,19 @@ message Transaction { bytes payload = 3; } + // Batched calls to be executed on the smart contract wallet. repeated BatchedCall calls = 1; + // Smart contract wallet type. + SCWalletType wallet_type = 2; + } + + // Execute transaction to a Smart Contract Wallet (ERC-4337 and ERC-7702). + message SCWalletExecute { + // Transaction to be executed on the smart contract wallet. + // TODO currently, smart contract wallet address is specified in `SigningInput.toAddress`, but it will be refactored soon. + Transaction transaction = 1; + // Smart contract wallet type. + SCWalletType wallet_type = 2; } // Payload transfer @@ -96,7 +108,10 @@ message Transaction { ERC721Transfer erc721_transfer = 4; ERC1155Transfer erc1155_transfer = 5; ContractGeneric contract_generic = 6; - Batch batch = 7; + // Batch transaction to a Smart Contract Wallet (ERC-4337 and ERC-7702). + SCWalletBatch scw_batch = 7; + // Execute transaction to a Smart Contract Wallet (ERC-4337 and ERC-7702). + SCWalletExecute scw_execute = 8; } } @@ -185,8 +200,8 @@ message Authority { string address = 2; } -// Smart Contract account type. -enum SCAccountType { +// Smart Contract Wallet type. +enum SCWalletType { // ERC-4337 compatible smart contract wallet. // https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/accounts/SimpleAccount.sol SimpleAccount = 0; @@ -225,6 +240,7 @@ message SigningInput { bytes max_fee_per_gas = 7; // Recipient's address. + // TODO currently, will be moved to each `Transaction` oneof soon. string to_address = 8; // The secret private key used for signing (32 bytes). @@ -245,10 +261,8 @@ message SigningInput { // Used in `TransactionMode::Enveloped` only. repeated Access access_list = 12; - // Smart contract account type. Used in `TransactionMode::UserOp` only. - SCAccountType user_operation_mode = 14; - // A smart contract to which we’re delegating to. + // Used in `TransactionMode::SetOp` only. // Currently, we support delegation to only one authority at a time. Authority eip7702_authority = 15; } diff --git a/swift/Tests/BarzTests.swift b/swift/Tests/BarzTests.swift index c30d27e070c..53f03565314 100644 --- a/swift/Tests/BarzTests.swift +++ b/swift/Tests/BarzTests.swift @@ -87,8 +87,12 @@ class BarzTests: XCTestCase { } $0.transaction = EthereumTransaction.with { - $0.transfer = EthereumTransaction.Transfer.with { - $0.amount = Data(hexString: "2386f26fc10000")! + $0.scwExecute = EthereumTransaction.SCWalletExecute.with { + $0.transaction = EthereumTransaction.with { + $0.transfer = EthereumTransaction.Transfer.with { + $0.amount = Data(hexString: "2386f26fc10000")! + } + } } } } @@ -124,10 +128,14 @@ class BarzTests: XCTestCase { salt: 0 ) } - + $0.transaction = EthereumTransaction.with { - $0.transfer = EthereumTransaction.Transfer.with { - $0.amount = Data(hexString: "2386f26fc10000")! + $0.scwExecute = EthereumTransaction.SCWalletExecute.with { + $0.transaction = EthereumTransaction.with { + $0.transfer = EthereumTransaction.Transfer.with { + $0.amount = Data(hexString: "2386f26fc10000")! + } + } } } } @@ -165,14 +173,14 @@ class BarzTests: XCTestCase { } $0.transaction = EthereumTransaction.with { - $0.batch = EthereumTransaction.Batch.with { + $0.scwBatch = EthereumTransaction.SCWalletBatch.with { $0.calls = [ - EthereumTransaction.Batch.BatchedCall.with { + EthereumTransaction.SCWalletBatch.BatchedCall.with { $0.address = "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266" $0.amount = Data(hexString: "00")! $0.payload = approveCall }, - EthereumTransaction.Batch.BatchedCall.with { + EthereumTransaction.SCWalletBatch.BatchedCall.with { $0.address = "0x03bBb5660B8687C2aa453A0e42dCb6e0732b1266" $0.amount = Data(hexString: "00")! $0.payload = transferCall diff --git a/tests/chains/Ethereum/BarzTests.cpp b/tests/chains/Ethereum/BarzTests.cpp index bb3fb750c89..2d7106d16f3 100644 --- a/tests/chains/Ethereum/BarzTests.cpp +++ b/tests/chains/Ethereum/BarzTests.cpp @@ -171,7 +171,7 @@ TEST(Barz, SignK1TransferAccountDeployed) { user_operation.set_sender(sender); input.set_private_key(key.data(), key.size()); - auto& transfer = *input.mutable_transaction()->mutable_transfer(); + auto& transfer = *input.mutable_transaction()->mutable_scw_execute()->mutable_transaction()->mutable_transfer(); transfer.set_amount(amount.data(), amount.size()); std::string expected = "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"100000\",\"initCode\":\"0x\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"2\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0xb16Db98B365B1f89191996942612B14F1Da4Bd5f\",\"signature\":\"0x80e84992ebf8d5f71180231163ed150a7557ed0aa4b4bcee23d463a09847e4642d0fbf112df2e5fa067adf4b2fa17fc4a8ac172134ba5b78e3ec9c044e7f28d71c\",\"verificationGasLimit\":\"100000\"}"; @@ -224,7 +224,7 @@ TEST(Barz, SignR1TransferAccountNotDeployed) { user_operation.set_init_code(initCode.data(), initCode.size()); input.set_private_key(key.data(), key.size()); - auto& transfer = *input.mutable_transaction()->mutable_transfer(); + auto& transfer = *input.mutable_transaction()->mutable_scw_execute()->mutable_transaction()->mutable_transfer(); transfer.set_amount(amount.data(), amount.size()); std::string expected = "{\"callData\":\"0xb61d27f600000000000000000000000061061fcae11fd5461535e134eff67a98cfff44e9000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000\",\"callGasLimit\":\"2500000\",\"initCode\":\"0x3fc708630d85a3b5ec217e53100ec2b735d4f800296601cd0000000000000000000000005034534efe9902779ed6ea6983f435c00f3bc51000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104b173a6a812025c40c38bac46343646bd0a8137c807aae6e04aac238cc24d2ad2116ca14d23d357588ff2aabd7db29d5976f4ecc8037775db86f67e873a306b1f00000000000000000000000000000000000000000000000000000000000000\",\"maxFeePerGas\":\"7033440745\",\"maxPriorityFeePerGas\":\"7033440745\",\"nonce\":\"0\",\"paymasterAndData\":\"0x\",\"preVerificationGas\":\"46856\",\"sender\":\"0x1392Ae041BfBdBAA0cFF9234a0C8F64df97B7218\",\"signature\":\"0xbf1b68323974e71ad9bd6dfdac07dc062599d150615419bb7876740d2bcf3c8909aa7e627bb0e08a2eab930e2e7313247c9b683c884236dd6ea0b6834fb2cb0a1b\",\"verificationGasLimit\":\"3000000\"}"; @@ -286,7 +286,7 @@ TEST(Barz, SignR1BatchedTransferAccountDeployed) { auto transferCall = data(TWDataBytes(transferCallEncoded.get()), TWDataSize(transferCallEncoded.get())); EXPECT_EQ(hex(transferCall), "a9059cbb0000000000000000000000005ff137d4b0fdcd49dca30c7cf57e578a026d27890000000000000000000000000000000000000000000000008ac7230489e80000"); - auto *batch = input.mutable_transaction()->mutable_batch(); + auto *batch = input.mutable_transaction()->mutable_scw_batch(); auto *c1 = batch->add_calls(); c1->set_address(to); c1->set_amount(amount.data(), amount.size()); From e374216fcf5dd48ae95f7169328dfcad6df6e48e Mon Sep 17 00:00:00 2001 From: gupnik Date: Thu, 17 Apr 2025 13:49:09 +0530 Subject: [PATCH 18/23] Migrates `scrypt.c` to Rust (#4366) * Migrates `scrypt.c` to Rust * Migrates to v4 cache * Revert "Migrates to v4 cache" This reverts commit 6113eb571d330a14ec984902993ae744cc4709d0. * Updates sccache action * Uses correct version * Fixes FFI test * Adds keystore to default * Fixes Tests * Clippy fix * Updates memory for wasm * Actually use the config * Renames tw_keystore to tw_crypto and updates test * FMT --- .github/workflows/codegen-v2.yml | 2 +- .github/workflows/linux-ci-rust.yml | 6 +- rust/Cargo.lock | 61 ++++++++---- rust/Cargo.toml | 8 +- rust/tw_crypto/Cargo.toml | 13 +++ rust/tw_crypto/fuzz/.gitignore | 5 + rust/tw_crypto/fuzz/Cargo.toml | 27 ++++++ .../fuzz/fuzz_targets/crypto_scrypt.rs | 36 +++++++ rust/tw_crypto/src/crypto_scrypt/mod.rs | 42 +++++++++ rust/tw_crypto/src/crypto_scrypt/params.rs | 94 +++++++++++++++++++ rust/tw_crypto/src/crypto_scrypt/romix.rs | 81 ++++++++++++++++ rust/tw_crypto/src/ffi/crypto_scrypt.rs | 65 +++++++++++++ rust/tw_crypto/src/ffi/mod.rs | 7 ++ rust/tw_crypto/src/lib.rs | 8 ++ rust/tw_crypto/tests/crypto_scrypt.rs | 44 +++++++++ rust/tw_crypto/tests/crypto_scrypt_ffi.rs | 36 +++++++ rust/wallet_core_rs/Cargo.toml | 3 + rust/wallet_core_rs/cbindgen.toml | 2 + rust/wallet_core_rs/src/lib.rs | 2 + src/Keystore/EncryptionParameters.cpp | 29 ++++-- tools/rust-test | 5 +- 21 files changed, 541 insertions(+), 35 deletions(-) create mode 100644 rust/tw_crypto/Cargo.toml create mode 100644 rust/tw_crypto/fuzz/.gitignore create mode 100644 rust/tw_crypto/fuzz/Cargo.toml create mode 100644 rust/tw_crypto/fuzz/fuzz_targets/crypto_scrypt.rs create mode 100644 rust/tw_crypto/src/crypto_scrypt/mod.rs create mode 100644 rust/tw_crypto/src/crypto_scrypt/params.rs create mode 100644 rust/tw_crypto/src/crypto_scrypt/romix.rs create mode 100644 rust/tw_crypto/src/ffi/crypto_scrypt.rs create mode 100644 rust/tw_crypto/src/ffi/mod.rs create mode 100644 rust/tw_crypto/src/lib.rs create mode 100644 rust/tw_crypto/tests/crypto_scrypt.rs create mode 100644 rust/tw_crypto/tests/crypto_scrypt_ffi.rs diff --git a/.github/workflows/codegen-v2.yml b/.github/workflows/codegen-v2.yml index 8438d788d56..d7bde064e0d 100644 --- a/.github/workflows/codegen-v2.yml +++ b/.github/workflows/codegen-v2.yml @@ -21,7 +21,7 @@ jobs: tools/install-sys-dependencies-linux - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.3 + uses: mozilla-actions/sccache-action@v0.0.8 - name: Install Rust dependencies run: | diff --git a/.github/workflows/linux-ci-rust.yml b/.github/workflows/linux-ci-rust.yml index 377c60ee23d..d2940428273 100644 --- a/.github/workflows/linux-ci-rust.yml +++ b/.github/workflows/linux-ci-rust.yml @@ -29,7 +29,7 @@ jobs: tools/install-sys-dependencies-linux - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.3 + uses: mozilla-actions/sccache-action@v0.0.8 - name: Cache Rust uses: Swatinem/rust-cache@v2 @@ -74,7 +74,7 @@ jobs: tools/install-sys-dependencies-linux - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.3 + uses: mozilla-actions/sccache-action@v0.0.8 - name: Cache Rust uses: Swatinem/rust-cache@v2 @@ -105,7 +105,7 @@ jobs: tools/install-sys-dependencies-mac - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.3 + uses: mozilla-actions/sccache-action@v0.0.8 - name: Cache Rust uses: Swatinem/rust-cache@v2 diff --git a/rust/Cargo.lock b/rust/Cargo.lock index b60b8a02478..95548d44aa7 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -62,7 +62,7 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "digest 0.10.6", + "digest 0.10.7", "itertools", "num-bigint", "num-traits", @@ -101,7 +101,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ "ark-std", - "digest 0.10.6", + "digest 0.10.7", "num-bigint", ] @@ -267,7 +267,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -516,7 +516,7 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest 0.10.6", + "digest 0.10.7", "fiat-crypto", "rustc_version", "subtle", @@ -600,9 +600,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.3", "const-oid", @@ -617,7 +617,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a48e5d537b8a30c0b023116d981b16334be1485af7ca68db3a2b7024cbc957fd" dependencies = [ "der", - "digest 0.10.6", + "digest 0.10.7", "elliptic-curve", "rfc6979", "signature", @@ -637,7 +637,7 @@ checksum = "75c71eaa367f2e5d556414a8eea812bc62985c879748d6403edabd9cb03f16e7" dependencies = [ "base16ct", "crypto-bigint", - "digest 0.10.6", + "digest 0.10.7", "ff", "generic-array", "group", @@ -739,7 +739,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343cfc165f92a988fd60292f7a0bfde4352a5a0beff9fbec29251ca4e9676e4d" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -807,7 +807,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -1119,6 +1119,16 @@ dependencies = [ "nom", ] +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -1331,7 +1341,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -1493,18 +1503,18 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -1513,7 +1523,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "keccak", ] @@ -1523,7 +1533,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "rand_core", ] @@ -1935,6 +1945,18 @@ dependencies = [ "tw_proto", ] +[[package]] +name = "tw_crypto" +version = "0.1.0" +dependencies = [ + "pbkdf2", + "salsa20", + "sha2", + "tw_encoding", + "tw_memory", + "tw_misc", +] + [[package]] name = "tw_decred" version = "0.1.0" @@ -2037,7 +2059,7 @@ dependencies = [ "arbitrary 1.3.0", "blake-hash", "blake2b-ref", - "digest 0.10.6", + "digest 0.10.7", "groestl", "hmac", "ripemd", @@ -2076,7 +2098,7 @@ dependencies = [ "crypto_box", "curve25519-dalek", "der", - "digest 0.10.6", + "digest 0.10.7", "ecdsa", "k256", "lazy_static", @@ -2515,6 +2537,7 @@ dependencies = [ "tw_any_coin", "tw_bitcoin", "tw_coin_registry", + "tw_crypto", "tw_encoding", "tw_ethereum", "tw_hash", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 09ab546a720..ab72b60c61b 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -37,6 +37,7 @@ members = [ "tw_evm", "tw_hash", "tw_keypair", + "tw_crypto", "tw_macros", "tw_memory", "tw_misc", @@ -56,11 +57,14 @@ panic = "abort" [profile.wasm-test] inherits = "release" -# Fixes an incredibly slow compilation of `curve25519-dalek` package. -opt-level = 1 +opt-level = 3 debug = true debug-assertions = true overflow-checks = true +# Fixes an incredibly slow compilation of `curve25519-dalek` package. +[profile.wasm-test.package.curve25519-dalek] +opt-level = 1 + [profile.release.package.curve25519-dalek] opt-level = 2 diff --git a/rust/tw_crypto/Cargo.toml b/rust/tw_crypto/Cargo.toml new file mode 100644 index 00000000000..57032eed9b3 --- /dev/null +++ b/rust/tw_crypto/Cargo.toml @@ -0,0 +1,13 @@ + +[package] +name = "tw_crypto" +version = "0.1.0" +edition = "2021" + +[dependencies] +pbkdf2 = "0.12.2" +salsa20 = "0.10.2" +sha2 = "0.10.8" +tw_encoding = { path = "../tw_encoding" } +tw_memory = { path = "../tw_memory" } +tw_misc = { path = "../tw_misc" } diff --git a/rust/tw_crypto/fuzz/.gitignore b/rust/tw_crypto/fuzz/.gitignore new file mode 100644 index 00000000000..5c404b9583f --- /dev/null +++ b/rust/tw_crypto/fuzz/.gitignore @@ -0,0 +1,5 @@ +target +corpus +artifacts +coverage +Cargo.lock diff --git a/rust/tw_crypto/fuzz/Cargo.toml b/rust/tw_crypto/fuzz/Cargo.toml new file mode 100644 index 00000000000..e24a3f503f1 --- /dev/null +++ b/rust/tw_crypto/fuzz/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "tw_crypto-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = { version = "0.4", features = ["arbitrary-derive"] } + +[dependencies.tw_crypto] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "crypto_scrypt" +path = "fuzz_targets/crypto_scrypt.rs" +test = false +doc = false diff --git a/rust/tw_crypto/fuzz/fuzz_targets/crypto_scrypt.rs b/rust/tw_crypto/fuzz/fuzz_targets/crypto_scrypt.rs new file mode 100644 index 00000000000..3bccbdcf800 --- /dev/null +++ b/rust/tw_crypto/fuzz/fuzz_targets/crypto_scrypt.rs @@ -0,0 +1,36 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![no_main] + +use libfuzzer_sys::{arbitrary, fuzz_target}; +use tw_crypto::crypto_scrypt::{params::Params, scrypt}; + +#[derive(arbitrary::Arbitrary, Debug)] +struct ScryptInput<'a> { + password: &'a [u8], + salt: &'a [u8], + log_n: u8, + r: u32, + p: u32, + desired_len: usize, +} + +fuzz_target!(|input: ScryptInput<'_>| { + // Greater r, p parameters make `scrypt` incredibly slow. + if input.r > 16 || input.p > 16 { + return; + } + + let params = Params { + log_n: input.log_n, + r: input.r, + p: input.p, + desired_len: input.desired_len, + }; + + let _ = scrypt(input.password, input.salt, ¶ms); +}); diff --git a/rust/tw_crypto/src/crypto_scrypt/mod.rs b/rust/tw_crypto/src/crypto_scrypt/mod.rs new file mode 100644 index 00000000000..8e0b475d853 --- /dev/null +++ b/rust/tw_crypto/src/crypto_scrypt/mod.rs @@ -0,0 +1,42 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::crypto_scrypt::params::{InvalidParams, Params}; +use pbkdf2::pbkdf2_hmac; +use sha2::Sha256; + +pub mod params; +mod romix; + +/// The scrypt key derivation function. +/// Original: https://github.com/RustCrypto/password-hashes/blob/a737bef1f992368f165face097d621bb1e76eba4/scrypt/src/lib.rs#L89 +/// +/// The only reason we should have rewritten the function is that it does unnecessary `log_n >= r * 16` check: +/// https://github.com/RustCrypto/password-hashes/blob/a737bef1f992368f165face097d621bb1e76eba4/scrypt/src/params.rs#L67-L72 +pub fn scrypt(password: &[u8], salt: &[u8], params: &Params) -> Result, InvalidParams> { + params.check_params()?; + + // The checks in the `Params::check_params` guarantee + // that the following is safe: + let n = params.n as usize; + let r128 = (params.r as usize) * 128; + let pr128 = (params.p as usize) * r128; + let nr128 = n * r128; + + let mut b = vec![0u8; pr128]; + pbkdf2_hmac::(password, salt, 1, &mut b); + + let mut v = vec![0u8; nr128]; + let mut t = vec![0u8; r128]; + + for chunk in &mut b.chunks_mut(r128) { + romix::scrypt_ro_mix(chunk, &mut v, &mut t, n); + } + + let mut output = vec![0u8; params.desired_len]; + pbkdf2_hmac::(password, &b, 1, &mut output); + Ok(output) +} diff --git a/rust/tw_crypto/src/crypto_scrypt/params.rs b/rust/tw_crypto/src/crypto_scrypt/params.rs new file mode 100644 index 00000000000..e4a76d43e3f --- /dev/null +++ b/rust/tw_crypto/src/crypto_scrypt/params.rs @@ -0,0 +1,94 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::mem::size_of; + +#[derive(Clone, Copy, Debug)] +pub struct InvalidParams; + +pub struct Params { + /// Scrypt parameter `N`: CPU/memory cost. + /// Must be a power of 2. + pub n: u32, + /// Scrypt parameter `r`: block size. + pub r: u32, + /// Scrypt parameter `p`: parallelism. + pub p: u32, + /// Scrypt parameter `Key length`. + pub desired_len: usize, +} + +impl Params { + /// Create a new instance of [`Params`]. + /// + /// # Conditions + /// - `log_n` must be less than `64` + /// - `r` must be greater than `0` and less than or equal to `4294967295` + /// - `p` must be greater than `0` and less than `4294967295` + /// - `desired_len` must be greater than `9` and less than or equal to `64` + /// + /// Original: https://github.com/RustCrypto/password-hashes/blob/a737bef1f992368f165face097d621bb1e76eba4/scrypt/src/params.rs#L44 + /// + /// The only reason we should have rewritten the function is that it does unnecessary `log_n >= r * 16` check: + /// https://github.com/RustCrypto/password-hashes/blob/a737bef1f992368f165face097d621bb1e76eba4/scrypt/src/params.rs#L67-L72 + pub fn check_params(&self) -> Result<(), InvalidParams> { + let log_n = self.try_log_n()?; + + let cond1 = (log_n as usize) < usize::BITS as usize; + let cond2 = size_of::() >= size_of::(); + let cond3 = self.r <= usize::MAX as u32 && self.p < usize::MAX as u32; + let cond4 = (10..=64).contains(&self.desired_len); + if !(self.r > 0 && self.p > 0 && cond1 && (cond2 || cond3) && cond4) { + return Err(InvalidParams); + } + + let r = self.r as usize; + let p = self.p as usize; + let n = self.n as usize; + + // check that r * 128 doesn't overflow + let r128 = r.checked_mul(128).ok_or(InvalidParams)?; + + // check that n * r * 128 doesn't overflow + r128.checked_mul(n).ok_or(InvalidParams)?; + + // check that p * r * 128 doesn't overflow + r128.checked_mul(p).ok_or(InvalidParams)?; + + // This check required by Scrypt: + // check: p <= ((2^32-1) * 32) / (128 * r) + // It takes a bit of re-arranging to get the check above into this form, + // but it is indeed the same. + if r * p >= 0x4000_0000 { + return Err(InvalidParams); + } + + // The following checks are copied from C++: + // https://github.com/trustwallet/wallet-core/blob/b530432921d7a9709428b0162673e0ab72de1c3d/src/Keystore/ScryptParameters.cpp#L27-L42 + + if (n & (n - 1)) != 0 || n < 2 { + // Invalid cost factor. + return Err(InvalidParams); + } + + let max_r = u32::MAX as usize / 128_usize / p; + let max_n = u32::MAX as usize / 128 / r; + if r > max_r || n > max_n { + return Err(InvalidParams); + } + + Ok(()) + } + + fn try_log_n(&self) -> Result { + let log_n = self.n.checked_ilog2().ok_or(InvalidParams)?; + // `Params::n` must be equal to 2^log_n. + if 1 << log_n != self.n { + return Err(InvalidParams); + } + log_n.try_into().map_err(|_| InvalidParams) + } +} diff --git a/rust/tw_crypto/src/crypto_scrypt/romix.rs b/rust/tw_crypto/src/crypto_scrypt/romix.rs new file mode 100644 index 00000000000..6eab91877bc --- /dev/null +++ b/rust/tw_crypto/src/crypto_scrypt/romix.rs @@ -0,0 +1,81 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +//! Original: https://github.com/RustCrypto/password-hashes/blob/master/scrypt/src/romix.rs +/// Execute the ROMix operation in-place. +/// b - the data to operate on +/// v - a temporary variable to store the vector V +/// t - a temporary variable to store the result of the xor +/// n - the scrypt parameter N +#[allow(clippy::many_single_char_names)] +pub(crate) fn scrypt_ro_mix(b: &mut [u8], v: &mut [u8], t: &mut [u8], n: usize) { + fn integerify(x: &[u8], n: usize) -> usize { + // n is a power of 2, so n - 1 gives us a bitmask that we can use to perform a calculation + // mod n using a simple bitwise and. + let mask = n - 1; + // This cast is safe since we're going to get the value mod n (which is a power of 2), so we + // don't have to care about truncating any of the high bits off + //let result = (LittleEndian::read_u32(&x[x.len() - 64..x.len() - 60]) as usize) & mask; + let t = u32::from_le_bytes(x[x.len() - 64..x.len() - 60].try_into().unwrap()); + (t as usize) & mask + } + + let len = b.len(); + + for chunk in v.chunks_mut(len) { + chunk.copy_from_slice(b); + scrypt_block_mix(chunk, b); + } + + for _ in 0..n { + let j = integerify(b, n); + xor(b, &v[j * len..(j + 1) * len], t); + scrypt_block_mix(t, b); + } +} + +/// Execute the BlockMix operation +/// input - the input vector. The length must be a multiple of 128. +/// output - the output vector. Must be the same length as input. +fn scrypt_block_mix(input: &[u8], output: &mut [u8]) { + use salsa20::{ + cipher::{typenum::U4, StreamCipherCore}, + SalsaCore, + }; + + type Salsa20_8 = SalsaCore; + + let mut x = [0u8; 64]; + x.copy_from_slice(&input[input.len() - 64..]); + + let mut t = [0u8; 64]; + + for (i, chunk) in input.chunks(64).enumerate() { + xor(&x, chunk, &mut t); + + let mut t2 = [0u32; 16]; + + for (c, b) in t.chunks_exact(4).zip(t2.iter_mut()) { + *b = u32::from_le_bytes(c.try_into().unwrap()); + } + + Salsa20_8::from_raw_state(t2).write_keystream_block((&mut x).into()); + + let pos = if i % 2 == 0 { + (i / 2) * 64 + } else { + (i / 2) * 64 + input.len() / 2 + }; + + output[pos..pos + 64].copy_from_slice(&x); + } +} + +fn xor(x: &[u8], y: &[u8], output: &mut [u8]) { + for ((out, &x_i), &y_i) in output.iter_mut().zip(x.iter()).zip(y.iter()) { + *out = x_i ^ y_i; + } +} diff --git a/rust/tw_crypto/src/ffi/crypto_scrypt.rs b/rust/tw_crypto/src/ffi/crypto_scrypt.rs new file mode 100644 index 00000000000..9ba61dd6a99 --- /dev/null +++ b/rust/tw_crypto/src/ffi/crypto_scrypt.rs @@ -0,0 +1,65 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![allow(clippy::missing_safety_doc)] + +use crate::crypto_scrypt::params::Params; +use crate::crypto_scrypt::scrypt; +use tw_memory::ffi::c_byte_array::{CByteArray, CByteArrayResult}; +use tw_memory::ffi::c_byte_array_ref::CByteArrayRef; +use tw_memory::ffi::c_result::ErrorCode; + +#[repr(C)] +pub enum ScryptError { + Ok = 0, + InvalidParams = 1, +} + +impl From for ErrorCode { + fn from(error: ScryptError) -> Self { + error as ErrorCode + } +} + +/// The scrypt key derivation function. +/// +/// \param password *nullable* byte array. +/// \param password_len the length of the `password` array. +/// \param salt *nullable* byte array. +/// \param salt_len the length of the `salt` array. +/// \param n scrypt parameter `N`: CPU/memory cost. +/// \param r scrypt parameter `r`: block size. +/// \param p scrypt parameter `p`: parallelism. +/// \param desired_len scrypt parameter `Key length`. +/// \return C-compatible byte array. +#[no_mangle] +pub unsafe extern "C" fn crypto_scrypt( + password: *const u8, + password_len: usize, + salt: *const u8, + salt_len: usize, + n: u32, + r: u32, + p: u32, + desired_len: usize, +) -> CByteArrayResult { + let password_ref = CByteArrayRef::new(password, password_len); + let password = password_ref.as_slice().unwrap_or_default(); + + let salt_ref = CByteArrayRef::new(salt, salt_len); + let salt = salt_ref.as_slice().unwrap_or_default(); + + let params = Params { + n, + r, + p, + desired_len, + }; + scrypt(password, salt, ¶ms) + .map(CByteArray::from) + .map_err(|_| ScryptError::InvalidParams) + .into() +} diff --git a/rust/tw_crypto/src/ffi/mod.rs b/rust/tw_crypto/src/ffi/mod.rs new file mode 100644 index 00000000000..f74c45f578b --- /dev/null +++ b/rust/tw_crypto/src/ffi/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod crypto_scrypt; diff --git a/rust/tw_crypto/src/lib.rs b/rust/tw_crypto/src/lib.rs new file mode 100644 index 00000000000..281790c771c --- /dev/null +++ b/rust/tw_crypto/src/lib.rs @@ -0,0 +1,8 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod crypto_scrypt; +pub mod ffi; diff --git a/rust/tw_crypto/tests/crypto_scrypt.rs b/rust/tw_crypto/tests/crypto_scrypt.rs new file mode 100644 index 00000000000..4dc59443e0b --- /dev/null +++ b/rust/tw_crypto/tests/crypto_scrypt.rs @@ -0,0 +1,44 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_crypto::crypto_scrypt::params::Params; +use tw_crypto::crypto_scrypt::scrypt; +use tw_encoding::hex; + +#[test] +fn test_scrypt() { + let password = hex::decode("70617373776f7264").unwrap(); + let salt = + hex::decode("80132842c6cde8f9d04582932ef92c3cad3ba6b41e1296ef681692372886db86").unwrap(); + + let params = Params { + n: 1 << 12, + r: 8, + p: 6, + desired_len: 32, + }; + let res = scrypt(&password, &salt, ¶ms).unwrap(); + assert_eq!( + hex::encode(&res, false), + "1217705511f43b7d2faea767a156a9946c579b3436ba27252a73278a7162cedc" + ); +} + +#[test] +fn test_scrypt_invalid_n() { + let password = hex::decode("70617373776f7264").unwrap(); + let salt = + hex::decode("80132842c6cde8f9d04582932ef92c3cad3ba6b41e1296ef681692372886db86").unwrap(); + + let params = Params { + // Must be a power of 2. + n: (1 << 12) + 1, + r: 8, + p: 6, + desired_len: 32, + }; + scrypt(&password, &salt, ¶ms).unwrap_err(); +} diff --git a/rust/tw_crypto/tests/crypto_scrypt_ffi.rs b/rust/tw_crypto/tests/crypto_scrypt_ffi.rs new file mode 100644 index 00000000000..1deb0fb2b79 --- /dev/null +++ b/rust/tw_crypto/tests/crypto_scrypt_ffi.rs @@ -0,0 +1,36 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_crypto::ffi::crypto_scrypt::crypto_scrypt; +use tw_encoding::hex; +use tw_memory::ffi::c_byte_array::CByteArray; + +#[test] +fn test_crypto_scrypt_ffi_null_salt() { + let password = hex::decode("70617373776f7264").unwrap(); + let password = CByteArray::from(password); + + let salt = CByteArray::null(); + + let res = unsafe { + crypto_scrypt( + password.data(), + password.size(), + salt.data(), + salt.size(), + 16384, + 8, + 4, + 32, + ) + } + .unwrap(); + let data = unsafe { res.into_vec() }; + assert_eq!( + hex::encode(&data, false), + "004f57df809101216a343d6215879a9a7f1d7e2c04ef2845b4494cf5f10181a1" + ); +} diff --git a/rust/wallet_core_rs/Cargo.toml b/rust/wallet_core_rs/Cargo.toml index c66a335b140..bacaf4b68f2 100644 --- a/rust/wallet_core_rs/Cargo.toml +++ b/rust/wallet_core_rs/Cargo.toml @@ -13,6 +13,7 @@ default = [ "bitcoin", "ethereum", "keypair", + "crypto", "solana", "ton", "utils", @@ -21,6 +22,7 @@ any-coin = ["tw_any_coin"] bitcoin = ["tw_bitcoin", "tw_coin_registry"] ethereum = ["tw_ethereum", "tw_coin_registry"] keypair = ["tw_keypair"] +crypto = ["tw_crypto"] solana = ["tw_solana"] ton = ["tw_ton"] utils = [ @@ -41,6 +43,7 @@ tw_encoding = { path = "../tw_encoding", optional = true } tw_ethereum = { path = "../chains/tw_ethereum", optional = true } tw_hash = { path = "../tw_hash", optional = true } tw_keypair = { path = "../tw_keypair", optional = true } +tw_crypto = { path = "../tw_crypto", optional = true } tw_memory = { path = "../tw_memory", optional = true } tw_number = { path = "../tw_number", optional = true } tw_macros = { path = "../tw_macros" } diff --git a/rust/wallet_core_rs/cbindgen.toml b/rust/wallet_core_rs/cbindgen.toml index 6697f798a56..43458d4b26d 100644 --- a/rust/wallet_core_rs/cbindgen.toml +++ b/rust/wallet_core_rs/cbindgen.toml @@ -11,6 +11,7 @@ extra_bindings = [ "tw_encoding", "tw_hash", "tw_keypair", + "tw_crypto", "tw_memory", ] include = [ @@ -18,5 +19,6 @@ include = [ "tw_encoding", "tw_hash", "tw_keypair", + "tw_crypto", "tw_memory", ] diff --git a/rust/wallet_core_rs/src/lib.rs b/rust/wallet_core_rs/src/lib.rs index f26f8e9364e..449bb15050e 100644 --- a/rust/wallet_core_rs/src/lib.rs +++ b/rust/wallet_core_rs/src/lib.rs @@ -6,6 +6,8 @@ pub extern crate tw_any_coin; #[cfg(feature = "bitcoin")] pub extern crate tw_bitcoin; +#[cfg(feature = "crypto")] +pub extern crate tw_crypto; #[cfg(feature = "utils")] pub extern crate tw_encoding; #[cfg(feature = "utils")] diff --git a/src/Keystore/EncryptionParameters.cpp b/src/Keystore/EncryptionParameters.cpp index db34105c024..56326bb01b6 100644 --- a/src/Keystore/EncryptionParameters.cpp +++ b/src/Keystore/EncryptionParameters.cpp @@ -8,8 +8,8 @@ #include #include -#include #include +#include using namespace TW; @@ -37,6 +37,23 @@ static const auto kdfParams = "kdfparams"; static const auto mac = "mac"; } // namespace CodingKeys +static Data rustScrypt(const Data& password, const ScryptParameters& params) { + Rust::CByteArrayResultWrapper res = Rust::crypto_scrypt( + password.data(), + password.size(), + params.salt.data(), + params.salt.size(), + params.n, + params.r, + params.p, + params.desiredKeyLength + ); + if (!res.isOk()) { + throw std::runtime_error("Invalid scrypt parameters"); + } + return res.unwrap().data; +} + EncryptionParameters::EncryptionParameters(const nlohmann::json& json) { auto cipher = json[CodingKeys::cipher].get(); cipherParams = AESParameters::AESParametersFromJson(json[CodingKeys::cipherParams], cipher); @@ -68,10 +85,7 @@ nlohmann::json EncryptionParameters::json() const { EncryptedPayload::EncryptedPayload(const Data& password, const Data& data, const EncryptionParameters& params) : params(std::move(params)), _mac() { auto scryptParams = std::get(this->params.kdfParams); - auto derivedKey = Data(scryptParams.desiredKeyLength); - scrypt(reinterpret_cast(password.data()), password.size(), scryptParams.salt.data(), - scryptParams.salt.size(), scryptParams.n, scryptParams.r, scryptParams.p, derivedKey.data(), - scryptParams.desiredKeyLength); + auto derivedKey = rustScrypt(password, scryptParams); aes_encrypt_ctx ctx; auto result = 0; @@ -106,10 +120,7 @@ Data EncryptedPayload::decrypt(const Data& password) const { auto mac = Data(); if (auto* scryptParams = std::get_if(¶ms.kdfParams); scryptParams) { - derivedKey.resize(scryptParams->defaultDesiredKeyLength); - scrypt(password.data(), password.size(), scryptParams->salt.data(), - scryptParams->salt.size(), scryptParams->n, scryptParams->r, scryptParams->p, derivedKey.data(), - scryptParams->defaultDesiredKeyLength); + derivedKey = rustScrypt(password, *scryptParams); mac = computeMAC(derivedKey.end() - params.getKeyBytesSize(), derivedKey.end(), encrypted); } else if (auto* pbkdf2Params = std::get_if(¶ms.kdfParams); pbkdf2Params) { derivedKey.resize(pbkdf2Params->defaultDesiredKeyLength); diff --git a/tools/rust-test b/tools/rust-test index 05cb5f47638..895c64c9e26 100755 --- a/tools/rust-test +++ b/tools/rust-test @@ -18,7 +18,10 @@ if [[ "$1" == "wasm" ]]; then source ../emsdk/emsdk_env.sh export CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER=node - cargo test --target wasm32-unknown-emscripten --profile wasm-test --workspace --exclude wallet_core_bin + # Increase the `TOTAL_MEMORY` value if required. + CONFIG='build.rustflags=["-C","link-arg=-s","-C","link-arg=TOTAL_MEMORY=33554432"]' + + cargo --config "$CONFIG" test --target wasm32-unknown-emscripten --profile wasm-test --workspace --exclude wallet_core_bin elif [[ "$1" == "doc" ]]; then cargo test --doc else From 2aaffee74aca9f83e3f2fd96940e61f4824aaff3 Mon Sep 17 00:00:00 2001 From: gupnik Date: Thu, 17 Apr 2025 14:16:32 +0530 Subject: [PATCH 19/23] Migrates `pbkdf2.c` to rust (#4367) * Migrates `scrypt.c` to Rust * Migrates to v4 cache * Revert "Migrates to v4 cache" This reverts commit 6113eb571d330a14ec984902993ae744cc4709d0. * Updates sccache action * Uses correct version * Fixes FFI test * Adds keystore to default * Fixes Tests * Clippy fix * Updates memory for wasm * Actually use the config * Renames tw_keystore to tw_crypto and updates test * FMT * Migrates `pbkdf2.c` to rust * Addresses review comment * Minor * Use tw_ffi to expose FFIs * Use FFI generator * Trigger Build * Uses non null data * Renames --- codegen-v2/src/codegen/cpp/code_gen_types.rs | 1 + rust/Cargo.lock | 1 + rust/tw_crypto/Cargo.toml | 4 ++ rust/tw_crypto/src/crypto_pbkdf2/mod.rs | 19 +++++++++ rust/tw_crypto/src/ffi/crypto_pbkdf2.rs | 37 ++++++++++++++++ rust/tw_crypto/src/ffi/crypto_scrypt.rs | 35 +++++++--------- rust/tw_crypto/src/ffi/mod.rs | 1 + rust/tw_crypto/src/lib.rs | 1 + rust/tw_crypto/tests/crypto_pbkdf2.rs | 34 +++++++++++++++ rust/tw_crypto/tests/crypto_pbkdf2_ffi.rs | 44 ++++++++++++++++++++ rust/tw_crypto/tests/crypto_scrypt_ffi.rs | 26 ++++-------- src/Keystore/EncryptionParameters.cpp | 41 ++++++++++++------ 12 files changed, 194 insertions(+), 50 deletions(-) create mode 100644 rust/tw_crypto/src/crypto_pbkdf2/mod.rs create mode 100644 rust/tw_crypto/src/ffi/crypto_pbkdf2.rs create mode 100644 rust/tw_crypto/tests/crypto_pbkdf2.rs create mode 100644 rust/tw_crypto/tests/crypto_pbkdf2_ffi.rs diff --git a/codegen-v2/src/codegen/cpp/code_gen_types.rs b/codegen-v2/src/codegen/cpp/code_gen_types.rs index 555fa8b8e88..257b20863fb 100644 --- a/codegen-v2/src/codegen/cpp/code_gen_types.rs +++ b/codegen-v2/src/codegen/cpp/code_gen_types.rs @@ -128,6 +128,7 @@ impl TWType { "u16" => "uint16_t".to_string(), "u32" => "uint32_t".to_string(), "u64" => "uint64_t".to_string(), + "usize" => "size_t".to_string(), "i8" => "int8_t".to_string(), "i16" => "int16_t".to_string(), "i32" => "int32_t".to_string(), diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 95548d44aa7..0285f7d1b0a 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1953,6 +1953,7 @@ dependencies = [ "salsa20", "sha2", "tw_encoding", + "tw_macros", "tw_memory", "tw_misc", ] diff --git a/rust/tw_crypto/Cargo.toml b/rust/tw_crypto/Cargo.toml index 57032eed9b3..adf2186dabd 100644 --- a/rust/tw_crypto/Cargo.toml +++ b/rust/tw_crypto/Cargo.toml @@ -9,5 +9,9 @@ pbkdf2 = "0.12.2" salsa20 = "0.10.2" sha2 = "0.10.8" tw_encoding = { path = "../tw_encoding" } +tw_macros = { path = "../tw_macros" } tw_memory = { path = "../tw_memory" } tw_misc = { path = "../tw_misc" } + +[dev-dependencies] +tw_memory = { path = "../tw_memory", features = ["test-utils"] } diff --git a/rust/tw_crypto/src/crypto_pbkdf2/mod.rs b/rust/tw_crypto/src/crypto_pbkdf2/mod.rs new file mode 100644 index 00000000000..9fd1363a0dd --- /dev/null +++ b/rust/tw_crypto/src/crypto_pbkdf2/mod.rs @@ -0,0 +1,19 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use pbkdf2::pbkdf2_hmac; +use sha2::Sha256; + +pub fn pbkdf2_hmac_sha256( + password: &[u8], + salt: &[u8], + iterations: u32, + desired_len: usize, +) -> Vec { + let mut output = vec![0u8; desired_len]; + pbkdf2_hmac::(password, salt, iterations, &mut output); + output +} diff --git a/rust/tw_crypto/src/ffi/crypto_pbkdf2.rs b/rust/tw_crypto/src/ffi/crypto_pbkdf2.rs new file mode 100644 index 00000000000..47478df2e40 --- /dev/null +++ b/rust/tw_crypto/src/ffi/crypto_pbkdf2.rs @@ -0,0 +1,37 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![allow(clippy::missing_safety_doc)] + +use crate::crypto_pbkdf2::pbkdf2_hmac_sha256; +use tw_macros::tw_ffi; +use tw_memory::ffi::{tw_data::TWData, Nonnull, NullableMut, RawPtrTrait}; + +/// The PBKDF2 key derivation function. +/// +/// \param password data. +/// \param salt data. +/// \param iterations PBKDF2 parameter `iterations`. +/// \param desired_len PBKDF2 parameter `desired_len`. +/// \return *nullable* data. +#[tw_ffi(ty = static_function, class = TWCrypto, name = PBKDF2)] +#[no_mangle] +pub unsafe extern "C" fn crypto_pbkdf2( + password: Nonnull, + salt: Nonnull, + iterations: u32, + desired_len: usize, +) -> NullableMut { + let password = TWData::from_ptr_as_ref(password) + .map(|data| data.as_slice()) + .unwrap_or_default(); + let salt = TWData::from_ptr_as_ref(salt) + .map(|data| data.as_slice()) + .unwrap_or_default(); + + let output = pbkdf2_hmac_sha256(password, salt, iterations, desired_len); + TWData::from(output).into_ptr() +} diff --git a/rust/tw_crypto/src/ffi/crypto_scrypt.rs b/rust/tw_crypto/src/ffi/crypto_scrypt.rs index 9ba61dd6a99..295a0d60767 100644 --- a/rust/tw_crypto/src/ffi/crypto_scrypt.rs +++ b/rust/tw_crypto/src/ffi/crypto_scrypt.rs @@ -8,9 +8,9 @@ use crate::crypto_scrypt::params::Params; use crate::crypto_scrypt::scrypt; -use tw_memory::ffi::c_byte_array::{CByteArray, CByteArrayResult}; -use tw_memory::ffi::c_byte_array_ref::CByteArrayRef; +use tw_macros::tw_ffi; use tw_memory::ffi::c_result::ErrorCode; +use tw_memory::ffi::{tw_data::TWData, Nonnull, NullableMut, RawPtrTrait}; #[repr(C)] pub enum ScryptError { @@ -26,31 +26,29 @@ impl From for ErrorCode { /// The scrypt key derivation function. /// -/// \param password *nullable* byte array. -/// \param password_len the length of the `password` array. -/// \param salt *nullable* byte array. -/// \param salt_len the length of the `salt` array. +/// \param password data. +/// \param salt data. /// \param n scrypt parameter `N`: CPU/memory cost. /// \param r scrypt parameter `r`: block size. /// \param p scrypt parameter `p`: parallelism. /// \param desired_len scrypt parameter `Key length`. /// \return C-compatible byte array. +#[tw_ffi(ty = static_function, class = TWCrypto, name = Scrypt)] #[no_mangle] pub unsafe extern "C" fn crypto_scrypt( - password: *const u8, - password_len: usize, - salt: *const u8, - salt_len: usize, + password: Nonnull, + salt: Nonnull, n: u32, r: u32, p: u32, desired_len: usize, -) -> CByteArrayResult { - let password_ref = CByteArrayRef::new(password, password_len); - let password = password_ref.as_slice().unwrap_or_default(); - - let salt_ref = CByteArrayRef::new(salt, salt_len); - let salt = salt_ref.as_slice().unwrap_or_default(); +) -> NullableMut { + let password = TWData::from_ptr_as_ref(password) + .map(|data| data.as_slice()) + .unwrap_or_default(); + let salt = TWData::from_ptr_as_ref(salt) + .map(|data| data.as_slice()) + .unwrap_or_default(); let params = Params { n, @@ -59,7 +57,6 @@ pub unsafe extern "C" fn crypto_scrypt( desired_len, }; scrypt(password, salt, ¶ms) - .map(CByteArray::from) - .map_err(|_| ScryptError::InvalidParams) - .into() + .map(|output| TWData::from(output).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) } diff --git a/rust/tw_crypto/src/ffi/mod.rs b/rust/tw_crypto/src/ffi/mod.rs index f74c45f578b..d9743335122 100644 --- a/rust/tw_crypto/src/ffi/mod.rs +++ b/rust/tw_crypto/src/ffi/mod.rs @@ -4,4 +4,5 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +pub mod crypto_pbkdf2; pub mod crypto_scrypt; diff --git a/rust/tw_crypto/src/lib.rs b/rust/tw_crypto/src/lib.rs index 281790c771c..cbf63fbb35c 100644 --- a/rust/tw_crypto/src/lib.rs +++ b/rust/tw_crypto/src/lib.rs @@ -4,5 +4,6 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +pub mod crypto_pbkdf2; pub mod crypto_scrypt; pub mod ffi; diff --git a/rust/tw_crypto/tests/crypto_pbkdf2.rs b/rust/tw_crypto/tests/crypto_pbkdf2.rs new file mode 100644 index 00000000000..709ca804dac --- /dev/null +++ b/rust/tw_crypto/tests/crypto_pbkdf2.rs @@ -0,0 +1,34 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_crypto::crypto_pbkdf2::pbkdf2_hmac_sha256; +use tw_encoding::{base64, base64::STANDARD, hex}; + +#[test] +fn test_pbkdf2_hmac_sha256() { + let password = hex::decode("70617373776f7264").unwrap(); + let salt = hex::decode("73616C74").unwrap(); + + let data = pbkdf2_hmac_sha256(&password, &salt, 1, 20); + assert_eq!( + hex::encode(&data, false), + "120fb6cffcf8b32c43e7225256c4f837a86548c9" + ); + + let data = pbkdf2_hmac_sha256(&password, &salt, 4096, 20); + assert_eq!( + hex::encode(&data, false), + "c5e478d59288c841aa530db6845c4c8d962893a0" + ); + + let salt2 = base64::decode("kNHS+Mx//slRsmLF9396HQ==", STANDARD).unwrap(); + + let data = pbkdf2_hmac_sha256(&password, &salt2, 100, 32); + assert_eq!( + hex::encode(&data, false), + "9cf33ebd3542c691fac6f61609a8d13355a0adf4d15eed77cc9d13f792b77c3a" + ); +} diff --git a/rust/tw_crypto/tests/crypto_pbkdf2_ffi.rs b/rust/tw_crypto/tests/crypto_pbkdf2_ffi.rs new file mode 100644 index 00000000000..9efc1d2b538 --- /dev/null +++ b/rust/tw_crypto/tests/crypto_pbkdf2_ffi.rs @@ -0,0 +1,44 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_crypto::ffi::crypto_pbkdf2::crypto_pbkdf2; +use tw_encoding::{base64, base64::STANDARD, hex}; +use tw_memory::ffi::tw_data::TWData; +use tw_memory::ffi::RawPtrTrait; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; + +#[test] +fn test_crypto_pbkdf2_ffi() { + let password = hex::decode("70617373776f7264").unwrap(); + let password = TWDataHelper::create(password); + + let salt = hex::decode("73616C74").unwrap(); + let salt = TWDataHelper::create(salt); + + let res = unsafe { crypto_pbkdf2(password.ptr(), salt.ptr(), 1, 20) }; + let res = unsafe { TWData::from_ptr_as_mut(res).unwrap() }; + assert_eq!( + hex::encode(res.to_vec(), false), + "120fb6cffcf8b32c43e7225256c4f837a86548c9" + ); + + let res = unsafe { crypto_pbkdf2(password.ptr(), salt.ptr(), 4096, 20) }; + let res = unsafe { TWData::from_ptr_as_mut(res).unwrap() }; + assert_eq!( + hex::encode(res.to_vec(), false), + "c5e478d59288c841aa530db6845c4c8d962893a0" + ); + + let salt2 = base64::decode("kNHS+Mx//slRsmLF9396HQ==", STANDARD).unwrap(); + let salt2 = TWDataHelper::create(salt2); + + let res = unsafe { crypto_pbkdf2(password.ptr(), salt2.ptr(), 100, 32) }; + let res = unsafe { TWData::from_ptr_as_mut(res).unwrap() }; + assert_eq!( + hex::encode(res.to_vec(), false), + "9cf33ebd3542c691fac6f61609a8d13355a0adf4d15eed77cc9d13f792b77c3a" + ); +} diff --git a/rust/tw_crypto/tests/crypto_scrypt_ffi.rs b/rust/tw_crypto/tests/crypto_scrypt_ffi.rs index 1deb0fb2b79..5df66f70710 100644 --- a/rust/tw_crypto/tests/crypto_scrypt_ffi.rs +++ b/rust/tw_crypto/tests/crypto_scrypt_ffi.rs @@ -6,31 +6,21 @@ use tw_crypto::ffi::crypto_scrypt::crypto_scrypt; use tw_encoding::hex; -use tw_memory::ffi::c_byte_array::CByteArray; +use tw_memory::ffi::tw_data::TWData; +use tw_memory::ffi::RawPtrTrait; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; #[test] fn test_crypto_scrypt_ffi_null_salt() { let password = hex::decode("70617373776f7264").unwrap(); - let password = CByteArray::from(password); + let password = TWDataHelper::create(password); - let salt = CByteArray::null(); + let salt = TWDataHelper::create(Vec::new()); - let res = unsafe { - crypto_scrypt( - password.data(), - password.size(), - salt.data(), - salt.size(), - 16384, - 8, - 4, - 32, - ) - } - .unwrap(); - let data = unsafe { res.into_vec() }; + let res = unsafe { crypto_scrypt(password.ptr(), salt.ptr(), 16384, 8, 4, 32) }; + let res = unsafe { TWData::from_ptr_as_mut(res).unwrap() }; assert_eq!( - hex::encode(&data, false), + hex::encode(res.to_vec(), false), "004f57df809101216a343d6215879a9a7f1d7e2c04ef2845b4494cf5f10181a1" ); } diff --git a/src/Keystore/EncryptionParameters.cpp b/src/Keystore/EncryptionParameters.cpp index 56326bb01b6..e92fd6650f1 100644 --- a/src/Keystore/EncryptionParameters.cpp +++ b/src/Keystore/EncryptionParameters.cpp @@ -7,9 +7,8 @@ #include "../Hash.h" #include -#include #include -#include +#include "TrustWalletCore/Generated/TWCrypto.h" using namespace TW; @@ -38,20 +37,39 @@ static const auto mac = "mac"; } // namespace CodingKeys static Data rustScrypt(const Data& password, const ScryptParameters& params) { - Rust::CByteArrayResultWrapper res = Rust::crypto_scrypt( - password.data(), - password.size(), - params.salt.data(), - params.salt.size(), + Rust::TWDataWrapper passwordData = password; + Rust::TWDataWrapper saltData = params.salt; + + Rust::TWDataWrapper res = Rust::crypto_scrypt( + passwordData.get(), + saltData.get(), params.n, params.r, params.p, params.desiredKeyLength ); - if (!res.isOk()) { + auto data = res.toDataOrDefault(); + if (data.empty()) { throw std::runtime_error("Invalid scrypt parameters"); } - return res.unwrap().data; + return data; +} + +static Data rustPbkdf2(const Data& password, const PBKDF2Parameters& params) { + Rust::TWDataWrapper passwordData = password; + Rust::TWDataWrapper saltData = params.salt; + + Rust::TWDataWrapper res = Rust::crypto_pbkdf2( + passwordData.get(), + saltData.get(), + params.iterations, + params.desiredKeyLength + ); + auto data = res.toDataOrDefault(); + if (data.empty()) { + throw std::runtime_error("Invalid pbkdf2 parameters"); + } + return data; } EncryptionParameters::EncryptionParameters(const nlohmann::json& json) { @@ -123,10 +141,7 @@ Data EncryptedPayload::decrypt(const Data& password) const { derivedKey = rustScrypt(password, *scryptParams); mac = computeMAC(derivedKey.end() - params.getKeyBytesSize(), derivedKey.end(), encrypted); } else if (auto* pbkdf2Params = std::get_if(¶ms.kdfParams); pbkdf2Params) { - derivedKey.resize(pbkdf2Params->defaultDesiredKeyLength); - pbkdf2_hmac_sha256(password.data(), static_cast(password.size()), pbkdf2Params->salt.data(), - static_cast(pbkdf2Params->salt.size()), pbkdf2Params->iterations, derivedKey.data(), - pbkdf2Params->defaultDesiredKeyLength); + derivedKey = rustPbkdf2(password, *pbkdf2Params); mac = computeMAC(derivedKey.end() - params.getKeyBytesSize(), derivedKey.end(), encrypted); } else { throw DecryptionError::unsupportedKDF; From f694d32fd09ecfac82e198abd8f0382adcf8f0f3 Mon Sep 17 00:00:00 2001 From: gupnik Date: Fri, 25 Apr 2025 14:37:35 +0530 Subject: [PATCH 20/23] Replaces `aeskey.c` with Rust implementation via FFI (#4377) * Replaces `aeskey.c` with Rust implementation via FFI * Migrates TWAES as well * Adds AES CBC * Trigger Build * Fixes tests * Adds tests * Add C++ tests for CBC * Adds more C++ Tests * Addresses review comments * Addresses remaining review comments * Minor * Fix iOS Tests * Fix API --- codegen-v2/src/codegen/cpp/code_gen.rs | 4 + codegen-v2/src/codegen/cpp/code_gen_types.rs | 1 + .../src/codegen/swift/templates/WalletCore.h | 1 - include/TrustWalletCore/TWAES.h | 57 --- rust/Cargo.lock | 42 +++ rust/tw_crypto/Cargo.toml | 3 + rust/tw_crypto/src/crypto_aes_cbc/mod.rs | 150 ++++++++ rust/tw_crypto/src/crypto_aes_cbc/padding.rs | 116 ++++++ rust/tw_crypto/src/crypto_aes_ctr/mod.rs | 97 +++++ rust/tw_crypto/src/ffi/crypto_aes_cbc.rs | 187 ++++++++++ rust/tw_crypto/src/ffi/crypto_aes_ctr.rs | 167 +++++++++ rust/tw_crypto/src/ffi/mod.rs | 2 + rust/tw_crypto/src/lib.rs | 6 + rust/tw_crypto/tests/crypto_aes_ctr_ffi.rs | 179 +++++++++ rust/tw_crypto/tests/cryto_aes_cbc_ffi.rs | 347 ++++++++++++++++++ src/Encrypt.cpp | 131 +++---- src/Keystore/EncryptionParameters.cpp | 106 ++++-- src/interface/TWAES.cpp | 45 --- tests/common/EncryptTests.cpp | 31 +- tests/common/Keystore/StoredKeyTests.cpp | 64 ++++ tests/interface/TWAESTests.cpp | 3 +- 21 files changed, 1501 insertions(+), 238 deletions(-) delete mode 100644 include/TrustWalletCore/TWAES.h create mode 100644 rust/tw_crypto/src/crypto_aes_cbc/mod.rs create mode 100644 rust/tw_crypto/src/crypto_aes_cbc/padding.rs create mode 100644 rust/tw_crypto/src/crypto_aes_ctr/mod.rs create mode 100644 rust/tw_crypto/src/ffi/crypto_aes_cbc.rs create mode 100644 rust/tw_crypto/src/ffi/crypto_aes_ctr.rs create mode 100644 rust/tw_crypto/tests/crypto_aes_ctr_ffi.rs create mode 100644 rust/tw_crypto/tests/cryto_aes_cbc_ffi.rs delete mode 100644 src/interface/TWAES.cpp diff --git a/codegen-v2/src/codegen/cpp/code_gen.rs b/codegen-v2/src/codegen/cpp/code_gen.rs index 486583c0f61..e27e9d0c131 100644 --- a/codegen-v2/src/codegen/cpp/code_gen.rs +++ b/codegen-v2/src/codegen/cpp/code_gen.rs @@ -51,6 +51,10 @@ fn generate_header_includes(file: &mut std::fs::File, info: &TWConfig) -> Result { // Need to handle this case separately because it's not a pointer type writeln!(file, "#include ")?; + } else if ty.contains("TWFFIAESPaddingMode") + && included_headers.insert("TWAESPaddingMode.h".to_string()) + { + writeln!(file, "#include ")?; } } } diff --git a/codegen-v2/src/codegen/cpp/code_gen_types.rs b/codegen-v2/src/codegen/cpp/code_gen_types.rs index 257b20863fb..d306fc3d537 100644 --- a/codegen-v2/src/codegen/cpp/code_gen_types.rs +++ b/codegen-v2/src/codegen/cpp/code_gen_types.rs @@ -134,6 +134,7 @@ impl TWType { "i32" => "int32_t".to_string(), "i64" => "int64_t".to_string(), "TWFFICoinType" => "enum TWCoinType".to_string(), + "TWFFIAESPaddingMode" => "enum TWAESPaddingMode".to_string(), _ => ty.to_string(), }, TWType::Pointer(pointer_type, ty) => { diff --git a/codegen-v2/src/codegen/swift/templates/WalletCore.h b/codegen-v2/src/codegen/swift/templates/WalletCore.h index 76f61b9fa74..a0d5d0e9127 100644 --- a/codegen-v2/src/codegen/swift/templates/WalletCore.h +++ b/codegen-v2/src/codegen/swift/templates/WalletCore.h @@ -16,7 +16,6 @@ FOUNDATION_EXPORT const unsigned char WalletCoreVersionString[]; #include "TWAnySigner.h" -#include "TWAES.h" #include "TWAESPaddingMode.h" #include "TWAccount.h" #include "TWAnyAddress.h" diff --git a/include/TrustWalletCore/TWAES.h b/include/TrustWalletCore/TWAES.h deleted file mode 100644 index e4a30c656d1..00000000000 --- a/include/TrustWalletCore/TWAES.h +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#pragma once - -#include "TWBase.h" -#include "TWData.h" -#include "TWAESPaddingMode.h" - -TW_EXTERN_C_BEGIN - -/// AES encryption/decryption methods. -TW_EXPORT_STRUCT -struct TWAES { - uint8_t unused; // C doesn't allow zero-sized struct -}; - -/// Encrypts a block of Data using AES in Cipher Block Chaining (CBC) mode. -/// -/// \param key encryption key Data, must be 16, 24, or 32 bytes long. -/// \param data Data to encrypt. -/// \param iv initialization vector. -/// \param mode padding mode. -/// \return encrypted Data. -TW_EXPORT_STATIC_METHOD -TWData *_Nullable TWAESEncryptCBC(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv, enum TWAESPaddingMode mode); - -/// Decrypts a block of data using AES in Cipher Block Chaining (CBC) mode. -/// -/// \param key decryption key Data, must be 16, 24, or 32 bytes long. -/// \param data Data to decrypt. -/// \param iv initialization vector Data. -/// \param mode padding mode. -/// \return decrypted Data. -TW_EXPORT_STATIC_METHOD -TWData *_Nullable TWAESDecryptCBC(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv, enum TWAESPaddingMode mode); - -/// Encrypts a block of data using AES in Counter (CTR) mode. -/// -/// \param key encryption key Data, must be 16, 24, or 32 bytes long. -/// \param data Data to encrypt. -/// \param iv initialization vector Data. -/// \return encrypted Data. -TW_EXPORT_STATIC_METHOD -TWData *_Nullable TWAESEncryptCTR(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv); - -/// Decrypts a block of data using AES in Counter (CTR) mode. -/// -/// \param key decryption key Data, must be 16, 24, or 32 bytes long. -/// \param data Data to decrypt. -/// \param iv initialization vector Data. -/// \return decrypted Data. -TW_EXPORT_STATIC_METHOD -TWData *_Nullable TWAESDecryptCTR(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv); - -TW_EXTERN_C_END diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 0285f7d1b0a..3adedf6b4bc 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -12,6 +12,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "aho-corasick" version = "0.7.20" @@ -294,6 +305,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "borsh" version = "1.3.1" @@ -348,6 +368,15 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.0.79" @@ -507,6 +536,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -864,6 +902,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ + "block-padding", "generic-array", ] @@ -1949,6 +1988,9 @@ dependencies = [ name = "tw_crypto" version = "0.1.0" dependencies = [ + "aes", + "cbc", + "ctr", "pbkdf2", "salsa20", "sha2", diff --git a/rust/tw_crypto/Cargo.toml b/rust/tw_crypto/Cargo.toml index adf2186dabd..2f99218dd87 100644 --- a/rust/tw_crypto/Cargo.toml +++ b/rust/tw_crypto/Cargo.toml @@ -5,6 +5,9 @@ version = "0.1.0" edition = "2021" [dependencies] +aes = "0.8.4" +cbc = "0.1.2" +ctr = "0.9.2" pbkdf2 = "0.12.2" salsa20 = "0.10.2" sha2 = "0.10.8" diff --git a/rust/tw_crypto/src/crypto_aes_cbc/mod.rs b/rust/tw_crypto/src/crypto_aes_cbc/mod.rs new file mode 100644 index 00000000000..1c2da6782e7 --- /dev/null +++ b/rust/tw_crypto/src/crypto_aes_cbc/mod.rs @@ -0,0 +1,150 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod padding; + +use crate::{KEY_SIZE_AES_128, KEY_SIZE_AES_192, KEY_SIZE_AES_256}; +use aes::cipher::{ + Block, BlockDecryptMut, BlockEncryptMut, BlockSizeUser, KeyIvInit, StreamCipherError, +}; +use padding::{PaddingMode, BLOCK_SIZE_AES}; + +type Aes128CbcEnc = cbc::Encryptor; +type Aes128CbcDec = cbc::Decryptor; +type Aes192CbcEnc = cbc::Encryptor; +type Aes192CbcDec = cbc::Decryptor; +type Aes256CbcEnc = cbc::Encryptor; +type Aes256CbcDec = cbc::Decryptor; + +fn blocks(data: &[u8]) -> Vec> { + data.chunks(BLOCK_SIZE_AES) + .map(Block::::clone_from_slice) + .collect() +} + +fn aes_cbc_encrypt_impl( + data: &[u8], + iv: &[u8], + key: &[u8], + key_size: usize, + padding_mode: PaddingMode, +) -> Result, StreamCipherError> { + let key = if key.len() > key_size { + &key[0..key_size] + } else { + key + }; + let data = padding_mode.pad(data); + let mut blocks = blocks::(&data); + let mut cipher = E::new(key.into(), iv.into()); + cipher.encrypt_blocks_mut(&mut blocks[..]); + let buffer = blocks.concat(); + Ok(buffer) +} + +fn aes_cbc_decrypt_impl( + data: &[u8], + iv: &[u8], + key: &[u8], + key_size: usize, + padding_mode: PaddingMode, +) -> Result, StreamCipherError> { + let key = if key.len() > key_size { + &key[0..key_size] + } else { + key + }; + if data.len() % BLOCK_SIZE_AES != 0 { + return Err(StreamCipherError); + } + let mut blocks = blocks::(data); + let mut cipher = D::new(key.into(), iv.into()); + cipher.decrypt_blocks_mut(&mut blocks[..]); + let buffer = blocks.concat(); + Ok(padding_mode.unpad(&buffer)) +} + +pub fn aes_cbc_encrypt_128( + data: &[u8], + iv: &[u8], + key: &[u8], + padding_mode: PaddingMode, +) -> Result, StreamCipherError> { + aes_cbc_encrypt_impl::(data, iv, key, KEY_SIZE_AES_128, padding_mode) +} + +pub fn aes_cbc_decrypt_128( + data: &[u8], + iv: &[u8], + key: &[u8], + padding_mode: PaddingMode, +) -> Result, StreamCipherError> { + aes_cbc_decrypt_impl::(data, iv, key, KEY_SIZE_AES_128, padding_mode) +} + +pub fn aes_cbc_encrypt_192( + data: &[u8], + iv: &[u8], + key: &[u8], + padding_mode: PaddingMode, +) -> Result, StreamCipherError> { + aes_cbc_encrypt_impl::(data, iv, key, KEY_SIZE_AES_192, padding_mode) +} + +pub fn aes_cbc_decrypt_192( + data: &[u8], + iv: &[u8], + key: &[u8], + padding_mode: PaddingMode, +) -> Result, StreamCipherError> { + aes_cbc_decrypt_impl::(data, iv, key, KEY_SIZE_AES_192, padding_mode) +} + +pub fn aes_cbc_encrypt_256( + data: &[u8], + iv: &[u8], + key: &[u8], + padding_mode: PaddingMode, +) -> Result, StreamCipherError> { + aes_cbc_encrypt_impl::(data, iv, key, KEY_SIZE_AES_256, padding_mode) +} + +pub fn aes_cbc_decrypt_256( + data: &[u8], + iv: &[u8], + key: &[u8], + padding_mode: PaddingMode, +) -> Result, StreamCipherError> { + aes_cbc_decrypt_impl::(data, iv, key, KEY_SIZE_AES_256, padding_mode) +} + +pub fn aes_cbc_encrypt( + data: &[u8], + iv: &[u8], + key: &[u8], + padding_mode: PaddingMode, +) -> Result, StreamCipherError> { + match key.len() { + KEY_SIZE_AES_128 => aes_cbc_encrypt_128(data, iv, key, padding_mode), + KEY_SIZE_AES_192 => aes_cbc_encrypt_192(data, iv, key, padding_mode), + KEY_SIZE_AES_256 => aes_cbc_encrypt_256(data, iv, key, padding_mode), + _ => Err(StreamCipherError), + } +} + +pub fn aes_cbc_decrypt( + data: &[u8], + iv: &[u8], + key: &[u8], + padding_mode: PaddingMode, +) -> Result, StreamCipherError> { + match key.len() { + KEY_SIZE_AES_128 => aes_cbc_decrypt_128(data, iv, key, padding_mode), + KEY_SIZE_AES_192 => aes_cbc_decrypt_192(data, iv, key, padding_mode), + KEY_SIZE_AES_256 => aes_cbc_decrypt_256(data, iv, key, padding_mode), + _ => Err(StreamCipherError), + } +} diff --git a/rust/tw_crypto/src/crypto_aes_cbc/padding.rs b/rust/tw_crypto/src/crypto_aes_cbc/padding.rs new file mode 100644 index 00000000000..7439c8425e2 --- /dev/null +++ b/rust/tw_crypto/src/crypto_aes_cbc/padding.rs @@ -0,0 +1,116 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub const BLOCK_SIZE_AES: usize = 16; + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub enum PaddingMode { + Zero = 0, + PKCS7 = 1, +} + +pub type TWFFIAESPaddingMode = u32; + +#[derive(Clone, Copy, Debug)] +pub struct InvalidPaddingMode; + +impl TryFrom for PaddingMode { + type Error = InvalidPaddingMode; + + fn try_from(value: u32) -> Result { + match value { + 0 => Ok(PaddingMode::Zero), + 1 => Ok(PaddingMode::PKCS7), + _ => Err(InvalidPaddingMode), + } + } +} + +impl PaddingMode { + pub fn padding_size(&self, data_size: usize) -> usize { + match self { + PaddingMode::Zero => { + if data_size % BLOCK_SIZE_AES == 0 { + 0 + } else { + BLOCK_SIZE_AES - (data_size % BLOCK_SIZE_AES) + } + }, + PaddingMode::PKCS7 => { + if data_size % BLOCK_SIZE_AES == 0 { + BLOCK_SIZE_AES + } else { + BLOCK_SIZE_AES - (data_size % BLOCK_SIZE_AES) + } + }, + } + } + + pub fn pad(&self, data: &[u8]) -> Vec { + let padding_size = self.padding_size(data.len()); + let mut padded = data.to_vec(); + match self { + PaddingMode::Zero => { + padded.extend(std::iter::repeat(0).take(padding_size)); + }, + PaddingMode::PKCS7 => { + padded.extend(std::iter::repeat(padding_size as u8).take(padding_size)); + }, + } + padded + } + + pub fn unpad(&self, data: &[u8]) -> Vec { + if data.is_empty() { + return data.to_vec(); + } + match self { + PaddingMode::PKCS7 => { + let padding_len = data[data.len() - 1] as usize; + if padding_len <= data.len() { + data[..data.len() - padding_len].to_vec() + } else { + data.to_vec() + } + }, + // Zero padding can be ambiguous, so we return the original data + // and the caller can strip the padding if needed + PaddingMode::Zero => data.to_vec(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_padding_size() { + let zero_mode = PaddingMode::Zero; + let pkcs7_mode = PaddingMode::PKCS7; + + assert_eq!(zero_mode.padding_size(0), 0); + assert_eq!(zero_mode.padding_size(1), 15); + assert_eq!(zero_mode.padding_size(8), 8); + assert_eq!(zero_mode.padding_size(15), 1); + assert_eq!(zero_mode.padding_size(16), 0); + assert_eq!(zero_mode.padding_size(17), 15); + assert_eq!(zero_mode.padding_size(24), 8); + assert_eq!(zero_mode.padding_size(31), 1); + assert_eq!(zero_mode.padding_size(32), 0); + + assert_eq!(pkcs7_mode.padding_size(0), 16); + assert_eq!(pkcs7_mode.padding_size(1), 15); + assert_eq!(pkcs7_mode.padding_size(8), 8); + assert_eq!(pkcs7_mode.padding_size(15), 1); + assert_eq!(pkcs7_mode.padding_size(16), 16); + assert_eq!(pkcs7_mode.padding_size(17), 15); + assert_eq!(pkcs7_mode.padding_size(24), 8); + assert_eq!(pkcs7_mode.padding_size(31), 1); + assert_eq!(pkcs7_mode.padding_size(32), 16); + } +} diff --git a/rust/tw_crypto/src/crypto_aes_ctr/mod.rs b/rust/tw_crypto/src/crypto_aes_ctr/mod.rs new file mode 100644 index 00000000000..66addbfa980 --- /dev/null +++ b/rust/tw_crypto/src/crypto_aes_ctr/mod.rs @@ -0,0 +1,97 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::{KEY_SIZE_AES_128, KEY_SIZE_AES_192, KEY_SIZE_AES_256}; +use aes::cipher::{KeyIvInit, StreamCipher, StreamCipherError}; +use aes::{Aes128, Aes192, Aes256}; +use ctr::Ctr64BE; + +type Aes128Ctr64BE = Ctr64BE; +type Aes192Ctr64BE = Ctr64BE; +type Aes256Ctr64BE = Ctr64BE; + +fn aes_ctr_process( + data: &[u8], + iv: &[u8], + key: &[u8], + key_size: usize, +) -> Result, StreamCipherError> { + let key = if key.len() > key_size { + &key[0..key_size] + } else { + key + }; + let mut cipher = C::new(key.into(), iv.into()); + let mut data_vec = data.to_vec(); + cipher.try_apply_keystream(&mut data_vec)?; + Ok(data_vec) +} + +pub fn aes_ctr_encrypt_128( + data: &[u8], + iv: &[u8], + key: &[u8], +) -> Result, StreamCipherError> { + aes_ctr_process::(data, iv, key, KEY_SIZE_AES_128) +} + +pub fn aes_ctr_decrypt_128( + data: &[u8], + iv: &[u8], + key: &[u8], +) -> Result, StreamCipherError> { + aes_ctr_process::(data, iv, key, KEY_SIZE_AES_128) +} + +pub fn aes_ctr_encrypt_192( + data: &[u8], + iv: &[u8], + key: &[u8], +) -> Result, StreamCipherError> { + aes_ctr_process::(data, iv, key, KEY_SIZE_AES_192) +} + +pub fn aes_ctr_decrypt_192( + data: &[u8], + iv: &[u8], + key: &[u8], +) -> Result, StreamCipherError> { + aes_ctr_process::(data, iv, key, KEY_SIZE_AES_192) +} + +pub fn aes_ctr_encrypt_256( + data: &[u8], + iv: &[u8], + key: &[u8], +) -> Result, StreamCipherError> { + aes_ctr_process::(data, iv, key, KEY_SIZE_AES_256) +} + +pub fn aes_ctr_decrypt_256( + data: &[u8], + iv: &[u8], + key: &[u8], +) -> Result, StreamCipherError> { + aes_ctr_process::(data, iv, key, KEY_SIZE_AES_256) +} + +pub fn aes_ctr_encrypt(data: &[u8], iv: &[u8], key: &[u8]) -> Result, StreamCipherError> { + match key.len() { + KEY_SIZE_AES_128 => aes_ctr_encrypt_128(data, iv, key), + KEY_SIZE_AES_192 => aes_ctr_encrypt_192(data, iv, key), + KEY_SIZE_AES_256 => aes_ctr_encrypt_256(data, iv, key), + _ => Err(StreamCipherError), + } +} + +pub fn aes_ctr_decrypt(data: &[u8], iv: &[u8], key: &[u8]) -> Result, StreamCipherError> { + match key.len() { + KEY_SIZE_AES_128 => aes_ctr_decrypt_128(data, iv, key), + KEY_SIZE_AES_192 => aes_ctr_decrypt_192(data, iv, key), + KEY_SIZE_AES_256 => aes_ctr_decrypt_256(data, iv, key), + _ => Err(StreamCipherError), + } +} diff --git a/rust/tw_crypto/src/ffi/crypto_aes_cbc.rs b/rust/tw_crypto/src/ffi/crypto_aes_cbc.rs new file mode 100644 index 00000000000..e0c0df6ce6e --- /dev/null +++ b/rust/tw_crypto/src/ffi/crypto_aes_cbc.rs @@ -0,0 +1,187 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![allow(clippy::missing_safety_doc)] + +use crate::crypto_aes_cbc::{ + aes_cbc_decrypt, aes_cbc_decrypt_128, aes_cbc_decrypt_192, aes_cbc_decrypt_256, + aes_cbc_encrypt, aes_cbc_encrypt_128, aes_cbc_encrypt_192, aes_cbc_encrypt_256, + padding::{PaddingMode, TWFFIAESPaddingMode}, +}; +use aes::cipher::StreamCipherError; +use tw_macros::tw_ffi; +use tw_memory::ffi::{tw_data::TWData, Nonnull, NullableMut, RawPtrTrait}; +use tw_misc::try_or_else; + +unsafe fn handle_aes_cbc_operation( + data: Nonnull, + iv: Nonnull, + key: Nonnull, + padding_mode: TWFFIAESPaddingMode, + operation: F, +) -> NullableMut +where + F: FnOnce(&[u8], &[u8], &[u8], PaddingMode) -> Result, StreamCipherError>, +{ + let padding_mode = try_or_else!(PaddingMode::try_from(padding_mode), std::ptr::null_mut); + let data = TWData::from_ptr_as_ref(data) + .map(|data| data.as_slice()) + .unwrap_or_default(); + let iv = TWData::from_ptr_as_ref(iv) + .map(|data| data.as_slice()) + .unwrap_or_default(); + let key = TWData::from_ptr_as_ref(key) + .map(|data| data.as_slice()) + .unwrap_or_default(); + + operation(data, iv, key, padding_mode) + .map(|output| TWData::from(output).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Encrypts a block of Data using AES in Cipher Block Chaining (CBC) mode with 128-bit key. +/// +/// \param key encryption key Data, must be 16 bytes long. +/// \param data Data to encrypt. +/// \param iv initialization vector. +/// \param mode padding mode. +/// \return encrypted Data. +#[tw_ffi(ty = static_function, class = TWAES, name = EncryptCBC128)] +#[no_mangle] +pub unsafe extern "C" fn tw_aes_encrypt_cbc_128( + key: Nonnull, + data: Nonnull, + iv: Nonnull, + mode: TWFFIAESPaddingMode, +) -> NullableMut { + handle_aes_cbc_operation(data, iv, key, mode, aes_cbc_encrypt_128) +} + +/// Decrypts a block of Data using AES in Cipher Block Chaining (CBC) mode with 128-bit key. +/// +/// \param key decryption key Data, must be 16 bytes long. +/// \param data Data to decrypt. +/// \param iv initialization vector. +/// \param mode padding mode. +/// \return decrypted Data. +#[tw_ffi(ty = static_function, class = TWAES, name = DecryptCBC128)] +#[no_mangle] +pub unsafe extern "C" fn tw_aes_decrypt_cbc_128( + key: Nonnull, + data: Nonnull, + iv: Nonnull, + mode: TWFFIAESPaddingMode, +) -> NullableMut { + handle_aes_cbc_operation(data, iv, key, mode, aes_cbc_decrypt_128) +} + +/// Encrypts a block of Data using AES in Cipher Block Chaining (CBC) mode with 192-bit key. +/// +/// \param key encryption key Data, must be 24 bytes long. +/// \param data Data to encrypt. +/// \param iv initialization vector. +/// \param mode padding mode. +/// \return encrypted Data. +#[tw_ffi(ty = static_function, class = TWAES, name = EncryptCBC192)] +#[no_mangle] +pub unsafe extern "C" fn tw_aes_encrypt_cbc_192( + key: Nonnull, + data: Nonnull, + iv: Nonnull, + mode: TWFFIAESPaddingMode, +) -> NullableMut { + handle_aes_cbc_operation(data, iv, key, mode, aes_cbc_encrypt_192) +} + +/// Decrypts a block of Data using AES in Cipher Block Chaining (CBC) mode with 192-bit key. +/// +/// \param key decryption key Data, must be 24 bytes long. +/// \param data Data to decrypt. +/// \param iv initialization vector. +/// \param mode padding mode. +/// \return decrypted Data. +#[tw_ffi(ty = static_function, class = TWAES, name = DecryptCBC192)] +#[no_mangle] +pub unsafe extern "C" fn tw_aes_decrypt_cbc_192( + key: Nonnull, + data: Nonnull, + iv: Nonnull, + mode: TWFFIAESPaddingMode, +) -> NullableMut { + handle_aes_cbc_operation(data, iv, key, mode, aes_cbc_decrypt_192) +} + +/// Encrypts a block of Data using AES in Cipher Block Chaining (CBC) mode with 256-bit key. +/// +/// \param key encryption key Data, must be 32 bytes long. +/// \param data Data to encrypt. +/// \param iv initialization vector. +/// \param mode padding mode. +/// \return encrypted Data. +#[tw_ffi(ty = static_function, class = TWAES, name = EncryptCBC256)] +#[no_mangle] +pub unsafe extern "C" fn tw_aes_encrypt_cbc_256( + key: Nonnull, + data: Nonnull, + iv: Nonnull, + mode: TWFFIAESPaddingMode, +) -> NullableMut { + handle_aes_cbc_operation(data, iv, key, mode, aes_cbc_encrypt_256) +} + +/// Decrypts a block of Data using AES in Cipher Block Chaining (CBC) mode with 256-bit key. +/// +/// \param key decryption key Data, must be 32 bytes long. +/// \param data Data to decrypt. +/// \param iv initialization vector. +/// \param mode padding mode. +/// \return decrypted Data. +#[tw_ffi(ty = static_function, class = TWAES, name = DecryptCBC256)] +#[no_mangle] +pub unsafe extern "C" fn tw_aes_decrypt_cbc_256( + key: Nonnull, + data: Nonnull, + iv: Nonnull, + mode: TWFFIAESPaddingMode, +) -> NullableMut { + handle_aes_cbc_operation(data, iv, key, mode, aes_cbc_decrypt_256) +} + +/// Encrypts a block of Data using AES in Cipher Block Chaining (CBC) mode. +/// +/// \param key encryption key Data, must be 16, 24, or 32 bytes long. +/// \param data Data to encrypt. +/// \param iv initialization vector. +/// \param mode padding mode. +/// \return encrypted Data. +#[tw_ffi(ty = static_function, class = TWAES, name = EncryptCBC)] +#[no_mangle] +pub unsafe extern "C" fn tw_aes_encrypt_cbc( + key: Nonnull, + data: Nonnull, + iv: Nonnull, + mode: TWFFIAESPaddingMode, +) -> NullableMut { + handle_aes_cbc_operation(data, iv, key, mode, aes_cbc_encrypt) +} + +/// Decrypts a block of data using AES in Cipher Block Chaining (CBC) mode. +/// +/// \param key decryption key Data, must be 16, 24, or 32 bytes long. +/// \param data Data to decrypt. +/// \param iv initialization vector Data. +/// \param mode padding mode. +/// \return decrypted Data. +#[tw_ffi(ty = static_function, class = TWAES, name = DecryptCBC)] +#[no_mangle] +pub unsafe extern "C" fn tw_aes_decrypt_cbc( + key: Nonnull, + data: Nonnull, + iv: Nonnull, + mode: TWFFIAESPaddingMode, +) -> NullableMut { + handle_aes_cbc_operation(data, iv, key, mode, aes_cbc_decrypt) +} diff --git a/rust/tw_crypto/src/ffi/crypto_aes_ctr.rs b/rust/tw_crypto/src/ffi/crypto_aes_ctr.rs new file mode 100644 index 00000000000..c7390c23a3b --- /dev/null +++ b/rust/tw_crypto/src/ffi/crypto_aes_ctr.rs @@ -0,0 +1,167 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![allow(clippy::missing_safety_doc)] + +use crate::crypto_aes_ctr::{ + aes_ctr_decrypt, aes_ctr_decrypt_128, aes_ctr_decrypt_192, aes_ctr_decrypt_256, + aes_ctr_encrypt, aes_ctr_encrypt_128, aes_ctr_encrypt_192, aes_ctr_encrypt_256, +}; +use aes::cipher::StreamCipherError; +use tw_macros::tw_ffi; +use tw_memory::ffi::{tw_data::TWData, Nonnull, NullableMut, RawPtrTrait}; + +unsafe fn handle_aes_ctr_operation( + data: Nonnull, + iv: Nonnull, + key: Nonnull, + operation: F, +) -> NullableMut +where + F: FnOnce(&[u8], &[u8], &[u8]) -> Result, StreamCipherError>, +{ + let data = TWData::from_ptr_as_ref(data) + .map(|data| data.as_slice()) + .unwrap_or_default(); + let iv = TWData::from_ptr_as_ref(iv) + .map(|data| data.as_slice()) + .unwrap_or_default(); + let key = TWData::from_ptr_as_ref(key) + .map(|data| data.as_slice()) + .unwrap_or_default(); + + operation(data, iv, key) + .map(|output| TWData::from(output).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Encrypts a block of data using AES in Counter (CTR) mode with 128-bit key. +/// +/// \param key encryption key Data, must be 16 bytes long. +/// \param data Data to encrypt. +/// \param iv initialization vector Data. +/// \return encrypted Data. +#[tw_ffi(ty = static_function, class = TWAES, name = EncryptCTR128)] +#[no_mangle] +pub unsafe extern "C" fn tw_aes_encrypt_ctr_128( + key: Nonnull, + data: Nonnull, + iv: Nonnull, +) -> NullableMut { + handle_aes_ctr_operation(data, iv, key, aes_ctr_encrypt_128) +} + +/// Decrypts a block of data using AES in Counter (CTR) mode with 128-bit key. +/// +/// \param key decryption key Data, must be 16 bytes long. +/// \param data Data to decrypt. +/// \param iv initialization vector Data. +/// \return decrypted Data. +#[tw_ffi(ty = static_function, class = TWAES, name = DecryptCTR128)] +#[no_mangle] +pub unsafe extern "C" fn tw_aes_decrypt_ctr_128( + key: Nonnull, + data: Nonnull, + iv: Nonnull, +) -> NullableMut { + handle_aes_ctr_operation(data, iv, key, aes_ctr_decrypt_128) +} + +/// Encrypts a block of data using AES in Counter (CTR) mode with 192-bit key. +/// +/// \param key encryption key Data, must be 24 bytes long. +/// \param data Data to encrypt. +/// \param iv initialization vector Data. +/// \return encrypted Data. +#[tw_ffi(ty = static_function, class = TWAES, name = EncryptCTR192)] +#[no_mangle] +pub unsafe extern "C" fn tw_aes_encrypt_ctr_192( + key: Nonnull, + data: Nonnull, + iv: Nonnull, +) -> NullableMut { + handle_aes_ctr_operation(data, iv, key, aes_ctr_encrypt_192) +} + +/// Decrypts a block of data using AES in Counter (CTR) mode with 192-bit key. +/// +/// \param key decryption key Data, must be 24 bytes long. +/// \param data Data to decrypt. +/// \param iv initialization vector Data. +/// \return decrypted Data. +#[tw_ffi(ty = static_function, class = TWAES, name = DecryptCTR192)] +#[no_mangle] +pub unsafe extern "C" fn tw_aes_decrypt_ctr_192( + key: Nonnull, + data: Nonnull, + iv: Nonnull, +) -> NullableMut { + handle_aes_ctr_operation(data, iv, key, aes_ctr_decrypt_192) +} + +/// Encrypts a block of data using AES in Counter (CTR) mode with 256-bit key. +/// +/// \param key encryption key Data, must be 32 bytes long. +/// \param data Data to encrypt. +/// \param iv initialization vector Data. +/// \return encrypted Data. +#[tw_ffi(ty = static_function, class = TWAES, name = EncryptCTR256)] +#[no_mangle] +pub unsafe extern "C" fn tw_aes_encrypt_ctr_256( + key: Nonnull, + data: Nonnull, + iv: Nonnull, +) -> NullableMut { + handle_aes_ctr_operation(data, iv, key, aes_ctr_encrypt_256) +} + +/// Decrypts a block of data using AES in Counter (CTR) mode with 256-bit key. +/// +/// \param key decryption key Data, must be 32 bytes long. +/// \param data Data to decrypt. +/// \param iv initialization vector Data. +/// \return decrypted Data. +#[tw_ffi(ty = static_function, class = TWAES, name = DecryptCTR256)] +#[no_mangle] +pub unsafe extern "C" fn tw_aes_decrypt_ctr_256( + key: Nonnull, + data: Nonnull, + iv: Nonnull, +) -> NullableMut { + handle_aes_ctr_operation(data, iv, key, aes_ctr_decrypt_256) +} + +/// Encrypts a block of data using AES in Counter (CTR) mode. +/// +/// \param key encryption key Data, must be 16, 24, or 32 bytes long. +/// \param data Data to encrypt. +/// \param iv initialization vector Data. +/// \return encrypted Data. +#[tw_ffi(ty = static_function, class = TWAES, name = EncryptCTR)] +#[no_mangle] +pub unsafe extern "C" fn tw_aes_encrypt_ctr( + key: Nonnull, + data: Nonnull, + iv: Nonnull, +) -> NullableMut { + handle_aes_ctr_operation(data, iv, key, aes_ctr_encrypt) +} + +/// Decrypts a block of data using AES in Counter (CTR) mode. +/// +/// \param key decryption key Data, must be 16, 24, or 32 bytes long. +/// \param data Data to decrypt. +/// \param iv initialization vector Data. +/// \return decrypted Data. +#[tw_ffi(ty = static_function, class = TWAES, name = DecryptCTR)] +#[no_mangle] +pub unsafe extern "C" fn tw_aes_decrypt_ctr( + key: Nonnull, + data: Nonnull, + iv: Nonnull, +) -> NullableMut { + handle_aes_ctr_operation(data, iv, key, aes_ctr_decrypt) +} diff --git a/rust/tw_crypto/src/ffi/mod.rs b/rust/tw_crypto/src/ffi/mod.rs index d9743335122..6716d80ff4c 100644 --- a/rust/tw_crypto/src/ffi/mod.rs +++ b/rust/tw_crypto/src/ffi/mod.rs @@ -4,5 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +pub mod crypto_aes_cbc; +pub mod crypto_aes_ctr; pub mod crypto_pbkdf2; pub mod crypto_scrypt; diff --git a/rust/tw_crypto/src/lib.rs b/rust/tw_crypto/src/lib.rs index cbf63fbb35c..5f3cff0fb90 100644 --- a/rust/tw_crypto/src/lib.rs +++ b/rust/tw_crypto/src/lib.rs @@ -4,6 +4,12 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +pub mod crypto_aes_cbc; +pub mod crypto_aes_ctr; pub mod crypto_pbkdf2; pub mod crypto_scrypt; pub mod ffi; + +pub const KEY_SIZE_AES_128: usize = 16; +pub const KEY_SIZE_AES_192: usize = 24; +pub const KEY_SIZE_AES_256: usize = 32; diff --git a/rust/tw_crypto/tests/crypto_aes_ctr_ffi.rs b/rust/tw_crypto/tests/crypto_aes_ctr_ffi.rs new file mode 100644 index 00000000000..9602a3dbbb5 --- /dev/null +++ b/rust/tw_crypto/tests/crypto_aes_ctr_ffi.rs @@ -0,0 +1,179 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_crypto::ffi::crypto_aes_ctr::*; +use tw_encoding::hex; +use tw_memory::ffi::tw_data::TWData; +use tw_memory::ffi::RawPtrTrait; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; + +#[test] +fn test_crypto_aes_ctr_128_ffi() { + let data_hex = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"; + let data = hex::decode(data_hex).unwrap(); + let data = TWDataHelper::create(data); + let iv = hex::decode("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff").unwrap(); + let iv = TWDataHelper::create(iv); + let key = hex::decode("2b7e151628aed2a6abf7158809cf4f3c").unwrap(); + let key = TWDataHelper::create(key); + + let encrypted = unsafe { tw_aes_encrypt_ctr_128(key.ptr(), data.ptr(), iv.ptr()) }; + let encrypted = unsafe { TWData::from_ptr_as_mut(encrypted).unwrap() }; + + let decrypted = unsafe { tw_aes_decrypt_ctr_128(key.ptr(), encrypted, iv.ptr()) }; + let decrypted = unsafe { TWData::from_ptr_as_mut(decrypted).unwrap() }; + + let decrypted_data = decrypted.to_vec(); + assert_eq!(hex::encode(&decrypted_data, false), data_hex); +} + +#[test] +fn test_crypto_aes_ctr_192_ffi() { + let data_hex = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"; + let data = hex::decode(data_hex).unwrap(); + let data = TWDataHelper::create(data); + let iv = hex::decode("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff").unwrap(); + let iv = TWDataHelper::create(iv); + let key = hex::decode("8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b").unwrap(); + let key = TWDataHelper::create(key); + + let encrypted = unsafe { tw_aes_encrypt_ctr_192(key.ptr(), data.ptr(), iv.ptr()) }; + let encrypted = unsafe { TWData::from_ptr_as_mut(encrypted).unwrap() }; + + let decrypted = unsafe { tw_aes_decrypt_ctr_192(key.ptr(), encrypted, iv.ptr()) }; + let decrypted = unsafe { TWData::from_ptr_as_mut(decrypted).unwrap() }; + + let decrypted_data = decrypted.to_vec(); + assert_eq!(hex::encode(&decrypted_data, false), data_hex); +} + +#[test] +fn test_crypto_aes_ctr_256_ffi() { + let data_hex = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"; + let data = hex::decode(data_hex).unwrap(); + let data = TWDataHelper::create(data); + let iv = hex::decode("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff").unwrap(); + let iv = TWDataHelper::create(iv); + let key = + hex::decode("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4").unwrap(); + let key = TWDataHelper::create(key); + + let encrypted = unsafe { tw_aes_encrypt_ctr_256(key.ptr(), data.ptr(), iv.ptr()) }; + let encrypted = unsafe { TWData::from_ptr_as_mut(encrypted).unwrap() }; + + let decrypted = unsafe { tw_aes_decrypt_ctr_256(key.ptr(), encrypted, iv.ptr()) }; + let decrypted = unsafe { TWData::from_ptr_as_mut(decrypted).unwrap() }; + + let decrypted_data = decrypted.to_vec(); + assert_eq!(hex::encode(&decrypted_data, false), data_hex); +} + +#[test] +fn test_crypto_aes_ctr_encrypt() { + let iv = hex::decode("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff").unwrap(); + let iv = TWDataHelper::create(iv); + let data = hex::decode("6bc1bee22e409f96e93d7e117393172a").unwrap(); + let data = TWDataHelper::create(data); + let key = + hex::decode("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4").unwrap(); + let key = TWDataHelper::create(key); + + let encrypt_result = unsafe { tw_aes_encrypt_ctr(key.ptr(), data.ptr(), iv.ptr()) }; + let encrypt_result = unsafe { TWData::from_ptr_as_mut(encrypt_result).unwrap() }; + + assert_eq!( + hex::encode(&encrypt_result.to_vec(), false), + "601ec313775789a5b7a7f504bbf3d228" + ); +} + +#[test] +fn test_crypto_aes_ctr_decrypt() { + let iv = hex::decode("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff").unwrap(); + let iv = TWDataHelper::create(iv); + let cipher = hex::decode("601ec313775789a5b7a7f504bbf3d228").unwrap(); + let cipher = TWDataHelper::create(cipher); + let key = + hex::decode("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4").unwrap(); + let key = TWDataHelper::create(key); + + let decrypt_result = unsafe { tw_aes_decrypt_ctr(key.ptr(), cipher.ptr(), iv.ptr()) }; + let decrypt_result = unsafe { TWData::from_ptr_as_mut(decrypt_result).unwrap() }; + + assert_eq!( + hex::encode(&decrypt_result.to_vec(), false), + "6bc1bee22e409f96e93d7e117393172a" + ); +} + +#[test] +fn test_crypto_aes_ctr_decrypt_multiple_blocks() { + let key = hex::decode("fac192ceb5fd772906bea3e118a69e8b").unwrap(); + let key = TWDataHelper::create(key); + let iv = hex::decode("83dbcc02d8ccb40e466191a123791e0e").unwrap(); + let iv = TWDataHelper::create(iv); + let data = + hex::decode("d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c").unwrap(); + let data = TWDataHelper::create(data); + + let decrypt_result = unsafe { tw_aes_decrypt_ctr(key.ptr(), data.ptr(), iv.ptr()) }; + let decrypt_result = unsafe { TWData::from_ptr_as_mut(decrypt_result).unwrap() }; + + assert_eq!( + hex::encode(&decrypt_result.to_vec(), false), + "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d" + ); +} + +#[test] +fn test_crypto_aes_ctr_encrypt_multiple_blocks() { + let key = hex::decode("e1094a016e6029eabc6f9e3c3cd9afb8").unwrap(); + let key = TWDataHelper::create(key); + let iv = hex::decode("884b972d70acece4ecf9b790ffce177e").unwrap(); + let iv = TWDataHelper::create(iv); + let data = hex::decode("726970706c652073636973736f7273206b69636b206d616d6d616c206869726520636f6c756d6e206f616b20616761696e2073756e206f66666572207765616c746820746f6d6f72726f77207761676f6e207475726e20666174616c00").unwrap(); + let data = TWDataHelper::create(data); + + let result = unsafe { tw_aes_encrypt_ctr(key.ptr(), data.ptr(), iv.ptr()) }; + let result = unsafe { TWData::from_ptr_as_mut(result).unwrap() }; + + assert_eq!( + hex::encode(&result.to_vec(), false), + "76b0a3ae037e7d6a50236c4c3ba7560edde4a8a951bf97bc10709e74d8e926c0431866b0ba9852d95bb0bbf41d109f1f3cf2f0af818f96d4f4109a1e3e5b224e3efd57288906a48d47b0006ccedcf96fde7362dedca952dda7cbdd359d" + ); +} + +#[test] +fn test_crypto_aes_ctr_encrypt_invalid_key_size() { + let iv = vec![0; 16]; + let iv = TWDataHelper::create(iv); + let key = vec![0; 19]; // Invalid key size + let key = TWDataHelper::create(key); + let data = vec![0; 100]; + let data = TWDataHelper::create(data); + + let result = unsafe { tw_aes_encrypt_ctr(key.ptr(), data.ptr(), iv.ptr()) }; + assert!( + result.is_null(), + "Expected null result for invalid key size" + ); +} + +#[test] +fn test_crypto_aes_ctr_decrypt_invalid_key_size() { + let iv = vec![0; 16]; + let iv = TWDataHelper::create(iv); + let key = vec![0; 19]; // Invalid key size + let key = TWDataHelper::create(key); + let data = vec![0; 100]; + let data = TWDataHelper::create(data); + + let result = unsafe { tw_aes_decrypt_ctr(key.ptr(), data.ptr(), iv.ptr()) }; + assert!( + result.is_null(), + "Expected null result for invalid key size" + ); +} diff --git a/rust/tw_crypto/tests/cryto_aes_cbc_ffi.rs b/rust/tw_crypto/tests/cryto_aes_cbc_ffi.rs new file mode 100644 index 00000000000..27efcfc914c --- /dev/null +++ b/rust/tw_crypto/tests/cryto_aes_cbc_ffi.rs @@ -0,0 +1,347 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_crypto::ffi::crypto_aes_cbc::*; +use tw_encoding::hex; +use tw_memory::ffi::tw_data::TWData; +use tw_memory::ffi::RawPtrTrait; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; + +#[test] +fn test_crypto_aes_cbc_encrypt_zero_padding() { + let key = + hex::decode("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4").unwrap(); + let key = TWDataHelper::create(key); + + let iv = hex::decode("000102030405060708090A0B0C0D0E0F").unwrap(); + let iv = TWDataHelper::create(iv); + + let data = hex::decode("6bc1bee22e409f96e93d7e117393172a").unwrap(); + let data = TWDataHelper::create(data); + + let encrypt_result = unsafe { tw_aes_encrypt_cbc(key.ptr(), data.ptr(), iv.ptr(), 0) }; + let encrypt_result = unsafe { TWData::from_ptr_as_mut(encrypt_result).unwrap() }; + + assert_eq!( + hex::encode(&encrypt_result.to_vec(), false), + "f58c4c04d6e5f1ba779eabfb5f7bfbd6" + ); +} + +#[test] +fn test_crypto_aes_cbc_decrypt_zero_padding() { + let key = + hex::decode("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4").unwrap(); + let key = TWDataHelper::create(key); + + let iv = hex::decode("000102030405060708090A0B0C0D0E0F").unwrap(); + let iv = TWDataHelper::create(iv); + + let cipher = hex::decode("f58c4c04d6e5f1ba779eabfb5f7bfbd6").unwrap(); + let cipher = TWDataHelper::create(cipher); + + let decrypt_result = unsafe { tw_aes_decrypt_cbc(key.ptr(), cipher.ptr(), iv.ptr(), 0) }; + let decrypt_result = unsafe { TWData::from_ptr_as_mut(decrypt_result).unwrap() }; + + assert_eq!( + hex::encode(&decrypt_result.to_vec(), false), + "6bc1bee22e409f96e93d7e117393172a" + ); +} + +#[test] +fn test_crypto_aes_cbc_encrypt_pkcs7_padding() { + let key2 = + hex::decode("bbc82a01ebdb14698faee4a9e5038de72c995a9f6bcdb21903d62408b0c5ca96").unwrap(); + let key2 = TWDataHelper::create(key2); + + let iv = hex::decode("37f8687086d31852979e228f4a97925b").unwrap(); + let iv = TWDataHelper::create(iv); + + let data = hex::decode("7b226a736f6e727063223a22322e30222c226964223a313535343334333833343735323434362c226572726f72223a7b22636f6465223a2d33323030302c226d657373616765223a2253657373696f6e2052656a6563746564227d7d").unwrap(); + let data = TWDataHelper::create(data); + + let encrypt_result = unsafe { tw_aes_encrypt_cbc(key2.ptr(), data.ptr(), iv.ptr(), 1) }; + let encrypt_result = unsafe { TWData::from_ptr_as_mut(encrypt_result).unwrap() }; + + assert_eq!(hex::encode(&encrypt_result.to_vec(), false), "23c75d1b3228742ddb12eeef5a5016e37a8980a77fabc6dd01e6a355d88851c611d37e0d17a2f9c30f659da6d42ba77aca9b84bd6a95e3924f47d9093fbf16e0fb55b165ec193489645b4f7d2573959305c8fa70f88fe5affc43e3084a5878d1"); +} + +#[test] +fn test_crypto_aes_cbc_decrypt_pkcs7_padding() { + let key2 = + hex::decode("bbc82a01ebdb14698faee4a9e5038de72c995a9f6bcdb21903d62408b0c5ca96").unwrap(); + let key2 = TWDataHelper::create(key2); + + let iv = hex::decode("debb62725b21c7577e4e498e10f096c7").unwrap(); + let iv = TWDataHelper::create(iv); + + let cipher = hex::decode("e7df9810ce66defcc03023ee945f5958c1d4697bf97945daeab5059c2bc6262642cbca82982ac690e77e16671770c200f348f743a7c6e5df5c74eb892ef9b45a9b5ddf0f08fa60c49e5b694688d1b0b521b43975e65b4e8d557a83f4d1aab0af").unwrap(); + let cipher = TWDataHelper::create(cipher); + + let decrypt_result = unsafe { tw_aes_decrypt_cbc(key2.ptr(), cipher.ptr(), iv.ptr(), 1) }; + let decrypt_result = unsafe { TWData::from_ptr_as_mut(decrypt_result).unwrap() }; + + assert_eq!(hex::encode(&decrypt_result.to_vec(), false), "7b226a736f6e727063223a22322e30222c226964223a313535343334333833343735323434362c226572726f72223a7b22636f6465223a2d33323030302c226d657373616765223a2253657373696f6e2052656a6563746564227d7d"); +} + +#[test] +fn test_crypto_aes_cbc_encrypt() { + let key = + hex::decode("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4").unwrap(); + let key = TWDataHelper::create(key); + + let iv = hex::decode("000102030405060708090A0B0C0D0E0F").unwrap(); + let iv = TWDataHelper::create(iv); + + let data = hex::decode("6bc1bee22e409f96e93d7e117393172a").unwrap(); + let data = TWDataHelper::create(data); + + let encrypt_result = unsafe { tw_aes_encrypt_cbc(key.ptr(), data.ptr(), iv.ptr(), 0) }; + let encrypt_result = unsafe { TWData::from_ptr_as_mut(encrypt_result).unwrap() }; + + assert_eq!( + hex::encode(&encrypt_result.to_vec(), false), + "f58c4c04d6e5f1ba779eabfb5f7bfbd6" + ); +} + +#[test] +fn test_crypto_aes_cbc_encrypt_with_padding() { + let key = + hex::decode("bf6cfdd852f79460981062f551f1dc3215b5e26609bc2a275d5b2da21798b489").unwrap(); + let key = TWDataHelper::create(key); + + let message = "secret message".as_bytes(); + let message = TWDataHelper::create(message.to_vec()); + + // Test with PKCS7 padding + { + let iv = hex::decode("f300888ca4f512cebdc0020ff0f7224c").unwrap(); + let iv = TWDataHelper::create(iv); + + let encrypt_result = unsafe { tw_aes_encrypt_cbc(key.ptr(), message.ptr(), iv.ptr(), 1) }; + let encrypt_result = unsafe { TWData::from_ptr_as_mut(encrypt_result).unwrap() }; + + assert_eq!( + hex::encode(&encrypt_result.to_vec(), false), + "7f896315e90e172bed65d005138f224d" + ); + } + + // Test with Zero padding + { + let iv = hex::decode("f300888ca4f512cebdc0020ff0f7224c").unwrap(); + let iv = TWDataHelper::create(iv); + + let encrypt_result = unsafe { tw_aes_encrypt_cbc(key.ptr(), message.ptr(), iv.ptr(), 0) }; + let encrypt_result = unsafe { TWData::from_ptr_as_mut(encrypt_result).unwrap() }; + + assert_eq!( + hex::encode(&encrypt_result.to_vec(), false), + "11bcbfebb2db19fb5a5cbf458e0f699e" + ); + } +} + +#[test] +fn test_crypto_aes_cbc_decrypt() { + let key = + hex::decode("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4").unwrap(); + let key = TWDataHelper::create(key); + + let iv = hex::decode("000102030405060708090A0B0C0D0E0F").unwrap(); + let iv = TWDataHelper::create(iv); + + let cipher = hex::decode("f58c4c04d6e5f1ba779eabfb5f7bfbd6").unwrap(); + let cipher = TWDataHelper::create(cipher); + + let decrypt_result = unsafe { tw_aes_decrypt_cbc(key.ptr(), cipher.ptr(), iv.ptr(), 0) }; + let decrypt_result = unsafe { TWData::from_ptr_as_mut(decrypt_result).unwrap() }; + + assert_eq!( + hex::encode(&decrypt_result.to_vec(), false), + "6bc1bee22e409f96e93d7e117393172a" + ); +} + +#[test] +fn test_crypto_aes_cbc_decrypt_with_padding() { + let key = + hex::decode("bf6cfdd852f79460981062f551f1dc3215b5e26609bc2a275d5b2da21798b489").unwrap(); + let key = TWDataHelper::create(key); + + // Test with PKCS7 padding + { + let encrypted_padded = hex::decode("7f896315e90e172bed65d005138f224d").unwrap(); + let encrypted_padded = TWDataHelper::create(encrypted_padded); + + let iv = hex::decode("f300888ca4f512cebdc0020ff0f7224c").unwrap(); + let iv = TWDataHelper::create(iv); + + let decrypt_result = + unsafe { tw_aes_decrypt_cbc(key.ptr(), encrypted_padded.ptr(), iv.ptr(), 1) }; + let decrypt_result = unsafe { TWData::from_ptr_as_mut(decrypt_result).unwrap() }; + + let secret_message = "secret message".as_bytes(); + assert_eq!(decrypt_result.to_vec(), secret_message); + } + + // Test with no padding + { + let encrypted_not_padded = hex::decode("11bcbfebb2db19fb5a5cbf458e0f699e").unwrap(); + let encrypted_not_padded = TWDataHelper::create(encrypted_not_padded); + + let iv = hex::decode("f300888ca4f512cebdc0020ff0f7224c").unwrap(); + let iv = TWDataHelper::create(iv); + + let decrypt_result = + unsafe { tw_aes_decrypt_cbc(key.ptr(), encrypted_not_padded.ptr(), iv.ptr(), 0) }; + let decrypt_result = unsafe { TWData::from_ptr_as_mut(decrypt_result).unwrap() }; + + let mut expected = "secret message".as_bytes().to_vec(); + expected.push(0); + expected.push(0); + assert_eq!(decrypt_result.to_vec(), expected); + } +} + +#[test] +fn test_crypto_aes_cbc_encrypt_multiple_blocks() { + let key = hex::decode("e1094a016e6029eabc6f9e3c3cd9afb8").unwrap(); + let key = TWDataHelper::create(key); + let iv = hex::decode("884b972d70acece4ecf9b790ffce177e").unwrap(); + let iv = TWDataHelper::create(iv); + let data = hex::decode("726970706c652073636973736f7273206b69636b206d616d6d616c206869726520636f6c756d6e206f616b20616761696e2073756e206f66666572207765616c746820746f6d6f72726f77207761676f6e207475726e20666174616c00").unwrap(); + let data = TWDataHelper::create(data); + + let encrypt_result = unsafe { tw_aes_encrypt_cbc(key.ptr(), data.ptr(), iv.ptr(), 0) }; + let encrypt_result = unsafe { TWData::from_ptr_as_mut(encrypt_result).unwrap() }; + + assert_eq!( + hex::encode(&encrypt_result.to_vec(), false), + "30e3ce939cdc80df375aaf6c2cdc7bc265f4eea20c90ab4825c5fc4b5c4517395ea1c28559bf0832a07f9a7fb8fc58786683a48aa8319be215a6b4a597eeaa443973b76401fe959c1bcb4991c9ee20b54c0244f8f43f0f0adcbb50e9ea913bf0" + ); +} + +#[test] +fn test_crypto_aes_cbc_decrypt_multiple_blocks() { + let key = hex::decode("fac192ceb5fd772906bea3e118a69e8b").unwrap(); + let key = TWDataHelper::create(key); + let iv = hex::decode("83dbcc02d8ccb40e466191a123791e0e").unwrap(); + let iv = TWDataHelper::create(iv); + let data = + hex::decode("d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c").unwrap(); + let data = TWDataHelper::create(data); + + let decrypt_result = unsafe { tw_aes_decrypt_cbc(key.ptr(), data.ptr(), iv.ptr(), 0) }; + let decrypt_result = unsafe { TWData::from_ptr_as_mut(decrypt_result).unwrap() }; + + assert_eq!( + hex::encode(&decrypt_result.to_vec(), false), + "d4ade7189ee99ba50399e60a27c9e0fd02cfd1cfa2d15e7491329f361645d2a4" + ); +} + +#[test] +fn test_crypto_aes_cbc_encrypt_invalid_key_size() { + let iv = TWDataHelper::create(vec![0; 16]); + let key = TWDataHelper::create(vec![0; 19]); + let data = TWDataHelper::create(vec![0; 100]); + + let result = unsafe { tw_aes_encrypt_cbc(key.ptr(), data.ptr(), iv.ptr(), 0) }; + assert!( + result.is_null(), + "Expected null result for invalid key size" + ); +} + +#[test] +fn test_crypto_aes_cbc_decrypt_invalid_key_size() { + let iv = TWDataHelper::create(vec![0; 16]); + let key = TWDataHelper::create(vec![0; 19]); + let data = TWDataHelper::create(vec![0; 100]); + + let result = unsafe { tw_aes_decrypt_cbc(key.ptr(), data.ptr(), iv.ptr(), 0) }; + assert!( + result.is_null(), + "Expected null result for invalid key size" + ); +} + +#[test] +fn test_crypto_aes_cbc_decrypt_invalid_data_size() { + let iv = TWDataHelper::create(vec![0; 16]); + let key = TWDataHelper::create(vec![0; 16]); + let data = TWDataHelper::create(vec![0; 100]); + + let result = unsafe { tw_aes_decrypt_cbc(key.ptr(), data.ptr(), iv.ptr(), 0) }; + assert!( + result.is_null(), + "Expected null result for invalid data size" + ); +} + +#[test] +fn test_crypto_aes_cbc_128_ffi() { + let data_hex = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"; + let data = hex::decode(data_hex).unwrap(); + let data = TWDataHelper::create(data); + let iv = hex::decode("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff").unwrap(); + let iv = TWDataHelper::create(iv); + let key = hex::decode("2b7e151628aed2a6abf7158809cf4f3c").unwrap(); + let key = TWDataHelper::create(key); + + let encrypted = unsafe { tw_aes_encrypt_cbc_128(key.ptr(), data.ptr(), iv.ptr(), 0) }; + let encrypted = unsafe { TWData::from_ptr_as_mut(encrypted).unwrap() }; + + let decrypted = unsafe { tw_aes_decrypt_cbc_128(key.ptr(), encrypted, iv.ptr(), 0) }; + let decrypted = unsafe { TWData::from_ptr_as_mut(decrypted).unwrap() }; + + let decrypted_data = decrypted.to_vec(); + assert_eq!(hex::encode(&decrypted_data, false), data_hex); +} + +#[test] +fn test_crypto_aes_cbc_192_ffi() { + let data_hex = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"; + let data = hex::decode(data_hex).unwrap(); + let data = TWDataHelper::create(data); + let iv = hex::decode("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff").unwrap(); + let iv = TWDataHelper::create(iv); + let key = hex::decode("8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b").unwrap(); + let key = TWDataHelper::create(key); + + let encrypted = unsafe { tw_aes_encrypt_cbc_192(key.ptr(), data.ptr(), iv.ptr(), 0) }; + let encrypted = unsafe { TWData::from_ptr_as_mut(encrypted).unwrap() }; + + let decrypted = unsafe { tw_aes_decrypt_cbc_192(key.ptr(), encrypted, iv.ptr(), 0) }; + let decrypted = unsafe { TWData::from_ptr_as_mut(decrypted).unwrap() }; + + let decrypted_data = decrypted.to_vec(); + assert_eq!(hex::encode(&decrypted_data, false), data_hex); +} + +#[test] +fn test_crypto_aes_cbc_256_ffi() { + let data_hex = "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"; + let data = hex::decode(data_hex).unwrap(); + let data = TWDataHelper::create(data); + let iv = hex::decode("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff").unwrap(); + let iv = TWDataHelper::create(iv); + let key = + hex::decode("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4").unwrap(); + let key = TWDataHelper::create(key); + + let encrypted = unsafe { tw_aes_encrypt_cbc_256(key.ptr(), data.ptr(), iv.ptr(), 0) }; + let encrypted = unsafe { TWData::from_ptr_as_mut(encrypted).unwrap() }; + + let decrypted = unsafe { tw_aes_decrypt_cbc_256(key.ptr(), encrypted, iv.ptr(), 0) }; + let decrypted = unsafe { TWData::from_ptr_as_mut(decrypted).unwrap() }; + + let decrypted_data = decrypted.to_vec(); + assert_eq!(hex::encode(&decrypted_data, false), data_hex); +} diff --git a/src/Encrypt.cpp b/src/Encrypt.cpp index a3a3c8a78a0..09c0d153310 100644 --- a/src/Encrypt.cpp +++ b/src/Encrypt.cpp @@ -4,104 +4,83 @@ #include "Encrypt.h" #include "Data.h" -#include #include #include #include -namespace TW::Encrypt { +#include "rust/Wrapper.h" +#include "TrustWalletCore/Generated/TWCrypto.h" -size_t paddingSize(size_t origSize, size_t blockSize, TWAESPaddingMode paddingMode) { - if (origSize % blockSize == 0) { - // even blocks - if (paddingMode == TWAESPaddingModePKCS7) { - return blockSize; - } - return 0; - } - // non-even - return blockSize - origSize % blockSize; -} +namespace TW::Encrypt { Data AESCBCEncrypt(const Data& key, const Data& data, Data& iv, TWAESPaddingMode paddingMode) { - aes_encrypt_ctx ctx; - if (aes_encrypt_key(key.data(), static_cast(key.size()), &ctx) == EXIT_FAILURE) { - throw std::invalid_argument("Invalid key"); - } + Rust::TWDataWrapper dataWrapper = data; + Rust::TWDataWrapper ivWrapper = iv; + Rust::TWDataWrapper keyWrapper = key; - // Message is padded to round block size, or by a full padding block if even - const size_t blockSize = AES_BLOCK_SIZE; - const auto padding = paddingSize(data.size(), blockSize, paddingMode); - const auto resultSize = data.size() + padding; - Data result(resultSize); - size_t idx; - for (idx = 0; idx < resultSize - blockSize; idx += blockSize) { - aes_cbc_encrypt(data.data() + idx, result.data() + idx, blockSize, iv.data(), &ctx); - } - // last block - if (idx < resultSize) { - uint8_t padded[blockSize] = {0}; - if (paddingMode == TWAESPaddingModePKCS7) { - std::memset(padded, static_cast(padding), blockSize); - } - std::memcpy(padded, data.data() + idx, data.size() - idx); - aes_cbc_encrypt(padded, result.data() + idx, blockSize, iv.data(), &ctx); + Rust::TWDataWrapper res = Rust::tw_aes_encrypt_cbc( + keyWrapper.get(), + dataWrapper.get(), + ivWrapper.get(), + paddingMode + ); + auto resData = res.toDataOrDefault(); + if (resData.empty()) { + throw std::runtime_error("Invalid aes cbc encrypt"); } - - return result; + return resData; } Data AESCBCDecrypt(const Data& key, const Data& data, Data& iv, TWAESPaddingMode paddingMode) { - const size_t blockSize = AES_BLOCK_SIZE; - if (data.size() % blockSize != 0) { - throw std::invalid_argument("Invalid data size"); - } - assert((data.size() % blockSize) == 0); + Rust::TWDataWrapper dataWrapper = data; + Rust::TWDataWrapper ivWrapper = iv; + Rust::TWDataWrapper keyWrapper = key; - aes_decrypt_ctx ctx; - if (aes_decrypt_key(key.data(), static_cast(key.size()), &ctx) != EXIT_SUCCESS) { - throw std::invalid_argument("Invalid key"); + Rust::TWDataWrapper res = Rust::tw_aes_decrypt_cbc( + keyWrapper.get(), + dataWrapper.get(), + ivWrapper.get(), + paddingMode + ); + auto resData = res.toDataOrDefault(); + if (resData.empty()) { + throw std::runtime_error("Invalid aes cbc decrypt"); } - - Data result(data.size()); - for (std::size_t i = 0; i < data.size(); i += blockSize) { - aes_cbc_decrypt(data.data() + i, result.data() + i, blockSize, iv.data(), &ctx); - } - - if (paddingMode == TWAESPaddingModePKCS7 && result.size() > 0) { - // need to remove padding - assert(result.size() > 0); - const byte paddingSize = result[result.size() - 1]; - if (paddingSize <= result.size()) { - // remove last paddingSize number of bytes - const size_t unpaddedSize = result.size() - paddingSize; - Data resultUnpadded = TW::data(result.data(), unpaddedSize); - return resultUnpadded; - } - } - return result; + return resData; } Data AESCTREncrypt(const Data& key, const Data& data, Data& iv) { - aes_encrypt_ctx ctx; - if (aes_encrypt_key(key.data(), static_cast(key.size()), &ctx) != EXIT_SUCCESS) { - throw std::invalid_argument("Invalid key"); - } + Rust::TWDataWrapper dataWrapper = data; + Rust::TWDataWrapper ivWrapper = iv; + Rust::TWDataWrapper keyWrapper = key; - Data result(data.size()); - aes_ctr_encrypt(data.data(), result.data(), static_cast(data.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); - return result; + Rust::TWDataWrapper res = Rust::tw_aes_encrypt_ctr( + keyWrapper.get(), + dataWrapper.get(), + ivWrapper.get() + ); + auto resData = res.toDataOrDefault(); + if (resData.empty()) { + throw std::runtime_error("Invalid aes ctr encrypt"); + } + return resData; } Data AESCTRDecrypt(const Data& key, const Data& data, Data& iv) { - aes_encrypt_ctx ctx; - if (aes_encrypt_key(key.data(), static_cast(key.size()), &ctx) != EXIT_SUCCESS) { - throw std::invalid_argument("Invalid key"); - } + Rust::TWDataWrapper dataWrapper = data; + Rust::TWDataWrapper ivWrapper = iv; + Rust::TWDataWrapper keyWrapper = key; - Data result(data.size()); - aes_ctr_decrypt(data.data(), result.data(), static_cast(data.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); - return result; + Rust::TWDataWrapper res = Rust::tw_aes_decrypt_ctr( + keyWrapper.get(), + dataWrapper.get(), + ivWrapper.get() + ); + auto resData = res.toDataOrDefault(); + if (resData.empty()) { + throw std::runtime_error("Invalid aes ctr decrypt"); + } + return resData; } } // namespace TW::Encrypt diff --git a/src/Keystore/EncryptionParameters.cpp b/src/Keystore/EncryptionParameters.cpp index e92fd6650f1..d7e089d1cee 100644 --- a/src/Keystore/EncryptionParameters.cpp +++ b/src/Keystore/EncryptionParameters.cpp @@ -6,8 +6,8 @@ #include "../Hash.h" -#include #include +#include #include "TrustWalletCore/Generated/TWCrypto.h" using namespace TW; @@ -71,6 +71,59 @@ static Data rustPbkdf2(const Data& password, const PBKDF2Parameters& params) { } return data; } +template +static Data rustAesOperation(const Data& data, const Data& iv, const Data& key, CryptoFunc cryptoFunc, const char* errorMsg) { + Rust::TWDataWrapper dataWrapper = data; + Rust::TWDataWrapper ivWrapper = iv; + Rust::TWDataWrapper keyWrapper = key; + + Rust::TWDataWrapper res = cryptoFunc( + keyWrapper.get(), + dataWrapper.get(), + ivWrapper.get() + ); + auto resData = res.toDataOrDefault(); + if (resData.empty()) { + throw std::runtime_error(errorMsg); + } + return resData; +} + +static Data rustAesCtrEncrypt128(const Data& data, const Data& iv, const Data& key) { + return rustAesOperation(data, iv, key, Rust::tw_aes_encrypt_ctr_128, "Invalid aes ctr encrypt 128"); +} + +static Data rustAesCtrDecrypt128(const Data& data, const Data& iv, const Data& key) { + return rustAesOperation(data, iv, key, Rust::tw_aes_decrypt_ctr_128, "Invalid aes ctr decrypt 128"); +} + +static Data rustAesCtrEncrypt192(const Data& data, const Data& iv, const Data& key) { + return rustAesOperation(data, iv, key, Rust::tw_aes_encrypt_ctr_192, "Invalid aes ctr encrypt 192"); +} + +static Data rustAesCtrDecrypt192(const Data& data, const Data& iv, const Data& key) { + return rustAesOperation(data, iv, key, Rust::tw_aes_decrypt_ctr_192, "Invalid aes ctr decrypt 192"); +} + +static Data rustAesCtrEncrypt256(const Data& data, const Data& iv, const Data& key) { + return rustAesOperation(data, iv, key, Rust::tw_aes_encrypt_ctr_256, "Invalid aes ctr encrypt 256"); +} + +static Data rustAesCtrDecrypt256(const Data& data, const Data& iv, const Data& key) { + return rustAesOperation(data, iv, key, Rust::tw_aes_decrypt_ctr_256, "Invalid aes ctr decrypt 256"); +} + +static Data rustAesCbcEncrypt(const Data& data, const Data& iv, const Data& key) { + return rustAesOperation(data, iv, key, + [](auto d, auto i, auto k) { return Rust::tw_aes_encrypt_cbc(d, i, k, TWAESPaddingModePKCS7); }, + "Invalid aes cbc encrypt"); +} + +static Data rustAesCbcDecrypt(const Data& data, const Data& iv, const Data& key) { + return rustAesOperation(data, iv, key, + [](auto d, auto i, auto k) { return Rust::tw_aes_decrypt_cbc(d, i, k, TWAESPaddingModePKCS7); }, + "Invalid aes cbc decrypt"); +} EncryptionParameters::EncryptionParameters(const nlohmann::json& json) { auto cipher = json[CodingKeys::cipher].get(); @@ -105,27 +158,21 @@ EncryptedPayload::EncryptedPayload(const Data& password, const Data& data, const auto scryptParams = std::get(this->params.kdfParams); auto derivedKey = rustScrypt(password, scryptParams); - aes_encrypt_ctx ctx; - auto result = 0; switch(this->params.cipherParams.mCipherEncryption) { case TWStoredKeyEncryptionAes128Ctr: - case TWStoredKeyEncryptionAes128Cbc: - result = aes_encrypt_key128(derivedKey.data(), &ctx); + encrypted = rustAesCtrEncrypt128(data, this->params.cipherParams.iv, derivedKey); break; case TWStoredKeyEncryptionAes192Ctr: - result = aes_encrypt_key192(derivedKey.data(), &ctx); + encrypted = rustAesCtrEncrypt192(data, this->params.cipherParams.iv, derivedKey); break; case TWStoredKeyEncryptionAes256Ctr: - result = aes_encrypt_key256(derivedKey.data(), &ctx); + encrypted = rustAesCtrEncrypt256(data, this->params.cipherParams.iv, derivedKey); + break; + case TWStoredKeyEncryptionAes128Cbc: + encrypted = rustAesCbcEncrypt(data, this->params.cipherParams.iv, derivedKey); break; } - assert(result == EXIT_SUCCESS); - if (result == EXIT_SUCCESS) { - Data iv = this->params.cipherParams.iv; - encrypted = Data(data.size()); - aes_ctr_encrypt(data.data(), encrypted.data(), static_cast(data.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); - _mac = computeMAC(derivedKey.end() - params.getKeyBytesSize(), derivedKey.end(), encrypted); - } + _mac = computeMAC(derivedKey.end() - params.getKeyBytesSize(), derivedKey.end(), encrypted); } EncryptedPayload::~EncryptedPayload() { @@ -153,24 +200,19 @@ Data EncryptedPayload::decrypt(const Data& password) const { Data decrypted(encrypted.size()); Data iv = params.cipherParams.iv; - const auto encryption = params.cipherParams.mCipherEncryption; - if (encryption == TWStoredKeyEncryptionAes128Ctr || encryption == TWStoredKeyEncryptionAes256Ctr) { - aes_encrypt_ctx ctx; - [[maybe_unused]] auto result = aes_encrypt_key(derivedKey.data(), params.getKeyBytesSize(), &ctx); - assert(result != EXIT_FAILURE); - - aes_ctr_decrypt(encrypted.data(), decrypted.data(), static_cast(encrypted.size()), iv.data(), - aes_ctr_cbuf_inc, &ctx); - } else if (encryption == TWStoredKeyEncryptionAes128Cbc) { - aes_decrypt_ctx ctx; - [[maybe_unused]] auto result = aes_decrypt_key(derivedKey.data(), params.getKeyBytesSize(), &ctx); - assert(result != EXIT_FAILURE); - - for (auto i = 0ul; i < encrypted.size(); i += params.getKeyBytesSize()) { - aes_cbc_decrypt(encrypted.data() + i, decrypted.data() + i, params.getKeyBytesSize(), iv.data(), &ctx); - } - } else { - throw DecryptionError::unsupportedCipher; + switch (params.cipherParams.mCipherEncryption) { + case TWStoredKeyEncryptionAes128Ctr: + decrypted = rustAesCtrDecrypt128(encrypted, iv, derivedKey); + break; + case TWStoredKeyEncryptionAes192Ctr: + decrypted = rustAesCtrDecrypt192(encrypted, iv, derivedKey); + break; + case TWStoredKeyEncryptionAes256Ctr: + decrypted = rustAesCtrDecrypt256(encrypted, iv, derivedKey); + break; + case TWStoredKeyEncryptionAes128Cbc: + decrypted = rustAesCbcDecrypt(encrypted, iv, derivedKey); + break; } return decrypted; diff --git a/src/interface/TWAES.cpp b/src/interface/TWAES.cpp deleted file mode 100644 index 7b63e735015..00000000000 --- a/src/interface/TWAES.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#include - -#include <../../src/Encrypt.h> - -using namespace TW; - -TWData *_Nullable TWAESEncryptCBC(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv, enum TWAESPaddingMode mode) { - try { - Data encrypted = Encrypt::AESCBCEncrypt(*((Data*)key), *((Data*)data), *((Data*)iv), mode); - return TWDataCreateWithData(&encrypted); - } catch (...) { - return nullptr; - } -} - -TWData *_Nullable TWAESDecryptCBC(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv, enum TWAESPaddingMode mode) { - try { - Data decrypted = Encrypt::AESCBCDecrypt(*((Data*)key), *((Data*)data), *((Data*)iv), mode); - return TWDataCreateWithData(&decrypted); - } catch (...) { - return nullptr; - } -} - -TWData *_Nullable TWAESEncryptCTR(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv) { - try { - Data encrypted = Encrypt::AESCTREncrypt(*((Data*)key), *((Data*)data), *((Data*)iv)); - return TWDataCreateWithData(&encrypted); - } catch (...) { - return nullptr; - } -} - -TWData *_Nullable TWAESDecryptCTR(TWData *_Nonnull key, TWData *_Nonnull data, TWData *_Nonnull iv) { - try { - Data decrypted = Encrypt::AESCTRDecrypt(*((Data*)key), *((Data*)data), *((Data*)iv)); - return TWDataCreateWithData(&decrypted); - } catch (...) { - return nullptr; - } -} diff --git a/tests/common/EncryptTests.cpp b/tests/common/EncryptTests.cpp index 52c030913a2..8e8d58c7614 100644 --- a/tests/common/EncryptTests.cpp +++ b/tests/common/EncryptTests.cpp @@ -20,27 +20,6 @@ inline void assertHexEqual(const Data& data, const char* expected) { EXPECT_EQ(hex(data), expected); } -TEST(Encrypt, paddingSize) { - EXPECT_EQ(paddingSize(0, 16, TWAESPaddingModeZero), 0ul); - EXPECT_EQ(paddingSize(1, 16, TWAESPaddingModeZero), 15ul); - EXPECT_EQ(paddingSize(8, 16, TWAESPaddingModeZero), 8ul); - EXPECT_EQ(paddingSize(15, 16, TWAESPaddingModeZero), 1ul); - EXPECT_EQ(paddingSize(16, 16, TWAESPaddingModeZero), 0ul); - EXPECT_EQ(paddingSize(17, 16, TWAESPaddingModeZero), 15ul); - EXPECT_EQ(paddingSize(24, 16, TWAESPaddingModeZero), 8ul); - EXPECT_EQ(paddingSize(31, 16, TWAESPaddingModeZero), 1ul); - EXPECT_EQ(paddingSize(32, 16, TWAESPaddingModeZero), 0ul); - EXPECT_EQ(paddingSize(0, 16, TWAESPaddingModePKCS7), 16ul); - EXPECT_EQ(paddingSize(1, 16, TWAESPaddingModePKCS7), 15ul); - EXPECT_EQ(paddingSize(8, 16, TWAESPaddingModePKCS7), 8ul); - EXPECT_EQ(paddingSize(15, 16, TWAESPaddingModePKCS7), 1ul); - EXPECT_EQ(paddingSize(16, 16, TWAESPaddingModePKCS7), 16ul); - EXPECT_EQ(paddingSize(17, 16, TWAESPaddingModePKCS7), 15ul); - EXPECT_EQ(paddingSize(24, 16, TWAESPaddingModePKCS7), 8ul); - EXPECT_EQ(paddingSize(31, 16, TWAESPaddingModePKCS7), 1ul); - EXPECT_EQ(paddingSize(32, 16, TWAESPaddingModePKCS7), 16ul); -} - TEST(Encrypt, AESCBCEncrypt) { auto iv = parse_hex("000102030405060708090A0B0C0D0E0F"); auto data = parse_hex("6bc1bee22e409f96e93d7e117393172a"); @@ -149,7 +128,7 @@ TEST(Encrypt, AESCBCEncryptInvalidKeySize) { Data iv = Data(16); try { Data result = AESCBCEncrypt(Data(19), Data(100), iv); - } catch (std::invalid_argument&) { + } catch (...) { // expected exception, OK return; } @@ -160,7 +139,7 @@ TEST(Encrypt, AESCBCDecryptInvalidKeySize) { Data iv = Data(16); try { Data result = AESCBCDecrypt(Data(19), Data(100), iv); - } catch (std::invalid_argument&) { + } catch (...) { // expected exception, OK return; } @@ -171,7 +150,7 @@ TEST(Encrypt, AESCBCDecryptInvalidDataSize) { Data iv = Data(16); try { Data result = AESCBCDecrypt(Data(16), Data(100), iv); - } catch (std::invalid_argument&) { + } catch (...) { // expected exception, OK return; } @@ -182,7 +161,7 @@ TEST(Encrypt, AESCTREncryptInvalidKeySize) { Data iv = Data(16); try { Data result = AESCTREncrypt(Data(19), Data(100), iv); - } catch (std::invalid_argument&) { + } catch (...) { // expected exception, OK return; } @@ -193,7 +172,7 @@ TEST(Encrypt, AESCTRDecryptInvalidKeySize) { Data iv = Data(16); try { Data result = AESCTRDecrypt(Data(19), Data(100), iv); - } catch (std::invalid_argument&) { + } catch (...) { // expected exception, OK return; } diff --git a/tests/common/Keystore/StoredKeyTests.cpp b/tests/common/Keystore/StoredKeyTests.cpp index 02c9bed047b..f3e3cbfb1e5 100644 --- a/tests/common/Keystore/StoredKeyTests.cpp +++ b/tests/common/Keystore/StoredKeyTests.cpp @@ -49,6 +49,54 @@ TEST(StoredKey, CreateWithMnemonic) { EXPECT_EQ(json["crypto"]["kdfparams"]["salt"].get().size(), 64ul); } +TEST(StoredKey, CreateWithMnemonicAes192Ctr) { + auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelDefault, TWStoredKeyEncryptionAes192Ctr); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + const Data& mnemo2Data = key.payload.decrypt(gPassword); + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gMnemonic)); + EXPECT_EQ(key.accounts.size(), 0ul); + EXPECT_EQ(key.wallet(gPassword).getMnemonic(), string(gMnemonic)); + + const auto json = key.json(); + EXPECT_EQ(json["name"], "name"); + EXPECT_EQ(json["type"], "mnemonic"); + EXPECT_EQ(json["version"], 3); + // Salt is 32 bytes, encoded as hex. + EXPECT_EQ(json["crypto"]["kdfparams"]["salt"].get().size(), 64ul); +} + +TEST(StoredKey, CreateWithMnemonicAes256Ctr) { + auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelDefault, TWStoredKeyEncryptionAes256Ctr); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + const Data& mnemo2Data = key.payload.decrypt(gPassword); + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gMnemonic)); + EXPECT_EQ(key.accounts.size(), 0ul); + EXPECT_EQ(key.wallet(gPassword).getMnemonic(), string(gMnemonic)); + + const auto json = key.json(); + EXPECT_EQ(json["name"], "name"); + EXPECT_EQ(json["type"], "mnemonic"); + EXPECT_EQ(json["version"], 3); + // Salt is 32 bytes, encoded as hex. + EXPECT_EQ(json["crypto"]["kdfparams"]["salt"].get().size(), 64ul); +} + +TEST(StoredKey, CreateWithMnemonicCbc) { + auto key = StoredKey::createWithMnemonic("name", gPassword, gMnemonic, TWStoredKeyEncryptionLevelDefault, TWStoredKeyEncryptionAes128Cbc); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + const Data& mnemo2Data = key.payload.decrypt(gPassword); + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gMnemonic)); + EXPECT_EQ(key.accounts.size(), 0ul); + EXPECT_EQ(key.wallet(gPassword).getMnemonic(), string(gMnemonic)); + + const auto json = key.json(); + EXPECT_EQ(json["name"], "name"); + EXPECT_EQ(json["type"], "mnemonic"); + EXPECT_EQ(json["version"], 3); + // Salt is 32 bytes, encoded as hex. + EXPECT_EQ(json["crypto"]["kdfparams"]["salt"].get().size(), 64ul); +} + TEST(StoredKey, CreateWithMnemonicInvalid) { try { auto key = StoredKey::createWithMnemonic("name", gPassword, "_THIS_IS_NOT_A_VALID_MNEMONIC_", TWStoredKeyEncryptionLevelDefault); @@ -461,6 +509,22 @@ TEST(StoredKey, CreateAccountsAes256) { EXPECT_EQ(key.account(coinTypeBc, &wallet)->extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); } +TEST(StoredKey, CreateAccountsAesCbc128) { + string mnemonicPhrase = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; + auto key = StoredKey::createWithMnemonic("name", gPassword, mnemonicPhrase, TWStoredKeyEncryptionLevelDefault, TWStoredKeyEncryptionAes128Cbc); + auto header = key.payload; + const auto wallet = key.wallet(gPassword); + + EXPECT_EQ(header.params.cipher(), "aes-128-cbc"); + EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->address, "0x494f60cb6Ac2c8F5E1393aD9FdBdF4Ad589507F7"); + EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->publicKey, "04cc32a479080d83fdcf69966713f0aad1bc1dc3ecf873b034894e84259841bc1c9b122717803e68905220ff54952d3f5ea2ab2698ca31f843addf94ae73fae9fd"); + EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->extendedPublicKey, ""); + + EXPECT_EQ(key.account(coinTypeBc, &wallet)->address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + EXPECT_EQ(key.account(coinTypeBc, &wallet)->publicKey, "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006"); + EXPECT_EQ(key.account(coinTypeBc, &wallet)->extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); +} + TEST(StoredKey, DecodingEthereumAddress) { const auto key = StoredKey::load(testDataPath("key.json")); diff --git a/tests/interface/TWAESTests.cpp b/tests/interface/TWAESTests.cpp index 52675f2aaab..f492703c2a3 100644 --- a/tests/interface/TWAESTests.cpp +++ b/tests/interface/TWAESTests.cpp @@ -4,7 +4,8 @@ #include "TestUtilities.h" -#include +#include +#include #include From eb4d6fc68dc2b35d0a33ee26874b69fd69021397 Mon Sep 17 00:00:00 2001 From: gupnik Date: Thu, 1 May 2025 01:35:36 +0530 Subject: [PATCH 21/23] Replaces `bip39.c` with Rust implementation via FFI (#4384) * Replaces `bip39.c` with Rust implementation via FFI * Makes clippy happy and minor fix * Minor fix * Minor * Adds support for Nonnull in code generator * Fixes sample go * Tries to fix memory leak * Use `toStringOrDefault()` * Addresses review comments * Further changes * Uses reference from BIP32 crate to obtain the seed * Minor * Minor --- codegen-v2/src/codegen/cpp/code_gen.rs | 26 +- .../src/codegen/swift/templates/WalletCore.h | 1 - include/TrustWalletCore/TWMnemonic.h | 37 -- rust/Cargo.lock | 39 ++ rust/tw_crypto/Cargo.toml | 2 + .../tw_crypto/src/crypto_mnemonic/mnemonic.rs | 107 ++++++ rust/tw_crypto/src/crypto_mnemonic/mod.rs | 7 + rust/tw_crypto/src/ffi/crypto_mnemonic.rs | 102 +++++ rust/tw_crypto/src/ffi/mod.rs | 1 + rust/tw_crypto/src/lib.rs | 1 + rust/tw_crypto/tests/crypto_mnemonic_ffi.rs | 349 ++++++++++++++++++ samples/go/core/mnemonic.go | 2 +- samples/go/dev-console/native/twmnemonic.go | 2 +- src/HDWallet.cpp | 30 +- src/Mnemonic.cpp | 80 ++-- src/Mnemonic.h | 11 + src/interface/TWMnemonic.cpp | 22 -- tests/interface/TWHDWalletTests.cpp | 2 +- tests/interface/TWMnemonicTests.cpp | 2 +- 19 files changed, 687 insertions(+), 136 deletions(-) delete mode 100644 include/TrustWalletCore/TWMnemonic.h create mode 100644 rust/tw_crypto/src/crypto_mnemonic/mnemonic.rs create mode 100644 rust/tw_crypto/src/crypto_mnemonic/mod.rs create mode 100644 rust/tw_crypto/src/ffi/crypto_mnemonic.rs create mode 100644 rust/tw_crypto/tests/crypto_mnemonic_ffi.rs delete mode 100644 src/interface/TWMnemonic.cpp diff --git a/codegen-v2/src/codegen/cpp/code_gen.rs b/codegen-v2/src/codegen/cpp/code_gen.rs index e27e9d0c131..569d018e955 100644 --- a/codegen-v2/src/codegen/cpp/code_gen.rs +++ b/codegen-v2/src/codegen/cpp/code_gen.rs @@ -38,10 +38,16 @@ fn generate_header_includes(file: &mut std::fs::File, info: &TWConfig) -> Result continue; } if included_headers.insert(header_name.clone()) { - if std::path::Path::new(&format!("{}{}.h", HEADER_IN_DIR, header_name)).exists() { + if std::path::Path::new(&format!("{}{}.h", HEADER_IN_DIR, header_name)) + .exists() + { writeln!(file, "#include ", header_name)?; } else { - writeln!(file, "#include ", header_name)?; + writeln!( + file, + "#include ", + header_name + )?; } } } @@ -179,7 +185,11 @@ fn generate_wrapper_header(info: &TWConfig) -> Result<()> { } fn generate_source_includes(file: &mut std::fs::File, info: &TWConfig) -> Result<()> { - writeln!(file, "#include ", info.class)?; + writeln!( + file, + "#include ", + info.class + )?; writeln!(file, "#include \"rust/Wrapper.h\"")?; // Include headers based on argument types @@ -244,7 +254,15 @@ fn generate_return_type(func: &TWFunction, converted_args: &Vec) -> Resu .map_err(|e| BadFormat(e.to_string()))?; } (TWPointerType::NonnullMut, "TWString") | (TWPointerType::Nonnull, "TWString") => { - panic!("Nonnull TWString is not supported"); + write!( + &mut return_string, + "\tconst Rust::TWStringWrapper result = Rust::{}{}\n\ + \tconst auto resultString = result.toStringOrDefault();\n\ + \treturn TWStringCreateWithUTF8Bytes(resultString.c_str());\n", + func.rust_name, + generate_function_call(&converted_args)?.as_str() + ) + .map_err(|e| BadFormat(e.to_string()))?; } (TWPointerType::NullableMut, "TWData") | (TWPointerType::Nullable, "TWData") => { write!( diff --git a/codegen-v2/src/codegen/swift/templates/WalletCore.h b/codegen-v2/src/codegen/swift/templates/WalletCore.h index a0d5d0e9127..896d167eb4e 100644 --- a/codegen-v2/src/codegen/swift/templates/WalletCore.h +++ b/codegen-v2/src/codegen/swift/templates/WalletCore.h @@ -53,7 +53,6 @@ FOUNDATION_EXPORT const unsigned char WalletCoreVersionString[]; #include "TWHRP.h" #include "TWHash.h" #include "TWLiquidStaking.h" -#include "TWMnemonic.h" #include "TWNEARAccount.h" #include "TWNervosAddress.h" #include "TWPBKDF2.h" diff --git a/include/TrustWalletCore/TWMnemonic.h b/include/TrustWalletCore/TWMnemonic.h deleted file mode 100644 index 2cfba1dba70..00000000000 --- a/include/TrustWalletCore/TWMnemonic.h +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#pragma once - -#include "TWBase.h" -#include "TWString.h" - -TW_EXTERN_C_BEGIN - -/// Mnemonic validate / lookup functions -TW_EXPORT_STRUCT -struct TWMnemonic; - -/// Determines whether a BIP39 English mnemonic phrase is valid. -/// -/// \param mnemonic Non-null BIP39 english mnemonic -/// \return true if the mnemonic is valid, false otherwise -TW_EXPORT_STATIC_METHOD -bool TWMnemonicIsValid(TWString *_Nonnull mnemonic); - -/// Determines whether word is a valid BIP39 English mnemonic word. -/// -/// \param word Non-null BIP39 English mnemonic word -/// \return true if the word is a valid BIP39 English mnemonic word, false otherwise -TW_EXPORT_STATIC_METHOD -bool TWMnemonicIsValidWord(TWString *_Nonnull word); - -/// Return BIP39 English words that match the given prefix. A single string is returned, with space-separated list of words. -/// -/// \param prefix Non-null string prefix -/// \return Single non-null string, space-separated list of words containing BIP39 words that match the given prefix. -TW_EXPORT_STATIC_METHOD -TWString* _Nonnull TWMnemonicSuggest(TWString *_Nonnull prefix); - -TW_EXTERN_C_END diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 3adedf6b4bc..bb957cce985 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -198,6 +198,19 @@ dependencies = [ "serde", ] +[[package]] +name = "bip39" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33415e24172c1b7d6066f6d999545375ab8e1d95421d6784bdfff9496f292387" +dependencies = [ + "bitcoin_hashes", + "rand", + "rand_core", + "serde", + "unicode-normalization", +] + [[package]] name = "bitcoin" version = "0.30.1" @@ -1755,6 +1768,21 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "toml_datetime" version = "0.6.5" @@ -1989,6 +2017,7 @@ name = "tw_crypto" version = "0.1.0" dependencies = [ "aes", + "bip39", "cbc", "ctr", "pbkdf2", @@ -1998,6 +2027,7 @@ dependencies = [ "tw_macros", "tw_memory", "tw_misc", + "zeroize", ] [[package]] @@ -2529,6 +2559,15 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-width" version = "0.1.10" diff --git a/rust/tw_crypto/Cargo.toml b/rust/tw_crypto/Cargo.toml index 2f99218dd87..4db96645097 100644 --- a/rust/tw_crypto/Cargo.toml +++ b/rust/tw_crypto/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] aes = "0.8.4" +bip39 = { version = "2.1.0", features = ["rand" ] } cbc = "0.1.2" ctr = "0.9.2" pbkdf2 = "0.12.2" @@ -15,6 +16,7 @@ tw_encoding = { path = "../tw_encoding" } tw_macros = { path = "../tw_macros" } tw_memory = { path = "../tw_memory" } tw_misc = { path = "../tw_misc" } +zeroize = "1.8.1" [dev-dependencies] tw_memory = { path = "../tw_memory", features = ["test-utils"] } diff --git a/rust/tw_crypto/src/crypto_mnemonic/mnemonic.rs b/rust/tw_crypto/src/crypto_mnemonic/mnemonic.rs new file mode 100644 index 00000000000..329faeac544 --- /dev/null +++ b/rust/tw_crypto/src/crypto_mnemonic/mnemonic.rs @@ -0,0 +1,107 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use bip39::{Error, Language, Mnemonic as Bip39Mnemonic}; +use std::fmt; + +const SUGGEST_MAX_COUNT: usize = 10; +const PBKDF2_ROUNDS: u32 = 2048; +const SEED_SIZE: usize = 64; + +pub struct Mnemonic { + mnemonic: Bip39Mnemonic, +} + +impl Mnemonic { + pub fn generate(strength: u32) -> Result { + if strength % 32 != 0 || !(128..=256).contains(&strength) { + return Err(Error::BadEntropyBitCount(strength as usize)); + } + + let length = strength / 8; + let mnemonic_length = length * 3 / 4; + + let mut rng = bip39::rand::thread_rng(); + let mnemonic = + Bip39Mnemonic::generate_in_with(&mut rng, Language::English, mnemonic_length as usize)?; + + Ok(Self { mnemonic }) + } + + pub fn generate_from_data(data: &[u8]) -> Result { + let mnemonic = Bip39Mnemonic::from_entropy(data)?; + Ok(Self { mnemonic }) + } + + pub fn parse(mnemonic: &str) -> Result { + if mnemonic.trim() != mnemonic || mnemonic.contains(" ") { + return Err(Error::InvalidChecksum); + } + let mnemonic = Bip39Mnemonic::parse_in(Language::English, mnemonic)?; + Ok(Self { mnemonic }) + } + + pub fn is_valid(mnemonic: &str) -> bool { + Self::parse(mnemonic).is_ok() + } + + pub fn is_valid_word(word: &str) -> bool { + let language = Language::English; + language.find_word(word).is_some() + } + + pub fn get_word(index: u32) -> Option { + let language = Language::English; + language + .word_list() + .get(index as usize) + .map(|w| w.to_string()) + } + + pub fn find_word(word: &str) -> Option { + let language = Language::English; + language.find_word(word).map(|index| index as u32) + } + + pub fn suggest(prefix: &str) -> String { + if prefix.is_empty() { + return "".to_string(); + } + let language = Language::English; + let prefix_string = prefix.to_lowercase(); + let words = language.words_by_prefix(&prefix_string); + let words_string = words + .iter() + .take(SUGGEST_MAX_COUNT) + .map(|w| w.to_string()) + .collect::>() + .join(" "); + words_string + } + + // Taken from https://github.com/iqlusioninc/crates/blob/95c6b87ce657dc51a0bd11159ef39c603a197f8d/bip32/src/mnemonic/phrase.rs#L134 + pub fn to_seed(mnemonic: &str, passphrase: &str) -> [u8; SEED_SIZE] { + let mut seed = [0u8; SEED_SIZE]; + let salt = zeroize::Zeroizing::new(format!("mnemonic{}", passphrase)); + pbkdf2::pbkdf2_hmac::( + mnemonic.as_bytes(), + salt.as_bytes(), + PBKDF2_ROUNDS, + &mut seed, + ); + seed + } + + pub fn to_entropy(&self) -> Vec { + self.mnemonic.to_entropy() + } +} + +impl fmt::Display for Mnemonic { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.mnemonic) + } +} diff --git a/rust/tw_crypto/src/crypto_mnemonic/mod.rs b/rust/tw_crypto/src/crypto_mnemonic/mod.rs new file mode 100644 index 00000000000..84cdb0d18dd --- /dev/null +++ b/rust/tw_crypto/src/crypto_mnemonic/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod mnemonic; diff --git a/rust/tw_crypto/src/ffi/crypto_mnemonic.rs b/rust/tw_crypto/src/ffi/crypto_mnemonic.rs new file mode 100644 index 00000000000..5a5ee41bab3 --- /dev/null +++ b/rust/tw_crypto/src/ffi/crypto_mnemonic.rs @@ -0,0 +1,102 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![allow(clippy::missing_safety_doc)] + +use crate::crypto_mnemonic::mnemonic::Mnemonic; +use tw_macros::tw_ffi; +use tw_memory::ffi::{ + tw_data::TWData, tw_string::TWString, Nonnull, NonnullMut, NullableMut, RawPtrTrait, +}; +use tw_misc::{try_or_else, try_or_false}; + +#[tw_ffi(ty = static_function, class = TWMnemonic, name = Generate)] +#[no_mangle] +pub unsafe extern "C" fn tw_mnemonic_generate(strength: u32) -> NullableMut { + let mnemonic = try_or_else!(Mnemonic::generate(strength), std::ptr::null_mut); + TWString::from(mnemonic.to_string()).into_ptr() +} + +#[tw_ffi(ty = static_function, class = TWMnemonic, name = GenerateFromData)] +#[no_mangle] +pub unsafe extern "C" fn tw_mnemonic_generate_from_data( + data: Nonnull, +) -> NullableMut { + let data = TWData::from_ptr_as_ref(data) + .map(|data| data.as_slice()) + .unwrap_or_default(); + + let mnemonic = try_or_else!(Mnemonic::generate_from_data(data), std::ptr::null_mut); + TWString::from(mnemonic.to_string()).into_ptr() +} + +#[tw_ffi(ty = static_function, class = TWMnemonic, name = IsValid)] +#[no_mangle] +pub unsafe extern "C" fn tw_mnemonic_is_valid(mnemonic: Nonnull) -> bool { + let mnemonic_string = try_or_false!(TWString::from_ptr_as_ref(mnemonic)); + let mnemonic_string = try_or_false!(mnemonic_string.as_str()); + Mnemonic::is_valid(mnemonic_string) +} + +#[tw_ffi(ty = static_function, class = TWMnemonic, name = IsValidWord)] +#[no_mangle] +pub unsafe extern "C" fn tw_mnemonic_is_valid_word(word: Nonnull) -> bool { + let word_string = try_or_false!(TWString::from_ptr_as_ref(word)); + let word_string = try_or_false!(word_string.as_str()); + Mnemonic::is_valid_word(word_string) +} + +#[tw_ffi(ty = static_function, class = TWMnemonic, name = GetWord)] +#[no_mangle] +pub unsafe extern "C" fn tw_mnemonic_get_word(index: u32) -> NullableMut { + let word = try_or_else!(Mnemonic::get_word(index), std::ptr::null_mut); + TWString::from(word.to_string()).into_ptr() +} + +#[tw_ffi(ty = static_function, class = TWMnemonic, name = FindWord)] +#[no_mangle] +pub unsafe extern "C" fn tw_mnemonic_find_word(word: Nonnull) -> i32 { + let word_string = try_or_else!(TWString::from_ptr_as_ref(word), || -1); + let word_string = try_or_else!(word_string.as_str(), || -1); + let index = Mnemonic::find_word(word_string); + index.map(|index| index as i32).unwrap_or(-1) +} + +#[tw_ffi(ty = static_function, class = TWMnemonic, name = Suggest)] +#[no_mangle] +pub unsafe extern "C" fn tw_mnemonic_suggest(prefix: Nonnull) -> NonnullMut { + let prefix_string = try_or_else!(TWString::from_ptr_as_ref(prefix), std::ptr::null_mut); + let prefix_string = try_or_else!(prefix_string.as_str(), std::ptr::null_mut); + let words_string = Mnemonic::suggest(prefix_string); + TWString::from(words_string).into_ptr() +} + +#[tw_ffi(ty = static_function, class = TWMnemonic, name = ToSeed)] +#[no_mangle] +pub unsafe extern "C" fn tw_mnemonic_to_seed( + mnemonic: Nonnull, + passphrase: Nonnull, +) -> NullableMut { + let mnemonic_string = try_or_else!(TWString::from_ptr_as_ref(mnemonic), std::ptr::null_mut); + let mnemonic_string = try_or_else!(mnemonic_string.as_str(), std::ptr::null_mut); + let passphrase_string = try_or_else!(TWString::from_ptr_as_ref(passphrase), std::ptr::null_mut); + let passphrase_string = try_or_else!(passphrase_string.as_str(), std::ptr::null_mut); + let seed = Mnemonic::to_seed(mnemonic_string, passphrase_string); + TWData::from(seed.to_vec()).into_ptr() +} + +#[tw_ffi(ty = static_function, class = TWMnemonic, name = ToEntropy)] +#[no_mangle] +pub unsafe extern "C" fn tw_mnemonic_to_entropy( + mnemonic: Nonnull, +) -> NullableMut { + let mnemonic_string = try_or_else!(TWString::from_ptr_as_ref(mnemonic), std::ptr::null_mut); + let mnemonic_string = try_or_else!(mnemonic_string.as_str(), std::ptr::null_mut); + let mnemonic = try_or_else!(Mnemonic::parse(mnemonic_string), || TWData::from(vec![]) + .into_ptr()); + let entropy = mnemonic.to_entropy(); + TWData::from(entropy).into_ptr() +} diff --git a/rust/tw_crypto/src/ffi/mod.rs b/rust/tw_crypto/src/ffi/mod.rs index 6716d80ff4c..3b79998b9bd 100644 --- a/rust/tw_crypto/src/ffi/mod.rs +++ b/rust/tw_crypto/src/ffi/mod.rs @@ -6,5 +6,6 @@ pub mod crypto_aes_cbc; pub mod crypto_aes_ctr; +pub mod crypto_mnemonic; pub mod crypto_pbkdf2; pub mod crypto_scrypt; diff --git a/rust/tw_crypto/src/lib.rs b/rust/tw_crypto/src/lib.rs index 5f3cff0fb90..46958f5f9ca 100644 --- a/rust/tw_crypto/src/lib.rs +++ b/rust/tw_crypto/src/lib.rs @@ -6,6 +6,7 @@ pub mod crypto_aes_cbc; pub mod crypto_aes_ctr; +pub mod crypto_mnemonic; pub mod crypto_pbkdf2; pub mod crypto_scrypt; pub mod ffi; diff --git a/rust/tw_crypto/tests/crypto_mnemonic_ffi.rs b/rust/tw_crypto/tests/crypto_mnemonic_ffi.rs new file mode 100644 index 00000000000..fd2a4d9d0d1 --- /dev/null +++ b/rust/tw_crypto/tests/crypto_mnemonic_ffi.rs @@ -0,0 +1,349 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_crypto::ffi::crypto_mnemonic::*; +use tw_encoding::hex; +use tw_memory::ffi::tw_data::TWData; +use tw_memory::ffi::tw_string::TWString; +use tw_memory::ffi::RawPtrTrait; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_memory::test_utils::tw_string_helper::TWStringHelper; + +#[test] +fn test_bip39_generate_and_check_mnemonic() { + let strength = 128; + let res = unsafe { tw_mnemonic_generate(strength) }; + let res = unsafe { TWString::from_ptr_as_ref(res).unwrap() }; + let mnemonic_string = res.as_str().unwrap(); + let mnemonic_words = mnemonic_string.split(" ").collect::>(); + assert_eq!(mnemonic_words.len(), 12); + + let is_valid = unsafe { tw_mnemonic_is_valid(res) }; + assert!(is_valid); + + let strength = 192; + let res = unsafe { tw_mnemonic_generate(strength) }; + let res = unsafe { TWString::from_ptr_as_ref(res).unwrap() }; + let mnemonic_string = res.as_str().unwrap(); + let mnemonic_words = mnemonic_string.split(" ").collect::>(); + assert_eq!(mnemonic_words.len(), 18); + + let is_valid = unsafe { tw_mnemonic_is_valid(res) }; + assert!(is_valid); + + let strength = 256; + let res = unsafe { tw_mnemonic_generate(strength) }; + let res = unsafe { TWString::from_ptr_as_ref(res).unwrap() }; + let mnemonic_string = res.as_str().unwrap(); + let mnemonic_words = mnemonic_string.split(" ").collect::>(); + assert_eq!(mnemonic_words.len(), 24); + + let is_valid = unsafe { tw_mnemonic_is_valid(res) }; + assert!(is_valid); +} + +#[test] +fn test_bip39_generate_mnemonic_from_data() { + let test_vectors = [ + // entropy, expected mnemonic, expected seed with passphrase "TREZOR" + ("00000000000000000000000000000000", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04"), + + ("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + "legal winner thank year wave sausage worth useful legal winner thank yellow", + "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607"), + + ("80808080808080808080808080808080", + "letter advice cage absurd amount doctor acoustic avoid letter advice cage above", + "d71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f12eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8"), + + ("ffffffffffffffffffffffffffffffff", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", + "ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a13332572917f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069"), + + ("0000000000000000000000000000000000000000000000000000000000000000", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art", + "bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8"), + + ("18ab19a9f54a9274f03e5209a2ac8a91", + "board flee heavy tunnel powder denial science ski answer betray cargo cat", + "6eff1bb21562918509c73cb990260db07c0ce34ff0e3cc4a8cb3276129fbcb300bddfe005831350efd633909f476c45c88253276d9fd0df6ef48609e8bb7dca8"), + + ("15da872c95a13dd738fbf50e427583ad61f18fd99f628c417a61cf8343c90419", + "beyond stage sleep clip because twist token leaf atom beauty genius food business side grid unable middle armed observe pair crouch tonight away coconut", + "b15509eaa2d09d3efd3e006ef42151b30367dc6e3aa5e44caba3fe4d3e352e65101fbdb86a96776b91946ff06f8eac594dc6ee1d3e82a42dfe1b40fef6bcc3fd"), + ]; + + for (entropy_hex, expected_mnemonic, expected_seed) in test_vectors.iter() { + let entropy = hex::decode(entropy_hex).unwrap(); + let entropy_data = TWDataHelper::create(entropy); + + let mnemonic_ptr = unsafe { tw_mnemonic_generate_from_data(entropy_data.ptr()) }; + let mnemonic_data = unsafe { TWString::from_ptr_as_ref(mnemonic_ptr).unwrap() }; + let mnemonic_string = mnemonic_data.as_str().unwrap(); + + assert_eq!(mnemonic_string, *expected_mnemonic); + + let is_valid = unsafe { tw_mnemonic_is_valid(mnemonic_ptr) }; + assert!(is_valid); + + let passphrase_string = TWStringHelper::create("TREZOR"); + + let seed_ptr = unsafe { tw_mnemonic_to_seed(mnemonic_ptr, passphrase_string.ptr()) }; + let seed_data = unsafe { TWData::from_ptr_as_ref(seed_ptr).unwrap() }; + assert_eq!(hex::encode(seed_data.to_vec(), false), *expected_seed); + } +} + +#[test] +fn test_bip39_check_mnemonic() { + let vectors_ok = [ + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "legal winner thank year wave sausage worth useful legal winner thank yellow", + "letter advice cage absurd amount doctor acoustic avoid letter advice cage above", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent", + "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will", + "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art", + "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title", + "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote", + "jelly better achieve collect unaware mountain thought cargo oxygen act hood bridge", + "renew stay biology evidence goat welcome casual join adapt armor shuffle fault little machine walk stumble urge swap", + "dignity pass list indicate nasty swamp pool script soccer toe leaf photo multiply desk host tomato cradle drill spread actor shine dismiss champion exotic", + "afford alter spike radar gate glance object seek swamp infant panel yellow", + "indicate race push merry suffer human cruise dwarf pole review arch keep canvas theme poem divorce alter left", + "clutch control vehicle tonight unusual clog visa ice plunge glimpse recipe series open hour vintage deposit universe tip job dress radar refuse motion taste", + "turtle front uncle idea crush write shrug there lottery flower risk shell", + "kiss carry display unusual confirm curtain upgrade antique rotate hello void custom frequent obey nut hole price segment", + "exile ask congress lamp submit jacket era scheme attend cousin alcohol catch course end lucky hurt sentence oven short ball bird grab wing top", + "board flee heavy tunnel powder denial science ski answer betray cargo cat", + "board blade invite damage undo sun mimic interest slam gaze truly inherit resist great inject rocket museum chief", + "beyond stage sleep clip because twist token leaf atom beauty genius food business side grid unable middle armed observe pair crouch tonight away coconut", + ]; + + let vectors_fail = [ + "above abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "above winner thank year wave sausage worth useful legal winner thank yellow", + "above advice cage absurd amount doctor acoustic avoid letter advice cage above", + "above zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", + "above abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent", + "above winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will", + "above advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always", + "above zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when", + "above abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art", + "above winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title", + "above advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless", + "above zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote", + "above better achieve collect unaware mountain thought cargo oxygen act hood bridge", + "above stay biology evidence goat welcome casual join adapt armor shuffle fault little machine walk stumble urge swap", + "above pass list indicate nasty swamp pool script soccer toe leaf photo multiply desk host tomato cradle drill spread actor shine dismiss champion exotic", + "above alter spike radar gate glance object seek swamp infant panel yellow", + "above race push merry suffer human cruise dwarf pole review arch keep canvas theme poem divorce alter left", + "above control vehicle tonight unusual clog visa ice plunge glimpse recipe series open hour vintage deposit universe tip job dress radar refuse motion taste", + "above front uncle idea crush write shrug there lottery flower risk shell", + "above carry display unusual confirm curtain upgrade antique rotate hello void custom frequent obey nut hole price segment", + "above ask congress lamp submit jacket era scheme attend cousin alcohol catch course end lucky hurt sentence oven short ball bird grab wing top", + "above flee heavy tunnel powder denial science ski answer betray cargo cat", + "above blade invite damage undo sun mimic interest slam gaze truly inherit resist great inject rocket museum chief", + "above stage sleep clip because twist token leaf atom beauty genius food business side grid unable middle armed observe pair crouch tonight away coconut", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "winner thank year wave sausage worth useful legal winner thank yellow", + "advice cage absurd amount doctor acoustic avoid letter advice cage above", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent", + "winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will", + "advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art", + "winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title", + "advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote", + "better achieve collect unaware mountain thought cargo oxygen act hood bridge", + "stay biology evidence goat welcome casual join adapt armor shuffle fault little machine walk stumble urge swap", + "pass list indicate nasty swamp pool script soccer toe leaf photo multiply desk host tomato cradle drill spread actor shine dismiss champion exotic", + "alter spike radar gate glance object seek swamp infant panel yellow", + "race push merry suffer human cruise dwarf pole review arch keep canvas theme poem divorce alter left", + "control vehicle tonight unusual clog visa ice plunge glimpse recipe series open hour vintage deposit universe tip job dress radar refuse motion taste", + "front uncle idea crush write shrug there lottery flower risk shell", + "carry display unusual confirm curtain upgrade antique rotate hello void custom frequent obey nut hole price segment", + "ask congress lamp submit jacket era scheme attend cousin alcohol catch course end lucky hurt sentence oven short ball bird grab wing top", + "flee heavy tunnel powder denial science ski answer betray cargo cat", + "blade invite damage undo sun mimic interest slam gaze truly inherit resist great inject rocket museum chief", + "stage sleep clip because twist token leaf atom beauty genius food business side grid unable middle armed observe pair crouch tonight away coconut", + ]; + + for mnemonic in vectors_ok { + let mnemonic_string = TWStringHelper::create(mnemonic); + let is_valid = unsafe { tw_mnemonic_is_valid(mnemonic_string.ptr()) }; + assert!(is_valid); + } + + for mnemonic in vectors_fail { + let mnemonic_string = TWStringHelper::create(mnemonic); + let is_valid = unsafe { tw_mnemonic_is_valid(mnemonic_string.ptr()) }; + assert!(!is_valid); + } +} + +#[test] +fn test_bip39_find_word() { + let word_string = TWStringHelper::create("aaaa"); + let index = unsafe { tw_mnemonic_find_word(word_string.ptr()) }; + assert_eq!(index, -1); + + let word_string = TWStringHelper::create("zzzz"); + let index = unsafe { tw_mnemonic_find_word(word_string.ptr()) }; + assert_eq!(index, -1); + + for i in 0..2048 { + let word = unsafe { tw_mnemonic_get_word(i) }; + let word_string = unsafe { TWString::from_ptr_as_ref(word).unwrap() }; + let index = unsafe { tw_mnemonic_find_word(word_string) }; + assert_eq!(index, i as i32); + } +} + +#[test] +fn test_mnemonic_is_valid() { + let valid_inputs = [ + "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal", + // 12 + "credit expect life fade cover suit response wash pear what skull force", + // 15 + "rebuild park fatigue flame one clap grocery scheme upon symbol rifle flush brave feed clutch", + // 18 + "find view amazing inject mistake school zone ticket deposit edit deer fuel expect pioneer alpha mirror joke private", + // 21 + "tiger parent future endorse chuckle crazy seat tomato orient prevent swarm nerve duty crazy chief cruel purity team happy strategy level", + // 24 + "admit smart swim bulk empty mystery state lyrics wrap welcome install seat supreme sunny sting roof once accuse envelope uncover arrive twice spoon squeeze", + ]; + + for mnemonic in valid_inputs { + let mnemonic_string = TWStringHelper::create(mnemonic); + let is_valid = unsafe { tw_mnemonic_is_valid(mnemonic_string.ptr()) }; + assert!(is_valid, "Expected valid: {}", mnemonic); + } + + let invalid_inputs = [ + // invalid word + "ripple scissors hisc mammal hire column oak again sun offer wealth tomorrow", + // invalid word + "high culture ostrich wrist exist ignore interest hybridous exclude width more", + // invalid checksum + "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow", + // invalid word count + "credit expect life fade cover suit response wash what skull force", + // extra space + " credit expect life fade cover suit response wash pear what skull force ", + // upper + "CREDIT expect life fade cover suit response wash pear what skull force", + // back is invalid word + "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn back", + // Spanish + "llanto radical atraer riesgo actuar masa fondo cielo dieta archivo sonrisa mamut", + ]; + + for mnemonic in invalid_inputs { + let mnemonic_string = TWStringHelper::create(mnemonic); + let is_valid = unsafe { tw_mnemonic_is_valid(mnemonic_string.ptr()) }; + assert!(!is_valid, "Expected invalid: {}", mnemonic); + } +} + +#[test] +fn test_mnemonic_is_valid_word() { + assert!(unsafe { tw_mnemonic_is_valid_word(TWStringHelper::create("credit").ptr()) }); + assert!(unsafe { tw_mnemonic_is_valid_word(TWStringHelper::create("airport").ptr()) }); + assert!(unsafe { tw_mnemonic_is_valid_word(TWStringHelper::create("robot").ptr()) }); + + assert!(!unsafe { tw_mnemonic_is_valid_word(TWStringHelper::create("hybridous").ptr()) }); + assert!(!unsafe { tw_mnemonic_is_valid_word(TWStringHelper::create("CREDIT").ptr()) }); + assert!(!unsafe { tw_mnemonic_is_valid_word(TWStringHelper::create("credit ").ptr()) }); + assert!(!unsafe { tw_mnemonic_is_valid_word(TWStringHelper::create("back").ptr()) }); +} + +#[test] +fn test_mnemonic_suggest() { + let test_cases = [ + ("air", "air airport"), + ("AIR", "air airport"), + ("abc", ""), + ("ai", "aim air airport aisle"), + ( + "an", + "analyst anchor ancient anger angle angry animal ankle announce annual", + ), + ( + "a", + "abandon ability able about above absent absorb abstract absurd abuse", + ), + ("str", "strategy street strike strong struggle"), + ("rob", "robot robust"), + ("saus", "sausage"), + ("saos", ""), + ("", ""), + ("3", ""), + (" a", ""), + (" ", ""), + ( + "f", + "fabric face faculty fade faint faith fall false fame family", + ), + ( + "fa", + "fabric face faculty fade faint faith fall false fame family", + ), + ("fam", "fame family famous"), + ("fami", "family"), + ("famil", "family"), + ("family", "family"), + ( + "p", + "pact paddle page pair palace palm panda panel panic panther", + ), + ( + "pr", + "practice praise predict prefer prepare present pretty prevent price pride", + ), + ( + "pro", + "problem process produce profit program project promote proof property prosper", + ), + ("prog", "program"), + ("progr", "program"), + ("progra", "program"), + ("program", "program"), + ]; + + for (prefix, expected) in test_cases { + let prefix_string = TWStringHelper::create(prefix); + let result = unsafe { tw_mnemonic_suggest(prefix_string.ptr()) }; + let result_string = unsafe { TWString::from_ptr_as_ref(result).unwrap() }; + let result_string = result_string.as_str().unwrap(); + assert_eq!(result_string, expected); + } +} + +#[test] +fn test_spanish_mnemonic() { + let mnemonic = + "llanto radical atraer riesgo actuar masa fondo cielo dieta archivo sonrisa mamut"; + let mnemonic_string = TWStringHelper::create(mnemonic); + + let entropy = unsafe { tw_mnemonic_to_entropy(mnemonic_string.ptr()) }; + let entropy_data = unsafe { TWData::from_ptr_as_ref(entropy).unwrap() }; + assert_eq!(hex::encode(entropy_data.to_vec(), false), ""); + + let seed = + unsafe { tw_mnemonic_to_seed(mnemonic_string.ptr(), TWStringHelper::create("").ptr()) }; + let seed_data = unsafe { TWData::from_ptr_as_ref(seed).unwrap() }; + assert_eq!(hex::encode(seed_data.to_vec(), false), "ec8f8703432fc7d32e699ee056e9d84b1435e6a64a6a40ad63dbde11eab189a276ddcec20f3326d3c6ee39cbd018585b104fc3633b801c011063ae4c318fb9b6"); +} diff --git a/samples/go/core/mnemonic.go b/samples/go/core/mnemonic.go index a1968b864b9..7b020aecc47 100644 --- a/samples/go/core/mnemonic.go +++ b/samples/go/core/mnemonic.go @@ -2,7 +2,7 @@ package core // #cgo CFLAGS: -I../../../include // #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -L../../../build/trezor-crypto -lTrustWalletCore -lwallet_core_rs -lprotobuf -lTrezorCrypto -lstdc++ -lm -// #include +// #include import "C" import "tw/types" diff --git a/samples/go/dev-console/native/twmnemonic.go b/samples/go/dev-console/native/twmnemonic.go index f159a1a486a..3d20c1b003f 100644 --- a/samples/go/dev-console/native/twmnemonic.go +++ b/samples/go/dev-console/native/twmnemonic.go @@ -1,6 +1,6 @@ package native -// #include +// #include import "C" func IsMnemonicValid(mn string) bool { diff --git a/src/HDWallet.cpp b/src/HDWallet.cpp index 4b50eb42726..9de2cb9e03b 100644 --- a/src/HDWallet.cpp +++ b/src/HDWallet.cpp @@ -19,7 +19,6 @@ #include #include -#include #include #include @@ -36,8 +35,6 @@ bool deserialize(const std::string& extended, TWCurve curve, Hash::Hasher hasher const char* curveName(TWCurve curve); } // namespace -const int MnemonicBufLength = Mnemonic::MaxWords * (BIP39_MAX_WORD_LENGTH + 3) + 20; // some extra slack - template HDWallet::HDWallet(const Data& seed) { std::copy_n(seed.begin(), seedSize, this->seed.begin()); @@ -47,28 +44,20 @@ template void HDWallet::updateSeedAndEntropy([[maybe_unused]] bool check) { assert(!check || Mnemonic::isValid(mnemonic)); // precondition - // generate seed from mnemonic - mnemonic_to_seed(mnemonic.c_str(), passphrase.c_str(), seed.data(), nullptr); - - // generate entropy bits from mnemonic - Data entropyRaw((Mnemonic::MaxWords * Mnemonic::BitsPerWord) / 8); - // entropy is truncated to fully bytes, 4 bytes for each 3 words (=33 bits) - auto entropyBytes = mnemonic_to_bits(mnemonic.c_str(), entropyRaw.data()) / 33 * 4; - // copy to truncate - entropy = data(entropyRaw.data(), entropyBytes); + auto seedData = Mnemonic::toSeed(mnemonic, passphrase); + std::copy_n(seedData.begin(), seedSize, seed.begin()); + entropy = Mnemonic::toEntropy(mnemonic); + assert(!check || entropy.size() > 10); } template HDWallet::HDWallet(int strength, const std::string& passphrase) : passphrase(passphrase) { - char buf[MnemonicBufLength]; - const char* mnemonic_chars = mnemonic_generate(strength, buf, MnemonicBufLength); - if (mnemonic_chars == nullptr) { + mnemonic = Mnemonic::generate(strength); + if (mnemonic.empty()) { throw std::invalid_argument("Invalid strength"); } - mnemonic = mnemonic_chars; - TW::memzero(buf, MnemonicBufLength); updateSeedAndEntropy(); } @@ -85,13 +74,10 @@ HDWallet::HDWallet(const std::string& mnemonic, const std::string& pas template HDWallet::HDWallet(const Data& entropy, const std::string& passphrase) : passphrase(passphrase) { - char buf[MnemonicBufLength]; - const char* mnemonic_chars = mnemonic_from_data(entropy.data(), static_cast(entropy.size()), buf, MnemonicBufLength); - if (mnemonic_chars == nullptr) { + mnemonic = Mnemonic::generateFromData(entropy); + if (mnemonic.empty()) { throw std::invalid_argument("Invalid mnemonic data"); } - mnemonic = mnemonic_chars; - TW::memzero(buf, MnemonicBufLength); updateSeedAndEntropy(); } diff --git a/src/Mnemonic.cpp b/src/Mnemonic.cpp index a6e9a2227cd..a4fef987981 100644 --- a/src/Mnemonic.cpp +++ b/src/Mnemonic.cpp @@ -4,8 +4,7 @@ #include "Mnemonic.h" -#include -#include +#include "rust/Wrapper.h" #include #include @@ -17,60 +16,49 @@ namespace TW { const int Mnemonic::SuggestMaxCount = 10; +std::string Mnemonic::generate(uint32_t strength) { + const Rust::TWStringWrapper result = Rust::tw_mnemonic_generate(strength); + return result.toStringOrDefault(); +} + +std::string Mnemonic::generateFromData(const Data& data) { + const Rust::TWDataWrapper dataRustData = data; + const Rust::TWStringWrapper result = Rust::tw_mnemonic_generate_from_data(dataRustData.get()); + return result.toStringOrDefault(); +} + bool Mnemonic::isValid(const std::string& mnemonic) { - return mnemonic_check(mnemonic.c_str()) != 0; + const Rust::TWStringWrapper mnemonicRustStr = mnemonic; + return Rust::tw_mnemonic_is_valid(mnemonicRustStr.get()); } -inline const char* const* mnemonicWordlist() { return wordlist; } +std::string Mnemonic::getWord(uint32_t index) { + const Rust::TWStringWrapper result = Rust::tw_mnemonic_get_word(index); + return result.toStringOrDefault(); +} bool Mnemonic::isValidWord(const std::string& word) { - const char* wordC = word.c_str(); - const auto len = word.length(); - // Although this operation is not security-critical, we aim for constant-time operation here as well - // (i.e., no early exit on match) - auto found = false; - for (const char* const* w = mnemonicWordlist(); *w != nullptr; ++w) { - if (std::string(*w).size() == len && strncmp(*w, wordC, len) == 0) { - found = true; - } - } - return found; + const Rust::TWStringWrapper wordRustStr = word; + return Rust::tw_mnemonic_is_valid_word(wordRustStr.get()); } std::string Mnemonic::suggest(const std::string& prefix) { - if (prefix.size() == 0) { - return ""; - } - assert(prefix.size() >= 1); - // lowercase prefix - std::string prefixLo = prefix; - std::transform(prefixLo.begin(), prefixLo.end(), prefixLo.begin(), - [](unsigned char c){ return std::tolower(c); }); - const char* prefixLoC = prefixLo.c_str(); + const Rust::TWStringWrapper prefixRustStr = prefix; + const Rust::TWStringWrapper result = Rust::tw_mnemonic_suggest(prefixRustStr.get()); + return result.toStringOrDefault(); +} - std::vector result; - for (const char* const* word = mnemonicWordlist(); *word != nullptr; ++word) { - // check first letter match (optimization) - if ((*word)[0] == prefixLo[0]) { - if (strncmp(*word, prefixLoC, prefixLo.length()) == 0) { - // we have a match - result.emplace_back(*word); - if (result.size() >= SuggestMaxCount) { - break; // enough results - } - } - } - } +Data Mnemonic::toSeed(const std::string& mnemonic, const std::string& passphrase) { + const Rust::TWStringWrapper mnemonicRustStr = mnemonic; + const Rust::TWStringWrapper passphraseRustStr = passphrase; + const Rust::TWDataWrapper result = Rust::tw_mnemonic_to_seed(mnemonicRustStr.get(), passphraseRustStr.get()); + return result.toDataOrDefault(); +} - // convert results to one string - std::string resultString; - for (auto& word: result) { - if (resultString.length() > 0) { - resultString += " "; - } - resultString += word; - } - return resultString; +Data Mnemonic::toEntropy(const std::string& mnemonic) { + const Rust::TWStringWrapper mnemonicRustStr = mnemonic; + const Rust::TWDataWrapper result = Rust::tw_mnemonic_to_entropy(mnemonicRustStr.get()); + return result.toDataOrDefault(); } } // namespace TW diff --git a/src/Mnemonic.h b/src/Mnemonic.h index dc6fc4ff2d6..3efb39ad187 100644 --- a/src/Mnemonic.h +++ b/src/Mnemonic.h @@ -4,6 +4,7 @@ #pragma once +#include "Data.h" #include namespace TW { @@ -16,6 +17,10 @@ class Mnemonic { static constexpr int BitsPerWord = 11; // each word encodes this many bits (there are 2^11=2048 different words) public: + static std::string generate(uint32_t strength); + + static std::string generateFromData(const Data& data); + /// Determines whether a BIP39 English mnemonic phrase is valid. // E.g. for a valid mnemonic: "credit expect life fade cover suit response wash pear what skull force" static bool isValid(const std::string& mnemonic); @@ -23,6 +28,8 @@ class Mnemonic { /// Determines whether word is a valid BIP39 English menemonic word. static bool isValidWord(const std::string& word); + static std::string getWord(uint32_t index); + /// Return BIP39 English words that match the given prefix. // - A single string is returned, with space-separated list of words (or single word or empty string) // (Why not array? To simplify the cross-language interfaces) @@ -38,6 +45,10 @@ class Mnemonic { // - 'a'-> 'abandon ability able about above absent absorb abstract absurd abuse' static std::string suggest(const std::string& prefix); + static Data toSeed(const std::string& mnemonic, const std::string& passphrase); + + static Data toEntropy(const std::string& mnemonic); + static const int SuggestMaxCount; }; diff --git a/src/interface/TWMnemonic.cpp b/src/interface/TWMnemonic.cpp deleted file mode 100644 index e5694b3c9a4..00000000000 --- a/src/interface/TWMnemonic.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#include - -#include "../Mnemonic.h" - -using namespace TW; - -bool TWMnemonicIsValid(TWString *_Nonnull mnemonic) { - return Mnemonic::isValid(TWStringUTF8Bytes(mnemonic)); -} - -bool TWMnemonicIsValidWord(TWString *_Nonnull word) { - return Mnemonic::isValidWord(TWStringUTF8Bytes(word)); -} - -TWString* _Nonnull TWMnemonicSuggest(TWString *_Nonnull prefix) { - auto result = Mnemonic::suggest(std::string(TWStringUTF8Bytes(prefix))); - return TWStringCreateWithUTF8Bytes(result.c_str()); -} diff --git a/tests/interface/TWHDWalletTests.cpp b/tests/interface/TWHDWalletTests.cpp index 4f03674358e..970ca669c7a 100644 --- a/tests/interface/TWHDWalletTests.cpp +++ b/tests/interface/TWHDWalletTests.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/tests/interface/TWMnemonicTests.cpp b/tests/interface/TWMnemonicTests.cpp index 2fd55e74b85..9a9b1cb7640 100644 --- a/tests/interface/TWMnemonicTests.cpp +++ b/tests/interface/TWMnemonicTests.cpp @@ -4,7 +4,7 @@ #include "TestUtilities.h" -#include +#include TEST(TWMnemonic, IsValid) { EXPECT_TRUE(TWMnemonicIsValid(STRING("credit expect life fade cover suit response wash pear what skull force").get())); From 54821490ece5c50fdb529e3960c24f354f04783a Mon Sep 17 00:00:00 2001 From: gupnik Date: Mon, 19 May 2025 20:33:02 +0530 Subject: [PATCH 22/23] Migrates Key derivation to Rust (#4395) * Adds derivation for various curves * Adds FFI integration * Adds ability to provide hashers in FFI * Trigger Build * FMT * Fix clippy * Fixes ffi issues * FMT * Minor fix * Minor * Fixes sonar cube issues * More sonarqube fixes * Fix memory leaks * Fixes another memory leak * Minor * Minor * Addresses review comments and adds tests * FMT * Makes clippy happy * Addresses review comments * Minor * Uses zeroize for tweak * Addresses review comment * Clippy fix --- codegen-v2/src/codegen/cpp/code_gen.rs | 2 +- rust/Cargo.lock | 54 +- rust/tw_crypto/Cargo.toml | 7 + .../tw_crypto/src/crypto_hd_node/ecdsa/mod.rs | 8 + .../src/crypto_hd_node/ecdsa/nist256p1/mod.rs | 45 + .../src/crypto_hd_node/ecdsa/secp256k1/mod.rs | 45 + .../src/crypto_hd_node/ed25519/cardano/mod.rs | 86 ++ .../src/crypto_hd_node/ed25519/mod.rs | 84 ++ .../src/crypto_hd_node/ed25519/waves/mod.rs | 47 + rust/tw_crypto/src/crypto_hd_node/error.rs | 84 ++ .../extended_key/bip32_private_key.rs | 70 ++ .../extended_key/bip32_public_key.rs | 61 ++ .../extended_key/extended_private_key.rs | 304 ++++++ .../extended_key/extended_public_key.rs | 151 +++ .../crypto_hd_node/extended_key/hd_version.rs | 67 ++ .../src/crypto_hd_node/extended_key/mod.rs | 11 + rust/tw_crypto/src/crypto_hd_node/hd_node.rs | 234 +++++ .../src/crypto_hd_node/hd_node_public.rs | 122 +++ rust/tw_crypto/src/crypto_hd_node/mod.rs | 13 + .../src/crypto_hd_node/zilliqa_schnorr/mod.rs | 45 + rust/tw_crypto/src/ffi/crypto_hd_node.rs | 219 ++++ .../src/ffi/crypto_hd_node_public.rs | 102 ++ rust/tw_crypto/src/ffi/mod.rs | 2 + rust/tw_crypto/src/lib.rs | 1 + rust/tw_crypto/tests/bip39_vectors.json | 148 +++ rust/tw_crypto/tests/extended_private_key.rs | 768 ++++++++++++++ rust/tw_crypto/tests/hd_node_ffi.rs | 935 ++++++++++++++++++ rust/tw_crypto/tests/hd_node_public_ffi.rs | 155 +++ rust/tw_hash/Cargo.toml | 1 + rust/tw_hash/src/hasher.rs | 37 +- rust/tw_hash/src/hmac.rs | 4 +- .../tw_keypair/src/ecdsa/nist256p1/private.rs | 31 +- rust/tw_keypair/src/ecdsa/nist256p1/public.rs | 19 +- .../tw_keypair/src/ecdsa/secp256k1/private.rs | 30 +- rust/tw_keypair/src/ecdsa/secp256k1/public.rs | 19 +- .../modifications/cardano/extended_private.rs | 54 +- .../modifications/cardano/extended_public.rs | 34 +- .../ed25519/modifications/waves/private.rs | 11 + rust/tw_keypair/src/ed25519/private.rs | 9 + rust/tw_keypair/src/traits.rs | 5 + .../tw_keypair/src/zilliqa_schnorr/private.rs | 28 + rust/tw_keypair/src/zilliqa_schnorr/public.rs | 22 +- src/HDWallet.cpp | 330 +++---- src/HDWallet.h | 3 - tests/chains/Cardano/AddressTests.cpp | 6 +- 45 files changed, 4242 insertions(+), 271 deletions(-) create mode 100644 rust/tw_crypto/src/crypto_hd_node/ecdsa/mod.rs create mode 100644 rust/tw_crypto/src/crypto_hd_node/ecdsa/nist256p1/mod.rs create mode 100644 rust/tw_crypto/src/crypto_hd_node/ecdsa/secp256k1/mod.rs create mode 100644 rust/tw_crypto/src/crypto_hd_node/ed25519/cardano/mod.rs create mode 100644 rust/tw_crypto/src/crypto_hd_node/ed25519/mod.rs create mode 100644 rust/tw_crypto/src/crypto_hd_node/ed25519/waves/mod.rs create mode 100644 rust/tw_crypto/src/crypto_hd_node/error.rs create mode 100644 rust/tw_crypto/src/crypto_hd_node/extended_key/bip32_private_key.rs create mode 100644 rust/tw_crypto/src/crypto_hd_node/extended_key/bip32_public_key.rs create mode 100644 rust/tw_crypto/src/crypto_hd_node/extended_key/extended_private_key.rs create mode 100644 rust/tw_crypto/src/crypto_hd_node/extended_key/extended_public_key.rs create mode 100644 rust/tw_crypto/src/crypto_hd_node/extended_key/hd_version.rs create mode 100644 rust/tw_crypto/src/crypto_hd_node/extended_key/mod.rs create mode 100644 rust/tw_crypto/src/crypto_hd_node/hd_node.rs create mode 100644 rust/tw_crypto/src/crypto_hd_node/hd_node_public.rs create mode 100644 rust/tw_crypto/src/crypto_hd_node/mod.rs create mode 100644 rust/tw_crypto/src/crypto_hd_node/zilliqa_schnorr/mod.rs create mode 100644 rust/tw_crypto/src/ffi/crypto_hd_node.rs create mode 100644 rust/tw_crypto/src/ffi/crypto_hd_node_public.rs create mode 100644 rust/tw_crypto/tests/bip39_vectors.json create mode 100644 rust/tw_crypto/tests/extended_private_key.rs create mode 100644 rust/tw_crypto/tests/hd_node_ffi.rs create mode 100644 rust/tw_crypto/tests/hd_node_public_ffi.rs diff --git a/codegen-v2/src/codegen/cpp/code_gen.rs b/codegen-v2/src/codegen/cpp/code_gen.rs index 569d018e955..fae1f93d5aa 100644 --- a/codegen-v2/src/codegen/cpp/code_gen.rs +++ b/codegen-v2/src/codegen/cpp/code_gen.rs @@ -330,7 +330,7 @@ fn generate_return_type(func: &TWFunction, converted_args: &Vec) -> Resu let wrapper_class_name = class_name.replace("TW", ""); let null_return = match pointer_type { TWPointerType::Nullable | TWPointerType::NullableMut => { - "if (!resultRaw) {{ return nullptr; }}\n" + "\tif (!resultRaw) {{ return nullptr; }}\n" } _ => "", }; diff --git a/rust/Cargo.lock b/rust/Cargo.lock index bb957cce985..81f10be8aa5 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -198,6 +198,25 @@ dependencies = [ "serde", ] +[[package]] +name = "bip32" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db40d3dfbeab4e031d78c844642fa0caa0b0db11ce1607ac9d2986dff1405c69" +dependencies = [ + "bs58 0.5.1", + "hmac", + "k256", + "once_cell", + "pbkdf2", + "rand_core", + "ripemd", + "secp256k1", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "bip39" version = "2.1.0" @@ -357,6 +376,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "sha2", + "tinyvec", +] + [[package]] name = "bumpalo" version = "3.12.0" @@ -549,6 +578,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cryptoxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382ce8820a5bb815055d3553a610e8cb542b2d767bbacea99038afda96cd760d" + [[package]] name = "ctr" version = "0.9.2" @@ -674,6 +709,15 @@ dependencies = [ "signature", ] +[[package]] +name = "ed25519-bip32" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb588f93c0d91b2f668849fd6d030cddb0b2e31f105963be189da5acdf492a21" +dependencies = [ + "cryptoxide", +] + [[package]] name = "either" version = "1.8.1" @@ -2017,13 +2061,20 @@ name = "tw_crypto" version = "0.1.0" dependencies = [ "aes", + "bip32", "bip39", + "bs58 0.5.1", "cbc", "ctr", + "ed25519-bip32", "pbkdf2", "salsa20", + "serde_json", "sha2", + "strum_macros", "tw_encoding", + "tw_hash", + "tw_keypair", "tw_macros", "tw_memory", "tw_misc", @@ -2052,7 +2103,7 @@ dependencies = [ "arbitrary 1.3.0", "bcs", "bech32", - "bs58", + "bs58 0.4.0", "ciborium", "data-encoding", "hex", @@ -2141,6 +2192,7 @@ dependencies = [ "sha1", "sha2", "sha3", + "strum_macros", "tw_encoding", "tw_memory", "zeroize", diff --git a/rust/tw_crypto/Cargo.toml b/rust/tw_crypto/Cargo.toml index 4db96645097..5540a3bf822 100644 --- a/rust/tw_crypto/Cargo.toml +++ b/rust/tw_crypto/Cargo.toml @@ -6,13 +6,19 @@ edition = "2021" [dependencies] aes = "0.8.4" +bip32 = "0.5.3" bip39 = { version = "2.1.0", features = ["rand" ] } +bs58 = { version = "0.5.0" } cbc = "0.1.2" ctr = "0.9.2" +ed25519-bip32 = "0.4.1" pbkdf2 = "0.12.2" salsa20 = "0.10.2" sha2 = "0.10.8" +strum_macros = "0.25" tw_encoding = { path = "../tw_encoding" } +tw_hash = { path = "../tw_hash" } +tw_keypair = { path = "../tw_keypair" } tw_macros = { path = "../tw_macros" } tw_memory = { path = "../tw_memory" } tw_misc = { path = "../tw_misc" } @@ -20,3 +26,4 @@ zeroize = "1.8.1" [dev-dependencies] tw_memory = { path = "../tw_memory", features = ["test-utils"] } +serde_json = "1.0" diff --git a/rust/tw_crypto/src/crypto_hd_node/ecdsa/mod.rs b/rust/tw_crypto/src/crypto_hd_node/ecdsa/mod.rs new file mode 100644 index 00000000000..91c3f231868 --- /dev/null +++ b/rust/tw_crypto/src/crypto_hd_node/ecdsa/mod.rs @@ -0,0 +1,8 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod nist256p1; +pub mod secp256k1; diff --git a/rust/tw_crypto/src/crypto_hd_node/ecdsa/nist256p1/mod.rs b/rust/tw_crypto/src/crypto_hd_node/ecdsa/nist256p1/mod.rs new file mode 100644 index 00000000000..85055eaa0e5 --- /dev/null +++ b/rust/tw_crypto/src/crypto_hd_node/ecdsa/nist256p1/mod.rs @@ -0,0 +1,45 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use bip32::ChildNumber; +use tw_hash::H256; +use tw_keypair::traits::DerivableKeyTrait; +use tw_keypair::{ecdsa::nist256p1, tw::Curve}; + +use crate::crypto_hd_node::error::{Error, Result}; +use crate::crypto_hd_node::extended_key::{ + bip32_private_key::BIP32PrivateKey, bip32_public_key::BIP32PublicKey, +}; + +impl BIP32PrivateKey for nist256p1::PrivateKey { + type BIP32PublicKey = nist256p1::PublicKey; + + fn derive_child(&self, other: &[u8], _child_number: ChildNumber) -> Result { + let other = H256::try_from(other).map_err(|_| Error::InvalidKeyData)?; + ::derive_child(self, other) + .map_err(|_| Error::DerivationFailed) + } + + fn curve() -> Curve { + Curve::Nist256p1 + } + + fn bip32_name() -> &'static str { + "Nist256p1 seed" + } + + fn public_key(&self) -> Self::BIP32PublicKey { + self.public() + } +} + +impl BIP32PublicKey for nist256p1::PublicKey { + fn derive_child(&self, other: &[u8], _child_number: ChildNumber) -> Result { + let other = H256::try_from(other).map_err(|_| Error::InvalidKeyData)?; + ::derive_child(self, other) + .map_err(|_| Error::DerivationFailed) + } +} diff --git a/rust/tw_crypto/src/crypto_hd_node/ecdsa/secp256k1/mod.rs b/rust/tw_crypto/src/crypto_hd_node/ecdsa/secp256k1/mod.rs new file mode 100644 index 00000000000..cbec66022e5 --- /dev/null +++ b/rust/tw_crypto/src/crypto_hd_node/ecdsa/secp256k1/mod.rs @@ -0,0 +1,45 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use bip32::ChildNumber; +use tw_hash::H256; +use tw_keypair::traits::DerivableKeyTrait; +use tw_keypair::{ecdsa::secp256k1, tw::Curve}; + +use crate::crypto_hd_node::error::{Error, Result}; +use crate::crypto_hd_node::extended_key::{ + bip32_private_key::BIP32PrivateKey, bip32_public_key::BIP32PublicKey, +}; + +impl BIP32PrivateKey for secp256k1::PrivateKey { + type BIP32PublicKey = secp256k1::PublicKey; + + fn derive_child(&self, other: &[u8], _child_number: ChildNumber) -> Result { + let other = H256::try_from(other).map_err(|_| Error::InvalidKeyData)?; + ::derive_child(self, other) + .map_err(|_| Error::DerivationFailed) + } + + fn curve() -> Curve { + Curve::Secp256k1 + } + + fn bip32_name() -> &'static str { + "Bitcoin seed" + } + + fn public_key(&self) -> Self::BIP32PublicKey { + self.public() + } +} + +impl BIP32PublicKey for secp256k1::PublicKey { + fn derive_child(&self, other: &[u8], _child_number: ChildNumber) -> Result { + let other = H256::try_from(other).map_err(|_| Error::InvalidKeyData)?; + ::derive_child(self, other) + .map_err(|_| Error::DerivationFailed) + } +} diff --git a/rust/tw_crypto/src/crypto_hd_node/ed25519/cardano/mod.rs b/rust/tw_crypto/src/crypto_hd_node/ed25519/cardano/mod.rs new file mode 100644 index 00000000000..caadd306dd0 --- /dev/null +++ b/rust/tw_crypto/src/crypto_hd_node/ed25519/cardano/mod.rs @@ -0,0 +1,86 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::str::FromStr; + +use bip32::{ChildNumber, DerivationPath}; +use ed25519_bip32::{XPRV_SIZE, XPUB_SIZE}; +use tw_keypair::{ed25519, tw::Curve}; +use tw_misc::traits::{ToBytesVec, ToBytesZeroizing}; +use zeroize::Zeroizing; + +use crate::crypto_hd_node::error::{Error, Result}; +use crate::crypto_hd_node::extended_key::{ + bip32_private_key::BIP32PrivateKey, bip32_public_key::BIP32PublicKey, +}; + +impl BIP32PrivateKey for ed25519::cardano::ExtendedPrivateKey { + type BIP32PublicKey = ed25519::cardano::ExtendedPublicKey; + + fn derive_tweak( + &self, + chain_code: &bip32::ChainCode, + _child_number: bip32::ChildNumber, + ) -> Result<(Zeroizing>, bip32::ChainCode)> { + Ok((Zeroizing::new(vec![]), *chain_code)) + } + + fn derive_child(&self, _other: &[u8], child_number: ChildNumber) -> Result { + let bytes = self.to_zeroizing_vec(); + let bytes: [u8; XPRV_SIZE] = bytes.as_slice()[..XPRV_SIZE] + .try_into() + .expect("Should not fail"); + let bip32_xpr = + ed25519_bip32::XPrv::from_bytes_verified(bytes).map_err(|_| Error::InvalidKeyData)?; + let child: ed25519_bip32::XPrv = + bip32_xpr.derive(ed25519_bip32::DerivationScheme::V2, child_number.0); + Self::try_from(child.as_ref()).map_err(|_| Error::InvalidKeyData) + } + + fn curve() -> Curve { + Curve::Ed25519ExtendedCardano + } + + fn bip32_name() -> &'static str { + "ed25519 cardano seed" + } + + fn public_key(&self) -> Self::BIP32PublicKey { + self.public() + } +} + +impl BIP32PublicKey for ed25519::cardano::ExtendedPublicKey { + fn derive_child(&self, _other: &[u8], child_number: ChildNumber) -> Result { + let bytes = self.to_vec(); + let bytes: [u8; XPUB_SIZE] = bytes[..XPUB_SIZE].try_into().expect("Should not fail"); + let bip32_xpub = ed25519_bip32::XPub::from_bytes(bytes); + let child: ed25519_bip32::XPub = bip32_xpub + .derive(ed25519_bip32::DerivationScheme::V2, child_number.0) + .map_err(|_| Error::DerivationFailed)?; + Self::try_from(child.as_ref()).map_err(|_| Error::InvalidKeyData) + } +} + +pub fn cardano_staking_derivation_path(path: &DerivationPath) -> Result { + if path.len() < 4 { + return Err(Error::InvalidDerivationPath); + } + let mut staking_path = DerivationPath::from_str("m")?; + for (i, item) in path.iter().enumerate() { + if i == 3 { + if item.index() > 1 { + return Err(Error::InvalidDerivationPath); + } + staking_path.push(ChildNumber::new(2, false)?); + } else if i == 4 { + staking_path.push(ChildNumber::new(0, false)?); + } else { + staking_path.push(item); + } + } + Ok(staking_path) +} diff --git a/rust/tw_crypto/src/crypto_hd_node/ed25519/mod.rs b/rust/tw_crypto/src/crypto_hd_node/ed25519/mod.rs new file mode 100644 index 00000000000..f15e4e5391d --- /dev/null +++ b/rust/tw_crypto/src/crypto_hd_node/ed25519/mod.rs @@ -0,0 +1,84 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod cardano; +pub mod waves; + +use bip32::ChildNumber; +use tw_keypair::{ed25519, tw::Curve}; + +use crate::crypto_hd_node::error::{Error, Result}; +use crate::crypto_hd_node::extended_key::{ + bip32_private_key::BIP32PrivateKey, bip32_public_key::BIP32PublicKey, +}; + +impl BIP32PrivateKey for ed25519::sha512::PrivateKey { + type BIP32PublicKey = ed25519::sha512::PublicKey; + + fn derive_child(&self, other: &[u8], child_number: ChildNumber) -> Result { + if child_number.is_hardened() { + Self::try_from(other).map_err(|_| Error::InvalidKeyData) + } else { + Ok(self.clone()) + } + } + + fn curve() -> Curve { + Curve::Ed25519 + } + + fn bip32_name() -> &'static str { + "ed25519 seed" + } + + fn public_key(&self) -> Self::BIP32PublicKey { + self.public() + } +} + +impl BIP32PublicKey for ed25519::sha512::PublicKey { + fn derive_child(&self, other: &[u8], child_number: ChildNumber) -> Result { + if child_number.is_hardened() { + Self::try_from(other).map_err(|_| Error::InvalidKeyData) + } else { + Ok(self.clone()) + } + } +} + +impl BIP32PrivateKey for ed25519::blake2b::PrivateKey { + type BIP32PublicKey = ed25519::blake2b::PublicKey; + + fn derive_child(&self, other: &[u8], child_number: ChildNumber) -> Result { + if child_number.is_hardened() { + Self::try_from(other).map_err(|_| Error::InvalidKeyData) + } else { + Ok(self.clone()) + } + } + + fn curve() -> Curve { + Curve::Ed25519Blake2bNano + } + + fn bip32_name() -> &'static str { + "ed25519 seed" + } + + fn public_key(&self) -> Self::BIP32PublicKey { + self.public() + } +} + +impl BIP32PublicKey for ed25519::blake2b::PublicKey { + fn derive_child(&self, other: &[u8], child_number: ChildNumber) -> Result { + if child_number.is_hardened() { + Self::try_from(other).map_err(|_| Error::InvalidKeyData) + } else { + Ok(self.clone()) + } + } +} diff --git a/rust/tw_crypto/src/crypto_hd_node/ed25519/waves/mod.rs b/rust/tw_crypto/src/crypto_hd_node/ed25519/waves/mod.rs new file mode 100644 index 00000000000..b4602cccd91 --- /dev/null +++ b/rust/tw_crypto/src/crypto_hd_node/ed25519/waves/mod.rs @@ -0,0 +1,47 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use bip32::ChildNumber; +use tw_keypair::{ed25519, tw::Curve}; + +use crate::crypto_hd_node::error::{Error, Result}; +use crate::crypto_hd_node::extended_key::{ + bip32_private_key::BIP32PrivateKey, bip32_public_key::BIP32PublicKey, +}; + +impl BIP32PrivateKey for ed25519::waves::PrivateKey { + type BIP32PublicKey = ed25519::waves::PublicKey; + + fn derive_child(&self, other: &[u8], child_number: ChildNumber) -> Result { + if child_number.is_hardened() { + Self::try_from(other).map_err(|_| Error::InvalidKeyData) + } else { + Ok(self.clone()) + } + } + + fn curve() -> Curve { + Curve::Curve25519Waves + } + + fn bip32_name() -> &'static str { + "curve25519 seed" + } + + fn public_key(&self) -> Self::BIP32PublicKey { + self.public() + } +} + +impl BIP32PublicKey for ed25519::waves::PublicKey { + fn derive_child(&self, other: &[u8], child_number: ChildNumber) -> Result { + if child_number.is_hardened() { + Self::try_from(other).map_err(|_| Error::InvalidKeyData) + } else { + Ok(self.clone()) + } + } +} diff --git a/rust/tw_crypto/src/crypto_hd_node/error.rs b/rust/tw_crypto/src/crypto_hd_node/error.rs new file mode 100644 index 00000000000..1d35740410f --- /dev/null +++ b/rust/tw_crypto/src/crypto_hd_node/error.rs @@ -0,0 +1,84 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_keypair::KeyPairError; + +pub type Result = core::result::Result; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Error { + /// BIP32 error + BIP32(bip32::Error), + + /// Unsupported curve + UnsupportedCurve(u32), + + /// Invalid length + InvalidLength, + + /// Decode error + Decode, + + /// Invalid key data + InvalidKeyData, + + /// Invalid checksum + InvalidChecksum, + + /// Base58 error + Base58, + + /// Invalid depth + InvalidDepth, + + /// Invalid child number + InvalidChildNumber, + + /// Derivation failed + DerivationFailed, + + /// Invalid derivation path + InvalidDerivationPath, + + /// Invalid chain code + InvalidChainCode, +} + +impl From for Error { + fn from(error: bip32::Error) -> Error { + Error::BIP32(error) + } +} + +impl From for Error { + fn from(_: core::array::TryFromSliceError) -> Error { + Error::Decode + } +} + +impl From for Error { + fn from(_: tw_encoding::hex::FromHexError) -> Error { + Error::Decode + } +} + +impl From for Error { + fn from(_: bs58::decode::Error) -> Error { + Error::Base58 + } +} + +impl From for Error { + fn from(_: bs58::encode::Error) -> Error { + Error::Base58 + } +} + +impl From for Error { + fn from(_: KeyPairError) -> Error { + Error::InvalidKeyData + } +} diff --git a/rust/tw_crypto/src/crypto_hd_node/extended_key/bip32_private_key.rs b/rust/tw_crypto/src/crypto_hd_node/extended_key/bip32_private_key.rs new file mode 100644 index 00000000000..0c1e5f913d6 --- /dev/null +++ b/rust/tw_crypto/src/crypto_hd_node/extended_key/bip32_private_key.rs @@ -0,0 +1,70 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::str::FromStr; + +use bip32::{ChainCode, ChildNumber, KEY_SIZE}; +use sha2::digest::Mac; +use tw_hash::hmac::HmacSha512; +use tw_keypair::tw::Curve; +use tw_misc::traits::{FromSlice, ToBytesVec, ToBytesZeroizing}; +use zeroize::Zeroizing; + +use crate::crypto_hd_node::error::Result; +use crate::crypto_hd_node::extended_key::bip32_public_key::BIP32PublicKey; + +/// Trait for key types which can be derived using BIP32. +pub trait BIP32PrivateKey: Sized + Clone + ToBytesZeroizing + FromSlice + FromStr { + /// Public key type which corresponds to this private key. + type BIP32PublicKey: BIP32PublicKey; + + /// Derive a child key from a parent key and the a provided tweak value, + /// i.e. where `other` is referred to as "I sub L" in BIP32 and sourced + /// from the left half of the HMAC-SHA-512 output. + fn derive_child(&self, other: &[u8], child_number: ChildNumber) -> Result; + + /// Get the curve of the private key. + fn curve() -> Curve; + + /// Get the BIP32 name of the curve. + fn bip32_name() -> &'static str; + + /// Get the [`Self::PublicKey`] that corresponds to this private key. + fn public_key(&self) -> Self::BIP32PublicKey; + + /// Derive a tweak value that can be used to generate the child key (see [`derive_child`]). + /// + /// The `chain_code` is either a newly initialized one, + /// or one obtained from the previous invocation of `derive_tweak()` + /// (for a multi-level derivation). + /// + /// **Warning:** make sure that if you are creating a new `chain_code`, you are doing so + /// in a cryptographically safe way. + /// Normally this would be done according to BIP-39 (within [`ExtendedPrivateKey::new`]). + fn derive_tweak( + &self, + chain_code: &ChainCode, + child_number: ChildNumber, + ) -> Result<(Zeroizing>, ChainCode)> { + let mut hmac = HmacSha512::new_from_slice(chain_code).expect("Should not fail"); + + if child_number.is_hardened() { + hmac.update(&[0]); + hmac.update(&self.to_zeroizing_vec()); + } else { + hmac.update(&self.public_key().to_vec()); + } + + hmac.update(&child_number.to_bytes()); + + let result = hmac.finalize().into_bytes(); + let (tweak_bytes, chain_code_bytes) = result.split_at(KEY_SIZE); + + let chain_code = chain_code_bytes.try_into()?; + + Ok((Zeroizing::new(tweak_bytes.to_vec()), chain_code)) + } +} diff --git a/rust/tw_crypto/src/crypto_hd_node/extended_key/bip32_public_key.rs b/rust/tw_crypto/src/crypto_hd_node/extended_key/bip32_public_key.rs new file mode 100644 index 00000000000..7bf04731048 --- /dev/null +++ b/rust/tw_crypto/src/crypto_hd_node/extended_key/bip32_public_key.rs @@ -0,0 +1,61 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use bip32::{ChainCode, ChildNumber, KeyFingerprint, KEY_SIZE}; +use sha2::digest::Mac; +use tw_hash::hasher::{Hasher, StatefulHasher}; +use tw_hash::hmac::HmacSha512; +use tw_misc::traits::{FromSlice, ToBytesVec}; + +use crate::crypto_hd_node::error::{Error, Result}; + +/// Trait for key types which can be derived using BIP32. +pub trait BIP32PublicKey: Sized + Clone + ToBytesVec + FromSlice { + /// Compute a 4-byte key fingerprint for this public key. + /// + /// Default implementation uses `RIPEMD160(SHA256(public_key))`. + fn fingerprint(&self, hasher: Hasher) -> KeyFingerprint { + let digest = hasher.hash(self.to_vec().as_slice()); + digest[..4].try_into().expect("digest truncated") + } + + /// Derive a child key from a parent key and a provided tweak value. + fn derive_child(&self, other: &[u8], child_number: ChildNumber) -> Result; + + /// Derive a tweak value that can be used to generate the child key (see [`derive_child`]). + /// + /// The `chain_code` is either a newly initialized one, + /// or one obtained from the previous invocation of `derive_tweak()` + /// (for a multi-level derivation). + /// + /// **Warning:** make sure that if you are creating a new `chain_code`, you are doing so + /// in a cryptographically safe way. + /// Normally this would be done according to BIP-39 (within [`ExtendedPrivateKey::new`]). + /// + /// **Note:** `child_number` cannot be a hardened one (will result in an error). + fn derive_tweak( + &self, + chain_code: &ChainCode, + child_number: ChildNumber, + ) -> Result<(Vec, ChainCode)> { + if child_number.is_hardened() { + // Cannot derive child public keys for hardened `ChildNumber`s + return Err(Error::InvalidChildNumber); + } + + let mut hmac = HmacSha512::new_from_slice(chain_code).expect("Should not fail"); + + hmac.update(&self.to_vec()); + hmac.update(&child_number.to_bytes()); + + let result = hmac.finalize().into_bytes(); + let (tweak_bytes, chain_code_bytes) = result.split_at(KEY_SIZE); + + let chain_code = chain_code_bytes.try_into()?; + + Ok((tweak_bytes.to_vec(), chain_code)) + } +} diff --git a/rust/tw_crypto/src/crypto_hd_node/extended_key/extended_private_key.rs b/rust/tw_crypto/src/crypto_hd_node/extended_key/extended_private_key.rs new file mode 100644 index 00000000000..7ff92d0decf --- /dev/null +++ b/rust/tw_crypto/src/crypto_hd_node/extended_key/extended_private_key.rs @@ -0,0 +1,304 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use bip32::{ + ChildNumber, DerivationPath, ExtendedKey, ExtendedKeyAttrs, KeyFingerprint, Prefix, Version, + KEY_SIZE, +}; +use sha2::digest::Mac; +use tw_encoding::hex::{self, ToHex}; +use tw_hash::hasher::Hasher; +use tw_hash::hasher::StatefulHasher; +use tw_hash::hmac::HmacSha512; +use tw_keypair::tw::Curve; +use tw_misc::traits::ToBytesZeroizing; +use zeroize::Zeroize; +use zeroize::Zeroizing; + +use crate::crypto_hd_node::error::{Error, Result}; +use crate::crypto_hd_node::extended_key::{ + bip32_private_key::BIP32PrivateKey, extended_public_key::ExtendedPublicKey, +}; + +use super::hd_version::HDVersion; + +/// Extended private keys derived using BIP32. +/// +/// Generic around a [`PrivateKey`] type. When the `secp256k1` feature of this +/// crate is enabled, the [`XPrv`] type provides a convenient alias for +/// extended ECDSA/secp256k1 private keys. +#[derive(Clone)] +pub struct ExtendedPrivateKey { + /// Derived private key + private_key: K, + + /// Extended key attributes. + attrs: ExtendedKeyAttrs, +} + +impl ExtendedPrivateKey +where + K: BIP32PrivateKey, +{ + /// Create the root extended key for the given seed value. + pub fn new(seed: S) -> Result + where + S: AsRef<[u8]>, + { + let (private_key, chain_code) = if K::curve() == Curve::Ed25519ExtendedCardano { + let mut digest = [0u8; 128]; + pbkdf2::pbkdf2_hmac::(&[], seed.as_ref(), 4096, &mut digest); + + let secret = &mut digest[0..96]; + secret[0] &= 0xf8; + secret[31] &= 0x1f; + secret[31] |= 0x40; + + ( + K::try_from(&digest[0..96]).map_err(|_| Error::InvalidKeyData)?, + digest[64..96].try_into()?, + ) + } else { + let domain_separator = hex::decode(&K::bip32_name().to_hex())?; + + let mut hmac = HmacSha512::new_from_slice(&domain_separator).expect("Should not fail"); + hmac.update(seed.as_ref()); + + let result = hmac.finalize().into_bytes(); + let (private_key, chain_code) = result.split_at(KEY_SIZE); + ( + K::try_from(private_key).map_err(|_| Error::InvalidKeyData)?, + chain_code.try_into()?, + ) + }; + + let attrs = ExtendedKeyAttrs { + depth: 0, + parent_fingerprint: KeyFingerprint::default(), + child_number: ChildNumber::default(), + chain_code, + }; + + Ok(ExtendedPrivateKey { private_key, attrs }) + } + + /// Derive a child key from the given [`DerivationPath`]. + pub fn derive_from_path(&self, path: &DerivationPath, hasher: Hasher) -> Result { + path.iter().try_fold(self.clone(), |key, child_num| { + key.derive_child(child_num, hasher) + }) + } + + /// Derive a child key for a particular [`ChildNumber`]. + pub fn derive_child(&self, child_number: ChildNumber, hasher: Hasher) -> Result { + let depth = self.attrs.depth.checked_add(1).ok_or(Error::InvalidDepth)?; + let (tweak, chain_code) = self + .private_key + .derive_tweak(&self.attrs.chain_code, child_number)?; + + // We should technically loop here if the tweak is zero or overflows + // the order of the underlying elliptic curve group, incrementing the + // index, however per "Child key derivation (CKD) functions": + // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions + // + // > "Note: this has probability lower than 1 in 2^127." + // + // ...so instead, we simply return an error if this were ever to happen, + // as the chances of it happening are vanishingly small. + let private_key = self.private_key.derive_child(&tweak, child_number)?; + + let attrs = ExtendedKeyAttrs { + parent_fingerprint: self.public_key().fingerprint(hasher), + child_number, + chain_code, + depth, + }; + + Ok(ExtendedPrivateKey { private_key, attrs }) + } + + /// Borrow the derived private key value. + pub fn private_key(&self) -> &K { + &self.private_key + } + + /// Serialize the derived public key as bytes. + pub fn public_key(&self) -> ExtendedPublicKey { + self.into() + } + + /// Get attributes for this key such as depth, parent fingerprint, + /// child number, and chain code. + pub fn attrs(&self) -> &ExtendedKeyAttrs { + &self.attrs + } + + /// Serialize this key as an [`ExtendedKey`]. + pub fn to_extended_key(&self, prefix: Prefix) -> Result { + if K::curve() == Curve::Ed25519ExtendedCardano { + return Err(Error::UnsupportedCurve(K::curve().to_raw())); + } + // Add leading `0` byte + let mut key_bytes = [0u8; KEY_SIZE + 1]; + key_bytes[1..].copy_from_slice(&self.private_key.to_zeroizing_vec()); + + Ok(ExtendedKey { + prefix, + attrs: self.attrs.clone(), + key_bytes, + }) + } + + /// Serialize this key as a self-[`Zeroizing`] `String`. + pub fn to_string(&self, prefix: Prefix) -> Result> { + Ok(Zeroizing::new(self.to_extended_key(prefix)?.to_string())) + } + + pub fn from_base58(xprv: &str, hasher: Hasher) -> Result { + let extended_key = decode_base58(xprv, hasher)?; + extended_key.try_into() + } +} + +impl TryFrom for ExtendedPrivateKey +where + K: BIP32PrivateKey, +{ + type Error = Error; + + fn try_from(key: ExtendedKey) -> Result { + let version = HDVersion::from_repr(key.prefix.version()).ok_or(Error::InvalidKeyData)?; + if version.is_private() && key.key_bytes[0] == 0 { + Ok(ExtendedPrivateKey { + private_key: K::try_from(&key.key_bytes[1..]).map_err(|_| Error::InvalidKeyData)?, + attrs: key.attrs.clone(), + }) + } else { + Err(Error::InvalidKeyData) + } + } +} + +impl ToBytesZeroizing for ExtendedPrivateKey +where + K: BIP32PrivateKey, +{ + fn to_zeroizing_vec(&self) -> Zeroizing> { + self.private_key.to_zeroizing_vec() + } +} + +/// Size of an extended key when deserialized into bytes from Base58. +const BYTE_SIZE: usize = 78; + +/// Size of the checksum in a Base58Check-encoded extended key. +const CHECKSUM_LEN: usize = 4; + +/// Maximum size of a Base58Check-encoded extended key in bytes. +/// +/// Note that extended keys can also be 111-bytes. +const MAX_BASE58_SIZE: usize = 112; + +/// Write a Base58-encoded key to the provided buffer, returning a `String` +/// containing the serialized data. +pub fn encode_base58(extended_key: &ExtendedKey, hasher: Hasher) -> Result { + let mut buffer = [0u8; MAX_BASE58_SIZE]; + + let mut bytes = [0u8; BYTE_SIZE]; // with 4-byte checksum + bytes[..4].copy_from_slice(&extended_key.prefix.to_bytes()); + bytes[4] = extended_key.attrs.depth; + bytes[5..9].copy_from_slice(&extended_key.attrs.parent_fingerprint); + bytes[9..13].copy_from_slice(&extended_key.attrs.child_number.to_bytes()); + bytes[13..45].copy_from_slice(&extended_key.attrs.chain_code); + bytes[45..78].copy_from_slice(&extended_key.key_bytes); + + let checksum = hasher.hash(&bytes); + + let mut bytes_with_checksum = [0u8; BYTE_SIZE + CHECKSUM_LEN]; + bytes_with_checksum[..BYTE_SIZE].copy_from_slice(&bytes); + bytes_with_checksum[78..].copy_from_slice(&checksum[..CHECKSUM_LEN]); + bytes.zeroize(); + + let base58_len = bs58::encode(&bytes_with_checksum).onto(buffer.as_mut())?; + bytes_with_checksum.zeroize(); + + String::from_utf8(buffer[..base58_len].to_vec()).map_err(|_| Error::Base58) +} + +pub fn decode_base58(base58: &str, hasher: Hasher) -> Result { + let mut bytes = [0u8; BYTE_SIZE + CHECKSUM_LEN]; // with 4-byte checksum + let decoded_len = bs58::decode(base58).onto(&mut bytes)?; + + if decoded_len != BYTE_SIZE + CHECKSUM_LEN { + return Err(Error::Decode); + } + + let checksum_index = decoded_len - CHECKSUM_LEN; + + let expected_checksum = &bytes[checksum_index..decoded_len]; + + let hash = hasher.hash(&bytes[0..checksum_index]); + let (checksum, _) = hash.split_at(CHECKSUM_LEN); + + if checksum != expected_checksum { + return Err(Error::InvalidChecksum); + } + + let prefix = base58.get(..4).ok_or(Error::Decode).and_then(|chars| { + validate_prefix(chars)?; + let version = Version::from_be_bytes(bytes[..4].try_into()?); + Ok(Prefix::from_parts_unchecked(chars, version)) + })?; + + let depth = bytes[4]; + let parent_fingerprint = bytes[5..9].try_into()?; + let child_number = ChildNumber::from_bytes(bytes[9..13].try_into()?); + let chain_code = bytes[13..45].try_into()?; + let key_bytes = bytes[45..78].try_into()?; + bytes.zeroize(); + + let attrs = ExtendedKeyAttrs { + depth, + parent_fingerprint, + child_number, + chain_code, + }; + + Ok(ExtendedKey { + prefix, + attrs, + key_bytes, + }) +} + +const fn validate_prefix(s: &str) -> Result<&str> { + if s.len() != Prefix::LENGTH { + return Err(Error::Decode); + } + + let mut i = 0; + + while i < Prefix::LENGTH { + if s.as_bytes()[i].is_ascii_alphabetic() { + i += 1; + } else { + return Err(Error::Decode); + } + } + + Ok(s) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_validate_prefix() { + assert!(validate_prefix("eaab").is_ok()); + assert!(validate_prefix("eaab1").is_err()); + } +} diff --git a/rust/tw_crypto/src/crypto_hd_node/extended_key/extended_public_key.rs b/rust/tw_crypto/src/crypto_hd_node/extended_key/extended_public_key.rs new file mode 100644 index 00000000000..dde1a40accb --- /dev/null +++ b/rust/tw_crypto/src/crypto_hd_node/extended_key/extended_public_key.rs @@ -0,0 +1,151 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use bip32::{ + ChildNumber, DerivationPath, ExtendedKey, ExtendedKeyAttrs, KeyFingerprint, Prefix, KEY_SIZE, +}; +use tw_misc::traits::ToBytesVec; + +use crate::crypto_hd_node::error::{Error, Result}; +use crate::crypto_hd_node::extended_key::{ + bip32_private_key::BIP32PrivateKey, bip32_public_key::BIP32PublicKey, + extended_private_key::ExtendedPrivateKey, +}; + +use super::{extended_private_key::decode_base58, hd_version::HDVersion}; +use tw_hash::hasher::Hasher; + +/// Extended public keys derived using BIP32. +/// +/// Generic around a [`PublicKey`] type. When the `secp256k1` feature of this +/// crate is enabled, the [`XPub`] type provides a convenient alias for +/// extended ECDSA/secp256k1 public keys. +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub struct ExtendedPublicKey { + /// Derived public key + public_key: K, + + /// Extended key attributes. + attrs: ExtendedKeyAttrs, +} + +impl ExtendedPublicKey +where + K: BIP32PublicKey, +{ + /// Obtain the non-extended public key value `K`. + pub fn public_key(&self) -> &K { + &self.public_key + } + + /// Compute a 4-byte key fingerprint for this extended public key. + pub fn fingerprint(&self, hasher: Hasher) -> KeyFingerprint { + self.public_key().fingerprint(hasher) + } + + /// Serialize the raw public key as a byte array (e.g. SEC1-encoded). + pub fn to_bytes(&self) -> Vec { + self.public_key.to_vec() + } + + /// Serialize this key as an [`ExtendedKey`]. + pub fn to_extended_key(&self, prefix: Prefix) -> ExtendedKey { + let bytes = self.to_bytes(); + + let mut key_bytes = [0u8; KEY_SIZE + 1]; + if bytes.len() == KEY_SIZE { + // Add leading `0` byte + key_bytes[1..].copy_from_slice(&self.to_bytes()); + } else { + key_bytes.copy_from_slice(&self.to_bytes()); + } + + ExtendedKey { + prefix, + attrs: self.attrs.clone(), + key_bytes, + } + } + + pub fn derive_from_path(&self, path: &DerivationPath, hasher: Hasher) -> Result { + path.iter().try_fold(self.clone(), |key, child_num| { + key.derive_child(child_num, hasher) + }) + } + + /// Derive a child key for a particular [`ChildNumber`]. + pub fn derive_child(&self, child_number: ChildNumber, hasher: Hasher) -> Result { + let depth = self.attrs.depth.checked_add(1).ok_or(Error::InvalidDepth)?; + let (tweak, chain_code) = self + .public_key + .derive_tweak(&self.attrs.chain_code, child_number)?; + + // We should technically loop here if the tweak is zero or overflows + // the order of the underlying elliptic curve group, incrementing the + // index, however per "Child key derivation (CKD) functions": + // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions + // + // > "Note: this has probability lower than 1 in 2^127." + // + // ...so instead, we simply return an error if this were ever to happen, + // as the chances of it happening are vanishingly small. + let public_key = self.public_key.derive_child(&tweak, child_number)?; + + let attrs = ExtendedKeyAttrs { + parent_fingerprint: self.public_key.fingerprint(hasher), + child_number, + chain_code, + depth, + }; + + Ok(ExtendedPublicKey { public_key, attrs }) + } + + pub fn from_base58(xpub: &str, hasher: Hasher) -> Result { + let extended_key = decode_base58(xpub, hasher)?; + extended_key.try_into() + } +} + +impl From<&ExtendedPrivateKey> for ExtendedPublicKey +where + K: BIP32PrivateKey, +{ + fn from(xprv: &ExtendedPrivateKey) -> ExtendedPublicKey { + ExtendedPublicKey { + public_key: xprv.private_key().public_key(), + attrs: xprv.attrs().clone(), + } + } +} + +impl TryFrom for ExtendedPublicKey +where + K: BIP32PublicKey, +{ + type Error = Error; + + fn try_from(key: ExtendedKey) -> Result { + let version = HDVersion::from_repr(key.prefix.version()).ok_or(Error::InvalidKeyData)?; + if version.is_public() { + Ok(ExtendedPublicKey { + public_key: K::try_from(&key.key_bytes).map_err(|_| Error::InvalidKeyData)?, + attrs: key.attrs.clone(), + }) + } else { + Err(Error::InvalidKeyData) + } + } +} + +impl ToBytesVec for ExtendedPublicKey +where + K: BIP32PublicKey, +{ + fn to_vec(&self) -> Vec { + self.public_key.to_vec() + } +} diff --git a/rust/tw_crypto/src/crypto_hd_node/extended_key/hd_version.rs b/rust/tw_crypto/src/crypto_hd_node/extended_key/hd_version.rs new file mode 100644 index 00000000000..15ebde44c50 --- /dev/null +++ b/rust/tw_crypto/src/crypto_hd_node/extended_key/hd_version.rs @@ -0,0 +1,67 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#[repr(u32)] +#[derive(strum_macros::FromRepr)] +pub enum HDVersion { + None = 0, + + // Bitcoin + XPUB = 0x0488b21e, + XPRV = 0x0488ade4, + YPUB = 0x049d7cb2, + YPRV = 0x049d7878, + ZPUB = 0x04b24746, + ZPRV = 0x04b2430c, + VPUB = 0x045f1cf6, + VPRV = 0x045f18bc, + TPUB = 0x043587cf, + TPRV = 0x04358394, + + // Litecoin + LTUB = 0x019da462, + LTPV = 0x019d9cfe, + MTUB = 0x01b26ef6, + MTPV = 0x01b26792, + TTUB = 0x0436f6e1, + TTPV = 0x0436ef7d, + + // Decred + DPUB = 0x2fda926, + DPRV = 0x2fda4e8, + + // Dogecoin + DGUB = 0x02facafd, + DGPV = 0x02fac398, +} + +impl HDVersion { + pub fn is_public(&self) -> bool { + matches!( + self, + HDVersion::XPUB + | HDVersion::YPUB + | HDVersion::ZPUB + | HDVersion::LTUB + | HDVersion::MTUB + | HDVersion::DPUB + | HDVersion::DGUB + ) + } + + pub fn is_private(&self) -> bool { + matches!( + self, + HDVersion::XPRV + | HDVersion::YPRV + | HDVersion::ZPRV + | HDVersion::LTPV + | HDVersion::MTPV + | HDVersion::DPRV + | HDVersion::DGPV + ) + } +} diff --git a/rust/tw_crypto/src/crypto_hd_node/extended_key/mod.rs b/rust/tw_crypto/src/crypto_hd_node/extended_key/mod.rs new file mode 100644 index 00000000000..fbd87b529d7 --- /dev/null +++ b/rust/tw_crypto/src/crypto_hd_node/extended_key/mod.rs @@ -0,0 +1,11 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod bip32_private_key; +pub mod bip32_public_key; +pub mod extended_private_key; +pub mod extended_public_key; +pub mod hd_version; diff --git a/rust/tw_crypto/src/crypto_hd_node/hd_node.rs b/rust/tw_crypto/src/crypto_hd_node/hd_node.rs new file mode 100644 index 00000000000..d8624e56353 --- /dev/null +++ b/rust/tw_crypto/src/crypto_hd_node/hd_node.rs @@ -0,0 +1,234 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::str::FromStr; + +use bip32::{DerivationPath, Prefix}; +use tw_hash::hasher::Hasher; +use tw_keypair::tw::Curve; +use tw_misc::traits::{ToBytesVec, ToBytesZeroizing}; +use zeroize::Zeroizing; + +use crate::crypto_hd_node::error::{Error, Result}; +use crate::crypto_hd_node::extended_key::extended_private_key::ExtendedPrivateKey; + +use super::ed25519::cardano::cardano_staking_derivation_path; +use super::extended_key::extended_private_key::encode_base58; + +pub type XPrvSecp256k1 = ExtendedPrivateKey; +pub type XPrvNist256p1 = ExtendedPrivateKey; +pub type XPrvEd25519 = ExtendedPrivateKey; +pub type XPrvEd25519Blake2bNano = ExtendedPrivateKey; +pub type XPrvCurve25519Waves = ExtendedPrivateKey; +pub type XPrvCardano = ExtendedPrivateKey; +pub type XPrvZilliqaSchnorr = ExtendedPrivateKey; + +pub enum HDNode { + Secp256k1(XPrvSecp256k1), + Nist256p1(XPrvNist256p1), + Ed25519(XPrvEd25519), + Ed25519Blake2bNano(XPrvEd25519Blake2bNano), + Curve25519Waves(XPrvCurve25519Waves), + Ed25519ExtendedCardano(Box, Option>), + ZilliqaSchnorr(XPrvZilliqaSchnorr), +} + +impl HDNode { + pub fn new(seed: &[u8], curve: Curve) -> Result { + match curve { + Curve::Secp256k1 => { + let xprv = XPrvSecp256k1::new(seed)?; + Ok(HDNode::Secp256k1(xprv)) + }, + Curve::Nist256p1 => { + let xprv = XPrvNist256p1::new(seed)?; + Ok(HDNode::Nist256p1(xprv)) + }, + Curve::Ed25519 => { + let xprv = XPrvEd25519::new(seed)?; + Ok(HDNode::Ed25519(xprv)) + }, + Curve::Ed25519Blake2bNano => { + let xprv = XPrvEd25519Blake2bNano::new(seed)?; + Ok(HDNode::Ed25519Blake2bNano(xprv)) + }, + Curve::Curve25519Waves => { + let xprv = XPrvCurve25519Waves::new(seed)?; + Ok(HDNode::Curve25519Waves(xprv)) + }, + Curve::Ed25519ExtendedCardano => { + let xprv = XPrvCardano::new(seed)?; + Ok(HDNode::Ed25519ExtendedCardano(Box::new(xprv), None)) + }, + Curve::ZilliqaSchnorr => { + let xprv = XPrvZilliqaSchnorr::new(seed)?; + Ok(HDNode::ZilliqaSchnorr(xprv)) + }, + _ => Err(Error::UnsupportedCurve(curve.to_raw())), + } + } + + pub fn derive_from_path(&self, path: &str, hasher: Hasher) -> Result { + let path = DerivationPath::from_str(path)?; + match self { + HDNode::Secp256k1(xprv) => { + let xprv = xprv.derive_from_path(&path, hasher)?; + Ok(HDNode::Secp256k1(xprv)) + }, + HDNode::Nist256p1(xprv) => { + let xprv = xprv.derive_from_path(&path, hasher)?; + Ok(HDNode::Nist256p1(xprv)) + }, + HDNode::Ed25519(xprv) => { + let xprv = xprv.derive_from_path(&path, hasher)?; + Ok(HDNode::Ed25519(xprv)) + }, + HDNode::Ed25519Blake2bNano(xprv) => { + let xprv = xprv.derive_from_path(&path, hasher)?; + Ok(HDNode::Ed25519Blake2bNano(xprv)) + }, + HDNode::Curve25519Waves(xprv) => { + let xprv = xprv.derive_from_path(&path, hasher)?; + Ok(HDNode::Curve25519Waves(xprv)) + }, + HDNode::Ed25519ExtendedCardano(xprv, _) => { + let xprv1 = xprv.derive_from_path(&path, hasher)?; + let staking_path = cardano_staking_derivation_path(&path)?; + let xprv2 = xprv.derive_from_path(&staking_path, hasher)?; + Ok(HDNode::Ed25519ExtendedCardano( + Box::new(xprv1), + Some(Box::new(xprv2)), + )) + }, + HDNode::ZilliqaSchnorr(xprv) => { + let xprv = xprv.derive_from_path(&path, hasher)?; + Ok(HDNode::ZilliqaSchnorr(xprv)) + }, + } + } + + pub fn private_key_data(&self) -> Result>> { + match self { + HDNode::Secp256k1(xprv) => Ok(xprv.private_key().to_zeroizing_vec()), + HDNode::Nist256p1(xprv) => Ok(xprv.private_key().to_zeroizing_vec()), + HDNode::Ed25519(xprv) => Ok(xprv.private_key().to_zeroizing_vec()), + HDNode::Ed25519Blake2bNano(xprv) => Ok(xprv.private_key().to_zeroizing_vec()), + HDNode::Curve25519Waves(xprv) => Ok(xprv.private_key().to_zeroizing_vec()), + HDNode::Ed25519ExtendedCardano(xprv, xprv2) => { + let mut data = xprv.private_key().to_zeroizing_vec(); + if let Some(xprv2) = xprv2 { + data.extend(xprv2.private_key().to_zeroizing_vec().as_slice()); + } + Ok(data) + }, + HDNode::ZilliqaSchnorr(xprv) => Ok(xprv.private_key().to_zeroizing_vec()), + } + } + + pub fn public_key_data(&self) -> Result> { + match self { + HDNode::Secp256k1(xprv) => Ok(xprv.public_key().to_bytes().to_vec()), + HDNode::Nist256p1(xprv) => Ok(xprv.public_key().to_bytes().to_vec()), + HDNode::Ed25519(xprv) => Ok(xprv.public_key().to_bytes().to_vec()), + HDNode::Ed25519Blake2bNano(xprv) => Ok(xprv.public_key().to_bytes().to_vec()), + HDNode::Curve25519Waves(xprv) => Ok(xprv.public_key().to_bytes().to_vec()), + HDNode::Ed25519ExtendedCardano(xprv, xprv2) => { + let mut data = xprv.public_key().to_bytes().to_vec(); + if let Some(xprv2) = xprv2 { + data.extend(xprv2.public_key().to_bytes().to_vec()); + } + Ok(data) + }, + HDNode::ZilliqaSchnorr(xprv) => Ok(xprv.public_key().to_bytes().to_vec()), + } + } + + pub fn chain_code(&self) -> Result> { + match self { + HDNode::Secp256k1(xprv) => Ok(xprv.attrs().chain_code.to_vec()), + HDNode::Nist256p1(xprv) => Ok(xprv.attrs().chain_code.to_vec()), + HDNode::Ed25519(xprv) => Ok(xprv.attrs().chain_code.to_vec()), + HDNode::Ed25519Blake2bNano(xprv) => Ok(xprv.attrs().chain_code.to_vec()), + HDNode::Curve25519Waves(xprv) => Ok(xprv.attrs().chain_code.to_vec()), + HDNode::Ed25519ExtendedCardano(xprv, _) => Ok(xprv.attrs().chain_code.to_vec()), + HDNode::ZilliqaSchnorr(xprv) => Ok(xprv.attrs().chain_code.to_vec()), + } + } + + pub fn depth(&self) -> Result { + match self { + HDNode::Secp256k1(xprv) => Ok(xprv.attrs().depth), + HDNode::Nist256p1(xprv) => Ok(xprv.attrs().depth), + HDNode::Ed25519(xprv) => Ok(xprv.attrs().depth), + HDNode::Ed25519Blake2bNano(xprv) => Ok(xprv.attrs().depth), + HDNode::Curve25519Waves(xprv) => Ok(xprv.attrs().depth), + HDNode::Ed25519ExtendedCardano(xprv, _) => Ok(xprv.attrs().depth), + HDNode::ZilliqaSchnorr(xprv) => Ok(xprv.attrs().depth), + } + } + + pub fn child_number(&self) -> Result { + match self { + HDNode::Secp256k1(xprv) => Ok(xprv.attrs().child_number.0), + HDNode::Nist256p1(xprv) => Ok(xprv.attrs().child_number.0), + HDNode::Ed25519(xprv) => Ok(xprv.attrs().child_number.0), + HDNode::Ed25519Blake2bNano(xprv) => Ok(xprv.attrs().child_number.0), + HDNode::Curve25519Waves(xprv) => Ok(xprv.attrs().child_number.0), + HDNode::Ed25519ExtendedCardano(xprv, _) => Ok(xprv.attrs().child_number.0), + HDNode::ZilliqaSchnorr(xprv) => Ok(xprv.attrs().child_number.0), + } + } + + pub fn extended_private_key(&self, version: u32, hasher: Hasher) -> Result { + let prefix = Prefix::try_from(version)?; + let extended_key = match self { + HDNode::Secp256k1(xprv) => xprv.to_extended_key(prefix)?, + HDNode::Nist256p1(xprv) => xprv.to_extended_key(prefix)?, + HDNode::Ed25519(xprv) => xprv.to_extended_key(prefix)?, + HDNode::Ed25519Blake2bNano(xprv) => xprv.to_extended_key(prefix)?, + HDNode::Curve25519Waves(xprv) => xprv.to_extended_key(prefix)?, + HDNode::Ed25519ExtendedCardano(xprv, _) => xprv.to_extended_key(prefix)?, + HDNode::ZilliqaSchnorr(xprv) => xprv.to_extended_key(prefix)?, + }; + encode_base58(&extended_key, hasher) + } + + pub fn extended_public_key(&self, version: u32, hasher: Hasher) -> Result { + let prefix = Prefix::try_from(version)?; + let extended_key = match self { + HDNode::Secp256k1(xprv) => xprv.public_key().to_extended_key(prefix), + HDNode::Nist256p1(xprv) => xprv.public_key().to_extended_key(prefix), + HDNode::Ed25519(xprv) => xprv.public_key().to_extended_key(prefix), + HDNode::Ed25519Blake2bNano(xprv) => xprv.public_key().to_extended_key(prefix), + HDNode::Curve25519Waves(xprv) => xprv.public_key().to_extended_key(prefix), + HDNode::Ed25519ExtendedCardano(xprv, _) => xprv.public_key().to_extended_key(prefix), + HDNode::ZilliqaSchnorr(xprv) => xprv.public_key().to_extended_key(prefix), + }; + encode_base58(&extended_key, hasher) + } + + pub fn try_from(s: &str, curve: Curve, hasher: Hasher) -> Result { + match curve { + Curve::Secp256k1 => Ok(HDNode::Secp256k1(XPrvSecp256k1::from_base58(s, hasher)?)), + Curve::Nist256p1 => Ok(HDNode::Nist256p1(XPrvNist256p1::from_base58(s, hasher)?)), + Curve::Ed25519 => Ok(HDNode::Ed25519(XPrvEd25519::from_base58(s, hasher)?)), + Curve::Ed25519Blake2bNano => Ok(HDNode::Ed25519Blake2bNano( + XPrvEd25519Blake2bNano::from_base58(s, hasher)?, + )), + Curve::Curve25519Waves => Ok(HDNode::Curve25519Waves( + XPrvCurve25519Waves::from_base58(s, hasher)?, + )), + Curve::Ed25519ExtendedCardano => Ok(HDNode::Ed25519ExtendedCardano( + Box::new(XPrvCardano::from_base58(s, hasher)?), + None, + )), + Curve::ZilliqaSchnorr => Ok(HDNode::ZilliqaSchnorr(XPrvZilliqaSchnorr::from_base58( + s, hasher, + )?)), + _ => Err(Error::UnsupportedCurve(curve.to_raw())), + } + } +} diff --git a/rust/tw_crypto/src/crypto_hd_node/hd_node_public.rs b/rust/tw_crypto/src/crypto_hd_node/hd_node_public.rs new file mode 100644 index 00000000000..717546f5080 --- /dev/null +++ b/rust/tw_crypto/src/crypto_hd_node/hd_node_public.rs @@ -0,0 +1,122 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::str::FromStr; + +use bip32::DerivationPath; +use tw_hash::hasher::Hasher; +use tw_keypair::tw::Curve; +use tw_misc::traits::ToBytesVec; + +use crate::crypto_hd_node::error::{Error, Result}; +use crate::crypto_hd_node::extended_key::extended_public_key::ExtendedPublicKey; + +use super::ed25519::cardano::cardano_staking_derivation_path; + +pub type XPubSecp256k1 = ExtendedPublicKey; +pub type XPubNist256p1 = ExtendedPublicKey; +pub type XPubEd25519 = ExtendedPublicKey; +pub type XPubEd25519Blake2bNano = ExtendedPublicKey; +pub type XPubCurve25519Waves = ExtendedPublicKey; +pub type XPubCardano = ExtendedPublicKey; +pub type XPubZilliqaSchnorr = ExtendedPublicKey; + +pub enum HDNodePublic { + Secp256k1(XPubSecp256k1), + Nist256p1(XPubNist256p1), + Ed25519(XPubEd25519), + Ed25519Blake2bNano(XPubEd25519Blake2bNano), + Curve25519Waves(XPubCurve25519Waves), + Ed25519ExtendedCardano(Box, Option>), + ZilliqaSchnorr(XPubZilliqaSchnorr), +} + +impl HDNodePublic { + pub fn try_from(s: &str, curve: Curve, hasher: Hasher) -> Result { + match curve { + Curve::Secp256k1 => { + let xpub = XPubSecp256k1::from_base58(s, hasher)?; + Ok(HDNodePublic::Secp256k1(xpub)) + }, + Curve::Nist256p1 => { + let xpub = XPubNist256p1::from_base58(s, hasher)?; + Ok(HDNodePublic::Nist256p1(xpub)) + }, + Curve::Ed25519 => { + let xpub = XPubEd25519::from_base58(s, hasher)?; + Ok(HDNodePublic::Ed25519(xpub)) + }, + Curve::Ed25519Blake2bNano => { + let xpub = XPubEd25519Blake2bNano::from_base58(s, hasher)?; + Ok(HDNodePublic::Ed25519Blake2bNano(xpub)) + }, + Curve::Curve25519Waves => { + let xpub = XPubCurve25519Waves::from_base58(s, hasher)?; + Ok(HDNodePublic::Curve25519Waves(xpub)) + }, + Curve::Ed25519ExtendedCardano => { + let xpub = XPubCardano::from_base58(s, hasher)?; + Ok(HDNodePublic::Ed25519ExtendedCardano(Box::new(xpub), None)) + }, + Curve::ZilliqaSchnorr => { + let xpub = XPubZilliqaSchnorr::from_base58(s, hasher)?; + Ok(HDNodePublic::ZilliqaSchnorr(xpub)) + }, + _ => Err(Error::UnsupportedCurve(curve.to_raw())), + } + } + + pub fn derive_from_path(&self, path: &str, hasher: Hasher) -> Result { + let path = DerivationPath::from_str(path)?; + match self { + HDNodePublic::Secp256k1(xpub) => { + let xpub = xpub.derive_from_path(&path, hasher)?; + Ok(HDNodePublic::Secp256k1(xpub)) + }, + HDNodePublic::Nist256p1(xpub) => { + let xpub = xpub.derive_from_path(&path, hasher)?; + Ok(HDNodePublic::Nist256p1(xpub)) + }, + HDNodePublic::Ed25519(xpub) => { + let xpub = xpub.derive_from_path(&path, hasher)?; + Ok(HDNodePublic::Ed25519(xpub)) + }, + HDNodePublic::Ed25519Blake2bNano(xpub) => { + let xpub = xpub.derive_from_path(&path, hasher)?; + Ok(HDNodePublic::Ed25519Blake2bNano(xpub)) + }, + HDNodePublic::Curve25519Waves(xpub) => { + let xpub = xpub.derive_from_path(&path, hasher)?; + Ok(HDNodePublic::Curve25519Waves(xpub)) + }, + HDNodePublic::Ed25519ExtendedCardano(xpub, _) => { + let xpub = xpub.derive_from_path(&path, hasher)?; + let staking_path = cardano_staking_derivation_path(&path)?; + let xpub2 = xpub.derive_from_path(&staking_path, hasher)?; + Ok(HDNodePublic::Ed25519ExtendedCardano( + Box::new(xpub), + Some(Box::new(xpub2)), + )) + }, + HDNodePublic::ZilliqaSchnorr(xpub) => { + let xpub = xpub.derive_from_path(&path, hasher)?; + Ok(HDNodePublic::ZilliqaSchnorr(xpub)) + }, + } + } + + pub fn public_key_data(&self) -> Result> { + match self { + HDNodePublic::Secp256k1(xpub) => Ok(xpub.public_key().to_vec()), + HDNodePublic::Nist256p1(xpub) => Ok(xpub.public_key().to_vec()), + HDNodePublic::Ed25519(xpub) => Ok(xpub.public_key().to_vec()), + HDNodePublic::Ed25519Blake2bNano(xpub) => Ok(xpub.public_key().to_vec()), + HDNodePublic::Curve25519Waves(xpub) => Ok(xpub.public_key().to_vec()), + HDNodePublic::Ed25519ExtendedCardano(xpub, _) => Ok(xpub.public_key().to_vec()), + HDNodePublic::ZilliqaSchnorr(xpub) => Ok(xpub.public_key().to_vec()), + } + } +} diff --git a/rust/tw_crypto/src/crypto_hd_node/mod.rs b/rust/tw_crypto/src/crypto_hd_node/mod.rs new file mode 100644 index 00000000000..8435e7cfbd0 --- /dev/null +++ b/rust/tw_crypto/src/crypto_hd_node/mod.rs @@ -0,0 +1,13 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod ecdsa; +pub mod ed25519; +pub mod error; +pub mod extended_key; +pub mod hd_node; +pub mod hd_node_public; +pub mod zilliqa_schnorr; diff --git a/rust/tw_crypto/src/crypto_hd_node/zilliqa_schnorr/mod.rs b/rust/tw_crypto/src/crypto_hd_node/zilliqa_schnorr/mod.rs new file mode 100644 index 00000000000..59df32c2698 --- /dev/null +++ b/rust/tw_crypto/src/crypto_hd_node/zilliqa_schnorr/mod.rs @@ -0,0 +1,45 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use bip32::ChildNumber; +use tw_hash::H256; +use tw_keypair::traits::DerivableKeyTrait; +use tw_keypair::{tw::Curve, zilliqa_schnorr}; + +use crate::crypto_hd_node::error::{Error, Result}; +use crate::crypto_hd_node::extended_key::{ + bip32_private_key::BIP32PrivateKey, bip32_public_key::BIP32PublicKey, +}; + +impl BIP32PrivateKey for zilliqa_schnorr::PrivateKey { + type BIP32PublicKey = zilliqa_schnorr::PublicKey; + + fn derive_child(&self, other: &[u8], _child_number: ChildNumber) -> Result { + let other = H256::try_from(other).map_err(|_| Error::InvalidKeyData)?; + ::derive_child(self, other) + .map_err(|_| Error::DerivationFailed) + } + + fn curve() -> Curve { + Curve::ZilliqaSchnorr + } + + fn bip32_name() -> &'static str { + "Bitcoin seed" + } + + fn public_key(&self) -> Self::BIP32PublicKey { + self.public() + } +} + +impl BIP32PublicKey for zilliqa_schnorr::PublicKey { + fn derive_child(&self, other: &[u8], _child_number: ChildNumber) -> Result { + let other = H256::try_from(other).map_err(|_| Error::InvalidKeyData)?; + ::derive_child(self, other) + .map_err(|_| Error::DerivationFailed) + } +} diff --git a/rust/tw_crypto/src/ffi/crypto_hd_node.rs b/rust/tw_crypto/src/ffi/crypto_hd_node.rs new file mode 100644 index 00000000000..5518d632e55 --- /dev/null +++ b/rust/tw_crypto/src/ffi/crypto_hd_node.rs @@ -0,0 +1,219 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![allow(clippy::missing_safety_doc)] + +use crate::crypto_hd_node::hd_node::HDNode; +use tw_hash::hasher::Hasher; +use tw_keypair::tw::Curve; +use tw_macros::tw_ffi; +use tw_memory::ffi::{ + tw_data::TWData, tw_string::TWString, Nonnull, NonnullMut, NullableMut, RawPtrTrait, +}; +use tw_misc::try_or_else; + +pub struct TWHDNode(pub(crate) HDNode); + +impl RawPtrTrait for TWHDNode {} + +impl AsRef for TWHDNode { + fn as_ref(&self) -> &HDNode { + &self.0 + } +} + +/// Create a HDNode with the given seed and curve. +/// +/// \param seed *non-null* byte array. +/// \param curve the curve to use. +/// \note Should be deleted with \tw_hd_node_delete. +/// \return Nullable pointer to HDNode. +#[tw_ffi(ty = constructor, class = TWHDNode, name = CreateWithSeed)] +#[no_mangle] +pub unsafe extern "C" fn tw_hd_node_create_with_seed( + seed: Nonnull, + curve: u32, +) -> NullableMut { + let data = TWData::from_ptr_as_ref(seed) + .map(|data| data.as_slice()) + .unwrap_or_default(); + let curve = try_or_else!(Curve::from_raw(curve), std::ptr::null_mut); + + HDNode::new(data, curve) + .map(|hd_node| TWHDNode(hd_node).into_ptr()) + // Return null if the private key is invalid. + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Create a HDNode from an extended private key. +/// +/// \param extended_private_key *non-null* string. +/// \param curve the curve to use. +/// \param hasher the hasher to use. +/// \return Nullable pointer to HDNode. +#[tw_ffi(ty = constructor, class = TWHDNode, name = CreateWithExtendedPrivateKey)] +#[no_mangle] +pub unsafe extern "C" fn tw_hd_node_create_with_extended_private_key( + extended_private_key: Nonnull, + curve: u32, + hasher: u32, +) -> NullableMut { + let extended_private_key_ref = try_or_else!( + TWString::from_ptr_as_ref(extended_private_key), + std::ptr::null_mut + ); + let extended_private_key_str = + try_or_else!(extended_private_key_ref.as_str(), std::ptr::null_mut); + let curve = try_or_else!(Curve::from_raw(curve), std::ptr::null_mut); + let hasher = try_or_else!(Hasher::from_repr(hasher), std::ptr::null_mut); + HDNode::try_from(extended_private_key_str, curve, hasher) + .map(|hd_node| TWHDNode(hd_node).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Delete the given HDNode. +/// +/// \param key *non-null* pointer to HDNode. +#[tw_ffi(ty = destructor, class = TWHDNode, name = Delete)] +#[no_mangle] +pub unsafe extern "C" fn tw_hd_node_delete(key: NonnullMut) { + // Take the ownership back to rust and drop the owner. + let _ = TWHDNode::from_ptr(key); +} + +/// Derive a child node from a given hd_node. +/// +/// \param hd_node *non-null* pointer to a hd_node. +/// \param path the path to derive. +/// \return Nullable pointer to HDNode. +#[tw_ffi(ty = static_function, class = TWHDNode, name = DeriveFromPath)] +#[no_mangle] +pub unsafe extern "C" fn tw_hd_node_derive_from_path( + hd_node: Nonnull, + path: Nonnull, + hasher: u32, +) -> NullableMut { + let hd_node_ref = try_or_else!(TWHDNode::from_ptr_as_ref(hd_node), std::ptr::null_mut); + let path_ref = try_or_else!(TWString::from_ptr_as_ref(path), std::ptr::null_mut); + let path_str = try_or_else!(path_ref.as_str(), std::ptr::null_mut); + let hasher = try_or_else!(Hasher::from_repr(hasher), std::ptr::null_mut); + hd_node_ref + .0 + .derive_from_path(path_str, hasher) + .map(|hd_node| TWHDNode(hd_node).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Returns the raw private-key data of a given hd_node. +/// +/// \param hd_node *non-null* pointer to a hd_node. +/// \return byte array. +#[tw_ffi(ty = property, class = TWHDNode, name = PrivateKeyData)] +#[no_mangle] +pub unsafe extern "C" fn tw_hd_node_private_key_data( + hd_node: Nonnull, +) -> NonnullMut { + let hd_node_ref = try_or_else!(TWHDNode::from_ptr_as_ref(hd_node), std::ptr::null_mut); + hd_node_ref + .0 + .private_key_data() + .map(|data| TWData::from(data.to_vec()).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Returns the raw public-key data of a given hd_node. +/// +/// \param hd_node *non-null* pointer to a hd_node. +/// \return byte array. +#[tw_ffi(ty = property, class = TWHDNode, name = PublicKeyData)] +#[no_mangle] +pub unsafe extern "C" fn tw_hd_node_public_key_data( + hd_node: Nonnull, +) -> NonnullMut { + let hd_node_ref = try_or_else!(TWHDNode::from_ptr_as_ref(hd_node), std::ptr::null_mut); + hd_node_ref + .0 + .public_key_data() + .map(|data| TWData::from(data).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Returns the chain code of a given hd_node. +/// +/// \param hd_node *non-null* pointer to a hd_node. +/// \return byte array. +#[tw_ffi(ty = property, class = TWHDNode, name = ChainCode)] +#[no_mangle] +pub unsafe extern "C" fn tw_hd_node_chain_code(hd_node: Nonnull) -> NonnullMut { + let hd_node_ref = try_or_else!(TWHDNode::from_ptr_as_ref(hd_node), std::ptr::null_mut); + hd_node_ref + .0 + .chain_code() + .map(|data| TWData::from(data).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Returns the depth of a given hd_node. +/// +/// \param hd_node *non-null* pointer to a hd_node. +/// \return depth. +#[tw_ffi(ty = property, class = TWHDNode, name = Depth)] +#[no_mangle] +pub unsafe extern "C" fn tw_hd_node_depth(hd_node: Nonnull) -> u8 { + let hd_node_ref = try_or_else!(TWHDNode::from_ptr_as_ref(hd_node), || 0); + hd_node_ref.0.depth().unwrap_or_default() +} + +/// Returns the child number of a given hd_node. +/// +/// \param hd_node *non-null* pointer to a hd_node. +/// \return child number. +#[tw_ffi(ty = property, class = TWHDNode, name = ChildNumber)] +#[no_mangle] +pub unsafe extern "C" fn tw_hd_node_child_number(hd_node: Nonnull) -> u32 { + let hd_node_ref = try_or_else!(TWHDNode::from_ptr_as_ref(hd_node), || 0); + hd_node_ref.0.child_number().unwrap_or_default() +} + +/// Returns the extended private key of a given hd_node. +/// +/// \param hd_node *non-null* pointer to a hd_node. +/// \return extended private key. +#[tw_ffi(ty = static_function, class = TWHDNode, name = ExtendedPrivateKey)] +#[no_mangle] +pub unsafe extern "C" fn tw_hd_node_extended_private_key( + hd_node: Nonnull, + version: u32, + hasher: u32, +) -> NonnullMut { + let hasher = try_or_else!(Hasher::from_repr(hasher), std::ptr::null_mut); + let hd_node_ref = try_or_else!(TWHDNode::from_ptr_as_ref(hd_node), std::ptr::null_mut); + hd_node_ref + .0 + .extended_private_key(version, hasher) + .map(|key| TWString::from(key).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Returns the extended public key of a given hd_node. +/// +/// \param hd_node *non-null* pointer to a hd_node. +/// \return extended public key. +#[tw_ffi(ty = static_function, class = TWHDNode, name = ExtendedPublicKey)] +#[no_mangle] +pub unsafe extern "C" fn tw_hd_node_extended_public_key( + hd_node: Nonnull, + version: u32, + hasher: u32, +) -> NonnullMut { + let hasher = try_or_else!(Hasher::from_repr(hasher), std::ptr::null_mut); + let hd_node_ref = try_or_else!(TWHDNode::from_ptr_as_ref(hd_node), std::ptr::null_mut); + hd_node_ref + .0 + .extended_public_key(version, hasher) + .map(|key| TWString::from(key).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} diff --git a/rust/tw_crypto/src/ffi/crypto_hd_node_public.rs b/rust/tw_crypto/src/ffi/crypto_hd_node_public.rs new file mode 100644 index 00000000000..c161bf2212d --- /dev/null +++ b/rust/tw_crypto/src/ffi/crypto_hd_node_public.rs @@ -0,0 +1,102 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![allow(clippy::missing_safety_doc)] + +use crate::crypto_hd_node::hd_node_public::HDNodePublic; +use tw_hash::hasher::Hasher; +use tw_keypair::tw::Curve; +use tw_macros::tw_ffi; +use tw_memory::ffi::{ + tw_data::TWData, tw_string::TWString, Nonnull, NonnullMut, NullableMut, RawPtrTrait, +}; +use tw_misc::try_or_else; + +pub struct TWHDNodePublic(pub(crate) HDNodePublic); + +impl RawPtrTrait for TWHDNodePublic {} + +impl AsRef for TWHDNodePublic { + fn as_ref(&self) -> &HDNodePublic { + &self.0 + } +} + +/// Create a HDNodePublic from an extended public key. +/// +/// \param extended_public_key *non-null* string. +/// \param curve the curve to use. +/// \param hasher the hasher to use. +/// \return Nullable pointer to HDNodePublic. +#[tw_ffi(ty = constructor, class = TWHDNodePublic, name = CreateWithExtendedPublicKey)] +#[no_mangle] +pub unsafe extern "C" fn tw_hd_node_public_create_with_extended_public_key( + extended_public_key: Nonnull, + curve: u32, + hasher: u32, +) -> NullableMut { + let extended_public_key_ref = try_or_else!( + TWString::from_ptr_as_ref(extended_public_key), + std::ptr::null_mut + ); + let extended_public_key_str = + try_or_else!(extended_public_key_ref.as_str(), std::ptr::null_mut); + let curve = try_or_else!(Curve::from_raw(curve), std::ptr::null_mut); + let hasher = try_or_else!(Hasher::from_repr(hasher), std::ptr::null_mut); + HDNodePublic::try_from(extended_public_key_str, curve, hasher) + .map(|hd_node| TWHDNodePublic(hd_node).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Delete the given HDNode. +/// +/// \param key *non-null* pointer to HDNode. +#[tw_ffi(ty = destructor, class = TWHDNodePublic, name = Delete)] +#[no_mangle] +pub unsafe extern "C" fn tw_hd_node_public_delete(key: NonnullMut) { + // Take the ownership back to rust and drop the owner. + let _ = TWHDNodePublic::from_ptr(key); +} + +/// Derive a child key for a particular path. +/// +/// \param hd_node *non-null* pointer to HDNodePublic. +/// \param path *non-null* string. +/// \return Nullable pointer to HDNodePublic. +#[tw_ffi(ty = static_function, class = TWHDNodePublic, name = DeriveFromPath)] +#[no_mangle] +pub unsafe extern "C" fn tw_hd_node_public_derive_from_path( + hd_node: Nonnull, + path: Nonnull, + hasher: u32, +) -> NullableMut { + let hd_node_ref = try_or_else!(TWHDNodePublic::from_ptr_as_ref(hd_node), std::ptr::null_mut); + let path_ref = try_or_else!(TWString::from_ptr_as_ref(path), std::ptr::null_mut); + let path_str = try_or_else!(path_ref.as_str(), std::ptr::null_mut); + let hasher = try_or_else!(Hasher::from_repr(hasher), std::ptr::null_mut); + hd_node_ref + .0 + .derive_from_path(path_str, hasher) + .map(|hd_node| TWHDNodePublic(hd_node).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} + +/// Get the public key data. +/// +/// \param hd_node *non-null* pointer to HDNodePublic. +/// \return *non-null* pointer to TWData. +#[tw_ffi(ty = property, class = TWHDNodePublic, name = PublicKeyData)] +#[no_mangle] +pub unsafe extern "C" fn tw_hd_node_public_public_key_data( + hd_node: Nonnull, +) -> NonnullMut { + let hd_node_ref = try_or_else!(TWHDNodePublic::from_ptr_as_ref(hd_node), std::ptr::null_mut); + hd_node_ref + .0 + .public_key_data() + .map(|data| TWData::from(data).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} diff --git a/rust/tw_crypto/src/ffi/mod.rs b/rust/tw_crypto/src/ffi/mod.rs index 3b79998b9bd..fa8a5064491 100644 --- a/rust/tw_crypto/src/ffi/mod.rs +++ b/rust/tw_crypto/src/ffi/mod.rs @@ -6,6 +6,8 @@ pub mod crypto_aes_cbc; pub mod crypto_aes_ctr; +pub mod crypto_hd_node; +pub mod crypto_hd_node_public; pub mod crypto_mnemonic; pub mod crypto_pbkdf2; pub mod crypto_scrypt; diff --git a/rust/tw_crypto/src/lib.rs b/rust/tw_crypto/src/lib.rs index 46958f5f9ca..5178982a713 100644 --- a/rust/tw_crypto/src/lib.rs +++ b/rust/tw_crypto/src/lib.rs @@ -6,6 +6,7 @@ pub mod crypto_aes_cbc; pub mod crypto_aes_ctr; +pub mod crypto_hd_node; pub mod crypto_mnemonic; pub mod crypto_pbkdf2; pub mod crypto_scrypt; diff --git a/rust/tw_crypto/tests/bip39_vectors.json b/rust/tw_crypto/tests/bip39_vectors.json new file mode 100644 index 00000000000..c15add0e28e --- /dev/null +++ b/rust/tw_crypto/tests/bip39_vectors.json @@ -0,0 +1,148 @@ +{ + "english": [ + [ + "00000000000000000000000000000000", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04", + "xprv9s21ZrQH143K3h3fDYiay8mocZ3afhfULfb5GX8kCBdno77K4HiA15Tg23wpbeF1pLfs1c5SPmYHrEpTuuRhxMwvKDwqdKiGJS9XFKzUsAF" + ], + [ + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + "legal winner thank year wave sausage worth useful legal winner thank yellow", + "2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1296106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607", + "xprv9s21ZrQH143K2gA81bYFHqU68xz1cX2APaSq5tt6MFSLeXnCKV1RVUJt9FWNTbrrryem4ZckN8k4Ls1H6nwdvDTvnV7zEXs2HgPezuVccsq" + ], + [ + "80808080808080808080808080808080", + "letter advice cage absurd amount doctor acoustic avoid letter advice cage above", + "d71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f12eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8", + "xprv9s21ZrQH143K2shfP28KM3nr5Ap1SXjz8gc2rAqqMEynmjt6o1qboCDpxckqXavCwdnYds6yBHZGKHv7ef2eTXy461PXUjBFQg6PrwY4Gzq" + ], + [ + "ffffffffffffffffffffffffffffffff", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", + "ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a13332572917f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069", + "xprv9s21ZrQH143K2V4oox4M8Zmhi2Fjx5XK4Lf7GKRvPSgydU3mjZuKGCTg7UPiBUD7ydVPvSLtg9hjp7MQTYsW67rZHAXeccqYqrsx8LcXnyd" + ], + [ + "000000000000000000000000000000000000000000000000", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent", + "035895f2f481b1b0f01fcf8c289c794660b289981a78f8106447707fdd9666ca06da5a9a565181599b79f53b844d8a71dd9f439c52a3d7b3e8a79c906ac845fa", + "xprv9s21ZrQH143K3mEDrypcZ2usWqFgzKB6jBBx9B6GfC7fu26X6hPRzVjzkqkPvDqp6g5eypdk6cyhGnBngbjeHTe4LsuLG1cCmKJka5SMkmU" + ], + [ + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will", + "f2b94508732bcbacbcc020faefecfc89feafa6649a5491b8c952cede496c214a0c7b3c392d168748f2d4a612bada0753b52a1c7ac53c1e93abd5c6320b9e95dd", + "xprv9s21ZrQH143K3Lv9MZLj16np5GzLe7tDKQfVusBni7toqJGcnKRtHSxUwbKUyUWiwpK55g1DUSsw76TF1T93VT4gz4wt5RM23pkaQLnvBh7" + ], + [ + "808080808080808080808080808080808080808080808080", + "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always", + "107d7c02a5aa6f38c58083ff74f04c607c2d2c0ecc55501dadd72d025b751bc27fe913ffb796f841c49b1d33b610cf0e91d3aa239027f5e99fe4ce9e5088cd65", + "xprv9s21ZrQH143K3VPCbxbUtpkh9pRG371UCLDz3BjceqP1jz7XZsQ5EnNkYAEkfeZp62cDNj13ZTEVG1TEro9sZ9grfRmcYWLBhCocViKEJae" + ], + [ + "ffffffffffffffffffffffffffffffffffffffffffffffff", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when", + "0cd6e5d827bb62eb8fc1e262254223817fd068a74b5b449cc2f667c3f1f985a76379b43348d952e2265b4cd129090758b3e3c2c49103b5051aac2eaeb890a528", + "xprv9s21ZrQH143K36Ao5jHRVhFGDbLP6FCx8BEEmpru77ef3bmA928BxsqvVM27WnvvyfWywiFN8K6yToqMaGYfzS6Db1EHAXT5TuyCLBXUfdm" + ], + [ + "0000000000000000000000000000000000000000000000000000000000000000", + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art", + "bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8", + "xprv9s21ZrQH143K32qBagUJAMU2LsHg3ka7jqMcV98Y7gVeVyNStwYS3U7yVVoDZ4btbRNf4h6ibWpY22iRmXq35qgLs79f312g2kj5539ebPM" + ], + [ + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", + "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title", + "bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146ad717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87", + "xprv9s21ZrQH143K3Y1sd2XVu9wtqxJRvybCfAetjUrMMco6r3v9qZTBeXiBZkS8JxWbcGJZyio8TrZtm6pkbzG8SYt1sxwNLh3Wx7to5pgiVFU" + ], + [ + "8080808080808080808080808080808080808080808080808080808080808080", + "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless", + "c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61af0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f", + "xprv9s21ZrQH143K3CSnQNYC3MqAAqHwxeTLhDbhF43A4ss4ciWNmCY9zQGvAKUSqVUf2vPHBTSE1rB2pg4avopqSiLVzXEU8KziNnVPauTqLRo" + ], + [ + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote", + "dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad", + "xprv9s21ZrQH143K2WFF16X85T2QCpndrGwx6GueB72Zf3AHwHJaknRXNF37ZmDrtHrrLSHvbuRejXcnYxoZKvRquTPyp2JiNG3XcjQyzSEgqCB" + ], + [ + "9e885d952ad362caeb4efe34a8e91bd2", + "ozone drill grab fiber curtain grace pudding thank cruise elder eight picnic", + "274ddc525802f7c828d8ef7ddbcdc5304e87ac3535913611fbbfa986d0c9e5476c91689f9c8a54fd55bd38606aa6a8595ad213d4c9c9f9aca3fb217069a41028", + "xprv9s21ZrQH143K2oZ9stBYpoaZ2ktHj7jLz7iMqpgg1En8kKFTXJHsjxry1JbKH19YrDTicVwKPehFKTbmaxgVEc5TpHdS1aYhB2s9aFJBeJH" + ], + [ + "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b", + "gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog", + "628c3827a8823298ee685db84f55caa34b5cc195a778e52d45f59bcf75aba68e4d7590e101dc414bc1bbd5737666fbbef35d1f1903953b66624f910feef245ac", + "xprv9s21ZrQH143K3uT8eQowUjsxrmsA9YUuQQK1RLqFufzybxD6DH6gPY7NjJ5G3EPHjsWDrs9iivSbmvjc9DQJbJGatfa9pv4MZ3wjr8qWPAK" + ], + [ + "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c", + "hamster diagram private dutch cause delay private meat slide toddler razor book happy fancy gospel tennis maple dilemma loan word shrug inflict delay length", + "64c87cde7e12ecf6704ab95bb1408bef047c22db4cc7491c4271d170a1b213d20b385bc1588d9c7b38f1b39d415665b8a9030c9ec653d75e65f847d8fc1fc440", + "xprv9s21ZrQH143K2XTAhys3pMNcGn261Fi5Ta2Pw8PwaVPhg3D8DWkzWQwjTJfskj8ofb81i9NP2cUNKxwjueJHHMQAnxtivTA75uUFqPFeWzk" + ], + [ + "c0ba5a8e914111210f2bd131f3d5e08d", + "scheme spot photo card baby mountain device kick cradle pact join borrow", + "ea725895aaae8d4c1cf682c1bfd2d358d52ed9f0f0591131b559e2724bb234fca05aa9c02c57407e04ee9dc3b454aa63fbff483a8b11de949624b9f1831a9612", + "xprv9s21ZrQH143K3FperxDp8vFsFycKCRcJGAFmcV7umQmcnMZaLtZRt13QJDsoS5F6oYT6BB4sS6zmTmyQAEkJKxJ7yByDNtRe5asP2jFGhT6" + ], + [ + "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3", + "horn tenant knee talent sponsor spell gate clip pulse soap slush warm silver nephew swap uncle crack brave", + "fd579828af3da1d32544ce4db5c73d53fc8acc4ddb1e3b251a31179cdb71e853c56d2fcb11aed39898ce6c34b10b5382772db8796e52837b54468aeb312cfc3d", + "xprv9s21ZrQH143K3R1SfVZZLtVbXEB9ryVxmVtVMsMwmEyEvgXN6Q84LKkLRmf4ST6QrLeBm3jQsb9gx1uo23TS7vo3vAkZGZz71uuLCcywUkt" + ], + [ + "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863", + "panda eyebrow bullet gorilla call smoke muffin taste mesh discover soft ostrich alcohol speed nation flash devote level hobby quick inner drive ghost inside", + "72be8e052fc4919d2adf28d5306b5474b0069df35b02303de8c1729c9538dbb6fc2d731d5f832193cd9fb6aeecbc469594a70e3dd50811b5067f3b88b28c3e8d", + "xprv9s21ZrQH143K2WNnKmssvZYM96VAr47iHUQUTUyUXH3sAGNjhJANddnhw3i3y3pBbRAVk5M5qUGFr4rHbEWwXgX4qrvrceifCYQJbbFDems" + ], + [ + "23db8160a31d3e0dca3688ed941adbf3", + "cat swing flag economy stadium alone churn speed unique patch report train", + "deb5f45449e615feff5640f2e49f933ff51895de3b4381832b3139941c57b59205a42480c52175b6efcffaa58a2503887c1e8b363a707256bdd2b587b46541f5", + "xprv9s21ZrQH143K4G28omGMogEoYgDQuigBo8AFHAGDaJdqQ99QKMQ5J6fYTMfANTJy6xBmhvsNZ1CJzRZ64PWbnTFUn6CDV2FxoMDLXdk95DQ" + ], + [ + "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0", + "light rule cinnamon wrap drastic word pride squirrel upgrade then income fatal apart sustain crack supply proud access", + "4cbdff1ca2db800fd61cae72a57475fdc6bab03e441fd63f96dabd1f183ef5b782925f00105f318309a7e9c3ea6967c7801e46c8a58082674c860a37b93eda02", + "xprv9s21ZrQH143K3wtsvY8L2aZyxkiWULZH4vyQE5XkHTXkmx8gHo6RUEfH3Jyr6NwkJhvano7Xb2o6UqFKWHVo5scE31SGDCAUsgVhiUuUDyh" + ], + [ + "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad", + "all hour make first leader extend hole alien behind guard gospel lava path output census museum junior mass reopen famous sing advance salt reform", + "26e975ec644423f4a4c4f4215ef09b4bd7ef924e85d1d17c4cf3f136c2863cf6df0a475045652c57eb5fb41513ca2a2d67722b77e954b4b3fc11f7590449191d", + "xprv9s21ZrQH143K3rEfqSM4QZRVmiMuSWY9wugscmaCjYja3SbUD3KPEB1a7QXJoajyR2T1SiXU7rFVRXMV9XdYVSZe7JoUXdP4SRHTxsT1nzm" + ], + [ + "f30f8c1da665478f49b001d94c5fc452", + "vessel ladder alter error federal sibling chat ability sun glass valve picture", + "2aaa9242daafcee6aa9d7269f17d4efe271e1b9a529178d7dc139cd18747090bf9d60295d0ce74309a78852a9caadf0af48aae1c6253839624076224374bc63f", + "xprv9s21ZrQH143K2QWV9Wn8Vvs6jbqfF1YbTCdURQW9dLFKDovpKaKrqS3SEWsXCu6ZNky9PSAENg6c9AQYHcg4PjopRGGKmdD313ZHszymnps" + ], + [ + "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05", + "scissors invite lock maple supreme raw rapid void congress muscle digital elegant little brisk hair mango congress clump", + "7b4a10be9d98e6cba265566db7f136718e1398c71cb581e1b2f464cac1ceedf4f3e274dc270003c670ad8d02c4558b2f8e39edea2775c9e232c7cb798b069e88", + "xprv9s21ZrQH143K4aERa2bq7559eMCCEs2QmmqVjUuzfy5eAeDX4mqZffkYwpzGQRE2YEEeLVRoH4CSHxianrFaVnMN2RYaPUZJhJx8S5j6puX" + ], + [ + "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f", + "void come effort suffer camp survey warrior heavy shoot primary clutch crush open amazing screen patrol group space point ten exist slush involve unfold", + "01f5bced59dec48e362f2c45b5de68b9fd6c92c6634f44d6d40aab69056506f0e35524a518034ddc1192e1dacd32c1ed3eaa3c3b131c88ed8e7e54c49a5d0998", + "xprv9s21ZrQH143K39rnQJknpH1WEPFJrzmAqqasiDcVrNuk926oizzJDDQkdiTvNPr2FYDYzWgiMiC63YmfPAa2oPyNB23r2g7d1yiK6WpqaQS" + ] + ] +} \ No newline at end of file diff --git a/rust/tw_crypto/tests/extended_private_key.rs b/rust/tw_crypto/tests/extended_private_key.rs new file mode 100644 index 00000000000..92c9fc25312 --- /dev/null +++ b/rust/tw_crypto/tests/extended_private_key.rs @@ -0,0 +1,768 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use bip32::{ChildNumber, DerivationPath, Prefix, XPub}; +use std::str::FromStr; +use tw_crypto::crypto_hd_node::ed25519::cardano::cardano_staking_derivation_path; +use tw_crypto::crypto_hd_node::error::{Error, Result}; +use tw_crypto::crypto_hd_node::extended_key::bip32_private_key::BIP32PrivateKey; +use tw_crypto::crypto_hd_node::extended_key::bip32_public_key::BIP32PublicKey; +use tw_crypto::crypto_hd_node::extended_key::extended_private_key::ExtendedPrivateKey; +use tw_crypto::crypto_hd_node::extended_key::extended_private_key::{decode_base58, encode_base58}; +use tw_crypto::crypto_hd_node::hd_node::{ + XPrvCardano, XPrvCurve25519Waves, XPrvEd25519, XPrvEd25519Blake2bNano, XPrvNist256p1, + XPrvSecp256k1, XPrvZilliqaSchnorr, +}; +use tw_crypto::crypto_hd_node::hd_node_public::XPubSecp256k1; +use tw_crypto::crypto_mnemonic::mnemonic::Mnemonic; +use tw_encoding::hex; +use tw_hash::hasher::Hasher; +use tw_keypair::tw::Curve; +use tw_misc::traits::{ToBytesVec, ToBytesZeroizing}; +use zeroize::Zeroizing; + +#[test] +fn test_from_seed() { + let seed = Mnemonic::to_seed( + "tiny escape drive pupil flavor endless love walk gadget match filter luxury", + "", + ); + assert_eq!(hex::encode(seed, false), "d430216f5b506dfd281d6ff6e92150d205868923df00774bc301e5ffdc2f4d1ad38a602017ddea6fc7d6315345d8b9cadbd8213ed2ffce5dfc550fa918665eb8"); + let xprv = XPrvSecp256k1::new(&seed); + assert!(xprv.is_ok()); +} + +#[test] +fn test_extended_private_key() { + let seed = Mnemonic::to_seed("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal", ""); + let xprv = XPrvSecp256k1::new(&seed).unwrap(); + + let child_xprv = xprv + .derive_from_path( + &DerivationPath::from_str("m/44'/0'/0'").unwrap(), + Hasher::Sha256ripemd, + ) + .unwrap(); + let child_extended_key = child_xprv.to_string(Prefix::ZPRV).unwrap().to_string(); + assert_eq!(child_extended_key, "zprvAcwsTZNaY1f7rfwsy5GseSDStYBrxwtsBZDkb3iyuQUs4NF6n58BuH7Xj54RuaSCWtU5CiQzuYQgFgqr1HokgKcVAeGeXokhJUAJeP3VmvY"); + + let child_extended_key = child_xprv.to_extended_key(Prefix::ZPRV).unwrap(); + assert_eq!(child_extended_key.to_string(), "zprvAcwsTZNaY1f7rfwsy5GseSDStYBrxwtsBZDkb3iyuQUs4NF6n58BuH7Xj54RuaSCWtU5CiQzuYQgFgqr1HokgKcVAeGeXokhJUAJeP3VmvY"); + + let encoded = encode_base58(&child_extended_key, Hasher::Sha256d).unwrap(); + assert_eq!(encoded, child_extended_key.to_string()); + + let decoded = decode_base58(&child_extended_key.to_string(), Hasher::Sha256d).unwrap(); + assert_eq!(decoded.to_string(), child_extended_key.to_string()); + + let decoded_failed = decode_base58(&child_extended_key.to_string(), Hasher::Sha256ripemd); + assert!(decoded_failed.is_err()); + + let decode_invalid = decode_base58("invalid", Hasher::Sha256d); + assert!(decode_invalid.is_err()); + + let decode_invalid = decode_base58("ea", Hasher::Sha256d); + assert!(decode_invalid.is_err()); + + let child_xprv = xprv + .derive_from_path( + &DerivationPath::from_str("m/44'/0'/1'").unwrap(), + Hasher::Sha256ripemd, + ) + .unwrap(); + let child_extended_key = child_xprv.to_extended_key(Prefix::ZPRV).unwrap(); + assert_eq!(child_extended_key.to_string(), "zprvAcwsTZNaY1f7sifgNNgdNa4P9mPtyg3zRVgwkx2qF9Sn7F255MzP6Zyumn6bgV5xuoS8ZrDvjzE7APcFSacXdzFYpGvyybb1bnAoh5nHxpn"); +} + +#[test] +fn test_extended_private_key_mtpv() { + let xprv = XPrvSecp256k1::from_base58("Mtpv7SkyM349Svcf1WiRtB5hC91ZZkVsGuv3kz1V7tThGxBFBzBLFnw6LpaSvwpHHuy8dAfMBqpBvaSAHzbffvhj2TwfojQxM7Ppm3CzW67AFL5", Hasher::Sha256d).unwrap(); + + let path = "m/0/4"; + let child_xprv = xprv + .derive_from_path( + &DerivationPath::from_str(path).unwrap(), + Hasher::Sha256ripemd, + ) + .unwrap(); + + let public_key = child_xprv.public_key(); + assert_eq!( + hex::encode(public_key.to_bytes(), false), + "02c36f9c3051e9cfbb196ecc35311f3ad705ea6798ffbe6b039e70f6bd047e6f2c" + ); +} + +#[test] +fn test_extended_public_key() { + let seed = Mnemonic::to_seed("ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal", ""); + let xprv = XPrvSecp256k1::new(&seed).unwrap(); + + let extended_key = xprv.to_extended_key(Prefix::ZPRV).unwrap(); + let xpub = XPubSecp256k1::try_from(extended_key); + assert!(xpub.is_err()); + + let child_xprv = xprv + .derive_from_path( + &DerivationPath::from_str("m/44'/0'/0'").unwrap(), + Hasher::Sha256ripemd, + ) + .unwrap(); + let child_extended_key = child_xprv.public_key().to_extended_key(Prefix::ZPUB); + assert_eq!(child_extended_key.to_string(), "zpub6qwDs4uUNPDR5A2M56ot1aABSa2MNQciYn9MPS8bTk1qwAaFKcSST5S1aLidvPp9twqpaumG7vikR2vHhBXjp5oGgHyMvWK3AtUkfeEgyns"); + + let child_xprv = xprv + .derive_from_path( + &DerivationPath::from_str("m/44'/0'/1'").unwrap(), + Hasher::Sha256ripemd, + ) + .unwrap(); + let child_extended_key = child_xprv.public_key().to_extended_key(Prefix::ZPUB); + assert_eq!(child_extended_key.to_string(), "zpub6qwDs4uUNPDR6Ck9UQDdji17hoEPP8mqnicYZLSSoUykz3MDcuJdeNJPd3BozqEafeLZkegWqzAvkgA4JZZ5tTN2rDpGKfk54essyfx1eZP"); +} + +#[test] +fn test_get_key_by_curve() { + let deriv_path = "m/44'/539'/0'/0/0"; + let mnemonic = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + let seed = Mnemonic::to_seed(mnemonic, ""); + + // Test with Secp256k1 curve + { + let xprv = XPrvSecp256k1::new(&seed).unwrap(); + let path = DerivationPath::from_str(deriv_path).unwrap(); + let xprv = xprv.derive_from_path(&path, Hasher::Sha256ripemd).unwrap(); + let private_key = xprv.private_key().to_zeroizing_vec(); + assert_eq!( + hex::encode(private_key, false), + "4fb8657d6464adcaa086d6758d7f0b6b6fc026c98dc1671fcc6460b5a74abc62" + ); + } + + // Test with NIST256p1 curve + { + let xprv = XPrvNist256p1::new(&seed).unwrap(); + let path = DerivationPath::from_str(deriv_path).unwrap(); + let xprv = xprv.derive_from_path(&path, Hasher::Sha256ripemd).unwrap(); + + let private_key = xprv.to_zeroizing_vec(); + assert_eq!( + hex::encode(private_key, false), + "a13df52d5a5b438bbf921bbf86276e4347fe8e2f2ed74feaaee12b77d6d26f86" + ); + } +} + +#[test] +fn test_derive_xpub_pub_vs_priv_pub() { + // Test different routes for deriving address from mnemonic, result should be the same: + // - Direct: mnemonic -> seed -> privateKey -> publicKey -> address + // - Extended Public: mnemonic -> seed -> zpub -> publicKey -> address + // - Extended Private: mnemonic -> seed -> zpriv -> privateKey -> publicKey -> address + + let mnemonic = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + let seed = Mnemonic::to_seed(mnemonic, ""); + let xprv = XPrvSecp256k1::new(&seed).unwrap(); + + let deriv_path1 = DerivationPath::from_str("m/84'/0'/0'/0/0").unwrap(); + let deriv_path2 = DerivationPath::from_str("m/84'/0'/0'/0/2").unwrap(); + let expected_public_key1 = "02df9ef2a7a5552765178b181e1e1afdefc7849985c7dfe9647706dd4fa40df6ac"; + let expected_public_key2 = "031e1f64d2f6768dccb6814545b2e2d58e26ad5f91b7cbaffe881ed572c65060db"; + + // -> privateKey -> publicKey + { + let xprv1 = xprv + .derive_from_path(&deriv_path1, Hasher::Sha256ripemd) + .unwrap(); + assert_eq!( + hex::encode(xprv1.public_key().to_bytes(), false), + expected_public_key1 + ); + } + { + let xprv2 = xprv + .derive_from_path(&deriv_path2, Hasher::Sha256ripemd) + .unwrap(); + assert_eq!( + hex::encode(xprv2.public_key().to_bytes(), false), + expected_public_key2 + ); + } + + // zpub -> publicKey + let account_path = DerivationPath::from_str("m/84'/0'/0'").unwrap(); + let account_xprv = xprv + .derive_from_path(&account_path, Hasher::Sha256ripemd) + .unwrap(); + let zpub = account_xprv + .public_key() + .to_extended_key(Prefix::ZPUB) + .to_string(); + assert_eq!(zpub, "zpub6rNUNtxSa9Gxvm4Bdxf1MPMwrvkzwDx6vP96Hkzw3jiQKdg3fhXBStxjn12YixQB8h88B3RMSRscRstf9AEVaYr3MAqVBEWBDuEJU4PGaT9"); + + let deriv_path1 = DerivationPath::from_str("m/0/0").unwrap(); + { + let public_key1 = XPub::from_str(&zpub).unwrap(); + let public_key1 = deriv_path1.iter().fold(public_key1, |key, child_num| { + key.derive_child(child_num).unwrap() //_or(key) + }); + assert_eq!( + hex::encode(public_key1.to_bytes(), false), + expected_public_key1 + ); + } + let deriv_path2 = DerivationPath::from_str("m/0/2").unwrap(); + { + let public_key2 = XPub::from_str(&zpub).unwrap(); + let public_key2 = deriv_path2.iter().fold(public_key2, |key, child_num| { + key.derive_child(child_num).unwrap() //_or(key) + }); + assert_eq!( + hex::encode(public_key2.to_bytes(), false), + expected_public_key2 + ); + } + + // zpriv -> privateKey -> publicKey + let zpriv = account_xprv + .to_extended_key(Prefix::ZPRV) + .unwrap() + .to_string(); + assert_eq!(zpriv, "zprvAdP7yPRYjmifiGyiXw7zzFRDJtvWXmEFZADVVNbKVQBRSqLu8ACvu6eFvhrnnw4QwdTD8PUVa48MguwiPTiyfn85zWx9iA5MYy4Eufu5bas"); + + let deriv_path1 = DerivationPath::from_str("m/0/0").unwrap(); + { + let private_key1 = XPrvSecp256k1::from_base58(&zpriv, Hasher::Sha256d).unwrap(); + let private_key1 = deriv_path1.iter().fold(private_key1, |key, child_num| { + key.derive_child(child_num, Hasher::Sha256ripemd).unwrap() //_or(key) + }); + assert_eq!( + hex::encode(private_key1.public_key().to_bytes(), false), + expected_public_key1 + ); + } + + let deriv_path2 = DerivationPath::from_str("m/0/2").unwrap(); + { + let private_key2 = XPrvSecp256k1::from_base58(&zpriv, Hasher::Sha256d).unwrap(); + let private_key2 = deriv_path2.iter().fold(private_key2, |key, child_num| { + key.derive_child(child_num, Hasher::Sha256ripemd).unwrap() //_or(key) + }); + assert_eq!( + hex::encode(private_key2.public_key().to_bytes(), false), + expected_public_key2 + ); + } +} + +#[test] +fn test_aptos_key() { + let mnemonic = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + let seed = Mnemonic::to_seed(mnemonic, ""); + let xprv = XPrvEd25519::new(&seed).unwrap(); + + let deriv_path = DerivationPath::from_str("m/44'/637'/0'/0'/0'").unwrap(); + let xprv = xprv + .derive_from_path(&deriv_path, Hasher::Sha256ripemd) + .unwrap(); + assert_eq!( + hex::encode(xprv.private_key().to_zeroizing_vec(), false), + "7f2634c0e2414a621e96e39c41d09021700cee12ee43328ed094c5580cd0bd6f" + ); + + let public_key = xprv.public_key(); + assert_eq!( + hex::encode(public_key.to_vec(), false), + "633e5c7e355bdd484706436ce1f06fdf280bd7c2229a7f9b6489684412c6967c" + ); + + let child_public_key = public_key.derive_child(bip32::ChildNumber(0), Hasher::Sha256ripemd); + assert!(child_public_key.is_ok()); +} + +#[test] +fn test_cardano_key() { + let mnemonic = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + let entropy = Mnemonic::parse(mnemonic).unwrap().to_entropy(); + let xprv = XPrvCardano::new(&entropy).unwrap(); + + let deriv_path = DerivationPath::from_str("m/44'/637'/0'/0'/0").unwrap(); + let xprv = xprv + .derive_from_path(&deriv_path, Hasher::Sha256ripemd) + .unwrap(); + assert_eq!( + hex::encode(xprv.private_key().to_zeroizing_vec(), false), + "680113743091be93bcdab47ec2f6a2e3c710812f3f051ebb84ac70aa15a14952c8d771b5dd2726467412ed62c37d6c819c36d1dba83991a8585c31bb4790f2cde5232f0770ce99adfc7e6ec1a5270f52d6435c30ceb51415258d1eaccd28b5fe" + ); + + let extended_key = xprv.to_extended_key(Prefix::ZPRV); + assert!(extended_key.is_err()); + + let public_key = xprv.public_key(); + assert_eq!( + hex::encode(public_key.to_vec(), false), + "797a077d4f2cca772b45fa67ada88502000470adf7f81fcb578357a73649fc76e5232f0770ce99adfc7e6ec1a5270f52d6435c30ceb51415258d1eaccd28b5fe" + ); + + let child_public_key = public_key.derive_child(bip32::ChildNumber(0), Hasher::Sha256ripemd); + assert!(child_public_key.is_ok()); +} + +#[test] +fn test_nano_key() { + let mnemonic = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + let seed = Mnemonic::to_seed(mnemonic, ""); + let xprv = XPrvEd25519Blake2bNano::new(&seed).unwrap(); + assert_eq!( + hex::encode(xprv.private_key().to_zeroizing_vec(), false), + "d258c2521f7802b8e83c32f2cc97bd06b69747847390c5e247a3d19faa74202e" + ); + let xpub = xprv.public_key(); + assert_eq!( + hex::encode(xpub.to_bytes(), false), + "9833ff5684764ca31955727966df954be334ea051ad8c285eea6e4fbaa549001" + ); + + let deriv_path = DerivationPath::from_str("m/44'/637'/0'/0'").unwrap(); + let xprv = xprv + .derive_from_path(&deriv_path, Hasher::Sha256ripemd) + .unwrap(); + assert_eq!( + hex::encode(xprv.private_key().to_zeroizing_vec(), false), + "ffd43b8b4273e69a8278b9dbb4ac724134a878adc82927e503145c935b432959" + ); + + let public_key = xprv.public_key(); + assert_eq!( + hex::encode(public_key.to_bytes(), false), + "37d29aff891f03abaa1ea1989cff6de0f46bd677f3ca7b3abb6e7f1c03786540" + ); + + let child_public_key = public_key.derive_child(bip32::ChildNumber(0), Hasher::Sha256ripemd); + assert!(child_public_key.is_ok()); +} + +#[test] +fn test_waves_key() { + let mnemonic = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + let seed = Mnemonic::to_seed(mnemonic, ""); + let xprv = XPrvCurve25519Waves::new(&seed).unwrap(); + assert_eq!( + hex::encode(xprv.private_key().to_zeroizing_vec(), false), + "7374826cbd731cf656c11b3fdd458084288f655c8fd4056175996655d0fda4c9" + ); + let xpub = xprv.public_key(); + assert_eq!( + hex::encode(xpub.to_bytes(), false), + "b6c00ffdacb469da62062a1dc8218a733a61720ab0942ba3625194281faf7d3d" + ); + + let deriv_path = DerivationPath::from_str("m/44'/5741564'/0'/0'/0").unwrap(); + let xprv = xprv.derive_from_path(&deriv_path, Hasher::Sha256ripemd); + assert!(xprv.is_ok()); + + let child_public_key = xprv + .unwrap() + .public_key() + .derive_child(bip32::ChildNumber(0), Hasher::Sha256ripemd); + assert!(child_public_key.is_ok()); +} + +#[test] +fn test_zilliqa_schnorr_key() { + let mnemonic = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + let seed = Mnemonic::to_seed(mnemonic, ""); + let xprv = XPrvZilliqaSchnorr::new(&seed).unwrap(); + assert_eq!( + hex::encode(xprv.private_key().to_zeroizing_vec(), false), + "d1b2b553b053f278d510a8494ead811252b1d5ec0da4434d0997a75de92bcea9" + ); + let xpub = xprv.public_key(); + assert_eq!( + hex::encode(xpub.to_bytes(), false), + "02f54cd391076f956b1cfc37cf182c18373f7c1566408c1748132cf4e782498e19" + ); + + let deriv_path = DerivationPath::from_str("m/44'/637'/0'/0'/0").unwrap(); + let xprv = xprv + .derive_from_path(&deriv_path, Hasher::Sha256ripemd) + .unwrap(); + assert_eq!( + hex::encode(xprv.private_key().to_zeroizing_vec(), false), + "4fc45a32e714677a8d3fbed23a8e1afbba8decbf60d479149129342dc894d2a4" + ); + + let public_key = xprv.public_key(); + assert_eq!( + hex::encode(public_key.to_bytes(), false), + "03758382b7e39cf4790a6b4388254e36fa7aedd48e4595ad219687a8495c27d364" + ); + + let child_public_key = public_key.derive_child(bip32::ChildNumber(0), Hasher::Sha256ripemd); + assert!(child_public_key.is_ok()); +} + +#[test] +fn test_nist256p1_key_derivation_failure() { + let private_key = tw_keypair::ecdsa::nist256p1::PrivateKey::from_str( + "a13df52d5a5b438bbf921bbf86276e4347fe8e2f2ed74feaaee12b77d6d26f86", + ) + .unwrap(); + let child_key = private_key.derive_child(&[0x00], ChildNumber(0)); + assert!(child_key.is_err()); + + let public_key = tw_keypair::ecdsa::nist256p1::PublicKey::try_from( + "02d03f2d72c850abe7fbde0507c661a9c940808f751d6d1c08f1c632b632af52ce", + ) + .unwrap(); + let child_key = public_key.derive_child(&[0x00], ChildNumber::new(0, true).unwrap()); + assert!(child_key.is_err()); +} + +#[test] +fn test_secp256k1_key_derivation_failure() { + let private_key = tw_keypair::ecdsa::secp256k1::PrivateKey::from_str( + "4fb8657d6464adcaa086d6758d7f0b6b6fc026c98dc1671fcc6460b5a74abc62", + ) + .unwrap(); + let child_key = private_key.derive_child(&[0x50], ChildNumber(0)); + assert!(child_key.is_err()); + + let public_key = tw_keypair::ecdsa::secp256k1::PublicKey::try_from( + "023fc76c1210da890c598a3868f267d7d6a2c2c1fa4c60e6e105c9ef9f9f6e6532", + ) + .unwrap(); + let child_key = public_key.derive_child(&[0x00], ChildNumber::new(0, true).unwrap()); + assert!(child_key.is_err()); +} + +#[test] +fn test_ed25519_key_derivation() { + let private_key = tw_keypair::ed25519::sha512::PrivateKey::from_str( + "7f2634c0e2414a621e96e39c41d09021700cee12ee43328ed094c5580cd0bd6f", + ) + .unwrap(); + let child_key = private_key.derive_child(&[0x00], ChildNumber::new(0, false).unwrap()); + assert!(child_key.is_ok()); + + let child_key = private_key.derive_child(&[0x00], ChildNumber::new(0, true).unwrap()); + assert!(child_key.is_err()); + + let public_key = tw_keypair::ed25519::sha512::PublicKey::try_from( + "633e5c7e355bdd484706436ce1f06fdf280bd7c2229a7f9b6489684412c6967c", + ) + .unwrap(); + let child_key = public_key.derive_child(&[0x00], ChildNumber::new(0, true).unwrap()); + assert!(child_key.is_err()); +} + +#[test] +fn test_cardano_key_derivation() { + let private_key = tw_keypair::ed25519::cardano::ExtendedPrivateKey::from_str("680113743091be93bcdab47ec2f6a2e3c710812f3f051ebb84ac70aa15a14952c8d771b5dd2726467412ed62c37d6c819c36d1dba83991a8585c31bb4790f2cde5232f0770ce99adfc7e6ec1a5270f52d6435c30ceb51415258d1eaccd28b5fe").unwrap(); + let child_key = private_key.derive_child(&[0x00], ChildNumber(0)); + assert!(child_key.is_ok()); + + let public_key = tw_keypair::ed25519::cardano::ExtendedPublicKey::try_from("797a077d4f2cca772b45fa67ada88502000470adf7f81fcb578357a73649fc76e5232f0770ce99adfc7e6ec1a5270f52d6435c30ceb51415258d1eaccd28b5fe").unwrap(); + let child_key = public_key.derive_child(&[0x00], ChildNumber::new(0, true).unwrap()); + assert!(child_key.is_err()); + + assert_eq!( + ::bip32_name(), + "ed25519 cardano seed" + ); + + let path = + cardano_staking_derivation_path(&DerivationPath::from_str("m/1852'/1815'/0'/0/0").unwrap()) + .unwrap(); + assert_eq!(path.to_string(), "m/1852'/1815'/0'/2/0"); + + let invalid = + cardano_staking_derivation_path(&DerivationPath::from_str("m/1852'/1815'").unwrap()); + assert!(invalid.is_err()); + + let invalid = + cardano_staking_derivation_path(&DerivationPath::from_str("m/1852'/1815'/0'/4/0").unwrap()); + assert!(invalid.is_err()); +} + +#[test] +fn test_nano_key_derivation_failure() { + let private_key = tw_keypair::ed25519::blake2b::PrivateKey::from_str( + "d258c2521f7802b8e83c32f2cc97bd06b69747847390c5e247a3d19faa74202e", + ) + .unwrap(); + let child_key = private_key.derive_child(&[0x00], ChildNumber::new(0, true).unwrap()); + assert!(child_key.is_err()); + + let public_key = tw_keypair::ed25519::blake2b::PublicKey::try_from( + "9833ff5684764ca31955727966df954be334ea051ad8c285eea6e4fbaa549001", + ) + .unwrap(); + let child_key = public_key.derive_child(&[0x00], ChildNumber::new(0, true).unwrap()); + assert!(child_key.is_err()); + + let child_key = public_key.derive_child(&[0x00], ChildNumber::new(0, false).unwrap()); + assert!(child_key.is_ok()); +} + +#[test] +fn test_wave_key_derivation_failure() { + let private_key = tw_keypair::ed25519::waves::PrivateKey::from_str( + "7374826cbd731cf656c11b3fdd458084288f655c8fd4056175996655d0fda4c9", + ) + .unwrap(); + let child_key = private_key.derive_child(&[0x00], ChildNumber::new(0, true).unwrap()); + assert!(child_key.is_err()); + + let public_key = tw_keypair::ed25519::waves::PublicKey::try_from( + "b6c00ffdacb469da62062a1dc8218a733a61720ab0942ba3625194281faf7d3d", + ) + .unwrap(); + let child_key = public_key.derive_child(&[0x00], ChildNumber::new(0, true).unwrap()); + assert!(child_key.is_err()); +} + +#[test] +fn test_zilliqa_schnorr_key_derivation_failure() { + let private_key = tw_keypair::zilliqa_schnorr::PrivateKey::from_str( + "d1b2b553b053f278d510a8494ead811252b1d5ec0da4434d0997a75de92bcea9", + ) + .unwrap(); + let child_key = private_key.derive_child(&[0x00], ChildNumber::new(0, true).unwrap()); + assert!(child_key.is_err()); + + let public_key = tw_keypair::zilliqa_schnorr::PublicKey::from_str( + "02f54cd391076f956b1cfc37cf182c18373f7c1566408c1748132cf4e782498e19", + ) + .unwrap(); + let child_key = public_key.derive_child(&[0x00], ChildNumber::new(0, true).unwrap()); + assert!(child_key.is_err()); +} + +#[test] +fn test_bip_tweak() { + let private_key = tw_keypair::ecdsa::secp256k1::PrivateKey::from_str( + "4fb8657d6464adcaa086d6758d7f0b6b6fc026c98dc1671fcc6460b5a74abc62", + ) + .unwrap(); + let tweak = private_key.derive_tweak(&[0; 32], ChildNumber::new(0, true).unwrap()); + assert!(tweak.is_ok()); + + let public_key = tw_keypair::ecdsa::secp256k1::PublicKey::try_from( + "023fc76c1210da890c598a3868f267d7d6a2c2c1fa4c60e6e105c9ef9f9f6e6532", + ) + .unwrap(); + let tweak = public_key.derive_tweak(&[0; 32], ChildNumber::new(0, true).unwrap()); + assert!(tweak.is_err()); +} + +#[test] +fn test_invalid_key_cardano() { + #[derive(Clone)] + struct InvalidPrivateKey; + + #[derive(Clone)] + struct InvalidPublicKey; + + impl BIP32PrivateKey for InvalidPrivateKey { + type BIP32PublicKey = InvalidPublicKey; + + fn curve() -> Curve { + Curve::Ed25519ExtendedCardano + } + + fn derive_child(&self, _other: &[u8], _child_number: ChildNumber) -> Result { + Err(Error::InvalidKeyData) + } + + fn bip32_name() -> &'static str { + "InvalidPrivateKey" + } + + fn public_key(&self) -> Self::BIP32PublicKey { + InvalidPublicKey + } + } + + impl BIP32PublicKey for InvalidPublicKey { + fn derive_child(&self, _other: &[u8], _child_number: ChildNumber) -> Result { + Err(Error::InvalidKeyData) + } + } + + impl ToBytesZeroizing for InvalidPrivateKey { + fn to_zeroizing_vec(&self) -> Zeroizing> { + Zeroizing::new(vec![]) + } + } + + impl FromStr for InvalidPrivateKey { + type Err = Error; + + fn from_str(_s: &str) -> Result { + Err(Error::InvalidKeyData) + } + } + + impl ToBytesVec for InvalidPublicKey { + fn to_vec(&self) -> Vec { + vec![] + } + } + + impl TryFrom<&[u8]> for InvalidPrivateKey { + type Error = Error; + + fn try_from(_slice: &[u8]) -> Result { + Err(Error::InvalidKeyData) + } + } + + impl TryFrom<&[u8]> for InvalidPublicKey { + type Error = Error; + + fn try_from(_slice: &[u8]) -> Result { + Err(Error::InvalidKeyData) + } + } + + let xprv = ExtendedPrivateKey::::new("00"); + assert!(xprv.is_err()); + + let invalid_key = InvalidPrivateKey; + let child_key = invalid_key.derive_child(&[0x00], ChildNumber::new(0, true).unwrap()); + assert!(child_key.is_err()); + + let invalid_key = InvalidPublicKey; + let child_key = invalid_key.derive_child(&[0x00], ChildNumber::new(0, true).unwrap()); + assert!(child_key.is_err()); +} + +#[test] +fn test_invalid_key_secp256k1() { + #[derive(Clone)] + struct InvalidPrivateKey; + + #[derive(Clone)] + struct InvalidPublicKey; + + impl BIP32PrivateKey for InvalidPrivateKey { + type BIP32PublicKey = InvalidPublicKey; + + fn curve() -> Curve { + Curve::Secp256k1 + } + + fn derive_child(&self, _other: &[u8], _child_number: ChildNumber) -> Result { + Err(Error::InvalidKeyData) + } + + fn bip32_name() -> &'static str { + "InvalidPrivateKey" + } + + fn public_key(&self) -> Self::BIP32PublicKey { + InvalidPublicKey + } + } + + impl BIP32PublicKey for InvalidPublicKey { + fn derive_child(&self, _other: &[u8], _child_number: ChildNumber) -> Result { + Err(Error::InvalidKeyData) + } + } + + impl ToBytesZeroizing for InvalidPrivateKey { + fn to_zeroizing_vec(&self) -> Zeroizing> { + Zeroizing::new(vec![]) + } + } + + impl FromStr for InvalidPrivateKey { + type Err = Error; + + fn from_str(_s: &str) -> Result { + Err(Error::InvalidKeyData) + } + } + + impl ToBytesVec for InvalidPublicKey { + fn to_vec(&self) -> Vec { + vec![] + } + } + + impl TryFrom<&[u8]> for InvalidPrivateKey { + type Error = Error; + + fn try_from(_slice: &[u8]) -> Result { + Err(Error::InvalidKeyData) + } + } + + impl TryFrom<&[u8]> for InvalidPublicKey { + type Error = Error; + + fn try_from(_slice: &[u8]) -> Result { + Err(Error::InvalidKeyData) + } + } + + let xprv = ExtendedPrivateKey::::new("00"); + assert!(xprv.is_err()); +} + +#[test] +fn test_error_from_slice_error() { + let test = |vec: Vec| -> Result<[u8; 4]> { Ok(vec.as_slice().try_into()?) }; + + assert!(test(vec![1, 2, 3]).unwrap_err() == Error::Decode); + assert!(test(vec![1, 2, 3, 4]).is_ok()); +} + +#[test] +fn test_error_from_hex_error() { + let test = |hex: &str| -> Result<()> { + let _ = hex::decode(hex)?; + Ok(()) + }; + + assert!(test("ZZ").unwrap_err() == Error::Decode); + assert!(test("eaab").is_ok()); +} + +#[test] +fn test_error_from_base58_encode_error() { + let test = |vec: Vec| -> Result<()> { + let mut buffer = [0u8; 100]; + let _ = bs58::encode(vec).onto(buffer.as_mut())?; + Ok(()) + }; + + assert!(test(vec![0; 300]).unwrap_err() == Error::Base58); + assert!(test(vec![1, 2, 3, 4]).is_ok()); +} + +#[test] +fn test_error_from_base58_decode_error() { + let test = |base58: &str| -> Result<()> { + let _ = bs58::decode(base58).into_vec()?; + Ok(()) + }; + + assert!(test("hello world").unwrap_err() == Error::Base58); + assert!(test("eaab").is_ok()); +} + +#[test] +fn test_error_from_key_pair_error() { + let test = |data: &[u8]| -> Result<()> { + let _ = tw_keypair::ecdsa::nist256p1::PrivateKey::try_from(data)?; + Ok(()) + }; + + assert!(test(&[1, 2, 3, 4]).unwrap_err() == Error::InvalidKeyData); +} diff --git a/rust/tw_crypto/tests/hd_node_ffi.rs b/rust/tw_crypto/tests/hd_node_ffi.rs new file mode 100644 index 00000000000..3068382ed80 --- /dev/null +++ b/rust/tw_crypto/tests/hd_node_ffi.rs @@ -0,0 +1,935 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_crypto::crypto_hd_node::hd_node::HDNode; +use tw_crypto::crypto_mnemonic::mnemonic::Mnemonic; +use tw_crypto::ffi::crypto_hd_node::{ + tw_hd_node_chain_code, tw_hd_node_child_number, tw_hd_node_create_with_extended_private_key, + tw_hd_node_create_with_seed, tw_hd_node_delete, tw_hd_node_depth, tw_hd_node_derive_from_path, + tw_hd_node_extended_private_key, tw_hd_node_extended_public_key, tw_hd_node_private_key_data, + tw_hd_node_public_key_data, TWHDNode, +}; +use tw_crypto::ffi::crypto_hd_node_public::{ + tw_hd_node_public_create_with_extended_public_key, tw_hd_node_public_derive_from_path, + tw_hd_node_public_public_key_data, TWHDNodePublic, +}; +use tw_crypto::ffi::crypto_mnemonic::{tw_mnemonic_to_entropy, tw_mnemonic_to_seed}; +use tw_encoding::hex; +use tw_hash::hasher::Hasher; +use tw_keypair::tw::Curve; +use tw_memory::ffi::{tw_data::TWData, tw_string::TWString, RawPtrTrait}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_memory::test_utils::tw_string_helper::TWStringHelper; +use tw_misc::traits::ToBytesVec; + +const BIP39_TEST_VECTORS: &str = include_str!("bip39_vectors.json"); + +#[test] +fn test_extended_private_key() { + let mnemonic = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + let seed = Mnemonic::to_seed(mnemonic, ""); + let hd_node = HDNode::new(&seed, Curve::Secp256k1).unwrap(); + + // BIP44 purpose and Bitcoin coin type + let purpose = 44; + let coin = 0; + + // ZPRV version + let hd_version = 0x04b2430c; + + // default path m/44'/0'/0' + let path = format!("m/{}'/{}'/{}'", purpose, coin, 0); + let derived_node = hd_node + .derive_from_path(&path, Hasher::Sha256ripemd) + .unwrap(); + let ext_priv_key1 = derived_node + .extended_private_key(hd_version, Hasher::Sha256d) + .unwrap(); + assert_eq!(ext_priv_key1, "zprvAcwsTZNaY1f7rfwsy5GseSDStYBrxwtsBZDkb3iyuQUs4NF6n58BuH7Xj54RuaSCWtU5CiQzuYQgFgqr1HokgKcVAeGeXokhJUAJeP3VmvY"); + + // explicitly specify default account=0 + let path = format!("m/{}'/{}'/{}'", purpose, coin, 0); + let derived_node = hd_node + .derive_from_path(&path, Hasher::Sha256ripemd) + .unwrap(); + let ext_priv_key2 = derived_node + .extended_private_key(hd_version, Hasher::Sha256d) + .unwrap(); + assert_eq!(ext_priv_key2, "zprvAcwsTZNaY1f7rfwsy5GseSDStYBrxwtsBZDkb3iyuQUs4NF6n58BuH7Xj54RuaSCWtU5CiQzuYQgFgqr1HokgKcVAeGeXokhJUAJeP3VmvY"); + + // custom account=1 + let path = format!("m/{}'/{}'/{}'", purpose, coin, 1); + let derived_node = hd_node + .derive_from_path(&path, Hasher::Sha256ripemd) + .unwrap(); + let ext_priv_key3 = derived_node + .extended_private_key(hd_version, Hasher::Sha256d) + .unwrap(); + assert_eq!(ext_priv_key3, "zprvAcwsTZNaY1f7sifgNNgdNa4P9mPtyg3zRVgwkx2qF9Sn7F255MzP6Zyumn6bgV5xuoS8ZrDvjzE7APcFSacXdzFYpGvyybb1bnAoh5nHxpn"); +} + +#[test] +fn test_extended_private_key_ffi() { + let curve: Curve = Curve::Secp256k1; + + let mnemonic = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + let mnemonic_string = TWStringHelper::create(mnemonic); + let passphrase_string = TWStringHelper::create(""); + + let seed_ptr = unsafe { tw_mnemonic_to_seed(mnemonic_string.ptr(), passphrase_string.ptr()) }; + + let hd_node_ptr = unsafe { tw_hd_node_create_with_seed(seed_ptr, curve.to_raw()) }; + let hd_node = unsafe { TWHDNode::from_ptr_as_ref(hd_node_ptr).unwrap() }; + + let chain_code = unsafe { tw_hd_node_chain_code(hd_node_ptr) }; + assert!(!chain_code.is_null()); + + let pubkey_hasher = Hasher::Sha256ripemd; + + let base58_hasher = Hasher::Sha256d; + + // BIP44 purpose and Bitcoin coin type + let purpose = 44; + let coin = 0; + + // ZPRV version + let hd_version = 0x04b2430c; + + // explicitly specify default account=0 + let path = format!("m/{}'/{}'/{}'", purpose, coin, 0); + let path_string = TWStringHelper::create(&path); + let derived_node_ptr = + unsafe { tw_hd_node_derive_from_path(hd_node, path_string.ptr(), pubkey_hasher as u32) }; + let derived_node = unsafe { TWHDNode::from_ptr_as_ref(derived_node_ptr).unwrap() }; + + let ext_priv_key = + unsafe { tw_hd_node_extended_private_key(derived_node, hd_version, base58_hasher as u32) }; + let ext_priv_key_string = unsafe { TWString::from_ptr_as_ref(ext_priv_key).unwrap() }; + let ext_priv_key_string = ext_priv_key_string.as_str().unwrap(); + assert_eq!(ext_priv_key_string, "zprvAcwsTZNaY1f7rfwsy5GseSDStYBrxwtsBZDkb3iyuQUs4NF6n58BuH7Xj54RuaSCWtU5CiQzuYQgFgqr1HokgKcVAeGeXokhJUAJeP3VmvY"); + + // custom account=1 + let path = format!("m/{}'/{}'/{}'", purpose, coin, 1); + let path_string = TWStringHelper::create(&path); + let derived_node_ptr = + unsafe { tw_hd_node_derive_from_path(hd_node, path_string.ptr(), pubkey_hasher as u32) }; + let derived_node = unsafe { TWHDNode::from_ptr_as_ref(derived_node_ptr).unwrap() }; + + let ext_priv_key = + unsafe { tw_hd_node_extended_private_key(derived_node, hd_version, base58_hasher as u32) }; + let ext_priv_key_string = unsafe { TWString::from_ptr_as_ref(ext_priv_key).unwrap() }; + let ext_priv_key_string = ext_priv_key_string.as_str().unwrap(); + assert_eq!(ext_priv_key_string, "zprvAcwsTZNaY1f7sifgNNgdNa4P9mPtyg3zRVgwkx2qF9Sn7F255MzP6Zyumn6bgV5xuoS8ZrDvjzE7APcFSacXdzFYpGvyybb1bnAoh5nHxpn"); + + let depth = unsafe { tw_hd_node_depth(derived_node_ptr) }; + assert_eq!(depth, 3); + + let child_number = unsafe { tw_hd_node_child_number(derived_node_ptr) }; + assert_eq!(child_number, 2147483649); + + unsafe { tw_hd_node_delete(hd_node_ptr) }; +} + +#[test] +fn test_extended_private_key_nist256p1_ffi() { + let curve: Curve = Curve::Nist256p1; + + let mnemonic = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + let mnemonic_string = TWStringHelper::create(mnemonic); + let passphrase_string = TWStringHelper::create(""); + + let seed_ptr = unsafe { tw_mnemonic_to_seed(mnemonic_string.ptr(), passphrase_string.ptr()) }; + + let hd_node_ptr = unsafe { tw_hd_node_create_with_seed(seed_ptr, curve.to_raw()) }; + let hd_node = unsafe { TWHDNode::from_ptr_as_ref(hd_node_ptr).unwrap() }; + + let chain_code = unsafe { tw_hd_node_chain_code(hd_node_ptr) }; + assert!(!chain_code.is_null()); + + let pubkey_hasher = Hasher::Sha256ripemd; + + let path = "m/44'/539'/0'/0/0"; + let path_string = TWStringHelper::create(&path); + let derived_node_ptr = + unsafe { tw_hd_node_derive_from_path(hd_node, path_string.ptr(), pubkey_hasher as u32) }; + + let private_key_data = unsafe { tw_hd_node_private_key_data(derived_node_ptr) }; + let private_key_bytes = unsafe { TWData::from_ptr_as_ref(private_key_data).unwrap().to_vec() }; + + assert_eq!( + hex::encode(private_key_bytes, false), + "a13df52d5a5b438bbf921bbf86276e4347fe8e2f2ed74feaaee12b77d6d26f86" + ); + + let pubkey_data = unsafe { tw_hd_node_public_key_data(derived_node_ptr) }; + assert!(!pubkey_data.is_null()); + + let depth = unsafe { tw_hd_node_depth(derived_node_ptr) }; + assert_eq!(depth, 5); + + let child_number = unsafe { tw_hd_node_child_number(derived_node_ptr) }; + assert_eq!(child_number, 0); +} + +#[test] +fn test_extended_private_key_ed25519_blake2b_ffi() { + let curve: Curve = Curve::Ed25519Blake2bNano; + + let mnemonic = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + let mnemonic_string = TWStringHelper::create(mnemonic); + let passphrase_string = TWStringHelper::create(""); + + let seed_ptr = unsafe { tw_mnemonic_to_seed(mnemonic_string.ptr(), passphrase_string.ptr()) }; + + let hd_node_ptr = unsafe { tw_hd_node_create_with_seed(seed_ptr, curve.to_raw()) }; + let hd_node = unsafe { TWHDNode::from_ptr_as_ref(hd_node_ptr).unwrap() }; + + let pubkey_hasher = Hasher::Sha256ripemd; + + let path = "m/44'/637'/0'/0'"; + let path_string = TWStringHelper::create(&path); + let derived_node_ptr = + unsafe { tw_hd_node_derive_from_path(hd_node, path_string.ptr(), pubkey_hasher as u32) }; + + let private_key_data = unsafe { tw_hd_node_private_key_data(derived_node_ptr) }; + let private_key_bytes = unsafe { TWData::from_ptr_as_ref(private_key_data).unwrap().to_vec() }; + + assert_eq!( + hex::encode(private_key_bytes, false), + "ffd43b8b4273e69a8278b9dbb4ac724134a878adc82927e503145c935b432959" + ); + + let pubkey_data = unsafe { tw_hd_node_public_key_data(derived_node_ptr) }; + let pubkey_bytes = unsafe { TWData::from_ptr_as_ref(pubkey_data).unwrap().to_vec() }; + + assert_eq!( + hex::encode(pubkey_bytes, false), + "37d29aff891f03abaa1ea1989cff6de0f46bd677f3ca7b3abb6e7f1c03786540" + ); + + let chain_code = unsafe { tw_hd_node_chain_code(hd_node_ptr) }; + assert!(!chain_code.is_null()); + + let depth = unsafe { tw_hd_node_depth(derived_node_ptr) }; + assert_eq!(depth, 4); + + let child_number = unsafe { tw_hd_node_child_number(derived_node_ptr) }; + assert_eq!(child_number, 2147483648); +} + +#[test] +fn test_extended_private_key_waves_ffi() { + let curve: Curve = Curve::Curve25519Waves; + + let mnemonic = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + let mnemonic_string = TWStringHelper::create(mnemonic); + let passphrase_string = TWStringHelper::create(""); + + let seed_ptr = unsafe { tw_mnemonic_to_seed(mnemonic_string.ptr(), passphrase_string.ptr()) }; + + let hd_node_ptr = unsafe { tw_hd_node_create_with_seed(seed_ptr, curve.to_raw()) }; + + let private_key_data = unsafe { tw_hd_node_private_key_data(hd_node_ptr) }; + let private_key_bytes = unsafe { TWData::from_ptr_as_ref(private_key_data).unwrap().to_vec() }; + + assert_eq!( + hex::encode(private_key_bytes, false), + "7374826cbd731cf656c11b3fdd458084288f655c8fd4056175996655d0fda4c9" + ); + + let pubkey_data = unsafe { tw_hd_node_public_key_data(hd_node_ptr) }; + let pubkey_bytes = unsafe { TWData::from_ptr_as_ref(pubkey_data).unwrap().to_vec() }; + + assert_eq!( + hex::encode(pubkey_bytes, false), + "b6c00ffdacb469da62062a1dc8218a733a61720ab0942ba3625194281faf7d3d" + ); + + let chain_code = unsafe { tw_hd_node_chain_code(hd_node_ptr) }; + assert!(!chain_code.is_null()); + + let hd_node = unsafe { TWHDNode::from_ptr_as_ref(hd_node_ptr).unwrap() }; + let pubkey_hasher = Hasher::Sha256ripemd; + + let path = "m/44'/5741564'/0'/0'/0'"; + let path_string = TWStringHelper::create(&path); + let derived_node_ptr = + unsafe { tw_hd_node_derive_from_path(hd_node, path_string.ptr(), pubkey_hasher as u32) }; + assert!(!derived_node_ptr.is_null()); + + let depth = unsafe { tw_hd_node_depth(derived_node_ptr) }; + assert_eq!(depth, 5); + + let child_number = unsafe { tw_hd_node_child_number(derived_node_ptr) }; + assert_eq!(child_number, 2147483648); +} + +#[test] +fn test_extended_private_key_zillqa_schnorr_ffi() { + let curve: Curve = Curve::ZilliqaSchnorr; + + let mnemonic = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + let mnemonic_string = TWStringHelper::create(mnemonic); + let passphrase_string = TWStringHelper::create(""); + + let seed_ptr = unsafe { tw_mnemonic_to_seed(mnemonic_string.ptr(), passphrase_string.ptr()) }; + + let hd_node_ptr = unsafe { tw_hd_node_create_with_seed(seed_ptr, curve.to_raw()) }; + + let private_key_data = unsafe { tw_hd_node_private_key_data(hd_node_ptr) }; + let private_key_bytes = unsafe { TWData::from_ptr_as_ref(private_key_data).unwrap().to_vec() }; + + assert_eq!( + hex::encode(private_key_bytes, false), + "d1b2b553b053f278d510a8494ead811252b1d5ec0da4434d0997a75de92bcea9" + ); + + let pubkey_data = unsafe { tw_hd_node_public_key_data(hd_node_ptr) }; + let pubkey_bytes = unsafe { TWData::from_ptr_as_ref(pubkey_data).unwrap().to_vec() }; + + assert_eq!( + hex::encode(pubkey_bytes, false), + "02f54cd391076f956b1cfc37cf182c18373f7c1566408c1748132cf4e782498e19" + ); + + let chain_code = unsafe { tw_hd_node_chain_code(hd_node_ptr) }; + assert!(!chain_code.is_null()); + + let depth = unsafe { tw_hd_node_depth(hd_node_ptr) }; + assert_eq!(depth, 0); + + let pubkey_hasher = Hasher::Sha256ripemd; + + let path = "m/44'/637'/0'/0'/0"; + let path_string = TWStringHelper::create(&path); + let derived_node_ptr = unsafe { + tw_hd_node_derive_from_path(hd_node_ptr, path_string.ptr(), pubkey_hasher as u32) + }; + + let private_key_data = unsafe { tw_hd_node_private_key_data(derived_node_ptr) }; + let private_key_bytes = unsafe { TWData::from_ptr_as_ref(private_key_data).unwrap().to_vec() }; + + assert_eq!( + hex::encode(private_key_bytes, false), + "4fc45a32e714677a8d3fbed23a8e1afbba8decbf60d479149129342dc894d2a4" + ); + + let pubkey_data = unsafe { tw_hd_node_public_key_data(derived_node_ptr) }; + let pubkey_bytes = unsafe { TWData::from_ptr_as_ref(pubkey_data).unwrap().to_vec() }; + + assert_eq!( + hex::encode(pubkey_bytes, false), + "03758382b7e39cf4790a6b4388254e36fa7aedd48e4595ad219687a8495c27d364" + ); + + let depth = unsafe { tw_hd_node_depth(derived_node_ptr) }; + assert_eq!(depth, 5); + + let child_number = unsafe { tw_hd_node_child_number(derived_node_ptr) }; + assert_eq!(child_number, 0); +} + +#[test] +fn test_extended_public_key_ffi() { + let curve: Curve = Curve::Secp256k1; + + let mnemonic = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + let mnemonic_string = TWStringHelper::create(mnemonic); + let passphrase_string = TWStringHelper::create("TREZOR"); + + let seed_ptr = unsafe { tw_mnemonic_to_seed(mnemonic_string.ptr(), passphrase_string.ptr()) }; + + let hd_node_ptr = unsafe { tw_hd_node_create_with_seed(seed_ptr, curve.to_raw()) }; + let hd_node = unsafe { TWHDNode::from_ptr_as_ref(hd_node_ptr).unwrap() }; + + // BIP44 purpose and Bitcoin coin type + let purpose = 44; + let coin = 42; + + // DPRV version + let prv_hd_version = 0x2fda4e8; + let pub_hd_version = 0x2fda926; + + let pubkey_hasher = Hasher::Blake256ripemd; + + let base58_hasher = Hasher::Blake256d; + + // explicitly specify default account=0 + let path = format!("m/{}'/{}'/{}'", purpose, coin, 0); + let path_string = TWStringHelper::create(&path); + let derived_node_ptr = + unsafe { tw_hd_node_derive_from_path(hd_node, path_string.ptr(), pubkey_hasher as u32) }; + let derived_node = unsafe { TWHDNode::from_ptr_as_ref(derived_node_ptr).unwrap() }; + + let ext_pub_key = unsafe { + tw_hd_node_extended_public_key(derived_node, pub_hd_version, base58_hasher as u32) + }; + let ext_pub_key_string = unsafe { TWString::from_ptr_as_ref(ext_pub_key).unwrap() }; + let ext_pub_key_string = ext_pub_key_string.as_str().unwrap(); + assert_eq!(ext_pub_key_string, "dpubZFUmm9oh5zmQkR2Tr2AXS4tCkTWg4B27SpCPFkapZrrAqgU1EwgEFgrmi6EnLGXhak86yDHhXPxFAnGU58W5S4e8NCKG1ASUVaxwRqqNdfP"); + + let ext_priv_key = unsafe { + tw_hd_node_extended_private_key(derived_node, prv_hd_version, base58_hasher as u32) + }; + let ext_priv_key_string = unsafe { TWString::from_ptr_as_ref(ext_priv_key).unwrap() }; + let ext_priv_key_string = ext_priv_key_string.as_str().unwrap(); + assert_eq!(ext_priv_key_string, "dprv3oggQ2FQ1chcr18hbW7Aur5x8SxQdES3FGa4WqeTZnFY88SNMzLdB7LkZLroF4bGAqWS8sDm3w4DKyYV7sDKfC6JMSVHnVJdpDLgHioq1vq"); +} + +#[test] +fn test_private_key_from_xprv() { + use tw_keypair::ecdsa::secp256k1::PrivateKey; + + let xprv = "xprv9yqEgpMG2KCjvotCxaiMkzmKJpDXz2xZi3yUe4XsURvo9DUbPySW1qRbdeDLiSxZt88hESHUhm2AAe2EqfWM9ucdQzH3xv1HoKoLDqHMK9n"; + let xprv_string = TWStringHelper::create(xprv); + + // Create HDNode from extended private key + let hd_node_ptr = unsafe { + tw_hd_node_create_with_extended_private_key( + xprv_string.ptr(), + Curve::Secp256k1.to_raw(), + Hasher::Sha256d as u32, + ) + }; + + let pubkey_hasher = Hasher::Sha256ripemd; + + let path = "m/0/3"; + let path_string = TWStringHelper::create(path); + let derived_node_ptr = unsafe { + tw_hd_node_derive_from_path(hd_node_ptr, path_string.ptr(), pubkey_hasher as u32) + }; + + let private_key_data = unsafe { tw_hd_node_private_key_data(derived_node_ptr) }; + assert!(!private_key_data.is_null()); + + let private_key_bytes = unsafe { TWData::from_ptr_as_ref(private_key_data).unwrap().to_vec() }; + let private_key = PrivateKey::try_from(private_key_bytes.as_slice()).unwrap(); + let public_key = private_key.public(); + + let public_key_hex = hex::encode(public_key.to_vec(), false); + assert_eq!( + public_key_hex, + "025108168f7e5aad52f7381c18d8f880744dbee21dc02c15abe512da0b1cca7e2f" + ); +} + +#[test] +fn test_private_key_from_xprv_invalid() { + let pubkey_hasher = Hasher::Sha256ripemd; + + { + // Version bytes (first 4) are invalid, 0x00000000 + let xprv = "11117pE7xwz2GARukXY8Vj2ge4ozfX4HLgy5ztnJXjr5btzJE8EbtPhZwrcPWAodW2aFeYiXkXjSxJYm5QrnhSKFXDgACcFdMqGns9VLqESCq3"; + let xprv_string = TWStringHelper::create(xprv); + + // Create HDNode from extended private key + let hd_node_ptr = unsafe { + tw_hd_node_create_with_extended_private_key( + xprv_string.ptr(), + Curve::Secp256k1.to_raw(), + Hasher::Sha256d as u32, + ) + }; + + let path = "m/0/3"; + let path_string = TWStringHelper::create(path); + let derived_node_ptr = unsafe { + tw_hd_node_derive_from_path(hd_node_ptr, path_string.ptr(), pubkey_hasher as u32) + }; + + let private_key_data = unsafe { tw_hd_node_private_key_data(derived_node_ptr) }; + assert!(private_key_data.is_null()); + } + + { + // Version bytes (first 4) are invalid, 0xdeadbeef + let xprv = "pGoh3VZXR4mTkT4bfqj4paog12KmHkAWkdLY8HNsZagD1ihVccygLr1ioLBhVQsny47uEh5swP3KScFc4JJrazx1Y7xvzmH2y5AseLgVMwomBTg2"; + let xprv_string = TWStringHelper::create(xprv); + + // Create HDNode from extended private key + let hd_node_ptr = unsafe { + tw_hd_node_create_with_extended_private_key( + xprv_string.ptr(), + Curve::Secp256k1.to_raw(), + Hasher::Sha256d as u32, + ) + }; + + let path = "m/0/3"; + let path_string = TWStringHelper::create(path); + let derived_node_ptr = unsafe { + tw_hd_node_derive_from_path(hd_node_ptr, path_string.ptr(), pubkey_hasher as u32) + }; + + let private_key_data = unsafe { tw_hd_node_private_key_data(derived_node_ptr) }; + assert!(private_key_data.is_null()); + } +} + +#[test] +fn test_derive_xpub_pub_vs_priv_pub() { + // Test different routes for deriving address from mnemonic, result should be the same: + // - Direct: mnemonic -> seed -> privateKey -> publicKey -> address + // - Extended Public: mnemonic -> seed -> zpub -> publicKey -> address + // - Extended Private: mnemonic -> seed -> zpriv -> privateKey -> publicKey -> address + let curve: Curve = Curve::Secp256k1; + + let mnemonic = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; + let mnemonic_string = TWStringHelper::create(mnemonic); + let passphrase_string = TWStringHelper::create(""); + + let seed_ptr = unsafe { tw_mnemonic_to_seed(mnemonic_string.ptr(), passphrase_string.ptr()) }; + + let hd_node_ptr = unsafe { tw_hd_node_create_with_seed(seed_ptr, curve.to_raw()) }; + let hd_node = unsafe { TWHDNode::from_ptr_as_ref(hd_node_ptr).unwrap() }; + + let deriv_path1 = "m/84'/0'/0'/0/0"; + let deriv_path2 = "m/84'/0'/0'/0/2"; + let expected_public_key1 = "02df9ef2a7a5552765178b181e1e1afdefc7849985c7dfe9647706dd4fa40df6ac"; + let expected_public_key2 = "031e1f64d2f6768dccb6814545b2e2d58e26ad5f91b7cbaffe881ed572c65060db"; + + let pubkey_hasher = Hasher::Sha256ripemd; + + let base58_hasher = Hasher::Sha256d; + + // -> privateKey -> publicKey + { + let path_string = TWStringHelper::create(deriv_path1); + let derived_node_ptr = unsafe { + tw_hd_node_derive_from_path(hd_node, path_string.ptr(), pubkey_hasher as u32) + }; + + let private_key_data = unsafe { tw_hd_node_private_key_data(derived_node_ptr) }; + let private_key_bytes = + unsafe { TWData::from_ptr_as_ref(private_key_data).unwrap().to_vec() }; + let public_key_hex = hex::encode( + tw_keypair::ecdsa::secp256k1::PrivateKey::try_from(private_key_bytes.as_slice()) + .unwrap() + .public() + .to_vec(), + false, + ); + assert_eq!(public_key_hex, expected_public_key1); + } + { + let path_string = TWStringHelper::create(deriv_path2); + let derived_node_ptr = unsafe { + tw_hd_node_derive_from_path(hd_node, path_string.ptr(), pubkey_hasher as u32) + }; + + let private_key_data = unsafe { tw_hd_node_private_key_data(derived_node_ptr) }; + let private_key_bytes = + unsafe { TWData::from_ptr_as_ref(private_key_data).unwrap().to_vec() }; + let public_key_hex = hex::encode( + tw_keypair::ecdsa::secp256k1::PrivateKey::try_from(private_key_bytes.as_slice()) + .unwrap() + .public() + .to_vec(), + false, + ); + assert_eq!(public_key_hex, expected_public_key2); + } + + // zpub -> publicKey + let account_path = "m/84'/0'/0'"; + let account_path_string = TWStringHelper::create(account_path); + let account_node_ptr = unsafe { + tw_hd_node_derive_from_path(hd_node, account_path_string.ptr(), pubkey_hasher as u32) + }; + let account_node = unsafe { TWHDNode::from_ptr_as_ref(account_node_ptr).unwrap() }; + + let zpub_version = 0x04b24746; // TWHDVersionZPUB + let zpub_ptr = + unsafe { tw_hd_node_extended_public_key(account_node, zpub_version, base58_hasher as u32) }; + let zpub = unsafe { + TWString::from_ptr_as_ref(zpub_ptr) + .unwrap() + .as_str() + .unwrap() + }; + + assert_eq!(zpub, "zpub6rNUNtxSa9Gxvm4Bdxf1MPMwrvkzwDx6vP96Hkzw3jiQKdg3fhXBStxjn12YixQB8h88B3RMSRscRstf9AEVaYr3MAqVBEWBDuEJU4PGaT9"); + + { + let hd_node_ptr = unsafe { + tw_hd_node_public_create_with_extended_public_key( + zpub_ptr, + curve.to_raw(), + Hasher::Sha256d as u32, + ) + }; + let hd_node = unsafe { TWHDNodePublic::from_ptr_as_ref(hd_node_ptr).unwrap() }; + + let child_path = "m/0/0"; + let child_path_string = TWStringHelper::create(child_path); + let derived_node_ptr = unsafe { + tw_hd_node_public_derive_from_path( + hd_node, + child_path_string.ptr(), + pubkey_hasher as u32, + ) + }; + + let public_key_data = unsafe { tw_hd_node_public_public_key_data(derived_node_ptr) }; + let public_key_bytes = + unsafe { TWData::from_ptr_as_ref(public_key_data).unwrap().to_vec() }; + let public_key_hex = hex::encode(public_key_bytes, false); + assert_eq!(public_key_hex, expected_public_key1); + } + + { + let hd_node_ptr = unsafe { + tw_hd_node_public_create_with_extended_public_key( + zpub_ptr, + curve.to_raw(), + Hasher::Sha256d as u32, + ) + }; + let hd_node = unsafe { TWHDNodePublic::from_ptr_as_ref(hd_node_ptr).unwrap() }; + + let child_path = "m/0/2"; + let child_path_string = TWStringHelper::create(child_path); + let derived_node_ptr = unsafe { + tw_hd_node_public_derive_from_path( + hd_node, + child_path_string.ptr(), + pubkey_hasher as u32, + ) + }; + + let public_key_data = unsafe { tw_hd_node_public_public_key_data(derived_node_ptr) }; + let public_key_bytes = + unsafe { TWData::from_ptr_as_ref(public_key_data).unwrap().to_vec() }; + let public_key_hex = hex::encode(public_key_bytes, false); + assert_eq!(public_key_hex, expected_public_key2); + } + + // zpriv -> privateKey -> publicKey + let zpriv_version = 0x04b2430c; // TWHDVersionZPRV + let zpriv_ptr = unsafe { + tw_hd_node_extended_private_key(account_node, zpriv_version, base58_hasher as u32) + }; + let zpriv = unsafe { + TWString::from_ptr_as_ref(zpriv_ptr) + .unwrap() + .as_str() + .unwrap() + }; + + assert_eq!(zpriv, "zprvAdP7yPRYjmifiGyiXw7zzFRDJtvWXmEFZADVVNbKVQBRSqLu8ACvu6eFvhrnnw4QwdTD8PUVa48MguwiPTiyfn85zWx9iA5MYy4Eufu5bas"); + + { + let zpriv_string = TWStringHelper::create(zpriv); + let zpriv_node_ptr = unsafe { + tw_hd_node_create_with_extended_private_key( + zpriv_string.ptr(), + Curve::Secp256k1.to_raw(), + Hasher::Sha256d as u32, + ) + }; + + let child_path = "m/0/0"; + let child_path_string = TWStringHelper::create(child_path); + let child_node_ptr = unsafe { + tw_hd_node_derive_from_path( + zpriv_node_ptr, + child_path_string.ptr(), + pubkey_hasher as u32, + ) + }; + + let private_key_data = unsafe { tw_hd_node_private_key_data(child_node_ptr) }; + let private_key_bytes = + unsafe { TWData::from_ptr_as_ref(private_key_data).unwrap().to_vec() }; + let public_key_hex = hex::encode( + tw_keypair::ecdsa::secp256k1::PrivateKey::try_from(private_key_bytes.as_slice()) + .unwrap() + .public() + .to_vec(), + false, + ); + assert_eq!(public_key_hex, expected_public_key1); + } + { + let zpriv_string = TWStringHelper::create(zpriv); + let zpriv_node_ptr = unsafe { + tw_hd_node_create_with_extended_private_key( + zpriv_string.ptr(), + Curve::Secp256k1.to_raw(), + Hasher::Sha256d as u32, + ) + }; + + let child_path = "m/0/2"; + let child_path_string = TWStringHelper::create(child_path); + let child_node_ptr = unsafe { + tw_hd_node_derive_from_path( + zpriv_node_ptr, + child_path_string.ptr(), + pubkey_hasher as u32, + ) + }; + + let private_key_data = unsafe { tw_hd_node_private_key_data(child_node_ptr) }; + let private_key_bytes = + unsafe { TWData::from_ptr_as_ref(private_key_data).unwrap().to_vec() }; + let public_key_hex = hex::encode( + tw_keypair::ecdsa::secp256k1::PrivateKey::try_from(private_key_bytes.as_slice()) + .unwrap() + .public() + .to_vec(), + false, + ); + assert_eq!(public_key_hex, expected_public_key2); + } +} + +#[test] +fn test_hedera_key() { + let curve: Curve = Curve::Ed25519; + + // Hedera specific DER prefixes + let hedera_der_prefix_private = "302e020100300506032b657004220420"; + let hedera_der_prefix_public = "302a300506032b6570032100"; + + let pubkey_hasher = Hasher::Sha256ripemd; + + { + let mnemonic = "inmate flip alley wear offer often piece magnet surge toddler submit right radio absent pear floor belt raven price stove replace reduce plate home"; + let mnemonic_string = TWStringHelper::create(mnemonic); + let passphrase_string = TWStringHelper::create(""); + + let seed_ptr = + unsafe { tw_mnemonic_to_seed(mnemonic_string.ptr(), passphrase_string.ptr()) }; + + let hd_node_ptr = unsafe { tw_hd_node_create_with_seed(seed_ptr, curve.to_raw()) }; + let hd_node = unsafe { TWHDNode::from_ptr_as_ref(hd_node_ptr).unwrap() }; + + let path = "m/44'/3030'/0'/0'/0"; + let path_string = TWStringHelper::create(&path); + let derived_node_ptr = unsafe { + tw_hd_node_derive_from_path(hd_node, path_string.ptr(), pubkey_hasher as u32) + }; + + let private_key_data = unsafe { tw_hd_node_private_key_data(derived_node_ptr) }; + let private_key_bytes = + unsafe { TWData::from_ptr_as_ref(private_key_data).unwrap().to_vec() }; + + let public_key_data = unsafe { tw_hd_node_public_key_data(derived_node_ptr) }; + let public_key_bytes = + unsafe { TWData::from_ptr_as_ref(public_key_data).unwrap().to_vec() }; + + assert_eq!( + format!("{}{}", hedera_der_prefix_private, hex::encode(private_key_bytes, false)), + "302e020100300506032b657004220420853f15aecd22706b105da1d709b4ac05b4906170c2b9c7495dff9af49e1391da" + ); + assert_eq!( + format!("{}{}", hedera_der_prefix_public, hex::encode(public_key_bytes, false)), + "302a300506032b6570032100b63b3815f453cf697b53b290b1d78e88c725d39bde52c34c79fb5b4c93894673" + ); + } + + { + let mnemonic = + "walk gun glide frequent exhaust sugar siege prosper staff skill swarm label"; + let mnemonic_string = TWStringHelper::create(mnemonic); + let passphrase_string = TWStringHelper::create(""); + + let seed_ptr = + unsafe { tw_mnemonic_to_seed(mnemonic_string.ptr(), passphrase_string.ptr()) }; + + let hd_node_ptr = unsafe { tw_hd_node_create_with_seed(seed_ptr, curve.to_raw()) }; + let hd_node = unsafe { TWHDNode::from_ptr_as_ref(hd_node_ptr).unwrap() }; + + let path = "m/44'/3030'/0'/0'/0"; + let path_string = TWStringHelper::create(&path); + let derived_node_ptr = unsafe { + tw_hd_node_derive_from_path(hd_node, path_string.ptr(), pubkey_hasher as u32) + }; + + let private_key_data = unsafe { tw_hd_node_private_key_data(derived_node_ptr) }; + let private_key_bytes = + unsafe { TWData::from_ptr_as_ref(private_key_data).unwrap().to_vec() }; + + let public_key_data = unsafe { tw_hd_node_public_key_data(derived_node_ptr) }; + let public_key_bytes = + unsafe { TWData::from_ptr_as_ref(public_key_data).unwrap().to_vec() }; + + assert_eq!( + format!("{}{}", hedera_der_prefix_private, hex::encode(private_key_bytes, false)), + "302e020100300506032b657004220420650c5120cbdc6244e3d10001eb27eea4dd3f80c331b3b6969fa434797d4edd50" + ); + assert_eq!( + format!("{}{}", hedera_der_prefix_public, hex::encode(public_key_bytes, false)), + "302a300506032b65700321007df3e1ab790b28de4706d36a7aa99a0e043cb3e2c3d6ec6686e4af7f638b0860" + ); + } +} + +#[test] +fn test_cardano_key() { + let mnemonic = "cost dash dress stove morning robust group affair stomach vacant route volume yellow salute laugh"; + let mnemonic_string = TWStringHelper::create(mnemonic); + + let seed_ptr = unsafe { tw_mnemonic_to_entropy(mnemonic_string.ptr()) }; + + let curve = Curve::Ed25519ExtendedCardano; + let hd_node_ptr = unsafe { tw_hd_node_create_with_seed(seed_ptr, curve.to_raw()) }; + let hd_node = unsafe { TWHDNode::from_ptr_as_ref(hd_node_ptr).unwrap() }; + + let pubkey_hasher = Hasher::Sha256ripemd; + + let deriv_path = "m/1852'/1815'/0'/0/0"; + let path_string = TWStringHelper::create(deriv_path); + let derived_node_ptr = + unsafe { tw_hd_node_derive_from_path(hd_node, path_string.ptr(), pubkey_hasher as u32) }; + + let public_key_data = unsafe { tw_hd_node_public_key_data(derived_node_ptr) }; + let public_key_bytes = unsafe { TWData::from_ptr_as_ref(public_key_data).unwrap().to_vec() }; + + assert_eq!( + hex::encode(public_key_bytes, false), + "fafa7eb4146220db67156a03a5f7a79c666df83eb31abbfbe77c85e06d40da3110f3245ddf9132ecef98c670272ef39c03a232107733d4a1d28cb53318df26faf4b8d5201961e68f2e177ba594101f513ee70fe70a41324e8ea8eb787ffda6f4bf2eea84515a4e16c4ff06c92381822d910b5cbf9e9c144e1fb76a6291af7276" + ); + + let invalid_deriv_path = "m/1852'/1815'/'/0/0"; + let invalid_path_string = TWStringHelper::create(invalid_deriv_path); + let invalid_derived_node_ptr = unsafe { + tw_hd_node_derive_from_path(hd_node, invalid_path_string.ptr(), pubkey_hasher as u32) + }; + assert!(invalid_derived_node_ptr.is_null()); +} + +#[test] +fn test_bip39_vectors() { + // BIP39 test vectors, from https://github.com/trezor/python-mnemonic/blob/master/vectors.json + let vectors: serde_json::Value = serde_json::from_str(&BIP39_TEST_VECTORS).unwrap(); + let english_vectors = vectors["english"].as_array().unwrap(); + + let curve: Curve = Curve::Secp256k1; + let base58_hasher = Hasher::Sha256d; + let version = 0x0488ade4; + + for v in english_vectors { + let seed = v[2].as_str().unwrap(); + let xprv = v[3].as_str().unwrap(); + + let seed_data = TWDataHelper::create(hex::decode(seed).unwrap()); + + let hd_node_ptr = unsafe { tw_hd_node_create_with_seed(seed_data.ptr(), curve.to_raw()) }; + let hd_node = unsafe { TWHDNode::from_ptr_as_ref(hd_node_ptr).unwrap() }; + + let xprv_ptr = + unsafe { tw_hd_node_extended_private_key(hd_node, version, base58_hasher as u32) }; + let xprv_string = unsafe { + TWString::from_ptr_as_ref(xprv_ptr) + .unwrap() + .as_str() + .unwrap() + }; + assert_eq!(xprv_string, xprv); + } +} + +#[test] +fn test_extended_public_key_iost_ffi() { + let curve: Curve = Curve::Ed25519; + + let mnemonic = "often tobacco bread scare imitate song kind common bar forest yard wisdom"; + let mnemonic_string = TWStringHelper::create(mnemonic); + let passphrase_string = TWStringHelper::create(""); + + let seed_ptr = unsafe { tw_mnemonic_to_seed(mnemonic_string.ptr(), passphrase_string.ptr()) }; + + let hd_node_ptr = unsafe { tw_hd_node_create_with_seed(seed_ptr, curve.to_raw()) }; + let hd_node = unsafe { TWHDNode::from_ptr_as_ref(hd_node_ptr).unwrap() }; + + // BIP44 purpose and Bitcoin coin type + let purpose = 44; + let coin = 899; + + // XPUB + let pub_hd_version = 0x0488b21e; + + let pubkey_hasher = Hasher::Sha256ripemd; + + let base58_hasher = Hasher::Sha256d; + + // // explicitly specify default account=0 + let path = format!("m/{}'/{}'/{}'", purpose, coin, 0); + let path_string = TWStringHelper::create(&path); + let derived_node_ptr = + unsafe { tw_hd_node_derive_from_path(hd_node, path_string.ptr(), pubkey_hasher as u32) }; + let derived_node = unsafe { TWHDNode::from_ptr_as_ref(derived_node_ptr).unwrap() }; + + let ext_pub_key = unsafe { + tw_hd_node_extended_public_key(derived_node, pub_hd_version, base58_hasher as u32) + }; + let ext_pub_key_string = unsafe { TWString::from_ptr_as_ref(ext_pub_key).unwrap() }; + let ext_pub_key_string = ext_pub_key_string.as_str().unwrap(); + assert_eq!(ext_pub_key_string, "xpub6CazMhni6xNtFaEeRqeaa2S3LyfrQWXk8YoEykEUyKpYyxqxG18HSo3e8Kkco5YkEddiCEF1pav6gXy71sGFKMux9rcdc8TCEfZG662hhxg"); +} + +#[test] +fn test_aeternity_key() { + let curve: Curve = Curve::Ed25519; + + let mnemonic = + "shoot island position soft burden budget tooth cruel issue economy destroy above"; + let mnemonic_string = TWStringHelper::create(mnemonic); + let passphrase_string = TWStringHelper::create(""); + + let seed_ptr = unsafe { tw_mnemonic_to_seed(mnemonic_string.ptr(), passphrase_string.ptr()) }; + + let hd_node_ptr = unsafe { tw_hd_node_create_with_seed(seed_ptr, curve.to_raw()) }; + let hd_node = unsafe { TWHDNode::from_ptr_as_ref(hd_node_ptr).unwrap() }; + + let pubkey_hasher = Hasher::Sha256ripemd; + + let path = "m/44'/457'/0'/0'/0'"; + let path_string = TWStringHelper::create(path); + let derived_node_ptr = + unsafe { tw_hd_node_derive_from_path(hd_node, path_string.ptr(), pubkey_hasher as u32) }; + assert!(!derived_node_ptr.is_null()); + // let derived_node = unsafe { TWHDNode::from_ptr_as_ref(derived_node_ptr).unwrap() }; +} + +#[test] +fn test_binance_key() { + let curve: Curve = Curve::Secp256k1; + + let mnemonic = "rabbit tilt arm protect banner ill produce vendor april bike much identify pond upset front easily glass gallery address hair priority focus forest angle"; + let mnemonic_string = TWStringHelper::create(mnemonic); + let passphrase_string = TWStringHelper::create(""); + + let seed_ptr = unsafe { tw_mnemonic_to_seed(mnemonic_string.ptr(), passphrase_string.ptr()) }; + + let hd_node_ptr = unsafe { tw_hd_node_create_with_seed(seed_ptr, curve.to_raw()) }; + let hd_node = unsafe { TWHDNode::from_ptr_as_ref(hd_node_ptr).unwrap() }; + + let private_key_data = unsafe { tw_hd_node_private_key_data(hd_node_ptr) }; + let private_key_bytes = unsafe { TWData::from_ptr_as_ref(private_key_data).unwrap().to_vec() }; + assert_eq!( + hex::encode(private_key_bytes, true), + "0xfdf3a7c4962ac691f9fe4058521c7ea388be14f24d2340fe0a4a8105a60e666d" + ); + + let pubkey_hasher = Hasher::Sha256ripemd; + + let path = "m/44'/714'/0'/0/0"; + let path_string = TWStringHelper::create(path); + let derived_node_ptr = + unsafe { tw_hd_node_derive_from_path(hd_node, path_string.ptr(), pubkey_hasher as u32) }; + + let private_key_data = unsafe { tw_hd_node_private_key_data(derived_node_ptr) }; + let private_key_bytes = unsafe { TWData::from_ptr_as_ref(private_key_data).unwrap().to_vec() }; + + assert_eq!( + hex::encode(private_key_bytes, true), + "0x727f677b390c151caf9c206fd77f77918f56904b5504243db9b21e51182c4c06" + ); +} diff --git a/rust/tw_crypto/tests/hd_node_public_ffi.rs b/rust/tw_crypto/tests/hd_node_public_ffi.rs new file mode 100644 index 00000000000..0fc6e1d749b --- /dev/null +++ b/rust/tw_crypto/tests/hd_node_public_ffi.rs @@ -0,0 +1,155 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_crypto::ffi::crypto_hd_node_public::{ + tw_hd_node_public_create_with_extended_public_key, tw_hd_node_public_delete, + tw_hd_node_public_derive_from_path, tw_hd_node_public_public_key_data, +}; +use tw_encoding::hex; +use tw_hash::hasher::Hasher; +use tw_keypair::tw::Curve; +use tw_memory::ffi::{tw_data::TWData, RawPtrTrait}; +use tw_memory::test_utils::tw_string_helper::TWStringHelper; +use tw_misc::traits::ToBytesVec; + +#[test] +fn test_public_key_from_zpub() { + use tw_keypair::ecdsa::secp256k1::PublicKey; + + let zpub = "zpub6rNUNtxSa9Gxvm4Bdxf1MPMwrvkzwDx6vP96Hkzw3jiQKdg3fhXBStxjn12YixQB8h88B3RMSRscRstf9AEVaYr3MAqVBEWBDuEJU4PGaT9"; + let zpub_string = TWStringHelper::create(zpub); + + // Create HDNode from extended private key + let hd_node_ptr = unsafe { + tw_hd_node_public_create_with_extended_public_key( + zpub_string.ptr(), + Curve::Secp256k1.to_raw(), + Hasher::Sha256d as u32, + ) + }; + + let path = "m/0/0"; + let path_string = TWStringHelper::create(path); + + let hasher = Hasher::Sha256ripemd; + + let derived_node_ptr = unsafe { + tw_hd_node_public_derive_from_path(hd_node_ptr, path_string.ptr(), hasher as u32) + }; + + let public_key_data = unsafe { tw_hd_node_public_public_key_data(derived_node_ptr) }; + assert!(!public_key_data.is_null()); + + let public_key_bytes = unsafe { TWData::from_ptr_as_ref(public_key_data).unwrap().to_vec() }; + let public_key = PublicKey::try_from(public_key_bytes.as_slice()).unwrap(); + + let public_key_hex = hex::encode(public_key.to_vec(), false); + assert_eq!( + public_key_hex, + "02df9ef2a7a5552765178b181e1e1afdefc7849985c7dfe9647706dd4fa40df6ac" + ); + + unsafe { tw_hd_node_public_delete(hd_node_ptr) }; +} + +#[test] +fn test_public_key_from_dpub() { + use tw_keypair::ecdsa::secp256k1::PublicKey; + + let zpub = "dpubZFUmm9oh5zmQkR2Tr2AXS4tCkTWg4B27SpCPFkapZrrAqgU1EwgEFgrmi6EnLGXhak86yDHhXPxFAnGU58W5S4e8NCKG1ASUVaxwRqqNdfP"; + let zpub_string = TWStringHelper::create(zpub); + + // Create HDNode from extended private key + let hd_node_ptr = unsafe { + tw_hd_node_public_create_with_extended_public_key( + zpub_string.ptr(), + Curve::Secp256k1.to_raw(), + Hasher::Blake256d as u32, + ) + }; + + let path = "m/0/0"; + let path_string = TWStringHelper::create(path); + + let hasher = Hasher::Blake256ripemd; + + let derived_node_ptr = unsafe { + tw_hd_node_public_derive_from_path(hd_node_ptr, path_string.ptr(), hasher as u32) + }; + + let public_key_data = unsafe { tw_hd_node_public_public_key_data(derived_node_ptr) }; + assert!(!public_key_data.is_null()); + + let public_key_bytes = unsafe { TWData::from_ptr_as_ref(public_key_data).unwrap().to_vec() }; + let public_key = PublicKey::try_from(public_key_bytes.as_slice()).unwrap(); + + let public_key_hex = hex::encode(public_key.to_vec(), false); + assert_eq!( + public_key_hex, + "035328794a9392d6618c0f8071d2ba25fecef85230d916c2ef17f578af70483103" + ); +} + +#[test] +fn test_public_key_from_extended_ethereum() { + let xpub = "xpub6C7LtZJgtz1BKXG9mExKUxYvX7HSF38UMMmGbpqNQw3DfYwAw8E6sH7VSVxFipvEEm2afSqTjoRgcLmycXX4zfxCWJ4HY73a9KdgvfHEQGB"; + let xpub_string = TWStringHelper::create(xpub); + + let hd_node_ptr = unsafe { + tw_hd_node_public_create_with_extended_public_key( + xpub_string.ptr(), + Curve::Secp256k1.to_raw(), + Hasher::Sha256d as u32, + ) + }; + assert!(!hd_node_ptr.is_null()); + + let hasher = Hasher::Sha256ripemd; + let path = "m/0/1"; + let path_string = TWStringHelper::create(path); + + let derived_node_ptr = unsafe { + tw_hd_node_public_derive_from_path(hd_node_ptr, path_string.ptr(), hasher as u32) + }; + + let public_key_data = unsafe { tw_hd_node_public_public_key_data(derived_node_ptr) }; + assert!(!public_key_data.is_null()); + + let public_key_bytes = unsafe { TWData::from_ptr_as_ref(public_key_data).unwrap().to_vec() }; + + let public_key_hex = hex::encode(public_key_bytes, false); + assert_eq!( + public_key_hex, + "024516c4aa5352035e1bb5be132694e1389a4ac37d32e5e717d35ee4c4dfab5132" + ); +} + +#[test] +fn test_public_key_from_zpub_nist() { + let zpub = "zpub6vBy1RjDYQ9EQZJYiWM1qvSdxKyjmEETobMBD9T9bwwmSGX1y4XrSDAKBZzHCjckGoh8gMEFmx3qyUEU2kcg3wMtUG5CpTtpqdzf8YShMLg"; + let zpub_string = TWStringHelper::create(zpub); + + // Create HDNode from extended private key + let hd_node_ptr = unsafe { + tw_hd_node_public_create_with_extended_public_key( + zpub_string.ptr(), + Curve::Nist256p1.to_raw(), + Hasher::Sha256d as u32, + ) + }; + + let path = "m/0/0"; + let path_string = TWStringHelper::create(path); + + let hasher = Hasher::Sha256ripemd; + + let derived_node_ptr = unsafe { + tw_hd_node_public_derive_from_path(hd_node_ptr, path_string.ptr(), hasher as u32) + }; + + let public_key_data = unsafe { tw_hd_node_public_public_key_data(derived_node_ptr) }; + assert!(!public_key_data.is_null()); +} diff --git a/rust/tw_hash/Cargo.toml b/rust/tw_hash/Cargo.toml index c9b8ab18c42..ddb71b89653 100644 --- a/rust/tw_hash/Cargo.toml +++ b/rust/tw_hash/Cargo.toml @@ -18,6 +18,7 @@ serde = { version = "1.0", features = ["derive"], optional = true } sha1 = "0.10.5" sha2 = "0.10.6" sha3 = "0.10.6" +strum_macros = "0.25" tw_encoding = { path = "../tw_encoding" } tw_memory = { path = "../tw_memory" } zeroize = "1.8.1" diff --git a/rust/tw_hash/src/hasher.rs b/rust/tw_hash/src/hasher.rs index badb1237472..9eb5d7048f3 100644 --- a/rust/tw_hash/src/hasher.rs +++ b/rust/tw_hash/src/hasher.rs @@ -2,12 +2,13 @@ // // Copyright © 2017 Trust Wallet. -use crate::blake::blake_256; +use crate::blake::{blake256_d, blake_256}; +use crate::groestl::groestl_512d; use crate::ripemd::{blake256_ripemd, sha256_ripemd}; use crate::sha2::{sha256, sha256_d}; use crate::sha3::keccak256; use crate::{H160, H256}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use tw_memory::Data; #[macro_export] @@ -81,25 +82,30 @@ pub trait StatefulHasher { /// /// Add hash types if necessary. For example, when add a new hasher to `registry.json`, /// otherwise use hash functions directly. -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)] +#[repr(u32)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, strum_macros::FromRepr)] pub enum Hasher { #[serde(rename = "sha256")] - Sha256, + Sha256 = 1, #[serde(rename = "keccak256")] - Keccak256, + Keccak256 = 4, + #[serde(rename = "blake256")] + Blake256 = 10, /// SHA256 hash of the SHA256 hash #[serde(rename = "sha256d")] - Sha256d, + Sha256d = 12, /// ripemd hash of the SHA256 hash #[serde(rename = "sha256ripemd")] - Sha256ripemd, - #[serde(rename = "blake256")] - Blake256, + Sha256ripemd = 13, + #[serde(rename = "blake256d")] + Blake256d = 15, /// ripemd hash of the BLAKE256 hash #[serde(rename = "blake256ripemd")] - Blake256ripemd, + Blake256ripemd = 16, + #[serde(rename = "groestl512d")] + Groestl512d = 17, #[serde(rename = "tapsighash")] - TapSighash, + TapSighash = 18, } impl StatefulHasher for Hasher { @@ -111,6 +117,8 @@ impl StatefulHasher for Hasher { Hasher::Sha256ripemd => sha256_ripemd(data), Hasher::Blake256 => blake_256(data), Hasher::Blake256ripemd => blake256_ripemd(data), + Hasher::Blake256d => blake256_d(data), + Hasher::Groestl512d => groestl_512d(data), Hasher::TapSighash => tapsighash(data), } } @@ -118,8 +126,13 @@ impl StatefulHasher for Hasher { /// Returns a corresponding hash len. fn hash_len(&self) -> usize { match self { - Hasher::Sha256 | Hasher::Keccak256 | Hasher::Sha256d | Hasher::Blake256 => H256::len(), + Hasher::Sha256 + | Hasher::Keccak256 + | Hasher::Sha256d + | Hasher::Blake256 + | Hasher::Blake256d => H256::len(), Hasher::Sha256ripemd | Hasher::Blake256ripemd => H160::len(), + Hasher::Groestl512d => H256::len(), Hasher::TapSighash => H256::len(), } } diff --git a/rust/tw_hash/src/hmac.rs b/rust/tw_hash/src/hmac.rs index 475c5bbdb7f..7bcd56edcd3 100644 --- a/rust/tw_hash/src/hmac.rs +++ b/rust/tw_hash/src/hmac.rs @@ -3,10 +3,12 @@ // Copyright © 2017 Trust Wallet. use hmac::{Hmac, Mac}; -use sha2::Sha256; +use sha2::{Sha256, Sha512}; type HmacSha256 = Hmac; +pub type HmacSha512 = Hmac; + pub fn hmac_sha256(key: &[u8], input: &[u8]) -> Vec { let mut mac = HmacSha256::new_from_slice(key).unwrap(); mac.update(input); diff --git a/rust/tw_keypair/src/ecdsa/nist256p1/private.rs b/rust/tw_keypair/src/ecdsa/nist256p1/private.rs index 0d2ede5bf83..1b44c87329d 100644 --- a/rust/tw_keypair/src/ecdsa/nist256p1/private.rs +++ b/rust/tw_keypair/src/ecdsa/nist256p1/private.rs @@ -2,9 +2,11 @@ // // Copyright © 2017 Trust Wallet. +use std::str::FromStr; + use crate::ecdsa::nist256p1::public::PublicKey; use crate::ecdsa::nist256p1::Signature; -use crate::traits::SigningKeyTrait; +use crate::traits::{DerivableKeyTrait, SigningKeyTrait}; use crate::{KeyPairError, KeyPairResult}; use p256::ecdsa::SigningKey; use tw_encoding::hex; @@ -13,7 +15,7 @@ use tw_misc::traits::ToBytesZeroizing; use zeroize::{ZeroizeOnDrop, Zeroizing}; /// Represents a `nist256p1` private key. -#[derive(ZeroizeOnDrop)] +#[derive(ZeroizeOnDrop, Clone)] pub struct PrivateKey { pub(crate) secret: SigningKey, } @@ -56,9 +58,34 @@ impl<'a> TryFrom<&'a str> for PrivateKey { } } +impl FromStr for PrivateKey { + type Err = KeyPairError; + + fn from_str(hex: &str) -> Result { + Self::try_from(hex) + } +} + impl ToBytesZeroizing for PrivateKey { fn to_zeroizing_vec(&self) -> Zeroizing> { let secret = Zeroizing::new(self.secret.to_bytes()); Zeroizing::new(secret.as_slice().to_vec()) } } + +impl DerivableKeyTrait for PrivateKey { + fn derive_child(&self, other: H256) -> KeyPairResult { + let child_scalar = Option::::from(p256::NonZeroScalar::from_repr( + other.take().into(), + )) + .ok_or(KeyPairError::InternalError)?; + + let derived_scalar = self.secret.as_nonzero_scalar().as_ref() + child_scalar.as_ref(); + + let secret = Option::::from(p256::NonZeroScalar::new(derived_scalar)) + .map(Into::into) + .ok_or(KeyPairError::InternalError)?; + + Ok(PrivateKey { secret }) + } +} diff --git a/rust/tw_keypair/src/ecdsa/nist256p1/public.rs b/rust/tw_keypair/src/ecdsa/nist256p1/public.rs index 6ac0ec429c7..31e93552fcf 100644 --- a/rust/tw_keypair/src/ecdsa/nist256p1/public.rs +++ b/rust/tw_keypair/src/ecdsa/nist256p1/public.rs @@ -3,8 +3,9 @@ // Copyright © 2017 Trust Wallet. use crate::ecdsa::nist256p1::{Signature, VerifySignature}; -use crate::traits::VerifyingKeyTrait; +use crate::traits::{DerivableKeyTrait, VerifyingKeyTrait}; use crate::{KeyPairError, KeyPairResult}; +use ecdsa::elliptic_curve::group::prime::PrimeCurveAffine; use p256::ecdsa::signature::hazmat::PrehashVerifier; use p256::ecdsa::VerifyingKey; use tw_encoding::hex; @@ -90,3 +91,19 @@ impl ToBytesVec for PublicKey { self.compressed().to_vec() } } + +impl DerivableKeyTrait for PublicKey { + fn derive_child(&self, other: H256) -> KeyPairResult { + let child_scalar = Option::::from(p256::NonZeroScalar::from_repr( + other.take().into(), + )) + .ok_or(KeyPairError::InvalidPublicKey)?; + + let projective_point: p256::ProjectivePoint = self.public.as_affine().into(); + let child_point = projective_point + (p256::AffinePoint::generator() * *child_scalar); + let public = VerifyingKey::from_affine(child_point.into()) + .map_err(|_| KeyPairError::InternalError)?; + + Ok(PublicKey { public }) + } +} diff --git a/rust/tw_keypair/src/ecdsa/secp256k1/private.rs b/rust/tw_keypair/src/ecdsa/secp256k1/private.rs index ec80de8b098..d7ee61395c0 100644 --- a/rust/tw_keypair/src/ecdsa/secp256k1/private.rs +++ b/rust/tw_keypair/src/ecdsa/secp256k1/private.rs @@ -2,8 +2,11 @@ // // Copyright © 2017 Trust Wallet. +use std::str::FromStr; + use crate::ecdsa::secp256k1::public::PublicKey; use crate::ecdsa::secp256k1::Signature; +use crate::traits::DerivableKeyTrait; use crate::traits::SigningKeyTrait; use crate::{KeyPairError, KeyPairResult}; use k256::ecdsa::{SigningKey, VerifyingKey}; @@ -15,7 +18,7 @@ use tw_misc::traits::ToBytesZeroizing; use zeroize::{ZeroizeOnDrop, Zeroizing}; /// Represents a `secp256k1` private key. -#[derive(ZeroizeOnDrop)] +#[derive(ZeroizeOnDrop, Clone)] pub struct PrivateKey { pub(crate) secret: SigningKey, } @@ -79,9 +82,34 @@ impl<'a> TryFrom<&'a str> for PrivateKey { } } +impl FromStr for PrivateKey { + type Err = KeyPairError; + + fn from_str(hex: &str) -> Result { + Self::try_from(hex) + } +} + impl ToBytesZeroizing for PrivateKey { fn to_zeroizing_vec(&self) -> Zeroizing> { let secret = Zeroizing::new(self.secret.to_bytes()); Zeroizing::new(secret.as_slice().to_vec()) } } + +impl DerivableKeyTrait for PrivateKey { + fn derive_child(&self, other: H256) -> KeyPairResult { + let child_scalar = Option::::from(k256::NonZeroScalar::from_repr( + other.take().into(), + )) + .ok_or(KeyPairError::InternalError)?; + + let derived_scalar = self.secret.as_nonzero_scalar().as_ref() + child_scalar.as_ref(); + + let secret = Option::::from(k256::NonZeroScalar::new(derived_scalar)) + .map(Into::into) + .ok_or(KeyPairError::InternalError)?; + + Ok(PrivateKey { secret }) + } +} diff --git a/rust/tw_keypair/src/ecdsa/secp256k1/public.rs b/rust/tw_keypair/src/ecdsa/secp256k1/public.rs index 73f61021069..dde33bf56c9 100644 --- a/rust/tw_keypair/src/ecdsa/secp256k1/public.rs +++ b/rust/tw_keypair/src/ecdsa/secp256k1/public.rs @@ -3,9 +3,10 @@ // Copyright © 2017 Trust Wallet. use crate::ecdsa::secp256k1::{Signature, VerifySignature}; -use crate::traits::VerifyingKeyTrait; +use crate::traits::{DerivableKeyTrait, VerifyingKeyTrait}; use crate::{KeyPairError, KeyPairResult}; use der::Document; +use ecdsa::elliptic_curve::group::prime::PrimeCurveAffine; use k256::ecdsa::signature::hazmat::PrehashVerifier; use k256::ecdsa::VerifyingKey; use tw_encoding::hex; @@ -119,3 +120,19 @@ impl ToBytesVec for PublicKey { self.compressed().to_vec() } } + +impl DerivableKeyTrait for PublicKey { + fn derive_child(&self, other: H256) -> KeyPairResult { + let child_scalar = Option::::from(k256::NonZeroScalar::from_repr( + other.take().into(), + )) + .ok_or(KeyPairError::InternalError)?; + + let projective_point: k256::ProjectivePoint = self.public.as_affine().into(); + let child_point = projective_point + (k256::AffinePoint::generator() * *child_scalar); + let public = VerifyingKey::from_affine(child_point.into()) + .map_err(|_| KeyPairError::InternalError)?; + + Ok(PublicKey { public }) + } +} diff --git a/rust/tw_keypair/src/ed25519/modifications/cardano/extended_private.rs b/rust/tw_keypair/src/ed25519/modifications/cardano/extended_private.rs index 6a2bd83124a..e4dc37cf582 100644 --- a/rust/tw_keypair/src/ed25519/modifications/cardano/extended_private.rs +++ b/rust/tw_keypair/src/ed25519/modifications/cardano/extended_private.rs @@ -12,22 +12,25 @@ use crate::ed25519::Hasher512; use crate::traits::SigningKeyTrait; use crate::{KeyPairError, KeyPairResult}; use std::ops::Range; +use std::str::FromStr; use tw_encoding::hex; use tw_hash::H256; use tw_misc::traits::ToBytesZeroizing; use zeroize::{ZeroizeOnDrop, Zeroizing}; /// Represents an `ed25519` extended private key that is used in Cardano blockchain. +#[derive(Clone)] pub struct ExtendedPrivateKey { /// The first half (96 bytes) of the extended secret. key: ExtendedSecretPart, /// The second half (96 bytes) of the extended secret. - second_key: ExtendedSecretPart, + second_key: Option>, } /// cbindgen:ignore impl ExtendedPrivateKey { /// The number of bytes in a serialized private key (192 bytes). + pub const SECRET_LEN: usize = ExtendedSecretPart::::LEN; pub const LEN: usize = ExtendedSecretPart::::LEN * 2; const KEY_RANGE: Range = 0..ExtendedSecretPart::::LEN; const SECOND_KEY_RANGE: Range = ExtendedSecretPart::::LEN..Self::LEN; @@ -35,13 +38,17 @@ impl ExtendedPrivateKey { /// Returns an associated Cardano extended `ed25519` public key. pub fn public(&self) -> ExtendedPublicKey { let key_public = PublicKey::with_expanded_secret_no_mangle(&self.key.expanded_key); - let second_key_public = - PublicKey::with_expanded_secret_no_mangle(&self.second_key.expanded_key); - let key = ExtendedPublicPart::new(key_public, self.key.chain_code); - let second_key = ExtendedPublicPart::new(second_key_public, self.second_key.chain_code); - ExtendedPublicKey::new(key, second_key) + if let Some(second_key) = &self.second_key { + let second_key_public = + PublicKey::with_expanded_secret_no_mangle(&second_key.expanded_key); + let second_key = ExtendedPublicPart::new(second_key_public, second_key.chain_code); + + ExtendedPublicKey::new(key, Some(second_key)) + } else { + ExtendedPublicKey::new(key, None) + } } } @@ -59,7 +66,9 @@ impl SigningKeyTrait for ExtendedPrivateKey { impl ToBytesZeroizing for ExtendedPrivateKey { fn to_zeroizing_vec(&self) -> Zeroizing> { let mut res = self.key.to_zeroizing_vec(); - res.extend_from_slice(self.second_key.to_zeroizing_vec().as_slice()); + if let Some(second_key) = &self.second_key { + res.extend_from_slice(second_key.to_zeroizing_vec().as_slice()); + } res } } @@ -68,13 +77,22 @@ impl<'a, H: Hasher512> TryFrom<&'a [u8]> for ExtendedPrivateKey { type Error = KeyPairError; fn try_from(bytes: &'a [u8]) -> Result { - if bytes.len() != Self::LEN { - return Err(KeyPairError::InvalidSecretKey); + if bytes.len() == Self::LEN { + let key = ExtendedSecretPart::try_from(&bytes[Self::KEY_RANGE])?; + let second_key = ExtendedSecretPart::try_from(&bytes[Self::SECOND_KEY_RANGE])?; + Ok(ExtendedPrivateKey { + key, + second_key: Some(second_key), + }) + } else if bytes.len() == Self::SECRET_LEN { + let key = ExtendedSecretPart::try_from(&bytes[Self::KEY_RANGE])?; + Ok(ExtendedPrivateKey { + key, + second_key: None, + }) + } else { + Err(KeyPairError::InvalidSecretKey) } - let key = ExtendedSecretPart::try_from(&bytes[Self::KEY_RANGE])?; - let second_key = ExtendedSecretPart::try_from(&bytes[Self::SECOND_KEY_RANGE])?; - - Ok(ExtendedPrivateKey { key, second_key }) } } @@ -87,7 +105,15 @@ impl<'a, H: Hasher512> TryFrom<&'a str> for ExtendedPrivateKey { } } -#[derive(ZeroizeOnDrop)] +impl FromStr for ExtendedPrivateKey { + type Err = KeyPairError; + + fn from_str(hex: &str) -> Result { + Self::try_from(hex) + } +} + +#[derive(ZeroizeOnDrop, Clone)] struct ExtendedSecretPart { secret: H256, extension: H256, diff --git a/rust/tw_keypair/src/ed25519/modifications/cardano/extended_public.rs b/rust/tw_keypair/src/ed25519/modifications/cardano/extended_public.rs index d3d82dba764..a9f4f596d38 100644 --- a/rust/tw_keypair/src/ed25519/modifications/cardano/extended_public.rs +++ b/rust/tw_keypair/src/ed25519/modifications/cardano/extended_public.rs @@ -18,18 +18,22 @@ pub struct ExtendedPublicKey { /// The first half of the public key (64 bytes). key: ExtendedPublicPart, /// The second half of the public key (64 bytes). - second_key: ExtendedPublicPart, + second_key: Option>, } /// cbindgen:ignore impl ExtendedPublicKey { /// The number of bytes in a serialized public key. + pub const PUBLIC_LEN: usize = ExtendedPublicPart::::LEN; pub const LEN: usize = ExtendedPublicPart::::LEN * 2; const KEY_RANGE: Range = 0..ExtendedPublicPart::::LEN; const SECOND_KEY_RANGE: Range = ExtendedPublicPart::::LEN..Self::LEN; /// Creates a public key with the given [`ExtendedPublicPart`] first and second keys. - pub(crate) fn new(key: ExtendedPublicPart, second_key: ExtendedPublicPart) -> Self { + pub(crate) fn new( + key: ExtendedPublicPart, + second_key: Option>, + ) -> Self { ExtendedPublicKey { key, second_key } } @@ -51,7 +55,9 @@ impl VerifyingKeyTrait for ExtendedPublicKey { impl ToBytesVec for ExtendedPublicKey { fn to_vec(&self) -> Vec { let mut res = self.key.to_vec(); - res.extend_from_slice(self.second_key.to_vec().as_slice()); + if let Some(second_key) = &self.second_key { + res.extend_from_slice(second_key.to_vec().as_slice()); + } res } } @@ -60,14 +66,22 @@ impl<'a, H: Hasher512> TryFrom<&'a [u8]> for ExtendedPublicKey { type Error = KeyPairError; fn try_from(bytes: &'a [u8]) -> Result { - if bytes.len() != Self::LEN { - return Err(KeyPairError::InvalidPublicKey); + if bytes.len() == Self::LEN { + let key = ExtendedPublicPart::try_from(&bytes[Self::KEY_RANGE])?; + let second_key = ExtendedPublicPart::try_from(&bytes[Self::SECOND_KEY_RANGE])?; + Ok(ExtendedPublicKey { + key, + second_key: Some(second_key), + }) + } else if bytes.len() == Self::PUBLIC_LEN { + let key = ExtendedPublicPart::try_from(&bytes[Self::KEY_RANGE])?; + Ok(ExtendedPublicKey { + key, + second_key: None, + }) + } else { + Err(KeyPairError::InvalidPublicKey) } - - let key = ExtendedPublicPart::try_from(&bytes[Self::KEY_RANGE])?; - let second_key = ExtendedPublicPart::try_from(&bytes[Self::SECOND_KEY_RANGE])?; - - Ok(ExtendedPublicKey { key, second_key }) } } diff --git a/rust/tw_keypair/src/ed25519/modifications/waves/private.rs b/rust/tw_keypair/src/ed25519/modifications/waves/private.rs index 4c6c68f5e46..bcbabb2b4b3 100644 --- a/rust/tw_keypair/src/ed25519/modifications/waves/private.rs +++ b/rust/tw_keypair/src/ed25519/modifications/waves/private.rs @@ -2,6 +2,8 @@ // // Copyright © 2017 Trust Wallet. +use std::str::FromStr; + use crate::ed25519::modifications::waves::public::PublicKey; use crate::ed25519::modifications::waves::signature::Signature; use crate::ed25519::{private::PrivateKey as StandardPrivateKey, Hasher512}; @@ -12,6 +14,7 @@ use tw_misc::traits::ToBytesZeroizing; use zeroize::Zeroizing; /// Represents an `ed25519` private key that is used in Waves blockchain. +#[derive(Clone)] pub struct PrivateKey { standard_key: StandardPrivateKey, } @@ -52,6 +55,14 @@ impl<'a, H: Hasher512> TryFrom<&'a str> for PrivateKey { } } +impl FromStr for PrivateKey { + type Err = KeyPairError; + + fn from_str(hex: &str) -> Result { + Self::try_from(hex) + } +} + impl ToBytesZeroizing for PrivateKey { fn to_zeroizing_vec(&self) -> Zeroizing> { self.standard_key.to_zeroizing_vec() diff --git a/rust/tw_keypair/src/ed25519/private.rs b/rust/tw_keypair/src/ed25519/private.rs index ce0c2ef056f..c9cf6baf1f1 100644 --- a/rust/tw_keypair/src/ed25519/private.rs +++ b/rust/tw_keypair/src/ed25519/private.rs @@ -9,6 +9,7 @@ use crate::ed25519::Hasher512; use crate::traits::SigningKeyTrait; use crate::{KeyPairError, KeyPairResult}; use std::fmt; +use std::str::FromStr; use tw_encoding::hex; use tw_hash::H256; use tw_misc::traits::ToBytesZeroizing; @@ -70,6 +71,14 @@ impl<'a, H: Hasher512> TryFrom<&'a str> for PrivateKey { } } +impl FromStr for PrivateKey { + type Err = KeyPairError; + + fn from_str(hex: &str) -> Result { + Self::try_from(hex) + } +} + impl ToBytesZeroizing for PrivateKey { fn to_zeroizing_vec(&self) -> Zeroizing> { Zeroizing::new(self.secret.to_vec()) diff --git a/rust/tw_keypair/src/traits.rs b/rust/tw_keypair/src/traits.rs index 5ac9e30c773..5f78d89a82b 100644 --- a/rust/tw_keypair/src/traits.rs +++ b/rust/tw_keypair/src/traits.rs @@ -3,6 +3,7 @@ // Copyright © 2017 Trust Wallet. use crate::KeyPairResult; +use tw_hash::H256; use tw_misc::traits::{FromSlice, ToBytesVec, ToBytesZeroizing}; pub trait KeyPairTrait: FromSlice + SigningKeyTrait + VerifyingKeyTrait { @@ -31,3 +32,7 @@ pub trait VerifyingKeyTrait { /// Verifies if the given `hash` was signed using the private key. fn verify(&self, signature: Self::VerifySignature, message: Self::SigningMessage) -> bool; } + +pub trait DerivableKeyTrait: Sized { + fn derive_child(&self, other: H256) -> KeyPairResult; +} diff --git a/rust/tw_keypair/src/zilliqa_schnorr/private.rs b/rust/tw_keypair/src/zilliqa_schnorr/private.rs index 71749063c34..e61ff8091ab 100644 --- a/rust/tw_keypair/src/zilliqa_schnorr/private.rs +++ b/rust/tw_keypair/src/zilliqa_schnorr/private.rs @@ -5,6 +5,7 @@ use super::PublicKey; use super::Signature; use crate::ecdsa::canonical::generate_k; +use crate::traits::DerivableKeyTrait; use crate::KeyPairResult; use crate::{traits::SigningKeyTrait, KeyPairError}; use ecdsa::elliptic_curve::PrimeField; @@ -22,7 +23,10 @@ use sha2::{Digest, Sha256}; use std::ops::Deref; use std::str::FromStr; use tw_encoding::hex; +use tw_hash::H256; +use tw_misc::traits::ToBytesZeroizing; use zeroize::ZeroizeOnDrop; +use zeroize::Zeroizing; type CurveDigest = ::Digest; @@ -115,6 +119,13 @@ impl Deref for PrivateKey { } } +impl ToBytesZeroizing for PrivateKey { + fn to_zeroizing_vec(&self) -> Zeroizing> { + let secret = Zeroizing::new(self.0.to_bytes()); + Zeroizing::new(secret.as_slice().to_vec()) + } +} + impl SigningKeyTrait for PrivateKey { type SigningMessage = Vec; type Signature = Signature; @@ -146,6 +157,23 @@ impl SigningKeyTrait for PrivateKey { } } +impl DerivableKeyTrait for PrivateKey { + fn derive_child(&self, other: H256) -> KeyPairResult { + let child_scalar = Option::::from(k256::NonZeroScalar::from_repr( + other.take().into(), + )) + .ok_or(KeyPairError::InternalError)?; + + let derived_scalar = self.0.to_nonzero_scalar().as_ref() + child_scalar.as_ref(); + + let secret = Option::::from(k256::NonZeroScalar::new(derived_scalar)) + .map(Into::into) + .ok_or(KeyPairError::InternalError)?; + + Ok(PrivateKey(secret)) + } +} + #[cfg(test)] mod tests { use super::{PrivateKey, PublicKey}; diff --git a/rust/tw_keypair/src/zilliqa_schnorr/public.rs b/rust/tw_keypair/src/zilliqa_schnorr/public.rs index 6d22bc03588..269c9fc81fc 100644 --- a/rust/tw_keypair/src/zilliqa_schnorr/public.rs +++ b/rust/tw_keypair/src/zilliqa_schnorr/public.rs @@ -2,8 +2,9 @@ // // Copyright © 2017 Trust Wallet. -use crate::traits::VerifyingKeyTrait; -use crate::KeyPairError; +use crate::traits::{DerivableKeyTrait, VerifyingKeyTrait}; +use crate::{KeyPairError, KeyPairResult}; +use ecdsa::elliptic_curve::group::prime::PrimeCurveAffine; use k256::{ elliptic_curve::{ops::Reduce, sec1::ToEncodedPoint, Group}, AffinePoint, Scalar, U256, @@ -13,6 +14,7 @@ use std::fmt::Display; use std::ops::Deref; use std::str::FromStr; use tw_encoding::hex; +use tw_hash::H256; use tw_misc::traits::ToBytesVec; pub type VerifySignature = super::Signature; @@ -117,3 +119,19 @@ impl VerifyingKeyTrait for PublicKey { true } } + +impl DerivableKeyTrait for PublicKey { + fn derive_child(&self, other: H256) -> KeyPairResult { + let child_scalar = Option::::from(k256::NonZeroScalar::from_repr( + other.take().into(), + )) + .ok_or(KeyPairError::InternalError)?; + + let projective_point: k256::ProjectivePoint = self.0.as_affine().into(); + let child_point = projective_point + (k256::AffinePoint::generator() * *child_scalar); + let public = k256::PublicKey::from_affine(child_point.into()) + .map_err(|_| KeyPairError::InternalError)?; + + Ok(PublicKey(public)) + } +} diff --git a/src/HDWallet.cpp b/src/HDWallet.cpp index 9de2cb9e03b..d94a67b7f21 100644 --- a/src/HDWallet.cpp +++ b/src/HDWallet.cpp @@ -16,25 +16,16 @@ #include #include -#include - -#include -#include -#include +#include "Generated/HDNode.h" +#include +#include "Generated/HDNodePublic.h" +#include #include #include using namespace TW; -namespace { - -uint32_t fingerprint(HDNode* node, Hash::Hasher hasher); -std::string serialize(const HDNode* node, uint32_t fingerprint, uint32_t version, bool use_public, Hash::Hasher hasher); -bool deserialize(const std::string& extended, TWCurve curve, Hash::Hasher hasher, HDNode* node); -const char* curveName(TWCurve curve); -} // namespace - template HDWallet::HDWallet(const Data& seed) { std::copy_n(seed.begin(), seedSize, this->seed.begin()); @@ -89,102 +80,60 @@ HDWallet::~HDWallet() { } template -static HDNode getMasterNode(const HDWallet& wallet, TWCurve curve) { - const auto privateKeyType = PrivateKey::getType(curve); - HDNode node; - switch (privateKeyType) { - case TWPrivateKeyTypeCardano: { - // Derives the root Cardano HDNode from a passphrase and the entropy encoded in - // a BIP-0039 mnemonic using the Icarus derivation (V2) scheme - const auto entropy = wallet.getEntropy(); - uint8_t secret[CARDANO_SECRET_LENGTH]; - secret_from_entropy_cardano_icarus((const uint8_t*)"", 0, entropy.data(), int(entropy.size()), secret, nullptr); - hdnode_from_secret_cardano(secret, &node); - TW::memzero(secret, CARDANO_SECRET_LENGTH); - break; - } - case TWPrivateKeyTypeDefault: - default: - hdnode_from_seed(wallet.getSeed().data(), HDWallet::mSeedSize, curveName(curve), &node); - break; +static TWHDNode* getMasterNode(const HDWallet& wallet, TWCurve curve) { + TWData* seedData = nullptr; + if (curve == TWCurveED25519ExtendedCardano) { + seedData = TWDataCreateWithBytes(wallet.getEntropy().data(), wallet.getEntropy().size()); + } else { + seedData = TWDataCreateWithBytes(wallet.getSeed().data(), wallet.getSeed().size()); } + auto node = TWHDNodeCreateWithSeed(seedData, curve); + TWDataDelete(seedData); return node; } template -static HDNode getNode(const HDWallet& wallet, TWCurve curve, const DerivationPath& derivationPath) { - const auto privateKeyType = PrivateKey::getType(curve); +static TWHDNode* getNode(const HDWallet& wallet, TWCurve curve, const DerivationPath& derivationPath, TW::Hash::Hasher hasher) { auto node = getMasterNode(wallet, curve); - for (auto& index : derivationPath.indices) { - switch (privateKeyType) { - case TWPrivateKeyTypeCardano: - hdnode_private_ckd_cardano(&node, index.derivationIndex()); - break; - case TWPrivateKeyTypeDefault: - default: - hdnode_private_ckd(&node, index.derivationIndex()); - break; - } + if (node == nullptr) { + return nullptr; } - return node; + auto derivationPathString = TWStringCreateWithUTF8Bytes(derivationPath.string().c_str()); + auto childNode = TWHDNodeDeriveFromPath(node, derivationPathString, hasher); + TWStringDelete(derivationPathString); + TWHDNodeDelete(node); + return childNode; } template PrivateKey HDWallet::getMasterKey(TWCurve curve) const { - auto node = getMasterNode(*this, curve); - auto data = Data(node.private_key, node.private_key + PrivateKey::_size); - return PrivateKey(data, curve); -} - -template -PrivateKey HDWallet::getMasterKeyExtension(TWCurve curve) const { - auto node = getMasterNode(*this, curve); - auto data = Data(node.private_key_extension, node.private_key_extension + PrivateKey::_size); + auto node = getMasterNode(*this, curve); + if (node == nullptr) { + throw std::invalid_argument("Invalid master node"); + } + auto privateKeyData = TWHDNodePrivateKeyData(node); + TWHDNodeDelete(node); + auto data = Data(TWDataBytes(privateKeyData), TWDataBytes(privateKeyData) + TWDataSize(privateKeyData)); + TWDataDelete(privateKeyData); return PrivateKey(data, curve); } -template -DerivationPath HDWallet::cardanoStakingDerivationPath(const DerivationPath& path) { - DerivationPath stakingPath = path; - stakingPath.indices[3].value = 2; - stakingPath.indices[4].value = 0; - return stakingPath; -} - template PrivateKey HDWallet::getKeyByCurve(TWCurve curve, const DerivationPath& derivationPath) const { - const auto privateKeyType = PrivateKey::getType(curve); - auto node = getNode(*this, curve, derivationPath); - switch (privateKeyType) { - case TWPrivateKeyTypeCardano: { - if (derivationPath.indices.size() < 4 || derivationPath.indices[3].value > 1) { - // invalid derivation path - return PrivateKey(Data(PrivateKey::cardanoKeySize), curve); - } - const DerivationPath stakingPath = cardanoStakingDerivationPath(derivationPath); - - auto pkData = Data(node.private_key, node.private_key + PrivateKey::_size); - auto extData = Data(node.private_key_extension, node.private_key_extension + PrivateKey::_size); - auto chainCode = Data(node.chain_code, node.chain_code + PrivateKey::_size); - - // repeat with staking path - const auto node2 = getNode(*this, curve, stakingPath); - auto pkData2 = Data(node2.private_key, node2.private_key + PrivateKey::_size); - auto extData2 = Data(node2.private_key_extension, node2.private_key_extension + PrivateKey::_size); - auto chainCode2 = Data(node2.chain_code, node2.chain_code + PrivateKey::_size); - - TW::memzero(&node); - return PrivateKey(pkData, extData, chainCode, pkData2, extData2, chainCode2, curve); + auto curveToUse = curve == TWCurveStarkex ? TWCurveSECP256k1 : curve; + auto node = getNode(*this, curveToUse, derivationPath, Hash::HasherSha256ripemd); + if (node == nullptr) { + throw std::invalid_argument("Invalid derivation path"); } - case TWPrivateKeyTypeDefault: - default: - // default path - auto data = Data(node.private_key, node.private_key + PrivateKey::_size); - TW::memzero(&node); - if (curve == TWCurveStarkex) { - return ImmutableX::getPrivateKeyFromEthPrivKey(PrivateKey(data, TWCurveSECP256k1)); - } - return PrivateKey(data, curve); + auto privateKeyData = TWHDNodePrivateKeyData(node); + TWHDNodeDelete(node); + auto data = Data(TWDataBytes(privateKeyData), TWDataBytes(privateKeyData) + TWDataSize(privateKeyData)); + TWDataDelete(privateKeyData); + auto privateKey = PrivateKey(data, curveToUse); + if (curve == TWCurveStarkex) { + return ImmutableX::getPrivateKeyFromEthPrivKey(privateKey); + } else { + return privateKey; } } @@ -204,7 +153,15 @@ template std::string HDWallet::getRootKey(TWCoinType coin, TWHDVersion version) const { const auto curve = TWCoinTypeCurve(coin); auto node = getMasterNode(*this, curve); - return serialize(&node, 0, version, false, base58Hasher(coin)); + if (node == nullptr) { + return "Unsupported curve"; + } + auto extendedPrivateKey = TWHDNodeExtendedPrivateKey(node, version, TW::base58Hasher(coin)); + TWHDNodeDelete(node); + auto* bytes = TWStringUTF8Bytes(extendedPrivateKey); + std::string result(bytes); + TWStringDelete(extendedPrivateKey); + return result; } template @@ -226,11 +183,17 @@ std::string HDWallet::getExtendedPrivateKeyAccount(TWPurpose purpose, const auto curve = TWCoinTypeCurve(coin); const auto path = TW::derivationPath(coin, derivation); - auto derivationPath = DerivationPath({DerivationPathIndex(purpose, true), DerivationPathIndex(path.coin(), true)}); - auto node = getNode(*this, curve, derivationPath); - auto fingerprintValue = fingerprint(&node, publicKeyHasher(coin)); - hdnode_private_ckd(&node, account + 0x80000000); - return serialize(&node, fingerprintValue, version, false, base58Hasher(coin)); + auto derivationPath = DerivationPath({DerivationPathIndex(purpose, true), DerivationPathIndex(path.coin(), true), DerivationPathIndex(account, true)}); + auto node = getNode(*this, curve, derivationPath, TW::publicKeyHasher(coin)); + if (node == nullptr) { + return "Invalid derivation path"; + } + auto extendedPrivateKey = TWHDNodeExtendedPrivateKey(node, version, TW::base58Hasher(coin)); + TWHDNodeDelete(node); + auto* bytes = TWStringUTF8Bytes(extendedPrivateKey); + std::string result(bytes); + TWStringDelete(extendedPrivateKey); + return result; } template @@ -241,64 +204,75 @@ std::string HDWallet::getExtendedPublicKeyAccount(TWPurpose purpose, T const auto curve = TWCoinTypeCurve(coin); const auto path = TW::derivationPath(coin, derivation); - auto derivationPath = DerivationPath({DerivationPathIndex(purpose, true), DerivationPathIndex(path.coin(), true)}); - auto node = getNode(*this, curve, derivationPath); - auto fingerprintValue = fingerprint(&node, publicKeyHasher(coin)); - hdnode_private_ckd(&node, account + 0x80000000); - hdnode_fill_public_key(&node); - return serialize(&node, fingerprintValue, version, true, base58Hasher(coin)); + auto derivationPath = DerivationPath({DerivationPathIndex(purpose, true), DerivationPathIndex(path.coin(), true), DerivationPathIndex(account, true)}); + auto node = getNode(*this, curve, derivationPath, TW::publicKeyHasher(coin)); + if (node == nullptr) { + return "Invalid derivation path"; + } + auto extendedPublicKey = TWHDNodeExtendedPublicKey(node, version, TW::base58Hasher(coin)); + TWHDNodeDelete(node); + auto* bytes = TWStringUTF8Bytes(extendedPublicKey); + std::string result(bytes); + TWStringDelete(extendedPublicKey); + return result; } template std::optional HDWallet::getPublicKeyFromExtended(const std::string& extended, TWCoinType coin, const DerivationPath& path) { const auto curve = TW::curve(coin); - const auto hasher = TW::base58Hasher(coin); - - auto node = HDNode{}; - if (!deserialize(extended, curve, hasher, &node)) { + auto extendedString = TWStringCreateWithUTF8Bytes(extended.c_str()); + auto node = TWHDNodePublicCreateWithExtendedPublicKey(extendedString, curve, TW::base58Hasher(coin)); + TWStringDelete(extendedString); + if (node == nullptr) { return {}; } - if (node.curve->params == nullptr) { + auto childPath = DerivationPath({DerivationPathIndex(path.change(), false), DerivationPathIndex(path.address(), false)}); + auto childPathString = TWStringCreateWithUTF8Bytes(childPath.string().c_str()); + auto childNode = TWHDNodePublicDeriveFromPath(node, childPathString, TW::publicKeyHasher(coin)); + TWHDNodePublicDelete(node); + TWStringDelete(childPathString); + if (childNode == nullptr) { return {}; } - hdnode_public_ckd(&node, path.change()); - hdnode_public_ckd(&node, path.address()); - hdnode_fill_public_key(&node); - // These public key type are not applicable. Handled above, as node.curve->params is null - assert(curve != TWCurveED25519 && curve != TWCurveED25519Blake2bNano && curve != TWCurveED25519ExtendedCardano && curve != TWCurveCurve25519); + auto pubkeyData = TWHDNodePublicPublicKeyData(childNode); + TWHDNodePublicDelete(childNode); + auto data = Data(TWDataBytes(pubkeyData), TWDataBytes(pubkeyData) + TWDataSize(pubkeyData)); + TWDataDelete(pubkeyData); TWPublicKeyType keyType = TW::publicKeyType(coin); - if (curve == TWCurveSECP256k1) { - auto pubkey = PublicKey(Data(node.public_key, node.public_key + 33), TWPublicKeyTypeSECP256k1); - if (keyType == TWPublicKeyTypeSECP256k1Extended) { - return pubkey.extended(); - } else { - return pubkey; - } - } else if (curve == TWCurveNIST256p1) { - auto pubkey = PublicKey(Data(node.public_key, node.public_key + 33), TWPublicKeyTypeNIST256p1); - if (keyType == TWPublicKeyTypeNIST256p1Extended) { - return pubkey.extended(); - } else { - return pubkey; - } + if (keyType == TWPublicKeyTypeSECP256k1Extended) { + auto pubkey = PublicKey(data, TWPublicKeyTypeSECP256k1); + return pubkey.extended(); + } else if (keyType == TWPublicKeyTypeNIST256p1Extended) { + auto pubkey = PublicKey(data, TWPublicKeyTypeNIST256p1); + return pubkey.extended(); + } else { + return PublicKey(data, keyType); } - return {}; } template std::optional HDWallet::getPrivateKeyFromExtended(const std::string& extended, TWCoinType coin, const DerivationPath& path) { const auto curve = TW::curve(coin); - const auto hasher = TW::base58Hasher(coin); - - auto node = HDNode{}; - if (!deserialize(extended, curve, hasher, &node)) { + auto extendedString = TWStringCreateWithUTF8Bytes(extended.c_str()); + auto node = TWHDNodeCreateWithExtendedPrivateKey(extendedString, curve, TW::base58Hasher(coin)); + TWStringDelete(extendedString); + if (node == nullptr) { return {}; } - hdnode_private_ckd(&node, path.change()); - hdnode_private_ckd(&node, path.address()); - - return PrivateKey(Data(node.private_key, node.private_key + 32), curve); + auto childPath = DerivationPath({DerivationPathIndex(path.change(), false), DerivationPathIndex(path.address(), false)}); + auto childPathString = TWStringCreateWithUTF8Bytes(childPath.string().c_str()); + auto childNode = TWHDNodeDeriveFromPath(node, childPathString, TW::publicKeyHasher(coin)); + TWHDNodeDelete(node); + TWStringDelete(childPathString); + if (childNode == nullptr) { + return {}; + } + auto privkeyData = TWHDNodePrivateKeyData(childNode); + TWHDNodeDelete(childNode); + auto data = Data(TWDataBytes(privkeyData), TWDataBytes(privkeyData) + TWDataSize(privkeyData)); + TWDataDelete(privkeyData); + return PrivateKey(data, curve); } template @@ -308,88 +282,6 @@ PrivateKey HDWallet::bip32DeriveRawSeed(TWCoinType coin, const Data& s return wallet.getKeyByCurve(curve, path); } -namespace { - -uint32_t fingerprint(HDNode* node, Hash::Hasher hasher) { - hdnode_fill_public_key(node); - auto digest = Hash::hash(hasher, node->public_key, 33); - return ((uint32_t)digest[0] << 24) + (digest[1] << 16) + (digest[2] << 8) + digest[3]; -} - -std::string serialize(const HDNode* node, uint32_t fingerprint, uint32_t version, bool use_public, Hash::Hasher hasher) { - Data node_data; - node_data.reserve(78); - - encode32BE(version, node_data); - node_data.push_back(static_cast(node->depth)); - encode32BE(fingerprint, node_data); - encode32BE(node->child_num, node_data); - node_data.insert(node_data.end(), node->chain_code, node->chain_code + 32); - if (use_public) { - node_data.insert(node_data.end(), node->public_key, node->public_key + 33); - } else { - node_data.push_back(0); - node_data.insert(node_data.end(), node->private_key, node->private_key + 32); - } - - return Base58::encodeCheck(node_data, Rust::Base58Alphabet::Bitcoin, hasher); -} - -bool deserialize(const std::string& extended, TWCurve curve, Hash::Hasher hasher, HDNode* node) { - TW::memzero(node); - const char* curveNameStr = curveName(curve); - if (curveNameStr == nullptr || std::string(curveNameStr).empty()) { - return false; - } - node->curve = get_curve_by_name(curveNameStr); - assert(node->curve != nullptr); - - const auto node_data = Base58::decodeCheck(extended, Rust::Base58Alphabet::Bitcoin, hasher); - if (node_data.size() != 78) { - return false; - } - - uint32_t version = decode32BE(node_data.data()); - if (TWHDVersionIsPublic(static_cast(version))) { - std::copy(node_data.begin() + 45, node_data.begin() + 45 + 33, node->public_key); - } else if (TWHDVersionIsPrivate(static_cast(version))) { - if (node_data[45]) { // invalid data - return false; - } - std::copy(node_data.begin() + 46, node_data.begin() + 46 + 32, node->private_key); - } else { - return false; // invalid version - } - node->depth = node_data[4]; - node->child_num = decode32BE(node_data.data() + 9); - std::copy(node_data.begin() + 13, node_data.begin() + 13 + 32, node->chain_code); - return true; -} - -const char* curveName(TWCurve curve) { - switch (curve) { - case TWCurveStarkex: - case TWCurveSECP256k1: - case TWCurveZILLIQASchnorr: - return SECP256K1_NAME; - case TWCurveED25519: - return ED25519_NAME; - case TWCurveED25519Blake2bNano: - return ED25519_BLAKE2B_NANO_NAME; - case TWCurveED25519ExtendedCardano: - return ED25519_CARDANO_NAME; - case TWCurveNIST256p1: - return NIST256P1_NAME; - case TWCurveCurve25519: - return CURVE25519_NAME; - case TWCurveNone: - default: - return ""; - } -} - -} // namespace - namespace TW { template class HDWallet<32>; template class HDWallet<64>; diff --git a/src/HDWallet.h b/src/HDWallet.h index 7cff0fbcb2e..96d417f383b 100644 --- a/src/HDWallet.h +++ b/src/HDWallet.h @@ -74,9 +74,6 @@ class HDWallet { /// Returns master key. PrivateKey getMasterKey(TWCurve curve) const; - /// Returns the master private key extension (32 byte). - PrivateKey getMasterKeyExtension(TWCurve curve) const; - /// Returns the private key with the given derivation. PrivateKey getKey(const TWCoinType coin, TWDerivation derivation) const; diff --git a/tests/chains/Cardano/AddressTests.cpp b/tests/chains/Cardano/AddressTests.cpp index 8c042b46543..0edcac40785 100644 --- a/tests/chains/Cardano/AddressTests.cpp +++ b/tests/chains/Cardano/AddressTests.cpp @@ -236,7 +236,7 @@ TEST(CardanoAddress, MnemonicToAddressV3) { // V2 Tested against AdaLite auto mnemonicPlay1 = "youth away raise north opinion slice dash bus soldier dizzy bitter increase saddle live champion"; auto wallet = HDWallet(mnemonicPlay1, ""); - PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); + PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, 0, 0, 0)); PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); string addr = AddressV2(publicKey).string(); EXPECT_EQ("Ae2tdPwUPEZJYT9g1JgQWtLveUHavyRxQGi6hVzoQjct7yyCLGgk3pCyx7h", addr); @@ -245,7 +245,7 @@ TEST(CardanoAddress, MnemonicToAddressV3) { // V2 Tested against AdaLite auto mnemonicPlay2 = "return custom two home gain guilt kangaroo supply market current curtain tomorrow heavy blue robot"; auto wallet = HDWallet(mnemonicPlay2, ""); - PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); + PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, 0, 0, 0)); PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); string addr = AddressV2(publicKey).string(); EXPECT_EQ("Ae2tdPwUPEZLtJx7LA2XZ3zzwonH9x9ieX3dMzaTBD3TfXuKczjMSjTecr1", addr); @@ -255,7 +255,7 @@ TEST(CardanoAddress, MnemonicToAddressV3) { // In AdaLite V1 addr0 is DdzFFzCqrht7HGoJ87gznLktJGywK1LbAJT2sbd4txmgS7FcYLMQFhawb18ojS9Hx55mrbsHPr7PTraKh14TSQbGBPJHbDZ9QVh6Z6Di auto mnemonicALDemo = "civil void tool perfect avocado sweet immense fluid arrow aerobic boil flash"; auto wallet = HDWallet(mnemonicALDemo, ""); - PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, DerivationPathIndex(0, true).derivationIndex(), 0, 0)); + PrivateKey privateKey = wallet.getKey(TWCoinTypeCardano, DerivationPath(TWPurposeBIP44, TWCoinTypeCardano, 0, 0, 0)); PublicKey publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano); string addr = AddressV2(publicKey).string(); EXPECT_EQ("Ae2tdPwUPEZJbLcD8iLgN7hVGvq66WdR4zocntRekSP97Ds3MvCfmEDjJYu", addr); From ee9430d0851ed10d14ae75cdaade434e0b7510bf Mon Sep 17 00:00:00 2001 From: gupnik Date: Fri, 23 May 2025 13:11:45 +0530 Subject: [PATCH 23/23] Remove trezor crypto (#4404) * Adds derivation for various curves * Adds FFI integration * Adds ability to provide hashers in FFI * Trigger Build * FMT * Fix clippy * Fixes ffi issues * FMT * Minor fix * Minor * Fixes sonar cube issues * More sonarqube fixes * Fix memory leaks * Fixes another memory leak * Minor * Minor * Addresses review comments and adds tests * FMT * Makes clippy happy * Initial setup * Remove from scripts * Removes trezor crypto * Minor * Minor * Fixes test * Fixes * Fixes * Minor * Fix sonarqube issue * Fix sonarqube issues * Address review comments --- .clang-format | 2 +- .github/workflows/linux-ci.yml | 3 +- CMakeLists.txt | 7 +- WalletCore.podspec | 11 +- include/TrustWalletCore/TWPBKDF2.h | 36 - rust/tw_crypto/src/crypto_pbkdf2/mod.rs | 13 +- rust/tw_crypto/src/ffi/crypto_pbkdf2.rs | 38 +- rust/tw_crypto/tests/crypto_pbkdf2_ffi.rs | 27 +- .../tw_keypair/src/ecdsa/nist256p1/private.rs | 21 +- rust/tw_keypair/src/ecdsa/nist256p1/public.rs | 22 + rust/tw_keypair/src/ecdsa/secp256k1/mod.rs | 16 - .../tw_keypair/src/ecdsa/secp256k1/private.rs | 17 +- rust/tw_keypair/src/ecdsa/secp256k1/public.rs | 23 + .../src/ed25519/modifications/waves/public.rs | 9 + .../ed25519/modifications/waves/signature.rs | 2 +- rust/tw_keypair/src/ffi/curve25519.rs | 24 + rust/tw_keypair/src/ffi/ecdsa.rs | 132 + rust/tw_keypair/src/ffi/mod.rs | 2 + rust/tw_keypair/tests/ed25519_waves_tests.rs | 16 + rust/tw_keypair/tests/nist256p1_tests.rs | 78 +- .../tw_keypair/tests/private_key_ffi_tests.rs | 21 +- rust/tw_keypair/tests/public_key_ffi_tests.rs | 75 + rust/tw_keypair/tests/secp256k1_tests.rs | 76 +- samples/cpp/CMakeLists.txt | 4 +- samples/cpp/README.md | 2 +- samples/go/core/bitcoin.go | 2 +- samples/go/core/coin.go | 2 +- samples/go/core/datavector.go | 2 +- samples/go/core/mnemonic.go | 2 +- samples/go/core/publicKey.go | 2 +- samples/go/core/transaction.go | 2 +- samples/go/core/transactionHelper.go | 2 +- samples/go/core/wallet.go | 2 +- samples/go/dev-console/native/cgo.go | 2 +- samples/go/dev-console/prepare.sh | 1 - samples/go/types/twdata.go | 2 +- samples/go/types/twstring.go | 2 +- samples/rust/README.md | 2 +- samples/rust/src/build.rs | 3 +- src/Bech32Address.cpp | 1 - src/Bitcoin/CashAddress.cpp | 14 +- src/Bitcoin/SegwitAddress.cpp | 15 +- src/EOS/Address.cpp | 39 +- src/EOS/Signer.cpp | 3 - src/EOS/Transaction.cpp | 8 +- src/FIO/Address.cpp | 18 +- src/FIO/Encryption.cpp | 32 +- src/FIO/Signer.cpp | 3 - src/Groestlcoin/Address.cpp | 12 +- src/Icon/Address.cpp | 6 +- src/Keystore/AESParameters.cpp | 3 +- src/Keystore/EncryptionParameters.cpp | 2 +- src/Keystore/PBKDF2Parameters.cpp | 3 +- src/Keystore/ScryptParameters.cpp | 2 +- src/Keystore/StoredKey.cpp | 2 +- src/NULS/Address.cpp | 12 +- src/NULS/Signer.cpp | 14 +- src/Nano/Address.cpp | 89 +- src/Nimiq/Address.cpp | 6 +- src/Nimiq/Signer.cpp | 1 - src/Ontology/Address.cpp | 19 +- src/Ontology/ParamsBuilder.cpp | 19 +- src/PrivateKey.cpp | 2 +- src/PublicKey.cpp | 2 +- src/Stellar/Address.cpp | 2 +- src/Tezos/Address.cpp | 2 - src/Tezos/BinaryCoding.cpp | 1 - src/Utils.h | 17 + .../crypto/cash_addr.c => src/cash_addr.cpp | 24 +- .../include/TrezorCrypto => src}/cash_addr.h | 0 src/interface/TWBitcoinAddress.cpp | 1 - src/interface/TWHash.cpp | 11 +- src/interface/TWPBKDF2.cpp | 46 - src/interface/TWPrivateKey.cpp | 4 +- src/interface/TWPublicKey.cpp | 3 - src/interface/TWSegwitAddress.cpp | 1 - src/interface/TWString.cpp | 2 +- src/memory/memzero_wrapper.h | 2 +- {trezor-crypto/crypto => src}/memzero.c | 0 .../include/TrezorCrypto => src}/memzero.h | 0 trezor-crypto/crypto/rand.c => src/rand.cpp | 11 +- .../include/TrezorCrypto => src}/rand.h | 0 swift/common-xcframework.yml | 74 +- swift/project.yml | 74 +- tests/CMakeLists.txt | 2 +- tests/chains/NEO/SignerTests.cpp | 1 - tests/chains/NEO/TWAnySignerTests.cpp | 1 - tests/chains/Waves/SignerTests.cpp | 7 +- .../common/HDWallet/HDWalletInternalTests.cpp | 154 - tests/common/PrivateKeyTests.cpp | 22 +- tests/common/PublicKeyLegacy.h | 18 - tests/interface/TWPBKDF2Tests.cpp | 4 +- tools/build-and-test | 5 +- tools/coverage | 3 +- tools/tests | 3 +- trezor-crypto/.gitignore | 14 - trezor-crypto/CMakeLists.txt | 89 - trezor-crypto/crypto/AUTHORS | 2 - trezor-crypto/crypto/CONTRIBUTORS | 16 - trezor-crypto/crypto/LICENSE | 22 - trezor-crypto/crypto/README.md | 45 - trezor-crypto/crypto/address.c | 91 - trezor-crypto/crypto/aes/aes_modes.c | 957 -- trezor-crypto/crypto/aes/aescrypt.c | 307 - trezor-crypto/crypto/aes/aeskey.c | 561 - trezor-crypto/crypto/aes/aestab.c | 418 - trezor-crypto/crypto/base32.c | 241 - trezor-crypto/crypto/base58.c | 274 - trezor-crypto/crypto/bignum.c | 1827 ---- trezor-crypto/crypto/bip32.c | 834 -- trezor-crypto/crypto/bip39.c | 305 - trezor-crypto/crypto/blake256.c | 234 - trezor-crypto/crypto/blake2b.c | 334 - trezor-crypto/crypto/blake2s.c | 318 - trezor-crypto/crypto/cardano.c | 307 - trezor-crypto/crypto/chacha20poly1305/LICENSE | 21 - .../chacha20poly1305/chacha20poly1305.c | 60 - .../crypto/chacha20poly1305/chacha_merged.c | 253 - .../crypto/chacha20poly1305/poly1305-donna.c | 179 - .../crypto/chacha20poly1305/rfc7539.c | 48 - trezor-crypto/crypto/chacha_drbg.c | 126 - trezor-crypto/crypto/curves.c | 42 - trezor-crypto/crypto/ecdsa.c | 1251 --- trezor-crypto/crypto/ed25519-donna/README.md | 183 - .../ed25519-donna/curve25519-donna-32bit.c | 681 -- .../ed25519-donna/curve25519-donna-helpers.c | 66 - .../curve25519-donna-scalarmult-base.c | 67 - .../crypto/ed25519-donna/ed25519-blake2b.c | 8 - .../ed25519-donna-32bit-tables.c | 63 - .../ed25519-donna-basepoint-table.c | 261 - .../ed25519-donna/ed25519-donna-impl-base.c | 730 -- .../crypto/ed25519-donna/ed25519-keccak.c | 8 - .../crypto/ed25519-donna/ed25519-sha3.c | 8 - trezor-crypto/crypto/ed25519-donna/ed25519.c | 297 - .../crypto/ed25519-donna/modm-donna-32bit.c | 517 - trezor-crypto/crypto/groestl.c | 783 -- trezor-crypto/crypto/hasher.c | 147 - trezor-crypto/crypto/hmac.c | 176 - trezor-crypto/crypto/hmac_drbg.c | 130 - trezor-crypto/crypto/monero/base58.c | 246 - trezor-crypto/crypto/monero/base58.h | 43 - trezor-crypto/crypto/monero/int-util.h | 77 - trezor-crypto/crypto/monero/monero.h | 21 - trezor-crypto/crypto/monero/range_proof.c | 115 - trezor-crypto/crypto/monero/range_proof.h | 31 - trezor-crypto/crypto/monero/serialize.c | 53 - trezor-crypto/crypto/monero/serialize.h | 15 - trezor-crypto/crypto/monero/xmr.c | 143 - trezor-crypto/crypto/monero/xmr.h | 76 - trezor-crypto/crypto/nano.c | 151 - trezor-crypto/crypto/nem.c | 507 - trezor-crypto/crypto/nist256p1.c | 69 - trezor-crypto/crypto/nist256p1.table | 1664 --- trezor-crypto/crypto/pbkdf2.c | 179 - trezor-crypto/crypto/rc4.c | 56 - trezor-crypto/crypto/rfc6979.c | 62 - trezor-crypto/crypto/ripemd160.c | 343 - trezor-crypto/crypto/script.c | 66 - trezor-crypto/crypto/scrypt.c | 340 - trezor-crypto/crypto/secp256k1.c | 94 - trezor-crypto/crypto/secp256k1.table | 1664 --- trezor-crypto/crypto/setup.py | 39 - trezor-crypto/crypto/sha2.c | 1317 --- trezor-crypto/crypto/sha3.c | 397 - trezor-crypto/crypto/shamir.c | 335 - trezor-crypto/crypto/slip39.c | 151 - trezor-crypto/crypto/sodium/keypair.c | 63 - .../crypto/sodium/private/ed25519_ref10.c | 481 - .../sodium/private/ed25519_ref10_fe_25_5.c | 885 -- .../crypto/sodium/private/fe_25_5/fe.c | 237 - trezor-crypto/crypto/test.db | Bin 20467712 -> 0 bytes trezor-crypto/crypto/tests/CMakeLists.txt | 15 - trezor-crypto/crypto/tests/aestst.c | 178 - trezor-crypto/crypto/tests/aestst.h | 85 - trezor-crypto/crypto/tests/test_check.c | 9437 ----------------- .../crypto/tests/test_check_cardano.h | 571 - .../crypto/tests/test_check_cashaddr.h | 68 - .../crypto/tests/test_check_monero.h | 1182 --- trezor-crypto/crypto/tests/test_check_nano.h | 107 - .../crypto/tests/test_check_zilliqa.h | 214 - trezor-crypto/crypto/tests/test_curves.py | 353 - trezor-crypto/crypto/tests/test_openssl.c | 150 - trezor-crypto/crypto/tests/test_speed.c | 231 - trezor-crypto/crypto/tests/test_wycheproof.py | 721 -- trezor-crypto/crypto/tools/.gitignore | 3 - trezor-crypto/crypto/tools/README.md | 54 - trezor-crypto/crypto/tools/bip39bruteforce.c | 100 - trezor-crypto/crypto/tools/mktable.c | 69 - .../crypto/tools/nem_test_vectors.erb | 18 - .../crypto/tools/nem_test_vectors.rb | 126 - trezor-crypto/crypto/tools/xpubaddrgen.c | 48 - trezor-crypto/crypto/zilliqa.c | 191 - .../include/TrezorCrypto/TrezorCrypto.h | 29 - trezor-crypto/include/TrezorCrypto/address.h | 48 - trezor-crypto/include/TrezorCrypto/aes.h | 228 - .../include/TrezorCrypto/aes/aesopt.h | 784 -- .../include/TrezorCrypto/aes/aestab.h | 173 - trezor-crypto/include/TrezorCrypto/base32.h | 53 - trezor-crypto/include/TrezorCrypto/base58.h | 61 - trezor-crypto/include/TrezorCrypto/bignum.h | 178 - trezor-crypto/include/TrezorCrypto/bip32.h | 157 - trezor-crypto/include/TrezorCrypto/bip39.h | 71 - .../include/TrezorCrypto/bip39_english.h | 367 - trezor-crypto/include/TrezorCrypto/blake256.h | 61 - .../include/TrezorCrypto/blake2_common.h | 25 - trezor-crypto/include/TrezorCrypto/blake2b.h | 51 - trezor-crypto/include/TrezorCrypto/blake2s.h | 49 - trezor-crypto/include/TrezorCrypto/cardano.h | 63 - .../chacha20poly1305/chacha20poly1305.h | 19 - .../chacha20poly1305/ecrypt-config.h | 316 - .../chacha20poly1305/ecrypt-machine.h | 49 - .../chacha20poly1305/ecrypt-portable.h | 275 - .../chacha20poly1305/ecrypt-sync.h | 290 - .../chacha20poly1305/ecrypt-types.h | 53 - .../chacha20poly1305/poly1305-donna-32.h | 219 - .../chacha20poly1305/poly1305-donna.h | 20 - .../TrezorCrypto/chacha20poly1305/rfc7539.h | 10 - .../include/TrezorCrypto/chacha_drbg.h | 54 - .../include/TrezorCrypto/check_mem.h | 30 - trezor-crypto/include/TrezorCrypto/curves.h | 52 - trezor-crypto/include/TrezorCrypto/ecdsa.h | 137 - .../ed25519-donna/curve25519-donna-32bit.h | 87 - .../ed25519-donna/curve25519-donna-helpers.h | 30 - .../curve25519-donna-scalarmult-base.h | 8 - .../ed25519-donna/ed25519-blake2b.h | 21 - .../ed25519-donna-32bit-tables.h | 25 - .../ed25519-donna-basepoint-table.h | 10 - .../ed25519-donna/ed25519-donna-impl-base.h | 114 - .../ed25519-donna/ed25519-donna-portable.h | 32 - .../ed25519-donna/ed25519-donna.h | 60 - .../ed25519-hash-custom-blake2b.h | 23 - .../ed25519-hash-custom-keccak.h | 23 - .../ed25519-donna/ed25519-hash-custom-sha3.h | 23 - .../ed25519-donna/ed25519-hash-custom.h | 23 - .../ed25519-donna/ed25519-keccak.h | 21 - .../TrezorCrypto/ed25519-donna/ed25519-sha3.h | 21 - .../ed25519-donna/modm-donna-32bit.h | 87 - trezor-crypto/include/TrezorCrypto/ed25519.h | 44 - trezor-crypto/include/TrezorCrypto/endian.h | 132 - trezor-crypto/include/TrezorCrypto/groestl.h | 104 - .../include/TrezorCrypto/groestl_internal.h | 510 - trezor-crypto/include/TrezorCrypto/hasher.h | 82 - trezor-crypto/include/TrezorCrypto/hmac.h | 68 - .../include/TrezorCrypto/hmac_drbg.h | 43 - .../include/TrezorCrypto/monero/xmr.h | 67 - trezor-crypto/include/TrezorCrypto/nano.h | 56 - trezor-crypto/include/TrezorCrypto/nem.h | 164 - .../include/TrezorCrypto/nist256p1.h | 35 - trezor-crypto/include/TrezorCrypto/options.h | 107 - trezor-crypto/include/TrezorCrypto/pbkdf2.h | 74 - trezor-crypto/include/TrezorCrypto/rc4.h | 37 - trezor-crypto/include/TrezorCrypto/rfc6979.h | 41 - .../include/TrezorCrypto/ripemd160.h | 30 - trezor-crypto/include/TrezorCrypto/script.h | 31 - trezor-crypto/include/TrezorCrypto/scrypt.h | 68 - .../include/TrezorCrypto/secp256k1.h | 46 - trezor-crypto/include/TrezorCrypto/sha2.h | 126 - trezor-crypto/include/TrezorCrypto/sha3.h | 89 - trezor-crypto/include/TrezorCrypto/shamir.h | 70 - trezor-crypto/include/TrezorCrypto/slip39.h | 47 - .../include/TrezorCrypto/slip39_wordlist.h | 1246 --- .../include/TrezorCrypto/sodium/keypair.h | 20 - .../sodium/private/ed25519_ref10.h | 85 - .../sodium/private/ed25519_ref10_fe_25_5.h | 43 - .../sodium/private/fe_25_5/constants.h | 29 - .../TrezorCrypto/sodium/private/fe_25_5/fe.h | 25 - trezor-crypto/include/TrezorCrypto/zilliqa.h | 57 - trezor-crypto/setup_from_upstream.sh | 26 - trezor-crypto/version | 1 - wallet_core.srctrlprj | 1 - walletconsole/CMakeLists.txt | 2 +- walletconsole/lib/CMakeLists.txt | 4 +- wasm/CMakeLists.txt | 2 +- 273 files changed, 820 insertions(+), 45991 deletions(-) delete mode 100644 include/TrustWalletCore/TWPBKDF2.h create mode 100644 rust/tw_keypair/src/ffi/curve25519.rs create mode 100644 rust/tw_keypair/src/ffi/ecdsa.rs create mode 100644 src/Utils.h rename trezor-crypto/crypto/cash_addr.c => src/cash_addr.cpp (94%) rename {trezor-crypto/include/TrezorCrypto => src}/cash_addr.h (100%) delete mode 100644 src/interface/TWPBKDF2.cpp rename {trezor-crypto/crypto => src}/memzero.c (100%) rename {trezor-crypto/include/TrezorCrypto => src}/memzero.h (100%) rename trezor-crypto/crypto/rand.c => src/rand.cpp (85%) rename {trezor-crypto/include/TrezorCrypto => src}/rand.h (100%) delete mode 100644 tests/common/HDWallet/HDWalletInternalTests.cpp delete mode 100644 tests/common/PublicKeyLegacy.h delete mode 100644 trezor-crypto/.gitignore delete mode 100644 trezor-crypto/CMakeLists.txt delete mode 100644 trezor-crypto/crypto/AUTHORS delete mode 100644 trezor-crypto/crypto/CONTRIBUTORS delete mode 100644 trezor-crypto/crypto/LICENSE delete mode 100644 trezor-crypto/crypto/README.md delete mode 100644 trezor-crypto/crypto/address.c delete mode 100644 trezor-crypto/crypto/aes/aes_modes.c delete mode 100644 trezor-crypto/crypto/aes/aescrypt.c delete mode 100644 trezor-crypto/crypto/aes/aeskey.c delete mode 100644 trezor-crypto/crypto/aes/aestab.c delete mode 100644 trezor-crypto/crypto/base32.c delete mode 100644 trezor-crypto/crypto/base58.c delete mode 100644 trezor-crypto/crypto/bignum.c delete mode 100644 trezor-crypto/crypto/bip32.c delete mode 100644 trezor-crypto/crypto/bip39.c delete mode 100644 trezor-crypto/crypto/blake256.c delete mode 100644 trezor-crypto/crypto/blake2b.c delete mode 100644 trezor-crypto/crypto/blake2s.c delete mode 100644 trezor-crypto/crypto/cardano.c delete mode 100644 trezor-crypto/crypto/chacha20poly1305/LICENSE delete mode 100644 trezor-crypto/crypto/chacha20poly1305/chacha20poly1305.c delete mode 100644 trezor-crypto/crypto/chacha20poly1305/chacha_merged.c delete mode 100644 trezor-crypto/crypto/chacha20poly1305/poly1305-donna.c delete mode 100644 trezor-crypto/crypto/chacha20poly1305/rfc7539.c delete mode 100644 trezor-crypto/crypto/chacha_drbg.c delete mode 100644 trezor-crypto/crypto/curves.c delete mode 100644 trezor-crypto/crypto/ecdsa.c delete mode 100644 trezor-crypto/crypto/ed25519-donna/README.md delete mode 100644 trezor-crypto/crypto/ed25519-donna/curve25519-donna-32bit.c delete mode 100644 trezor-crypto/crypto/ed25519-donna/curve25519-donna-helpers.c delete mode 100644 trezor-crypto/crypto/ed25519-donna/curve25519-donna-scalarmult-base.c delete mode 100644 trezor-crypto/crypto/ed25519-donna/ed25519-blake2b.c delete mode 100644 trezor-crypto/crypto/ed25519-donna/ed25519-donna-32bit-tables.c delete mode 100644 trezor-crypto/crypto/ed25519-donna/ed25519-donna-basepoint-table.c delete mode 100644 trezor-crypto/crypto/ed25519-donna/ed25519-donna-impl-base.c delete mode 100644 trezor-crypto/crypto/ed25519-donna/ed25519-keccak.c delete mode 100644 trezor-crypto/crypto/ed25519-donna/ed25519-sha3.c delete mode 100644 trezor-crypto/crypto/ed25519-donna/ed25519.c delete mode 100644 trezor-crypto/crypto/ed25519-donna/modm-donna-32bit.c delete mode 100644 trezor-crypto/crypto/groestl.c delete mode 100644 trezor-crypto/crypto/hasher.c delete mode 100644 trezor-crypto/crypto/hmac.c delete mode 100644 trezor-crypto/crypto/hmac_drbg.c delete mode 100644 trezor-crypto/crypto/monero/base58.c delete mode 100644 trezor-crypto/crypto/monero/base58.h delete mode 100644 trezor-crypto/crypto/monero/int-util.h delete mode 100644 trezor-crypto/crypto/monero/monero.h delete mode 100644 trezor-crypto/crypto/monero/range_proof.c delete mode 100644 trezor-crypto/crypto/monero/range_proof.h delete mode 100644 trezor-crypto/crypto/monero/serialize.c delete mode 100644 trezor-crypto/crypto/monero/serialize.h delete mode 100644 trezor-crypto/crypto/monero/xmr.c delete mode 100644 trezor-crypto/crypto/monero/xmr.h delete mode 100644 trezor-crypto/crypto/nano.c delete mode 100644 trezor-crypto/crypto/nem.c delete mode 100644 trezor-crypto/crypto/nist256p1.c delete mode 100644 trezor-crypto/crypto/nist256p1.table delete mode 100644 trezor-crypto/crypto/pbkdf2.c delete mode 100644 trezor-crypto/crypto/rc4.c delete mode 100644 trezor-crypto/crypto/rfc6979.c delete mode 100644 trezor-crypto/crypto/ripemd160.c delete mode 100644 trezor-crypto/crypto/script.c delete mode 100644 trezor-crypto/crypto/scrypt.c delete mode 100644 trezor-crypto/crypto/secp256k1.c delete mode 100644 trezor-crypto/crypto/secp256k1.table delete mode 100755 trezor-crypto/crypto/setup.py delete mode 100644 trezor-crypto/crypto/sha2.c delete mode 100644 trezor-crypto/crypto/sha3.c delete mode 100644 trezor-crypto/crypto/shamir.c delete mode 100644 trezor-crypto/crypto/slip39.c delete mode 100644 trezor-crypto/crypto/sodium/keypair.c delete mode 100644 trezor-crypto/crypto/sodium/private/ed25519_ref10.c delete mode 100644 trezor-crypto/crypto/sodium/private/ed25519_ref10_fe_25_5.c delete mode 100644 trezor-crypto/crypto/sodium/private/fe_25_5/fe.c delete mode 100644 trezor-crypto/crypto/test.db delete mode 100644 trezor-crypto/crypto/tests/CMakeLists.txt delete mode 100644 trezor-crypto/crypto/tests/aestst.c delete mode 100644 trezor-crypto/crypto/tests/aestst.h delete mode 100644 trezor-crypto/crypto/tests/test_check.c delete mode 100644 trezor-crypto/crypto/tests/test_check_cardano.h delete mode 100644 trezor-crypto/crypto/tests/test_check_cashaddr.h delete mode 100644 trezor-crypto/crypto/tests/test_check_monero.h delete mode 100644 trezor-crypto/crypto/tests/test_check_nano.h delete mode 100644 trezor-crypto/crypto/tests/test_check_zilliqa.h delete mode 100755 trezor-crypto/crypto/tests/test_curves.py delete mode 100644 trezor-crypto/crypto/tests/test_openssl.c delete mode 100644 trezor-crypto/crypto/tests/test_speed.c delete mode 100755 trezor-crypto/crypto/tests/test_wycheproof.py delete mode 100644 trezor-crypto/crypto/tools/.gitignore delete mode 100644 trezor-crypto/crypto/tools/README.md delete mode 100644 trezor-crypto/crypto/tools/bip39bruteforce.c delete mode 100644 trezor-crypto/crypto/tools/mktable.c delete mode 100644 trezor-crypto/crypto/tools/nem_test_vectors.erb delete mode 100755 trezor-crypto/crypto/tools/nem_test_vectors.rb delete mode 100644 trezor-crypto/crypto/tools/xpubaddrgen.c delete mode 100644 trezor-crypto/crypto/zilliqa.c delete mode 100644 trezor-crypto/include/TrezorCrypto/TrezorCrypto.h delete mode 100644 trezor-crypto/include/TrezorCrypto/address.h delete mode 100644 trezor-crypto/include/TrezorCrypto/aes.h delete mode 100644 trezor-crypto/include/TrezorCrypto/aes/aesopt.h delete mode 100644 trezor-crypto/include/TrezorCrypto/aes/aestab.h delete mode 100644 trezor-crypto/include/TrezorCrypto/base32.h delete mode 100644 trezor-crypto/include/TrezorCrypto/base58.h delete mode 100644 trezor-crypto/include/TrezorCrypto/bignum.h delete mode 100644 trezor-crypto/include/TrezorCrypto/bip32.h delete mode 100644 trezor-crypto/include/TrezorCrypto/bip39.h delete mode 100644 trezor-crypto/include/TrezorCrypto/bip39_english.h delete mode 100644 trezor-crypto/include/TrezorCrypto/blake256.h delete mode 100644 trezor-crypto/include/TrezorCrypto/blake2_common.h delete mode 100644 trezor-crypto/include/TrezorCrypto/blake2b.h delete mode 100644 trezor-crypto/include/TrezorCrypto/blake2s.h delete mode 100644 trezor-crypto/include/TrezorCrypto/cardano.h delete mode 100644 trezor-crypto/include/TrezorCrypto/chacha20poly1305/chacha20poly1305.h delete mode 100644 trezor-crypto/include/TrezorCrypto/chacha20poly1305/ecrypt-config.h delete mode 100644 trezor-crypto/include/TrezorCrypto/chacha20poly1305/ecrypt-machine.h delete mode 100644 trezor-crypto/include/TrezorCrypto/chacha20poly1305/ecrypt-portable.h delete mode 100644 trezor-crypto/include/TrezorCrypto/chacha20poly1305/ecrypt-sync.h delete mode 100644 trezor-crypto/include/TrezorCrypto/chacha20poly1305/ecrypt-types.h delete mode 100644 trezor-crypto/include/TrezorCrypto/chacha20poly1305/poly1305-donna-32.h delete mode 100644 trezor-crypto/include/TrezorCrypto/chacha20poly1305/poly1305-donna.h delete mode 100644 trezor-crypto/include/TrezorCrypto/chacha20poly1305/rfc7539.h delete mode 100644 trezor-crypto/include/TrezorCrypto/chacha_drbg.h delete mode 100644 trezor-crypto/include/TrezorCrypto/check_mem.h delete mode 100644 trezor-crypto/include/TrezorCrypto/curves.h delete mode 100644 trezor-crypto/include/TrezorCrypto/ecdsa.h delete mode 100644 trezor-crypto/include/TrezorCrypto/ed25519-donna/curve25519-donna-32bit.h delete mode 100644 trezor-crypto/include/TrezorCrypto/ed25519-donna/curve25519-donna-helpers.h delete mode 100644 trezor-crypto/include/TrezorCrypto/ed25519-donna/curve25519-donna-scalarmult-base.h delete mode 100644 trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-blake2b.h delete mode 100644 trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-donna-32bit-tables.h delete mode 100644 trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-donna-basepoint-table.h delete mode 100644 trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-donna-impl-base.h delete mode 100644 trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-donna-portable.h delete mode 100644 trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-donna.h delete mode 100644 trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-hash-custom-blake2b.h delete mode 100644 trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-hash-custom-keccak.h delete mode 100644 trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-hash-custom-sha3.h delete mode 100644 trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-hash-custom.h delete mode 100644 trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-keccak.h delete mode 100644 trezor-crypto/include/TrezorCrypto/ed25519-donna/ed25519-sha3.h delete mode 100644 trezor-crypto/include/TrezorCrypto/ed25519-donna/modm-donna-32bit.h delete mode 100644 trezor-crypto/include/TrezorCrypto/ed25519.h delete mode 100644 trezor-crypto/include/TrezorCrypto/endian.h delete mode 100644 trezor-crypto/include/TrezorCrypto/groestl.h delete mode 100644 trezor-crypto/include/TrezorCrypto/groestl_internal.h delete mode 100644 trezor-crypto/include/TrezorCrypto/hasher.h delete mode 100644 trezor-crypto/include/TrezorCrypto/hmac.h delete mode 100644 trezor-crypto/include/TrezorCrypto/hmac_drbg.h delete mode 100644 trezor-crypto/include/TrezorCrypto/monero/xmr.h delete mode 100644 trezor-crypto/include/TrezorCrypto/nano.h delete mode 100644 trezor-crypto/include/TrezorCrypto/nem.h delete mode 100644 trezor-crypto/include/TrezorCrypto/nist256p1.h delete mode 100644 trezor-crypto/include/TrezorCrypto/options.h delete mode 100644 trezor-crypto/include/TrezorCrypto/pbkdf2.h delete mode 100644 trezor-crypto/include/TrezorCrypto/rc4.h delete mode 100644 trezor-crypto/include/TrezorCrypto/rfc6979.h delete mode 100644 trezor-crypto/include/TrezorCrypto/ripemd160.h delete mode 100644 trezor-crypto/include/TrezorCrypto/script.h delete mode 100644 trezor-crypto/include/TrezorCrypto/scrypt.h delete mode 100644 trezor-crypto/include/TrezorCrypto/secp256k1.h delete mode 100644 trezor-crypto/include/TrezorCrypto/sha2.h delete mode 100644 trezor-crypto/include/TrezorCrypto/sha3.h delete mode 100644 trezor-crypto/include/TrezorCrypto/shamir.h delete mode 100644 trezor-crypto/include/TrezorCrypto/slip39.h delete mode 100644 trezor-crypto/include/TrezorCrypto/slip39_wordlist.h delete mode 100644 trezor-crypto/include/TrezorCrypto/sodium/keypair.h delete mode 100644 trezor-crypto/include/TrezorCrypto/sodium/private/ed25519_ref10.h delete mode 100644 trezor-crypto/include/TrezorCrypto/sodium/private/ed25519_ref10_fe_25_5.h delete mode 100644 trezor-crypto/include/TrezorCrypto/sodium/private/fe_25_5/constants.h delete mode 100644 trezor-crypto/include/TrezorCrypto/sodium/private/fe_25_5/fe.h delete mode 100644 trezor-crypto/include/TrezorCrypto/zilliqa.h delete mode 100755 trezor-crypto/setup_from_upstream.sh delete mode 100644 trezor-crypto/version diff --git a/.clang-format b/.clang-format index c3a9fa31eb5..84f2e87c9d7 100644 --- a/.clang-format +++ b/.clang-format @@ -12,7 +12,7 @@ PointerAlignment: Left IncludeCategories: - Regex: '^"\.\./' Priority: 2 - - Regex: '^<(TrustWalletCore|TrezorCrypto)/' + - Regex: '^<(TrustWalletCore)/' Priority: 3 - Regex: '<.*>' Priority: 4 diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index acda9dcfe10..ab246aee393 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -54,8 +54,7 @@ jobs: CXX: /usr/bin/clang++ - name: Build and test run: | - ninja -Cbuild tests TrezorCryptoTests - build/trezor-crypto/crypto/tests/TrezorCryptoTests + ninja -Cbuild tests build/tests/tests --gtest_output=xml env: CC: /usr/bin/clang diff --git a/CMakeLists.txt b/CMakeLists.txt index 6fec5513326..57d49df4407 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,6 @@ target_link_directories(${PROJECT_NAME}_INTERFACE INTERFACE ${PREFIX}/lib) target_link_directories(${PROJECT_NAME}_INTERFACE INTERFACE ${WALLET_CORE_RS_TARGET_DIR}/release) set_project_warnings(${PROJECT_NAME}_INTERFACE) -add_subdirectory(trezor-crypto) set(WALLET_CORE_RS_LIB libwallet_core_rs.a) set(WALLET_CORE_BINDGEN ${WALLET_CORE_RS_TARGET_DIR}/release/${WALLET_CORE_RS_LIB}) @@ -72,7 +71,7 @@ if (${ANDROID}) elseif (${CMAKE_ANDROID_ARCH_ABI} STREQUAL "x86_64") set(WALLET_CORE_BINDGEN ${WALLET_CORE_RS_TARGET_DIR}/x86_64-linux-android/release/${WALLET_CORE_RS_LIB}) endif () - target_link_libraries(TrustWalletCore PUBLIC ${WALLET_CORE_BINDGEN} ${PROJECT_NAME}_INTERFACE PRIVATE TrezorCrypto protobuf ${log-lib} Boost::boost) + target_link_libraries(TrustWalletCore PUBLIC ${WALLET_CORE_BINDGEN} ${PROJECT_NAME}_INTERFACE PRIVATE protobuf ${log-lib} Boost::boost) elseif (${TW_COMPILE_JAVA}) message("Configuring for JNI") file(GLOB_RECURSE core_sources src/*.c src/*.cc src/*.cpp src/*.h jni/cpp/*.cpp jni/cpp/*.h) @@ -90,12 +89,12 @@ elseif (${TW_COMPILE_JAVA}) add_library(TrustWalletCore SHARED ${sources} ${PROTO_SRCS} ${PROTO_HDRS}) find_package(JNI REQUIRED) target_include_directories(TrustWalletCore PRIVATE ${JNI_INCLUDE_DIRS}) - target_link_libraries(TrustWalletCore PUBLIC ${WALLET_CORE_BINDGEN} ${PROJECT_NAME}_INTERFACE PRIVATE TrezorCrypto protobuf Boost::boost) + target_link_libraries(TrustWalletCore PUBLIC ${WALLET_CORE_BINDGEN} ${PROJECT_NAME}_INTERFACE PRIVATE protobuf Boost::boost) else () message("Configuring standalone") file(GLOB_RECURSE sources src/*.c src/*.cc src/*.cpp src/*.h) add_library(TrustWalletCore STATIC ${sources} ${PROTO_SRCS} ${PROTO_HDRS}) - target_link_libraries(TrustWalletCore PUBLIC ${WALLET_CORE_BINDGEN} ${PROJECT_NAME}_INTERFACE PRIVATE TrezorCrypto protobuf Boost::boost) + target_link_libraries(TrustWalletCore PUBLIC ${WALLET_CORE_BINDGEN} ${PROJECT_NAME}_INTERFACE PRIVATE protobuf Boost::boost) endif () if (TW_CODE_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") diff --git a/WalletCore.podspec b/WalletCore.podspec index ee960c3a1eb..77129c89049 100644 --- a/WalletCore.podspec +++ b/WalletCore.podspec @@ -37,8 +37,6 @@ Pod::Spec.new do |s| 'swift/Sources/*.{swift,h,m,cpp}', 'swift/Sources/Extensions/*.swift', 'swift/Sources/Generated/*.{swift,h}', - 'trezor-crypto/crypto/**/*.{c,h}', - 'trezor-crypto/include/**/*.{h}', "#{protobuf_source_dir}/src/google/protobuf/any.cc", "#{protobuf_source_dir}/src/google/protobuf/any.pb.cc", "#{protobuf_source_dir}/src/google/protobuf/any_lite.cc", @@ -125,11 +123,7 @@ Pod::Spec.new do |s| "#{protobuf_source_dir}/src/google/protobuf/wrappers.pb.cc" ss.exclude_files = - 'trezor-crypto/include/TrezorCrypto/base58.h', - 'trezor-crypto/crypto/monero', - 'trezor-crypto/crypto/tests', - 'trezor-crypto/crypto/tools', - 'trezor-crypto/crypto/rand.c', + 'src/rand.cpp', 'swift/Sources/Generated/WalletCore.h' ss.public_header_files = @@ -137,7 +131,6 @@ Pod::Spec.new do |s| 'swift/Sources/*.h' ss.preserve_paths = - 'trezor-crypto/crypto/*.{table}', "#{protobuf_source_dir}/src/**/*.{h,inc}", "#{include_dir}/nlohmann/**/*.hpp", 'src/proto/*.proto' @@ -145,12 +138,10 @@ Pod::Spec.new do |s| ss.xcconfig = { 'HEADER_SEARCH_PATHS' => '$(inherited) ' \ '$(SRCROOT)/../../wallet-core ' \ - '${SRCROOT}/../../trezor-crypto/crypto ', 'SYSTEM_HEADER_SEARCH_PATHS' => '$(inherited) ' \ '/usr/local/include ' \ '${SRCROOT}/../../include ' \ '${SRCROOT}/../../../build/local/include ' \ - "${SRCROOT}/../../trezor-crypto/include " \ "${SRCROOT}/../../protobuf ", 'GCC_WARN_UNUSED_FUNCTION' => 'NO', 'GCC_WARN_64_TO_32_BIT_CONVERSION' => 'NO', diff --git a/include/TrustWalletCore/TWPBKDF2.h b/include/TrustWalletCore/TWPBKDF2.h deleted file mode 100644 index 86c6cca6801..00000000000 --- a/include/TrustWalletCore/TWPBKDF2.h +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#pragma once - -#include "TWBase.h" -#include "TWData.h" - -TW_EXTERN_C_BEGIN - -/// Password-Based Key Derivation Function 2 -TW_EXPORT_STRUCT -struct TWPBKDF2; - -/// Derives a key from a password and a salt using PBKDF2 + Sha256. -/// -/// \param password is the master password from which a derived key is generated -/// \param salt is a sequence of bits, known as a cryptographic salt -/// \param iterations is the number of iterations desired -/// \param dkLen is the desired bit-length of the derived key -/// \return the derived key data. -TW_EXPORT_STATIC_METHOD -TWData *_Nullable TWPBKDF2HmacSha256(TWData *_Nonnull password, TWData *_Nonnull salt, uint32_t iterations, uint32_t dkLen); - -/// Derives a key from a password and a salt using PBKDF2 + Sha512. -/// -/// \param password is the master password from which a derived key is generated -/// \param salt is a sequence of bits, known as a cryptographic salt -/// \param iterations is the number of iterations desired -/// \param dkLen is the desired bit-length of the derived key -/// \return the derived key data. -TW_EXPORT_STATIC_METHOD -TWData *_Nullable TWPBKDF2HmacSha512(TWData *_Nonnull password, TWData *_Nonnull salt, uint32_t iterations, uint32_t dkLen); - -TW_EXTERN_C_END diff --git a/rust/tw_crypto/src/crypto_pbkdf2/mod.rs b/rust/tw_crypto/src/crypto_pbkdf2/mod.rs index 9fd1363a0dd..f18d85aa014 100644 --- a/rust/tw_crypto/src/crypto_pbkdf2/mod.rs +++ b/rust/tw_crypto/src/crypto_pbkdf2/mod.rs @@ -5,7 +5,7 @@ // file LICENSE at the root of the source code distribution tree. use pbkdf2::pbkdf2_hmac; -use sha2::Sha256; +use sha2::{Sha256, Sha512}; pub fn pbkdf2_hmac_sha256( password: &[u8], @@ -17,3 +17,14 @@ pub fn pbkdf2_hmac_sha256( pbkdf2_hmac::(password, salt, iterations, &mut output); output } + +pub fn pbkdf2_hmac_512( + password: &[u8], + salt: &[u8], + iterations: u32, + desired_len: usize, +) -> Vec { + let mut output = vec![0u8; desired_len]; + pbkdf2_hmac::(password, salt, iterations, &mut output); + output +} diff --git a/rust/tw_crypto/src/ffi/crypto_pbkdf2.rs b/rust/tw_crypto/src/ffi/crypto_pbkdf2.rs index 47478df2e40..1d1fb8e7e18 100644 --- a/rust/tw_crypto/src/ffi/crypto_pbkdf2.rs +++ b/rust/tw_crypto/src/ffi/crypto_pbkdf2.rs @@ -6,7 +6,7 @@ #![allow(clippy::missing_safety_doc)] -use crate::crypto_pbkdf2::pbkdf2_hmac_sha256; +use crate::crypto_pbkdf2::{pbkdf2_hmac_512, pbkdf2_hmac_sha256}; use tw_macros::tw_ffi; use tw_memory::ffi::{tw_data::TWData, Nonnull, NullableMut, RawPtrTrait}; @@ -15,15 +15,15 @@ use tw_memory::ffi::{tw_data::TWData, Nonnull, NullableMut, RawPtrTrait}; /// \param password data. /// \param salt data. /// \param iterations PBKDF2 parameter `iterations`. -/// \param desired_len PBKDF2 parameter `desired_len`. +/// \param dk_len PBKDF2 parameter `desired_len`. /// \return *nullable* data. -#[tw_ffi(ty = static_function, class = TWCrypto, name = PBKDF2)] +#[tw_ffi(ty = static_function, class = TWPBKDF2, name = HmacSha256)] #[no_mangle] -pub unsafe extern "C" fn crypto_pbkdf2( +pub unsafe extern "C" fn tw_pbkdf2_hmac_sha256( password: Nonnull, salt: Nonnull, iterations: u32, - desired_len: usize, + dk_len: usize, ) -> NullableMut { let password = TWData::from_ptr_as_ref(password) .map(|data| data.as_slice()) @@ -32,6 +32,32 @@ pub unsafe extern "C" fn crypto_pbkdf2( .map(|data| data.as_slice()) .unwrap_or_default(); - let output = pbkdf2_hmac_sha256(password, salt, iterations, desired_len); + let output = pbkdf2_hmac_sha256(password, salt, iterations, dk_len); + TWData::from(output).into_ptr() +} + +/// The PBKDF2 key derivation function. +/// +/// \param password data. +/// \param salt data. +/// \param iterations PBKDF2 parameter `iterations`. +/// \param dk_len PBKDF2 parameter `desired_len`. +/// \return *nullable* data. +#[tw_ffi(ty = static_function, class = TWPBKDF2, name = HmacSha512)] +#[no_mangle] +pub unsafe extern "C" fn tw_pbkdf2_hmac_sha512( + password: Nonnull, + salt: Nonnull, + iterations: u32, + dk_len: usize, +) -> NullableMut { + let password = TWData::from_ptr_as_ref(password) + .map(|data| data.as_slice()) + .unwrap_or_default(); + let salt = TWData::from_ptr_as_ref(salt) + .map(|data| data.as_slice()) + .unwrap_or_default(); + + let output = pbkdf2_hmac_512(password, salt, iterations, dk_len); TWData::from(output).into_ptr() } diff --git a/rust/tw_crypto/tests/crypto_pbkdf2_ffi.rs b/rust/tw_crypto/tests/crypto_pbkdf2_ffi.rs index 9efc1d2b538..16831fe2f48 100644 --- a/rust/tw_crypto/tests/crypto_pbkdf2_ffi.rs +++ b/rust/tw_crypto/tests/crypto_pbkdf2_ffi.rs @@ -4,28 +4,28 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -use tw_crypto::ffi::crypto_pbkdf2::crypto_pbkdf2; +use tw_crypto::ffi::crypto_pbkdf2::{tw_pbkdf2_hmac_sha256, tw_pbkdf2_hmac_sha512}; use tw_encoding::{base64, base64::STANDARD, hex}; use tw_memory::ffi::tw_data::TWData; use tw_memory::ffi::RawPtrTrait; use tw_memory::test_utils::tw_data_helper::TWDataHelper; #[test] -fn test_crypto_pbkdf2_ffi() { +fn test_pbkdf2_hmac_sha256_ffi() { let password = hex::decode("70617373776f7264").unwrap(); let password = TWDataHelper::create(password); let salt = hex::decode("73616C74").unwrap(); let salt = TWDataHelper::create(salt); - let res = unsafe { crypto_pbkdf2(password.ptr(), salt.ptr(), 1, 20) }; + let res = unsafe { tw_pbkdf2_hmac_sha256(password.ptr(), salt.ptr(), 1, 20) }; let res = unsafe { TWData::from_ptr_as_mut(res).unwrap() }; assert_eq!( hex::encode(res.to_vec(), false), "120fb6cffcf8b32c43e7225256c4f837a86548c9" ); - let res = unsafe { crypto_pbkdf2(password.ptr(), salt.ptr(), 4096, 20) }; + let res = unsafe { tw_pbkdf2_hmac_sha256(password.ptr(), salt.ptr(), 4096, 20) }; let res = unsafe { TWData::from_ptr_as_mut(res).unwrap() }; assert_eq!( hex::encode(res.to_vec(), false), @@ -35,10 +35,27 @@ fn test_crypto_pbkdf2_ffi() { let salt2 = base64::decode("kNHS+Mx//slRsmLF9396HQ==", STANDARD).unwrap(); let salt2 = TWDataHelper::create(salt2); - let res = unsafe { crypto_pbkdf2(password.ptr(), salt2.ptr(), 100, 32) }; + let res = unsafe { tw_pbkdf2_hmac_sha256(password.ptr(), salt2.ptr(), 100, 32) }; let res = unsafe { TWData::from_ptr_as_mut(res).unwrap() }; assert_eq!( hex::encode(res.to_vec(), false), "9cf33ebd3542c691fac6f61609a8d13355a0adf4d15eed77cc9d13f792b77c3a" ); } + +#[test] +fn test_pbkdf2_hmac_sha512_ffi() { + let password = hex::decode("70617373776f7264").unwrap(); + let password = TWDataHelper::create(password); + + let salt = hex::decode("73616C74").unwrap(); + let salt = TWDataHelper::create(salt); + + let res = unsafe { tw_pbkdf2_hmac_sha512(password.ptr(), salt.ptr(), 1, 20) }; + let res = unsafe { TWData::from_ptr_as_mut(res).unwrap() }; + + assert_eq!( + hex::encode(res.to_vec(), false), + "867f70cf1ade02cff3752599a3a53dc4af34c7a6" + ); +} diff --git a/rust/tw_keypair/src/ecdsa/nist256p1/private.rs b/rust/tw_keypair/src/ecdsa/nist256p1/private.rs index 1b44c87329d..71765d32b01 100644 --- a/rust/tw_keypair/src/ecdsa/nist256p1/private.rs +++ b/rust/tw_keypair/src/ecdsa/nist256p1/private.rs @@ -8,9 +8,11 @@ use crate::ecdsa::nist256p1::public::PublicKey; use crate::ecdsa::nist256p1::Signature; use crate::traits::{DerivableKeyTrait, SigningKeyTrait}; use crate::{KeyPairError, KeyPairResult}; -use p256::ecdsa::SigningKey; +use ecdsa::elliptic_curve::point::AffineCoordinates; +use p256::ecdsa::{SigningKey, VerifyingKey}; +use p256::{AffinePoint, ProjectivePoint}; use tw_encoding::hex; -use tw_hash::H256; +use tw_hash::{H256, H512}; use tw_misc::traits::ToBytesZeroizing; use zeroize::{ZeroizeOnDrop, Zeroizing}; @@ -25,6 +27,21 @@ impl PrivateKey { pub fn public(&self) -> PublicKey { PublicKey::new(*self.secret.verifying_key()) } + + // See https://github.com/fioprotocol/fiojs/blob/master/src/ecc/key_private.js + pub fn ecies_shared_key(&self, pubkey: &PublicKey) -> H512 { + let shared_secret = diffie_hellman(&self.secret, &pubkey.public); + let hash = tw_hash::sha2::sha512(shared_secret.x().as_slice()); + H512::try_from(hash.as_slice()).expect("Expected 64 byte array sha512 hash") + } +} + +/// This method is inspired by [elliptic_curve::ecdh::diffie_hellman](https://github.com/RustCrypto/traits/blob/f0dbe44fea56d4c17e625ababacb580fec842137/elliptic-curve/src/ecdh.rs#L60-L70) +fn diffie_hellman(private: &SigningKey, public: &VerifyingKey) -> AffinePoint { + let public_point = ProjectivePoint::from(*public.as_affine()); + let secret_scalar = private.as_nonzero_scalar().as_ref(); + // Multiply the secret and public to get a shared secret affine point (x, y). + (public_point * secret_scalar).to_affine() } impl SigningKeyTrait for PrivateKey { diff --git a/rust/tw_keypair/src/ecdsa/nist256p1/public.rs b/rust/tw_keypair/src/ecdsa/nist256p1/public.rs index 31e93552fcf..86306c8ccc8 100644 --- a/rust/tw_keypair/src/ecdsa/nist256p1/public.rs +++ b/rust/tw_keypair/src/ecdsa/nist256p1/public.rs @@ -2,14 +2,19 @@ // // Copyright © 2017 Trust Wallet. +use std::cmp::Ordering; + use crate::ecdsa::nist256p1::{Signature, VerifySignature}; use crate::traits::{DerivableKeyTrait, VerifyingKeyTrait}; use crate::{KeyPairError, KeyPairResult}; use ecdsa::elliptic_curve::group::prime::PrimeCurveAffine; +use ecdsa::elliptic_curve::point::AffineCoordinates; use p256::ecdsa::signature::hazmat::PrehashVerifier; use p256::ecdsa::VerifyingKey; use tw_encoding::hex; +use tw_hash::hasher::{Hasher, StatefulHasher}; use tw_hash::{H256, H264, H520}; +use tw_memory::Data; use tw_misc::traits::ToBytesVec; /// Represents a `nist256p1` public key. @@ -50,6 +55,23 @@ impl PublicKey { H520::try_from(self.public.to_encoded_point(compressed).as_bytes()) .expect("Expected 65 byte array Public Key") } + + pub fn hash(&self, hasher: Hasher) -> Data { + hasher.hash(self.compressed().as_slice()) + } + + pub fn compare(&self, other: &PublicKey) -> Ordering { + let self_as_affine = self.public.as_affine(); + let other_as_affine = other.public.as_affine(); + let result = self_as_affine.x().cmp(&other_as_affine.x()); + if result != Ordering::Equal { + return result; + } + self_as_affine + .y_is_odd() + .unwrap_u8() + .cmp(&other_as_affine.y_is_odd().unwrap_u8()) + } } impl VerifyingKeyTrait for PublicKey { diff --git a/rust/tw_keypair/src/ecdsa/secp256k1/mod.rs b/rust/tw_keypair/src/ecdsa/secp256k1/mod.rs index dcc76c801e0..9f33f6e6991 100644 --- a/rust/tw_keypair/src/ecdsa/secp256k1/mod.rs +++ b/rust/tw_keypair/src/ecdsa/secp256k1/mod.rs @@ -143,22 +143,6 @@ mod tests { assert!(!private.public().verify(verify_sig, hash_to_sign)); } - #[test] - fn test_shared_key_hash() { - let private = PrivateKey::try_from( - "9cd3b16e10bd574fed3743d8e0de0b7b4e6c69f3245ab5a168ef010d22bfefa0", - ) - .unwrap(); - let public = PublicKey::try_from( - "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992", - ) - .unwrap(); - let actual = private.shared_key_hash(&public); - let expected = - H256::from("ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a"); - assert_eq!(actual, expected); - } - #[test] fn test_public_key_recover() { let sign_bytes = H520::from("8720a46b5b3963790d94bcc61ad57ca02fd153584315bfa161ed3455e336ba624d68df010ed934b8792c5b6a57ba86c3da31d039f9612b44d1bf054132254de901"); diff --git a/rust/tw_keypair/src/ecdsa/secp256k1/private.rs b/rust/tw_keypair/src/ecdsa/secp256k1/private.rs index d7ee61395c0..d71171b4d7f 100644 --- a/rust/tw_keypair/src/ecdsa/secp256k1/private.rs +++ b/rust/tw_keypair/src/ecdsa/secp256k1/private.rs @@ -9,11 +9,12 @@ use crate::ecdsa::secp256k1::Signature; use crate::traits::DerivableKeyTrait; use crate::traits::SigningKeyTrait; use crate::{KeyPairError, KeyPairResult}; +use ecdsa::elliptic_curve::point::AffineCoordinates; use k256::ecdsa::{SigningKey, VerifyingKey}; -use k256::elliptic_curve::sec1::ToEncodedPoint; use k256::{AffinePoint, ProjectivePoint}; use tw_encoding::hex; use tw_hash::H256; +use tw_hash::H512; use tw_misc::traits::ToBytesZeroizing; use zeroize::{ZeroizeOnDrop, Zeroizing}; @@ -29,17 +30,11 @@ impl PrivateKey { PublicKey::new(*self.secret.verifying_key()) } - /// Computes an EC Diffie-Hellman secret in constant time. - /// The method is ported from [TW::PrivateKey::getSharedKey](https://github.com/trustwallet/wallet-core/blob/830b1c5baaf90692196163999e4ee2063c5f4e49/src/PrivateKey.cpp#L175-L191). - pub fn shared_key_hash(&self, pubkey: &PublicKey) -> H256 { + // See https://github.com/fioprotocol/fiojs/blob/master/src/ecc/key_private.js + pub fn ecies_shared_key(&self, pubkey: &PublicKey) -> H512 { let shared_secret = diffie_hellman(&self.secret, &pubkey.public); - - // Get a compressed shared secret (33 bytes with a tag in front). - let compress = true; - let shared_secret_compressed = shared_secret.to_encoded_point(compress); - - let shared_secret_hash = tw_hash::sha2::sha256(shared_secret_compressed.as_bytes()); - H256::try_from(shared_secret_hash.as_slice()).expect("Expected 32 byte array sha256 hash") + let hash = tw_hash::sha2::sha512(shared_secret.x().as_slice()); + H512::try_from(hash.as_slice()).expect("Expected 64 byte array sha512 hash") } } diff --git a/rust/tw_keypair/src/ecdsa/secp256k1/public.rs b/rust/tw_keypair/src/ecdsa/secp256k1/public.rs index dde33bf56c9..49fb0030fdd 100644 --- a/rust/tw_keypair/src/ecdsa/secp256k1/public.rs +++ b/rust/tw_keypair/src/ecdsa/secp256k1/public.rs @@ -2,15 +2,20 @@ // // Copyright © 2017 Trust Wallet. +use std::cmp::Ordering; + use crate::ecdsa::secp256k1::{Signature, VerifySignature}; use crate::traits::{DerivableKeyTrait, VerifyingKeyTrait}; use crate::{KeyPairError, KeyPairResult}; use der::Document; use ecdsa::elliptic_curve::group::prime::PrimeCurveAffine; +use ecdsa::elliptic_curve::point::AffineCoordinates; use k256::ecdsa::signature::hazmat::PrehashVerifier; use k256::ecdsa::VerifyingKey; use tw_encoding::hex; +use tw_hash::hasher::{Hasher, StatefulHasher}; use tw_hash::{Hash, H256, H264, H512, H520}; +use tw_memory::Data; use tw_misc::traits::ToBytesVec; /// Represents a `secp256k1` public key. @@ -79,6 +84,24 @@ impl PublicKey { let doc = Document::try_from(&spki).unwrap(); doc.into_vec() } + + pub fn hash(&self, hasher: Hasher) -> Data { + hasher.hash(self.compressed().as_slice()) + } + + pub fn compare(&self, other: &PublicKey) -> Ordering { + let self_as_affine = self.public.as_affine(); + let other_as_affine = other.public.as_affine(); + let result = self_as_affine.x().cmp(&other_as_affine.x()); + if result == Ordering::Equal { + self_as_affine + .y_is_odd() + .unwrap_u8() + .cmp(&other_as_affine.y_is_odd().unwrap_u8()) + } else { + result + } + } } impl VerifyingKeyTrait for PublicKey { diff --git a/rust/tw_keypair/src/ed25519/modifications/waves/public.rs b/rust/tw_keypair/src/ed25519/modifications/waves/public.rs index 1842f5e5cd3..a57d1e6b62c 100644 --- a/rust/tw_keypair/src/ed25519/modifications/waves/public.rs +++ b/rust/tw_keypair/src/ed25519/modifications/waves/public.rs @@ -13,6 +13,8 @@ use tw_encoding::hex; use tw_hash::H256; use tw_misc::traits::ToBytesVec; +use super::signature::PUBKEY_SIGN_MASK; + /// Represents an `ed25519` public key that is used in Waves blockchain. #[derive(Clone)] pub struct PublicKey { @@ -40,6 +42,13 @@ impl PublicKey { pub fn to_bytes(&self) -> H256 { self.curve25519_pk } + + pub fn to_standard_pubkey(&self) -> Option> { + let montgomery_point = MontgomeryPoint(self.curve25519_pk.take()); + let sign_bit = self.curve25519_pk[31] & PUBKEY_SIGN_MASK; + let edwards_point = montgomery_point.to_edwards(sign_bit)?; + Some(StandardPublicKey::::with_edwards_point(edwards_point)) + } } impl VerifyingKeyTrait for PublicKey { diff --git a/rust/tw_keypair/src/ed25519/modifications/waves/signature.rs b/rust/tw_keypair/src/ed25519/modifications/waves/signature.rs index 39613316940..5b15c7d7373 100644 --- a/rust/tw_keypair/src/ed25519/modifications/waves/signature.rs +++ b/rust/tw_keypair/src/ed25519/modifications/waves/signature.rs @@ -11,7 +11,7 @@ use tw_misc::traits::ToBytesVec; /// cbindgen:ignore /// Equals to 0x80. -const PUBKEY_SIGN_MASK: u8 = 0b1000_0000; +pub(crate) const PUBKEY_SIGN_MASK: u8 = 0b1000_0000; /// cbindgen:ignore /// Equals to 127. diff --git a/rust/tw_keypair/src/ffi/curve25519.rs b/rust/tw_keypair/src/ffi/curve25519.rs new file mode 100644 index 00000000000..0b8c4dba82f --- /dev/null +++ b/rust/tw_keypair/src/ffi/curve25519.rs @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#![allow(clippy::missing_safety_doc)] + +use crate::ed25519::waves::PublicKey as Curve25519PublicKey; +use tw_macros::tw_ffi; +use tw_memory::ffi::{tw_data::TWData, Nonnull, NullableMut, RawPtrTrait}; +use tw_misc::traits::ToBytesVec; +use tw_misc::try_or_else; + +#[tw_ffi(ty = static_function, class = TWCurve25519, name = PubkeyToEd25519)] +#[no_mangle] +pub unsafe extern "C" fn tw_curve25519_pubkey_to_ed25519( + pubkey: Nonnull, +) -> NullableMut { + let pubkey = TWData::from_ptr_as_ref(pubkey) + .map(|data| data.as_slice()) + .unwrap_or_default(); + let pubkey = try_or_else!(Curve25519PublicKey::try_from(pubkey), std::ptr::null_mut); + let ed25519_pubkey = try_or_else!(pubkey.to_standard_pubkey(), std::ptr::null_mut); + TWData::from(ed25519_pubkey.to_vec()).into_ptr() +} diff --git a/rust/tw_keypair/src/ffi/ecdsa.rs b/rust/tw_keypair/src/ffi/ecdsa.rs new file mode 100644 index 00000000000..5a6c02dc549 --- /dev/null +++ b/rust/tw_keypair/src/ffi/ecdsa.rs @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#![allow(clippy::missing_safety_doc)] + +use crate::ecdsa::nist256p1::{ + PrivateKey as NISSecPrivateKey, PublicKey as NISTPublicKey, Signature as NISSignature, +}; +use crate::ecdsa::secp256k1::{ + PrivateKey as SECPrivateKey, PublicKey as SECPublicKey, Signature as SECSignature, +}; +use tw_hash::hasher::Hasher; +use tw_macros::tw_ffi; +use tw_memory::ffi::{tw_data::TWData, Nonnull, NullableMut, RawPtrTrait}; +use tw_misc::try_or_else; + +/// Converts an ECDSA signature to a DER-encoded signature. +/// +/// \param sig *non-null* pointer to a block of data corresponding to the signature. +/// \param is_secp256k1 whether the signature is a secp256k1 signature. +/// +/// \return DER-encoded signature. +#[tw_ffi(ty = static_function, class = TWECDSA, name = SigToDER)] +#[no_mangle] +pub unsafe extern "C" fn tw_ecdsa_sig_to_der( + sig: Nonnull, + is_secp256k1: bool, +) -> NullableMut { + let sig = TWData::from_ptr_as_ref(sig) + .map(|data| data.as_slice()) + .unwrap_or_default(); + if is_secp256k1 { + let sig = try_or_else!(SECSignature::try_from(sig), std::ptr::null_mut); + sig.to_der() + .map(|output| TWData::from(output.der_bytes()).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) + } else { + let sig = try_or_else!(NISSignature::try_from(sig), std::ptr::null_mut); + sig.to_der() + .map(|output| TWData::from(output.der_bytes()).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) + } +} + +/// Computes the hash of a public key. +/// +/// \param pubkey *non-null* pointer to a block of data corresponding to the public key. +/// \param is_secp256k1 whether the public key is a secp256k1 public key. +/// \param hasher hasher type. +/// \return hash of the public key. +#[tw_ffi(ty = static_function, class = TWECDSA, name = PubkeyHash)] +#[no_mangle] +pub unsafe extern "C" fn tw_ecdsa_pubkey_hash( + pubkey_data: Nonnull, + is_secp256k1: bool, + hasher: u32, +) -> NullableMut { + let pubkey_data = TWData::from_ptr_as_ref(pubkey_data) + .map(|data| data.as_slice()) + .unwrap_or_default(); + let hasher = try_or_else!(Hasher::from_repr(hasher), std::ptr::null_mut); + let hash = if is_secp256k1 { + let pubkey = try_or_else!(SECPublicKey::try_from(pubkey_data), std::ptr::null_mut); + pubkey.hash(hasher) + } else { + let pubkey = try_or_else!(NISTPublicKey::try_from(pubkey_data), std::ptr::null_mut); + pubkey.hash(hasher) + }; + TWData::from(hash.to_vec()).into_ptr() +} + +/// Compares two ECDSA public keys. +/// +/// \param pubkey_data1 *non-null* pointer to a block of data corresponding to the first public key. +/// \param pubkey_data2 *non-null* pointer to a block of data corresponding to the second public key. +/// \param is_secp256k1 whether the public keys are secp256k1 public keys. +/// \return -2 if the public keys are invalid, otherwise the comparison result. +#[tw_ffi(ty = static_function, class = TWECDSA, name = PubkeyCompare)] +#[no_mangle] +pub unsafe extern "C" fn tw_ecdsa_pubkey_compare( + pubkey_data1: Nonnull, + pubkey_data2: Nonnull, + is_secp256k1: bool, +) -> i8 { + let pubkey_data1 = TWData::from_ptr_as_ref(pubkey_data1) + .map(|data| data.as_slice()) + .unwrap_or_default(); + let pubkey_data2 = TWData::from_ptr_as_ref(pubkey_data2) + .map(|data| data.as_slice()) + .unwrap_or_default(); + if is_secp256k1 { + let pubkey1 = try_or_else!(SECPublicKey::try_from(pubkey_data1), || -2); + let pubkey2 = try_or_else!(SECPublicKey::try_from(pubkey_data2), || -2); + pubkey1.compare(&pubkey2) as i8 + } else { + let pubkey1 = try_or_else!(NISTPublicKey::try_from(pubkey_data1), || -2); + let pubkey2 = try_or_else!(NISTPublicKey::try_from(pubkey_data2), || -2); + pubkey1.compare(&pubkey2) as i8 + } +} + +/// Computes the shared key. +/// +/// \param private_key *non-null* pointer to a block of data corresponding to the private key. +/// \param pubkey_data *non-null* pointer to a block of data corresponding to the public key. +/// \param is_secp256k1 whether the private key is a secp256k1 private key. +/// \return the shared key. +#[tw_ffi(ty = static_function, class = TWECDSA, name = SharedKey)] +#[no_mangle] +pub unsafe extern "C" fn tw_ecdsa_shared_key( + private_key: Nonnull, + pubkey_data: Nonnull, + is_secp256k1: bool, +) -> NullableMut { + let private_key = TWData::from_ptr_as_ref(private_key) + .map(|data| data.as_slice()) + .unwrap_or_default(); + let pubkey_data = TWData::from_ptr_as_ref(pubkey_data) + .map(|data| data.as_slice()) + .unwrap_or_default(); + let shared_key = if is_secp256k1 { + let private_key = try_or_else!(SECPrivateKey::try_from(private_key), std::ptr::null_mut); + let pubkey = try_or_else!(SECPublicKey::try_from(pubkey_data), std::ptr::null_mut); + private_key.ecies_shared_key(&pubkey) + } else { + let private_key = try_or_else!(NISSecPrivateKey::try_from(private_key), std::ptr::null_mut); + let pubkey = try_or_else!(NISTPublicKey::try_from(pubkey_data), std::ptr::null_mut); + private_key.ecies_shared_key(&pubkey) + }; + TWData::from(shared_key.to_vec()).into_ptr() +} diff --git a/rust/tw_keypair/src/ffi/mod.rs b/rust/tw_keypair/src/ffi/mod.rs index 33cdc647905..55cfa5ad735 100644 --- a/rust/tw_keypair/src/ffi/mod.rs +++ b/rust/tw_keypair/src/ffi/mod.rs @@ -4,5 +4,7 @@ pub mod asn; pub mod crypto_box; +pub mod curve25519; +pub mod ecdsa; pub mod privkey; pub mod pubkey; diff --git a/rust/tw_keypair/tests/ed25519_waves_tests.rs b/rust/tw_keypair/tests/ed25519_waves_tests.rs index c231ee362b1..1e55445100b 100644 --- a/rust/tw_keypair/tests/ed25519_waves_tests.rs +++ b/rust/tw_keypair/tests/ed25519_waves_tests.rs @@ -6,7 +6,9 @@ use serde::Deserialize; use tw_encoding::hex::{self, as_hex}; use tw_hash::{H256, H512}; use tw_keypair::ed25519::waves::KeyPair; +use tw_keypair::ffi::curve25519::tw_curve25519_pubkey_to_ed25519; use tw_keypair::traits::{KeyPairTrait, SigningKeyTrait, VerifyingKeyTrait}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; use tw_misc::traits::ToBytesZeroizing; /// The tests were generated in C++ using the `trezor-crypto` library. @@ -62,3 +64,17 @@ fn test_ed25519_waves_priv_to_pub() { assert_eq!(public.to_bytes(), test.public); } } + +#[test] +fn test_tw_curve25519_pubkey_to_ed25519() { + let pubkey = "559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d"; + let pubkey_data = hex::decode(pubkey).unwrap(); + let tw_pubkey = TWDataHelper::create(pubkey_data); + + let hash = TWDataHelper::wrap(unsafe { tw_curve25519_pubkey_to_ed25519(tw_pubkey.ptr()) }); + + assert_eq!( + hex::encode(hash.to_vec().unwrap(), false), + "ff84c4bfc095df25b01e48807715856d95af93d88c5b57f30cb0ce567ca4ce56" + ); +} diff --git a/rust/tw_keypair/tests/nist256p1_tests.rs b/rust/tw_keypair/tests/nist256p1_tests.rs index 570f98d4fe0..6024dd47792 100644 --- a/rust/tw_keypair/tests/nist256p1_tests.rs +++ b/rust/tw_keypair/tests/nist256p1_tests.rs @@ -3,10 +3,13 @@ // Copyright © 2017 Trust Wallet. use serde::Deserialize; -use tw_encoding::hex::as_hex; +use tw_encoding::hex::{self, as_hex}; use tw_hash::{H256, H264, H520}; use tw_keypair::ecdsa::nist256p1::{PrivateKey, PublicKey, VerifySignature}; +use tw_keypair::ffi::ecdsa::{tw_ecdsa_pubkey_hash, tw_ecdsa_sig_to_der}; use tw_keypair::traits::VerifyingKeyTrait; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_misc::traits::ToBytesVec; /// The tests were generated in C++ using the `trezor-crypto` library. const NIST256P1_VERIFY: &str = include_str!("nist256p1_verify.json"); @@ -53,3 +56,76 @@ fn test_nist256p1_priv_to_pub() { assert_eq!(actual_public, test.public); } } + +#[test] +fn test_nist256p1_pubkey_compare() { + let p1 = "031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486"; + let p2 = "03d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1ee"; + let p3 = "0305947c8564734b0e634531c405a0b6488e2cb9bcde5eddefcf3f008f0c048115"; + + let pubkey1 = PublicKey::try_from(hex::decode(p1).unwrap().as_slice()).unwrap(); + let pubkey2 = PublicKey::try_from(hex::decode(p2).unwrap().as_slice()).unwrap(); + let pubkey3 = PublicKey::try_from(hex::decode(p3).unwrap().as_slice()).unwrap(); + + let mut pubkeys = vec![pubkey1, pubkey2, pubkey3]; + pubkeys.sort_by(|a, b| a.compare(b)); + assert_eq!(hex::encode(pubkeys[0].to_vec(), false), p3); + assert_eq!(hex::encode(pubkeys[1].to_vec(), false), p1); + assert_eq!(hex::encode(pubkeys[2].to_vec(), false), p2); +} + +#[test] +fn test_nist256p1_shared_key() { + let private_key = PrivateKey::try_from( + hex::decode("bca2a4e7db34577e1193d6b6312244a246832228598c91fd5123cba52c182979") + .unwrap() + .as_slice(), + ) + .unwrap(); + let public_key = PublicKey::try_from( + hex::decode("031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486") + .unwrap() + .as_slice(), + ) + .unwrap(); + let shared_key = private_key.ecies_shared_key(&public_key); + assert_eq!(hex::encode(shared_key, false), "d5277ff8bda8bd043a663583019b9b4397b58c69a7fdbb9c39e6525eb99e5183f9a82cd4ce9f75d81ebc61ced5c763d612a9f8dc255ba4aea25675d882a8e514"); +} + +#[test] +fn test_tw_sig_to_der() { + let signature = "6cc8a52ea475c5ab090bb91f62e1e3e9831450b6941d30ed0600a08acec014db65e7ecee3a3e1f95a53054a03f16f0e44f1d0aa3a44b87a4664819a0f5c0f38800"; + let signature_data = hex::decode(signature).unwrap(); + let tw_signature = TWDataHelper::create(signature_data); + + let signature = TWDataHelper::wrap(unsafe { tw_ecdsa_sig_to_der(tw_signature.ptr(), false) }); + + assert_eq!( + hex::encode(signature.to_vec().unwrap(), false), + "304402206cc8a52ea475c5ab090bb91f62e1e3e9831450b6941d30ed0600a08acec014db022065e7ecee3a3e1f95a53054a03f16f0e44f1d0aa3a44b87a4664819a0f5c0f388" + ); +} + +#[test] +fn test_tw_invalid_sig_to_der() { + let signature = "6ccd"; + let signature_data = hex::decode(signature).unwrap(); + let tw_signature = TWDataHelper::create(signature_data); + + let signature = TWDataHelper::wrap(unsafe { tw_ecdsa_sig_to_der(tw_signature.ptr(), false) }); + assert_eq!(signature.is_null(), true); +} + +#[test] +fn test_tw_pubkey_hash() { + let pubkey = "031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486"; + let pubkey_data = hex::decode(pubkey).unwrap(); + let tw_pubkey = TWDataHelper::create(pubkey_data); + + let hash = TWDataHelper::wrap(unsafe { tw_ecdsa_pubkey_hash(tw_pubkey.ptr(), false, 1) }); + + assert_eq!( + hex::encode(hash.to_vec().unwrap(), false), + "fc5f90ded54731474b8bb18ee579f77ad93427486a888d23a1af92719bcf5640" + ); +} diff --git a/rust/tw_keypair/tests/private_key_ffi_tests.rs b/rust/tw_keypair/tests/private_key_ffi_tests.rs index 68def0d326b..e6716336468 100644 --- a/rust/tw_keypair/tests/private_key_ffi_tests.rs +++ b/rust/tw_keypair/tests/private_key_ffi_tests.rs @@ -7,14 +7,17 @@ use tw_hash::sha2::sha256; use tw_hash::sha3::keccak256; use tw_hash::H256; use tw_keypair::ffi::privkey::{ - tw_private_key_create_with_data, tw_private_key_get_public_key_by_type, - tw_private_key_is_valid, tw_private_key_sign, tw_private_key_sign_as_der, + tw_private_key_bytes, tw_private_key_create_with_data, tw_private_key_data, + tw_private_key_get_public_key_by_type, tw_private_key_is_valid, tw_private_key_sign, + tw_private_key_sign_as_der, tw_private_key_size, }; use tw_keypair::ffi::pubkey::{tw_public_key_data, tw_public_key_delete, tw_public_key_verify}; use tw_keypair::test_utils::tw_private_key_helper::TWPrivateKeyHelper; use tw_keypair::test_utils::tw_public_key_helper::TWPublicKeyHelper; use tw_keypair::tw::{Curve, PublicKeyType}; use tw_memory::ffi::c_byte_array::CByteArray; +use tw_memory::ffi::tw_data::tw_data_create_with_bytes; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; fn test_sign(curve: Curve, secret: &str, msg: &str, expected_sign: &str) { let tw_privkey = TWPrivateKeyHelper::with_hex(secret, curve.to_raw()); @@ -34,6 +37,20 @@ fn test_tw_private_key_create() { ); assert!(!tw_privkey.is_null()); + let bytes = unsafe { tw_private_key_bytes(tw_privkey.ptr()) }; + let size = unsafe { tw_private_key_size(tw_privkey.ptr()) }; + let tw_data = TWDataHelper::wrap(unsafe { tw_data_create_with_bytes(bytes, size) }); + assert_eq!( + hex::encode(tw_data.to_vec().unwrap(), false), + "ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a" + ); + + let data = unsafe { tw_private_key_data(tw_privkey.ptr()) }; + assert_eq!( + hex::encode(unsafe { data.into_vec() }, false), + "ef2cf705af8714b35c0855030f358f2bee356ff3579cea2607b2025d80133c3a" + ); + // Invalid hex. let tw_privkey = TWPrivateKeyHelper::with_bytes(*b"123", Curve::Secp256k1.to_raw()); assert!(tw_privkey.is_null()); diff --git a/rust/tw_keypair/tests/public_key_ffi_tests.rs b/rust/tw_keypair/tests/public_key_ffi_tests.rs index cc4b4fdbb21..bdf163c5b58 100644 --- a/rust/tw_keypair/tests/public_key_ffi_tests.rs +++ b/rust/tw_keypair/tests/public_key_ffi_tests.rs @@ -5,6 +5,7 @@ use tw_encoding::hex; use tw_hash::sha2::sha256; use tw_hash::sha3::keccak256; +use tw_keypair::ffi::ecdsa::{tw_ecdsa_pubkey_compare, tw_ecdsa_shared_key}; use tw_keypair::ffi::privkey::{ tw_private_key_get_public_key_by_type, tw_private_key_sign, tw_private_key_sign_as_der, }; @@ -17,6 +18,7 @@ use tw_keypair::test_utils::tw_private_key_helper::TWPrivateKeyHelper; use tw_keypair::test_utils::tw_public_key_helper::TWPublicKeyHelper; use tw_keypair::tw::{Curve, PublicKeyType}; use tw_memory::ffi::c_byte_array::CByteArray; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; fn test_verify(ty: PublicKeyType, public: &str, msg: &str, sign: &str) { let tw_public = TWPublicKeyHelper::with_hex(public, ty); @@ -335,3 +337,76 @@ fn test_public_key_is_valid() { assert!(is_valid); } + +#[test] +fn test_zillqa_schnorr_public_key_is_valid() { + let public_key_hex = "034ae47910d58b9bde819c3cffa8de4441955508db00aa2540db8e6bf6e99abc1b"; + let bytes = hex::decode(public_key_hex).unwrap(); + let bytes_ptr = bytes.as_ptr(); + let bytes_len = bytes.len(); + + let is_valid = unsafe { + tw_public_key_is_valid(bytes_ptr, bytes_len, PublicKeyType::ZilliqaSchnorr as u32) + }; + + assert!(is_valid); +} + +#[test] +fn test_secp256k1_shared_key_ffi() { + let private_key = TWDataHelper::create( + hex::decode("2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90").unwrap(), + ); + let public_key = TWDataHelper::create( + hex::decode("024edfcf9dfe6c0b5c83d1ab3f78d1b39a46ebac6798e08e19761f5ed89ec83c10").unwrap(), + ); + let shared_key = TWDataHelper::wrap(unsafe { + tw_ecdsa_shared_key(private_key.ptr(), public_key.ptr(), true) + }); + assert_eq!(hex::encode(shared_key.to_vec().unwrap(), false), "a71b4ec5a9577926a1d2aa1d9d99327fd3b68f6a1ea597200a0d890bd3331df300a2d49fec0b2b3e6969ce9263c5d6cf47c191c1ef149373ecc9f0d98116b598"); +} + +#[test] +fn test_nist256p1_shared_key_ffi() { + let private_key = TWDataHelper::create( + hex::decode("bca2a4e7db34577e1193d6b6312244a246832228598c91fd5123cba52c182979").unwrap(), + ); + let public_key = TWDataHelper::create( + hex::decode("031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486").unwrap(), + ); + let shared_key = TWDataHelper::wrap(unsafe { + tw_ecdsa_shared_key(private_key.ptr(), public_key.ptr(), false) + }); + assert_eq!(hex::encode(shared_key.to_vec().unwrap(), false), "d5277ff8bda8bd043a663583019b9b4397b58c69a7fdbb9c39e6525eb99e5183f9a82cd4ce9f75d81ebc61ced5c763d612a9f8dc255ba4aea25675d882a8e514"); +} + +#[test] +fn test_nist256p1_pubkey_compare() { + let public_key_1 = TWDataHelper::create( + hex::decode("031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486").unwrap(), + ); + let public_key_2 = TWDataHelper::create( + hex::decode("03d9fd62df332403d9114f3fa3da0d5aec9dfa42948c2f50738d52470469a1a1ee").unwrap(), + ); + let public_key_3 = TWDataHelper::create( + hex::decode("0305947c8564734b0e634531c405a0b6488e2cb9bcde5eddefcf3f008f0c048115").unwrap(), + ); + let result = unsafe { tw_ecdsa_pubkey_compare(public_key_1.ptr(), public_key_2.ptr(), false) }; + assert_eq!(result, -1); + let result = unsafe { tw_ecdsa_pubkey_compare(public_key_1.ptr(), public_key_3.ptr(), false) }; + assert_eq!(result, 1); + let result = unsafe { tw_ecdsa_pubkey_compare(public_key_2.ptr(), public_key_3.ptr(), false) }; + assert_eq!(result, 1); +} + +#[test] +fn test_secp256k1_pubkey_compare() { + let public_key_1 = TWDataHelper::create( + hex::decode("02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992").unwrap(), + ); + let public_key_2 = TWDataHelper::create( + hex::decode("0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1").unwrap(), + ); + let result = unsafe { tw_ecdsa_pubkey_compare(public_key_1.ptr(), public_key_2.ptr(), true) }; + assert_eq!(result, 1); +} diff --git a/rust/tw_keypair/tests/secp256k1_tests.rs b/rust/tw_keypair/tests/secp256k1_tests.rs index 9e56c024862..76886161e8a 100644 --- a/rust/tw_keypair/tests/secp256k1_tests.rs +++ b/rust/tw_keypair/tests/secp256k1_tests.rs @@ -3,10 +3,14 @@ // Copyright © 2017 Trust Wallet. use serde::Deserialize; +use tw_encoding::hex; use tw_encoding::hex::as_hex; use tw_hash::{H256, H520}; -use tw_keypair::ecdsa::secp256k1::{KeyPair, VerifySignature}; +use tw_keypair::ecdsa::secp256k1::{KeyPair, PrivateKey, PublicKey, VerifySignature}; +use tw_keypair::ffi::ecdsa::{tw_ecdsa_pubkey_hash, tw_ecdsa_sig_to_der}; use tw_keypair::traits::{SigningKeyTrait, VerifyingKeyTrait}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_misc::traits::ToBytesVec; /// The tests were generated in C++ using the `trezor-crypto` library. const SECP256K1_SIGN: &str = include_str!("secp256k1_sign.json"); @@ -33,3 +37,73 @@ fn test_secp256k1_sign_verify() { assert!(keypair.verify(verify_sign, test.hash)); } } + +#[test] +fn test_secp256k1_pubkey_compare() { + let p1 = "02a18a98316b5f52596e75bfa5ca9fa9912edd0c989b86b73d41bb64c9c6adb992"; + let p2 = "0399c6f51ad6f98c9c583f8e92bb7758ab2ca9a04110c0a1126ec43e5453d196c1"; + + let pubkey1 = PublicKey::try_from(hex::decode(p1).unwrap().as_slice()).unwrap(); + let pubkey2 = PublicKey::try_from(hex::decode(p2).unwrap().as_slice()).unwrap(); + + let mut pubkeys = vec![pubkey1, pubkey2]; + pubkeys.sort_by(|a, b| a.compare(b)); + assert_eq!(hex::encode(pubkeys[0].to_vec(), false), p2); + assert_eq!(hex::encode(pubkeys[1].to_vec(), false), p1); +} + +#[test] +fn test_secp256k1_shared_key() { + let private_key = PrivateKey::try_from( + hex::decode("2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90") + .unwrap() + .as_slice(), + ) + .unwrap(); + let public_key = PublicKey::try_from( + hex::decode("024edfcf9dfe6c0b5c83d1ab3f78d1b39a46ebac6798e08e19761f5ed89ec83c10") + .unwrap() + .as_slice(), + ) + .unwrap(); + let shared_key = private_key.ecies_shared_key(&public_key); + assert_eq!(hex::encode(shared_key, false), "a71b4ec5a9577926a1d2aa1d9d99327fd3b68f6a1ea597200a0d890bd3331df300a2d49fec0b2b3e6969ce9263c5d6cf47c191c1ef149373ecc9f0d98116b598"); +} + +#[test] +fn test_tw_sig_to_der() { + let signature = "882d92979f3fd4df2b19eecea6b4ca3104898774e83506d934736a80697a19366f4fde413d1fcf73adc00dd3938be427cea62a2aa166eab209703a3f63bd77fc00"; + let signature_data = hex::decode(signature).unwrap(); + let tw_signature = TWDataHelper::create(signature_data); + + let signature = TWDataHelper::wrap(unsafe { tw_ecdsa_sig_to_der(tw_signature.ptr(), true) }); + + assert_eq!( + hex::encode(signature.to_vec().unwrap(), false), + "3045022100882d92979f3fd4df2b19eecea6b4ca3104898774e83506d934736a80697a193602206f4fde413d1fcf73adc00dd3938be427cea62a2aa166eab209703a3f63bd77fc" + ); +} + +#[test] +fn test_tw_invalid_sig_to_der() { + let signature = "6ccd"; + let signature_data = hex::decode(signature).unwrap(); + let tw_signature = TWDataHelper::create(signature_data); + + let signature = TWDataHelper::wrap(unsafe { tw_ecdsa_sig_to_der(tw_signature.ptr(), false) }); + assert_eq!(signature.is_null(), true); +} + +#[test] +fn test_tw_pubkey_hash() { + let pubkey = "031bec1250aa8f78275f99a6663688f31085848d0ed92f1203e447125f927b7486"; + let pubkey_data = hex::decode(pubkey).unwrap(); + let tw_pubkey = TWDataHelper::create(pubkey_data); + + let hash = TWDataHelper::wrap(unsafe { tw_ecdsa_pubkey_hash(tw_pubkey.ptr(), true, 1) }); + + assert_eq!( + hex::encode(hash.to_vec().unwrap(), false), + "fc5f90ded54731474b8bb18ee579f77ad93427486a888d23a1af92719bcf5640" + ); +} diff --git a/samples/cpp/CMakeLists.txt b/samples/cpp/CMakeLists.txt index 02bfe6e2ad5..1c4fa3e2448 100644 --- a/samples/cpp/CMakeLists.txt +++ b/samples/cpp/CMakeLists.txt @@ -28,7 +28,7 @@ set (CMAKE_C_STANDARD_REQUIRED ON) # ${WALLET_CORE}/src -- internal TrustWalletCore files, for signer protobuf messages # ${WALLET_CORE}/build/local/include) -- for protobuf includes include_directories (${CMAKE_SOURCE_DIR} ${WALLET_CORE}/include ${WALLET_CORE}/src ${WALLET_CORE}/build/local/include) -link_directories (${WALLET_CORE}/build ${WALLET_CORE}/build/trezor-crypto ${WALLET_CORE}/build/local/lib ${WALLET_CORE}/rust/target/release) +link_directories (${WALLET_CORE}/build ${WALLET_CORE}/build/local/lib ${WALLET_CORE}/rust/target/release) find_library(WALLET_CORE_LIB_FILE TrustWalletCore PATH ${WALLET_CORE}/build) if (NOT WALLET_CORE_LIB_FILE) @@ -76,4 +76,4 @@ SET (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${PLATFORM_LINK_FLAGS}") add_executable (sample sample.cpp) # link with our library, and default platform libraries -target_link_libraries (sample PUBLIC TrustWalletCore ${WALLET_CORE_RS_LIB_FILE} TrezorCrypto protobuf pthread ${PLATFORM_LIBS}) +target_link_libraries (sample PUBLIC TrustWalletCore ${WALLET_CORE_RS_LIB_FILE} protobuf pthread ${PLATFORM_LIBS}) diff --git a/samples/cpp/README.md b/samples/cpp/README.md index 5998b7c59d7..628f2a3f48e 100644 --- a/samples/cpp/README.md +++ b/samples/cpp/README.md @@ -23,7 +23,7 @@ and [Build Instructions](https://developer.trustwallet.com/wallet-core/building) You need to download and build WalletCore yourself (there is no official binary distribution). -The dependencies TrezorCrypto and protobuf are also needed, these also come with WalletCore. +The dependency protobuf is also needed, this also comes with WalletCore. You need to [build](https://developer.trustwallet.com/wallet-core/building) the library. diff --git a/samples/go/core/bitcoin.go b/samples/go/core/bitcoin.go index b9b0d218298..56223db74a8 100644 --- a/samples/go/core/bitcoin.go +++ b/samples/go/core/bitcoin.go @@ -1,7 +1,7 @@ package core // #cgo CFLAGS: -I../../../include -// #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -L../../../build/trezor-crypto -lTrustWalletCore -lwallet_core_rs -lprotobuf -lTrezorCrypto -lstdc++ -lm +// #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -lTrustWalletCore -lwallet_core_rs -lprotobuf -lstdc++ -lm // #include // #include // #include diff --git a/samples/go/core/coin.go b/samples/go/core/coin.go index f1dd5dc94d9..aced3e60fbd 100644 --- a/samples/go/core/coin.go +++ b/samples/go/core/coin.go @@ -1,7 +1,7 @@ package core // #cgo CFLAGS: -I../../../include -// #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -L../../../build/trezor-crypto -lTrustWalletCore -lwallet_core_rs -lprotobuf -lTrezorCrypto -lstdc++ -lm +// #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -lTrustWalletCore -lwallet_core_rs -lprotobuf -lstdc++ -lm // #include // #include import "C" diff --git a/samples/go/core/datavector.go b/samples/go/core/datavector.go index 04a4e4bd520..60cade5449e 100644 --- a/samples/go/core/datavector.go +++ b/samples/go/core/datavector.go @@ -1,7 +1,7 @@ package core // #cgo CFLAGS: -I../../../include -// #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -L../../../build/trezor-crypto -lTrustWalletCore -lwallet_core_rs -lprotobuf -lTrezorCrypto -lstdc++ -lm +// #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -lTrustWalletCore -lwallet_core_rs -lprotobuf -lstdc++ -lm // #include import "C" import "tw/types" diff --git a/samples/go/core/mnemonic.go b/samples/go/core/mnemonic.go index 7b020aecc47..5414117568c 100644 --- a/samples/go/core/mnemonic.go +++ b/samples/go/core/mnemonic.go @@ -1,7 +1,7 @@ package core // #cgo CFLAGS: -I../../../include -// #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -L../../../build/trezor-crypto -lTrustWalletCore -lwallet_core_rs -lprotobuf -lTrezorCrypto -lstdc++ -lm +// #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -lTrustWalletCore -lwallet_core_rs -lprotobuf -lstdc++ -lm // #include import "C" diff --git a/samples/go/core/publicKey.go b/samples/go/core/publicKey.go index bf70a64622f..b97a4faa874 100644 --- a/samples/go/core/publicKey.go +++ b/samples/go/core/publicKey.go @@ -1,7 +1,7 @@ package core // #cgo CFLAGS: -I../../../include -// #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -L../../../build/trezor-crypto -lTrustWalletCore -lwallet_core_rs -lprotobuf -lTrezorCrypto -lstdc++ -lm +// #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -lTrustWalletCore -lwallet_core_rs -lprotobuf -lstdc++ -lm // #include import "C" diff --git a/samples/go/core/transaction.go b/samples/go/core/transaction.go index 8aa9e1a900c..f5633fb238a 100644 --- a/samples/go/core/transaction.go +++ b/samples/go/core/transaction.go @@ -1,7 +1,7 @@ package core // #cgo CFLAGS: -I../../../include -// #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -L../../../build/trezor-crypto -lTrustWalletCore -lwallet_core_rs -lprotobuf -lTrezorCrypto -lstdc++ -lm +// #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -lTrustWalletCore -lwallet_core_rs -lprotobuf -lstdc++ -lm // #include // #include import "C" diff --git a/samples/go/core/transactionHelper.go b/samples/go/core/transactionHelper.go index df2a78c64dd..2b9b87d2640 100644 --- a/samples/go/core/transactionHelper.go +++ b/samples/go/core/transactionHelper.go @@ -1,7 +1,7 @@ package core // #cgo CFLAGS: -I../../../include -// #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -L../../../build/trezor-crypto -lTrustWalletCore -lwallet_core_rs -lprotobuf -lTrezorCrypto -lstdc++ -lm +// #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -lTrustWalletCore -lwallet_core_rs -lprotobuf -lstdc++ -lm // #include import "C" import "tw/types" diff --git a/samples/go/core/wallet.go b/samples/go/core/wallet.go index ee95b763631..a002fa62383 100644 --- a/samples/go/core/wallet.go +++ b/samples/go/core/wallet.go @@ -1,7 +1,7 @@ package core // #cgo CFLAGS: -I../../../include -// #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -L../../../build/trezor-crypto -lTrustWalletCore -lwallet_core_rs -lprotobuf -lTrezorCrypto -lstdc++ -lm +// #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -lTrustWalletCore -lwallet_core_rs -lprotobuf -lstdc++ -lm // #include // #include // #include diff --git a/samples/go/dev-console/native/cgo.go b/samples/go/dev-console/native/cgo.go index b393d457f7a..2c497614aa4 100644 --- a/samples/go/dev-console/native/cgo.go +++ b/samples/go/dev-console/native/cgo.go @@ -1,7 +1,7 @@ package native // #cgo CFLAGS: -I packaged/include -// #cgo LDFLAGS: -lTrustWalletCore -lstdc++ -lm -lprotobuf -lwallet_core_rs -lTrezorCrypto +// #cgo LDFLAGS: -lTrustWalletCore -lstdc++ -lm -lprotobuf -lwallet_core_rs // // // #cgo LDFLAGS: -Wl,-rpath,${SRCDIR}/packaged/lib -L${SRCDIR}/packaged/lib diff --git a/samples/go/dev-console/prepare.sh b/samples/go/dev-console/prepare.sh index ff2a4f6af3a..2555093d9f3 100755 --- a/samples/go/dev-console/prepare.sh +++ b/samples/go/dev-console/prepare.sh @@ -6,6 +6,5 @@ ninja cp libTrustWalletCore.a ../native/packaged/lib/ cp libprotobuf.a ../native/packaged/lib cp ../../../../build/local/lib/libwallet_core_rs.a ../native/packaged/lib -cp trezor-crypto/libTrezorCrypto.a ../native/packaged/lib cd - cp -R ../../../include native/packaged/ diff --git a/samples/go/types/twdata.go b/samples/go/types/twdata.go index 5bc9fa90f77..df61b9b0310 100644 --- a/samples/go/types/twdata.go +++ b/samples/go/types/twdata.go @@ -1,7 +1,7 @@ package types // #cgo CFLAGS: -I../../../include -// #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -L../../../build/trezor-crypto -lTrustWalletCore -lwallet_core_rs -lprotobuf -lTrezorCrypto -lstdc++ -lm +// #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -lTrustWalletCore -lwallet_core_rs -lprotobuf -lstdc++ -lm // #include import "C" diff --git a/samples/go/types/twstring.go b/samples/go/types/twstring.go index b61c52c565a..4df40d7d076 100644 --- a/samples/go/types/twstring.go +++ b/samples/go/types/twstring.go @@ -1,7 +1,7 @@ package types // #cgo CFLAGS: -I../../../include -// #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -L../../../build/trezor-crypto -lTrustWalletCore -lwallet_core_rs -lprotobuf -lTrezorCrypto -lstdc++ -lm +// #cgo LDFLAGS: -L../../../build -L../../../build/local/lib -lTrustWalletCore -lwallet_core_rs -lprotobuf -lstdc++ -lm // #include import "C" diff --git a/samples/rust/README.md b/samples/rust/README.md index 237b65b34ae..f9923b9584a 100644 --- a/samples/rust/README.md +++ b/samples/rust/README.md @@ -20,7 +20,7 @@ cargo run - The app links with the wallet-core library (C/C++). - The `walletcore_iface.rs` file contains the interface definitions in Rust. -- Links with `TrustWalletCore`, `TrezorCrypto`, `protobuf`, and the platform libc (`c++` or `stdc++`). Build/link parameters are in `build.rs`. +- Links with `TrustWalletCore`, `protobuf`, and the platform libc (`c++` or `stdc++`). Build/link parameters are in `build.rs`. - Rust proto files are created during the build process, from the `.proto` files in wallet-core, into subfolder `src/wc_proto` (see `build.rs`). diff --git a/samples/rust/src/build.rs b/samples/rust/src/build.rs index d027e03a745..ea5e6e734ab 100644 --- a/samples/rust/src/build.rs +++ b/samples/rust/src/build.rs @@ -9,7 +9,7 @@ use std::path::Path; static WALLET_CORE_PROJECT_DIR: &str = "../.."; // libs to link with, in reverse dependency order -static LIBS: [&str; 4] = ["TrustWalletCore", "TrezorCrypto", "protobuf", "wallet_core_rs"]; +static LIBS: [&str; 3] = ["TrustWalletCore", "protobuf", "wallet_core_rs"]; fn main() { // Generate protobuf interface files @@ -35,7 +35,6 @@ fn main() { println!("Protobuf codegen to {} ready", out_dir); println!("cargo:rustc-link-search=native={}/build", WALLET_CORE_PROJECT_DIR); - println!("cargo:rustc-link-search=native={}/build/trezor-crypto", WALLET_CORE_PROJECT_DIR); println!("cargo:rustc-link-search=native={}/build/local/lib", WALLET_CORE_PROJECT_DIR); println!("cargo:rustc-link-search=native={}/rust/target/release", WALLET_CORE_PROJECT_DIR); diff --git a/src/Bech32Address.cpp b/src/Bech32Address.cpp index 8394773b635..dae0f08b513 100644 --- a/src/Bech32Address.cpp +++ b/src/Bech32Address.cpp @@ -5,7 +5,6 @@ #include "Bech32Address.h" #include "Bech32.h" #include "Data.h" -#include using namespace TW; diff --git a/src/Bitcoin/CashAddress.cpp b/src/Bitcoin/CashAddress.cpp index 1f98d5fb48c..4c9326911e0 100644 --- a/src/Bitcoin/CashAddress.cpp +++ b/src/Bitcoin/CashAddress.cpp @@ -5,8 +5,9 @@ #include "CashAddress.h" #include "../Coin.h" -#include -#include +#include "../cash_addr.h" +#include "../Hash.h" +#include #include #include @@ -83,7 +84,14 @@ CashAddress::CashAddress(std::string hrp, const PublicKey& publicKey) } std::array payload{}; payload[0] = 0; - ecdsa_get_pubkeyhash(publicKey.bytes.data(), HASHER_SHA2_RIPEMD, payload.data() + 1); + auto data = TWDataCreateWithBytes(publicKey.bytes.data(), publicKey.bytes.size()); + auto result = TWECDSAPubkeyHash(data, true, Hash::HasherSha256ripemd); + TWDataDelete(data); + if (result == nullptr) { + throw std::invalid_argument("Invalid public key hash"); + } + std::copy(TWDataBytes(result), TWDataBytes(result) + TWDataSize(result), payload.begin() + 1); + TWDataDelete(result); size_t outlen = 0; auto success = cash_addr_to_data(bytes.data(), &outlen, payload.data(), 21) != 0; diff --git a/src/Bitcoin/SegwitAddress.cpp b/src/Bitcoin/SegwitAddress.cpp index 70ec462d391..c72d55b11c6 100644 --- a/src/Bitcoin/SegwitAddress.cpp +++ b/src/Bitcoin/SegwitAddress.cpp @@ -5,8 +5,8 @@ #include "SegwitAddress.h" #include "../Bech32.h" - -#include +#include "../Hash.h" +#include namespace TW::Bitcoin { @@ -34,8 +34,15 @@ SegwitAddress::SegwitAddress(const PublicKey& publicKey, std::string hrp) throw std::invalid_argument("SegwitAddress needs a compressed SECP256k1 public key."); } witnessProgram.resize(20); - ecdsa_get_pubkeyhash(publicKey.compressed().bytes.data(), HASHER_SHA2_RIPEMD, - witnessProgram.data()); + auto compressed = publicKey.compressed(); + auto data = TWDataCreateWithBytes(compressed.bytes.data(), compressed.bytes.size()); + auto result = TWECDSAPubkeyHash(data, true, Hash::HasherSha256ripemd); + TWDataDelete(data); + if (result == nullptr) { + throw std::invalid_argument("Invalid public key hash"); + } + std::copy(TWDataBytes(result), TWDataBytes(result) + TWDataSize(result), witnessProgram.begin()); + TWDataDelete(result); } std::tuple SegwitAddress::decode(const std::string& addr) { diff --git a/src/EOS/Address.cpp b/src/EOS/Address.cpp index 72e6d52d2ed..6c9333c508f 100644 --- a/src/EOS/Address.cpp +++ b/src/EOS/Address.cpp @@ -5,8 +5,7 @@ #include "Address.h" #include "../Base58.h" #include "../BinaryCoding.h" - -#include +#include "../Hash.h" #include @@ -35,36 +34,30 @@ bool Address::isValid(const Data& bytes, EOS::Type type) { /// IMPORTANT: THERE ARE NO SIZE CHECKS. THE BUFFER IS ASSUMED /// TO HAVE PublicKeyDataSize bytes. uint32_t Address::createChecksum(const Data& bytes, Type type) { - // create our own checksum and compare the two - uint8_t hash[RIPEMD160_DIGEST_LENGTH]; - RIPEMD160_CTX ctx; - ripemd160_Init(&ctx); - - // add the bytes to the hash input - ripemd160_Update(&ctx, bytes.data(), PublicKeyDataSize); - - // append the prefix to the hash input as well in case of modern types + Data hashInput; + append(hashInput, Data(bytes.begin(), bytes.begin() + PublicKeyDataSize)); + switch (type) { case Type::Legacy: // no extra input break; - case Type::ModernK1: - ripemd160_Update(&ctx, - (const uint8_t*)Modern::K1::prefix.c_str(), - static_cast(Modern::K1::prefix.size())); + case Type::ModernK1: { + auto startK1 = (const uint8_t*)Modern::K1::prefix.c_str(); + auto endK1 = startK1 + static_cast(Modern::K1::prefix.size()); + append(hashInput, Data(startK1, endK1)); break; + } - case Type::ModernR1: - ripemd160_Update(&ctx, - (const uint8_t*)Modern::R1::prefix.c_str(), - static_cast(Modern::R1::prefix.size())); + case Type::ModernR1: { + auto startR1 = (const uint8_t*)Modern::R1::prefix.c_str(); + auto endR1 = startR1 + static_cast(Modern::R1::prefix.size()); + append(hashInput, Data(startR1, endR1)); break; } + } - // finalize the hash - ripemd160_Final(&ctx, hash); - - return decode32LE(hash); + auto hash = Hash::ripemd(hashInput.data(), hashInput.size()); + return decode32LE(hash.data()); } /// Extracts and verifies the key data from a base58 string. diff --git a/src/EOS/Signer.cpp b/src/EOS/Signer.cpp index a9fe3dbc608..4a4bc8ffe95 100644 --- a/src/EOS/Signer.cpp +++ b/src/EOS/Signer.cpp @@ -5,9 +5,6 @@ #include "Signer.h" #include "Asset.h" #include "PackedTransaction.h" - -#include - namespace TW::EOS { Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { diff --git a/src/EOS/Transaction.cpp b/src/EOS/Transaction.cpp index f5ae6608f46..96fe968dcd2 100644 --- a/src/EOS/Transaction.cpp +++ b/src/EOS/Transaction.cpp @@ -5,8 +5,7 @@ #include "../Base58.h" #include "../HexCoding.h" #include "Transaction.h" - -#include +#include "../Hash.h" #include #include @@ -43,10 +42,7 @@ std::string Signature::string() const noexcept { buffer.push_back(static_cast(c)); } - Data hash; - hash.resize(RIPEMD160_DIGEST_LENGTH); - - ripemd160(buffer.data(), static_cast(buffer.size()), hash.data()); + auto hash = Hash::ripemd(buffer.data(), buffer.size()); // drop the subPrefix and append the checksum to the bufer buffer.resize(DataSize); diff --git a/src/FIO/Address.cpp b/src/FIO/Address.cpp index abfc1046846..314a9f2c7cd 100644 --- a/src/FIO/Address.cpp +++ b/src/FIO/Address.cpp @@ -5,9 +5,7 @@ #include "Address.h" #include "../Base58.h" #include "../BinaryCoding.h" - -#include - +#include "../Hash.h" #include using namespace TW; @@ -34,18 +32,8 @@ bool Address::isValid(const Data& bytes) { /// Creates a checksum of PublicKeyDataSize bytes at the buffer uint32_t Address::createChecksum(const Data& bytes) { - // create checksum and compare - uint8_t hash[RIPEMD160_DIGEST_LENGTH]; - RIPEMD160_CTX ctx; - ripemd160_Init(&ctx); - - // add the bytes to the hash input - ripemd160_Update(&ctx, bytes.data(), PublicKey::secp256k1Size); - - // finalize the hash - ripemd160_Final(&ctx, hash); - - return decode32LE(hash); + auto hash = Hash::ripemd(bytes.data(), PublicKey::secp256k1Size); + return decode32LE(hash.data()); } /// Decode and verifies the key data from a base58 string. diff --git a/src/FIO/Encryption.cpp b/src/FIO/Encryption.cpp index ee2c2b54c6c..08dc09f49dc 100644 --- a/src/FIO/Encryption.cpp +++ b/src/FIO/Encryption.cpp @@ -3,6 +3,7 @@ // Copyright © 2017 Trust Wallet. #include "Encryption.h" +#include "rand.h" #include "../Base64.h" #include "../Encrypt.h" @@ -10,12 +11,11 @@ #include "../HexCoding.h" #include "../PrivateKey.h" #include "../PublicKey.h" - -#include -#include -#include +#include "../Utils.h" +#include #include + #include namespace TW::FIO { @@ -87,22 +87,14 @@ Data Encryption::checkDecrypt(const Data& secret, const Data& message) { Data Encryption::getSharedSecret(const PrivateKey& privateKey1, const PublicKey& publicKey2) { // See https://github.com/fioprotocol/fiojs/blob/master/src/ecc/key_private.js - - curve_point KBP; - [[maybe_unused]] int read_res = ecdsa_read_pubkey(&secp256k1, publicKey2.bytes.data(), &KBP); - assert(read_res); - - bignum256 privBN; - bn_read_be(privateKey1.bytes.data(), &privBN); - - curve_point P; - point_multiply(&secp256k1, &privBN, &KBP, &P); - - Data S(32); - bn_write_be(&P.x, S.data()); - - // SHA512 used in ECIES - return Hash::sha512(S); + + auto privateKey = wrapTWData(TWDataCreateWithBytes(privateKey1.bytes.data(), privateKey1.bytes.size())); + auto publicKey = wrapTWData(TWDataCreateWithBytes(publicKey2.bytes.data(), publicKey2.bytes.size())); + auto sharedKey = wrapTWData(TWECDSASharedKey(privateKey.get(), publicKey.get(), true)); + if (sharedKey == nullptr) { + throw std::invalid_argument("Invalid shared key"); + } + return dataFromTWData(sharedKey); } Data Encryption::encrypt(const PrivateKey& privateKey1, const PublicKey& publicKey2, const Data& message, const Data& iv) { diff --git a/src/FIO/Signer.cpp b/src/FIO/Signer.cpp index 571f62912a5..7652d7772fd 100644 --- a/src/FIO/Signer.cpp +++ b/src/FIO/Signer.cpp @@ -12,9 +12,6 @@ #include "../proto/Common.pb.h" #include "TransactionBuilder.h" -#include -#include - #include namespace TW::FIO { diff --git a/src/Groestlcoin/Address.cpp b/src/Groestlcoin/Address.cpp index 74253e2799b..ea1657681d6 100644 --- a/src/Groestlcoin/Address.cpp +++ b/src/Groestlcoin/Address.cpp @@ -6,7 +6,9 @@ #include "Base58.h" #include -#include +#include "../Hash.h" +#include "../Utils.h" +#include namespace TW::Groestlcoin { @@ -50,7 +52,13 @@ Address::Address(const PublicKey& publicKey, uint8_t prefix) { throw std::invalid_argument("Groestlcoin::Address needs a compressed SECP256k1 public key."); } bytes[0] = prefix; - ecdsa_get_pubkeyhash(publicKey.bytes.data(), HASHER_SHA2_RIPEMD, bytes.data() + 1); + auto data = wrapTWData(TWDataCreateWithBytes(publicKey.bytes.data(), publicKey.bytes.size())); + auto result = wrapTWData(TWECDSAPubkeyHash(data.get(), true, Hash::HasherSha256ripemd)); + if (result == nullptr) { + throw std::invalid_argument("Invalid public key hash"); + } + auto resultData = dataFromTWData(result); + std::copy(resultData.begin(), resultData.end(), bytes.begin() + 1); } std::string Address::string() const { diff --git a/src/Icon/Address.cpp b/src/Icon/Address.cpp index 15eaf1d7a02..d50321ef586 100644 --- a/src/Icon/Address.cpp +++ b/src/Icon/Address.cpp @@ -5,8 +5,7 @@ #include "Address.h" #include "../HexCoding.h" - -#include +#include "../Hash.h" namespace TW::Icon { @@ -43,8 +42,7 @@ Address::Address(const std::string& string) { Address::Address(const PublicKey& publicKey, enum AddressType type) : type(type) { - auto hash = std::array(); - sha3_256(publicKey.bytes.data() + 1, publicKey.bytes.size() - 1, hash.data()); + auto hash = Hash::sha3_256(publicKey.bytes.data() + 1, publicKey.bytes.size() - 1); std::copy(hash.end() - Address::size, hash.end(), bytes.begin()); } diff --git a/src/Keystore/AESParameters.cpp b/src/Keystore/AESParameters.cpp index f2156144a6c..857c51e62bb 100644 --- a/src/Keystore/AESParameters.cpp +++ b/src/Keystore/AESParameters.cpp @@ -5,8 +5,7 @@ #include "AESParameters.h" #include "../HexCoding.h" - -#include +#include "rand.h" using namespace TW; diff --git a/src/Keystore/EncryptionParameters.cpp b/src/Keystore/EncryptionParameters.cpp index d7e089d1cee..620d077b578 100644 --- a/src/Keystore/EncryptionParameters.cpp +++ b/src/Keystore/EncryptionParameters.cpp @@ -59,7 +59,7 @@ static Data rustPbkdf2(const Data& password, const PBKDF2Parameters& params) { Rust::TWDataWrapper passwordData = password; Rust::TWDataWrapper saltData = params.salt; - Rust::TWDataWrapper res = Rust::crypto_pbkdf2( + Rust::TWDataWrapper res = Rust::tw_pbkdf2_hmac_sha256( passwordData.get(), saltData.get(), params.iterations, diff --git a/src/Keystore/PBKDF2Parameters.cpp b/src/Keystore/PBKDF2Parameters.cpp index e965e7bba16..be7a9868568 100644 --- a/src/Keystore/PBKDF2Parameters.cpp +++ b/src/Keystore/PBKDF2Parameters.cpp @@ -3,8 +3,7 @@ // Copyright © 2017 Trust Wallet. #include "PBKDF2Parameters.h" - -#include +#include "rand.h" using namespace TW; diff --git a/src/Keystore/ScryptParameters.cpp b/src/Keystore/ScryptParameters.cpp index 4ec194dd5e8..5bfbf090058 100644 --- a/src/Keystore/ScryptParameters.cpp +++ b/src/Keystore/ScryptParameters.cpp @@ -4,8 +4,8 @@ #include "ScryptParameters.h" -#include #include +#include "rand.h" using namespace TW; diff --git a/src/Keystore/StoredKey.cpp b/src/Keystore/StoredKey.cpp index ca66e60c179..845c4be8734 100644 --- a/src/Keystore/StoredKey.cpp +++ b/src/Keystore/StoredKey.cpp @@ -10,7 +10,7 @@ #include "PrivateKey.h" #include -#include +#include "memzero.h" #include #include diff --git a/src/NULS/Address.cpp b/src/NULS/Address.cpp index d71d4279341..5aba91e1cf9 100644 --- a/src/NULS/Address.cpp +++ b/src/NULS/Address.cpp @@ -3,11 +3,13 @@ // Copyright © 2017 Trust Wallet. #include "Address.h" -#include +#include +#include "../Hash.h" #include "../Base58.h" #include "../BinaryCoding.h" #include "../HexCoding.h" +#include "../Utils.h" using namespace TW; @@ -59,7 +61,13 @@ Address::Address(const TW::PublicKey& publicKey, bool isMainnet) { } // Address Type bytes[2] = addressType; - ecdsa_get_pubkeyhash(publicKey.bytes.data(), HASHER_SHA2_RIPEMD, bytes.begin() + 3); + auto data = wrapTWData(TWDataCreateWithBytes(publicKey.bytes.data(), publicKey.bytes.size())); + auto result = wrapTWData(TWECDSAPubkeyHash(data.get(), true, Hash::HasherSha256ripemd)); + if (result == nullptr) { + throw std::invalid_argument("Invalid public key hash"); + } + auto resultData = dataFromTWData(result); + std::copy(resultData.begin(), resultData.end(), bytes.begin() + 3); bytes[23] = checksum(bytes); } diff --git a/src/NULS/Signer.cpp b/src/NULS/Signer.cpp index 51b44385159..6bb550ff52e 100644 --- a/src/NULS/Signer.cpp +++ b/src/NULS/Signer.cpp @@ -3,12 +3,13 @@ // Copyright © 2017 Trust Wallet. #include "Signer.h" -#include #include "Address.h" #include "BinaryCoding.h" #include "../Hash.h" #include "../PrivateKey.h" +#include "../Utils.h" +#include using namespace TW; @@ -196,10 +197,15 @@ Data Signer::buildSignedTx(const std::vector publicKeys, std::copy(publicKeys[i].begin(), publicKeys[i].end(), std::back_inserter(transactionSignature)); - std::array tempSigBytes; - size_t size = ecdsa_sig_to_der(signatures[i].data(), tempSigBytes.data()); + auto sig = wrapTWData(TWDataCreateWithBytes(signatures[i].data(), signatures[i].size())); + auto der = wrapTWData(TWECDSASigToDER(sig.get(), true)); + if (der == nullptr) { + throw std::invalid_argument("Invalid signature"); + } + auto signature = Data{}; - std::copy(tempSigBytes.begin(), tempSigBytes.begin() + size, std::back_inserter(signature)); + auto derData = dataFromTWData(der); + std::copy(derData.begin(), derData.end(), std::back_inserter(signature)); encodeVarInt(signature.size(), transactionSignature); std::copy(signature.begin(), signature.end(), std::back_inserter(transactionSignature)); diff --git a/src/Nano/Address.cpp b/src/Nano/Address.cpp index beb05b919be..c0a3fd4c653 100644 --- a/src/Nano/Address.cpp +++ b/src/Nano/Address.cpp @@ -4,27 +4,69 @@ // Copyright © 2017 Trust Wallet. #include "Address.h" -#include +#include "../Hash.h" +#include "../Base32.h" #include namespace TW::Nano { +static const char* BASE32_ALPHABET_NANO = "13456789abcdefghijkmnopqrstuwxyz"; + +const size_t CHECKSUM_LEN = 5; +const size_t PADDING_LEN = 3; +const size_t ADDRESS_BASE_LENGTH = 60; +const size_t PUBLIC_KEY_LEN = 32; +const size_t RAW_LEN = PADDING_LEN + PUBLIC_KEY_LEN + CHECKSUM_LEN; + const std::string kPrefixNano{"nano_"}; const std::string kPrefixXrb{"xrb_"}; +bool isValidInternal(const std::string& address, const std::string& prefix, uint8_t* publicKey) { + if (address.length() != prefix.length() + ADDRESS_BASE_LENGTH) { + return false; + } + + // Validate that the prefix matches + if (address.substr(0, prefix.length()) != prefix) { + return false; + } + + // Try to decode the address + std::string encoded = "1111" + address.substr(prefix.length()); + + Data raw; + raw.resize(RAW_LEN); + if (!Base32::decode(encoded, raw, BASE32_ALPHABET_NANO)) { + return false; + } + + // Validate the checksum + Data checksum = Hash::blake2b( + Data(raw.begin() + PADDING_LEN, raw.begin() + PADDING_LEN + PUBLIC_KEY_LEN), + CHECKSUM_LEN + ); + + // Compare checksums + for (size_t i = 0; i < CHECKSUM_LEN; i++) { + if (raw[40 - CHECKSUM_LEN + i] != checksum[CHECKSUM_LEN - (i + 1)]) { + return false; + } + } + + if (publicKey != nullptr) { + std::copy(raw.begin() + PADDING_LEN, raw.begin() + PADDING_LEN + 32, publicKey); + } + + return true; +} + bool Address::isValid(const std::string& address) { bool valid = false; - valid = nano_validate_address( - kPrefixNano.c_str(), kPrefixNano.length(), - address.c_str(), address.length(), - nullptr); + valid = isValidInternal(address, kPrefixNano, nullptr); if (!valid) { - valid = nano_validate_address( - kPrefixXrb.c_str(), kPrefixXrb.length(), - address.c_str(), address.length(), - nullptr); + valid = isValidInternal(address, kPrefixXrb, nullptr); } return valid; @@ -33,16 +75,10 @@ bool Address::isValid(const std::string& address) { Address::Address(const std::string& address) { bool valid = false; - valid = nano_validate_address( - kPrefixNano.c_str(), kPrefixNano.length(), - address.c_str(), address.length(), - bytes.data()); + valid = isValidInternal(address, kPrefixNano, bytes.data()); if (!valid) { - valid = nano_validate_address( - kPrefixXrb.c_str(), kPrefixXrb.length(), - address.c_str(), address.length(), - bytes.data()); + valid = isValidInternal(address, kPrefixXrb, bytes.data()); } // Ensure address is valid @@ -61,16 +97,15 @@ Address::Address(const PublicKey& publicKey) { } std::string Address::string() const { - std::array out = {0}; - - size_t count = nano_get_address( - bytes.data(), - kPrefixNano.c_str(), kPrefixNano.length(), - out.data(), out.size()); - // closing \0 - assert(count < out.size()); - out[count] = 0; - return {out.data()}; + Data bytesAsData; + bytesAsData.assign(bytes.begin(), bytes.end()); + auto hash = Hash::blake2b(bytesAsData, CHECKSUM_LEN); + bytesAsData.insert(bytesAsData.begin(), PADDING_LEN, 0); + bytesAsData.insert(bytesAsData.end(), hash.rbegin(), hash.rend()); + + auto result = Base32::encode(bytesAsData, BASE32_ALPHABET_NANO); + result.replace(0, kPrefixNano.length()-1, kPrefixNano); + return result; } } // namespace TW::Nano diff --git a/src/Nimiq/Address.cpp b/src/Nimiq/Address.cpp index 46ab1bb1f5e..7653cfd03c1 100644 --- a/src/Nimiq/Address.cpp +++ b/src/Nimiq/Address.cpp @@ -5,8 +5,7 @@ #include "Address.h" #include "../Base32.h" - -#include +#include "../Hash.h" #include @@ -87,8 +86,7 @@ Address::Address(const std::vector& data) { } Address::Address(const PublicKey& publicKey) { - auto hash = std::array(); - tc_blake2b(publicKey.bytes.data(), 32, hash.data(), hash.size()); + auto hash = Hash::blake2b(publicKey.bytes.data(), publicKey.bytes.size()); std::copy(hash.begin(), hash.begin() + Address::size, bytes.begin()); } diff --git a/src/Nimiq/Signer.cpp b/src/Nimiq/Signer.cpp index 4ad577cf0a1..69d31b656c5 100644 --- a/src/Nimiq/Signer.cpp +++ b/src/Nimiq/Signer.cpp @@ -3,7 +3,6 @@ // Copyright © 2017 Trust Wallet. #include "Signer.h" -#include #include diff --git a/src/Ontology/Address.cpp b/src/Ontology/Address.cpp index 6cf098eb111..b2897237a41 100644 --- a/src/Ontology/Address.cpp +++ b/src/Ontology/Address.cpp @@ -7,8 +7,7 @@ #include "ParamsBuilder.h" #include "../Hash.h" - -#include +#include "../Base58.h" #include #include @@ -29,8 +28,7 @@ Address::Address(const std::string& b58Address) { if (!Address::isValid(b58Address)) { throw std::runtime_error("Invalid base58 encode address."); } - Data addressWithVersion(size + 1); - base58_decode_check(b58Address.c_str(), HASHER_SHA2D, addressWithVersion.data(), size + 1); + const auto addressWithVersion = Base58::decodeCheck(b58Address, Rust::Base58Alphabet::Bitcoin, Hash::HasherSha256d); std::copy(addressWithVersion.begin() + 1, addressWithVersion.end(), _data.begin()); } @@ -54,21 +52,16 @@ bool Address::isValid(const std::string& b58Address) noexcept { if (b58Address.length() != 34) { return false; } - Data addressWithVersion(size + 1); - auto len = - base58_decode_check(b58Address.c_str(), HASHER_SHA2D, addressWithVersion.data(), size + 1); - return len == size + 1; + const auto addressWithVersion = + Base58::decodeCheck(b58Address, Rust::Base58Alphabet::Bitcoin, Hash::HasherSha256d); + return addressWithVersion.size() == size + 1; } std::string Address::string() const { std::vector encodeData(size + 1); encodeData[0] = version; std::copy(_data.begin(), _data.end(), encodeData.begin() + 1); - size_t b58StrSize = 34; - std::string b58Str(b58StrSize, ' '); - base58_encode_check(encodeData.data(), (int)encodeData.size(), HASHER_SHA2D, &b58Str[0], - (int)b58StrSize + 1); - return b58Str; + return Base58::encodeCheck(encodeData, Rust::Base58Alphabet::Bitcoin, Hash::HasherSha256d); } } // namespace TW::Ontology diff --git a/src/Ontology/ParamsBuilder.cpp b/src/Ontology/ParamsBuilder.cpp index 7c529aa0080..beed82f2d1f 100644 --- a/src/Ontology/ParamsBuilder.cpp +++ b/src/Ontology/ParamsBuilder.cpp @@ -5,14 +5,13 @@ #include "ParamsBuilder.h" #include "Data.h" #include "OpCode.h" - -#include -#include -#include +#include "../Utils.h" #include #include +#include + namespace TW::Ontology { void ParamsBuilder::buildNeoVmParam(ParamsBuilder& builder, const NeoVmParamValue& param) { @@ -207,14 +206,10 @@ Data ParamsBuilder::fromMultiPubkey(uint8_t m, const std::vector& pubKeys) builder.push(m); auto sortedPubKeys = pubKeys; std::sort(sortedPubKeys.begin(), sortedPubKeys.end(), [](Data& o1, Data& o2) -> int { - curve_point p1, p2; - ecdsa_read_pubkey(&nist256p1, o1.data(), &p1); - ecdsa_read_pubkey(&nist256p1, o2.data(), &p2); - auto result = bn_is_less(&p1.x, &p2.x); - if (result != 0) { - return result; - } - return bn_is_less(&p1.y, &p2.y); + auto pubkey1 = wrapTWData(TWDataCreateWithBytes(o1.data(), o1.size())); + auto pubkey2 = wrapTWData(TWDataCreateWithBytes(o2.data(), o2.size())); + auto result = TWECDSAPubkeyCompare(pubkey1.get(), pubkey2.get(), false); + return result < 0; }); for (auto const& pk : sortedPubKeys) { builder.push(pk); diff --git a/src/PrivateKey.cpp b/src/PrivateKey.cpp index 1195535c832..4534cd21b48 100644 --- a/src/PrivateKey.cpp +++ b/src/PrivateKey.cpp @@ -7,7 +7,7 @@ #include "HexCoding.h" #include "PublicKey.h" -#include +#include #include diff --git a/src/PublicKey.cpp b/src/PublicKey.cpp index 5be44c9bc78..5ebb3a604cc 100644 --- a/src/PublicKey.cpp +++ b/src/PublicKey.cpp @@ -8,7 +8,7 @@ #include "rust/bindgen/WalletCoreRSBindgen.h" #include "rust/Wrapper.h" -#include +#include "memzero.h" #include diff --git a/src/Stellar/Address.cpp b/src/Stellar/Address.cpp index dbf2261388e..fab5894328f 100644 --- a/src/Stellar/Address.cpp +++ b/src/Stellar/Address.cpp @@ -7,7 +7,7 @@ #include "../Base32.h" #include "../HexCoding.h" -#include +#include"memzero.h" #include #include diff --git a/src/Tezos/Address.cpp b/src/Tezos/Address.cpp index f079b45ce96..0a630451320 100644 --- a/src/Tezos/Address.cpp +++ b/src/Tezos/Address.cpp @@ -11,8 +11,6 @@ #include "../Hash.h" #include "../HexCoding.h" -#include - namespace TW::Tezos { /// Address prefixes. diff --git a/src/Tezos/BinaryCoding.cpp b/src/Tezos/BinaryCoding.cpp index 6be3edd6442..8b13242a76d 100644 --- a/src/Tezos/BinaryCoding.cpp +++ b/src/Tezos/BinaryCoding.cpp @@ -8,7 +8,6 @@ #include "../PublicKey.h" #include "../PrivateKey.h" -#include #include namespace TW::Tezos { diff --git a/src/Utils.h b/src/Utils.h new file mode 100644 index 00000000000..2d74f9e67a2 --- /dev/null +++ b/src/Utils.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include +#include +#include "Data.h" + +inline std::shared_ptr wrapTWData(TWData* data) { + return std::shared_ptr(data, TWDataDelete); +} + +inline TW::Data dataFromTWData(const std::shared_ptr& data) { + return TW::Data(TWDataBytes(data.get()), TWDataBytes(data.get()) + TWDataSize(data.get())); +} diff --git a/trezor-crypto/crypto/cash_addr.c b/src/cash_addr.cpp similarity index 94% rename from trezor-crypto/crypto/cash_addr.c rename to src/cash_addr.cpp index 271f556678d..502fb967e9b 100644 --- a/trezor-crypto/crypto/cash_addr.c +++ b/src/cash_addr.cpp @@ -19,11 +19,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#include -#include -#include - -#include +#include +#include +#include +#include +#include "cash_addr.h" #define MAX_CASHADDR_SIZE 129 #define MAX_BASE32_SIZE 104 @@ -32,7 +32,7 @@ #define CHECKSUM_SIZE 8 uint64_t cashaddr_polymod_step(uint64_t pre) { - uint8_t b = pre >> 35; + uint8_t b = (uint8_t)(pre >> 35); return ((pre & 0x7FFFFFFFFULL) << 5) ^ (-((b >> 0) & 1) & 0x98f2bc8e61ULL) ^ (-((b >> 1) & 1) & 0x79b76d99e2ULL) ^ (-((b >> 2) & 1) & 0xf33e5fb3c4ULL) ^ @@ -60,7 +60,7 @@ int cash_encode(char* output, const char* hrp, const uint8_t* data, if (ch < 33 || ch > 126) { return 0; } - *(output++) = ch; + *(output++) = (char)ch; chk = cashaddr_polymod_step(chk) ^ (ch & 0x1f); ++i; } @@ -88,7 +88,7 @@ int cash_encode(char* output, const char* hrp, const uint8_t* data, int cash_decode(char* hrp, uint8_t* data, size_t* data_len, const char* input) { uint64_t chk = 1; size_t i = 0; - size_t input_len = strlen(input); + size_t input_len = std::string(input).size(); size_t hrp_len = 0; int have_lower = 0, have_upper = 0; if (input_len < CHECKSUM_SIZE || input_len > MAX_CASHADDR_SIZE) { @@ -117,7 +117,7 @@ int cash_decode(char* hrp, uint8_t* data, size_t* data_len, const char* input) { have_upper = 1; ch = (ch - 'A') + 'a'; } - hrp[i] = ch; + hrp[i] = (char)ch; chk = cashaddr_polymod_step(chk) ^ (ch & 0x1f); } hrp[i] = 0; @@ -132,7 +132,7 @@ int cash_decode(char* hrp, uint8_t* data, size_t* data_len, const char* input) { } chk = cashaddr_polymod_step(chk) ^ v; if (i + CHECKSUM_SIZE < input_len) { - data[i - (1 + hrp_len)] = v; + data[i - (1 + hrp_len)] = (uint8_t)v; } ++i; } @@ -152,12 +152,12 @@ static int convert_bits(uint8_t* out, size_t* outlen, int outbits, bits += inbits; while (bits >= outbits) { bits -= outbits; - out[(*outlen)++] = (val >> bits) & maxv; + out[(*outlen)++] = (uint8_t)((val >> bits) & maxv); } } if (pad) { if (bits) { - out[(*outlen)++] = (val << (outbits - bits)) & maxv; + out[(*outlen)++] = (uint8_t)((val << (outbits - bits)) & maxv); } } else if (((val << (outbits - bits)) & maxv) || bits >= inbits) { return 0; diff --git a/trezor-crypto/include/TrezorCrypto/cash_addr.h b/src/cash_addr.h similarity index 100% rename from trezor-crypto/include/TrezorCrypto/cash_addr.h rename to src/cash_addr.h diff --git a/src/interface/TWBitcoinAddress.cpp b/src/interface/TWBitcoinAddress.cpp index e81dd75a32d..48831a5983f 100644 --- a/src/interface/TWBitcoinAddress.cpp +++ b/src/interface/TWBitcoinAddress.cpp @@ -5,7 +5,6 @@ #include "../Base58.h" #include "../Bitcoin/Address.h" -#include #include #include diff --git a/src/interface/TWHash.cpp b/src/interface/TWHash.cpp index a6a5528627d..b2e33318f1d 100644 --- a/src/interface/TWHash.cpp +++ b/src/interface/TWHash.cpp @@ -7,10 +7,6 @@ #include "Data.h" #include "BinaryCoding.h" -#include -#include -#include -#include #include @@ -82,13 +78,10 @@ TWData* _Nonnull TWHashSHA256SHA256(TWData* _Nonnull data) { } TWData *_Nonnull TWHashBlake2bPersonal(TWData *_Nonnull data, TWData * _Nonnull personal, size_t outlen) { - auto resultBytes = TW::Data(outlen); - auto dataBytes = TWDataBytes(data); auto personalBytes = TWDataBytes(personal); auto personalSize = TWDataSize(personal); - tc_blake2b_Personal(dataBytes, static_cast(TWDataSize(data)), personalBytes, personalSize, resultBytes.data(), outlen); - auto result = TWDataCreateWithBytes(resultBytes.data(), outlen); - return result; + const auto result = Hash::blake2b(reinterpret_cast(TWDataBytes(data)), TWDataSize(data), outlen, Data(personalBytes, personalBytes + personalSize)); + return TWDataCreateWithBytes(result.data(), result.size()); } TWData* _Nonnull TWHashSHA256RIPEMD(TWData* _Nonnull data) { diff --git a/src/interface/TWPBKDF2.cpp b/src/interface/TWPBKDF2.cpp deleted file mode 100644 index 3fae3225482..00000000000 --- a/src/interface/TWPBKDF2.cpp +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#include -#include -#include - -#include "Data.h" - -using namespace TW; - -TWData* _Nullable TWPBKDF2HmacSha256(TWData* _Nonnull password, TWData* _Nonnull salt, - uint32_t iterations, uint32_t dkLen) { - - Data key(dkLen); - int passLen = static_cast(TWDataSize(password)); - int saltLen = static_cast(TWDataSize(salt)); - pbkdf2_hmac_sha256( - TWDataBytes(password), - passLen, - TWDataBytes(salt), - saltLen, - iterations, - key.data(), - dkLen - ); - return TWDataCreateWithData(&key); -} - -TWData* _Nullable TWPBKDF2HmacSha512(TWData* _Nonnull password, TWData* _Nonnull salt, - uint32_t iterations, uint32_t dkLen) { - Data key(dkLen); - int passLen = static_cast(TWDataSize(password)); - int saltLen = static_cast(TWDataSize(salt)); - pbkdf2_hmac_sha512( - TWDataBytes(password), - passLen, - TWDataBytes(salt), - saltLen, - iterations, - key.data(), - dkLen - ); - return TWDataCreateWithData(&key); -} diff --git a/src/interface/TWPrivateKey.cpp b/src/interface/TWPrivateKey.cpp index 9ca55d6b419..26eb71a92f3 100644 --- a/src/interface/TWPrivateKey.cpp +++ b/src/interface/TWPrivateKey.cpp @@ -2,12 +2,10 @@ // // Copyright © 2017 Trust Wallet. +#include "rand.h" #include "../PrivateKey.h" #include "../PublicKey.h" -#include -#include -#include #include #include diff --git a/src/interface/TWPublicKey.cpp b/src/interface/TWPublicKey.cpp index 913504b1f0b..e220dec45c6 100644 --- a/src/interface/TWPublicKey.cpp +++ b/src/interface/TWPublicKey.cpp @@ -7,9 +7,6 @@ #include "../HexCoding.h" #include "../PublicKey.h" -#include -#include - using TW::PublicKey; struct TWPublicKey *_Nullable TWPublicKeyCreateWithData(TWData *_Nonnull data, enum TWPublicKeyType type) { diff --git a/src/interface/TWSegwitAddress.cpp b/src/interface/TWSegwitAddress.cpp index 5d74bdb3716..e473a2b2123 100644 --- a/src/interface/TWSegwitAddress.cpp +++ b/src/interface/TWSegwitAddress.cpp @@ -4,7 +4,6 @@ #include "../Bitcoin/SegwitAddress.h" -#include #include #include #include diff --git a/src/interface/TWString.cpp b/src/interface/TWString.cpp index 3641660b8d8..a3a2389c128 100644 --- a/src/interface/TWString.cpp +++ b/src/interface/TWString.cpp @@ -4,7 +4,7 @@ #include -#include +#include "memzero.h" #include TWString *_Nonnull TWStringCreateWithUTF8Bytes(const char *_Nonnull bytes) { diff --git a/src/memory/memzero_wrapper.h b/src/memory/memzero_wrapper.h index 71af4291e78..c1062501032 100644 --- a/src/memory/memzero_wrapper.h +++ b/src/memory/memzero_wrapper.h @@ -8,7 +8,7 @@ #include -#include +#include "memzero.h" namespace TW { diff --git a/trezor-crypto/crypto/memzero.c b/src/memzero.c similarity index 100% rename from trezor-crypto/crypto/memzero.c rename to src/memzero.c diff --git a/trezor-crypto/include/TrezorCrypto/memzero.h b/src/memzero.h similarity index 100% rename from trezor-crypto/include/TrezorCrypto/memzero.h rename to src/memzero.h diff --git a/trezor-crypto/crypto/rand.c b/src/rand.cpp similarity index 85% rename from trezor-crypto/crypto/rand.c rename to src/rand.cpp index 9f22a0a5812..48b63d3ad23 100644 --- a/trezor-crypto/crypto/rand.c +++ b/src/rand.cpp @@ -21,23 +21,24 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include +#include "rand.h" #include #include #include #include +#include // [wallet-core] uint32_t __attribute__((weak)) random32(void) { int randomData = open("/dev/urandom", O_RDONLY); if (randomData < 0) { - return 0; + throw std::runtime_error("Failed to open /dev/urandom"); } uint32_t result; if (read(randomData, &result, sizeof(result)) < 0) { - return 0; + throw std::runtime_error("Failed to read from /dev/urandom"); } close(randomData); @@ -48,10 +49,10 @@ uint32_t __attribute__((weak)) random32(void) { void __attribute__((weak)) random_buffer(uint8_t *buf, size_t len) { int randomData = open("/dev/urandom", O_RDONLY); if (randomData < 0) { - return; + throw std::runtime_error("Failed to open /dev/urandom"); } if (read(randomData, buf, len) < 0) { - return; + throw std::runtime_error("Failed to read from /dev/urandom"); } close(randomData); } diff --git a/trezor-crypto/include/TrezorCrypto/rand.h b/src/rand.h similarity index 100% rename from trezor-crypto/include/TrezorCrypto/rand.h rename to src/rand.h diff --git a/swift/common-xcframework.yml b/swift/common-xcframework.yml index 962bbaf4239..e7d216caa18 100644 --- a/swift/common-xcframework.yml +++ b/swift/common-xcframework.yml @@ -8,8 +8,8 @@ options: macOS: 10.14 settings: base: - HEADER_SEARCH_PATHS: $(SRCROOT)/wallet-core ${SRCROOT}/trezor-crypto/crypto - SYSTEM_HEADER_SEARCH_PATHS: ${SRCROOT}/include ${SRCROOT}/../build/local/include ${SRCROOT}/trezor-crypto/include $(SRCROOT)/protobuf /usr/local/include /opt/homebrew/include + HEADER_SEARCH_PATHS: $(SRCROOT)/wallet-core + SYSTEM_HEADER_SEARCH_PATHS: ${SRCROOT}/include ${SRCROOT}/../build/local/include $(SRCROOT)/protobuf /usr/local/include /opt/homebrew/include CLANG_CXX_LANGUAGE_STANDARD: c++20 GCC_WARN_64_TO_32_BIT_CONVERSION: NO @@ -37,8 +37,6 @@ targets: - "Cosmos/Protobuf/*.proto" - "Hedera/Protobuf/*.proto" dependencies: - - target: trezor-crypto_${platform} - link: true - target: protobuf_${platform} link: true - sdk: libc++.tbd @@ -51,74 +49,6 @@ targets: CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER: $(inherited) false OTHER_CFLAGS: $(inherited) -Wno-comma - trezor-crypto: - type: library.static - platform: [iOS, macOS] - deploymentTarget: - iOS: 12.0 - macOS: 10.14 - sources: - - trezor-crypto/crypto/bignum.c - - trezor-crypto/crypto/ecdsa.c - - trezor-crypto/crypto/curves.c - - trezor-crypto/crypto/secp256k1.c - - trezor-crypto/crypto/rand.c - - trezor-crypto/crypto/hmac.c - - trezor-crypto/crypto/bip32.c - - trezor-crypto/crypto/bip39.c - - trezor-crypto/crypto/pbkdf2.c - - trezor-crypto/crypto/base58.c - - trezor-crypto/crypto/base32.c - - trezor-crypto/crypto/address.c - - trezor-crypto/crypto/script.c - - trezor-crypto/crypto/ripemd160.c - - trezor-crypto/crypto/sha2.c - - trezor-crypto/crypto/sha3.c - - trezor-crypto/crypto/hasher.c - - trezor-crypto/crypto/aes/aescrypt.c - - trezor-crypto/crypto/aes/aeskey.c - - trezor-crypto/crypto/aes/aestab.c - - trezor-crypto/crypto/aes/aes_modes.c - - trezor-crypto/crypto/ed25519-donna/curve25519-donna-32bit.c - - trezor-crypto/crypto/ed25519-donna/curve25519-donna-helpers.c - - trezor-crypto/crypto/ed25519-donna/modm-donna-32bit.c - - trezor-crypto/crypto/ed25519-donna/ed25519-donna-basepoint-table.c - - trezor-crypto/crypto/ed25519-donna/ed25519-donna-32bit-tables.c - - trezor-crypto/crypto/ed25519-donna/ed25519-donna-impl-base.c - - trezor-crypto/crypto/ed25519-donna/ed25519.c - - trezor-crypto/crypto/ed25519-donna/curve25519-donna-scalarmult-base.c - - trezor-crypto/crypto/ed25519-donna/ed25519-blake2b.c - - trezor-crypto/crypto/ed25519-donna/ed25519-sha3.c - - trezor-crypto/crypto/ed25519-donna/ed25519-keccak.c - - trezor-crypto/crypto/monero/base58.c - - trezor-crypto/crypto/monero/serialize.c - - trezor-crypto/crypto/monero/range_proof.c - - trezor-crypto/crypto/blake256.c - - trezor-crypto/crypto/blake2b.c - - trezor-crypto/crypto/blake2s.c - - trezor-crypto/crypto/chacha_drbg.c - - trezor-crypto/crypto/chacha20poly1305/chacha20poly1305.c - - trezor-crypto/crypto/chacha20poly1305/chacha_merged.c - - trezor-crypto/crypto/chacha20poly1305/poly1305-donna.c - - trezor-crypto/crypto/chacha20poly1305/rfc7539.c - - trezor-crypto/crypto/rc4.c - - trezor-crypto/crypto/nano.c - - trezor-crypto/crypto/nem.c - - trezor-crypto/crypto/cash_addr.c - - trezor-crypto/crypto/memzero.c - - trezor-crypto/crypto/scrypt.c - - trezor-crypto/crypto/nist256p1.c - - trezor-crypto/crypto/groestl.c - - trezor-crypto/crypto/hmac_drbg.c - - trezor-crypto/crypto/rfc6979.c - - trezor-crypto/crypto/zilliqa.c - - trezor-crypto/crypto/cardano.c - - trezor-crypto/crypto/shamir.c - - trezor-crypto/crypto/sodium/private/fe_25_5/fe.c - - trezor-crypto/crypto/sodium/private/ed25519_ref10.c - - trezor-crypto/crypto/sodium/private/ed25519_ref10_fe_25_5.c - - trezor-crypto/crypto/sodium/keypair.c - protobuf: type: library.static platform: [iOS, macOS] diff --git a/swift/project.yml b/swift/project.yml index 9d68acab6cd..33e88af61b4 100644 --- a/swift/project.yml +++ b/swift/project.yml @@ -4,8 +4,8 @@ options: createIntermediateGroups: true settings: base: - HEADER_SEARCH_PATHS: $(SRCROOT)/wallet-core ${SRCROOT}/trezor-crypto/crypto - SYSTEM_HEADER_SEARCH_PATHS: ${SRCROOT}/include ${SRCROOT}/../build/local/include ${SRCROOT}/trezor-crypto/include $(SRCROOT)/protobuf /usr/local/include /opt/homebrew/include + HEADER_SEARCH_PATHS: $(SRCROOT)/wallet-core + SYSTEM_HEADER_SEARCH_PATHS: ${SRCROOT}/include ${SRCROOT}/../build/local/include $(SRCROOT)/protobuf /usr/local/include /opt/homebrew/include CLANG_CXX_LANGUAGE_STANDARD: c++20 SWIFT_VERSION: 5.1 IPHONEOS_DEPLOYMENT_TARGET: 13.0 @@ -33,8 +33,6 @@ targets: - "Hedera/Protobuf/*.proto" - Sources dependencies: - - target: trezor-crypto - link: true - target: protobuf link: true - sdk: libc++.tbd @@ -72,74 +70,6 @@ targets: - target: WalletCore - sdk: libc++.tbd - trezor-crypto: - type: library.static - platform: iOS - sources: - - trezor-crypto/crypto/bignum.c - - trezor-crypto/crypto/ecdsa.c - - trezor-crypto/crypto/curves.c - - trezor-crypto/crypto/secp256k1.c - - trezor-crypto/crypto/rand.c - - trezor-crypto/crypto/hmac.c - - trezor-crypto/crypto/bip32.c - - trezor-crypto/crypto/bip39.c - - trezor-crypto/crypto/pbkdf2.c - - trezor-crypto/crypto/base58.c - - trezor-crypto/crypto/base32.c - - trezor-crypto/crypto/address.c - - trezor-crypto/crypto/script.c - - trezor-crypto/crypto/ripemd160.c - - trezor-crypto/crypto/sha2.c - - trezor-crypto/crypto/sha3.c - - trezor-crypto/crypto/hasher.c - - trezor-crypto/crypto/aes/aescrypt.c - - trezor-crypto/crypto/aes/aeskey.c - - trezor-crypto/crypto/aes/aestab.c - - trezor-crypto/crypto/aes/aes_modes.c - - trezor-crypto/crypto/ed25519-donna/curve25519-donna-32bit.c - - trezor-crypto/crypto/ed25519-donna/curve25519-donna-helpers.c - - trezor-crypto/crypto/ed25519-donna/modm-donna-32bit.c - - trezor-crypto/crypto/ed25519-donna/ed25519-donna-basepoint-table.c - - trezor-crypto/crypto/ed25519-donna/ed25519-donna-32bit-tables.c - - trezor-crypto/crypto/ed25519-donna/ed25519-donna-impl-base.c - - trezor-crypto/crypto/ed25519-donna/ed25519.c - - trezor-crypto/crypto/ed25519-donna/curve25519-donna-scalarmult-base.c - - trezor-crypto/crypto/ed25519-donna/ed25519-blake2b.c - - trezor-crypto/crypto/ed25519-donna/ed25519-sha3.c - - trezor-crypto/crypto/ed25519-donna/ed25519-keccak.c - - trezor-crypto/crypto/monero/base58.c - - trezor-crypto/crypto/monero/serialize.c - - trezor-crypto/crypto/monero/range_proof.c - - trezor-crypto/crypto/blake256.c - - trezor-crypto/crypto/blake2b.c - - trezor-crypto/crypto/blake2s.c - - trezor-crypto/crypto/chacha_drbg.c - - trezor-crypto/crypto/chacha20poly1305/chacha20poly1305.c - - trezor-crypto/crypto/chacha20poly1305/chacha_merged.c - - trezor-crypto/crypto/chacha20poly1305/poly1305-donna.c - - trezor-crypto/crypto/chacha20poly1305/rfc7539.c - - trezor-crypto/crypto/rc4.c - - trezor-crypto/crypto/nano.c - - trezor-crypto/crypto/nem.c - - trezor-crypto/crypto/cash_addr.c - - trezor-crypto/crypto/memzero.c - - trezor-crypto/crypto/scrypt.c - - trezor-crypto/crypto/nist256p1.c - - trezor-crypto/crypto/groestl.c - - trezor-crypto/crypto/hmac_drbg.c - - trezor-crypto/crypto/rfc6979.c - - trezor-crypto/crypto/zilliqa.c - - trezor-crypto/crypto/cardano.c - - trezor-crypto/crypto/shamir.c - - trezor-crypto/crypto/sodium/private/fe_25_5/fe.c - - trezor-crypto/crypto/sodium/private/ed25519_ref10.c - - trezor-crypto/crypto/sodium/private/ed25519_ref10_fe_25_5.c - - trezor-crypto/crypto/sodium/keypair.c - settings: - GCC_WARN_64_TO_32_BIT_CONVERSION: NO - OTHER_CFLAGS: $(inherited) -Wno-comma - protobuf: type: library.static platform: iOS diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 87ec8f86384..9fa76638556 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -21,7 +21,7 @@ add_subdirectory(${CMAKE_SOURCE_DIR}/build/local/src/gtest/googletest-1.16.0 # Test executable file(GLOB_RECURSE test_sources *.cpp **/*.cpp **/*.cc) add_executable(tests ${test_sources}) -target_link_libraries(tests gtest_main TrezorCrypto TrustWalletCore walletconsolelib protobuf Boost::boost) +target_link_libraries(tests gtest_main TrustWalletCore walletconsolelib protobuf Boost::boost) target_include_directories(tests PRIVATE ${CMAKE_SOURCE_DIR}/src) target_include_directories(tests PRIVATE ${CMAKE_SOURCE_DIR}/tests/common) target_compile_options(tests PRIVATE "-Wall") diff --git a/tests/chains/NEO/SignerTests.cpp b/tests/chains/NEO/SignerTests.cpp index e53f0a4f92d..69fbb7088ff 100644 --- a/tests/chains/NEO/SignerTests.cpp +++ b/tests/chains/NEO/SignerTests.cpp @@ -4,7 +4,6 @@ #include "HexCoding.h" #include "PublicKey.h" -#include "PublicKeyLegacy.h" #include "NEO/Address.h" #include "NEO/Signer.h" diff --git a/tests/chains/NEO/TWAnySignerTests.cpp b/tests/chains/NEO/TWAnySignerTests.cpp index 461912180ac..b080f432408 100644 --- a/tests/chains/NEO/TWAnySignerTests.cpp +++ b/tests/chains/NEO/TWAnySignerTests.cpp @@ -3,7 +3,6 @@ // Copyright © 2017 Trust Wallet. #include "PrivateKey.h" -#include "PublicKeyLegacy.h" #include "TestUtilities.h" #include #include "HexCoding.h" diff --git a/tests/chains/Waves/SignerTests.cpp b/tests/chains/Waves/SignerTests.cpp index c4e6f61e263..bf67021ae62 100644 --- a/tests/chains/Waves/SignerTests.cpp +++ b/tests/chains/Waves/SignerTests.cpp @@ -6,8 +6,9 @@ #include "PublicKey.h" #include "Waves/Signer.h" #include "Waves/Transaction.h" +#include "Utils.h" -#include +#include #include using namespace TW; @@ -55,7 +56,9 @@ TEST(WavesSigner, curve25519_pk_to_ed25519) { parse_hex("559a50cb45a9a8e8d4f83295c354725990164d10bb505275d1a3086c08fb935d"); auto r = Data(); r.resize(32); - curve25519_pk_to_ed25519(r.data(), publicKeyCurve25519.data()); + auto curvePubkey = wrapTWData(TWDataCreateWithBytes(publicKeyCurve25519.data(), publicKeyCurve25519.size())); + auto ed25519Pubkey = wrapTWData(TWCurve25519PubkeyToEd25519(curvePubkey.get())); + r = dataFromTWData(ed25519Pubkey); EXPECT_EQ(hex(r), "ff84c4bfc095df25b01e48807715856d95af93d88c5b57f30cb0ce567ca4ce56"); } diff --git a/tests/common/HDWallet/HDWalletInternalTests.cpp b/tests/common/HDWallet/HDWalletInternalTests.cpp deleted file mode 100644 index 6b0554a65d3..00000000000 --- a/tests/common/HDWallet/HDWalletInternalTests.cpp +++ /dev/null @@ -1,154 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#include "HDWallet.h" -#include "Data.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include "PublicKey.h" -#include -#include -#include "TestUtilities.h" - -#include -#include -#include - -namespace TW::HDWalletInternalTests { - -const auto mnemonic1 = "ripple scissors kick mammal hire column oak again sun offer wealth tomorrow wagon turn fatal"; - -std::string nodeToHexString(const HDNode& node) { - std::string s; - s += std::to_string(node.depth); - s += "-" + std::to_string(node.child_num); - s += "--" + hex(data(node.chain_code, 32)); - s += "--" + hex(data(node.private_key, 32)); - s += "--" + hex(data(node.private_key_extension, 32)); - s += "--" + hex(data(node.public_key, 33)); - return s; -} - -Data publicKeyFromPrivateKey(const Data& privateKey) { - return PrivateKey(privateKey, TWCurveSECP256k1).getPublicKey(TWPublicKeyTypeSECP256k1).bytes; -} - -TEST(HDWalletInternal, SquareDerivationRoutes) { - /* - Test 'square' derivation routes, result should be the same. - Performing private derivation, then taking the public key yields the same as - taking the public key first, and performing public derivation. - This makes XPUB schemes possible. - - priv_node --priv_deriv.--> priv_child_node - - | | - get_pub get_pub - | | - v v - - pub_node ---pub_deriv.---> pub_key - - */ - - HDWallet wallet = HDWallet(mnemonic1, ""); - const auto derivationPath = DerivationPath("m/84'/0'/0'/1"); - const auto dpLastIndex = 2; - const auto ExpectedFinalPublicKey = "02e0b4765e9012bfbbfe8c714f99beb681acc0a92bdb5acbf2f362c0aff986ad49"; - - // getMasterNode - auto masterNode = HDNode(); - hdnode_from_seed(wallet.getSeed().data(), HDWallet<>::mSeedSize, SECP256K1_NAME, &masterNode); - - auto node0 = masterNode; - // getNode - for (auto& index : derivationPath.indices) { - hdnode_private_ckd(&node0, index.derivationIndex()); - } - EXPECT_EQ(nodeToHexString(node0), "4-1--8423e869d887b6b0e7ca5f3bbed6e69234fb5d51aa02e9e8069fbffb2dc3c095--55f85b343359ec33aa3519a40673773d1f52677a044c7185529ce8f5fdb70625--0000000000000000000000000000000000000000000000000000000000000000--000000000000000000000000000000000000000000000000000000000000000000"); - - { - // Route 1 step 1: private node derivation - auto node11 = node0; - EXPECT_EQ(hdnode_private_ckd(&node11, dpLastIndex), 1); - EXPECT_EQ(nodeToHexString(node11), "5-2--53aaec7a112524bc3c8c91b278ccb0cf7e322077dac6a832563eb33149bb0051--512503395481ea0c26fe341bc342c29f4a706be003d12179ec6b65aa8a8c352d--0000000000000000000000000000000000000000000000000000000000000000--000000000000000000000000000000000000000000000000000000000000000000"); - - // Route 1 step 2: public key from private - const auto publicKey = publicKeyFromPrivateKey(data(node11.private_key, 32)); - EXPECT_EQ(hex(publicKey), ExpectedFinalPublicKey); - } - - { - // Route 2 step 1: public node from private (extended public key) - auto node21 = node0; - const auto pub21 = publicKeyFromPrivateKey(data(node21.private_key, 32)); - ::memcpy(node21.public_key, pub21.data(), 33); - EXPECT_EQ(nodeToHexString(node21), "4-1--8423e869d887b6b0e7ca5f3bbed6e69234fb5d51aa02e9e8069fbffb2dc3c095--55f85b343359ec33aa3519a40673773d1f52677a044c7185529ce8f5fdb70625--0000000000000000000000000000000000000000000000000000000000000000--026a940b5b683237037ecb230c402c5e351f38d41f00215e4d36006e9ff6b5cfba"); - - // Route 2 step 2: public node derivation - auto node22 = node21; - EXPECT_EQ(hdnode_public_ckd(&node22, dpLastIndex), 1); - EXPECT_EQ(nodeToHexString(node22), "5-2--53aaec7a112524bc3c8c91b278ccb0cf7e322077dac6a832563eb33149bb0051--0000000000000000000000000000000000000000000000000000000000000000--0000000000000000000000000000000000000000000000000000000000000000--02e0b4765e9012bfbbfe8c714f99beb681acc0a92bdb5acbf2f362c0aff986ad49"); - const auto publicKey = PublicKey(data(node22.public_key, 33), TWPublicKeyTypeSECP256k1); - EXPECT_EQ(hex(publicKey.bytes), ExpectedFinalPublicKey); - } -} - -TEST(HDWalletInternal, PrivateAndPublicCkdDerivation) { - /* - - PrivateKey1 ----> PrivateKey2 - - | | - v v - - PublicKey1 ----> PublicKey2 - - */ - - const auto PrivateKey1 = "55f85b343359ec33aa3519a40673773d1f52677a044c7185529ce8f5fdb70625"; - const auto PrivateKey2 = "512503395481ea0c26fe341bc342c29f4a706be003d12179ec6b65aa8a8c352d"; - const auto PublicKey1 = "026a940b5b683237037ecb230c402c5e351f38d41f00215e4d36006e9ff6b5cfba"; - const auto PublicKey2 = "02e0b4765e9012bfbbfe8c714f99beb681acc0a92bdb5acbf2f362c0aff986ad49"; - const auto ChainCode0 = "8423e869d887b6b0e7ca5f3bbed6e69234fb5d51aa02e9e8069fbffb2dc3c095"; - const auto dpIndex = 2; - const auto curve = get_curve_by_name(SECP256K1_NAME); - - { // PrivateKey1 -> PublicKey1 - EXPECT_EQ(hex(publicKeyFromPrivateKey(parse_hex(PrivateKey1))), PublicKey1); - } - { // PrivateKey2 -> PublicKey2 - EXPECT_EQ(hex(publicKeyFromPrivateKey(parse_hex(PrivateKey2))), PublicKey2); - } - { // PrivateKey1 -> PrivateKey2 - auto node = HDNode(); - node.depth = 4; - node.child_num = 1; - node.curve = curve; - ::memcpy(node.chain_code, parse_hex(ChainCode0).data(), 32); - ::memcpy(node.private_key, parse_hex(PrivateKey1).data(), 32); - EXPECT_EQ(nodeToHexString(node), "4-1--8423e869d887b6b0e7ca5f3bbed6e69234fb5d51aa02e9e8069fbffb2dc3c095--55f85b343359ec33aa3519a40673773d1f52677a044c7185529ce8f5fdb70625--0000000000000000000000000000000000000000000000000000000000000000--000000000000000000000000000000000000000000000000000000000000000000"); - - EXPECT_EQ(hdnode_private_ckd(&node, dpIndex), 1); - - EXPECT_EQ(nodeToHexString(node), "5-2--53aaec7a112524bc3c8c91b278ccb0cf7e322077dac6a832563eb33149bb0051--512503395481ea0c26fe341bc342c29f4a706be003d12179ec6b65aa8a8c352d--0000000000000000000000000000000000000000000000000000000000000000--000000000000000000000000000000000000000000000000000000000000000000"); - EXPECT_EQ(hex(data(node.private_key, 32)), PrivateKey2); - } - { // PublicKey1 -> PublicKey2 - auto node = HDNode(); - node.depth = 4; - node.child_num = 1; - node.curve = curve; - ::memcpy(node.chain_code, parse_hex(ChainCode0).data(), 32); - ::memcpy(node.public_key, parse_hex(PublicKey1).data(), 33); - EXPECT_EQ(nodeToHexString(node), "4-1--8423e869d887b6b0e7ca5f3bbed6e69234fb5d51aa02e9e8069fbffb2dc3c095--0000000000000000000000000000000000000000000000000000000000000000--0000000000000000000000000000000000000000000000000000000000000000--026a940b5b683237037ecb230c402c5e351f38d41f00215e4d36006e9ff6b5cfba"); - - EXPECT_EQ(hdnode_public_ckd(&node, dpIndex), 1); - - EXPECT_EQ(nodeToHexString(node), "5-2--53aaec7a112524bc3c8c91b278ccb0cf7e322077dac6a832563eb33149bb0051--0000000000000000000000000000000000000000000000000000000000000000--0000000000000000000000000000000000000000000000000000000000000000--02e0b4765e9012bfbbfe8c714f99beb681acc0a92bdb5acbf2f362c0aff986ad49"); - EXPECT_EQ(hex(data(node.public_key, 33)), PublicKey2); - } -} - -} // namespace diff --git a/tests/common/PrivateKeyTests.cpp b/tests/common/PrivateKeyTests.cpp index db420b8ee8a..145d8dc6f65 100644 --- a/tests/common/PrivateKeyTests.cpp +++ b/tests/common/PrivateKeyTests.cpp @@ -5,9 +5,8 @@ #include "Hash.h" #include "HexCoding.h" #include "PrivateKey.h" -#include +#include "rand.h" #include "PublicKey.h" -#include "PublicKeyLegacy.h" #include @@ -271,25 +270,6 @@ TEST(PrivateKey, SignNIST256p1) { hex(actual)); } -TEST(PrivateKey, SignNIST256p1VerifyLegacy) { - for (auto i = 0; i < 1000; ++i) { - Data secret(32); - random_buffer(secret.data(), 32); - - Data msg(32); - random_buffer(msg.data(), 32); - - PrivateKey privateKey(secret, TWCurveNIST256p1); - auto signature = privateKey.sign(msg); - - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeNIST256p1); - EXPECT_TRUE(TrezorCrypto::verifyNist256p1Signature(publicKey.bytes, signature, msg)) - << "Error verifying nist256p1 signature" << std::endl - << "Private key: " << hex(secret) << std::endl - << "Message: " << hex(msg); - } -} - int isCanonical([[maybe_unused]] uint8_t by, [[maybe_unused]] const uint8_t sig[64]) { return 1; } diff --git a/tests/common/PublicKeyLegacy.h b/tests/common/PublicKeyLegacy.h deleted file mode 100644 index 33007667c76..00000000000 --- a/tests/common/PublicKeyLegacy.h +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -#pragma once - -#include -#include -#include "Data.h" - -namespace TW::TrezorCrypto { - -/// Verifies a signature for the provided message by using `trezor-crypto` library (legacy implementation). -inline bool verifyNist256p1Signature(const Data& publicKey, const Data& signature, const Data& message) { - return ecdsa_verify_digest(&nist256p1, publicKey.data(), signature.data(), message.data()) == 0; -} - -} // namespace TW::TrezorCrypto diff --git a/tests/interface/TWPBKDF2Tests.cpp b/tests/interface/TWPBKDF2Tests.cpp index dbd22ca6fa4..70d53daabf3 100644 --- a/tests/interface/TWPBKDF2Tests.cpp +++ b/tests/interface/TWPBKDF2Tests.cpp @@ -4,12 +4,12 @@ #include "TestUtilities.h" -#include +#include #include TEST(TWPBKDF2, Sha256_sha512) { - auto password = DATA("50617373776f7264"); // Password + auto password = DATA("50617373776f7264"); // Password auto salt = DATA("4e61436c"); // NaCl auto sha256Result = WRAPD(TWPBKDF2HmacSha256(password.get(), salt.get(), 80000, 128)); diff --git a/tools/build-and-test b/tools/build-and-test index bd4ba92c6cf..e97fab530ef 100755 --- a/tools/build-and-test +++ b/tools/build-and-test @@ -11,17 +11,14 @@ tools/generate-files native echo "#### Building... ####" cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -make -Cbuild -j12 tests TrezorCryptoTests +make -Cbuild -j12 tests if [ -x "$(command -v clang-tidy)" ]; then echo "#### Linting... ####" tools/lint fi -echo "#### Running trezor-crypto tests... ####" export CK_TIMEOUT_MULTIPLIER=4 -build/trezor-crypto/crypto/tests/TrezorCryptoTests - echo "#### Running unit tests... ####" FILTER="*" if [ -n "$1" ]; then diff --git a/tools/coverage b/tools/coverage index 88b246fecd7..00dd403e4fb 100755 --- a/tools/coverage +++ b/tools/coverage @@ -8,11 +8,10 @@ # - run cmake, to enable coverage measurement # cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Debug # - build tester -# make -Cbuild -j12 tests TrezorCryptoTests +# make -Cbuild -j12 tests # - Remove any old coverage files # find . -name "*.gcda" -exec rm \{\} \; # - run unit tests -# ./build/trezor-crypto/crypto/tests/TrezorCryptoTests # ./build/tests/tests tests --gtest_output=xml # - generate coverage info (slow). Optionally generate report in HTML format: # ./tools/coverage html diff --git a/tools/tests b/tools/tests index 58a23f853d2..8ee3832b6b0 100755 --- a/tools/tests +++ b/tools/tests @@ -5,10 +5,9 @@ set -e cmake -H. -Bbuild -make -Cbuild -j12 tests TrezorCryptoTests +make -Cbuild -j12 tests export CK_TIMEOUT_MULTIPLIER=4 -build/trezor-crypto/crypto/tests/TrezorCryptoTests build/tests/tests tests diff --git a/trezor-crypto/.gitignore b/trezor-crypto/.gitignore deleted file mode 100644 index c98ec5d108b..00000000000 --- a/trezor-crypto/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -.cache/ -.vscode/ -_attic/ -*.o -*.d -*.exe -*~ -tests/aestst -tests/test_openssl -tests/test_speed -tests/test_check -tests/libtrezor-crypto.so -*.os -*.pyc diff --git a/trezor-crypto/CMakeLists.txt b/trezor-crypto/CMakeLists.txt deleted file mode 100644 index a3ef46a38f2..00000000000 --- a/trezor-crypto/CMakeLists.txt +++ /dev/null @@ -1,89 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# Copyright © 2017 Trust Wallet. - -set(TW_WARNING_FLAGS - -W - -Wall - -Wextra - -Wimplicit-function-declaration - -Wredundant-decls - -Wstrict-prototypes - -Wundef - -Wshadow - -Wpointer-arith - -Wformat - -Wreturn-type - -Wsign-compare - -Wmultichar - -Wformat-nonliteral - -Winit-self - -Wuninitialized - -Wformat-security - -Wno-missing-braces -) - -set(CMAKE_C_STANDARD 11) - -add_library(TrezorCrypto - crypto/bignum.c crypto/ecdsa.c crypto/curves.c crypto/secp256k1.c crypto/rand.c crypto/hmac.c crypto/bip32.c crypto/bip39.c crypto/slip39.c crypto/pbkdf2.c crypto/base58.c crypto/base32.c - crypto/address.c - crypto/script.c - crypto/ripemd160.c - crypto/sha2.c - crypto/sha3.c - crypto/hasher.c - crypto/aes/aescrypt.c crypto/aes/aeskey.c crypto/aes/aestab.c crypto/aes/aes_modes.c - crypto/ed25519-donna/curve25519-donna-32bit.c crypto/ed25519-donna/curve25519-donna-helpers.c crypto/ed25519-donna/modm-donna-32bit.c - crypto/ed25519-donna/ed25519-donna-basepoint-table.c crypto/ed25519-donna/ed25519-donna-32bit-tables.c crypto/ed25519-donna/ed25519-donna-impl-base.c - crypto/ed25519-donna/ed25519.c crypto/ed25519-donna/curve25519-donna-scalarmult-base.c crypto/ed25519-donna/ed25519-sha3.c crypto/ed25519-donna/ed25519-keccak.c crypto/ed25519-donna/ed25519-blake2b.c - crypto/sodium/private/fe_25_5/fe.c crypto/sodium/private/ed25519_ref10.c crypto/sodium/private/ed25519_ref10_fe_25_5.c crypto/sodium/keypair.c - crypto/monero/base58.c - crypto/monero/serialize.c - #crypto/monero/xmr.c - crypto/monero/range_proof.c - crypto/blake256.c - crypto/blake2b.c crypto/blake2s.c - crypto/chacha_drbg.c - crypto/chacha20poly1305/chacha20poly1305.c crypto/chacha20poly1305/chacha_merged.c crypto/chacha20poly1305/poly1305-donna.c crypto/chacha20poly1305/rfc7539.c - crypto/rc4.c - crypto/nano.c - crypto/nem.c - crypto/cash_addr.c - crypto/memzero.c - crypto/scrypt.c - crypto/nist256p1.c - crypto/groestl.c - crypto/hmac_drbg.c - crypto/rfc6979.c - crypto/shamir.c - crypto/zilliqa.c - crypto/cardano.c -) - -if (EMSCRIPTEN) - message(STATUS "Skip building trezor-crypto/tests") - set(TW_WARNING_FLAGS ${TW_WARNING_FLAGS} -Wno-bitwise-instead-of-logical) -else () - if(NOT ANDROID AND NOT IOS_PLATFORM AND NOT TW_COMPILE_JAVA) - add_subdirectory(crypto/tests) - endif() -endif() - -target_compile_options(TrezorCrypto PRIVATE ${TW_WARNING_FLAGS} -Werror PUBLIC -Wno-deprecated-volatile) - -target_include_directories(TrezorCrypto - PUBLIC - $ - $ - PRIVATE - src -) - -install( - TARGETS TrezorCrypto - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -) - -install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/trezor-crypto/crypto/AUTHORS b/trezor-crypto/crypto/AUTHORS deleted file mode 100644 index 51c9bdab086..00000000000 --- a/trezor-crypto/crypto/AUTHORS +++ /dev/null @@ -1,2 +0,0 @@ -Tomas Dzetkulic -Pavol Rusnak diff --git a/trezor-crypto/crypto/CONTRIBUTORS b/trezor-crypto/crypto/CONTRIBUTORS deleted file mode 100644 index 300b9788d21..00000000000 --- a/trezor-crypto/crypto/CONTRIBUTORS +++ /dev/null @@ -1,16 +0,0 @@ -Tomas Dzetkulic -Pavol Rusnak -Jochen Hoenicke -Dustin Laurence -Ondrej Mikle -Roman Zeyde -Alex Beregszaszi -netanelkl -Jan Pochyla -Ondrej Mikle -Josh Billings -Adam Mackler -Oleg Andreev -mog -John Dvorak -Christian Reitter diff --git a/trezor-crypto/crypto/LICENSE b/trezor-crypto/crypto/LICENSE deleted file mode 100644 index 1ea1df7037e..00000000000 --- a/trezor-crypto/crypto/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2013 Tomas Dzetkulic -Copyright (c) 2013 Pavol Rusnak - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/trezor-crypto/crypto/README.md b/trezor-crypto/crypto/README.md deleted file mode 100644 index 856dbb44ac5..00000000000 --- a/trezor-crypto/crypto/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# trezor-crypto - -[![Build Status](https://travis-ci.org/trezor/trezor-crypto.svg?branch=master)](https://travis-ci.org/trezor/trezor-crypto) [![gitter](https://badges.gitter.im/trezor/community.svg)](https://gitter.im/trezor/community) - -Heavily optimized cryptography algorithms for embedded devices. - -These include: -- AES/Rijndael encryption/decryption -- Big Number (256 bit) Arithmetics -- BIP32 Hierarchical Deterministic Wallets -- BIP39 Mnemonic code -- ECDSA signing/verifying (supports secp256k1 and nist256p1 curves, - uses RFC6979 for deterministic signatures) -- ECDSA public key derivation -- Base32 (RFC4648 and custom alphabets) -- Base58 address representation -- Ed25519 signing/verifying (also SHA3 and Keccak variants) -- ECDH using secp256k1, nist256p1 and Curve25519 -- HMAC-SHA256 and HMAC-SHA512 -- PBKDF2 -- RIPEMD-160 -- SHA1 -- SHA2-256/SHA2-512 -- SHA3/Keccak -- BLAKE2s/BLAKE2b -- Chacha20-Poly1305 -- unit tests (using Check - check.sf.net; in test_check.c) -- tests against OpenSSL (in test_openssl.c) -- integrated Wycheproof tests -- public key conversion between Curve25519 to Ed25519 and vice versa - -Distributed under MIT License. - -## Some parts of the library come from external sources: - -- AES: https://github.com/BrianGladman/aes -- Base58: https://github.com/luke-jr/libbase58 -- BLAKE2s/BLAKE2b: https://github.com/BLAKE2/BLAKE2 -- RIPEMD-160: https://github.com/ARMmbed/mbedtls -- SHA1/SHA2: http://www.aarongifford.com/computers/sha.html -- SHA3: https://github.com/rhash/RHash -- Curve25519: https://github.com/agl/curve25519-donna -- Ed25519: https://github.com/floodyberry/ed25519-donna -- Chacha20: https://github.com/wg/c20p1305 -- Poly1305: https://github.com/floodyberry/poly1305-donna diff --git a/trezor-crypto/crypto/address.c b/trezor-crypto/crypto/address.c deleted file mode 100644 index 77f307b0ae8..00000000000 --- a/trezor-crypto/crypto/address.c +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright (c) 2016 Daira Hopwood - * Copyright (c) 2016 Pavol Rusnak - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include -#include - -size_t address_prefix_bytes_len(uint32_t address_type) { - if (address_type <= 0xFF) return 1; - if (address_type <= 0xFFFF) return 2; - if (address_type <= 0xFFFFFF) return 3; - return 4; -} - -void address_write_prefix_bytes(uint32_t address_type, uint8_t *out) { - if (address_type > 0xFFFFFF) *(out++) = address_type >> 24; - if (address_type > 0xFFFF) *(out++) = (address_type >> 16) & 0xFF; - if (address_type > 0xFF) *(out++) = (address_type >> 8) & 0xFF; - *(out++) = address_type & 0xFF; -} - -bool address_check_prefix(const uint8_t *addr, uint32_t address_type) { - if (address_type <= 0xFF) { - return address_type == (uint32_t)(addr[0]); - } - if (address_type <= 0xFFFF) { - return address_type == (((uint32_t)addr[0] << 8) | ((uint32_t)addr[1])); - } - if (address_type <= 0xFFFFFF) { - return address_type == (((uint32_t)addr[0] << 16) | - ((uint32_t)addr[1] << 8) | ((uint32_t)addr[2])); - } - return address_type == - (((uint32_t)addr[0] << 24) | ((uint32_t)addr[1] << 16) | - ((uint32_t)addr[2] << 8) | ((uint32_t)addr[3])); -} - -#if USE_ETHEREUM -#include - -void ethereum_address_checksum(const uint8_t *addr, char *address, bool rskip60, - uint32_t chain_id) { - const char *hex = "0123456789abcdef"; - for (int i = 0; i < 20; i++) { - address[i * 2] = hex[(addr[i] >> 4) & 0xF]; - address[i * 2 + 1] = hex[addr[i] & 0xF]; - } - address[40] = 0; - - SHA3_CTX ctx = {0}; - uint8_t hash[32] = {0}; - keccak_256_Init(&ctx); - if (rskip60) { - char prefix[16] = {0}; - int prefix_size = bn_format_uint64(chain_id, NULL, "0x", 0, 0, false, - prefix, sizeof(prefix)); - keccak_Update(&ctx, (const uint8_t *)prefix, prefix_size); - } - keccak_Update(&ctx, (const uint8_t *)address, 40); - keccak_Final(&ctx, hash); - - for (int i = 0; i < 20; i++) { - if (hash[i] & 0x80 && address[i * 2] >= 'a' && address[i * 2] <= 'f') { - address[i * 2] -= 0x20; - } - if (hash[i] & 0x08 && address[i * 2 + 1] >= 'a' && - address[i * 2 + 1] <= 'f') { - address[i * 2 + 1] -= 0x20; - } - } -} -#endif diff --git a/trezor-crypto/crypto/aes/aes_modes.c b/trezor-crypto/crypto/aes/aes_modes.c deleted file mode 100644 index 15877912619..00000000000 --- a/trezor-crypto/crypto/aes/aes_modes.c +++ /dev/null @@ -1,957 +0,0 @@ -/* ---------------------------------------------------------------------------- -Copyright (c) 1998-2013, Brian Gladman, Worcester, UK. All rights reserved. - -The redistribution and use of this software (with or without changes) -is allowed without the payment of fees or royalties provided that: - - source code distributions include the above copyright notice, this - list of conditions and the following disclaimer; - - binary distributions include the above copyright notice, this list - of conditions and the following disclaimer in their documentation. - -This software is provided 'as is' with no explicit or implied warranties -in respect of its operation, including, but not limited to, correctness -and fitness for purpose. ---------------------------------------------------------------------------- -Issue Date: 20/12/2007 - - These subroutines implement multiple block AES modes for ECB, CBC, CFB, - OFB and CTR encryption, The code provides support for the VIA Advanced - Cryptography Engine (ACE). - - NOTE: In the following subroutines, the AES contexts (ctx) must be - 16 byte aligned if VIA ACE is being used -*/ - -#include -#include -#include - -#include - -#if defined( AES_MODES ) -#if defined(__cplusplus) -extern "C" -{ -#endif - -#if defined( _MSC_VER ) && ( _MSC_VER > 800 ) -#pragma intrinsic(memcpy) -#endif - -#define BFR_BLOCKS 8 - -/* These values are used to detect long word alignment in order to */ -/* speed up some buffer operations. This facility may not work on */ -/* some machines so this define can be commented out if necessary */ - -#define FAST_BUFFER_OPERATIONS - -#define lp32(x) ((uint32_t*)(x)) - -#if defined( USE_VIA_ACE_IF_PRESENT ) - -#include "aes_via_ace.h" - -#pragma pack(16) - -aligned_array(unsigned long, enc_gen_table, 12, 16) = NEH_ENC_GEN_DATA; -aligned_array(unsigned long, enc_load_table, 12, 16) = NEH_ENC_LOAD_DATA; -aligned_array(unsigned long, enc_hybrid_table, 12, 16) = NEH_ENC_HYBRID_DATA; -aligned_array(unsigned long, dec_gen_table, 12, 16) = NEH_DEC_GEN_DATA; -aligned_array(unsigned long, dec_load_table, 12, 16) = NEH_DEC_LOAD_DATA; -aligned_array(unsigned long, dec_hybrid_table, 12, 16) = NEH_DEC_HYBRID_DATA; - -/* NOTE: These control word macros must only be used after */ -/* a key has been set up because they depend on key size */ -/* See the VIA ACE documentation for key type information */ -/* and aes_via_ace.h for non-default NEH_KEY_TYPE values */ - -#ifndef NEH_KEY_TYPE -# define NEH_KEY_TYPE NEH_HYBRID -#endif - -#if NEH_KEY_TYPE == NEH_LOAD -#define kd_adr(c) ((uint8_t*)(c)->ks) -#elif NEH_KEY_TYPE == NEH_GENERATE -#define kd_adr(c) ((uint8_t*)(c)->ks + (c)->inf.b[0]) -#elif NEH_KEY_TYPE == NEH_HYBRID -#define kd_adr(c) ((uint8_t*)(c)->ks + ((c)->inf.b[0] == 160 ? 160 : 0)) -#else -#error no key type defined for VIA ACE -#endif - -#else - -#define aligned_array(type, name, no, stride) type name[no] -#define aligned_auto(type, name, no, stride) type name[no] - -#endif - -#if defined( _MSC_VER ) && _MSC_VER > 1200 - -#define via_cwd(cwd, ty, dir, len) \ - unsigned long* cwd = (dir##_##ty##_table + ((len - 128) >> 4)) - -#else - -#define via_cwd(cwd, ty, dir, len) \ - aligned_auto(unsigned long, cwd, 4, 16); \ - cwd[1] = cwd[2] = cwd[3] = 0; \ - cwd[0] = neh_##dir##_##ty##_key(len) - -#endif - -/* test the code for detecting and setting pointer alignment */ - -AES_RETURN aes_test_alignment_detection(unsigned int n) /* 4 <= n <= 16 */ -{ uint8_t p[16]; - uint32_t i = 0, count_eq = 0, count_neq = 0; - - if(n < 4 || n > 16) - return EXIT_FAILURE; - - for(i = 0; i < n; ++i) - { - uint8_t *qf = ALIGN_FLOOR(p + i, n), - *qh = ALIGN_CEIL(p + i, n); - - if(qh == qf) - ++count_eq; - else if(qh == qf + n) - ++count_neq; - else - return EXIT_FAILURE; - } - return (count_eq != 1 || count_neq != n - 1 ? EXIT_FAILURE : EXIT_SUCCESS); -} - -AES_RETURN aes_mode_reset(aes_encrypt_ctx ctx[1]) -{ - ctx->inf.b[2] = 0; - return EXIT_SUCCESS; -} - -AES_RETURN aes_ecb_encrypt(const unsigned char *ibuf, unsigned char *obuf, - int len, const aes_encrypt_ctx ctx[1]) -{ int nb = len >> AES_BLOCK_SIZE_P2; - - if(len & (AES_BLOCK_SIZE - 1)) - return EXIT_FAILURE; - -#if defined( USE_VIA_ACE_IF_PRESENT ) - - if(ctx->inf.b[1] == 0xff) - { uint8_t *ksp = (uint8_t*)(ctx->ks); - via_cwd(cwd, hybrid, enc, 2 * ctx->inf.b[0] - 192); - - if(ALIGN_OFFSET( ctx, 16 )) - return EXIT_FAILURE; - - if(!ALIGN_OFFSET( ibuf, 16 ) && !ALIGN_OFFSET( obuf, 16 )) - { - via_ecb_op5(ksp, cwd, ibuf, obuf, nb); - } - else - { aligned_auto(uint8_t, buf, BFR_BLOCKS * AES_BLOCK_SIZE, 16); - uint8_t *ip = NULL, *op = NULL; - - while(nb) - { - int m = (nb > BFR_BLOCKS ? BFR_BLOCKS : nb); - - ip = (ALIGN_OFFSET( ibuf, 16 ) ? buf : ibuf); - op = (ALIGN_OFFSET( obuf, 16 ) ? buf : obuf); - - if(ip != ibuf) - memcpy(buf, ibuf, m * AES_BLOCK_SIZE); - - via_ecb_op5(ksp, cwd, ip, op, m); - - if(op != obuf) - memcpy(obuf, buf, m * AES_BLOCK_SIZE); - - ibuf += m * AES_BLOCK_SIZE; - obuf += m * AES_BLOCK_SIZE; - nb -= m; - } - } - - return EXIT_SUCCESS; - } - -#endif - -#if !defined( ASSUME_VIA_ACE_PRESENT ) - while(nb--) - { - if(aes_encrypt(ibuf, obuf, ctx) != EXIT_SUCCESS) - return EXIT_FAILURE; - ibuf += AES_BLOCK_SIZE; - obuf += AES_BLOCK_SIZE; - } -#endif - return EXIT_SUCCESS; -} - -AES_RETURN aes_ecb_decrypt(const unsigned char *ibuf, unsigned char *obuf, - int len, const aes_decrypt_ctx ctx[1]) -{ int nb = len >> AES_BLOCK_SIZE_P2; - - if(len & (AES_BLOCK_SIZE - 1)) - return EXIT_FAILURE; - -#if defined( USE_VIA_ACE_IF_PRESENT ) - - if(ctx->inf.b[1] == 0xff) - { uint8_t *ksp = kd_adr(ctx); - via_cwd(cwd, hybrid, dec, 2 * ctx->inf.b[0] - 192); - - if(ALIGN_OFFSET( ctx, 16 )) - return EXIT_FAILURE; - - if(!ALIGN_OFFSET( ibuf, 16 ) && !ALIGN_OFFSET( obuf, 16 )) - { - via_ecb_op5(ksp, cwd, ibuf, obuf, nb); - } - else - { aligned_auto(uint8_t, buf, BFR_BLOCKS * AES_BLOCK_SIZE, 16); - uint8_t *ip = NULL, *op = NULL; - - while(nb) - { - int m = (nb > BFR_BLOCKS ? BFR_BLOCKS : nb); - - ip = (ALIGN_OFFSET( ibuf, 16 ) ? buf : ibuf); - op = (ALIGN_OFFSET( obuf, 16 ) ? buf : obuf); - - if(ip != ibuf) - memcpy(buf, ibuf, m * AES_BLOCK_SIZE); - - via_ecb_op5(ksp, cwd, ip, op, m); - - if(op != obuf) - memcpy(obuf, buf, m * AES_BLOCK_SIZE); - - ibuf += m * AES_BLOCK_SIZE; - obuf += m * AES_BLOCK_SIZE; - nb -= m; - } - } - - return EXIT_SUCCESS; - } - -#endif - -#if !defined( ASSUME_VIA_ACE_PRESENT ) - while(nb--) - { - if(aes_decrypt(ibuf, obuf, ctx) != EXIT_SUCCESS) - return EXIT_FAILURE; - ibuf += AES_BLOCK_SIZE; - obuf += AES_BLOCK_SIZE; - } -#endif - return EXIT_SUCCESS; -} - -AES_RETURN aes_cbc_encrypt(const unsigned char *ibuf, unsigned char *obuf, - int len, unsigned char *iv, const aes_encrypt_ctx ctx[1]) -{ int nb = len >> AES_BLOCK_SIZE_P2; - - if(len & (AES_BLOCK_SIZE - 1)) - return EXIT_FAILURE; - -#if defined( USE_VIA_ACE_IF_PRESENT ) - - if(ctx->inf.b[1] == 0xff) - { uint8_t *ksp = (uint8_t*)(ctx->ks), *ivp = iv; - aligned_auto(uint8_t, liv, AES_BLOCK_SIZE, 16); - via_cwd(cwd, hybrid, enc, 2 * ctx->inf.b[0] - 192); - - if(ALIGN_OFFSET( ctx, 16 )) - return EXIT_FAILURE; - - if(ALIGN_OFFSET( iv, 16 )) /* ensure an aligned iv */ - { - ivp = liv; - memcpy(liv, iv, AES_BLOCK_SIZE); - } - - if(!ALIGN_OFFSET( ibuf, 16 ) && !ALIGN_OFFSET( obuf, 16 ) && !ALIGN_OFFSET( iv, 16 )) - { - via_cbc_op7(ksp, cwd, ibuf, obuf, nb, ivp, ivp); - } - else - { aligned_auto(uint8_t, buf, BFR_BLOCKS * AES_BLOCK_SIZE, 16); - uint8_t *ip = NULL, *op = NULL; - - while(nb) - { - int m = (nb > BFR_BLOCKS ? BFR_BLOCKS : nb); - - ip = (ALIGN_OFFSET( ibuf, 16 ) ? buf : ibuf); - op = (ALIGN_OFFSET( obuf, 16 ) ? buf : obuf); - - if(ip != ibuf) - memcpy(buf, ibuf, m * AES_BLOCK_SIZE); - - via_cbc_op7(ksp, cwd, ip, op, m, ivp, ivp); - - if(op != obuf) - memcpy(obuf, buf, m * AES_BLOCK_SIZE); - - ibuf += m * AES_BLOCK_SIZE; - obuf += m * AES_BLOCK_SIZE; - nb -= m; - } - } - - if(iv != ivp) - memcpy(iv, ivp, AES_BLOCK_SIZE); - - return EXIT_SUCCESS; - } - -#endif - -#if !defined( ASSUME_VIA_ACE_PRESENT ) -# ifdef FAST_BUFFER_OPERATIONS - if(!ALIGN_OFFSET( ibuf, 4 ) && !ALIGN_OFFSET( iv, 4 )) - while(nb--) - { - lp32(iv)[0] ^= lp32(ibuf)[0]; - lp32(iv)[1] ^= lp32(ibuf)[1]; - lp32(iv)[2] ^= lp32(ibuf)[2]; - lp32(iv)[3] ^= lp32(ibuf)[3]; - if(aes_encrypt(iv, iv, ctx) != EXIT_SUCCESS) - return EXIT_FAILURE; - memcpy(obuf, iv, AES_BLOCK_SIZE); - ibuf += AES_BLOCK_SIZE; - obuf += AES_BLOCK_SIZE; - } - else -# endif - while(nb--) - { - iv[ 0] ^= ibuf[ 0]; iv[ 1] ^= ibuf[ 1]; - iv[ 2] ^= ibuf[ 2]; iv[ 3] ^= ibuf[ 3]; - iv[ 4] ^= ibuf[ 4]; iv[ 5] ^= ibuf[ 5]; - iv[ 6] ^= ibuf[ 6]; iv[ 7] ^= ibuf[ 7]; - iv[ 8] ^= ibuf[ 8]; iv[ 9] ^= ibuf[ 9]; - iv[10] ^= ibuf[10]; iv[11] ^= ibuf[11]; - iv[12] ^= ibuf[12]; iv[13] ^= ibuf[13]; - iv[14] ^= ibuf[14]; iv[15] ^= ibuf[15]; - if(aes_encrypt(iv, iv, ctx) != EXIT_SUCCESS) - return EXIT_FAILURE; - memcpy(obuf, iv, AES_BLOCK_SIZE); - ibuf += AES_BLOCK_SIZE; - obuf += AES_BLOCK_SIZE; - } -#endif - return EXIT_SUCCESS; -} - -AES_RETURN aes_cbc_decrypt(const unsigned char *ibuf, unsigned char *obuf, - int len, unsigned char *iv, const aes_decrypt_ctx ctx[1]) -{ unsigned char tmp[AES_BLOCK_SIZE]; - int nb = len >> AES_BLOCK_SIZE_P2; - - if(len & (AES_BLOCK_SIZE - 1)) - return EXIT_FAILURE; - -#if defined( USE_VIA_ACE_IF_PRESENT ) - - if(ctx->inf.b[1] == 0xff) - { uint8_t *ksp = kd_adr(ctx), *ivp = iv; - aligned_auto(uint8_t, liv, AES_BLOCK_SIZE, 16); - via_cwd(cwd, hybrid, dec, 2 * ctx->inf.b[0] - 192); - - if(ALIGN_OFFSET( ctx, 16 )) - return EXIT_FAILURE; - - if(ALIGN_OFFSET( iv, 16 )) /* ensure an aligned iv */ - { - ivp = liv; - memcpy(liv, iv, AES_BLOCK_SIZE); - } - - if(!ALIGN_OFFSET( ibuf, 16 ) && !ALIGN_OFFSET( obuf, 16 ) && !ALIGN_OFFSET( iv, 16 )) - { - via_cbc_op6(ksp, cwd, ibuf, obuf, nb, ivp); - } - else - { aligned_auto(uint8_t, buf, BFR_BLOCKS * AES_BLOCK_SIZE, 16); - uint8_t *ip = NULL, *op = NULL; - - while(nb) - { - int m = (nb > BFR_BLOCKS ? BFR_BLOCKS : nb); - - ip = (ALIGN_OFFSET( ibuf, 16 ) ? buf : ibuf); - op = (ALIGN_OFFSET( obuf, 16 ) ? buf : obuf); - - if(ip != ibuf) - memcpy(buf, ibuf, m * AES_BLOCK_SIZE); - - via_cbc_op6(ksp, cwd, ip, op, m, ivp); - - if(op != obuf) - memcpy(obuf, buf, m * AES_BLOCK_SIZE); - - ibuf += m * AES_BLOCK_SIZE; - obuf += m * AES_BLOCK_SIZE; - nb -= m; - } - } - - if(iv != ivp) - memcpy(iv, ivp, AES_BLOCK_SIZE); - - return EXIT_SUCCESS; - } -#endif - -#if !defined( ASSUME_VIA_ACE_PRESENT ) -# ifdef FAST_BUFFER_OPERATIONS - if(!ALIGN_OFFSET( obuf, 4 ) && !ALIGN_OFFSET( iv, 4 )) - while(nb--) - { - memcpy(tmp, ibuf, AES_BLOCK_SIZE); - if(aes_decrypt(ibuf, obuf, ctx) != EXIT_SUCCESS) - return EXIT_FAILURE; - lp32(obuf)[0] ^= lp32(iv)[0]; - lp32(obuf)[1] ^= lp32(iv)[1]; - lp32(obuf)[2] ^= lp32(iv)[2]; - lp32(obuf)[3] ^= lp32(iv)[3]; - memcpy(iv, tmp, AES_BLOCK_SIZE); - ibuf += AES_BLOCK_SIZE; - obuf += AES_BLOCK_SIZE; - } - else -# endif - while(nb--) - { - memcpy(tmp, ibuf, AES_BLOCK_SIZE); - if(aes_decrypt(ibuf, obuf, ctx) != EXIT_SUCCESS) - return EXIT_FAILURE; - obuf[ 0] ^= iv[ 0]; obuf[ 1] ^= iv[ 1]; - obuf[ 2] ^= iv[ 2]; obuf[ 3] ^= iv[ 3]; - obuf[ 4] ^= iv[ 4]; obuf[ 5] ^= iv[ 5]; - obuf[ 6] ^= iv[ 6]; obuf[ 7] ^= iv[ 7]; - obuf[ 8] ^= iv[ 8]; obuf[ 9] ^= iv[ 9]; - obuf[10] ^= iv[10]; obuf[11] ^= iv[11]; - obuf[12] ^= iv[12]; obuf[13] ^= iv[13]; - obuf[14] ^= iv[14]; obuf[15] ^= iv[15]; - memcpy(iv, tmp, AES_BLOCK_SIZE); - ibuf += AES_BLOCK_SIZE; - obuf += AES_BLOCK_SIZE; - } -#endif - return EXIT_SUCCESS; -} - -AES_RETURN aes_cfb_encrypt(const unsigned char *ibuf, unsigned char *obuf, - int len, unsigned char *iv, aes_encrypt_ctx ctx[1]) -{ int cnt = 0, b_pos = (int)ctx->inf.b[2], nb; - - if(b_pos) /* complete any partial block */ - { - while(b_pos < AES_BLOCK_SIZE && cnt < len) - { - *obuf++ = (iv[b_pos++] ^= *ibuf++); - cnt++; - } - - b_pos = (b_pos == AES_BLOCK_SIZE ? 0 : b_pos); - } - - if((nb = (len - cnt) >> AES_BLOCK_SIZE_P2) != 0) /* process whole blocks */ - { -#if defined( USE_VIA_ACE_IF_PRESENT ) - - if(ctx->inf.b[1] == 0xff) - { int m; - uint8_t *ksp = (uint8_t*)(ctx->ks), *ivp = iv; - aligned_auto(uint8_t, liv, AES_BLOCK_SIZE, 16); - via_cwd(cwd, hybrid, enc, 2 * ctx->inf.b[0] - 192); - - if(ALIGN_OFFSET( ctx, 16 )) - return EXIT_FAILURE; - - if(ALIGN_OFFSET( iv, 16 )) /* ensure an aligned iv */ - { - ivp = liv; - memcpy(liv, iv, AES_BLOCK_SIZE); - } - - if(!ALIGN_OFFSET( ibuf, 16 ) && !ALIGN_OFFSET( obuf, 16 )) - { - via_cfb_op7(ksp, cwd, ibuf, obuf, nb, ivp, ivp); - ibuf += nb * AES_BLOCK_SIZE; - obuf += nb * AES_BLOCK_SIZE; - cnt += nb * AES_BLOCK_SIZE; - } - else /* input, output or both are unaligned */ - { aligned_auto(uint8_t, buf, BFR_BLOCKS * AES_BLOCK_SIZE, 16); - uint8_t *ip = NULL, *op = NULL; - - while(nb) - { - m = (nb > BFR_BLOCKS ? BFR_BLOCKS : nb), nb -= m; - - ip = (ALIGN_OFFSET( ibuf, 16 ) ? buf : ibuf); - op = (ALIGN_OFFSET( obuf, 16 ) ? buf : obuf); - - if(ip != ibuf) - memcpy(buf, ibuf, m * AES_BLOCK_SIZE); - - via_cfb_op7(ksp, cwd, ip, op, m, ivp, ivp); - - if(op != obuf) - memcpy(obuf, buf, m * AES_BLOCK_SIZE); - - ibuf += m * AES_BLOCK_SIZE; - obuf += m * AES_BLOCK_SIZE; - cnt += m * AES_BLOCK_SIZE; - } - } - - if(ivp != iv) - memcpy(iv, ivp, AES_BLOCK_SIZE); - } -#else -# ifdef FAST_BUFFER_OPERATIONS - if(!ALIGN_OFFSET( ibuf, 4 ) && !ALIGN_OFFSET( obuf, 4 ) && !ALIGN_OFFSET( iv, 4 )) - while(cnt + AES_BLOCK_SIZE <= len) - { - assert(b_pos == 0); - if(aes_encrypt(iv, iv, ctx) != EXIT_SUCCESS) - return EXIT_FAILURE; - lp32(obuf)[0] = lp32(iv)[0] ^= lp32(ibuf)[0]; - lp32(obuf)[1] = lp32(iv)[1] ^= lp32(ibuf)[1]; - lp32(obuf)[2] = lp32(iv)[2] ^= lp32(ibuf)[2]; - lp32(obuf)[3] = lp32(iv)[3] ^= lp32(ibuf)[3]; - ibuf += AES_BLOCK_SIZE; - obuf += AES_BLOCK_SIZE; - cnt += AES_BLOCK_SIZE; - } - else -# endif - while(cnt + AES_BLOCK_SIZE <= len) - { - assert(b_pos == 0); - if(aes_encrypt(iv, iv, ctx) != EXIT_SUCCESS) - return EXIT_FAILURE; - obuf[ 0] = iv[ 0] ^= ibuf[ 0]; obuf[ 1] = iv[ 1] ^= ibuf[ 1]; - obuf[ 2] = iv[ 2] ^= ibuf[ 2]; obuf[ 3] = iv[ 3] ^= ibuf[ 3]; - obuf[ 4] = iv[ 4] ^= ibuf[ 4]; obuf[ 5] = iv[ 5] ^= ibuf[ 5]; - obuf[ 6] = iv[ 6] ^= ibuf[ 6]; obuf[ 7] = iv[ 7] ^= ibuf[ 7]; - obuf[ 8] = iv[ 8] ^= ibuf[ 8]; obuf[ 9] = iv[ 9] ^= ibuf[ 9]; - obuf[10] = iv[10] ^= ibuf[10]; obuf[11] = iv[11] ^= ibuf[11]; - obuf[12] = iv[12] ^= ibuf[12]; obuf[13] = iv[13] ^= ibuf[13]; - obuf[14] = iv[14] ^= ibuf[14]; obuf[15] = iv[15] ^= ibuf[15]; - ibuf += AES_BLOCK_SIZE; - obuf += AES_BLOCK_SIZE; - cnt += AES_BLOCK_SIZE; - } -#endif - } - - while(cnt < len) - { - if(!b_pos && aes_encrypt(iv, iv, ctx) != EXIT_SUCCESS) - return EXIT_FAILURE; - - while(cnt < len && b_pos < AES_BLOCK_SIZE) - { - *obuf++ = (iv[b_pos++] ^= *ibuf++); - cnt++; - } - - b_pos = (b_pos == AES_BLOCK_SIZE ? 0 : b_pos); - } - - ctx->inf.b[2] = (uint8_t)b_pos; - return EXIT_SUCCESS; -} - -AES_RETURN aes_cfb_decrypt(const unsigned char *ibuf, unsigned char *obuf, - int len, unsigned char *iv, aes_encrypt_ctx ctx[1]) -{ int cnt = 0, b_pos = (int)ctx->inf.b[2], nb; - - if(b_pos) /* complete any partial block */ - { uint8_t t; - - while(b_pos < AES_BLOCK_SIZE && cnt < len) - { - t = *ibuf++; - *obuf++ = t ^ iv[b_pos]; - iv[b_pos++] = t; - cnt++; - } - - b_pos = (b_pos == AES_BLOCK_SIZE ? 0 : b_pos); - } - - if((nb = (len - cnt) >> AES_BLOCK_SIZE_P2) != 0) /* process whole blocks */ - { -#if defined( USE_VIA_ACE_IF_PRESENT ) - - if(ctx->inf.b[1] == 0xff) - { int m; - uint8_t *ksp = (uint8_t*)(ctx->ks), *ivp = iv; - aligned_auto(uint8_t, liv, AES_BLOCK_SIZE, 16); - via_cwd(cwd, hybrid, dec, 2 * ctx->inf.b[0] - 192); - - if(ALIGN_OFFSET( ctx, 16 )) - return EXIT_FAILURE; - - if(ALIGN_OFFSET( iv, 16 )) /* ensure an aligned iv */ - { - ivp = liv; - memcpy(liv, iv, AES_BLOCK_SIZE); - } - - if(!ALIGN_OFFSET( ibuf, 16 ) && !ALIGN_OFFSET( obuf, 16 )) - { - via_cfb_op6(ksp, cwd, ibuf, obuf, nb, ivp); - ibuf += nb * AES_BLOCK_SIZE; - obuf += nb * AES_BLOCK_SIZE; - cnt += nb * AES_BLOCK_SIZE; - } - else /* input, output or both are unaligned */ - { aligned_auto(uint8_t, buf, BFR_BLOCKS * AES_BLOCK_SIZE, 16); - uint8_t *ip = NULL, *op = NULL; - - while(nb) - { - m = (nb > BFR_BLOCKS ? BFR_BLOCKS : nb), nb -= m; - - ip = (ALIGN_OFFSET( ibuf, 16 ) ? buf : ibuf); - op = (ALIGN_OFFSET( obuf, 16 ) ? buf : obuf); - - if(ip != ibuf) /* input buffer is not aligned */ - memcpy(buf, ibuf, m * AES_BLOCK_SIZE); - - via_cfb_op6(ksp, cwd, ip, op, m, ivp); - - if(op != obuf) /* output buffer is not aligned */ - memcpy(obuf, buf, m * AES_BLOCK_SIZE); - - ibuf += m * AES_BLOCK_SIZE; - obuf += m * AES_BLOCK_SIZE; - cnt += m * AES_BLOCK_SIZE; - } - } - - if(ivp != iv) - memcpy(iv, ivp, AES_BLOCK_SIZE); - } -#else -# ifdef FAST_BUFFER_OPERATIONS - if(!ALIGN_OFFSET( ibuf, 4 ) && !ALIGN_OFFSET( obuf, 4 ) &&!ALIGN_OFFSET( iv, 4 )) - while(cnt + AES_BLOCK_SIZE <= len) - { uint32_t t; - - assert(b_pos == 0); - if(aes_encrypt(iv, iv, ctx) != EXIT_SUCCESS) - return EXIT_FAILURE; - t = lp32(ibuf)[0], lp32(obuf)[0] = t ^ lp32(iv)[0], lp32(iv)[0] = t; - t = lp32(ibuf)[1], lp32(obuf)[1] = t ^ lp32(iv)[1], lp32(iv)[1] = t; - t = lp32(ibuf)[2], lp32(obuf)[2] = t ^ lp32(iv)[2], lp32(iv)[2] = t; - t = lp32(ibuf)[3], lp32(obuf)[3] = t ^ lp32(iv)[3], lp32(iv)[3] = t; - ibuf += AES_BLOCK_SIZE; - obuf += AES_BLOCK_SIZE; - cnt += AES_BLOCK_SIZE; - } - else -# endif - while(cnt + AES_BLOCK_SIZE <= len) - { uint8_t t; - - assert(b_pos == 0); - if(aes_encrypt(iv, iv, ctx) != EXIT_SUCCESS) - return EXIT_FAILURE; - t = ibuf[ 0], obuf[ 0] = t ^ iv[ 0], iv[ 0] = t; - t = ibuf[ 1], obuf[ 1] = t ^ iv[ 1], iv[ 1] = t; - t = ibuf[ 2], obuf[ 2] = t ^ iv[ 2], iv[ 2] = t; - t = ibuf[ 3], obuf[ 3] = t ^ iv[ 3], iv[ 3] = t; - t = ibuf[ 4], obuf[ 4] = t ^ iv[ 4], iv[ 4] = t; - t = ibuf[ 5], obuf[ 5] = t ^ iv[ 5], iv[ 5] = t; - t = ibuf[ 6], obuf[ 6] = t ^ iv[ 6], iv[ 6] = t; - t = ibuf[ 7], obuf[ 7] = t ^ iv[ 7], iv[ 7] = t; - t = ibuf[ 8], obuf[ 8] = t ^ iv[ 8], iv[ 8] = t; - t = ibuf[ 9], obuf[ 9] = t ^ iv[ 9], iv[ 9] = t; - t = ibuf[10], obuf[10] = t ^ iv[10], iv[10] = t; - t = ibuf[11], obuf[11] = t ^ iv[11], iv[11] = t; - t = ibuf[12], obuf[12] = t ^ iv[12], iv[12] = t; - t = ibuf[13], obuf[13] = t ^ iv[13], iv[13] = t; - t = ibuf[14], obuf[14] = t ^ iv[14], iv[14] = t; - t = ibuf[15], obuf[15] = t ^ iv[15], iv[15] = t; - ibuf += AES_BLOCK_SIZE; - obuf += AES_BLOCK_SIZE; - cnt += AES_BLOCK_SIZE; - } -#endif - } - - while(cnt < len) - { uint8_t t; - - if(!b_pos && aes_encrypt(iv, iv, ctx) != EXIT_SUCCESS) - return EXIT_FAILURE; - - while(cnt < len && b_pos < AES_BLOCK_SIZE) - { - t = *ibuf++; - *obuf++ = t ^ iv[b_pos]; - iv[b_pos++] = t; - cnt++; - } - - b_pos = (b_pos == AES_BLOCK_SIZE ? 0 : b_pos); - } - - ctx->inf.b[2] = (uint8_t)b_pos; - return EXIT_SUCCESS; -} - -AES_RETURN aes_ofb_crypt(const unsigned char *ibuf, unsigned char *obuf, - int len, unsigned char *iv, aes_encrypt_ctx ctx[1]) -{ int cnt = 0, b_pos = (int)ctx->inf.b[2], nb; - - if(b_pos) /* complete any partial block */ - { - while(b_pos < AES_BLOCK_SIZE && cnt < len) - { - *obuf++ = iv[b_pos++] ^ *ibuf++; - cnt++; - } - - b_pos = (b_pos == AES_BLOCK_SIZE ? 0 : b_pos); - } - - if((nb = (len - cnt) >> AES_BLOCK_SIZE_P2) != 0) /* process whole blocks */ - { -#if defined( USE_VIA_ACE_IF_PRESENT ) - - if(ctx->inf.b[1] == 0xff) - { int m; - uint8_t *ksp = (uint8_t*)(ctx->ks), *ivp = iv; - aligned_auto(uint8_t, liv, AES_BLOCK_SIZE, 16); - via_cwd(cwd, hybrid, enc, 2 * ctx->inf.b[0] - 192); - - if(ALIGN_OFFSET( ctx, 16 )) - return EXIT_FAILURE; - - if(ALIGN_OFFSET( iv, 16 )) /* ensure an aligned iv */ - { - ivp = liv; - memcpy(liv, iv, AES_BLOCK_SIZE); - } - - if(!ALIGN_OFFSET( ibuf, 16 ) && !ALIGN_OFFSET( obuf, 16 )) - { - via_ofb_op6(ksp, cwd, ibuf, obuf, nb, ivp); - ibuf += nb * AES_BLOCK_SIZE; - obuf += nb * AES_BLOCK_SIZE; - cnt += nb * AES_BLOCK_SIZE; - } - else /* input, output or both are unaligned */ - { aligned_auto(uint8_t, buf, BFR_BLOCKS * AES_BLOCK_SIZE, 16); - uint8_t *ip = NULL, *op = NULL; - - while(nb) - { - m = (nb > BFR_BLOCKS ? BFR_BLOCKS : nb), nb -= m; - - ip = (ALIGN_OFFSET( ibuf, 16 ) ? buf : ibuf); - op = (ALIGN_OFFSET( obuf, 16 ) ? buf : obuf); - - if(ip != ibuf) - memcpy(buf, ibuf, m * AES_BLOCK_SIZE); - - via_ofb_op6(ksp, cwd, ip, op, m, ivp); - - if(op != obuf) - memcpy(obuf, buf, m * AES_BLOCK_SIZE); - - ibuf += m * AES_BLOCK_SIZE; - obuf += m * AES_BLOCK_SIZE; - cnt += m * AES_BLOCK_SIZE; - } - } - - if(ivp != iv) - memcpy(iv, ivp, AES_BLOCK_SIZE); - } -#else -# ifdef FAST_BUFFER_OPERATIONS - if(!ALIGN_OFFSET( ibuf, 4 ) && !ALIGN_OFFSET( obuf, 4 ) && !ALIGN_OFFSET( iv, 4 )) - while(cnt + AES_BLOCK_SIZE <= len) - { - assert(b_pos == 0); - if(aes_encrypt(iv, iv, ctx) != EXIT_SUCCESS) - return EXIT_FAILURE; - lp32(obuf)[0] = lp32(iv)[0] ^ lp32(ibuf)[0]; - lp32(obuf)[1] = lp32(iv)[1] ^ lp32(ibuf)[1]; - lp32(obuf)[2] = lp32(iv)[2] ^ lp32(ibuf)[2]; - lp32(obuf)[3] = lp32(iv)[3] ^ lp32(ibuf)[3]; - ibuf += AES_BLOCK_SIZE; - obuf += AES_BLOCK_SIZE; - cnt += AES_BLOCK_SIZE; - } - else -# endif - while(cnt + AES_BLOCK_SIZE <= len) - { - assert(b_pos == 0); - if(aes_encrypt(iv, iv, ctx) != EXIT_SUCCESS) - return EXIT_FAILURE; - obuf[ 0] = iv[ 0] ^ ibuf[ 0]; obuf[ 1] = iv[ 1] ^ ibuf[ 1]; - obuf[ 2] = iv[ 2] ^ ibuf[ 2]; obuf[ 3] = iv[ 3] ^ ibuf[ 3]; - obuf[ 4] = iv[ 4] ^ ibuf[ 4]; obuf[ 5] = iv[ 5] ^ ibuf[ 5]; - obuf[ 6] = iv[ 6] ^ ibuf[ 6]; obuf[ 7] = iv[ 7] ^ ibuf[ 7]; - obuf[ 8] = iv[ 8] ^ ibuf[ 8]; obuf[ 9] = iv[ 9] ^ ibuf[ 9]; - obuf[10] = iv[10] ^ ibuf[10]; obuf[11] = iv[11] ^ ibuf[11]; - obuf[12] = iv[12] ^ ibuf[12]; obuf[13] = iv[13] ^ ibuf[13]; - obuf[14] = iv[14] ^ ibuf[14]; obuf[15] = iv[15] ^ ibuf[15]; - ibuf += AES_BLOCK_SIZE; - obuf += AES_BLOCK_SIZE; - cnt += AES_BLOCK_SIZE; - } -#endif - } - - while(cnt < len) - { - if(!b_pos && aes_encrypt(iv, iv, ctx) != EXIT_SUCCESS) - return EXIT_FAILURE; - - while(cnt < len && b_pos < AES_BLOCK_SIZE) - { - *obuf++ = iv[b_pos++] ^ *ibuf++; - cnt++; - } - - b_pos = (b_pos == AES_BLOCK_SIZE ? 0 : b_pos); - } - - ctx->inf.b[2] = (uint8_t)b_pos; - return EXIT_SUCCESS; -} - -#define BFR_LENGTH (BFR_BLOCKS * AES_BLOCK_SIZE) - -AES_RETURN aes_ctr_crypt(const unsigned char *ibuf, unsigned char *obuf, - int len, unsigned char *cbuf, cbuf_inc ctr_inc, aes_encrypt_ctx ctx[1]) -{ unsigned char *ip; - int i = 0, blen = 0, b_pos = (int)(ctx->inf.b[2]); - -#if defined( USE_VIA_ACE_IF_PRESENT ) - aligned_auto(uint8_t, buf, BFR_LENGTH, 16); - if(ctx->inf.b[1] == 0xff && ALIGN_OFFSET( ctx, 16 )) - return EXIT_FAILURE; -#else - uint8_t buf[BFR_LENGTH] = {0}; -#endif - - if(b_pos) - { - memcpy(buf, cbuf, AES_BLOCK_SIZE); - if(aes_ecb_encrypt(buf, buf, AES_BLOCK_SIZE, ctx) != EXIT_SUCCESS) - return EXIT_FAILURE; - - while(b_pos < AES_BLOCK_SIZE && len) - { - *obuf++ = *ibuf++ ^ buf[b_pos++]; - --len; - } - - if(len) - ctr_inc(cbuf), b_pos = 0; - } - - while(len) - { - blen = (len > BFR_LENGTH ? BFR_LENGTH : len), len -= blen; - - for(i = 0, ip = buf; i < (blen >> AES_BLOCK_SIZE_P2); ++i) - { - memcpy(ip, cbuf, AES_BLOCK_SIZE); - ctr_inc(cbuf); - ip += AES_BLOCK_SIZE; - } - - if(blen & (AES_BLOCK_SIZE - 1)) - memcpy(ip, cbuf, AES_BLOCK_SIZE), i++; - -#if defined( USE_VIA_ACE_IF_PRESENT ) - if(ctx->inf.b[1] == 0xff) - { - via_cwd(cwd, hybrid, enc, 2 * ctx->inf.b[0] - 192); - via_ecb_op5((ctx->ks), cwd, buf, buf, i); - } - else -#endif - if(aes_ecb_encrypt(buf, buf, i * AES_BLOCK_SIZE, ctx) != EXIT_SUCCESS) - return EXIT_FAILURE; - - i = 0; ip = buf; -# ifdef FAST_BUFFER_OPERATIONS - if(!ALIGN_OFFSET( ibuf, 4 ) && !ALIGN_OFFSET( obuf, 4 ) && !ALIGN_OFFSET( ip, 4 )) - while(i + AES_BLOCK_SIZE <= blen) - { - lp32(obuf)[0] = lp32(ibuf)[0] ^ lp32(ip)[0]; - lp32(obuf)[1] = lp32(ibuf)[1] ^ lp32(ip)[1]; - lp32(obuf)[2] = lp32(ibuf)[2] ^ lp32(ip)[2]; - lp32(obuf)[3] = lp32(ibuf)[3] ^ lp32(ip)[3]; - i += AES_BLOCK_SIZE; - ip += AES_BLOCK_SIZE; - ibuf += AES_BLOCK_SIZE; - obuf += AES_BLOCK_SIZE; - } - else -#endif - while(i + AES_BLOCK_SIZE <= blen) - { - obuf[ 0] = ibuf[ 0] ^ ip[ 0]; obuf[ 1] = ibuf[ 1] ^ ip[ 1]; - obuf[ 2] = ibuf[ 2] ^ ip[ 2]; obuf[ 3] = ibuf[ 3] ^ ip[ 3]; - obuf[ 4] = ibuf[ 4] ^ ip[ 4]; obuf[ 5] = ibuf[ 5] ^ ip[ 5]; - obuf[ 6] = ibuf[ 6] ^ ip[ 6]; obuf[ 7] = ibuf[ 7] ^ ip[ 7]; - obuf[ 8] = ibuf[ 8] ^ ip[ 8]; obuf[ 9] = ibuf[ 9] ^ ip[ 9]; - obuf[10] = ibuf[10] ^ ip[10]; obuf[11] = ibuf[11] ^ ip[11]; - obuf[12] = ibuf[12] ^ ip[12]; obuf[13] = ibuf[13] ^ ip[13]; - obuf[14] = ibuf[14] ^ ip[14]; obuf[15] = ibuf[15] ^ ip[15]; - i += AES_BLOCK_SIZE; - ip += AES_BLOCK_SIZE; - ibuf += AES_BLOCK_SIZE; - obuf += AES_BLOCK_SIZE; - } - - while(i++ < blen) - *obuf++ = *ibuf++ ^ ip[b_pos++]; - } - - ctx->inf.b[2] = (uint8_t)b_pos; - return EXIT_SUCCESS; -} - -void aes_ctr_cbuf_inc(unsigned char *cbuf) -{ - int i = AES_BLOCK_SIZE - 1; - while (i >= 0) { - cbuf[i]++; - if (cbuf[i]) return; // if there was no overflow - i--; - } -} - -#if defined(__cplusplus) -} -#endif -#endif diff --git a/trezor-crypto/crypto/aes/aescrypt.c b/trezor-crypto/crypto/aes/aescrypt.c deleted file mode 100644 index 8a168d04efe..00000000000 --- a/trezor-crypto/crypto/aes/aescrypt.c +++ /dev/null @@ -1,307 +0,0 @@ -/* ---------------------------------------------------------------------------- -Copyright (c) 1998-2013, Brian Gladman, Worcester, UK. All rights reserved. - -The redistribution and use of this software (with or without changes) -is allowed without the payment of fees or royalties provided that: - - source code distributions include the above copyright notice, this - list of conditions and the following disclaimer; - - binary distributions include the above copyright notice, this list - of conditions and the following disclaimer in their documentation. - -This software is provided 'as is' with no explicit or implied warranties -in respect of its operation, including, but not limited to, correctness -and fitness for purpose. ---------------------------------------------------------------------------- -Issue Date: 20/12/2007 -*/ - -#include -#include - -#if defined( USE_INTEL_AES_IF_PRESENT ) -# include "aes_ni.h" -#else -/* map names here to provide the external API ('name' -> 'aes_name') */ -# define aes_xi(x) aes_ ## x -#endif - -#if defined(__cplusplus) -extern "C" -{ -#endif - -#define si(y,x,k,c) (s(y,c) = word_in(x, c) ^ (k)[c]) -#define so(y,x,c) word_out(y, c, s(x,c)) - -#if defined(ARRAYS) -#define locals(y,x) x[4],y[4] -#else -#define locals(y,x) x##0,x##1,x##2,x##3,y##0,y##1,y##2,y##3 -#endif - -#define l_copy(y, x) s(y,0) = s(x,0); s(y,1) = s(x,1); \ - s(y,2) = s(x,2); s(y,3) = s(x,3); -#define state_in(y,x,k) si(y,x,k,0); si(y,x,k,1); si(y,x,k,2); si(y,x,k,3) -#define state_out(y,x) so(y,x,0); so(y,x,1); so(y,x,2); so(y,x,3) -#define round(rm,y,x,k) rm(y,x,k,0); rm(y,x,k,1); rm(y,x,k,2); rm(y,x,k,3) - -#if ( FUNCS_IN_C & ENCRYPTION_IN_C ) - -/* Visual C++ .Net v7.1 provides the fastest encryption code when using - Pentium optimiation with small code but this is poor for decryption - so we need to control this with the following VC++ pragmas -*/ - -#if defined( _MSC_VER ) && !defined( _WIN64 ) && !defined( __clang__ ) -#pragma optimize( "s", on ) -#endif - -/* Given the column (c) of the output state variable, the following - macros give the input state variables which are needed in its - computation for each row (r) of the state. All the alternative - macros give the same end values but expand into different ways - of calculating these values. In particular the complex macro - used for dynamically variable block sizes is designed to expand - to a compile time constant whenever possible but will expand to - conditional clauses on some branches (I am grateful to Frank - Yellin for this construction) -*/ - -#define fwd_var(x,r,c)\ - ( r == 0 ? ( c == 0 ? s(x,0) : c == 1 ? s(x,1) : c == 2 ? s(x,2) : s(x,3))\ - : r == 1 ? ( c == 0 ? s(x,1) : c == 1 ? s(x,2) : c == 2 ? s(x,3) : s(x,0))\ - : r == 2 ? ( c == 0 ? s(x,2) : c == 1 ? s(x,3) : c == 2 ? s(x,0) : s(x,1))\ - : ( c == 0 ? s(x,3) : c == 1 ? s(x,0) : c == 2 ? s(x,1) : s(x,2))) - -#if defined(FT4_SET) -#undef dec_fmvars -#define fwd_rnd(y,x,k,c) (s(y,c) = (k)[c] ^ four_tables(x,t_use(f,n),fwd_var,rf1,c)) -#elif defined(FT1_SET) -#undef dec_fmvars -#define fwd_rnd(y,x,k,c) (s(y,c) = (k)[c] ^ one_table(x,upr,t_use(f,n),fwd_var,rf1,c)) -#else -#define fwd_rnd(y,x,k,c) (s(y,c) = (k)[c] ^ fwd_mcol(no_table(x,t_use(s,box),fwd_var,rf1,c))) -#endif - -#if defined(FL4_SET) -#define fwd_lrnd(y,x,k,c) (s(y,c) = (k)[c] ^ four_tables(x,t_use(f,l),fwd_var,rf1,c)) -#elif defined(FL1_SET) -#define fwd_lrnd(y,x,k,c) (s(y,c) = (k)[c] ^ one_table(x,ups,t_use(f,l),fwd_var,rf1,c)) -#else -#define fwd_lrnd(y,x,k,c) (s(y,c) = (k)[c] ^ no_table(x,t_use(s,box),fwd_var,rf1,c)) -#endif - -AES_RETURN aes_xi(encrypt)(const unsigned char *in, unsigned char *out, const aes_encrypt_ctx cx[1]) -{ uint32_t locals(b0, b1); - const uint32_t *kp = NULL; -#if defined( dec_fmvars ) - dec_fmvars; /* declare variables for fwd_mcol() if needed */ -#endif - - if(cx->inf.b[0] != 10 * AES_BLOCK_SIZE && cx->inf.b[0] != 12 * AES_BLOCK_SIZE && cx->inf.b[0] != 14 * AES_BLOCK_SIZE) - return EXIT_FAILURE; - - kp = cx->ks; - state_in(b0, in, kp); - -#if (ENC_UNROLL == FULL) - - switch(cx->inf.b[0]) - { - case 14 * AES_BLOCK_SIZE: - round(fwd_rnd, b1, b0, kp + 1 * N_COLS); - round(fwd_rnd, b0, b1, kp + 2 * N_COLS); - kp += 2 * N_COLS; - //-fallthrough - case 12 * AES_BLOCK_SIZE: - round(fwd_rnd, b1, b0, kp + 1 * N_COLS); - round(fwd_rnd, b0, b1, kp + 2 * N_COLS); - kp += 2 * N_COLS; - //-fallthrough - case 10 * AES_BLOCK_SIZE: - round(fwd_rnd, b1, b0, kp + 1 * N_COLS); - round(fwd_rnd, b0, b1, kp + 2 * N_COLS); - round(fwd_rnd, b1, b0, kp + 3 * N_COLS); - round(fwd_rnd, b0, b1, kp + 4 * N_COLS); - round(fwd_rnd, b1, b0, kp + 5 * N_COLS); - round(fwd_rnd, b0, b1, kp + 6 * N_COLS); - round(fwd_rnd, b1, b0, kp + 7 * N_COLS); - round(fwd_rnd, b0, b1, kp + 8 * N_COLS); - round(fwd_rnd, b1, b0, kp + 9 * N_COLS); - round(fwd_lrnd, b0, b1, kp +10 * N_COLS); - //-fallthrough - } - -#else - -#if (ENC_UNROLL == PARTIAL) - { uint32_t rnd; - for(rnd = 0; rnd < (cx->inf.b[0] >> 5) - 1; ++rnd) - { - kp += N_COLS; - round(fwd_rnd, b1, b0, kp); - kp += N_COLS; - round(fwd_rnd, b0, b1, kp); - } - kp += N_COLS; - round(fwd_rnd, b1, b0, kp); -#else - { uint32_t rnd; - for(rnd = 0; rnd < (cx->inf.b[0] >> 4) - 1; ++rnd) - { - kp += N_COLS; - round(fwd_rnd, b1, b0, kp); - l_copy(b0, b1); - } -#endif - kp += N_COLS; - round(fwd_lrnd, b0, b1, kp); - } -#endif - - state_out(out, b0); - return EXIT_SUCCESS; -} - -#endif - -#if ( FUNCS_IN_C & DECRYPTION_IN_C) - -/* Visual C++ .Net v7.1 provides the fastest encryption code when using - Pentium optimiation with small code but this is poor for decryption - so we need to control this with the following VC++ pragmas -*/ - -#if defined( _MSC_VER ) && !defined( _WIN64 ) && !defined( __clang__ ) -#pragma optimize( "t", on ) -#endif - -/* Given the column (c) of the output state variable, the following - macros give the input state variables which are needed in its - computation for each row (r) of the state. All the alternative - macros give the same end values but expand into different ways - of calculating these values. In particular the complex macro - used for dynamically variable block sizes is designed to expand - to a compile time constant whenever possible but will expand to - conditional clauses on some branches (I am grateful to Frank - Yellin for this construction) -*/ - -#define inv_var(x,r,c)\ - ( r == 0 ? ( c == 0 ? s(x,0) : c == 1 ? s(x,1) : c == 2 ? s(x,2) : s(x,3))\ - : r == 1 ? ( c == 0 ? s(x,3) : c == 1 ? s(x,0) : c == 2 ? s(x,1) : s(x,2))\ - : r == 2 ? ( c == 0 ? s(x,2) : c == 1 ? s(x,3) : c == 2 ? s(x,0) : s(x,1))\ - : ( c == 0 ? s(x,1) : c == 1 ? s(x,2) : c == 2 ? s(x,3) : s(x,0))) - -#if defined(IT4_SET) -#undef dec_imvars -#define inv_rnd(y,x,k,c) (s(y,c) = (k)[c] ^ four_tables(x,t_use(i,n),inv_var,rf1,c)) -#elif defined(IT1_SET) -#undef dec_imvars -#define inv_rnd(y,x,k,c) (s(y,c) = (k)[c] ^ one_table(x,upr,t_use(i,n),inv_var,rf1,c)) -#else -#define inv_rnd(y,x,k,c) (s(y,c) = inv_mcol((k)[c] ^ no_table(x,t_use(i,box),inv_var,rf1,c))) -#endif - -#if defined(IL4_SET) -#define inv_lrnd(y,x,k,c) (s(y,c) = (k)[c] ^ four_tables(x,t_use(i,l),inv_var,rf1,c)) -#elif defined(IL1_SET) -#define inv_lrnd(y,x,k,c) (s(y,c) = (k)[c] ^ one_table(x,ups,t_use(i,l),inv_var,rf1,c)) -#else -#define inv_lrnd(y,x,k,c) (s(y,c) = (k)[c] ^ no_table(x,t_use(i,box),inv_var,rf1,c)) -#endif - -/* This code can work with the decryption key schedule in the */ -/* order that is used for encryption (where the 1st decryption */ -/* round key is at the high end ot the schedule) or with a key */ -/* schedule that has been reversed to put the 1st decryption */ -/* round key at the low end of the schedule in memory (when */ -/* AES_REV_DKS is defined) */ - -#ifdef AES_REV_DKS -#define key_ofs 0 -#define rnd_key(n) (kp + n * N_COLS) -#else -#define key_ofs 1 -#define rnd_key(n) (kp - n * N_COLS) -#endif - -AES_RETURN aes_xi(decrypt)(const unsigned char *in, unsigned char *out, const aes_decrypt_ctx cx[1]) -{ uint32_t locals(b0, b1); -#if defined( dec_imvars ) - dec_imvars; /* declare variables for inv_mcol() if needed */ -#endif - const uint32_t *kp = NULL; - - if(cx->inf.b[0] != 10 * AES_BLOCK_SIZE && cx->inf.b[0] != 12 * AES_BLOCK_SIZE && cx->inf.b[0] != 14 * AES_BLOCK_SIZE) - return EXIT_FAILURE; - - kp = cx->ks + (key_ofs ? (cx->inf.b[0] >> 2) : 0); - state_in(b0, in, kp); - -#if (DEC_UNROLL == FULL) - - kp = cx->ks + (key_ofs ? 0 : (cx->inf.b[0] >> 2)); - switch(cx->inf.b[0]) - { - case 14 * AES_BLOCK_SIZE: - round(inv_rnd, b1, b0, rnd_key(-13)); - round(inv_rnd, b0, b1, rnd_key(-12)); - //-fallthrough - case 12 * AES_BLOCK_SIZE: - round(inv_rnd, b1, b0, rnd_key(-11)); - round(inv_rnd, b0, b1, rnd_key(-10)); - //-fallthrough - case 10 * AES_BLOCK_SIZE: - round(inv_rnd, b1, b0, rnd_key(-9)); - round(inv_rnd, b0, b1, rnd_key(-8)); - round(inv_rnd, b1, b0, rnd_key(-7)); - round(inv_rnd, b0, b1, rnd_key(-6)); - round(inv_rnd, b1, b0, rnd_key(-5)); - round(inv_rnd, b0, b1, rnd_key(-4)); - round(inv_rnd, b1, b0, rnd_key(-3)); - round(inv_rnd, b0, b1, rnd_key(-2)); - round(inv_rnd, b1, b0, rnd_key(-1)); - round(inv_lrnd, b0, b1, rnd_key( 0)); - //-fallthrough - } - -#else - -#if (DEC_UNROLL == PARTIAL) - { uint32_t rnd; - for(rnd = 0; rnd < (cx->inf.b[0] >> 5) - 1; ++rnd) - { - kp = rnd_key(1); - round(inv_rnd, b1, b0, kp); - kp = rnd_key(1); - round(inv_rnd, b0, b1, kp); - } - kp = rnd_key(1); - round(inv_rnd, b1, b0, kp); -#else - { uint32_t rnd; - for(rnd = 0; rnd < (cx->inf.b[0] >> 4) - 1; ++rnd) - { - kp = rnd_key(1); - round(inv_rnd, b1, b0, kp); - l_copy(b0, b1); - } -#endif - kp = rnd_key(1); - round(inv_lrnd, b0, b1, kp); - } -#endif - - state_out(out, b0); - return EXIT_SUCCESS; -} - -#endif - -#if defined(__cplusplus) -} -#endif diff --git a/trezor-crypto/crypto/aes/aeskey.c b/trezor-crypto/crypto/aes/aeskey.c deleted file mode 100644 index 74bc9eb6eab..00000000000 --- a/trezor-crypto/crypto/aes/aeskey.c +++ /dev/null @@ -1,561 +0,0 @@ -/* ---------------------------------------------------------------------------- -Copyright (c) 1998-2013, Brian Gladman, Worcester, UK. All rights reserved. - -The redistribution and use of this software (with or without changes) -is allowed without the payment of fees or royalties provided that: - - source code distributions include the above copyright notice, this - list of conditions and the following disclaimer; - - binary distributions include the above copyright notice, this list - of conditions and the following disclaimer in their documentation. - -This software is provided 'as is' with no explicit or implied warranties -in respect of its operation, including, but not limited to, correctness -and fitness for purpose. ---------------------------------------------------------------------------- -Issue Date: 20/12/2007 -*/ - -#include -#include -#include - -#if defined( USE_INTEL_AES_IF_PRESENT ) -# include "aes_ni.h" -#else -/* map names here to provide the external API ('name' -> 'aes_name') */ -# define aes_xi(x) aes_ ## x -#endif - -#ifdef USE_VIA_ACE_IF_PRESENT -# include "aes_via_ace.h" -#endif - -#if defined(__cplusplus) -extern "C" -{ -#endif - -/* Initialise the key schedule from the user supplied key. The key - length can be specified in bytes, with legal values of 16, 24 - and 32, or in bits, with legal values of 128, 192 and 256. These - values correspond with Nk values of 4, 6 and 8 respectively. - - The following macros implement a single cycle in the key - schedule generation process. The number of cycles needed - for each cx->n_col and nk value is: - - nk = 4 5 6 7 8 - ------------------------------ - cx->n_col = 4 10 9 8 7 7 - cx->n_col = 5 14 11 10 9 9 - cx->n_col = 6 19 15 12 11 11 - cx->n_col = 7 21 19 16 13 14 - cx->n_col = 8 29 23 19 17 14 -*/ - -#if defined( REDUCE_CODE_SIZE ) -# define ls_box ls_sub - uint32_t ls_sub(const uint32_t t, const uint32_t n); -# define inv_mcol im_sub - uint32_t im_sub(const uint32_t x); -# ifdef ENC_KS_UNROLL -# undef ENC_KS_UNROLL -# endif -# ifdef DEC_KS_UNROLL -# undef DEC_KS_UNROLL -# endif -#endif - -#if (FUNCS_IN_C & ENC_KEYING_IN_C) - -#if defined(AES_128) || defined( AES_VAR ) - -#define ke4(k,i) \ -{ k[4*(i)+4] = ss[0] ^= ls_box(ss[3],3) ^ t_use(r,c)[i]; \ - k[4*(i)+5] = ss[1] ^= ss[0]; \ - k[4*(i)+6] = ss[2] ^= ss[1]; \ - k[4*(i)+7] = ss[3] ^= ss[2]; \ -} - -AES_RETURN aes_xi(encrypt_key128)(const unsigned char *key, aes_encrypt_ctx cx[1]) -{ uint32_t ss[4]; - - cx->ks[0] = ss[0] = word_in(key, 0); - cx->ks[1] = ss[1] = word_in(key, 1); - cx->ks[2] = ss[2] = word_in(key, 2); - cx->ks[3] = ss[3] = word_in(key, 3); - -#ifdef ENC_KS_UNROLL - ke4(cx->ks, 0); ke4(cx->ks, 1); - ke4(cx->ks, 2); ke4(cx->ks, 3); - ke4(cx->ks, 4); ke4(cx->ks, 5); - ke4(cx->ks, 6); ke4(cx->ks, 7); - ke4(cx->ks, 8); -#else - { uint32_t i; - for(i = 0; i < 9; ++i) - ke4(cx->ks, i); - } -#endif - ke4(cx->ks, 9); - cx->inf.l = 0; - cx->inf.b[0] = 10 * AES_BLOCK_SIZE; - -#ifdef USE_VIA_ACE_IF_PRESENT - if(VIA_ACE_AVAILABLE) - cx->inf.b[1] = 0xff; -#endif - return EXIT_SUCCESS; -} - -#endif - -#if defined(AES_192) || defined( AES_VAR ) - -#define kef6(k,i) \ -{ k[6*(i)+ 6] = ss[0] ^= ls_box(ss[5],3) ^ t_use(r,c)[i]; \ - k[6*(i)+ 7] = ss[1] ^= ss[0]; \ - k[6*(i)+ 8] = ss[2] ^= ss[1]; \ - k[6*(i)+ 9] = ss[3] ^= ss[2]; \ -} - -#define ke6(k,i) \ -{ kef6(k,i); \ - k[6*(i)+10] = ss[4] ^= ss[3]; \ - k[6*(i)+11] = ss[5] ^= ss[4]; \ -} - -AES_RETURN aes_xi(encrypt_key192)(const unsigned char *key, aes_encrypt_ctx cx[1]) -{ uint32_t ss[6]; - - cx->ks[0] = ss[0] = word_in(key, 0); - cx->ks[1] = ss[1] = word_in(key, 1); - cx->ks[2] = ss[2] = word_in(key, 2); - cx->ks[3] = ss[3] = word_in(key, 3); - cx->ks[4] = ss[4] = word_in(key, 4); - cx->ks[5] = ss[5] = word_in(key, 5); - -#ifdef ENC_KS_UNROLL - ke6(cx->ks, 0); ke6(cx->ks, 1); - ke6(cx->ks, 2); ke6(cx->ks, 3); - ke6(cx->ks, 4); ke6(cx->ks, 5); - ke6(cx->ks, 6); -#else - { uint32_t i; - for(i = 0; i < 7; ++i) - ke6(cx->ks, i); - } -#endif - kef6(cx->ks, 7); - cx->inf.l = 0; - cx->inf.b[0] = 12 * AES_BLOCK_SIZE; - -#ifdef USE_VIA_ACE_IF_PRESENT - if(VIA_ACE_AVAILABLE) - cx->inf.b[1] = 0xff; -#endif - return EXIT_SUCCESS; -} - -#endif - -#if defined(AES_256) || defined( AES_VAR ) - -#define kef8(k,i) \ -{ k[8*(i)+ 8] = ss[0] ^= ls_box(ss[7],3) ^ t_use(r,c)[i]; \ - k[8*(i)+ 9] = ss[1] ^= ss[0]; \ - k[8*(i)+10] = ss[2] ^= ss[1]; \ - k[8*(i)+11] = ss[3] ^= ss[2]; \ -} - -#define ke8(k,i) \ -{ kef8(k,i); \ - k[8*(i)+12] = ss[4] ^= ls_box(ss[3],0); \ - k[8*(i)+13] = ss[5] ^= ss[4]; \ - k[8*(i)+14] = ss[6] ^= ss[5]; \ - k[8*(i)+15] = ss[7] ^= ss[6]; \ -} - -AES_RETURN aes_xi(encrypt_key256)(const unsigned char *key, aes_encrypt_ctx cx[1]) -{ uint32_t ss[8]; - - cx->ks[0] = ss[0] = word_in(key, 0); - cx->ks[1] = ss[1] = word_in(key, 1); - cx->ks[2] = ss[2] = word_in(key, 2); - cx->ks[3] = ss[3] = word_in(key, 3); - cx->ks[4] = ss[4] = word_in(key, 4); - cx->ks[5] = ss[5] = word_in(key, 5); - cx->ks[6] = ss[6] = word_in(key, 6); - cx->ks[7] = ss[7] = word_in(key, 7); - -#ifdef ENC_KS_UNROLL - ke8(cx->ks, 0); ke8(cx->ks, 1); - ke8(cx->ks, 2); ke8(cx->ks, 3); - ke8(cx->ks, 4); ke8(cx->ks, 5); -#else - { uint32_t i; - for(i = 0; i < 6; ++i) - ke8(cx->ks, i); - } -#endif - kef8(cx->ks, 6); - cx->inf.l = 0; - cx->inf.b[0] = 14 * AES_BLOCK_SIZE; - -#ifdef USE_VIA_ACE_IF_PRESENT - if(VIA_ACE_AVAILABLE) - cx->inf.b[1] = 0xff; -#endif - return EXIT_SUCCESS; -} - -#endif - -#endif - -#if (FUNCS_IN_C & DEC_KEYING_IN_C) - -/* this is used to store the decryption round keys */ -/* in forward or reverse order */ - -#ifdef AES_REV_DKS -#define v(n,i) ((n) - (i) + 2 * ((i) & 3)) -#else -#define v(n,i) (i) -#endif - -#if DEC_ROUND == NO_TABLES -#define ff(x) (x) -#else -#define ff(x) inv_mcol(x) -#if defined( dec_imvars ) -#define d_vars dec_imvars -#endif -#endif - -#if defined(AES_128) || defined( AES_VAR ) - -#define k4e(k,i) \ -{ k[v(40,(4*(i))+4)] = ss[0] ^= ls_box(ss[3],3) ^ t_use(r,c)[i]; \ - k[v(40,(4*(i))+5)] = ss[1] ^= ss[0]; \ - k[v(40,(4*(i))+6)] = ss[2] ^= ss[1]; \ - k[v(40,(4*(i))+7)] = ss[3] ^= ss[2]; \ -} - -#if 1 - -#define kdf4(k,i) \ -{ ss[0] = ss[0] ^ ss[2] ^ ss[1] ^ ss[3]; \ - ss[1] = ss[1] ^ ss[3]; \ - ss[2] = ss[2] ^ ss[3]; \ - ss[4] = ls_box(ss[(i+3) % 4], 3) ^ t_use(r,c)[i]; \ - ss[i % 4] ^= ss[4]; \ - ss[4] ^= k[v(40,(4*(i)))]; k[v(40,(4*(i))+4)] = ff(ss[4]); \ - ss[4] ^= k[v(40,(4*(i))+1)]; k[v(40,(4*(i))+5)] = ff(ss[4]); \ - ss[4] ^= k[v(40,(4*(i))+2)]; k[v(40,(4*(i))+6)] = ff(ss[4]); \ - ss[4] ^= k[v(40,(4*(i))+3)]; k[v(40,(4*(i))+7)] = ff(ss[4]); \ -} - -#define kd4(k,i) \ -{ ss[4] = ls_box(ss[(i+3) % 4], 3) ^ t_use(r,c)[i]; \ - ss[i % 4] ^= ss[4]; ss[4] = ff(ss[4]); \ - k[v(40,(4*(i))+4)] = ss[4] ^= k[v(40,(4*(i)))]; \ - k[v(40,(4*(i))+5)] = ss[4] ^= k[v(40,(4*(i))+1)]; \ - k[v(40,(4*(i))+6)] = ss[4] ^= k[v(40,(4*(i))+2)]; \ - k[v(40,(4*(i))+7)] = ss[4] ^= k[v(40,(4*(i))+3)]; \ -} - -#define kdl4(k,i) \ -{ ss[4] = ls_box(ss[(i+3) % 4], 3) ^ t_use(r,c)[i]; ss[i % 4] ^= ss[4]; \ - k[v(40,(4*(i))+4)] = (ss[0] ^= ss[1]) ^ ss[2] ^ ss[3]; \ - k[v(40,(4*(i))+5)] = ss[1] ^ ss[3]; \ - k[v(40,(4*(i))+6)] = ss[0]; \ - k[v(40,(4*(i))+7)] = ss[1]; \ -} - -#else - -#define kdf4(k,i) \ -{ ss[0] ^= ls_box(ss[3],3) ^ t_use(r,c)[i]; k[v(40,(4*(i))+ 4)] = ff(ss[0]); \ - ss[1] ^= ss[0]; k[v(40,(4*(i))+ 5)] = ff(ss[1]); \ - ss[2] ^= ss[1]; k[v(40,(4*(i))+ 6)] = ff(ss[2]); \ - ss[3] ^= ss[2]; k[v(40,(4*(i))+ 7)] = ff(ss[3]); \ -} - -#define kd4(k,i) \ -{ ss[4] = ls_box(ss[3],3) ^ t_use(r,c)[i]; \ - ss[0] ^= ss[4]; ss[4] = ff(ss[4]); k[v(40,(4*(i))+ 4)] = ss[4] ^= k[v(40,(4*(i)))]; \ - ss[1] ^= ss[0]; k[v(40,(4*(i))+ 5)] = ss[4] ^= k[v(40,(4*(i))+ 1)]; \ - ss[2] ^= ss[1]; k[v(40,(4*(i))+ 6)] = ss[4] ^= k[v(40,(4*(i))+ 2)]; \ - ss[3] ^= ss[2]; k[v(40,(4*(i))+ 7)] = ss[4] ^= k[v(40,(4*(i))+ 3)]; \ -} - -#define kdl4(k,i) \ -{ ss[0] ^= ls_box(ss[3],3) ^ t_use(r,c)[i]; k[v(40,(4*(i))+ 4)] = ss[0]; \ - ss[1] ^= ss[0]; k[v(40,(4*(i))+ 5)] = ss[1]; \ - ss[2] ^= ss[1]; k[v(40,(4*(i))+ 6)] = ss[2]; \ - ss[3] ^= ss[2]; k[v(40,(4*(i))+ 7)] = ss[3]; \ -} - -#endif - -AES_RETURN aes_xi(decrypt_key128)(const unsigned char *key, aes_decrypt_ctx cx[1]) -{ uint32_t ss[5]; -#if defined( d_vars ) - d_vars; -#endif - - cx->ks[v(40,(0))] = ss[0] = word_in(key, 0); - cx->ks[v(40,(1))] = ss[1] = word_in(key, 1); - cx->ks[v(40,(2))] = ss[2] = word_in(key, 2); - cx->ks[v(40,(3))] = ss[3] = word_in(key, 3); - -#ifdef DEC_KS_UNROLL - kdf4(cx->ks, 0); kd4(cx->ks, 1); - kd4(cx->ks, 2); kd4(cx->ks, 3); - kd4(cx->ks, 4); kd4(cx->ks, 5); - kd4(cx->ks, 6); kd4(cx->ks, 7); - kd4(cx->ks, 8); kdl4(cx->ks, 9); -#else - { uint32_t i; - for(i = 0; i < 10; ++i) - k4e(cx->ks, i); -#if !(DEC_ROUND == NO_TABLES) - for(i = N_COLS; i < 10 * N_COLS; ++i) - cx->ks[i] = inv_mcol(cx->ks[i]); -#endif - } -#endif - cx->inf.l = 0; - cx->inf.b[0] = 10 * AES_BLOCK_SIZE; - -#ifdef USE_VIA_ACE_IF_PRESENT - if(VIA_ACE_AVAILABLE) - cx->inf.b[1] = 0xff; -#endif - return EXIT_SUCCESS; -} - -#endif - -#if defined(AES_192) || defined( AES_VAR ) - -#define k6ef(k,i) \ -{ k[v(48,(6*(i))+ 6)] = ss[0] ^= ls_box(ss[5],3) ^ t_use(r,c)[i]; \ - k[v(48,(6*(i))+ 7)] = ss[1] ^= ss[0]; \ - k[v(48,(6*(i))+ 8)] = ss[2] ^= ss[1]; \ - k[v(48,(6*(i))+ 9)] = ss[3] ^= ss[2]; \ -} - -#define k6e(k,i) \ -{ k6ef(k,i); \ - k[v(48,(6*(i))+10)] = ss[4] ^= ss[3]; \ - k[v(48,(6*(i))+11)] = ss[5] ^= ss[4]; \ -} - -#define kdf6(k,i) \ -{ ss[0] ^= ls_box(ss[5],3) ^ t_use(r,c)[i]; k[v(48,(6*(i))+ 6)] = ff(ss[0]); \ - ss[1] ^= ss[0]; k[v(48,(6*(i))+ 7)] = ff(ss[1]); \ - ss[2] ^= ss[1]; k[v(48,(6*(i))+ 8)] = ff(ss[2]); \ - ss[3] ^= ss[2]; k[v(48,(6*(i))+ 9)] = ff(ss[3]); \ - ss[4] ^= ss[3]; k[v(48,(6*(i))+10)] = ff(ss[4]); \ - ss[5] ^= ss[4]; k[v(48,(6*(i))+11)] = ff(ss[5]); \ -} - -#define kd6(k,i) \ -{ ss[6] = ls_box(ss[5],3) ^ t_use(r,c)[i]; \ - ss[0] ^= ss[6]; ss[6] = ff(ss[6]); k[v(48,(6*(i))+ 6)] = ss[6] ^= k[v(48,(6*(i)))]; \ - ss[1] ^= ss[0]; k[v(48,(6*(i))+ 7)] = ss[6] ^= k[v(48,(6*(i))+ 1)]; \ - ss[2] ^= ss[1]; k[v(48,(6*(i))+ 8)] = ss[6] ^= k[v(48,(6*(i))+ 2)]; \ - ss[3] ^= ss[2]; k[v(48,(6*(i))+ 9)] = ss[6] ^= k[v(48,(6*(i))+ 3)]; \ - ss[4] ^= ss[3]; k[v(48,(6*(i))+10)] = ss[6] ^= k[v(48,(6*(i))+ 4)]; \ - ss[5] ^= ss[4]; k[v(48,(6*(i))+11)] = ss[6] ^= k[v(48,(6*(i))+ 5)]; \ -} - -#define kdl6(k,i) \ -{ ss[0] ^= ls_box(ss[5],3) ^ t_use(r,c)[i]; k[v(48,(6*(i))+ 6)] = ss[0]; \ - ss[1] ^= ss[0]; k[v(48,(6*(i))+ 7)] = ss[1]; \ - ss[2] ^= ss[1]; k[v(48,(6*(i))+ 8)] = ss[2]; \ - ss[3] ^= ss[2]; k[v(48,(6*(i))+ 9)] = ss[3]; \ -} - -AES_RETURN aes_xi(decrypt_key192)(const unsigned char *key, aes_decrypt_ctx cx[1]) -{ uint32_t ss[7]; -#if defined( d_vars ) - d_vars; -#endif - - cx->ks[v(48,(0))] = ss[0] = word_in(key, 0); - cx->ks[v(48,(1))] = ss[1] = word_in(key, 1); - cx->ks[v(48,(2))] = ss[2] = word_in(key, 2); - cx->ks[v(48,(3))] = ss[3] = word_in(key, 3); - -#ifdef DEC_KS_UNROLL - ss[4] = word_in(key, 4); - ss[5] = word_in(key, 5); - cx->ks[v(48,(4))] = ff(ss[4]); - cx->ks[v(48,(5))] = ff(ss[5]); - kdf6(cx->ks, 0); kd6(cx->ks, 1); - kd6(cx->ks, 2); kd6(cx->ks, 3); - kd6(cx->ks, 4); kd6(cx->ks, 5); - kd6(cx->ks, 6); kdl6(cx->ks, 7); -#else - cx->ks[v(48,(4))] = ss[4] = word_in(key, 4); - cx->ks[v(48,(5))] = ss[5] = word_in(key, 5); - { uint32_t i; - - for(i = 0; i < 7; ++i) - k6e(cx->ks, i); - k6ef(cx->ks, 7); -#if !(DEC_ROUND == NO_TABLES) - for(i = N_COLS; i < 12 * N_COLS; ++i) - cx->ks[i] = inv_mcol(cx->ks[i]); -#endif - } -#endif - cx->inf.l = 0; - cx->inf.b[0] = 12 * AES_BLOCK_SIZE; - -#ifdef USE_VIA_ACE_IF_PRESENT - if(VIA_ACE_AVAILABLE) - cx->inf.b[1] = 0xff; -#endif - return EXIT_SUCCESS; -} - -#endif - -#if defined(AES_256) || defined( AES_VAR ) - -#define k8ef(k,i) \ -{ k[v(56,(8*(i))+ 8)] = ss[0] ^= ls_box(ss[7],3) ^ t_use(r,c)[i]; \ - k[v(56,(8*(i))+ 9)] = ss[1] ^= ss[0]; \ - k[v(56,(8*(i))+10)] = ss[2] ^= ss[1]; \ - k[v(56,(8*(i))+11)] = ss[3] ^= ss[2]; \ -} - -#define k8e(k,i) \ -{ k8ef(k,i); \ - k[v(56,(8*(i))+12)] = ss[4] ^= ls_box(ss[3],0); \ - k[v(56,(8*(i))+13)] = ss[5] ^= ss[4]; \ - k[v(56,(8*(i))+14)] = ss[6] ^= ss[5]; \ - k[v(56,(8*(i))+15)] = ss[7] ^= ss[6]; \ -} - -#define kdf8(k,i) \ -{ ss[0] ^= ls_box(ss[7],3) ^ t_use(r,c)[i]; k[v(56,(8*(i))+ 8)] = ff(ss[0]); \ - ss[1] ^= ss[0]; k[v(56,(8*(i))+ 9)] = ff(ss[1]); \ - ss[2] ^= ss[1]; k[v(56,(8*(i))+10)] = ff(ss[2]); \ - ss[3] ^= ss[2]; k[v(56,(8*(i))+11)] = ff(ss[3]); \ - ss[4] ^= ls_box(ss[3],0); k[v(56,(8*(i))+12)] = ff(ss[4]); \ - ss[5] ^= ss[4]; k[v(56,(8*(i))+13)] = ff(ss[5]); \ - ss[6] ^= ss[5]; k[v(56,(8*(i))+14)] = ff(ss[6]); \ - ss[7] ^= ss[6]; k[v(56,(8*(i))+15)] = ff(ss[7]); \ -} - -#define kd8(k,i) \ -{ ss[8] = ls_box(ss[7],3) ^ t_use(r,c)[i]; \ - ss[0] ^= ss[8]; ss[8] = ff(ss[8]); k[v(56,(8*(i))+ 8)] = ss[8] ^= k[v(56,(8*(i)))]; \ - ss[1] ^= ss[0]; k[v(56,(8*(i))+ 9)] = ss[8] ^= k[v(56,(8*(i))+ 1)]; \ - ss[2] ^= ss[1]; k[v(56,(8*(i))+10)] = ss[8] ^= k[v(56,(8*(i))+ 2)]; \ - ss[3] ^= ss[2]; k[v(56,(8*(i))+11)] = ss[8] ^= k[v(56,(8*(i))+ 3)]; \ - ss[8] = ls_box(ss[3],0); \ - ss[4] ^= ss[8]; ss[8] = ff(ss[8]); k[v(56,(8*(i))+12)] = ss[8] ^= k[v(56,(8*(i))+ 4)]; \ - ss[5] ^= ss[4]; k[v(56,(8*(i))+13)] = ss[8] ^= k[v(56,(8*(i))+ 5)]; \ - ss[6] ^= ss[5]; k[v(56,(8*(i))+14)] = ss[8] ^= k[v(56,(8*(i))+ 6)]; \ - ss[7] ^= ss[6]; k[v(56,(8*(i))+15)] = ss[8] ^= k[v(56,(8*(i))+ 7)]; \ -} - -#define kdl8(k,i) \ -{ ss[0] ^= ls_box(ss[7],3) ^ t_use(r,c)[i]; k[v(56,(8*(i))+ 8)] = ss[0]; \ - ss[1] ^= ss[0]; k[v(56,(8*(i))+ 9)] = ss[1]; \ - ss[2] ^= ss[1]; k[v(56,(8*(i))+10)] = ss[2]; \ - ss[3] ^= ss[2]; k[v(56,(8*(i))+11)] = ss[3]; \ -} - -AES_RETURN aes_xi(decrypt_key256)(const unsigned char *key, aes_decrypt_ctx cx[1]) -{ uint32_t ss[9]; -#if defined( d_vars ) - d_vars; -#endif - - cx->ks[v(56,(0))] = ss[0] = word_in(key, 0); - cx->ks[v(56,(1))] = ss[1] = word_in(key, 1); - cx->ks[v(56,(2))] = ss[2] = word_in(key, 2); - cx->ks[v(56,(3))] = ss[3] = word_in(key, 3); - -#ifdef DEC_KS_UNROLL - ss[4] = word_in(key, 4); - ss[5] = word_in(key, 5); - ss[6] = word_in(key, 6); - ss[7] = word_in(key, 7); - cx->ks[v(56,(4))] = ff(ss[4]); - cx->ks[v(56,(5))] = ff(ss[5]); - cx->ks[v(56,(6))] = ff(ss[6]); - cx->ks[v(56,(7))] = ff(ss[7]); - kdf8(cx->ks, 0); kd8(cx->ks, 1); - kd8(cx->ks, 2); kd8(cx->ks, 3); - kd8(cx->ks, 4); kd8(cx->ks, 5); - kdl8(cx->ks, 6); -#else - cx->ks[v(56,(4))] = ss[4] = word_in(key, 4); - cx->ks[v(56,(5))] = ss[5] = word_in(key, 5); - cx->ks[v(56,(6))] = ss[6] = word_in(key, 6); - cx->ks[v(56,(7))] = ss[7] = word_in(key, 7); - { uint32_t i; - - for(i = 0; i < 6; ++i) - k8e(cx->ks, i); - k8ef(cx->ks, 6); -#if !(DEC_ROUND == NO_TABLES) - for(i = N_COLS; i < 14 * N_COLS; ++i) - cx->ks[i] = inv_mcol(cx->ks[i]); -#endif - } -#endif - cx->inf.l = 0; - cx->inf.b[0] = 14 * AES_BLOCK_SIZE; - -#ifdef USE_VIA_ACE_IF_PRESENT - if(VIA_ACE_AVAILABLE) - cx->inf.b[1] = 0xff; -#endif - return EXIT_SUCCESS; -} - -#endif - -#endif - -#if defined( AES_VAR ) - -AES_RETURN aes_encrypt_key(const unsigned char *key, int key_len, aes_encrypt_ctx cx[1]) -{ - switch(key_len) - { - case 16: case 128: return aes_encrypt_key128(key, cx); - case 24: case 192: return aes_encrypt_key192(key, cx); - case 32: case 256: return aes_encrypt_key256(key, cx); - default: return EXIT_FAILURE; - } -} - -AES_RETURN aes_decrypt_key(const unsigned char *key, int key_len, aes_decrypt_ctx cx[1]) -{ - switch(key_len) - { - case 16: case 128: return aes_decrypt_key128(key, cx); - case 24: case 192: return aes_decrypt_key192(key, cx); - case 32: case 256: return aes_decrypt_key256(key, cx); - default: return EXIT_FAILURE; - } -} - -#endif - -#if defined(__cplusplus) -} -#endif diff --git a/trezor-crypto/crypto/aes/aestab.c b/trezor-crypto/crypto/aes/aestab.c deleted file mode 100644 index 8d63a800e94..00000000000 --- a/trezor-crypto/crypto/aes/aestab.c +++ /dev/null @@ -1,418 +0,0 @@ -/* ---------------------------------------------------------------------------- -Copyright (c) 1998-2013, Brian Gladman, Worcester, UK. All rights reserved. - -The redistribution and use of this software (with or without changes) -is allowed without the payment of fees or royalties provided that: - - source code distributions include the above copyright notice, this - list of conditions and the following disclaimer; - - binary distributions include the above copyright notice, this list - of conditions and the following disclaimer in their documentation. - -This software is provided 'as is' with no explicit or implied warranties -in respect of its operation, including, but not limited to, correctness -and fitness for purpose. ---------------------------------------------------------------------------- -Issue Date: 20/12/2007 -*/ - -#define DO_TABLES - -#include -#include - -#if defined(STATIC_TABLES) - -#define sb_data(w) {\ - w(0x63), w(0x7c), w(0x77), w(0x7b), w(0xf2), w(0x6b), w(0x6f), w(0xc5),\ - w(0x30), w(0x01), w(0x67), w(0x2b), w(0xfe), w(0xd7), w(0xab), w(0x76),\ - w(0xca), w(0x82), w(0xc9), w(0x7d), w(0xfa), w(0x59), w(0x47), w(0xf0),\ - w(0xad), w(0xd4), w(0xa2), w(0xaf), w(0x9c), w(0xa4), w(0x72), w(0xc0),\ - w(0xb7), w(0xfd), w(0x93), w(0x26), w(0x36), w(0x3f), w(0xf7), w(0xcc),\ - w(0x34), w(0xa5), w(0xe5), w(0xf1), w(0x71), w(0xd8), w(0x31), w(0x15),\ - w(0x04), w(0xc7), w(0x23), w(0xc3), w(0x18), w(0x96), w(0x05), w(0x9a),\ - w(0x07), w(0x12), w(0x80), w(0xe2), w(0xeb), w(0x27), w(0xb2), w(0x75),\ - w(0x09), w(0x83), w(0x2c), w(0x1a), w(0x1b), w(0x6e), w(0x5a), w(0xa0),\ - w(0x52), w(0x3b), w(0xd6), w(0xb3), w(0x29), w(0xe3), w(0x2f), w(0x84),\ - w(0x53), w(0xd1), w(0x00), w(0xed), w(0x20), w(0xfc), w(0xb1), w(0x5b),\ - w(0x6a), w(0xcb), w(0xbe), w(0x39), w(0x4a), w(0x4c), w(0x58), w(0xcf),\ - w(0xd0), w(0xef), w(0xaa), w(0xfb), w(0x43), w(0x4d), w(0x33), w(0x85),\ - w(0x45), w(0xf9), w(0x02), w(0x7f), w(0x50), w(0x3c), w(0x9f), w(0xa8),\ - w(0x51), w(0xa3), w(0x40), w(0x8f), w(0x92), w(0x9d), w(0x38), w(0xf5),\ - w(0xbc), w(0xb6), w(0xda), w(0x21), w(0x10), w(0xff), w(0xf3), w(0xd2),\ - w(0xcd), w(0x0c), w(0x13), w(0xec), w(0x5f), w(0x97), w(0x44), w(0x17),\ - w(0xc4), w(0xa7), w(0x7e), w(0x3d), w(0x64), w(0x5d), w(0x19), w(0x73),\ - w(0x60), w(0x81), w(0x4f), w(0xdc), w(0x22), w(0x2a), w(0x90), w(0x88),\ - w(0x46), w(0xee), w(0xb8), w(0x14), w(0xde), w(0x5e), w(0x0b), w(0xdb),\ - w(0xe0), w(0x32), w(0x3a), w(0x0a), w(0x49), w(0x06), w(0x24), w(0x5c),\ - w(0xc2), w(0xd3), w(0xac), w(0x62), w(0x91), w(0x95), w(0xe4), w(0x79),\ - w(0xe7), w(0xc8), w(0x37), w(0x6d), w(0x8d), w(0xd5), w(0x4e), w(0xa9),\ - w(0x6c), w(0x56), w(0xf4), w(0xea), w(0x65), w(0x7a), w(0xae), w(0x08),\ - w(0xba), w(0x78), w(0x25), w(0x2e), w(0x1c), w(0xa6), w(0xb4), w(0xc6),\ - w(0xe8), w(0xdd), w(0x74), w(0x1f), w(0x4b), w(0xbd), w(0x8b), w(0x8a),\ - w(0x70), w(0x3e), w(0xb5), w(0x66), w(0x48), w(0x03), w(0xf6), w(0x0e),\ - w(0x61), w(0x35), w(0x57), w(0xb9), w(0x86), w(0xc1), w(0x1d), w(0x9e),\ - w(0xe1), w(0xf8), w(0x98), w(0x11), w(0x69), w(0xd9), w(0x8e), w(0x94),\ - w(0x9b), w(0x1e), w(0x87), w(0xe9), w(0xce), w(0x55), w(0x28), w(0xdf),\ - w(0x8c), w(0xa1), w(0x89), w(0x0d), w(0xbf), w(0xe6), w(0x42), w(0x68),\ - w(0x41), w(0x99), w(0x2d), w(0x0f), w(0xb0), w(0x54), w(0xbb), w(0x16) } - -#define isb_data(w) {\ - w(0x52), w(0x09), w(0x6a), w(0xd5), w(0x30), w(0x36), w(0xa5), w(0x38),\ - w(0xbf), w(0x40), w(0xa3), w(0x9e), w(0x81), w(0xf3), w(0xd7), w(0xfb),\ - w(0x7c), w(0xe3), w(0x39), w(0x82), w(0x9b), w(0x2f), w(0xff), w(0x87),\ - w(0x34), w(0x8e), w(0x43), w(0x44), w(0xc4), w(0xde), w(0xe9), w(0xcb),\ - w(0x54), w(0x7b), w(0x94), w(0x32), w(0xa6), w(0xc2), w(0x23), w(0x3d),\ - w(0xee), w(0x4c), w(0x95), w(0x0b), w(0x42), w(0xfa), w(0xc3), w(0x4e),\ - w(0x08), w(0x2e), w(0xa1), w(0x66), w(0x28), w(0xd9), w(0x24), w(0xb2),\ - w(0x76), w(0x5b), w(0xa2), w(0x49), w(0x6d), w(0x8b), w(0xd1), w(0x25),\ - w(0x72), w(0xf8), w(0xf6), w(0x64), w(0x86), w(0x68), w(0x98), w(0x16),\ - w(0xd4), w(0xa4), w(0x5c), w(0xcc), w(0x5d), w(0x65), w(0xb6), w(0x92),\ - w(0x6c), w(0x70), w(0x48), w(0x50), w(0xfd), w(0xed), w(0xb9), w(0xda),\ - w(0x5e), w(0x15), w(0x46), w(0x57), w(0xa7), w(0x8d), w(0x9d), w(0x84),\ - w(0x90), w(0xd8), w(0xab), w(0x00), w(0x8c), w(0xbc), w(0xd3), w(0x0a),\ - w(0xf7), w(0xe4), w(0x58), w(0x05), w(0xb8), w(0xb3), w(0x45), w(0x06),\ - w(0xd0), w(0x2c), w(0x1e), w(0x8f), w(0xca), w(0x3f), w(0x0f), w(0x02),\ - w(0xc1), w(0xaf), w(0xbd), w(0x03), w(0x01), w(0x13), w(0x8a), w(0x6b),\ - w(0x3a), w(0x91), w(0x11), w(0x41), w(0x4f), w(0x67), w(0xdc), w(0xea),\ - w(0x97), w(0xf2), w(0xcf), w(0xce), w(0xf0), w(0xb4), w(0xe6), w(0x73),\ - w(0x96), w(0xac), w(0x74), w(0x22), w(0xe7), w(0xad), w(0x35), w(0x85),\ - w(0xe2), w(0xf9), w(0x37), w(0xe8), w(0x1c), w(0x75), w(0xdf), w(0x6e),\ - w(0x47), w(0xf1), w(0x1a), w(0x71), w(0x1d), w(0x29), w(0xc5), w(0x89),\ - w(0x6f), w(0xb7), w(0x62), w(0x0e), w(0xaa), w(0x18), w(0xbe), w(0x1b),\ - w(0xfc), w(0x56), w(0x3e), w(0x4b), w(0xc6), w(0xd2), w(0x79), w(0x20),\ - w(0x9a), w(0xdb), w(0xc0), w(0xfe), w(0x78), w(0xcd), w(0x5a), w(0xf4),\ - w(0x1f), w(0xdd), w(0xa8), w(0x33), w(0x88), w(0x07), w(0xc7), w(0x31),\ - w(0xb1), w(0x12), w(0x10), w(0x59), w(0x27), w(0x80), w(0xec), w(0x5f),\ - w(0x60), w(0x51), w(0x7f), w(0xa9), w(0x19), w(0xb5), w(0x4a), w(0x0d),\ - w(0x2d), w(0xe5), w(0x7a), w(0x9f), w(0x93), w(0xc9), w(0x9c), w(0xef),\ - w(0xa0), w(0xe0), w(0x3b), w(0x4d), w(0xae), w(0x2a), w(0xf5), w(0xb0),\ - w(0xc8), w(0xeb), w(0xbb), w(0x3c), w(0x83), w(0x53), w(0x99), w(0x61),\ - w(0x17), w(0x2b), w(0x04), w(0x7e), w(0xba), w(0x77), w(0xd6), w(0x26),\ - w(0xe1), w(0x69), w(0x14), w(0x63), w(0x55), w(0x21), w(0x0c), w(0x7d) } - -#define mm_data(w) {\ - w(0x00), w(0x01), w(0x02), w(0x03), w(0x04), w(0x05), w(0x06), w(0x07),\ - w(0x08), w(0x09), w(0x0a), w(0x0b), w(0x0c), w(0x0d), w(0x0e), w(0x0f),\ - w(0x10), w(0x11), w(0x12), w(0x13), w(0x14), w(0x15), w(0x16), w(0x17),\ - w(0x18), w(0x19), w(0x1a), w(0x1b), w(0x1c), w(0x1d), w(0x1e), w(0x1f),\ - w(0x20), w(0x21), w(0x22), w(0x23), w(0x24), w(0x25), w(0x26), w(0x27),\ - w(0x28), w(0x29), w(0x2a), w(0x2b), w(0x2c), w(0x2d), w(0x2e), w(0x2f),\ - w(0x30), w(0x31), w(0x32), w(0x33), w(0x34), w(0x35), w(0x36), w(0x37),\ - w(0x38), w(0x39), w(0x3a), w(0x3b), w(0x3c), w(0x3d), w(0x3e), w(0x3f),\ - w(0x40), w(0x41), w(0x42), w(0x43), w(0x44), w(0x45), w(0x46), w(0x47),\ - w(0x48), w(0x49), w(0x4a), w(0x4b), w(0x4c), w(0x4d), w(0x4e), w(0x4f),\ - w(0x50), w(0x51), w(0x52), w(0x53), w(0x54), w(0x55), w(0x56), w(0x57),\ - w(0x58), w(0x59), w(0x5a), w(0x5b), w(0x5c), w(0x5d), w(0x5e), w(0x5f),\ - w(0x60), w(0x61), w(0x62), w(0x63), w(0x64), w(0x65), w(0x66), w(0x67),\ - w(0x68), w(0x69), w(0x6a), w(0x6b), w(0x6c), w(0x6d), w(0x6e), w(0x6f),\ - w(0x70), w(0x71), w(0x72), w(0x73), w(0x74), w(0x75), w(0x76), w(0x77),\ - w(0x78), w(0x79), w(0x7a), w(0x7b), w(0x7c), w(0x7d), w(0x7e), w(0x7f),\ - w(0x80), w(0x81), w(0x82), w(0x83), w(0x84), w(0x85), w(0x86), w(0x87),\ - w(0x88), w(0x89), w(0x8a), w(0x8b), w(0x8c), w(0x8d), w(0x8e), w(0x8f),\ - w(0x90), w(0x91), w(0x92), w(0x93), w(0x94), w(0x95), w(0x96), w(0x97),\ - w(0x98), w(0x99), w(0x9a), w(0x9b), w(0x9c), w(0x9d), w(0x9e), w(0x9f),\ - w(0xa0), w(0xa1), w(0xa2), w(0xa3), w(0xa4), w(0xa5), w(0xa6), w(0xa7),\ - w(0xa8), w(0xa9), w(0xaa), w(0xab), w(0xac), w(0xad), w(0xae), w(0xaf),\ - w(0xb0), w(0xb1), w(0xb2), w(0xb3), w(0xb4), w(0xb5), w(0xb6), w(0xb7),\ - w(0xb8), w(0xb9), w(0xba), w(0xbb), w(0xbc), w(0xbd), w(0xbe), w(0xbf),\ - w(0xc0), w(0xc1), w(0xc2), w(0xc3), w(0xc4), w(0xc5), w(0xc6), w(0xc7),\ - w(0xc8), w(0xc9), w(0xca), w(0xcb), w(0xcc), w(0xcd), w(0xce), w(0xcf),\ - w(0xd0), w(0xd1), w(0xd2), w(0xd3), w(0xd4), w(0xd5), w(0xd6), w(0xd7),\ - w(0xd8), w(0xd9), w(0xda), w(0xdb), w(0xdc), w(0xdd), w(0xde), w(0xdf),\ - w(0xe0), w(0xe1), w(0xe2), w(0xe3), w(0xe4), w(0xe5), w(0xe6), w(0xe7),\ - w(0xe8), w(0xe9), w(0xea), w(0xeb), w(0xec), w(0xed), w(0xee), w(0xef),\ - w(0xf0), w(0xf1), w(0xf2), w(0xf3), w(0xf4), w(0xf5), w(0xf6), w(0xf7),\ - w(0xf8), w(0xf9), w(0xfa), w(0xfb), w(0xfc), w(0xfd), w(0xfe), w(0xff) } - -#define rc_data(w) {\ - w(0x01), w(0x02), w(0x04), w(0x08), w(0x10),w(0x20), w(0x40), w(0x80),\ - w(0x1b), w(0x36) } - -#define h0(x) (x) - -#define w0(p) bytes2word(p, 0, 0, 0) -#define w1(p) bytes2word(0, p, 0, 0) -#define w2(p) bytes2word(0, 0, p, 0) -#define w3(p) bytes2word(0, 0, 0, p) - -#define u0(p) bytes2word(f2(p), p, p, f3(p)) -#define u1(p) bytes2word(f3(p), f2(p), p, p) -#define u2(p) bytes2word(p, f3(p), f2(p), p) -#define u3(p) bytes2word(p, p, f3(p), f2(p)) - -#define v0(p) bytes2word(fe(p), f9(p), fd(p), fb(p)) -#define v1(p) bytes2word(fb(p), fe(p), f9(p), fd(p)) -#define v2(p) bytes2word(fd(p), fb(p), fe(p), f9(p)) -#define v3(p) bytes2word(f9(p), fd(p), fb(p), fe(p)) - -#endif - -#if defined(STATIC_TABLES) || !defined(FF_TABLES) - -#define f2(x) ((x<<1) ^ (((x>>7) & 1) * WPOLY)) -#define f4(x) ((x<<2) ^ (((x>>6) & 1) * WPOLY) ^ (((x>>6) & 2) * WPOLY)) -#define f8(x) ((x<<3) ^ (((x>>5) & 1) * WPOLY) ^ (((x>>5) & 2) * WPOLY) \ - ^ (((x>>5) & 4) * WPOLY)) -#define f3(x) (f2(x) ^ x) -#define f9(x) (f8(x) ^ x) -#define fb(x) (f8(x) ^ f2(x) ^ x) -#define fd(x) (f8(x) ^ f4(x) ^ x) -#define fe(x) (f8(x) ^ f4(x) ^ f2(x)) - -#else - -#define f2(x) ((x) ? pow[log[x] + 0x19] : 0) -#define f3(x) ((x) ? pow[log[x] + 0x01] : 0) -#define f9(x) ((x) ? pow[log[x] + 0xc7] : 0) -#define fb(x) ((x) ? pow[log[x] + 0x68] : 0) -#define fd(x) ((x) ? pow[log[x] + 0xee] : 0) -#define fe(x) ((x) ? pow[log[x] + 0xdf] : 0) - -#endif - -#include - -#if defined(__cplusplus) -extern "C" -{ -#endif - -#if defined(STATIC_TABLES) - -/* implemented in case of wrong call for fixed tables */ - -AES_RETURN aes_init(void) -{ - return EXIT_SUCCESS; -} - -#else /* Generate the tables for the dynamic table option */ - -#if defined(FF_TABLES) - -#define gf_inv(x) ((x) ? pow[ 255 - log[x]] : 0) - -#else - -/* It will generally be sensible to use tables to compute finite - field multiplies and inverses but where memory is scarse this - code might sometimes be better. But it only has effect during - initialisation so its pretty unimportant in overall terms. -*/ - -/* return 2 ^ (n - 1) where n is the bit number of the highest bit - set in x with x in the range 1 < x < 0x00000200. This form is - used so that locals within fi can be bytes rather than words -*/ - -static uint8_t hibit(const uint32_t x) -{ uint8_t r = (uint8_t)((x >> 1) | (x >> 2)); - - r |= (r >> 2); - r |= (r >> 4); - return (r + 1) >> 1; -} - -/* return the inverse of the finite field element x */ - -static uint8_t gf_inv(const uint8_t x) -{ uint8_t p1 = x, p2 = BPOLY, n1 = hibit(x), n2 = 0x80, v1 = 1, v2 = 0; - - if(x < 2) - return x; - - for( ; ; ) - { - if(n1) - while(n2 >= n1) /* divide polynomial p2 by p1 */ - { - n2 /= n1; /* shift smaller polynomial left */ - p2 ^= (p1 * n2) & 0xff; /* and remove from larger one */ - v2 ^= v1 * n2; /* shift accumulated value and */ - n2 = hibit(p2); /* add into result */ - } - else - return v1; - - if(n2) /* repeat with values swapped */ - while(n1 >= n2) - { - n1 /= n2; - p1 ^= p2 * n1; - v1 ^= v2 * n1; - n1 = hibit(p1); - } - else - return v2; - } -} - -#endif - -/* The forward and inverse affine transformations used in the S-box */ -uint8_t fwd_affine(const uint8_t x) -{ uint32_t w = x; - w ^= (w << 1) ^ (w << 2) ^ (w << 3) ^ (w << 4); - return 0x63 ^ ((w ^ (w >> 8)) & 0xff); -} - -uint8_t inv_affine(const uint8_t x) -{ uint32_t w = x; - w = (w << 1) ^ (w << 3) ^ (w << 6); - return 0x05 ^ ((w ^ (w >> 8)) & 0xff); -} - -int init = 0; - -AES_RETURN aes_init(void) -{ uint32_t i, w; - -#if defined(FF_TABLES) - - uint8_t pow[512] = {0}, log[256] = {0}; - - if(init) - return EXIT_SUCCESS; - /* log and power tables for GF(2^8) finite field with - WPOLY as modular polynomial - the simplest primitive - root is 0x03, used here to generate the tables - */ - - i = 0; w = 1; - do - { - pow[i] = (uint8_t)w; - pow[i + 255] = (uint8_t)w; - log[w] = (uint8_t)i++; - w ^= (w << 1) ^ (w & 0x80 ? WPOLY : 0); - } - while (w != 1); - -#else - if(init) - return EXIT_SUCCESS; -#endif - - for(i = 0, w = 1; i < RC_LENGTH; ++i) - { - t_set(r,c)[i] = bytes2word(w, 0, 0, 0); - w = f2(w); - } - - for(i = 0; i < 256; ++i) - { uint8_t b; - - b = fwd_affine(gf_inv((uint8_t)i)); - w = bytes2word(f2(b), b, b, f3(b)); - -#if defined( SBX_SET ) - t_set(s,box)[i] = b; -#endif - -#if defined( FT1_SET ) /* tables for a normal encryption round */ - t_set(f,n)[i] = w; -#endif -#if defined( FT4_SET ) - t_set(f,n)[0][i] = w; - t_set(f,n)[1][i] = upr(w,1); - t_set(f,n)[2][i] = upr(w,2); - t_set(f,n)[3][i] = upr(w,3); -#endif - w = bytes2word(b, 0, 0, 0); - -#if defined( FL1_SET ) /* tables for last encryption round (may also */ - t_set(f,l)[i] = w; /* be used in the key schedule) */ -#endif -#if defined( FL4_SET ) - t_set(f,l)[0][i] = w; - t_set(f,l)[1][i] = upr(w,1); - t_set(f,l)[2][i] = upr(w,2); - t_set(f,l)[3][i] = upr(w,3); -#endif - -#if defined( LS1_SET ) /* table for key schedule if t_set(f,l) above is*/ - t_set(l,s)[i] = w; /* not of the required form */ -#endif -#if defined( LS4_SET ) - t_set(l,s)[0][i] = w; - t_set(l,s)[1][i] = upr(w,1); - t_set(l,s)[2][i] = upr(w,2); - t_set(l,s)[3][i] = upr(w,3); -#endif - - b = gf_inv(inv_affine((uint8_t)i)); - w = bytes2word(fe(b), f9(b), fd(b), fb(b)); - -#if defined( IM1_SET ) /* tables for the inverse mix column operation */ - t_set(i,m)[b] = w; -#endif -#if defined( IM4_SET ) - t_set(i,m)[0][b] = w; - t_set(i,m)[1][b] = upr(w,1); - t_set(i,m)[2][b] = upr(w,2); - t_set(i,m)[3][b] = upr(w,3); -#endif - -#if defined( ISB_SET ) - t_set(i,box)[i] = b; -#endif -#if defined( IT1_SET ) /* tables for a normal decryption round */ - t_set(i,n)[i] = w; -#endif -#if defined( IT4_SET ) - t_set(i,n)[0][i] = w; - t_set(i,n)[1][i] = upr(w,1); - t_set(i,n)[2][i] = upr(w,2); - t_set(i,n)[3][i] = upr(w,3); -#endif - w = bytes2word(b, 0, 0, 0); -#if defined( IL1_SET ) /* tables for last decryption round */ - t_set(i,l)[i] = w; -#endif -#if defined( IL4_SET ) - t_set(i,l)[0][i] = w; - t_set(i,l)[1][i] = upr(w,1); - t_set(i,l)[2][i] = upr(w,2); - t_set(i,l)[3][i] = upr(w,3); -#endif - } - init = 1; - return EXIT_SUCCESS; -} - -/* - Automatic code initialisation (suggested by by Henrik S. Gaßmann) - based on code provided by Joe Lowe and placed in the public domain at: - http://stackoverflow.com/questions/1113409/attribute-constructor-equivalent-in-vc -*/ - -#ifdef _MSC_VER - -#pragma section(".CRT$XCU", read) - -__declspec(allocate(".CRT$XCU")) void (__cdecl *aes_startup)(void) = aes_init; - -#elif defined(__GNUC__) - -static void aes_startup(void) __attribute__((constructor)); - -static void aes_startup(void) -{ - aes_init(); -} - -#else - -#pragma message( "dynamic tables must be initialised manually on your system" ) - -#endif - -#endif - -#if defined(__cplusplus) -} -#endif - diff --git a/trezor-crypto/crypto/base32.c b/trezor-crypto/crypto/base32.c deleted file mode 100644 index aca56c4f461..00000000000 --- a/trezor-crypto/crypto/base32.c +++ /dev/null @@ -1,241 +0,0 @@ -/** - * Copyright (c) 2017 Saleem Rashid - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, E1PRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include - -#include - -const char *BASE32_ALPHABET_RFC4648 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ23456789"; - -static inline void base32_5to8(const uint8_t *in, uint8_t length, uint8_t *out); -static inline bool base32_8to5(const uint8_t *in, uint8_t length, uint8_t *out, - const char *alphabet); -static inline void base32_8to5_raw(const uint8_t *in, uint8_t length, - uint8_t *out); - -static inline int base32_encode_character(uint8_t decoded, - const char *alphabet); -static inline int base32_decode_character(char encoded, const char *alphabet); - -char *base32_encode(const uint8_t *in, size_t inlen, char *out, size_t outlen, - const char *alphabet) { - size_t length = base32_encoded_length(inlen); - if (outlen <= length) { - return NULL; - } - - base32_encode_unsafe(in, inlen, (uint8_t *)out); - - for (size_t i = 0; i < length; i++) { - int ret = base32_encode_character(out[i], alphabet); - - if (ret == -1) { - return false; - } else { - out[i] = ret; - } - } - - out[length] = '\0'; - return &out[length]; -} - -uint8_t *base32_decode(const char *in, size_t inlen, uint8_t *out, - size_t outlen, const char *alphabet) { - size_t length = base32_decoded_length(inlen); - if (outlen < length) { - return NULL; - } - - if (!base32_decode_unsafe((uint8_t *)in, inlen, (uint8_t *)out, alphabet)) { - return NULL; - } - - return &out[length]; -} - -void base32_encode_unsafe(const uint8_t *in, size_t inlen, uint8_t *out) { - uint8_t remainder = inlen % 5; - size_t limit = inlen - remainder; - - size_t i = 0, j = 0; - for (i = 0, j = 0; i < limit; i += 5, j += 8) { - base32_5to8(&in[i], 5, &out[j]); - } - - if (remainder) base32_5to8(&in[i], remainder, &out[j]); -} - -bool base32_decode_unsafe(const uint8_t *in, size_t inlen, uint8_t *out, - const char *alphabet) { - uint8_t remainder = inlen % 8; - size_t limit = inlen - remainder; - - size_t i = 0, j = 0; - for (i = 0, j = 0; i < limit; i += 8, j += 5) { - if (!base32_8to5(&in[i], 8, &out[j], alphabet)) { - return false; - } - } - - if (remainder && !base32_8to5(&in[i], remainder, &out[j], alphabet)) { - return false; - } - - return true; -} - -size_t base32_encoded_length(size_t inlen) { - uint8_t remainder = inlen % 5; - - return (inlen / 5) * 8 + (remainder * 8 + 4) / 5; -} - -size_t base32_decoded_length(size_t inlen) { - uint8_t remainder = inlen % 8; - - return (inlen / 8) * 5 + (remainder * 5) / 8; -} - -void base32_5to8(const uint8_t *in, uint8_t length, uint8_t *out) { - if (length >= 1) { - out[0] = (in[0] >> 3); - out[1] = (in[0] & 7) << 2; - } - - if (length >= 2) { - out[1] |= (in[1] >> 6); - out[2] = (in[1] >> 1) & 31; - out[3] = (in[1] & 1) << 4; - } - - if (length >= 3) { - out[3] |= (in[2] >> 4); - out[4] = (in[2] & 15) << 1; - } - - if (length >= 4) { - out[4] |= (in[3] >> 7); - out[5] = (in[3] >> 2) & 31; - out[6] = (in[3] & 3) << 3; - } - - if (length >= 5) { - out[6] |= (in[4] >> 5); - out[7] = (in[4] & 31); - } -} - -bool base32_8to5(const uint8_t *in, uint8_t length, uint8_t *out, - const char *alphabet) { - if (length == 1 || length == 3 || length == 6 || length > 8) { - return false; - } - - if (alphabet) { - uint8_t decoded[length]; - memset(decoded, 0, sizeof(decoded)); - - for (size_t i = 0; i < length; i++) { - int ret = base32_decode_character(in[i], alphabet); - - if (ret == -1) { - return false; - } else { - decoded[i] = ret; - } - } - - base32_8to5_raw(decoded, length, out); - } else { - base32_8to5_raw(in, length, out); - } - - return true; -} - -void base32_8to5_raw(const uint8_t *in, uint8_t length, uint8_t *out) { - if (length >= 2) { - out[0] = (in[0] << 3); - out[0] |= (in[1] >> 2); - } - - if (length >= 4) { - out[1] = (in[1] & 3) << 6; - out[1] |= (in[2] << 1); - out[1] |= (in[3] >> 4); - } - - if (length >= 5) { - out[2] = (in[3] & 15) << 4; - out[2] |= (in[4] >> 1); - } - - if (length >= 7) { - out[3] = (in[4] & 1) << 7; - out[3] |= (in[5] << 2); - out[3] |= (in[6] >> 3); - } - - if (length >= 8) { - out[4] = (in[6] & 7) << 5; - out[4] |= (in[7] & 31); - } -} - -int base32_encode_character(uint8_t decoded, const char *alphabet) { - if (decoded >> 5) { - return -1; - } - - if (alphabet == BASE32_ALPHABET_RFC4648) { - if (decoded < 26) { - return 'A' + decoded; - } else { - return '2' - 26 + decoded; - } - } - - return alphabet[decoded]; -} - -int base32_decode_character(char encoded, const char *alphabet) { - if (alphabet == BASE32_ALPHABET_RFC4648) { - if (encoded >= 'A' && encoded <= 'Z') { - return encoded - 'A'; - } else if (encoded >= 'a' && encoded <= 'z') { - return encoded - 'a'; - } else if (encoded >= '2' && encoded <= '7') { - return encoded - '2' + 26; - } else { - return -1; - } - } - - const char *occurrence = strchr(alphabet, encoded); - - if (occurrence) { - return occurrence - alphabet; - } else { - return -1; - } -} diff --git a/trezor-crypto/crypto/base58.c b/trezor-crypto/crypto/base58.c deleted file mode 100644 index 1a007a2cbc0..00000000000 --- a/trezor-crypto/crypto/base58.c +++ /dev/null @@ -1,274 +0,0 @@ -/** - * Copyright (c) 2012-2014 Luke Dashjr - * Copyright (c) 2013-2014 Pavol Rusnak - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include - -const char b58digits_ordered[] = - "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; -const int8_t b58digits_map[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, - 8, -1, -1, -1, -1, -1, -1, -1, 9, 10, 11, 12, 13, 14, 15, 16, -1, 17, 18, - 19, 20, 21, -1, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, - -1, -1, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, -1, -1, -1, -1, -}; - -typedef uint64_t b58_maxint_t; -typedef uint32_t b58_almostmaxint_t; -#define b58_almostmaxint_bits (sizeof(b58_almostmaxint_t) * 8) -const b58_almostmaxint_t b58_almostmaxint_mask = - ((((b58_maxint_t)1) << b58_almostmaxint_bits) - 1); - -// Decodes a null-terminated Base58 string `b58` to binary and writes the result -// at the end of the buffer `bin` of size `*binszp`. On success `*binszp` is set -// to the number of valid bytes at the end of the buffer. -bool b58tobin(void *bin, size_t *binszp, const char *b58) { - size_t binsz = *binszp; - - if (binsz == 0) { - return false; - } - - const unsigned char *b58u = (const unsigned char *)b58; - unsigned char *binu = bin; - size_t outisz = - (binsz + sizeof(b58_almostmaxint_t) - 1) / sizeof(b58_almostmaxint_t); - b58_almostmaxint_t outi[outisz]; - b58_maxint_t t = 0; - b58_almostmaxint_t c = 0; - size_t i = 0, j = 0; - uint8_t bytesleft = binsz % sizeof(b58_almostmaxint_t); - b58_almostmaxint_t zeromask = - bytesleft ? (b58_almostmaxint_mask << (bytesleft * 8)) : 0; - unsigned zerocount = 0; - - size_t b58sz = strlen(b58); - - memzero(outi, sizeof(outi)); - - // Leading zeros, just count - for (i = 0; i < b58sz && b58u[i] == '1'; ++i) ++zerocount; - - for (; i < b58sz; ++i) { - if (b58u[i] & 0x80) - // High-bit set on invalid digit - return false; - if (b58digits_map[b58u[i]] == -1) - // Invalid base58 digit - return false; - c = (unsigned)b58digits_map[b58u[i]]; - for (j = outisz; j--;) { - t = ((b58_maxint_t)outi[j]) * 58 + c; - c = t >> b58_almostmaxint_bits; - outi[j] = t & b58_almostmaxint_mask; - } - if (c) - // Output number too big (carry to the next int32) - return false; - if (outi[0] & zeromask) - // Output number too big (last int32 filled too far) - return false; - } - - j = 0; - if (bytesleft) { - for (i = bytesleft; i > 0; --i) { - *(binu++) = (outi[0] >> (8 * (i - 1))) & 0xff; - } - ++j; - } - - for (; j < outisz; ++j) { - for (i = sizeof(*outi); i > 0; --i) { - *(binu++) = (outi[j] >> (8 * (i - 1))) & 0xff; - } - } - - // locate the most significant byte - binu = bin; - for (i = 0; i < binsz; ++i) { - if (binu[i]) break; - } - - // prepend the correct number of null-bytes - if (zerocount > i) { - /* result too large */ - return false; - } - *binszp = binsz - i + zerocount; - - return true; -} - -int b58check(const void *bin, size_t binsz, HasherType hasher_type, - const char *base58str) { - unsigned char buf[32] = {0}; - const uint8_t *binc = bin; - unsigned i = 0; - if (binsz < 4) return -4; - hasher_Raw(hasher_type, bin, binsz - 4, buf); - if (memcmp(&binc[binsz - 4], buf, 4)) return -1; - - // Check number of zeros is correct AFTER verifying checksum (to avoid - // possibility of accessing base58str beyond the end) - for (i = 0; binc[i] == '\0' && base58str[i] == '1'; ++i) { - } // Just finding the end of zeros, nothing to do in loop - if (binc[i] == '\0' || base58str[i] == '1') return -3; - - return binc[0]; -} - -bool b58enc(char *b58, size_t *b58sz, const void *data, size_t binsz) { - const uint8_t *bin = data; - int carry = 0; - size_t i = 0, j = 0, high = 0, zcount = 0; - size_t size = 0; - - while (zcount < binsz && !bin[zcount]) ++zcount; - - size = (binsz - zcount) * 138 / 100 + 1; - uint8_t buf[size]; - memzero(buf, size); - - for (i = zcount, high = size - 1; i < binsz; ++i, high = j) { - for (carry = bin[i], j = size - 1; (j > high) || carry; --j) { - carry += 256 * buf[j]; - buf[j] = carry % 58; - carry /= 58; - if (!j) { - // Otherwise j wraps to maxint which is > high - break; - } - } - } - - for (j = 0; j < size && !buf[j]; ++j) - ; - - if (*b58sz <= zcount + size - j) { - *b58sz = zcount + size - j + 1; - return false; - } - - if (zcount) memset(b58, '1', zcount); - for (i = zcount; j < size; ++i, ++j) b58[i] = b58digits_ordered[buf[j]]; - b58[i] = '\0'; - *b58sz = i + 1; - - return true; -} - -int base58_encode_check(const uint8_t *data, int datalen, - HasherType hasher_type, char *str, int strsize) { - if (datalen > 128) { - return 0; - } - uint8_t buf[datalen + 32]; - memset(buf, 0, sizeof(buf)); - uint8_t *hash = buf + datalen; - memcpy(buf, data, datalen); - hasher_Raw(hasher_type, data, datalen, hash); - size_t res = strsize; - bool success = b58enc(str, &res, buf, datalen + 4); - memzero(buf, sizeof(buf)); - return success ? res : 0; -} - -int base58_decode_check(const char *str, HasherType hasher_type, uint8_t *data, - int datalen) { - if (datalen > 128) { - return 0; - } - uint8_t d[datalen + 4]; - memset(d, 0, sizeof(d)); - size_t res = datalen + 4; - if (b58tobin(d, &res, str) != true) { - return 0; - } - uint8_t *nd = d + datalen + 4 - res; - if (b58check(nd, res, hasher_type, str) < 0) { - return 0; - } - memcpy(data, nd, res - 4); - return res - 4; -} - -#if USE_GRAPHENE -int b58gphcheck(const void *bin, size_t binsz, const char *base58str) { - unsigned char buf[32] = {0}; - const uint8_t *binc = bin; - unsigned i = 0; - if (binsz < 4) return -4; - ripemd160(bin, binsz - 4, buf); // No double SHA256, but a single RIPEMD160 - if (memcmp(&binc[binsz - 4], buf, 4)) return -1; - - // Check number of zeros is correct AFTER verifying checksum (to avoid - // possibility of accessing base58str beyond the end) - for (i = 0; binc[i] == '\0' && base58str[i] == '1'; ++i) { - } // Just finding the end of zeros, nothing to do in loop - if (binc[i] == '\0' || base58str[i] == '1') return -3; - - return binc[0]; -} - -int base58gph_encode_check(const uint8_t *data, int datalen, char *str, - int strsize) { - if (datalen > 128) { - return 0; - } - uint8_t buf[datalen + 32]; - memset(buf, 0, sizeof(buf)); - uint8_t *hash = buf + datalen; - memcpy(buf, data, datalen); - ripemd160(data, datalen, hash); // No double SHA256, but a single RIPEMD160 - size_t res = strsize; - bool success = b58enc(str, &res, buf, datalen + 4); - memzero(buf, sizeof(buf)); - return success ? res : 0; -} - -int base58gph_decode_check(const char *str, uint8_t *data, int datalen) { - if (datalen > 128) { - return 0; - } - uint8_t d[datalen + 4]; - memset(d, 0, sizeof(d)); - size_t res = datalen + 4; - if (b58tobin(d, &res, str) != true) { - return 0; - } - uint8_t *nd = d + datalen + 4 - res; - if (b58gphcheck(nd, res, str) < 0) { - return 0; - } - memcpy(data, nd, res - 4); - return res - 4; -} -#endif diff --git a/trezor-crypto/crypto/bignum.c b/trezor-crypto/crypto/bignum.c deleted file mode 100644 index 5791448d100..00000000000 --- a/trezor-crypto/crypto/bignum.c +++ /dev/null @@ -1,1827 +0,0 @@ -/** - * Copyright (c) 2013-2014 Tomas Dzetkulic - * Copyright (c) 2013-2014 Pavol Rusnak - * Copyright (c) 2015 Jochen Hoenicke - * Copyright (c) 2016 Alex Beregszaszi - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include - -#include -#include -#include -#include - -#include -#include - -/* - This library implements 256-bit numbers arithmetic. - - An unsigned 256-bit number is represented by a bignum256 structure, that is an - array of nine 32-bit values called limbs. Limbs are digits of the number in - the base 2**29 representation in the little endian order. This means that - bignum256 x; - represents the value - sum([x[i] * 2**(29*i) for i in range(9)). - - A limb of a bignum256 is *normalized* iff it's less than 2**29. - A bignum256 is *normalized* iff every its limb is normalized. - A number is *fully reduced modulo p* iff it is less than p. - A number is *partly reduced modulo p* iff it is less than 2*p. - The number p is usually a prime number such that 2^256 - 2^224 <= p <= 2^256. - - All functions except bn_fast_mod expect that all their bignum256 inputs are - normalized. (The function bn_fast_mod allows the input number to have the - most significant limb unnormalized). All bignum256 outputs of all functions - are guaranteed to be normalized. - - A number can be partly reduced with bn_fast_mod, a partly reduced number can - be fully reduced with bn_mod. - - A function has *constant control flow with regard to its argument* iff the - order in which instructions of the function are executed doesn't depend on the - value of the argument. - A function has *constant memory access flow with regard to its argument* iff - the memory addresses that are acessed and the order in which they are accessed - don't depend on the value of the argument. - A function *has contant control (memory access) flow* iff it has constant - control (memory access) flow with regard to all its arguments. - - The following function has contant control flow with regard to its arugment - n, however is doesn't have constant memory access flow with regard to it: - void (int n, int *a) } - a[0] = 0; - a[n] = 0; // memory address reveals the value of n - } - - Unless stated otherwise all functions are supposed to have both constant - control flow and constant memory access flow. - */ - -#define BN_MAX_DECIMAL_DIGITS \ - 79 // floor(log(2**(LIMBS * BITS_PER_LIMB), 10)) + 1 - -// out_number = (bignum256) in_number -// Assumes in_number is a raw bigendian 256-bit number -// Guarantees out_number is normalized -void bn_read_be(const uint8_t *in_number, bignum256 *out_number) { - uint32_t temp = 0; - - for (int i = 0; i < BN_LIMBS - 1; i++) { - uint32_t limb = read_be(in_number + (BN_LIMBS - 2 - i) * 4); - - temp |= limb << (BN_EXTRA_BITS * i); - out_number->val[i] = temp & BN_LIMB_MASK; - - temp = limb >> (32 - BN_EXTRA_BITS * (i + 1)); - } - - out_number->val[BN_LIMBS - 1] = temp; -} - -// out_number = (256BE) in_number -// Assumes in_number < 2**256 -// Guarantess out_number is a raw bigendian 256-bit number -void bn_write_be(const bignum256 *in_number, uint8_t *out_number) { - uint32_t temp = in_number->val[BN_LIMBS - 1]; - for (int i = BN_LIMBS - 2; i >= 0; i--) { - uint32_t limb = in_number->val[i]; - - temp = (temp << (BN_BITS_PER_LIMB - BN_EXTRA_BITS * i)) | - (limb >> (BN_EXTRA_BITS * i)); - write_be(out_number + (BN_LIMBS - 2 - i) * 4, temp); - - temp = limb; - } -} - -// out_number = (bignum256) in_number -// Assumes in_number is a raw little endian 256-bit number -// Guarantees out_number is normalized -void bn_read_le(const uint8_t *in_number, bignum256 *out_number) { - uint32_t temp = 0; - for (int i = 0; i < BN_LIMBS - 1; i++) { - uint32_t limb = read_le(in_number + i * 4); - - temp |= limb << (BN_EXTRA_BITS * i); - out_number->val[i] = temp & BN_LIMB_MASK; - temp = limb >> (32 - BN_EXTRA_BITS * (i + 1)); - } - - out_number->val[BN_LIMBS - 1] = temp; -} - -// out_number = (256LE) in_number -// Assumes in_number < 2**256 -// Guarantess out_number is a raw little endian 256-bit number -void bn_write_le(const bignum256 *in_number, uint8_t *out_number) { - uint32_t temp = in_number->val[BN_LIMBS - 1]; - - for (int i = BN_LIMBS - 2; i >= 0; i--) { - uint32_t limb = in_number->val[i]; - temp = (temp << (BN_BITS_PER_LIMB - BN_EXTRA_BITS * i)) | - (limb >> (BN_EXTRA_BITS * i)); - write_le(out_number + i * 4, temp); - temp = limb; - } -} - -// out_number = (bignum256) in_number -// Guarantees out_number is normalized -void bn_read_uint32(uint32_t in_number, bignum256 *out_number) { - out_number->val[0] = in_number & BN_LIMB_MASK; - out_number->val[1] = in_number >> BN_BITS_PER_LIMB; - for (uint32_t i = 2; i < BN_LIMBS; i++) out_number->val[i] = 0; -} - -// out_number = (bignum256) in_number -// Guarantees out_number is normalized -void bn_read_uint64(uint64_t in_number, bignum256 *out_number) { - out_number->val[0] = in_number & BN_LIMB_MASK; - out_number->val[1] = (in_number >>= BN_BITS_PER_LIMB) & BN_LIMB_MASK; - out_number->val[2] = in_number >> BN_BITS_PER_LIMB; - for (uint32_t i = 3; i < BN_LIMBS; i++) out_number->val[i] = 0; -} - -// Returns the bitsize of x -// Assumes x is normalized -// The function doesn't have neither constant control flow nor constant memory -// access flow -int bn_bitcount(const bignum256 *x) { - for (int i = BN_LIMBS - 1; i >= 0; i--) { - uint32_t limb = x->val[i]; - if (limb != 0) { - // __builtin_clz returns the number of leading zero bits starting at the - // most significant bit position - return i * BN_BITS_PER_LIMB + (32 - __builtin_clz(limb)); - } - } - return 0; -} - -// Returns the number of decimal digits of x; if x is 0, returns 1 -// Assumes x is normalized -// The function doesn't have neither constant control flow nor constant memory -// access flow -unsigned int bn_digitcount(const bignum256 *x) { - bignum256 val = {0}; - bn_copy(x, &val); - - unsigned int digits = 1; - for (unsigned int i = 0; i < BN_MAX_DECIMAL_DIGITS; i += 3) { - uint32_t limb = 0; - - bn_divmod1000(&val, &limb); - - if (limb >= 100) { - digits = i + 3; - } else if (limb >= 10) { - digits = i + 2; - } else if (limb >= 1) { - digits = i + 1; - } - } - - memzero(&val, sizeof(val)); - - return digits; -} - -// x = 0 -// Guarantees x is normalized -void bn_zero(bignum256 *x) { - for (int i = 0; i < BN_LIMBS; i++) { - x->val[i] = 0; - } -} - -// x = 1 -// Guarantees x is normalized -void bn_one(bignum256 *x) { - x->val[0] = 1; - for (int i = 1; i < BN_LIMBS; i++) { - x->val[i] = 0; - } -} - -// Returns x == 0 -// Assumes x is normalized -int bn_is_zero(const bignum256 *x) { - uint32_t result = 0; - for (int i = 0; i < BN_LIMBS; i++) { - result |= x->val[i]; - } - return !result; -} - -// Returns x == 1 -// Assumes x is normalized -int bn_is_one(const bignum256 *x) { - uint32_t result = x->val[0] ^ 1; - for (int i = 1; i < BN_LIMBS; i++) { - result |= x->val[i]; - } - return !result; -} - -// Returns x < y -// Assumes x, y are normalized -int bn_is_less(const bignum256 *x, const bignum256 *y) { - uint32_t res1 = 0; - uint32_t res2 = 0; - for (int i = BN_LIMBS - 1; i >= 0; i--) { - res1 = (res1 << 1) | (x->val[i] < y->val[i]); - res2 = (res2 << 1) | (x->val[i] > y->val[i]); - } - return res1 > res2; -} - -// Returns x == y -// Assumes x, y are normalized -int bn_is_equal(const bignum256 *x, const bignum256 *y) { - uint32_t result = 0; - for (int i = 0; i < BN_LIMBS; i++) { - result |= x->val[i] ^ y->val[i]; - } - return !result; -} - -// res = cond if truecase else falsecase -// Assumes cond is either 0 or 1 -// Works properly even if &res == &truecase or &res == &falsecase or -// &truecase == &falsecase or &res == &truecase == &falsecase -void bn_cmov(bignum256 *res, volatile uint32_t cond, const bignum256 *truecase, - const bignum256 *falsecase) { - assert((int)(cond == 1) | (cond == 0)); - - uint32_t tmask = -cond; // tmask = 0xFFFFFFFF if cond else 0x00000000 - uint32_t fmask = ~tmask; // fmask = 0x00000000 if cond else 0xFFFFFFFF - - for (int i = 0; i < BN_LIMBS; i++) { - res->val[i] = (truecase->val[i] & tmask) | (falsecase->val[i] & fmask); - } -} - -// x = -x % prime if cond else x, -// Explicitly x = (3 * prime - x if x > prime else 2 * prime - x) if cond else -// else (x if x > prime else x + prime) -// Assumes x is normalized and partly reduced -// Assumes cond is either 1 or 0 -// Guarantees x is normalized -// Assumes prime is normalized and -// 0 < prime < 2**260 == 2**(BITS_PER_LIMB * LIMBS - 1) -void bn_cnegate(volatile uint32_t cond, bignum256 *x, const bignum256 *prime) { - assert((int)(cond == 1) | (cond == 0)); - - uint32_t tmask = -cond; // tmask = 0xFFFFFFFF if cond else 0x00000000 - uint32_t fmask = ~tmask; // fmask = 0x00000000 if cond else 0xFFFFFFFF - - bn_mod(x, prime); - // x < prime - - uint32_t acc1 = 1; - uint32_t acc2 = 0; - - for (int i = 0; i < BN_LIMBS; i++) { - acc1 += (BN_BASE - 1) + 2 * prime->val[i] - x->val[i]; - // acc1 neither overflows 32 bits nor underflows 0 - // Proof: - // acc1 + (BASE - 1) + 2 * prime[i] - x[i] - // >= (BASE - 1) - x >= (2**BITS_PER_LIMB - 1) - (2**BITS_PER_LIMB - 1) - // == 0 - // acc1 + (BASE - 1) + 2 * prime[i] - x[i] - // <= acc1 + (BASE - 1) + 2 * prime[i] - // <= (2**(32 - BITS_PER_LIMB) - 1) + 2 * (2**BITS_PER_LIMB - 1) + - // (2**BITS_PER_LIMB - 1) - // == 7 + 3 * 2**29 < 2**32 - - acc2 += prime->val[i] + x->val[i]; - // acc2 doesn't overflow 32 bits - // Proof: - // acc2 + prime[i] + x[i] - // <= 2**(32 - BITS_PER_LIMB) - 1 + 2 * (2**BITS_PER_LIMB - 1) - // == 2**(32 - BITS_PER_LIMB) + 2**(BITS_PER_LIMB + 1) - 2 - // == 2**30 + 5 < 2**32 - - // x = acc1 & LIMB_MASK if cond else acc2 & LIMB_MASK - x->val[i] = ((acc1 & tmask) | (acc2 & fmask)) & BN_LIMB_MASK; - - acc1 >>= BN_BITS_PER_LIMB; - // acc1 <= 7 == 2**(32 - BITS_PER_LIMB) - 1 - // acc1 == 2**(BITS_PER_LIMB * (i + 1)) + 2 * prime[:i + 1] - x[:i + 1] - // >> BITS_PER_LIMB * (i + 1) - - acc2 >>= BN_BITS_PER_LIMB; - // acc2 <= 7 == 2**(32 - BITS_PER_LIMB) - 1 - // acc2 == prime[:i + 1] + x[:i + 1] >> BITS_PER_LIMB * (i + 1) - } - - // assert(acc1 == 1); // assert prime <= 2**260 - // assert(acc2 == 0); - - // clang-format off - // acc1 == 1 - // Proof: - // acc1 == 2**(BITS_PER_LIMB * LIMBS) + 2 * prime[:LIMBS] - x[:LIMBS] >> BITS_PER_LIMB * LIMBS - // == 2**(BITS_PER_LIMB * LIMBS) + 2 * prime - x >> BITS_PER_LIMB * LIMBS - // <= 2**(BITS_PER_LIMB * LIMBS) + 2 * prime >> BITS_PER_LIMB * LIMBS - // <= 2**(BITS_PER_LIMB * LIMBS) + 2 * (2**(BITS_PER_LIMB * LIMBS - 1) - 1) >> BITS_PER_LIMB * LIMBS - // <= 2**(BITS_PER_LIMB * LIMBS) + 2**(BITS_PER_LIMB * LIMBS) - 2 >> BITS_PER_LIMB * LIMBS - // == 1 - - // acc1 == 2**(BITS_PER_LIMB * LIMBS) + 2 * prime[:LIMBS] - x[:LIMBS] >> BITS_PER_LIMB * LIMBS - // == 2**(BITS_PER_LIMB * LIMBS) + 2 * prime - x >> BITS_PER_LIMB * LIMBS - // >= 2**(BITS_PER_LIMB * LIMBS) + 0 >> BITS_PER_LIMB * LIMBS - // == 1 - - // acc2 == 0 - // Proof: - // acc2 == prime[:LIMBS] + x[:LIMBS] >> BITS_PER_LIMB * LIMBS - // == prime + x >> BITS_PER_LIMB * LIMBS - // <= 2 * prime - 1 >> BITS_PER_LIMB * LIMBS - // <= 2 * (2**(BITS_PER_LIMB * LIMBS - 1) - 1) - 1 >> 261 - // == 2**(BITS_PER_LIMB * LIMBS) - 3 >> BITS_PER_LIMB * LIMBS - // == 0 - // clang-format on -} - -// x <<= 1 -// Assumes x is normalized, x < 2**260 == 2**(LIMBS*BITS_PER_LIMB - 1) -// Guarantees x is normalized -void bn_lshift(bignum256 *x) { - for (int i = BN_LIMBS - 1; i > 0; i--) { - x->val[i] = ((x->val[i] << 1) & BN_LIMB_MASK) | - (x->val[i - 1] >> (BN_BITS_PER_LIMB - 1)); - } - x->val[0] = (x->val[0] << 1) & BN_LIMB_MASK; -} - -// x >>= 1, i.e. x = floor(x/2) -// Assumes x is normalized -// Guarantees x is normalized -// If x is partly reduced (fully reduced) modulo prime, -// guarantess x will be partly reduced (fully reduced) modulo prime -void bn_rshift(bignum256 *x) { - for (int i = 0; i < BN_LIMBS - 1; i++) { - x->val[i] = - (x->val[i] >> 1) | ((x->val[i + 1] & 1) << (BN_BITS_PER_LIMB - 1)); - } - x->val[BN_LIMBS - 1] >>= 1; -} - -// Sets i-th least significant bit (counting from zero) -// Assumes x is normalized and 0 <= i < 261 == LIMBS*BITS_PER_LIMB -// Guarantees x is normalized -// The function has constant control flow but not constant memory access flow -// with regard to i -void bn_setbit(bignum256 *x, uint16_t i) { - assert(i < BN_LIMBS * BN_BITS_PER_LIMB); - x->val[i / BN_BITS_PER_LIMB] |= (1u << (i % BN_BITS_PER_LIMB)); -} - -// clears i-th least significant bit (counting from zero) -// Assumes x is normalized and 0 <= i < 261 == LIMBS*BITS_PER_LIMB -// Guarantees x is normalized -// The function has constant control flow but not constant memory access flow -// with regard to i -void bn_clearbit(bignum256 *x, uint16_t i) { - assert(i < BN_LIMBS * BN_BITS_PER_LIMB); - x->val[i / BN_BITS_PER_LIMB] &= ~(1u << (i % BN_BITS_PER_LIMB)); -} - -// returns i-th least significant bit (counting from zero) -// Assumes x is normalized and 0 <= i < 261 == LIMBS*BITS_PER_LIMB -// The function has constant control flow but not constant memory access flow -// with regard to i -uint32_t bn_testbit(const bignum256 *x, uint16_t i) { - assert(i < BN_LIMBS * BN_BITS_PER_LIMB); - return (x->val[i / BN_BITS_PER_LIMB] >> (i % BN_BITS_PER_LIMB)) & 1; -} - -// res = x ^ y -// Assumes x, y are normalized -// Guarantees res is normalized -// Works properly even if &res == &x or &res == &y or &res == &x == &y -void bn_xor(bignum256 *res, const bignum256 *x, const bignum256 *y) { - for (int i = 0; i < BN_LIMBS; i++) { - res->val[i] = x->val[i] ^ y->val[i]; - } -} - -// x = x / 2 % prime -// Explicitly x = x / 2 if is_even(x) else (x + prime) / 2 -// Assumes x is normalized, x + prime < 261 == LIMBS * BITS_PER_LIMB -// Guarantees x is normalized -// If x is partly reduced (fully reduced) modulo prime, -// guarantess x will be partly reduced (fully reduced) modulo prime -// Assumes prime is an odd number and normalized -void bn_mult_half(bignum256 *x, const bignum256 *prime) { - // x = x / 2 if is_even(x) else (x + prime) / 2 - - uint32_t x_is_odd_mask = - -(x->val[0] & 1); // x_is_odd_mask = 0xFFFFFFFF if is_odd(x) else 0 - - uint32_t acc = (x->val[0] + (prime->val[0] & x_is_odd_mask)) >> 1; - // acc < 2**BITS_PER_LIMB - // Proof: - // acc == x[0] + prime[0] & x_is_odd_mask >> 1 - // <= (2**(BITS_PER_LIMB) - 1) + (2**(BITS_PER_LIMB) - 1) >> 1 - // == 2**(BITS_PER_LIMB + 1) - 2 >> 1 - // < 2**(BITS_PER_LIMB) - - for (int i = 0; i < BN_LIMBS - 1; i++) { - uint32_t temp = (x->val[i + 1] + (prime->val[i + 1] & x_is_odd_mask)); - // temp < 2**(BITS_PER_LIMB + 1) - // Proof: - // temp == x[i + 1] + val[i + 1] & x_is_odd_mask - // <= (2**(BITS_PER_LIMB) - 1) + (2**(BITS_PER_LIMB) - 1) - // < 2**(BITS_PER_LIMB + 1) - - acc += (temp & 1) << (BN_BITS_PER_LIMB - 1); - // acc doesn't overflow 32 bits - // Proof: - // acc + (temp & 1 << BITS_PER_LIMB - 1) - // <= 2**(BITS_PER_LIMB + 1) + 2**(BITS_PER_LIMB - 1) - // <= 2**30 + 2**28 < 2**32 - - x->val[i] = acc & BN_LIMB_MASK; - acc >>= BN_BITS_PER_LIMB; - acc += temp >> 1; - // acc < 2**(BITS_PER_LIMB + 1) - // Proof: - // acc + (temp >> 1) - // <= (2**(32 - BITS_PER_LIMB) - 1) + (2**(BITS_PER_LIMB + 1) - 1 >> 1) - // == 7 + 2**(BITS_PER_LIMB) - 1 < 2**(BITS_PER_LIMB + 1) - - // acc == x[:i+2]+(prime[:i+2] & x_is_odd_mask) >> BITS_PER_LIMB * (i+1) - } - x->val[BN_LIMBS - 1] = acc; - - // assert(acc >> BITS_PER_LIMB == 0); - // acc >> BITS_PER_LIMB == 0 - // Proof: - // acc - // == x[:LIMBS] + (prime[:LIMBS] & x_is_odd_mask) >> BITS_PER_LIMB*LIMBS - // == x + (prime & x_is_odd_mask) >> BITS_PER_LIMB * LIMBS - // <= x + prime >> BITS_PER_LIMB * LIMBS - // <= 2**(BITS_PER_LIMB * LIMBS) - 1 >> BITS_PER_LIMB * LIMBS - // == 0 -} - -// x = x * k % prime -// Assumes x is normalized, 0 <= k <= 8 = 2**(32 - BITS_PER_LIMB) -// Assumes prime is normalized and 2^256 - 2^224 <= prime <= 2^256 -// Guarantees x is normalized and partly reduced modulo prime -void bn_mult_k(bignum256 *x, uint8_t k, const bignum256 *prime) { - assert(k <= 8); - - for (int i = 0; i < BN_LIMBS; i++) { - x->val[i] = k * x->val[i]; - // x[i] doesn't overflow 32 bits - // k * x[i] <= 2**(32 - BITS_PER_LIMB) * (2**BITS_PER_LIMB - 1) - // < 2**(32 - BITS_PER_LIMB) * 2**BITS_PER_LIMB == 2**32 - } - - bn_fast_mod(x, prime); -} - -// Reduces partly reduced x modulo prime -// Explicitly x = x if x < prime else x - prime -// Assumes x is partly reduced modulo prime -// Guarantees x is fully reduced modulo prime -// Assumes prime is nonzero and normalized -void bn_mod(bignum256 *x, const bignum256 *prime) { - uint32_t x_less_prime = bn_is_less(x, prime); - - bignum256 temp = {0}; - bn_subtract(x, prime, &temp); - bn_cmov(x, x_less_prime, x, &temp); - - memzero(&temp, sizeof(temp)); -} - -// Auxiliary function for bn_multiply -// res = k * x -// Assumes k and x are normalized -// Guarantees res is normalized 18 digit little endian number in base 2**29 -void bn_multiply_long(const bignum256 *k, const bignum256 *x, - uint32_t res[2 * BN_LIMBS]) { - // Uses long multiplication in base 2**29, see - // https://en.wikipedia.org/wiki/Multiplication_algorithm#Long_multiplication - - uint64_t acc = 0; - - // compute lower half - for (int i = 0; i < BN_LIMBS; i++) { - for (int j = 0; j <= i; j++) { - acc += k->val[j] * (uint64_t)x->val[i - j]; - // acc doesn't overflow 64 bits - // Proof: - // acc <= acc + sum([k[j] * x[i-j] for j in range(i)]) - // <= (2**(64 - BITS_PER_LIMB) - 1) + - // LIMBS * (2**BITS_PER_LIMB - 1) * (2**BITS_PER_LIMB - 1) - // == (2**35 - 1) + 9 * (2**29 - 1) * (2**29 - 1) - // <= 2**35 + 9 * 2**58 < 2**64 - } - - res[i] = acc & BN_LIMB_MASK; - acc >>= BN_BITS_PER_LIMB; - // acc <= 2**35 - 1 == 2**(64 - BITS_PER_LIMB) - 1 - } - - // compute upper half - for (int i = BN_LIMBS; i < 2 * BN_LIMBS - 1; i++) { - for (int j = i - BN_LIMBS + 1; j < BN_LIMBS; j++) { - acc += k->val[j] * (uint64_t)x->val[i - j]; - // acc doesn't overflow 64 bits - // Proof: - // acc <= acc + sum([k[j] * x[i-j] for j in range(i)]) - // <= (2**(64 - BITS_PER_LIMB) - 1) - // LIMBS * (2**BITS_PER_LIMB - 1) * (2**BITS_PER_LIMB - 1) - // == (2**35 - 1) + 9 * (2**29 - 1) * (2**29 - 1) - // <= 2**35 + 9 * 2**58 < 2**64 - } - - res[i] = acc & (BN_BASE - 1); - acc >>= BN_BITS_PER_LIMB; - // acc < 2**35 == 2**(64 - BITS_PER_LIMB) - } - - res[2 * BN_LIMBS - 1] = acc; -} - -// Auxiliary function for bn_multiply -// Assumes 0 <= d <= 8 == LIMBS - 1 -// Assumes res is normalized and res < 2**(256 + 29*d + 31) -// Guarantess res in normalized and res < 2 * prime * 2**(29*d) -// Assumes prime is normalized, 2**256 - 2**224 <= prime <= 2**256 -void bn_multiply_reduce_step(uint32_t res[2 * BN_LIMBS], const bignum256 *prime, - uint32_t d) { - // clang-format off - // Computes res = res - (res // 2**(256 + BITS_PER_LIMB * d)) * prime * 2**(BITS_PER_LIMB * d) - - // res - (res // 2**(256 + BITS_PER_LIMB * d)) * prime * 2**(BITS_PER_LIMB * d) < 2 * prime * 2**(BITS_PER_LIMB * d) - // Proof: - // res - res // (2**(256 + BITS_PER_LIMB * d)) * 2**(BITS_PER_LIMB * d) * prime - // == res - res // (2**(256 + BITS_PER_LIMB * d)) * 2**(BITS_PER_LIMB * d) * (2**256 - (2**256 - prime)) - // == res - res // (2**(256 + BITS_PER_LIMB * d)) * 2**(BITS_PER_LIMB * d) * 2**256 + res // (2**(256 + BITS_PER_LIMB * d)) * 2**(BITS_PER_LIMB * d) * (2**256 - prime) - // == (res % 2**(256 + BITS_PER_LIMB * d)) + res // (2**256 + BITS_PER_LIMB * d) * 2**(BITS_PER_LIMB * d) * (2**256 - prime) - // <= (2**(256 + 29*d + 31) % 2**(256 + 29*d)) + (2**(256 + 29*d + 31) - 1) / (2**256 + 29*d) * 2**(29*d) * (2**256 - prime) - // <= 2**(256 + 29*d) + 2**(256 + 29*d + 31) / (2**256 + 29*d) * 2**(29*d) * (2**256 - prime) - // == 2**(256 + 29*d) + 2**31 * 2**(29*d) * (2**256 - prime) - // == 2**(29*d) * (2**256 + 2**31 * (2*256 - prime)) - // <= 2**(29*d) * (2**256 + 2**31 * 2*224) - // <= 2**(29*d) * (2**256 + 2**255) - // <= 2**(29*d) * 2 * (2**256 - 2**224) - // <= 2 * prime * 2**(29*d) - // clang-format on - - uint32_t coef = - (res[d + BN_LIMBS - 1] >> (256 - (BN_LIMBS - 1) * BN_BITS_PER_LIMB)) + - (res[d + BN_LIMBS] << ((BN_LIMBS * BN_BITS_PER_LIMB) - 256)); - - // coef == res // 2**(256 + BITS_PER_LIMB * d) - - // coef < 2**31 - // Proof: - // coef == res // 2**(256 + BITS_PER_LIMB * d) - // < 2**(256 + 29 * d + 31) // 2**(256 + 29 * d) - // == 2**31 - - const int shift = 31; - uint64_t acc = 1ull << shift; - - for (int i = 0; i < BN_LIMBS; i++) { - acc += (((uint64_t)(BN_BASE - 1)) << shift) + res[d + i] - - prime->val[i] * (uint64_t)coef; - // acc neither overflow 64 bits nor underflow zero - // Proof: - // acc + ((BASE - 1) << shift) + res[d + i] - prime[i] * coef - // >= ((BASE - 1) << shift) - prime[i] * coef - // == 2**shift * (2**BITS_PER_LIMB - 1) - (2**BITS_PER_LIMB - 1) * - // (2**31 - 1) - // == (2**shift - 2**31 + 1) * (2**BITS_PER_LIMB - 1) - // == (2**31 - 2**31 + 1) * (2**29 - 1) - // == 2**29 - 1 > 0 - // acc + ((BASE - 1) << shift) + res[d + i] - prime[i] * coef - // <= acc + ((BASE - 1) << shift) + res[d+i] - // <= (2**(64 - BITS_PER_LIMB) - 1) + 2**shift * (2**BITS_PER_LIMB - 1) - // + (2*BITS_PER_LIMB - 1) - // == (2**(64 - BITS_PER_LIMB) - 1) + (2**shift + 1) * - // (2**BITS_PER_LIMB - 1) - // == (2**35 - 1) + (2**31 + 1) * (2**29 - 1) - // <= 2**35 + 2**60 + 2**29 < 2**64 - - res[d + i] = acc & BN_LIMB_MASK; - acc >>= BN_BITS_PER_LIMB; - // acc <= 2**(64 - BITS_PER_LIMB) - 1 == 2**35 - 1 - - // acc == (1 << BITS_PER_LIMB * (i + 1) + shift) + res[d : d + i + 1] - // - coef * prime[:i + 1] >> BITS_PER_LIMB * (i + 1) - } - - // acc += (((uint64_t)(BASE - 1)) << shift) + res[d + LIMBS]; - // acc >>= BITS_PER_LIMB; - // assert(acc <= 1ul << shift); - - // clang-format off - // acc == 1 << shift - // Proof: - // acc - // == (1 << BITS_PER_LIMB * (LIMBS + 1) + shift) + res[d : d + LIMBS + 1] - coef * prime[:LIMBS] >> BITS_PER_LIMB * (LIMBS + 1) - // == (1 << BITS_PER_LIMB * (LIMBS + 1) + shift) + res[d : d + LIMBS + 1] - coef * prime >> BITS_PER_LIMB * (LIMBS + 1) - - // == (1 << BITS_PER_LIMB * (LIMBS + 1) + shift) + (res[d : d + LIMBS + 1] - coef * prime) >> BITS_PER_LIMB * (LIMBS + 1) - // <= (1 << BITS_PER_LIMB * (LIMBS + 1) + shift) + (res[:d] + BASE**d * res[d : d + LIMBS + 1] - BASE**d * coef * prime)//BASE**d >> BITS_PER_LIMB * (LIMBS + 1) - // <= (1 << BITS_PER_LIMB * (LIMBS + 1) + shift) + (res - BASE**d * coef * prime) // BASE**d >> BITS_PER_LIMB * (LIMBS + 1) - // == (1 << BITS_PER_LIMB * (LIMBS + 1) + shift) + (2 * prime * BASE**d) // BASE**d >> BITS_PER_LIMB * (LIMBS + 1) - // <= (1 << 321) + 2 * 2**256 >> 290 - // == 1 << 31 == 1 << shift - - // == (1 << BITS_PER_LIMB * (LIMBS + 1) + shift) + res[d : d + LIMBS + 1] - coef * prime[:LIMBS + 1] >> BITS_PER_LIMB * (LIMBS + 1) - // >= (1 << BITS_PER_LIMB * (LIMBS + 1) + shift) + 0 >> BITS_PER_LIMB * (LIMBS + 1) - // == 1 << shift - // clang-format on - - res[d + BN_LIMBS] = 0; -} - -// Auxiliary function for bn_multiply -// Partly reduces res and stores both in x and res -// Assumes res in normalized and res < 2**519 -// Guarantees x is normalized and partly reduced modulo prime -// Assumes prime is normalized, 2**256 - 2**224 <= prime <= 2**256 -void bn_multiply_reduce(bignum256 *x, uint32_t res[2 * BN_LIMBS], - const bignum256 *prime) { - for (int i = BN_LIMBS - 1; i >= 0; i--) { - // res < 2**(256 + 29*i + 31) - // Proof: - // if i == LIMBS - 1: - // res < 2**519 - // == 2**(256 + 29 * 8 + 31) - // == 2**(256 + 29 * (LIMBS - 1) + 31) - // else: - // res < 2 * prime * 2**(29 * (i + 1)) - // <= 2**256 * 2**(29*i + 29) < 2**(256 + 29*i + 31) - bn_multiply_reduce_step(res, prime, i); - } - - for (int i = 0; i < BN_LIMBS; i++) { - x->val[i] = res[i]; - } -} - -// x = k * x % prime -// Assumes k, x are normalized, k * x < 2**519 -// Guarantees x is normalized and partly reduced modulo prime -// Assumes prime is normalized, 2**256 - 2**224 <= prime <= 2**256 -void bn_multiply(const bignum256 *k, bignum256 *x, const bignum256 *prime) { - uint32_t res[2 * BN_LIMBS] = {0}; - - bn_multiply_long(k, x, res); - bn_multiply_reduce(x, res, prime); - - memzero(res, sizeof(res)); -} - -// Partly reduces x modulo prime -// Assumes limbs of x except the last (the most significant) one are normalized -// Assumes prime is normalized and 2^256 - 2^224 <= prime <= 2^256 -// Guarantees x is normalized and partly reduced modulo prime -void bn_fast_mod(bignum256 *x, const bignum256 *prime) { - // Computes x = x - (x // 2**256) * prime - - // x < 2**((LIMBS - 1) * BITS_PER_LIMB + 32) == 2**264 - - // x - (x // 2**256) * prime < 2 * prime - // Proof: - // x - (x // 2**256) * prime - // == x - (x // 2**256) * (2**256 - (2**256 - prime)) - // == x - ((x // 2**256) * 2**256) + (x // 2**256) * (2**256 - prime) - // == (x % prime) + (x // 2**256) * (2**256 - prime) - // <= prime - 1 + (2**264 // 2**256) * (2**256 - prime) - // <= 2**256 + 2**8 * 2**224 == 2**256 + 2**232 - // < 2 * (2**256 - 2**224) - // <= 2 * prime - - // x - (x // 2**256 - 1) * prime < 2 * prime - // Proof: - // x - (x // 2**256) * prime + prime - // == x - (x // 2**256) * (2**256 - (2**256 - prime)) + prime - // == x - ((x//2**256) * 2**256) + (x//2**256) * (2**256 - prime) + prime - // == (x % prime) + (x // 2**256) * (2**256 - prime) + prime - // <= 2 * prime - 1 + (2**264 // 2**256) * (2**256 - prime) - // <= 2 * prime + 2**8 * 2**224 == 2**256 + 2**232 + 2**256 - 2**224 - // < 2 * (2**256 - 2**224) - // <= 2 * prime - - uint32_t coef = - x->val[BN_LIMBS - 1] >> (256 - ((BN_LIMBS - 1) * BN_BITS_PER_LIMB)); - - // clang-format off - // coef == x // 2**256 - // 0 <= coef < 2**((LIMBS - 1) * BITS_PER_LIMB + 32 - 256) == 256 - // Proof: - //* Let x[[a : b] be the number consisting of a-th to (b-1)-th bit of the number x. - // x[LIMBS - 1] >> (256 - ((LIMBS - 1) * BITS_PER_LIMB)) - // == x[[(LIMBS - 1) * BITS_PER_LIMB : (LIMBS - 1) * BITS_PER_LIMB + 32]] >> (256 - ((LIMBS - 1) * BITS_PER_LIMB)) - // == x[[256 - ((LIMBS - 1) * BITS_PER_LIMB) + (LIMBS - 1) * BITS_PER_LIMB : (LIMBS - 1) * BITS_PER_LIMB + 32]] - // == x[[256 : (LIMBS - 1) * BITS_PER_LIMB + 32]] - // == x[[256 : 264]] == x // 2**256 - // clang-format on - - const int shift = 8; - uint64_t acc = 1ull << shift; - - for (int i = 0; i < BN_LIMBS; i++) { - acc += (((uint64_t)(BN_BASE - 1)) << shift) + x->val[i] - - prime->val[i] * (uint64_t)coef; - // acc neither overflows 64 bits nor underflows 0 - // Proof: - // acc + (BASE - 1 << shift) + x[i] - prime[i] * coef - // >= (BASE - 1 << shift) - prime[i] * coef - // >= 2**shift * (2**BITS_PER_LIMB - 1) - (2**BITS_PER_LIMB - 1) * 255 - // == (2**shift - 255) * (2**BITS_PER_LIMB - 1) - // == (2**8 - 255) * (2**29 - 1) == 2**29 - 1 >= 0 - // acc + (BASE - 1 << shift) + x[i] - prime[i] * coef - // <= acc + ((BASE - 1) << shift) + x[i] - // <= (2**(64 - BITS_PER_LIMB) - 1) + 2**shift * (2**BITS_PER_LIMB - 1) - // + (2**32 - 1) - // == (2**35 - 1) + 2**8 * (2**29 - 1) + 2**32 - // < 2**35 + 2**37 + 2**32 < 2**64 - - x->val[i] = acc & BN_LIMB_MASK; - acc >>= BN_BITS_PER_LIMB; - // acc <= 2**(64 - BITS_PER_LIMB) - 1 == 2**35 - 1 - - // acc == (1 << BITS_PER_LIMB * (i + 1) + shift) + x[:i + 1] - // - coef * prime[:i + 1] >> BITS_PER_LIMB * (i + 1) - } - - // assert(acc == 1 << shift); - - // clang-format off - // acc == 1 << shift - // Proof: - // acc - // == (1 << BITS_PER_LIMB * LIMBS + shift) + x[:LIMBS] - coef * prime[:LIMBS] >> BITS_PER_LIMB * LIMBS - // == (1 << BITS_PER_LIMB * LIMBS + shift) + (x - coef * prime) >> BITS_PER_LIMB * LIMBS - // <= (1 << BITS_PER_LIMB * LIMBS + shift) + (2 * prime) >> BITS_PER_LIMB * LIMBS - // <= (1 << BITS_PER_LIMB * LIMBS + shift) + 2 * 2**256 >> BITS_PER_LIMB * LIMBS - // <= 2**269 + 2**257 >> 2**261 - // <= 1 << 8 == 1 << shift - - // acc - // == (1 << BITS_PER_LIMB * LIMBS + shift) + x[:LIMBS] - coef * prime[:LIMBS] >> BITS_PER_LIMB * LIMBS - // >= (1 << BITS_PER_LIMB * LIMBS + shift) + 0 >> BITS_PER_LIMB * LIMBS - // == (1 << BITS_PER_LIMB * LIMBS + shift) + 0 >> BITS_PER_LIMB * LIMBS - // <= 1 << 8 == 1 << shift - // clang-format on -} - -// res = x**e % prime -// Assumes both x and e are normalized, x < 2**259 -// Guarantees res is normalized and partly reduced modulo prime -// Works properly even if &x == &res -// Assumes prime is normalized, 2**256 - 2**224 <= prime <= 2**256 -// The function doesn't have neither constant control flow nor constant memory -// access flow with regard to e -void bn_power_mod(const bignum256 *x, const bignum256 *e, - const bignum256 *prime, bignum256 *res) { - // Uses iterative right-to-left exponentiation by squaring, see - // https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method - - bignum256 acc = {0}; - bn_copy(x, &acc); - - bn_one(res); - for (int i = 0; i < BN_LIMBS; i++) { - uint32_t limb = e->val[i]; - - for (int j = 0; j < BN_BITS_PER_LIMB; j++) { - // Break if the following bits of the last limb are zero - if (i == BN_LIMBS - 1 && limb == 0) break; - - if (limb & 1) - // acc * res < 2**519 - // Proof: - // acc * res <= max(2**259 - 1, 2 * prime) * (2 * prime) - // == max(2**259 - 1, 2**257) * 2**257 < 2**259 * 2**257 - // == 2**516 < 2**519 - bn_multiply(&acc, res, prime); - - limb >>= 1; - // acc * acc < 2**519 - // Proof: - // acc * acc <= max(2**259 - 1, 2 * prime)**2 - // <= (2**259)**2 == 2**518 < 2**519 - bn_multiply(&acc, &acc, prime); - } - // acc == x**(e[:i + 1]) % prime - } - - memzero(&acc, sizeof(acc)); -} - -// x = sqrt(x) % prime -// Explicitly x = x**((prime+1)/4) % prime -// The other root is -sqrt(x) -// Assumes x is normalized, x < 2**259 and quadratic residuum mod prime -// Assumes prime is a prime number, prime % 4 == 3, it is normalized and -// 2**256 - 2**224 <= prime <= 2**256 -// Guarantees x is normalized and fully reduced modulo prime -// The function doesn't have neither constant control flow nor constant memory -// access flow with regard to prime -void bn_sqrt(bignum256 *x, const bignum256 *prime) { - // Uses the Lagrange formula for the primes of the special form, see - // http://en.wikipedia.org/wiki/Quadratic_residue#Prime_or_prime_power_modulus - // If prime % 4 == 3, then sqrt(x) % prime == x**((prime+1)//4) % prime - - assert(prime->val[BN_LIMBS - 1] % 4 == 3); - - // e = (prime + 1) // 4 - bignum256 e = {0}; - bn_copy(prime, &e); - bn_addi(&e, 1); - bn_rshift(&e); - bn_rshift(&e); - - bn_power_mod(x, &e, prime, x); - bn_mod(x, prime); - - memzero(&e, sizeof(e)); -} - -// a = 1/a % 2**n -// Assumes a is odd, 1 <= n <= 32 -// The function doesn't have neither constant control flow nor constant memory -// access flow with regard to n -uint32_t inverse_mod_power_two(uint32_t a, uint32_t n) { - // Uses "Explicit Quadratic Modular inverse modulo 2" from section 3.3 of "On - // Newton-Raphson iteration for multiplicative inverses modulo prime powers" - // by Jean-Guillaume Dumas, see - // https://arxiv.org/pdf/1209.6626.pdf - - // 1/a % 2**n - // = (2-a) * product([1 + (a-1)**(2**i) for i in range(1, floor(log2(n)))]) - - uint32_t acc = 2 - a; - uint32_t f = a - 1; - - // mask = (1 << n) - 1 - uint32_t mask = n == 32 ? 0xFFFFFFFF : (1u << n) - 1; - - for (uint32_t i = 1; i < n; i <<= 1) { - f = (f * f) & mask; - acc = (acc * (1 + f)) & mask; - } - - return acc; -} - -// x = (x / 2**BITS_PER_LIMB) % prime -// Assumes both x and prime are normalized -// Assumes prime is an odd number and normalized -// Guarantees x is normalized -// If x is partly reduced (fully reduced) modulo prime, -// guarantess x will be partly reduced (fully reduced) modulo prime -void bn_divide_base(bignum256 *x, const bignum256 *prime) { - // Uses an explicit formula for the modular inverse of power of two - // (x / 2**n) % prime == (x + ((-x / prime) % 2**n) * prime) // 2**n - // Proof: - // (x + ((-x / prime) % 2**n) * prime) % 2**n - // == (x - x / prime * prime) % 2**n - // == 0 - // (x + ((-1 / prime) % 2**n) * prime) % prime - // == x - // if x < prime: - // (x + ((-x / prime) % 2**n) * prime) // 2**n - // <= ((prime - 1) + (2**n - 1) * prime) / 2**n - // == (2**n * prime - 1) / 2**n == prime - 1 / 2**n < prime - // if x < 2 * prime: - // (x + ((-x / prime) % 2**n) * prime) // 2**n - // <= ((2 * prime - 1) + (2**n - 1) * prime) / 2**n - // == (2**n * prime + prime - 1) / 2**n - // == prime + (prime - 1) / 2**n < 2 * prime - - // m = (-x / prime) % 2**BITS_PER_LIMB - uint32_t m = (x->val[0] * (BN_BASE - inverse_mod_power_two( - prime->val[0], BN_BITS_PER_LIMB))) & - BN_LIMB_MASK; - // m < 2**BITS_PER_LIMB - - uint64_t acc = x->val[0] + (uint64_t)m * prime->val[0]; - acc >>= BN_BITS_PER_LIMB; - - for (int i = 1; i < BN_LIMBS; i++) { - acc = acc + x->val[i] + (uint64_t)m * prime->val[i]; - // acc does not overflow 64 bits - // acc == acc + x + m * prime - // <= 2**(64 - BITS_PER_LIMB) + 2**(BITS_PER_LIMB) - // 2**(BITS_PER_LIMB) * 2**(BITS_PER_LIMB) - // <= 2**(2 * BITS_PER_LIMB) + 2**(64 - BITS_PER_LIMB) + - // 2**(BITS_PER_LIMB) - // <= 2**58 + 2**35 + 2**29 < 2**64 - - x->val[i - 1] = acc & BN_LIMB_MASK; - acc >>= BN_BITS_PER_LIMB; - // acc < 2**35 == 2**(64 - BITS_PER_LIMB) - - // acc == x[:i + 1] + m * prime[:i + 1] >> BITS_PER_LIMB * (i + 1) - } - - x->val[BN_LIMBS - 1] = acc; - - assert(acc >> BN_BITS_PER_LIMB == 0); - - // clang-format off - // acc >> BITS_PER_LIMB == 0 - // Proof: - // acc >> BITS_PER_LIMB - // == (x[:LIMB] + m * prime[:LIMB] >> BITS_PER_LIMB * LIMBS) >> BITS_PER_LIMB * (LIMBS + 1) - // == x + m * prime >> BITS_PER_LIMB * (LIMBS + 1) - // <= (2**(BITS_PER_LIMB * LIMBS) - 1) + (2**BITS_PER_LIMB - 1) * (2**(BITS_PER_LIMB * LIMBS) - 1) >> BITS_PER_LIMB * (LIMBS + 1) - // == 2**(BITS_PER_LIMB * LIMBS) - 1 + 2**(BITS_PER_LIMB * (LIMBS + 1)) - 2**(BITS_PER_LIMB * LIMBS) - 2**BITS_PER_LIMB + 1 >> BITS_PER_LIMB * (LIMBS + 1) - // == 2**(BITS_PER_LIMB * (LIMBS + 1)) - 2**BITS_PER_LIMB >> BITS_PER_LIMB * (LIMBS + 1) - // == 0 - // clang-format on -} - -// x = 1/x % prime if x != 0 else 0 -// Assumes x is normalized -// Assumes prime is a prime number -// Guarantees x is normalized and fully reduced modulo prime -// Assumes prime is normalized, 2**256 - 2**224 <= prime <= 2**256 -// The function doesn't have neither constant control flow nor constant memory -// access flow with regard to prime -void bn_inverse_slow(bignum256 *x, const bignum256 *prime) { - // Uses formula 1/x % prime == x**(prime - 2) % prime - // See https://en.wikipedia.org/wiki/Fermat%27s_little_theorem - - bn_fast_mod(x, prime); - - // e = prime - 2 - bignum256 e = {0}; - bn_read_uint32(2, &e); - bn_subtract(prime, &e, &e); - - bn_power_mod(x, &e, prime, x); - bn_mod(x, prime); - - memzero(&e, sizeof(e)); -} - -#if false -// x = 1/x % prime if x != 0 else 0 -// Assumes x is is_normalized -// Assumes GCD(x, prime) = 1 -// Guarantees x is normalized and fully reduced modulo prime -// Assumes prime is odd, normalized, 2**256 - 2**224 <= prime <= 2**256 -// The function doesn't have neither constant control flow nor constant memory -// access flow with regard to prime and x -void bn_inverse_fast(bignum256 *x, const bignum256 *prime) { - // "The Almost Montgomery Inverse" from the section 3 of "Constant Time - // Modular Inversion" by Joppe W. Bos - // See http://www.joppebos.com/files/CTInversion.pdf - - /* - u = prime - v = x & prime - s = 1 - r = 0 - - k = 0 - while v != 1: - k += 1 - if is_even(u): - u = u // 2 - s = 2 * s - elif is_even(v): - v = v // 2 - r = 2 * r - elif v < u: - u = (u - v) // 2 - r = r + s - s = 2 * s - else: - v = (v - u) // 2 - s = r + s - r = 2 * r - - s = (s / 2**k) % prime - return s - */ - - if (bn_is_zero(x)) return; - - bn_fast_mod(x, prime); - bn_mod(x, prime); - - bignum256 u = {0}, v = {0}, r = {0}, s = {0}; - bn_copy(prime, &u); - bn_copy(x, &v); - bn_one(&s); - bn_zero(&r); - - int k = 0; - while (!bn_is_one(&v)) { - if ((u.val[0] & 1) == 0) { - bn_rshift(&u); - bn_lshift(&s); - } else if ((v.val[0] & 1) == 0) { - bn_rshift(&v); - bn_lshift(&r); - } else if (bn_is_less(&v, &u)) { - bn_subtract(&u, &v, &u); - bn_rshift(&u); - bn_add(&r, &s); - bn_lshift(&s); - } else { - bn_subtract(&v, &u, &v); - bn_rshift(&v); - bn_add(&s, &r); - bn_lshift(&r); - } - k += 1; - assert(!bn_is_zero(&v)); // assert GCD(x, prime) == 1 - } - - // s = s / 2**(k // BITS_PER_LIMB * BITS_PER_LIMB) - for (int i = 0; i < k / BITS_PER_LIMB; i++) { - bn_divide_base(&s, prime); - } - - // s = s / 2**(k % BITS_PER_LIMB) - for (int i = 0; i < k % BN_BITS_PER_LIMB; i++) { - bn_mult_half(&s, prime); - } - - bn_copy(&s, x); - - memzero(&u, sizeof(u)); - memzero(&v, sizeof(v)); - memzero(&r, sizeof(r)); - memzero(&s, sizeof(s)); -} -#endif - -// x = 1/x % prime if x != 0 else 0 -// Assumes x is is_normalized -// Assumes GCD(x, prime) = 1 -// Guarantees x is normalized and fully reduced modulo prime -// Assumes prime is odd, normalized, 2**256 - 2**224 <= prime <= 2**256 -// The function has constant control flow but not constant memory access flow -// with regard to prime and x -void bn_inverse_fast(bignum256 *x, const bignum256 *prime) { - // Custom constant time version of "The Almost Montgomery Inverse" from the - // section 3 of "Constant Time Modular Inversion" by Joppe W. Bos - // See http://www.joppebos.com/files/CTInversion.pdf - - /* - u = prime - v = x % prime - s = 1 - r = 0 - - k = 0 - while v != 1: - k += 1 - if is_even(u): # b1 - u = u // 2 - s = 2 * s - elif is_even(v): # b2 - v = v // 2 - r = 2 * r - elif v < u: # b3 - u = (u - v) // 2 - r = r + s - s = 2 * s - else: # b4 - v = (v - u) // 2 - s = r + s - r = 2 * r - - s = (s / 2**k) % prime - return s - */ - - bn_fast_mod(x, prime); - bn_mod(x, prime); - - bignum256 u = {0}, v = {0}, r = {0}, s = {0}; - bn_copy(prime, &u); - bn_copy(x, &v); - bn_one(&s); - bn_zero(&r); - - bignum256 zero = {0}; - bn_zero(&zero); - - int k = 0; - - int finished = 0, u_even = 0, v_even = 0, v_less_u = 0, b1 = 0, b2 = 0, - b3 = 0, b4 = 0; - finished = 0; - - for (int i = 0; i < 2 * BN_LIMBS * BN_BITS_PER_LIMB; i++) { - finished = finished | -bn_is_one(&v); - u_even = -bn_is_even(&u); - v_even = -bn_is_even(&v); - v_less_u = -bn_is_less(&v, &u); - - b1 = ~finished & u_even; - b2 = ~finished & ~b1 & v_even; - b3 = ~finished & ~b1 & ~b2 & v_less_u; - b4 = ~finished & ~b1 & ~b2 & ~b3; - -// The ternary operator for pointers with constant control flow -// BN_INVERSE_FAST_TERNARY(c, t, f) = t if c else f -// Very nasty hack, sorry for that -#define BN_INVERSE_FAST_TERNARY(c, t, f) \ - ((void *)(((c) & (uintptr_t)(t)) | (~(c) & (uintptr_t)(f)))) - - bn_subtract(BN_INVERSE_FAST_TERNARY(b3, &u, &v), - BN_INVERSE_FAST_TERNARY( - b3 | b4, BN_INVERSE_FAST_TERNARY(b3, &v, &u), &zero), - BN_INVERSE_FAST_TERNARY(b3, &u, &v)); - - bn_add(BN_INVERSE_FAST_TERNARY(b3, &r, &s), - BN_INVERSE_FAST_TERNARY(b3 | b4, BN_INVERSE_FAST_TERNARY(b3, &s, &r), - &zero)); - bn_rshift(BN_INVERSE_FAST_TERNARY(b1 | b3, &u, &v)); - bn_lshift(BN_INVERSE_FAST_TERNARY(b1 | b3, &s, &r)); - - k = k - ~finished; - } - - // s = s / 2**(k // BITS_PER_LIMB * BITS_PER_LIMB) - for (int i = 0; i < 2 * BN_LIMBS; i++) { - // s = s / 2**BITS_PER_LIMB % prime if i < k // BITS_PER_LIMB else s - bn_copy(&s, &r); - bn_divide_base(&r, prime); - bn_cmov(&s, i < k / BN_BITS_PER_LIMB, &r, &s); - } - - // s = s / 2**(k % BITS_PER_LIMB) - for (int i = 0; i < BN_BITS_PER_LIMB; i++) { - // s = s / 2 % prime if i < k % BITS_PER_LIMB else s - bn_copy(&s, &r); - bn_mult_half(&r, prime); - bn_cmov(&s, i < k % BN_BITS_PER_LIMB, &r, &s); - } - - bn_cmov(x, bn_is_zero(x), x, &s); - - memzero(&u, sizeof(u)); - memzero(&v, sizeof(v)); - memzero(&r, sizeof(s)); - memzero(&s, sizeof(s)); -} - -#if false -// x = 1/x % prime if x != 0 else 0 -// Assumes x is is_normalized -// Assumes GCD(x, prime) = 1 -// Guarantees x is normalized and fully reduced modulo prime -// Assumes prime is odd, normalized, 2**256 - 2**224 <= prime <= 2**256 -void bn_inverse_fast(bignum256 *x, const bignum256 *prime) { - // Custom constant time version of "The Almost Montgomery Inverse" from the - // section 3 of "Constant Time Modular Inversion" by Joppe W. Bos - // See http://www.joppebos.com/files/CTInversion.pdf - - /* - u = prime - v = x % prime - s = 1 - r = 0 - - k = 0 - while v != 1: - k += 1 - if is_even(u): # b1 - u = u // 2 - s = 2 * s - elif is_even(v): # b2 - v = v // 2 - r = 2 * r - elif v < u: # b3 - u = (u - v) // 2 - r = r + s - s = 2 * s - else: # b4 - v = (v - u) // 2 - s = r + s - r = 2 * r - - s = (s / 2**k) % prime - return s - */ - - bn_fast_mod(x, prime); - bn_mod(x, prime); - - bignum256 u = {0}, v = {0}, r = {0}, s = {0}; - bn_copy(prime, &u); - bn_copy(x, &v); - bn_one(&s); - bn_zero(&r); - - bignum256 zero = {0}; - bn_zero(&zero); - - int k = 0; - - uint32_t finished = 0, u_even = 0, v_even = 0, v_less_u = 0, b1 = 0, b2 = 0, - b3 = 0, b4 = 0; - finished = 0; - - bignum256 u_half = {0}, v_half = {0}, u_minus_v_half = {0}, v_minus_u_half = {0}, r_plus_s = {0}, r_twice = {0}, s_twice = {0}; - for (int i = 0; i < 2 * BN_LIMBS * BN_BITS_PER_LIMB; i++) { - finished = finished | bn_is_one(&v); - u_even = bn_is_even(&u); - v_even = bn_is_even(&v); - v_less_u = bn_is_less(&v, &u); - - b1 = (finished ^ 1) & u_even; - b2 = (finished ^ 1) & (b1 ^ 1) & v_even; - b3 = (finished ^ 1) & (b1 ^ 1) & (b2 ^ 1) & v_less_u; - b4 = (finished ^ 1) & (b1 ^ 1) & (b2 ^ 1) & (b3 ^ 1); - - // u_half = u // 2 - bn_copy(&u, &u_half); - bn_rshift(&u_half); - - // v_half = v // 2 - bn_copy(&v, &v_half); - bn_rshift(&v_half); - - // u_minus_v_half = (u - v) // 2 - bn_subtract(&u, &v, &u_minus_v_half); - bn_rshift(&u_minus_v_half); - - // v_minus_u_half = (v - u) // 2 - bn_subtract(&v, &u, &v_minus_u_half); - bn_rshift(&v_minus_u_half); - - // r_plus_s = r + s - bn_copy(&r, &r_plus_s); - bn_add(&r_plus_s, &s); - - // r_twice = 2 * r - bn_copy(&r, &r_twice); - bn_lshift(&r_twice); - - // s_twice = 2 * s - bn_copy(&s, &s_twice); - bn_lshift(&s_twice); - - bn_cmov(&u, b1, &u_half, &u); - bn_cmov(&u, b3, &u_minus_v_half, &u); - - bn_cmov(&v, b2, &v_half, &v); - bn_cmov(&v, b4, &v_minus_u_half, &v); - - bn_cmov(&r, b2 | b4, &r_twice, &r); - bn_cmov(&r, b3, &r_plus_s, &r); - - bn_cmov(&s, b1 | b3, &s_twice, &s); - bn_cmov(&s, b4, &r_plus_s, &s); - - k = k + (finished ^ 1); - } - - // s = s / 2**(k // BITS_PER_LIMB * BITS_PER_LIMB) - for (int i = 0; i < 2 * BN_LIMBS; i++) { - // s = s / 2**BITS_PER_LIMB % prime if i < k // BITS_PER_LIMB else s - bn_copy(&s, &r); - bn_divide_base(&r, prime); - bn_cmov(&s, i < k / BITS_PER_LIMB, &r, &s); - } - - // s = s / 2**(k % BITS_PER_LIMB) - for (int i = 0; i < BN_BITS_PER_LIMB; i++) { - // s = s / 2 % prime if i < k % BITS_PER_LIMB else s - bn_copy(&s, &r); - bn_mult_half(&r, prime); - bn_cmov(&s, i < k % BN_BITS_PER_LIMB, &r, &s); - } - - bn_cmov(x, bn_is_zero(x), x, &s); - - memzero(&u, sizeof(u)); - memzero(&v, sizeof(v)); - memzero(&r, sizeof(r)); - memzero(&s, sizeof(s)); - memzero(&u_half, sizeof(u_half)); - memzero(&v_half, sizeof(v_half)); - memzero(&u_minus_v_half, sizeof(u_minus_v_half)); - memzero(&v_minus_u_half, sizeof(v_minus_u_half)); - memzero(&r_twice, sizeof(r_twice)); - memzero(&s_twice, sizeof(s_twice)); - memzero(&r_plus_s, sizeof(r_plus_s)); -} -#endif - -// Normalizes x -// Assumes x < 2**261 == 2**(LIMBS * BITS_PER_LIMB) -// Guarantees x is normalized -void bn_normalize(bignum256 *x) { - uint32_t acc = 0; - - for (int i = 0; i < BN_LIMBS; i++) { - acc += x->val[i]; - // acc doesn't overflow 32 bits - // Proof: - // acc + x[i] - // <= (2**(32 - BITS_PER_LIMB) - 1) + (2**BITS_PER_LIMB - 1) - // == 7 + 2**29 - 1 < 2**32 - - x->val[i] = acc & BN_LIMB_MASK; - acc >>= (BN_BITS_PER_LIMB); - // acc <= 7 == 2**(32 - BITS_PER_LIMB) - 1 - } -} - -// x = x + y -// Assumes x, y are normalized, x + y < 2**(LIMBS*BITS_PER_LIMB) == 2**261 -// Guarantees x is normalized -// Works properly even if &x == &y -void bn_add(bignum256 *x, const bignum256 *y) { - uint32_t acc = 0; - for (int i = 0; i < BN_LIMBS; i++) { - acc += x->val[i] + y->val[i]; - // acc doesn't overflow 32 bits - // Proof: - // acc + x[i] + y[i] - // <= (2**(32 - BITS_PER_LIMB) - 1) + 2 * (2**BITS_PER_LIMB - 1) - // == (2**(32 - BITS_PER_LIMB) - 1) + 2**(BITS_PER_LIMB + 1) - 2 - // == 7 + 2**30 - 2 < 2**32 - - x->val[i] = acc & BN_LIMB_MASK; - acc >>= BN_BITS_PER_LIMB; - // acc <= 7 == 2**(32 - BITS_PER_LIMB) - 1 - - // acc == x[:i + 1] + y[:i + 1] >> BITS_PER_LIMB * (i + 1) - } - - // assert(acc == 0); // assert x + y < 2**261 - // acc == 0 - // Proof: - // acc == x[:LIMBS] + y[:LIMBS] >> LIMBS * BITS_PER_LIMB - // == x + y >> LIMBS * BITS_PER_LIMB - // <= 2**(LIMBS * BITS_PER_LIMB) - 1 >> LIMBS * BITS_PER_LIMB == 0 -} - -// x = x + y % prime -// Assumes x, y are normalized -// Guarantees x is normalized and partly reduced modulo prime -// Assumes prime is normalized and 2^256 - 2^224 <= prime <= 2^256 -void bn_addmod(bignum256 *x, const bignum256 *y, const bignum256 *prime) { - for (int i = 0; i < BN_LIMBS; i++) { - x->val[i] += y->val[i]; - // x[i] doesn't overflow 32 bits - // Proof: - // x[i] + y[i] - // <= 2 * (2**BITS_PER_LIMB - 1) - // == 2**30 - 2 < 2**32 - } - - bn_fast_mod(x, prime); -} - -// x = x + y -// Assumes x is normalized -// Assumes y <= 2**32 - 2**29 == 2**32 - 2**BITS_PER_LIMB and -// x + y < 2**261 == 2**(LIMBS * BITS_PER_LIMB) -// Guarantees x is normalized -void bn_addi(bignum256 *x, uint32_t y) { - // assert(y <= 3758096384); // assert y <= 2**32 - 2**29 - uint32_t acc = y; - - for (int i = 0; i < BN_LIMBS; i++) { - acc += x->val[i]; - // acc doesn't overflow 32 bits - // Proof: - // if i == 0: - // acc + x[i] == y + x[0] - // <= (2**32 - 2**BITS_PER_LIMB) + (2**BITS_PER_LIMB - 1) - // == 2**32 - 1 < 2**32 - // else: - // acc + x[i] - // <= (2**(32 - BITS_PER_LIMB) - 1) + (2**BITS_PER_LIMB - 1) - // == 7 + 2**29 - 1 < 2**32 - - x->val[i] = acc & BN_LIMB_MASK; - acc >>= (BN_BITS_PER_LIMB); - // acc <= 7 == 2**(32 - BITS_PER_LIMB) - 1 - - // acc == x[:i + 1] + y >> BITS_PER_LIMB * (i + 1) - } - - // assert(acc == 0); // assert x + y < 2**261 - // acc == 0 - // Proof: - // acc == x[:LIMBS] + y << LIMBS * BITS_PER_LIMB - // == x + y << LIMBS * BITS_PER_LIMB - // <= 2**(LIMBS + BITS_PER_LIMB) - 1 << LIMBS * BITS_PER_LIMB - // == 0 -} - -// x = x - y % prime -// Explicitly x = x + prime - y -// Assumes x, y are normalized -// Assumes y < prime[0], x + prime - y < 2**261 == 2**(LIMBS * BITS_PER_LIMB) -// Guarantees x is normalized -// If x is fully reduced modulo prime, -// guarantess x will be partly reduced modulo prime -// Assumes prime is nonzero and normalized -void bn_subi(bignum256 *x, uint32_t y, const bignum256 *prime) { - assert(y < prime->val[0]); - - // x = x + prime - y - - uint32_t acc = -y; - for (int i = 0; i < BN_LIMBS; i++) { - acc += x->val[i] + prime->val[i]; - // acc neither overflows 32 bits nor underflows 0 - // Proof: - // acc + x[i] + prime[i] - // <= (2**(32 - BITS_PER_LIMB) - 1) + 2 * (2**BITS_PER_LIMB - 1) - // <= 7 + 2**30 - 2 < 2**32 - // acc + x[i] + prime[i] - // >= -y + prime[0] >= 0 - - x->val[i] = acc & BN_LIMB_MASK; - acc >>= BN_BITS_PER_LIMB; - // acc <= 7 == 2**(32 - BITS_PER_LIMB) - 1 - - // acc == x[:i + 1] + prime[:i + 1] - y >> BITS_PER_LIMB * (i + 1) - } - - // assert(acc == 0); // assert x + prime - y < 2**261 - // acc == 0 - // Proof: - // acc == x[:LIMBS] + prime[:LIMBS] - y >> BITS_PER_LIMB * LIMBS - // == x + prime - y >> BITS_PER_LIMB * LIMBS - // <= 2**(LIMBS * BITS_PER_LIMB) - 1 >> BITS_PER_LIMB * LIMBS == 0 -} - -// res = x - y % prime -// Explicitly res = x + (2 * prime - y) -// Assumes x, y are normalized, y is partly reduced -// Assumes x + 2 * prime - y < 2**261 == 2**(BITS_PER_LIMB * LIMBS) -// Guarantees res is normalized -// Assumes prime is nonzero and normalized -void bn_subtractmod(const bignum256 *x, const bignum256 *y, bignum256 *res, - const bignum256 *prime) { - // res = x + (2 * prime - y) - - uint32_t acc = 1; - - for (int i = 0; i < BN_LIMBS; i++) { - acc += (BN_BASE - 1) + x->val[i] + 2 * prime->val[i] - y->val[i]; - // acc neither overflows 32 bits nor underflows 0 - // Proof: - // acc + (BASE - 1) + x[i] + 2 * prime[i] - y[i] - // >= (BASE - 1) - y[i] - // == (2**BITS_PER_LIMB - 1) - (2**BITS_PER_LIMB - 1) == 0 - // acc + (BASE - 1) + x[i] + 2 * prime[i] - y[i] - // <= acc + (BASE - 1) + x[i] + 2 * prime[i] - // <= (2**(32 - BITS_PER_LIMB) - 1) + (2**BITS_PER_LIMB - 1) + - // (2**BITS_PER_LIMB - 1) + 2 * (2**BITS_PER_LIMB - 1) - // <= (2**(32 - BITS_PER_LIMB) - 1) + 4 * (2**BITS_PER_LIMB - 1) - // == 7 + 4 * 2**29 - 4 == 2**31 + 3 < 2**32 - - res->val[i] = acc & (BN_BASE - 1); - acc >>= BN_BITS_PER_LIMB; - // acc <= 7 == 2**(32 - BITS_PER_LIMB) - 1 - - // acc == 2**(BITS_PER_LIMB * (i + 1)) + x[:i+1] - y[:i+1] + 2*prime[:i+1] - // >> BITS_PER_LIMB * (i+1) - } - - // assert(acc == 1); // assert x + 2 * prime - y < 2**261 - - // clang-format off - // acc == 1 - // Proof: - // acc == 2**(BITS_PER_LIMB * LIMBS) + x[:LIMBS] - y[:LIMBS] + 2 * prime[:LIMBS] >> BITS_PER_LIMB * LIMBS - // == 2**(BITS_PER_LIMB * LIMBS) + x - y + 2 * prime >> BITS_PER_LIMB * LIMBS - // == 2**(BITS_PER_LIMB * LIMBS) + x + (2 * prime - y) >> BITS_PER_LIMB * LIMBS - // <= 2**(BITS_PER_LIMB * LIMBS) + 2**(BITS_PER_LIMB * LIMBS) - 1 >> BITS_PER_LIMB * LIMBS - // <= 2 * 2**(BITS_PER_LIMB * LIMBS) - 1 >> BITS_PER_LIMB * LIMBS - // == 1 - - // acc == 2**(BITS_PER_LIMB * LIMBS) + x[:LIMBS] - y[:LIMBS] + 2 * prime[:LIMBS] >> BITS_PER_LIMB * LIMBS - // == 2**(BITS_PER_LIMB * LIMBS) + x - y + 2 * prime >> BITS_PER_LIMB * LIMBS - // == 2**(BITS_PER_LIMB * LIMBS) + x + (2 * prime - y) >> BITS_PER_LIMB * LIMBS - // >= 2**(BITS_PER_LIMB * LIMBS) + 0 + 1 >> BITS_PER_LIMB * LIMBS - // == 1 - // clang-format on -} - -// res = x - y -// Assumes x, y are normalized and x >= y -// Guarantees res is normalized -// Works properly even if &x == &y or &x == &res or &y == &res or -// &x == &y == &res -void bn_subtract(const bignum256 *x, const bignum256 *y, bignum256 *res) { - uint32_t acc = 1; - for (int i = 0; i < BN_LIMBS; i++) { - acc += (BN_BASE - 1) + x->val[i] - y->val[i]; - // acc neither overflows 32 bits nor underflows 0 - // Proof: - // acc + (BASE - 1) + x[i] - y[i] - // >= (BASE - 1) - y == (2**BITS_PER_LIMB - 1) - (2**BITS_PER_LIMB - 1) - // == 0 - // acc + (BASE - 1) + x[i] - y[i] - // <= acc + (BASE - 1) + x[i] - // <= (2**(32 - BITS_PER_LIMB) - 1) + (2**BITS_PER_LIMB - 1) + - // (2**BITS_PER_LIMB - 1) - // == 7 + 2 * 2**29 < 2 **32 - - res->val[i] = acc & BN_LIMB_MASK; - acc >>= BN_BITS_PER_LIMB; - // acc <= 7 == 2**(32 - BITS_PER_LIMB) - 1 - - // acc == 2**(BITS_PER_LIMB * (i + 1)) + x[:i + 1] - y[:i + 1] - // >> BITS_PER_LIMB * (i + 1) - } - - // assert(acc == 1); // assert x >= y - - // clang-format off - // acc == 1 - // Proof: - // acc == 2**(BITS_PER_LIMB * LIMBS) + x[:LIMBS] - y[:LIMBS] >> BITS_PER_LIMB * LIMBS - // == 2**(BITS_PER_LIMB * LIMBS) + x - y >> BITS_PER_LIMB * LIMBS - // == 2**(BITS_PER_LIMB * LIMBS) + x >> BITS_PER_LIMB * LIMBS - // <= 2**(BITS_PER_LIMB * LIMBS) + 2**(BITS_PER_LIMB * LIMBS) - 1 >> BITS_PER_LIMB * LIMBS - // <= 2 * 2**(BITS_PER_LIMB * LIMBS) - 1 >> BITS_PER_LIMB * LIMBS - // == 1 - - // acc == 2**(BITS_PER_LIMB * LIMBS) + x[:LIMBS] - y[:LIMBS] >> BITS_PER_LIMB * LIMBS - // == 2**(BITS_PER_LIMB * LIMBS) + x - y >> BITS_PER_LIMB * LIMBS - // >= 2**(BITS_PER_LIMB * LIMBS) >> BITS_PER_LIMB * LIMBS - // == 1 -} - -// q = x // d, r = x % d -// Assumes x is normalized, 1 <= d <= 61304 -// Guarantees q is normalized -void bn_long_division(bignum256 *x, uint32_t d, bignum256 *q, uint32_t *r) { - assert(1 <= d && d < 61304); - - uint32_t acc = 0; - - *r = x->val[BN_LIMBS - 1] % d; - q->val[BN_LIMBS - 1] = x->val[BN_LIMBS - 1] / d; - - for (int i = BN_LIMBS - 2; i >= 0; i--) { - acc = *r * (BN_BASE % d) + x->val[i]; - // acc doesn't overflow 32 bits - // Proof: - // r * (BASE % d) + x[i] - // <= (d - 1) * (d - 1) + (2**BITS_PER_LIMB - 1) - // == d**2 - 2*d + 2**BITS_PER_LIMB - // == 61304**2 - 2 * 61304 + 2**29 - // == 3758057808 + 2**29 < 2**32 - - q->val[i] = *r * (BN_BASE / d) + (acc / d); - // q[i] doesn't overflow 32 bits - // Proof: - // r * (BASE // d) + (acc // d) - // <= (d - 1) * (2**BITS_PER_LIMB / d) + - // ((d**2 - 2*d + 2**BITS_PER_LIMB) / d) - // <= (d - 1) * (2**BITS_PER_LIMB / d) + (d - 2 + 2**BITS_PER_LIMB / d) - // == (d - 1 + 1) * (2**BITS_PER_LIMB / d) + d - 2 - // == 2**BITS_PER_LIMB + d - 2 <= 2**29 + 61304 < 2**32 - - // q[i] == (r * BASE + x[i]) // d - // Proof: - // q[i] == r * (BASE // d) + (acc // d) - // == r * (BASE // d) + (r * (BASE % d) + x[i]) // d - // == (r * d * (BASE // d) + r * (BASE % d) + x[i]) // d - // == (r * (d * (BASE // d) + (BASE % d)) + x[i]) // d - // == (r * BASE + x[i]) // d - - // q[i] < 2**BITS_PER_LIMB - // Proof: - // q[i] == (r * BASE + x[i]) // d - // <= ((d - 1) * 2**BITS_PER_LIMB + (2**BITS_PER_LIMB - 1)) / d - // == (d * 2**BITS_PER_LIMB - 1) / d == 2**BITS_PER_LIMB - 1 / d - // < 2**BITS_PER_LIMB - - *r = acc % d; - // r == (r * BASE + x[i]) % d - // Proof: - // r == acc % d == (r * (BASE % d) + x[i]) % d - // == (r * BASE + x[i]) % d - - // x[:i] == q[:i] * d + r - } -} - -// x = x // 58, r = x % 58 -// Assumes x is normalized -// Guarantees x is normalized -void bn_divmod58(bignum256 *x, uint32_t *r) { bn_long_division(x, 58, x, r); } - -// x = x // 1000, r = x % 1000 -// Assumes x is normalized -// Guarantees x is normalized -void bn_divmod1000(bignum256 *x, uint32_t *r) { - bn_long_division(x, 1000, x, r); -} - -// x = x // 10, r = x % 10 -// Assumes x is normalized -// Guarantees x is normalized -void bn_divmod10(bignum256 *x, uint32_t *r) { bn_long_division(x, 10, x, r); } - -// Formats amount -// Assumes amount is normalized -// Assumes prefix and suffix are null-terminated strings -// Assumes output is an array of length output_length -// The function doesn't have neither constant control flow nor constant memory -// access flow with regard to any its argument -size_t bn_format(const bignum256 *amount, const char *prefix, const char *suffix, unsigned int decimals, int exponent, bool trailing, char *output, size_t output_length) { - -/* - Python prototype of the function: - - def format(amount, prefix, suffix, decimals, exponent, trailing): - if exponent >= 0: - amount *= 10 ** exponent - else: - amount //= 10 ** (-exponent) - - d = pow(10, decimals) - - if decimals: - output = "%d.%0*d" % (amount // d, decimals, amount % d) - if not trailing: - output = output.rstrip("0").rstrip(".") - else: - output = "%d" % (amount // d) - - return prefix + output + suffix -*/ - -// Auxiliary macro for bn_format -// If enough space adds one character to output starting from the end -#define BN_FORMAT_ADD_OUTPUT_CHAR(c) \ - { \ - --position; \ - if (output <= position && position < output + output_length) { \ - *position = (c); \ - } else { \ - memset(output, '\0', output_length); \ - return 0; \ - } \ - } - - bignum256 temp = {0}; - bn_copy(amount, &temp); - uint32_t digit = 0; - - char *position = output + output_length; - - // Add string ending character - BN_FORMAT_ADD_OUTPUT_CHAR('\0'); - - // Add suffix - size_t suffix_length = suffix ? strlen(suffix) : 0; - for (int i = suffix_length - 1; i >= 0; --i) - BN_FORMAT_ADD_OUTPUT_CHAR(suffix[i]) - - // amount //= 10**exponent - for (; exponent < 0; ++exponent) { - // if temp == 0, there is no need to divide it by 10 anymore - if (bn_is_zero(&temp)) { - exponent = 0; - break; - } - bn_divmod10(&temp, &digit); - } - - // exponent >= 0 && decimals >= 0 - - bool fractional_part = false; // is fractional-part of amount present - - { // Add fractional-part digits of amount - // Add trailing zeroes - unsigned int trailing_zeros = decimals < (unsigned int) exponent ? decimals : (unsigned int) exponent; - // When casting a negative int to unsigned int, UINT_MAX is added to the int before - // Since exponent >= 0, the value remains unchanged - decimals -= trailing_zeros; - exponent -= trailing_zeros; - - if (trailing && trailing_zeros) { - fractional_part = true; - for (; trailing_zeros > 0; --trailing_zeros) - BN_FORMAT_ADD_OUTPUT_CHAR('0') - } - - // exponent == 0 || decimals == 0 - - // Add significant digits and leading zeroes - for (; decimals > 0; --decimals) { - bn_divmod10(&temp, &digit); - - if (fractional_part || digit || trailing) { - fractional_part = true; - BN_FORMAT_ADD_OUTPUT_CHAR('0' + digit) - } - else if (bn_is_zero(&temp)) { - // We break since the remaining digits are zeroes and fractional_part == trailing == false - decimals = 0; - break; - } - } - // decimals == 0 - } - - if (fractional_part) { - BN_FORMAT_ADD_OUTPUT_CHAR('.') - } - - { // Add integer-part digits of amount - // Add trailing zeroes - if (!bn_is_zero(&temp)) { - for (; exponent > 0; --exponent) { - BN_FORMAT_ADD_OUTPUT_CHAR('0') - } - } - // decimals == 0 && exponent == 0 - - // Add significant digits - do { - bn_divmod10(&temp, &digit); - BN_FORMAT_ADD_OUTPUT_CHAR('0' + digit) - } while (!bn_is_zero(&temp)); - } - - // Add prefix - size_t prefix_length = prefix ? strlen(prefix) : 0; - for (int i = prefix_length - 1; i >= 0; --i) - BN_FORMAT_ADD_OUTPUT_CHAR(prefix[i]) - - // Move formatted amount to the start of output - int length = output - position + output_length; - memmove(output, position, length); - return length - 1; -} - -#if USE_BN_PRINT -// Prints x in hexadecimal -// Assumes x is normalized and x < 2**256 -void bn_print(const bignum256 *x) { - printf("%06x", x->val[8]); - printf("%08x", ((x->val[7] << 3) | (x->val[6] >> 26))); - printf("%07x", ((x->val[6] << 2) | (x->val[5] >> 27)) & 0x0FFFFFFF); - printf("%07x", ((x->val[5] << 1) | (x->val[4] >> 28)) & 0x0FFFFFFF); - printf("%07x", x->val[4] & 0x0FFFFFFF); - printf("%08x", ((x->val[3] << 3) | (x->val[2] >> 26))); - printf("%07x", ((x->val[2] << 2) | (x->val[1] >> 27)) & 0x0FFFFFFF); - printf("%07x", ((x->val[1] << 1) | (x->val[0] >> 28)) & 0x0FFFFFFF); - printf("%07x", x->val[0] & 0x0FFFFFFF); -} - -// Prints comma separated list of limbs of x -void bn_print_raw(const bignum256 *x) { - for (int i = 0; i < BN_LIMBS - 1; i++) { - printf("0x%08x, ", x->val[i]); - } - printf("0x%08x", x->val[BN_LIMBS - 1]); -} -#endif - -#if USE_INVERSE_FAST -void bn_inverse(bignum256 *x, const bignum256 *prime) { - bn_inverse_fast(x, prime); -} -#else -void bn_inverse(bignum256 *x, const bignum256 *prime) { - bn_inverse_slow(x, prime); -} -#endif diff --git a/trezor-crypto/crypto/bip32.c b/trezor-crypto/crypto/bip32.c deleted file mode 100644 index 4d821a8cf69..00000000000 --- a/trezor-crypto/crypto/bip32.c +++ /dev/null @@ -1,834 +0,0 @@ -/** - * Copyright (c) 2013-2016 Tomas Dzetkulic - * Copyright (c) 2013-2016 Pavol Rusnak - * Copyright (c) 2015-2016 Jochen Hoenicke - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if USE_KECCAK -#include -#endif -#if USE_NEM -#include "nem.h" -#endif -#if USE_CARDANO -#include -#endif -#include - -const curve_info ed25519_info = { - .bip32_name = ED25519_SEED_NAME, - .params = NULL, - .hasher_base58 = HASHER_SHA2D, - .hasher_sign = HASHER_SHA2D, - .hasher_pubkey = HASHER_SHA2_RIPEMD, - .hasher_script = HASHER_SHA2, -}; - -// [wallet-core] -const curve_info ed25519_blake2b_nano_info = { - .bip32_name = "ed25519 seed", - .params = NULL, - .hasher_base58 = HASHER_SHA2D, - .hasher_sign = HASHER_SHA2D, - .hasher_pubkey = HASHER_SHA2_RIPEMD, - .hasher_script = HASHER_SHA2, -}; - -const curve_info ed25519_sha3_info = { - .bip32_name = "ed25519-sha3 seed", - .params = NULL, - .hasher_base58 = HASHER_SHA2D, - .hasher_sign = HASHER_SHA2D, - .hasher_pubkey = HASHER_SHA2_RIPEMD, - .hasher_script = HASHER_SHA2, -}; - -#if USE_KECCAK -const curve_info ed25519_keccak_info = { - .bip32_name = "ed25519-keccak seed", - .params = NULL, - .hasher_base58 = HASHER_SHA2D, - .hasher_sign = HASHER_SHA2D, - .hasher_pubkey = HASHER_SHA2_RIPEMD, - .hasher_script = HASHER_SHA2, -}; -#endif - -const curve_info curve25519_info = { - .bip32_name = "curve25519 seed", - .params = NULL, - .hasher_base58 = HASHER_SHA2D, - .hasher_sign = HASHER_SHA2D, - .hasher_pubkey = HASHER_SHA2_RIPEMD, - .hasher_script = HASHER_SHA2, -}; - -int hdnode_from_xpub(uint32_t depth, uint32_t child_num, - const uint8_t *chain_code, const uint8_t *public_key, - const char *curve, HDNode *out) { - const curve_info *info = get_curve_by_name(curve); - if (info == 0) { - return 0; - } - if (public_key[0] != 0x02 && public_key[0] != 0x03) { // invalid pubkey - return 0; - } - out->curve = info; - out->depth = depth; - out->child_num = child_num; - memcpy(out->chain_code, chain_code, 32); - memzero(out->private_key, 32); - memzero(out->private_key_extension, 32); - memcpy(out->public_key, public_key, 33); - return 1; -} - -int hdnode_from_xprv(uint32_t depth, uint32_t child_num, - const uint8_t *chain_code, const uint8_t *private_key, - const char *curve, HDNode *out) { - bool failed = false; - const curve_info *info = get_curve_by_name(curve); - if (info == 0) { - failed = true; - } else if (info->params) { - bignum256 a = {0}; - bn_read_be(private_key, &a); - if (bn_is_zero(&a)) { // == 0 - failed = true; - } else { - if (!bn_is_less(&a, &info->params->order)) { // >= order - failed = true; - } - } - memzero(&a, sizeof(a)); - } - - if (failed) { - return 0; - } - - out->curve = info; - out->depth = depth; - out->child_num = child_num; - memcpy(out->chain_code, chain_code, 32); - memcpy(out->private_key, private_key, 32); - memzero(out->public_key, sizeof(out->public_key)); - memzero(out->private_key_extension, sizeof(out->private_key_extension)); - return 1; -} - -int hdnode_from_seed(const uint8_t *seed, int seed_len, const char *curve, - HDNode *out) { - CONFIDENTIAL uint8_t I[32 + 32]; - memzero(out, sizeof(HDNode)); - out->depth = 0; - out->child_num = 0; - out->curve = get_curve_by_name(curve); - if (out->curve == 0) { - return 0; - } - CONFIDENTIAL HMAC_SHA512_CTX ctx; - hmac_sha512_Init(&ctx, (const uint8_t *)out->curve->bip32_name, - strlen(out->curve->bip32_name)); - hmac_sha512_Update(&ctx, seed, seed_len); - hmac_sha512_Final(&ctx, I); - - if (out->curve->params) { - bignum256 a = {0}; - while (true) { - bn_read_be(I, &a); - if (!bn_is_zero(&a) // != 0 - && bn_is_less(&a, &out->curve->params->order)) { // < order - break; - } - hmac_sha512_Init(&ctx, (const uint8_t *)out->curve->bip32_name, - strlen(out->curve->bip32_name)); - hmac_sha512_Update(&ctx, I, sizeof(I)); - hmac_sha512_Final(&ctx, I); - } - memzero(&a, sizeof(a)); - } - memcpy(out->private_key, I, 32); - memcpy(out->chain_code, I + 32, 32); - memzero(out->public_key, sizeof(out->public_key)); - memzero(I, sizeof(I)); - return 1; -} - -uint32_t hdnode_fingerprint(HDNode *node) { - uint8_t digest[32] = {0}; - uint32_t fingerprint = 0; - - hdnode_fill_public_key(node); - hasher_Raw(node->curve->hasher_pubkey, node->public_key, 33, digest); - fingerprint = ((uint32_t)digest[0] << 24) + (digest[1] << 16) + - (digest[2] << 8) + digest[3]; - memzero(digest, sizeof(digest)); - return fingerprint; -} - -int hdnode_private_ckd_bip32(HDNode *inout, uint32_t i) { - CONFIDENTIAL uint8_t data[1 + 32 + 4]; - CONFIDENTIAL uint8_t I[32 + 32]; - CONFIDENTIAL bignum256 a, b; - -#if USE_CARDANO - if (inout->curve == &ed25519_cardano_info) { - return 0; - } -#endif - - if (i & 0x80000000) { // private derivation - data[0] = 0; - memcpy(data + 1, inout->private_key, 32); - } else { // public derivation - if (!inout->curve->params) { - return 0; - } - if (hdnode_fill_public_key(inout) != 0) { - return 0; - } - memcpy(data, inout->public_key, 33); - } - write_be(data + 33, i); - - bn_read_be(inout->private_key, &a); - - CONFIDENTIAL HMAC_SHA512_CTX ctx; - hmac_sha512_Init(&ctx, inout->chain_code, 32); - hmac_sha512_Update(&ctx, data, sizeof(data)); - hmac_sha512_Final(&ctx, I); - - if (inout->curve->params) { - while (true) { - bool failed = false; - bn_read_be(I, &b); - if (!bn_is_less(&b, &inout->curve->params->order)) { // >= order - failed = true; - } else { - bn_add(&b, &a); - bn_mod(&b, &inout->curve->params->order); - if (bn_is_zero(&b)) { - failed = true; - } - } - - if (!failed) { - bn_write_be(&b, inout->private_key); - break; - } - - data[0] = 1; - memcpy(data + 1, I + 32, 32); - hmac_sha512_Init(&ctx, inout->chain_code, 32); - hmac_sha512_Update(&ctx, data, sizeof(data)); - hmac_sha512_Final(&ctx, I); - } - } else { - memcpy(inout->private_key, I, 32); - } - - memcpy(inout->chain_code, I + 32, 32); - inout->depth++; - inout->child_num = i; - memzero(inout->public_key, sizeof(inout->public_key)); - - // making sure to wipe our memory - memzero(&a, sizeof(a)); - memzero(&b, sizeof(b)); - memzero(I, sizeof(I)); - memzero(data, sizeof(data)); - return 1; -} - -int hdnode_private_ckd(HDNode *inout, uint32_t i) { -#if USE_CARDANO - if (inout->curve == &ed25519_cardano_info) { - return hdnode_private_ckd_cardano(inout, i); - } else -#endif - { - return hdnode_private_ckd_bip32(inout, i); - } -} - -int hdnode_public_ckd_cp(const ecdsa_curve *curve, const curve_point *parent, - const uint8_t *parent_chain_code, uint32_t i, - curve_point *child, uint8_t *child_chain_code) { - uint8_t data[(1 + 32) + 4] = {0}; - uint8_t I[32 + 32] = {0}; - bignum256 c = {0}; - - if (i & 0x80000000) { // private derivation - return 0; - } - - data[0] = 0x02 | (parent->y.val[0] & 0x01); - bn_write_be(&parent->x, data + 1); - write_be(data + 33, i); - - while (true) { - hmac_sha512(parent_chain_code, 32, data, sizeof(data), I); - bn_read_be(I, &c); - if (bn_is_less(&c, &curve->order)) { // < order - scalar_multiply(curve, &c, child); // b = c * G - point_add(curve, parent, child); // b = a + b - if (!point_is_infinity(child)) { - if (child_chain_code) { - memcpy(child_chain_code, I + 32, 32); - } - - // Wipe all stack data. - memzero(data, sizeof(data)); - memzero(I, sizeof(I)); - memzero(&c, sizeof(c)); - return 1; - } - } - - data[0] = 1; - memcpy(data + 1, I + 32, 32); - } -} - -int hdnode_public_ckd(HDNode *inout, uint32_t i) { - curve_point parent = {0}, child = {0}; - - if (!ecdsa_read_pubkey(inout->curve->params, inout->public_key, &parent)) { - return 0; - } - if (!hdnode_public_ckd_cp(inout->curve->params, &parent, inout->chain_code, i, - &child, inout->chain_code)) { - return 0; - } - memzero(inout->private_key, 32); - inout->depth++; - inout->child_num = i; - inout->public_key[0] = 0x02 | (child.y.val[0] & 0x01); - bn_write_be(&child.x, inout->public_key + 1); - - // Wipe all stack data. - memzero(&parent, sizeof(parent)); - memzero(&child, sizeof(child)); - - return 1; -} - -void hdnode_public_ckd_address_optimized(const curve_point *pub, - const uint8_t *chain_code, uint32_t i, - uint32_t version, - HasherType hasher_pubkey, - HasherType hasher_base58, char *addr, - int addrsize, int addrformat) { - uint8_t child_pubkey[33] = {0}; - curve_point b = {0}; - - hdnode_public_ckd_cp(&secp256k1, pub, chain_code, i, &b, NULL); - child_pubkey[0] = 0x02 | (b.y.val[0] & 0x01); - bn_write_be(&b.x, child_pubkey + 1); - - switch (addrformat) { - case 1: // Segwit-in-P2SH - ecdsa_get_address_segwit_p2sh(child_pubkey, version, hasher_pubkey, - hasher_base58, addr, addrsize); - break; - default: // normal address - ecdsa_get_address(child_pubkey, version, hasher_pubkey, hasher_base58, - addr, addrsize); - break; - } -} - -#if USE_BIP32_CACHE -bool private_ckd_cache_root_set = false; -CONFIDENTIAL HDNode private_ckd_cache_root; -int private_ckd_cache_index = 0; - -CONFIDENTIAL struct { - bool set; - size_t depth; - uint32_t i[BIP32_CACHE_MAXDEPTH]; - HDNode node; -} private_ckd_cache[BIP32_CACHE_SIZE]; - -void bip32_cache_clear(void) { - private_ckd_cache_root_set = false; - private_ckd_cache_index = 0; - memzero(&private_ckd_cache_root, sizeof(private_ckd_cache_root)); - memzero(private_ckd_cache, sizeof(private_ckd_cache)); -} - -int hdnode_private_ckd_cached(HDNode *inout, const uint32_t *i, size_t i_count, - uint32_t *fingerprint) { - if (i_count == 0) { - // no way how to compute parent fingerprint - return 1; - } - if (i_count == 1) { - if (fingerprint) { - *fingerprint = hdnode_fingerprint(inout); - } - if (hdnode_private_ckd(inout, i[0]) == 0) return 0; - return 1; - } - - bool found = false; - // if root is not set or not the same - if (!private_ckd_cache_root_set || - memcmp(&private_ckd_cache_root, inout, sizeof(HDNode)) != 0) { - // clear the cache - private_ckd_cache_index = 0; - memzero(private_ckd_cache, sizeof(private_ckd_cache)); - // setup new root - memcpy(&private_ckd_cache_root, inout, sizeof(HDNode)); - private_ckd_cache_root_set = true; - } else { - // try to find parent - int j = 0; - for (j = 0; j < BIP32_CACHE_SIZE; j++) { - if (private_ckd_cache[j].set && - private_ckd_cache[j].depth == i_count - 1 && - memcmp(private_ckd_cache[j].i, i, (i_count - 1) * sizeof(uint32_t)) == - 0 && - private_ckd_cache[j].node.curve == inout->curve) { - memcpy(inout, &(private_ckd_cache[j].node), sizeof(HDNode)); - found = true; - break; - } - } - } - - // else derive parent - if (!found) { - size_t k = 0; - for (k = 0; k < i_count - 1; k++) { - if (hdnode_private_ckd(inout, i[k]) == 0) return 0; - } - // and save it - memzero(&(private_ckd_cache[private_ckd_cache_index]), - sizeof(private_ckd_cache[private_ckd_cache_index])); - private_ckd_cache[private_ckd_cache_index].set = true; - private_ckd_cache[private_ckd_cache_index].depth = i_count - 1; - memcpy(private_ckd_cache[private_ckd_cache_index].i, i, - (i_count - 1) * sizeof(uint32_t)); - memcpy(&(private_ckd_cache[private_ckd_cache_index].node), inout, - sizeof(HDNode)); - private_ckd_cache_index = (private_ckd_cache_index + 1) % BIP32_CACHE_SIZE; - } - - if (fingerprint) { - *fingerprint = hdnode_fingerprint(inout); - } - if (hdnode_private_ckd(inout, i[i_count - 1]) == 0) return 0; - - return 1; -} -#endif - -int hdnode_get_address_raw(HDNode *node, uint32_t version, uint8_t *addr_raw) { - if (hdnode_fill_public_key(node) != 0) { - return 1; - } - ecdsa_get_address_raw(node->public_key, version, node->curve->hasher_pubkey, - addr_raw); - return 0; -} - -int hdnode_get_address(HDNode *node, uint32_t version, char *addr, - int addrsize) { - if (hdnode_fill_public_key(node) != 0) { - return 1; - } - ecdsa_get_address(node->public_key, version, node->curve->hasher_pubkey, - node->curve->hasher_base58, addr, addrsize); - return 0; -} - -int hdnode_fill_public_key(HDNode *node) { - if (node->public_key[0] != 0) return 0; - -#if USE_BIP32_25519_CURVES - if (node->curve->params) { - if (ecdsa_get_public_key33(node->curve->params, node->private_key, - node->public_key) != 0) { - return 1; - } - } else { - node->public_key[0] = 1; - if (node->curve == &ed25519_info) { - ed25519_publickey(node->private_key, node->public_key + 1); - } else if (node->curve == &ed25519_sha3_info) { - ed25519_publickey_sha3(node->private_key, node->public_key + 1); -#if USE_KECCAK - } else if (node->curve == &ed25519_keccak_info) { - ed25519_publickey_keccak(node->private_key, node->public_key + 1); -#endif - } else if (node->curve == &curve25519_info) { - curve25519_scalarmult_basepoint(node->public_key + 1, node->private_key); -#if USE_CARDANO - } else if (node->curve == &ed25519_cardano_info) { - ed25519_publickey_ext(node->private_key, node->public_key + 1); -#endif - } - } -#else - - if (ecdsa_get_public_key33(node->curve->params, node->private_key, - node->public_key) != 0) { - return 1; - } -#endif - return 0; -} - -#if USE_ETHEREUM -int hdnode_get_ethereum_pubkeyhash(const HDNode *node, uint8_t *pubkeyhash) { - uint8_t buf[65] = {0}; - SHA3_CTX ctx = {0}; - - /* get uncompressed public key */ - if (ecdsa_get_public_key65(node->curve->params, node->private_key, buf) != - 0) { - return 0; - } - - /* compute sha3 of x and y coordinate without 04 prefix */ - sha3_256_Init(&ctx); - sha3_Update(&ctx, buf + 1, 64); - keccak_Final(&ctx, buf); - - /* result are the least significant 160 bits */ - memcpy(pubkeyhash, buf + 12, 20); - - return 1; -} -#endif - -#if USE_NEM -int hdnode_get_nem_address(HDNode *node, uint8_t version, char *address) { - if (node->curve != &ed25519_keccak_info) { - return 0; - } - - if (hdnode_fill_public_key(node) != 0) { - return 0; - } - - return nem_get_address(&node->public_key[1], version, address); -} - -int hdnode_get_nem_shared_key(const HDNode *node, - const ed25519_public_key peer_public_key, - const uint8_t *salt, ed25519_public_key mul, - uint8_t *shared_key) { - if (node->curve != &ed25519_keccak_info) { - return 0; - } - - // sizeof(ed25519_public_key) == SHA3_256_DIGEST_LENGTH - if (mul == NULL) mul = shared_key; - - if (ed25519_scalarmult_keccak(mul, node->private_key, peer_public_key)) { - return 0; - } - - for (size_t i = 0; i < 32; i++) { - shared_key[i] = mul[i] ^ salt[i]; - } - - keccak_256(shared_key, 32, shared_key); - return 1; -} - -int hdnode_nem_encrypt(const HDNode *node, const ed25519_public_key public_key, - const uint8_t *iv_immut, const uint8_t *salt, - const uint8_t *payload, size_t size, uint8_t *buffer) { - uint8_t last_block[AES_BLOCK_SIZE] = {0}; - uint8_t remainder = size % AES_BLOCK_SIZE; - - // Round down to last whole block - size -= remainder; - // Copy old last block - memcpy(last_block, &payload[size], remainder); - // Pad new last block with number of missing bytes - memset(&last_block[remainder], AES_BLOCK_SIZE - remainder, - AES_BLOCK_SIZE - remainder); - - // the IV gets mutated, so we make a copy not to touch the original - uint8_t iv[AES_BLOCK_SIZE] = {0}; - memcpy(iv, iv_immut, AES_BLOCK_SIZE); - - uint8_t shared_key[SHA3_256_DIGEST_LENGTH] = {0}; - if (!hdnode_get_nem_shared_key(node, public_key, salt, NULL, shared_key)) { - return 0; - } - - aes_encrypt_ctx ctx = {0}; - - int ret = aes_encrypt_key256(shared_key, &ctx); - memzero(shared_key, sizeof(shared_key)); - - if (ret != EXIT_SUCCESS) { - return 0; - } - - if (aes_cbc_encrypt(payload, buffer, size, iv, &ctx) != EXIT_SUCCESS) { - return 0; - } - - if (aes_cbc_encrypt(last_block, &buffer[size], sizeof(last_block), iv, - &ctx) != EXIT_SUCCESS) { - return 0; - } - - return 1; -} - -int hdnode_nem_decrypt(const HDNode *node, const ed25519_public_key public_key, - uint8_t *iv, const uint8_t *salt, const uint8_t *payload, - size_t size, uint8_t *buffer) { - uint8_t shared_key[SHA3_256_DIGEST_LENGTH] = {0}; - - if (!hdnode_get_nem_shared_key(node, public_key, salt, NULL, shared_key)) { - return 0; - } - - aes_decrypt_ctx ctx = {0}; - - int ret = aes_decrypt_key256(shared_key, &ctx); - memzero(shared_key, sizeof(shared_key)); - - if (ret != EXIT_SUCCESS) { - return 0; - } - - if (aes_cbc_decrypt(payload, buffer, size, iv, &ctx) != EXIT_SUCCESS) { - return 0; - } - - return 1; -} -#endif - -// msg is a data to be signed -// msg_len is the message length -int hdnode_sign(HDNode *node, const uint8_t *msg, uint32_t msg_len, - HasherType hasher_sign, uint8_t *sig, uint8_t *pby, - int (*is_canonical)(uint8_t by, uint8_t sig[64])) { - if (node->curve->params) { - return ecdsa_sign(node->curve->params, hasher_sign, node->private_key, msg, - msg_len, sig, pby, is_canonical); - } else if (node->curve == &curve25519_info) { - return 1; // signatures are not supported - } else { - if (node->curve == &ed25519_info) { - ed25519_sign(msg, msg_len, node->private_key, sig); - } else if (node->curve == &ed25519_sha3_info) { - ed25519_sign_sha3(msg, msg_len, node->private_key, sig); -#if USE_KECCAK - } else if (node->curve == &ed25519_keccak_info) { - ed25519_sign_keccak(msg, msg_len, node->private_key, sig); -#endif - } else { - return 1; // unknown or unsupported curve - } - return 0; - } -} - -int hdnode_sign_digest(HDNode *node, const uint8_t *digest, uint8_t *sig, - uint8_t *pby, - int (*is_canonical)(uint8_t by, uint8_t sig[64])) { - if (node->curve->params) { - return ecdsa_sign_digest(node->curve->params, node->private_key, digest, - sig, pby, is_canonical); - } else if (node->curve == &curve25519_info) { - return 1; // signatures are not supported - } else { - return hdnode_sign(node, digest, 32, 0, sig, pby, is_canonical); - } -} - -int hdnode_get_shared_key(const HDNode *node, const uint8_t *peer_public_key, - uint8_t *session_key, int *result_size) { - // Use elliptic curve Diffie-Helman to compute shared session key - if (node->curve->params) { - if (ecdh_multiply(node->curve->params, node->private_key, peer_public_key, - session_key) != 0) { - return 1; - } - *result_size = 65; - return 0; - } else if (node->curve == &curve25519_info) { - session_key[0] = 0x04; - if (peer_public_key[0] != 0x40) { - return 1; // Curve25519 public key should start with 0x40 byte. - } - curve25519_scalarmult(session_key + 1, node->private_key, - peer_public_key + 1); - *result_size = 33; - return 0; - } else { - *result_size = 0; - return 1; // ECDH is not supported - } -} - -static int hdnode_serialize(const HDNode *node, uint32_t fingerprint, - uint32_t version, bool use_private, char *str, - int strsize) { - uint8_t node_data[78] = {0}; - write_be(node_data, version); - node_data[4] = node->depth; - write_be(node_data + 5, fingerprint); - write_be(node_data + 9, node->child_num); - memcpy(node_data + 13, node->chain_code, 32); - if (use_private) { - node_data[45] = 0; - memcpy(node_data + 46, node->private_key, 32); - } else { - memcpy(node_data + 45, node->public_key, 33); - } - int ret = base58_encode_check(node_data, sizeof(node_data), - node->curve->hasher_base58, str, strsize); - memzero(node_data, sizeof(node_data)); - return ret; -} - -int hdnode_serialize_public(const HDNode *node, uint32_t fingerprint, - uint32_t version, char *str, int strsize) { - return hdnode_serialize(node, fingerprint, version, false, str, strsize); -} - -int hdnode_serialize_private(const HDNode *node, uint32_t fingerprint, - uint32_t version, char *str, int strsize) { - return hdnode_serialize(node, fingerprint, version, true, str, strsize); -} - -// check for validity of curve point in case of public data not performed -static int hdnode_deserialize(const char *str, uint32_t version, - bool use_private, const char *curve, HDNode *node, - uint32_t *fingerprint) { - uint8_t node_data[78] = {0}; - memzero(node, sizeof(HDNode)); - node->curve = get_curve_by_name(curve); - if (base58_decode_check(str, node->curve->hasher_base58, node_data, - sizeof(node_data)) != sizeof(node_data)) { - return -1; - } - uint32_t ver = read_be(node_data); - if (ver != version) { - return -3; // invalid version - } - if (use_private) { - // invalid data - if (node_data[45]) { - return -2; - } - memcpy(node->private_key, node_data + 46, 32); - memzero(node->public_key, sizeof(node->public_key)); - } else { - memzero(node->private_key, sizeof(node->private_key)); - memcpy(node->public_key, node_data + 45, 33); - } - node->depth = node_data[4]; - if (fingerprint) { - *fingerprint = read_be(node_data + 5); - } - node->child_num = read_be(node_data + 9); - memcpy(node->chain_code, node_data + 13, 32); - return 0; -} - -int hdnode_deserialize_public(const char *str, uint32_t version, - const char *curve, HDNode *node, - uint32_t *fingerprint) { - return hdnode_deserialize(str, version, false, curve, node, fingerprint); -} - -int hdnode_deserialize_private(const char *str, uint32_t version, - const char *curve, HDNode *node, - uint32_t *fingerprint) { - return hdnode_deserialize(str, version, true, curve, node, fingerprint); -} - -const curve_info *get_curve_by_name(const char *curve_name) { - if (curve_name == 0) { - return 0; - } - if (strcmp(curve_name, SECP256K1_NAME) == 0) { - return &secp256k1_info; - } - if (strcmp(curve_name, SECP256K1_DECRED_NAME) == 0) { - return &secp256k1_decred_info; - } - if (strcmp(curve_name, SECP256K1_GROESTL_NAME) == 0) { - return &secp256k1_groestl_info; - } - if (strcmp(curve_name, SECP256K1_SMART_NAME) == 0) { - return &secp256k1_smart_info; - } - if (strcmp(curve_name, NIST256P1_NAME) == 0) { - return &nist256p1_info; - } - if (strcmp(curve_name, ED25519_NAME) == 0) { - return &ed25519_info; - } - // [wallet-core] - if (strcmp(curve_name, ED25519_CARDANO_NAME) == 0) { - return &ed25519_cardano_info; - } - // [wallet-core] - if (strcmp(curve_name, ED25519_BLAKE2B_NANO_NAME) == 0) { - return &ed25519_blake2b_nano_info; - } - if (strcmp(curve_name, ED25519_SHA3_NAME) == 0) { - return &ed25519_sha3_info; - } -#if USE_KECCAK - if (strcmp(curve_name, ED25519_KECCAK_NAME) == 0) { - return &ed25519_keccak_info; - } -#endif - if (strcmp(curve_name, CURVE25519_NAME) == 0) { - return &curve25519_info; - } - return 0; -} diff --git a/trezor-crypto/crypto/bip39.c b/trezor-crypto/crypto/bip39.c deleted file mode 100644 index fc61539c938..00000000000 --- a/trezor-crypto/crypto/bip39.c +++ /dev/null @@ -1,305 +0,0 @@ -/** - * Copyright (c) 2013-2014 Tomas Dzetkulic - * Copyright (c) 2013-2014 Pavol Rusnak - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#if USE_BIP39_CACHE - -int bip39_cache_index = 0; - -CONFIDENTIAL struct { - bool set; - char mnemonic[256]; - char passphrase[64]; - uint8_t seed[512 / 8]; -} bip39_cache[BIP39_CACHE_SIZE]; - -void bip39_cache_clear(void) { - memzero(bip39_cache, sizeof(bip39_cache)); - bip39_cache_index = 0; -} - -#endif - -// [wallet-core] Added output buffer -const char *mnemonic_generate(int strength, char *buf, int buflen) { - if (strength % 32 || strength < 128 || strength > 256) { - return 0; - } - uint8_t data[32] = {0}; - random_buffer(data, 32); - const char *r = mnemonic_from_data(data, strength / 8, buf, buflen); - memzero(data, sizeof(data)); - return r; -} - -// [wallet-core] Global buffer no longer used -//CONFIDENTIAL char mnemo[24 * 10]; - -// [wallet-core] Added output buffer -const char *mnemonic_from_data(const uint8_t *data, int len, char *buf, int buflen) { - if (len % 4 || len < 16 || len > 32) { - return 0; - } - // [wallet-core] Check provided buffer validity, size - if (!buf || buflen < (BIP39_MAX_WORDS * (BIP39_MAX_WORD_LENGTH + 1))) { - return 0; - } - - uint8_t bits[32 + 1] = {0}; - - sha256_Raw(data, len, bits); - // checksum - bits[len] = bits[0]; - // data - memcpy(bits, data, len); - - int mlen = len * 3 / 4; - - int i = 0, j = 0, idx = 0; - char *p = buf; // [wallet-core] - for (i = 0; i < mlen; i++) { - idx = 0; - for (j = 0; j < 11; j++) { - idx <<= 1; - idx += (bits[(i * 11 + j) / 8] & (1 << (7 - ((i * 11 + j) % 8)))) > 0; - } - strcpy(p, wordlist[idx]); - p += strlen(wordlist[idx]); - *p = (i < mlen - 1) ? ' ' : 0; - p++; - } - memzero(bits, sizeof(bits)); - - return buf; // [wallet-core] -} - -// [wallet-core] No longer used -//void mnemonic_clear(void) { memzero(mnemo, sizeof(mnemo)); } - -int mnemonic_to_bits(const char *mnemonic, uint8_t *bits) { - if (!mnemonic) { - return 0; - } - - uint32_t i = 0, n = 0; - - while (mnemonic[i]) { - if (mnemonic[i] == ' ') { - n++; - } - i++; - } - n++; - - // check number of words - // [wallet-core] also accept 15- and 21-word - if (n != 12 && n != 15 && n != 18 && n != 21 && n != 24) { - return 0; - } - - char current_word[10] = {0}; - uint32_t j = 0, k = 0, ki = 0, bi = 0; - uint8_t result[32 + 1] = {0}; - - memzero(result, sizeof(result)); - i = 0; - while (mnemonic[i]) { - j = 0; - while (mnemonic[i] != ' ' && mnemonic[i] != 0) { - if (j >= sizeof(current_word) - 1) { - return 0; - } - current_word[j] = mnemonic[i]; - i++; - j++; - } - current_word[j] = 0; - if (mnemonic[i] != 0) { - i++; - } - k = 0; - for (;;) { - if (!wordlist[k]) { // word not found - return 0; - } - if (strcmp(current_word, wordlist[k]) == 0) { // word found on index k - for (ki = 0; ki < 11; ki++) { - if (k & (1 << (10 - ki))) { - result[bi / 8] |= 1 << (7 - (bi % 8)); - } - bi++; - } - break; - } - k++; - } - } - if (bi != n * 11) { - return 0; - } - memcpy(bits, result, sizeof(result)); - memzero(result, sizeof(result)); - - // returns amount of entropy + checksum BITS - return n * 11; -} - -int mnemonic_check(const char *mnemonic) { - uint8_t bits[32 + 1] = {0}; - int mnemonic_bits_len = mnemonic_to_bits(mnemonic, bits); - // [wallet-core] also accept 15- and 21-word - if (mnemonic_bits_len != (12 * 11) && mnemonic_bits_len != (18 * 11) && - mnemonic_bits_len != (24 * 11) && - mnemonic_bits_len != (15 * 11) && mnemonic_bits_len != (21 * 11)) { - return 0; - } - int words = mnemonic_bits_len / 11; - - uint8_t checksum = bits[words * 4 / 3]; - sha256_Raw(bits, words * 4 / 3, bits); - if (words == 12) { - return (bits[0] & 0xF0) == (checksum & 0xF0); // compare first 4 bits - } else if (words == 15) { - return (bits[0] & 0xF8) == (checksum & 0xF8); // compare first 5 bits - } else if (words == 18) { - return (bits[0] & 0xFC) == (checksum & 0xFC); // compare first 6 bits - } else if (words == 21) { - return (bits[0] & 0xFE) == (checksum & 0xFE); // compare first 7 bits - } else if (words == 24) { - return bits[0] == checksum; // compare 8 bits - } - return 0; -} - -// passphrase must be at most 256 characters otherwise it would be truncated -void mnemonic_to_seed(const char *mnemonic, const char *passphrase, - uint8_t seed[512 / 8], - void (*progress_callback)(uint32_t current, - uint32_t total)) { - int mnemoniclen = strlen(mnemonic); - int passphraselen = strnlen(passphrase, 256); -#if USE_BIP39_CACHE - // check cache - if (mnemoniclen < 256 && passphraselen < 64) { - for (int i = 0; i < BIP39_CACHE_SIZE; i++) { - if (!bip39_cache[i].set) continue; - if (strcmp(bip39_cache[i].mnemonic, mnemonic) != 0) continue; - if (strcmp(bip39_cache[i].passphrase, passphrase) != 0) continue; - // found the correct entry - memcpy(seed, bip39_cache[i].seed, 512 / 8); - return; - } - } -#endif - uint8_t salt[8 + 256] = {0}; - memcpy(salt, "mnemonic", 8); - memcpy(salt + 8, passphrase, passphraselen); - CONFIDENTIAL PBKDF2_HMAC_SHA512_CTX pctx; - pbkdf2_hmac_sha512_Init(&pctx, (const uint8_t *)mnemonic, mnemoniclen, salt, - passphraselen + 8, 1); - if (progress_callback) { - progress_callback(0, BIP39_PBKDF2_ROUNDS); - } - for (int i = 0; i < 16; i++) { - pbkdf2_hmac_sha512_Update(&pctx, BIP39_PBKDF2_ROUNDS / 16); - if (progress_callback) { - progress_callback((i + 1) * BIP39_PBKDF2_ROUNDS / 16, - BIP39_PBKDF2_ROUNDS); - } - } - pbkdf2_hmac_sha512_Final(&pctx, seed); - memzero(salt, sizeof(salt)); -#if USE_BIP39_CACHE - // store to cache - if (mnemoniclen < 256 && passphraselen < 64) { - bip39_cache[bip39_cache_index].set = true; - strcpy(bip39_cache[bip39_cache_index].mnemonic, mnemonic); - strcpy(bip39_cache[bip39_cache_index].passphrase, passphrase); - memcpy(bip39_cache[bip39_cache_index].seed, seed, 512 / 8); - bip39_cache_index = (bip39_cache_index + 1) % BIP39_CACHE_SIZE; - } -#endif -} - -// binary search for finding the word in the wordlist -int mnemonic_find_word(const char *word) { - int lo = 0, hi = BIP39_WORD_COUNT - 1; - while (lo <= hi) { - int mid = lo + (hi - lo) / 2; - int cmp = strcmp(word, wordlist[mid]); - if (cmp == 0) { - return mid; - } - if (cmp > 0) { - lo = mid + 1; - } else { - hi = mid - 1; - } - } - return -1; -} - -const char *mnemonic_complete_word(const char *prefix, int len) { - // we need to perform linear search, - // because we want to return the first match - for (const char *const *w = wordlist; *w != 0; w++) { - if (strncmp(*w, prefix, len) == 0) { - return *w; - } - } - return NULL; -} - -const char *mnemonic_get_word(int index) { - if (index >= 0 && index < BIP39_WORD_COUNT) { - return wordlist[index]; - } else { - return NULL; - } -} - -uint32_t mnemonic_word_completion_mask(const char *prefix, int len) { - if (len <= 0) { - return 0x3ffffff; // all letters (bits 1-26 set) - } - uint32_t res = 0; - for (const char *const *w = wordlist; *w != 0; w++) { - const char *word = *w; - if (strncmp(word, prefix, len) == 0 && word[len] >= 'a' && - word[len] <= 'z') { - res |= 1 << (word[len] - 'a'); - } - } - return res; -} diff --git a/trezor-crypto/crypto/blake256.c b/trezor-crypto/crypto/blake256.c deleted file mode 100644 index a4e9b489c37..00000000000 --- a/trezor-crypto/crypto/blake256.c +++ /dev/null @@ -1,234 +0,0 @@ -/* - BLAKE reference C implementation - - Copyright (c) 2012 Jean-Philippe Aumasson - - To the extent possible under law, the author(s) have dedicated all copyright - and related and neighboring rights to this software to the public domain - worldwide. This software is distributed without any warranty. - - You should have received a copy of the CC0 Public Domain Dedication along with - this software. If not, see . - */ -#include - -#include - -#define U8TO32_BIG(p) \ - (((uint32_t)((p)[0]) << 24) | ((uint32_t)((p)[1]) << 16) | \ - ((uint32_t)((p)[2]) << 8) | ((uint32_t)((p)[3]) )) - -#define U32TO8_BIG(p, v) \ - (p)[0] = (uint8_t)((v) >> 24); (p)[1] = (uint8_t)((v) >> 16); \ - (p)[2] = (uint8_t)((v) >> 8); (p)[3] = (uint8_t)((v) ); - -const uint8_t sigma[][16] = -{ - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, - {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }, - {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 }, - { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 }, - { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 }, - { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 }, - {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 }, - {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 }, - { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 }, - {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0 }, - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, - {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }, - {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 }, - { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 }, - { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 }, - { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 } -}; - -const uint32_t u256[16] = -{ - 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, - 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, - 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, - 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917 -}; - -const uint8_t padding[129] = -{ - 0x80, 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, 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, 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, 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, 0, 0, 0, 0 -}; - -static void blake256_compress( BLAKE256_CTX *S, const uint8_t *block ) -{ - uint32_t v[16] = {0}, m[16] = {0}, i = 0; -#define ROT(x,n) (((x)<<(32-n))|( (x)>>(n))) -#define G(a,b,c,d,e) \ - v[a] += (m[sigma[i][e]] ^ u256[sigma[i][e+1]]) + v[b]; \ - v[d] = ROT( v[d] ^ v[a],16); \ - v[c] += v[d]; \ - v[b] = ROT( v[b] ^ v[c],12); \ - v[a] += (m[sigma[i][e+1]] ^ u256[sigma[i][e]])+v[b]; \ - v[d] = ROT( v[d] ^ v[a], 8); \ - v[c] += v[d]; \ - v[b] = ROT( v[b] ^ v[c], 7); - - for( i = 0; i < 16; ++i ) m[i] = U8TO32_BIG( block + i * 4 ); - - for( i = 0; i < 8; ++i ) v[i] = S->h[i]; - - v[ 8] = S->s[0] ^ u256[0]; - v[ 9] = S->s[1] ^ u256[1]; - v[10] = S->s[2] ^ u256[2]; - v[11] = S->s[3] ^ u256[3]; - v[12] = u256[4]; - v[13] = u256[5]; - v[14] = u256[6]; - v[15] = u256[7]; - - /* don't xor t when the block is only padding */ - if ( !S->nullt ) - { - v[12] ^= S->t[0]; - v[13] ^= S->t[0]; - v[14] ^= S->t[1]; - v[15] ^= S->t[1]; - } - - for( i = 0; i < 14; ++i ) - { - /* column step */ - G( 0, 4, 8, 12, 0 ); - G( 1, 5, 9, 13, 2 ); - G( 2, 6, 10, 14, 4 ); - G( 3, 7, 11, 15, 6 ); - /* diagonal step */ - G( 0, 5, 10, 15, 8 ); - G( 1, 6, 11, 12, 10 ); - G( 2, 7, 8, 13, 12 ); - G( 3, 4, 9, 14, 14 ); - } - - for( i = 0; i < 16; ++i ) S->h[i % 8] ^= v[i]; - - for( i = 0; i < 8 ; ++i ) S->h[i] ^= S->s[i % 4]; -} - - -void blake256_Init( BLAKE256_CTX *S ) -{ - S->h[0] = 0x6a09e667; - S->h[1] = 0xbb67ae85; - S->h[2] = 0x3c6ef372; - S->h[3] = 0xa54ff53a; - S->h[4] = 0x510e527f; - S->h[5] = 0x9b05688c; - S->h[6] = 0x1f83d9ab; - S->h[7] = 0x5be0cd19; - S->t[0] = S->t[1] = S->buflen = S->nullt = 0; - S->s[0] = S->s[1] = S->s[2] = S->s[3] = 0; -} - - -void blake256_Update( BLAKE256_CTX *S, const uint8_t *in, size_t inlen ) -{ - size_t left = S->buflen; - size_t fill = 64 - left; - - /* data left and data received fill a block */ - if( left && ( inlen >= fill ) ) - { - memcpy( ( void * ) ( S->buf + left ), ( void * ) in, fill ); - S->t[0] += 512; - - if ( S->t[0] == 0 ) S->t[1]++; - - blake256_compress( S, S->buf ); - in += fill; - inlen -= fill; - left = 0; - } - - /* compress blocks of data received */ - while( inlen >= 64 ) - { - S->t[0] += 512; - - if ( S->t[0] == 0 ) S->t[1]++; - - blake256_compress( S, in ); - in += 64; - inlen -= 64; - } - - /* store any data left */ - if( inlen > 0 ) - { - memcpy( ( void * ) ( S->buf + left ), \ - ( void * ) in, ( size_t ) inlen ); - } - S->buflen = left + inlen; -} - - -void blake256_Final( BLAKE256_CTX *S, uint8_t *out ) -{ - uint8_t msglen[8] = {0}, zo = 0x01, oo = 0x81; - uint32_t lo = S->t[0] + ( S->buflen << 3 ), hi = S->t[1]; - - /* support for hashing more than 2^32 bits */ - if ( lo < ( S->buflen << 3 ) ) hi++; - - U32TO8_BIG( msglen + 0, hi ); - U32TO8_BIG( msglen + 4, lo ); - - if ( S->buflen == 55 ) /* one padding byte */ - { - S->t[0] -= 8; - blake256_Update( S, &oo, 1 ); - } - else - { - if ( S->buflen < 55 ) /* enough space to fill the block */ - { - if ( !S->buflen ) S->nullt = 1; - - S->t[0] -= 440 - ( S->buflen << 3 ); - blake256_Update( S, padding, 55 - S->buflen ); - } - else /* need 2 compressions */ - { - S->t[0] -= 512 - ( S->buflen << 3 ); - blake256_Update( S, padding, 64 - S->buflen ); - S->t[0] -= 440; - blake256_Update( S, padding + 1, 55 ); - S->nullt = 1; - } - - blake256_Update( S, &zo, 1 ); - S->t[0] -= 8; - } - - S->t[0] -= 64; - blake256_Update( S, msglen, 8 ); - U32TO8_BIG( out + 0, S->h[0] ); - U32TO8_BIG( out + 4, S->h[1] ); - U32TO8_BIG( out + 8, S->h[2] ); - U32TO8_BIG( out + 12, S->h[3] ); - U32TO8_BIG( out + 16, S->h[4] ); - U32TO8_BIG( out + 20, S->h[5] ); - U32TO8_BIG( out + 24, S->h[6] ); - U32TO8_BIG( out + 28, S->h[7] ); -} - - -void blake256( const uint8_t *in, size_t inlen, uint8_t *out ) -{ - BLAKE256_CTX S = {0}; - blake256_Init( &S ); - blake256_Update( &S, in, inlen ); - blake256_Final( &S, out ); -} diff --git a/trezor-crypto/crypto/blake2b.c b/trezor-crypto/crypto/blake2b.c deleted file mode 100644 index 5b71c58de65..00000000000 --- a/trezor-crypto/crypto/blake2b.c +++ /dev/null @@ -1,334 +0,0 @@ -/* - BLAKE2 reference source code package - reference C implementations - - Copyright 2012, Samuel Neves . You may use this under the - terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at - your option. The terms of these licenses can be found at: - - - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 - - OpenSSL license : https://www.openssl.org/source/license.html - - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 - - More information about the BLAKE2 hash function can be found at - https://blake2.net. -*/ - -#include - -#include -#include -#include - -typedef struct blake2b_param__ -{ - uint8_t digest_length; /* 1 */ - uint8_t key_length; /* 2 */ - uint8_t fanout; /* 3 */ - uint8_t depth; /* 4 */ - uint32_t leaf_length; /* 8 */ - uint32_t node_offset; /* 12 */ - uint32_t xof_length; /* 16 */ - uint8_t node_depth; /* 17 */ - uint8_t inner_length; /* 18 */ - uint8_t reserved[14]; /* 32 */ - uint8_t salt[BLAKE2B_SALTBYTES]; /* 48 */ - uint8_t personal[BLAKE2B_PERSONALBYTES]; /* 64 */ -} __attribute__((packed)) blake2b_param; - -const uint64_t blake2b_IV[8] = -{ - 0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, - 0x3c6ef372fe94f82bULL, 0xa54ff53a5f1d36f1ULL, - 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL, - 0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL -}; - -const uint8_t blake2b_sigma[12][16] = -{ - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , - { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } , - { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 } , - { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 } , - { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 } , - { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 } , - { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 } , - { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 } , - { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 } , - { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0 } , - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , - { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } -}; - - -static void blake2b_set_lastnode( blake2b_state *S ) -{ - S->f[1] = (uint64_t)-1; -} - -/* Some helper functions, not necessarily useful */ -static int blake2b_is_lastblock( const blake2b_state *S ) -{ - return S->f[0] != 0; -} - -static void blake2b_set_lastblock( blake2b_state *S ) -{ - if( S->last_node ) blake2b_set_lastnode( S ); - - S->f[0] = (uint64_t)-1; -} - -static void blake2b_increment_counter( blake2b_state *S, const uint64_t inc ) -{ - S->t[0] += inc; - S->t[1] += ( S->t[0] < inc ); -} - -static void blake2b_init0( blake2b_state *S ) -{ - size_t i = 0; - memzero( S, sizeof( blake2b_state ) ); - - for( i = 0; i < 8; ++i ) S->h[i] = blake2b_IV[i]; -} - -/* init xors IV with input parameter block */ -static int blake2b_init_param( blake2b_state *S, const blake2b_param *P ) -{ - const uint8_t *p = ( const uint8_t * )( P ); - size_t i = 0; - - blake2b_init0( S ); - - /* IV XOR ParamBlock */ - for( i = 0; i < 8; ++i ) - S->h[i] ^= load64( p + sizeof( S->h[i] ) * i ); - - S->outlen = P->digest_length; - return 0; -} - - -/* Sequential blake2b initialization */ -int tc_blake2b_Init( blake2b_state *S, size_t outlen ) -{ - blake2b_param P[1] = {0}; - - if ( ( !outlen ) || ( outlen > BLAKE2B_OUTBYTES ) ) return -1; - - P->digest_length = (uint8_t)outlen; - P->key_length = 0; - P->fanout = 1; - P->depth = 1; - store32( &P->leaf_length, 0 ); - store32( &P->node_offset, 0 ); - store32( &P->xof_length, 0 ); - P->node_depth = 0; - P->inner_length = 0; - memzero( P->reserved, sizeof( P->reserved ) ); - memzero( P->salt, sizeof( P->salt ) ); - memzero( P->personal, sizeof( P->personal ) ); - return blake2b_init_param( S, P ); -} - -int tc_blake2b_InitPersonal( blake2b_state *S, size_t outlen, const void *personal, size_t personal_len) -{ - blake2b_param P[1] = {0}; - - if ( ( !outlen ) || ( outlen > BLAKE2B_OUTBYTES ) ) return -1; - if ( ( !personal ) || ( personal_len != BLAKE2B_PERSONALBYTES ) ) return -1; - - P->digest_length = (uint8_t)outlen; - P->key_length = 0; - P->fanout = 1; - P->depth = 1; - store32( &P->leaf_length, 0 ); - store32( &P->node_offset, 0 ); - store32( &P->xof_length, 0 ); - P->node_depth = 0; - P->inner_length = 0; - memzero( P->reserved, sizeof( P->reserved ) ); - memzero( P->salt, sizeof( P->salt ) ); - memcpy( P->personal, personal, BLAKE2B_PERSONALBYTES ); - return blake2b_init_param( S, P ); -} - -int tc_blake2b_InitKey( blake2b_state *S, size_t outlen, const void *key, size_t keylen ) -{ - blake2b_param P[1] = {0}; - - if ( ( !outlen ) || ( outlen > BLAKE2B_OUTBYTES ) ) return -1; - - if ( !key || !keylen || keylen > BLAKE2B_KEYBYTES ) return -1; - - P->digest_length = (uint8_t)outlen; - P->key_length = (uint8_t)keylen; - P->fanout = 1; - P->depth = 1; - store32( &P->leaf_length, 0 ); - store32( &P->node_offset, 0 ); - store32( &P->xof_length, 0 ); - P->node_depth = 0; - P->inner_length = 0; - memzero( P->reserved, sizeof( P->reserved ) ); - memzero( P->salt, sizeof( P->salt ) ); - memzero( P->personal, sizeof( P->personal ) ); - - if( blake2b_init_param( S, P ) < 0 ) return -1; - - { - uint8_t block[BLAKE2B_BLOCKBYTES] = {0}; - memzero( block, BLAKE2B_BLOCKBYTES ); - memcpy( block, key, keylen ); - tc_blake2b_Update( S, block, BLAKE2B_BLOCKBYTES ); - memzero( block, BLAKE2B_BLOCKBYTES ); /* Burn the key from stack */ - } - return 0; -} - -#define G(r,i,a,b,c,d) \ - do { \ - a = a + b + m[blake2b_sigma[r][2*i+0]]; \ - d = rotr64(d ^ a, 32); \ - c = c + d; \ - b = rotr64(b ^ c, 24); \ - a = a + b + m[blake2b_sigma[r][2*i+1]]; \ - d = rotr64(d ^ a, 16); \ - c = c + d; \ - b = rotr64(b ^ c, 63); \ - } while(0) - -#define ROUND(r) \ - do { \ - G(r,0,v[ 0],v[ 4],v[ 8],v[12]); \ - G(r,1,v[ 1],v[ 5],v[ 9],v[13]); \ - G(r,2,v[ 2],v[ 6],v[10],v[14]); \ - G(r,3,v[ 3],v[ 7],v[11],v[15]); \ - G(r,4,v[ 0],v[ 5],v[10],v[15]); \ - G(r,5,v[ 1],v[ 6],v[11],v[12]); \ - G(r,6,v[ 2],v[ 7],v[ 8],v[13]); \ - G(r,7,v[ 3],v[ 4],v[ 9],v[14]); \ - } while(0) - -static void blake2b_compress( blake2b_state *S, const uint8_t block[BLAKE2B_BLOCKBYTES] ) -{ - uint64_t m[16] = {0}; - uint64_t v[16] = {0}; - size_t i = 0; - - for( i = 0; i < 16; ++i ) { - m[i] = load64( block + i * sizeof( m[i] ) ); - } - - for( i = 0; i < 8; ++i ) { - v[i] = S->h[i]; - } - - v[ 8] = blake2b_IV[0]; - v[ 9] = blake2b_IV[1]; - v[10] = blake2b_IV[2]; - v[11] = blake2b_IV[3]; - v[12] = blake2b_IV[4] ^ S->t[0]; - v[13] = blake2b_IV[5] ^ S->t[1]; - v[14] = blake2b_IV[6] ^ S->f[0]; - v[15] = blake2b_IV[7] ^ S->f[1]; - - ROUND( 0 ); - ROUND( 1 ); - ROUND( 2 ); - ROUND( 3 ); - ROUND( 4 ); - ROUND( 5 ); - ROUND( 6 ); - ROUND( 7 ); - ROUND( 8 ); - ROUND( 9 ); - ROUND( 10 ); - ROUND( 11 ); - - for( i = 0; i < 8; ++i ) { - S->h[i] = S->h[i] ^ v[i] ^ v[i + 8]; - } -} - -#undef G -#undef ROUND - -int tc_blake2b_Update( blake2b_state *S, const void *pin, size_t inlen ) -{ - const unsigned char * in = (const unsigned char *)pin; - if( inlen > 0 ) - { - size_t left = S->buflen; - size_t fill = BLAKE2B_BLOCKBYTES - left; - if( inlen > fill ) - { - S->buflen = 0; - memcpy( S->buf + left, in, fill ); /* Fill buffer */ - blake2b_increment_counter( S, BLAKE2B_BLOCKBYTES ); - blake2b_compress( S, S->buf ); /* Compress */ - in += fill; inlen -= fill; - while(inlen > BLAKE2B_BLOCKBYTES) { - blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES); - blake2b_compress( S, in ); - in += BLAKE2B_BLOCKBYTES; - inlen -= BLAKE2B_BLOCKBYTES; - } - } - memcpy( S->buf + S->buflen, in, inlen ); - S->buflen += inlen; - } - return 0; -} - -int tc_blake2b_Final( blake2b_state *S, void *out, size_t outlen ) -{ - uint8_t buffer[BLAKE2B_OUTBYTES] = {0}; - size_t i = 0; - - if( out == NULL || outlen < S->outlen ) - return -1; - - if( blake2b_is_lastblock( S ) ) - return -1; - - blake2b_increment_counter( S, S->buflen ); - blake2b_set_lastblock( S ); - memzero( S->buf + S->buflen, BLAKE2B_BLOCKBYTES - S->buflen ); /* Padding */ - blake2b_compress( S, S->buf ); - - for( i = 0; i < 8; ++i ) /* Output full hash to temp buffer */ - store64( buffer + sizeof( S->h[i] ) * i, S->h[i] ); - - memcpy( out, buffer, S->outlen ); - memzero(buffer, sizeof(buffer)); - return 0; -} - -int tc_blake2b(const uint8_t *msg, uint32_t msg_len, void *out, size_t outlen) -{ - BLAKE2B_CTX ctx; - if (0 != tc_blake2b_Init(&ctx, outlen)) return -1; - if (0 != tc_blake2b_Update(&ctx, msg, msg_len)) return -1; - if (0 != tc_blake2b_Final(&ctx, out, outlen)) return -1; - return 0; -} - -// [wallet-core] -int tc_blake2b_Personal(const uint8_t *msg, uint32_t msg_len, const void *personal, size_t personal_len, void *out, size_t outlen) -{ - BLAKE2B_CTX ctx; - if (0 != tc_blake2b_InitPersonal(&ctx, outlen, personal, personal_len)) return -1; - if (0 != tc_blake2b_Update(&ctx, msg, msg_len)) return -1; - if (0 != tc_blake2b_Final(&ctx, out, outlen)) return -1; - return 0; -} - -int tc_blake2b_Key(const uint8_t *msg, uint32_t msg_len, const void *key, size_t keylen, void *out, size_t outlen) -{ - BLAKE2B_CTX ctx; - if (0 != tc_blake2b_InitKey(&ctx, outlen, key, keylen)) return -1; - if (0 != tc_blake2b_Update(&ctx, msg, msg_len)) return -1; - if (0 != tc_blake2b_Final(&ctx, out, outlen)) return -1; - return 0; -} diff --git a/trezor-crypto/crypto/blake2s.c b/trezor-crypto/crypto/blake2s.c deleted file mode 100644 index 2dfd8523345..00000000000 --- a/trezor-crypto/crypto/blake2s.c +++ /dev/null @@ -1,318 +0,0 @@ -/* - BLAKE2 reference source code package - reference C implementations - - Copyright 2012, Samuel Neves . You may use this under the - terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at - your option. The terms of these licenses can be found at: - - - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 - - OpenSSL license : https://www.openssl.org/source/license.html - - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 - - More information about the BLAKE2 hash function can be found at - https://blake2.net. -*/ - -#include - -#include -#include -#include - -typedef struct blake2s_param__ -{ - uint8_t digest_length; /* 1 */ - uint8_t key_length; /* 2 */ - uint8_t fanout; /* 3 */ - uint8_t depth; /* 4 */ - uint32_t leaf_length; /* 8 */ - uint32_t node_offset; /* 12 */ - uint16_t xof_length; /* 14 */ - uint8_t node_depth; /* 15 */ - uint8_t inner_length; /* 16 */ - /* uint8_t reserved[0]; */ - uint8_t salt[BLAKE2S_SALTBYTES]; /* 24 */ - uint8_t personal[BLAKE2S_PERSONALBYTES]; /* 32 */ -} __attribute__((packed)) blake2s_param; - -const uint32_t blake2s_IV[8] = -{ - 0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL, - 0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL -}; - -const uint8_t blake2s_sigma[10][16] = -{ - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , - { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } , - { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 } , - { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 } , - { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 } , - { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 } , - { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 } , - { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 } , - { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 } , - { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0 } , -}; - -static void blake2s_set_lastnode( blake2s_state *S ) -{ - S->f[1] = (uint32_t)-1; -} - -/* Some helper functions, not necessarily useful */ -static int blake2s_is_lastblock( const blake2s_state *S ) -{ - return S->f[0] != 0; -} - -static void blake2s_set_lastblock( blake2s_state *S ) -{ - if( S->last_node ) blake2s_set_lastnode( S ); - - S->f[0] = (uint32_t)-1; -} - -static void blake2s_increment_counter( blake2s_state *S, const uint32_t inc ) -{ - S->t[0] += inc; - S->t[1] += ( S->t[0] < inc ); -} - -static void blake2s_init0( blake2s_state *S ) -{ - size_t i = 0; - memzero( S, sizeof( blake2s_state ) ); - - for( i = 0; i < 8; ++i ) S->h[i] = blake2s_IV[i]; -} - -/* init2 xors IV with input parameter block */ -int blake2s_init_param( blake2s_state *S, const blake2s_param *P ) -{ - const unsigned char *p = ( const unsigned char * )( P ); - size_t i = 0; - - blake2s_init0( S ); - - /* IV XOR ParamBlock */ - for( i = 0; i < 8; ++i ) - S->h[i] ^= load32( &p[i * 4] ); - - S->outlen = P->digest_length; - return 0; -} - - -/* Sequential blake2s initialization */ -int blake2s_Init( blake2s_state *S, size_t outlen ) -{ - blake2s_param P[1] = {0}; - - if ( ( !outlen ) || ( outlen > BLAKE2S_OUTBYTES ) ) return -1; - - P->digest_length = (uint8_t)outlen; - P->key_length = 0; - P->fanout = 1; - P->depth = 1; - store32( &P->leaf_length, 0 ); - store32( &P->node_offset, 0 ); - store16( &P->xof_length, 0 ); - P->node_depth = 0; - P->inner_length = 0; - /* memzero(P->reserved, sizeof(P->reserved) ); */ - memzero( P->salt, sizeof( P->salt ) ); - memzero( P->personal, sizeof( P->personal ) ); - return blake2s_init_param( S, P ); -} - -int blake2s_InitPersonal( blake2s_state *S, size_t outlen, const void *personal, size_t personal_len) -{ - blake2s_param P[1] = {0}; - - if ( ( !outlen ) || ( outlen > BLAKE2S_OUTBYTES ) ) return -1; - if ( ( !personal ) || ( personal_len != BLAKE2S_PERSONALBYTES ) ) return -1; - - P->digest_length = (uint8_t)outlen; - P->key_length = 0; - P->fanout = 1; - P->depth = 1; - store32( &P->leaf_length, 0 ); - store32( &P->node_offset, 0 ); - store16( &P->xof_length, 0 ); - P->node_depth = 0; - P->inner_length = 0; - /* memzero(P->reserved, sizeof(P->reserved) ); */ - memzero( P->salt, sizeof( P->salt ) ); - memcpy( P->personal, personal, BLAKE2S_PERSONALBYTES ); - return blake2s_init_param( S, P ); -} - - -int blake2s_InitKey( blake2s_state *S, size_t outlen, const void *key, size_t keylen ) -{ - blake2s_param P[1] = {0}; - - if ( ( !outlen ) || ( outlen > BLAKE2S_OUTBYTES ) ) return -1; - - if ( !key || !keylen || keylen > BLAKE2S_KEYBYTES ) return -1; - - P->digest_length = (uint8_t)outlen; - P->key_length = (uint8_t)keylen; - P->fanout = 1; - P->depth = 1; - store32( &P->leaf_length, 0 ); - store32( &P->node_offset, 0 ); - store16( &P->xof_length, 0 ); - P->node_depth = 0; - P->inner_length = 0; - /* memzero(P->reserved, sizeof(P->reserved) ); */ - memzero( P->salt, sizeof( P->salt ) ); - memzero( P->personal, sizeof( P->personal ) ); - - if( blake2s_init_param( S, P ) < 0 ) return -1; - - { - uint8_t block[BLAKE2S_BLOCKBYTES] = {0}; - memzero( block, BLAKE2S_BLOCKBYTES ); - memcpy( block, key, keylen ); - blake2s_Update( S, block, BLAKE2S_BLOCKBYTES ); - memzero( block, BLAKE2S_BLOCKBYTES ); /* Burn the key from stack */ - } - return 0; -} - -#define G(r,i,a,b,c,d) \ - do { \ - a = a + b + m[blake2s_sigma[r][2*i+0]]; \ - d = rotr32(d ^ a, 16); \ - c = c + d; \ - b = rotr32(b ^ c, 12); \ - a = a + b + m[blake2s_sigma[r][2*i+1]]; \ - d = rotr32(d ^ a, 8); \ - c = c + d; \ - b = rotr32(b ^ c, 7); \ - } while(0) - -#define ROUND(r) \ - do { \ - G(r,0,v[ 0],v[ 4],v[ 8],v[12]); \ - G(r,1,v[ 1],v[ 5],v[ 9],v[13]); \ - G(r,2,v[ 2],v[ 6],v[10],v[14]); \ - G(r,3,v[ 3],v[ 7],v[11],v[15]); \ - G(r,4,v[ 0],v[ 5],v[10],v[15]); \ - G(r,5,v[ 1],v[ 6],v[11],v[12]); \ - G(r,6,v[ 2],v[ 7],v[ 8],v[13]); \ - G(r,7,v[ 3],v[ 4],v[ 9],v[14]); \ - } while(0) - -static void blake2s_compress( blake2s_state *S, const uint8_t in[BLAKE2S_BLOCKBYTES] ) -{ - uint32_t m[16] = {0}; - uint32_t v[16] = {0}; - size_t i = 0; - - for( i = 0; i < 16; ++i ) { - m[i] = load32( in + i * sizeof( m[i] ) ); - } - - for( i = 0; i < 8; ++i ) { - v[i] = S->h[i]; - } - - v[ 8] = blake2s_IV[0]; - v[ 9] = blake2s_IV[1]; - v[10] = blake2s_IV[2]; - v[11] = blake2s_IV[3]; - v[12] = S->t[0] ^ blake2s_IV[4]; - v[13] = S->t[1] ^ blake2s_IV[5]; - v[14] = S->f[0] ^ blake2s_IV[6]; - v[15] = S->f[1] ^ blake2s_IV[7]; - - ROUND( 0 ); - ROUND( 1 ); - ROUND( 2 ); - ROUND( 3 ); - ROUND( 4 ); - ROUND( 5 ); - ROUND( 6 ); - ROUND( 7 ); - ROUND( 8 ); - ROUND( 9 ); - - for( i = 0; i < 8; ++i ) { - S->h[i] = S->h[i] ^ v[i] ^ v[i + 8]; - } -} - -#undef G -#undef ROUND - -int blake2s_Update( blake2s_state *S, const void *pin, size_t inlen ) -{ - const unsigned char * in = (const unsigned char *)pin; - if( inlen > 0 ) - { - size_t left = S->buflen; - size_t fill = BLAKE2S_BLOCKBYTES - left; - if( inlen > fill ) - { - S->buflen = 0; - memcpy( S->buf + left, in, fill ); /* Fill buffer */ - blake2s_increment_counter( S, BLAKE2S_BLOCKBYTES ); - blake2s_compress( S, S->buf ); /* Compress */ - in += fill; inlen -= fill; - while(inlen > BLAKE2S_BLOCKBYTES) { - blake2s_increment_counter(S, BLAKE2S_BLOCKBYTES); - blake2s_compress( S, in ); - in += BLAKE2S_BLOCKBYTES; - inlen -= BLAKE2S_BLOCKBYTES; - } - } - memcpy( S->buf + S->buflen, in, inlen ); - S->buflen += inlen; - } - return 0; -} - -int blake2s_Final( blake2s_state *S, void *out, size_t outlen ) -{ - uint8_t buffer[BLAKE2S_OUTBYTES] = {0}; - size_t i = 0; - - if( out == NULL || outlen < S->outlen ) - return -1; - - if( blake2s_is_lastblock( S ) ) - return -1; - - blake2s_increment_counter( S, ( uint32_t )S->buflen ); - blake2s_set_lastblock( S ); - memzero( S->buf + S->buflen, BLAKE2S_BLOCKBYTES - S->buflen ); /* Padding */ - blake2s_compress( S, S->buf ); - - for( i = 0; i < 8; ++i ) /* Output full hash to temp buffer */ - store32( buffer + sizeof( S->h[i] ) * i, S->h[i] ); - - memcpy( out, buffer, outlen ); - memzero(buffer, sizeof(buffer)); - return 0; -} - -int blake2s(const uint8_t *msg, uint32_t msg_len, void *out, size_t outlen) -{ - BLAKE2S_CTX ctx; - if (0 != blake2s_Init(&ctx, outlen)) return -1; - if (0 != blake2s_Update(&ctx, msg, msg_len)) return -1; - if (0 != blake2s_Final(&ctx, out, outlen)) return -1; - return 0; -} - -int blake2s_Key(const uint8_t *msg, uint32_t msg_len, const void *key, size_t keylen, void *out, size_t outlen) -{ - BLAKE2S_CTX ctx; - if (0 != blake2s_InitKey(&ctx, outlen, key, keylen)) return -1; - if (0 != blake2s_Update(&ctx, msg, msg_len)) return -1; - if (0 != blake2s_Final(&ctx, out, outlen)) return -1; - return 0; -} diff --git a/trezor-crypto/crypto/cardano.c b/trezor-crypto/crypto/cardano.c deleted file mode 100644 index 6b07f776c15..00000000000 --- a/trezor-crypto/crypto/cardano.c +++ /dev/null @@ -1,307 +0,0 @@ -/** - * Copyright (c) 2013-2021 SatoshiLabs - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if USE_CARDANO - -#define CARDANO_MAX_NODE_DEPTH 1048576 - -const curve_info ed25519_cardano_info = { - .bip32_name = ED25519_CARDANO_NAME, - .params = NULL, - .hasher_base58 = HASHER_SHA2D, - .hasher_sign = HASHER_SHA2D, - .hasher_pubkey = HASHER_SHA2_RIPEMD, - .hasher_script = HASHER_SHA2, -}; - -static void scalar_multiply8(const uint8_t *src, int bytes, uint8_t *dst) { - uint8_t prev_acc = 0; - for (int i = 0; i < bytes; i++) { - dst[i] = (src[i] << 3) + (prev_acc & 0x7); - prev_acc = src[i] >> 5; - } - dst[bytes] = src[bytes - 1] >> 5; -} - -static void scalar_add_256bits(const uint8_t *src1, const uint8_t *src2, - uint8_t *dst) { - uint16_t r = 0; - for (int i = 0; i < 32; i++) { - r = r + (uint16_t)src1[i] + (uint16_t)src2[i]; - dst[i] = r & 0xff; - r >>= 8; - } -} - -static void cardano_ed25519_tweak_bits(uint8_t private_key[32]) { - private_key[0] &= 0xf8; - private_key[31] &= 0x1f; - private_key[31] |= 0x40; -} - -int hdnode_private_ckd_cardano(HDNode *inout, uint32_t index) { - if (inout->curve != &ed25519_cardano_info) { - return 0; - } - - if (inout->depth >= CARDANO_MAX_NODE_DEPTH) { - return 0; - } - - // checks for hardened/non-hardened derivation, keysize 32 means we are - // dealing with public key and thus non-h, keysize 64 is for private key - int keysize = 32; - if (index & 0x80000000) { - keysize = 64; - } - - CONFIDENTIAL uint8_t data[1 + 64 + 4]; - CONFIDENTIAL uint8_t z[32 + 32]; - CONFIDENTIAL uint8_t priv_key[64]; - CONFIDENTIAL uint8_t res_key[64]; - - write_le(data + keysize + 1, index); - - memcpy(priv_key, inout->private_key, 32); - memcpy(priv_key + 32, inout->private_key_extension, 32); - - if (keysize == 64) { // private derivation - data[0] = 0; - memcpy(data + 1, inout->private_key, 32); - memcpy(data + 1 + 32, inout->private_key_extension, 32); - } else { // public derivation - if (hdnode_fill_public_key(inout) != 0) { - return 0; - } - data[0] = 2; - memcpy(data + 1, inout->public_key + 1, 32); - } - - CONFIDENTIAL HMAC_SHA512_CTX ctx; - hmac_sha512_Init(&ctx, inout->chain_code, 32); - hmac_sha512_Update(&ctx, data, 1 + keysize + 4); - hmac_sha512_Final(&ctx, z); - - CONFIDENTIAL uint8_t zl8[32]; - memzero(zl8, 32); - - /* get 8 * Zl */ - scalar_multiply8(z, 28, zl8); - /* Kl = 8*Zl + parent(K)l */ - scalar_add_256bits(zl8, priv_key, res_key); - - /* Kr = Zr + parent(K)r */ - scalar_add_256bits(z + 32, priv_key + 32, res_key + 32); - - memcpy(inout->private_key, res_key, 32); - memcpy(inout->private_key_extension, res_key + 32, 32); - - if (keysize == 64) { - data[0] = 1; - } else { - data[0] = 3; - } - hmac_sha512_Init(&ctx, inout->chain_code, 32); - hmac_sha512_Update(&ctx, data, 1 + keysize + 4); - hmac_sha512_Final(&ctx, z); - - memcpy(inout->chain_code, z + 32, 32); - inout->depth++; - inout->child_num = index; - memzero(inout->public_key, sizeof(inout->public_key)); - - // making sure to wipe our memory - memzero(z, sizeof(z)); - memzero(data, sizeof(data)); - memzero(priv_key, sizeof(priv_key)); - memzero(res_key, sizeof(res_key)); - return 1; -} - -int hdnode_from_secret_cardano(const uint8_t secret[CARDANO_SECRET_LENGTH], - HDNode *out) { - memzero(out, sizeof(HDNode)); - out->depth = 0; - out->child_num = 0; - out->curve = &ed25519_cardano_info; - memcpy(out->private_key, secret, 32); - memcpy(out->private_key_extension, secret + 32, 32); - memcpy(out->chain_code, secret + 64, 32); - - cardano_ed25519_tweak_bits(out->private_key); - - out->public_key[0] = 0; - if (hdnode_fill_public_key(out) != 0) { - return 0; - } - - return 1; -} - -// Derives the root Cardano secret from a master secret, aka seed, as defined in -// SLIP-0023. -int secret_from_seed_cardano_slip23(const uint8_t *seed, int seed_len, - uint8_t secret_out[CARDANO_SECRET_LENGTH]) { - CONFIDENTIAL uint8_t I[SHA512_DIGEST_LENGTH]; - CONFIDENTIAL HMAC_SHA512_CTX ctx; - - hmac_sha512_Init(&ctx, (const uint8_t *)ED25519_CARDANO_NAME, - strlen(ED25519_CARDANO_NAME)); - hmac_sha512_Update(&ctx, seed, seed_len); - hmac_sha512_Final(&ctx, I); - - sha512_Raw(I, 32, secret_out); - - memcpy(secret_out + SHA512_DIGEST_LENGTH, I + 32, 32); - cardano_ed25519_tweak_bits(secret_out); - - memzero(I, sizeof(I)); - memzero(&ctx, sizeof(ctx)); - return 1; -} - -// Derives the root Cardano secret from a BIP-32 master secret via the Ledger -// derivation: -// https://github.com/cardano-foundation/CIPs/blob/09d7d8ee1bd64f7e6b20b5a6cae088039dce00cb/CIP-0003/Ledger.md -int secret_from_seed_cardano_ledger(const uint8_t *seed, int seed_len, - uint8_t secret_out[CARDANO_SECRET_LENGTH]) { - CONFIDENTIAL uint8_t chain_code[SHA256_DIGEST_LENGTH]; - CONFIDENTIAL uint8_t root_key[SHA512_DIGEST_LENGTH]; - CONFIDENTIAL HMAC_SHA256_CTX ctx; - CONFIDENTIAL HMAC_SHA512_CTX sctx; - - const uint8_t *intermediate_result = seed; - int intermediate_result_len = seed_len; - do { - // STEP 1: derive a master secret like in BIP-32/SLIP-10 - hmac_sha512_Init(&sctx, (const uint8_t *)ED25519_SEED_NAME, - strlen(ED25519_SEED_NAME)); - hmac_sha512_Update(&sctx, intermediate_result, intermediate_result_len); - hmac_sha512_Final(&sctx, root_key); - - // STEP 2: check that the resulting key does not have a particular bit set, - // otherwise iterate like in SLIP-10 - intermediate_result = root_key; - intermediate_result_len = sizeof(root_key); - } while (root_key[31] & 0x20); - - // STEP 3: calculate the chain code as a HMAC-SHA256 of "\x01" + seed, - // key is "ed25519 seed" - hmac_sha256_Init(&ctx, (const unsigned char *)ED25519_SEED_NAME, - strlen(ED25519_SEED_NAME)); - hmac_sha256_Update(&ctx, (const unsigned char *)"\x01", 1); - hmac_sha256_Update(&ctx, seed, seed_len); - hmac_sha256_Final(&ctx, chain_code); - - // STEP 4: extract information into output - _Static_assert( - SHA512_DIGEST_LENGTH + SHA256_DIGEST_LENGTH == CARDANO_SECRET_LENGTH, - "Invalid configuration of Cardano secret size"); - memcpy(secret_out, root_key, SHA512_DIGEST_LENGTH); - memcpy(secret_out + SHA512_DIGEST_LENGTH, chain_code, SHA256_DIGEST_LENGTH); - - // STEP 5: tweak bits of the private key - cardano_ed25519_tweak_bits(secret_out); - - memzero(&ctx, sizeof(ctx)); - memzero(&sctx, sizeof(sctx)); - memzero(root_key, sizeof(root_key)); - memzero(chain_code, sizeof(chain_code)); - return 1; -} - -#define CARDANO_ICARUS_STEPS 32 -_Static_assert( - CARDANO_ICARUS_PBKDF2_ROUNDS % CARDANO_ICARUS_STEPS == 0, - "CARDANO_ICARUS_STEPS does not divide CARDANO_ICARUS_PBKDF2_ROUNDS"); -#define CARDANO_ICARUS_ROUNDS_PER_STEP \ - (CARDANO_ICARUS_PBKDF2_ROUNDS / CARDANO_ICARUS_STEPS) - -// Derives the root Cardano HDNode from a passphrase and the entropy encoded in -// a BIP-0039 mnemonic using the Icarus derivation scheme, aka V2 derivation -// scheme: -// https://github.com/cardano-foundation/CIPs/blob/09d7d8ee1bd64f7e6b20b5a6cae088039dce00cb/CIP-0003/Icarus.md -int secret_from_entropy_cardano_icarus( - const uint8_t *pass, int pass_len, const uint8_t *entropy, int entropy_len, - uint8_t secret_out[CARDANO_SECRET_LENGTH], - void (*progress_callback)(uint32_t, uint32_t)) { - CONFIDENTIAL PBKDF2_HMAC_SHA512_CTX pctx; - CONFIDENTIAL uint8_t digest[SHA512_DIGEST_LENGTH]; - uint32_t progress = 0; - - // PASS 1: first 64 bytes - pbkdf2_hmac_sha512_Init(&pctx, pass, pass_len, entropy, entropy_len, 1); - if (progress_callback) { - progress_callback(progress, CARDANO_ICARUS_PBKDF2_ROUNDS * 2); - } - for (int i = 0; i < CARDANO_ICARUS_STEPS; i++) { - pbkdf2_hmac_sha512_Update(&pctx, CARDANO_ICARUS_ROUNDS_PER_STEP); - if (progress_callback) { - progress += CARDANO_ICARUS_ROUNDS_PER_STEP; - progress_callback(progress, CARDANO_ICARUS_PBKDF2_ROUNDS * 2); - } - } - pbkdf2_hmac_sha512_Final(&pctx, digest); - - memcpy(secret_out, digest, SHA512_DIGEST_LENGTH); - - // PASS 2: remaining 32 bytes - pbkdf2_hmac_sha512_Init(&pctx, pass, pass_len, entropy, entropy_len, 2); - if (progress_callback) { - progress_callback(progress, CARDANO_ICARUS_PBKDF2_ROUNDS * 2); - } - for (int i = 0; i < CARDANO_ICARUS_STEPS; i++) { - pbkdf2_hmac_sha512_Update(&pctx, CARDANO_ICARUS_ROUNDS_PER_STEP); - if (progress_callback) { - progress += CARDANO_ICARUS_ROUNDS_PER_STEP; - progress_callback(progress, CARDANO_ICARUS_PBKDF2_ROUNDS * 2); - } - } - pbkdf2_hmac_sha512_Final(&pctx, digest); - - memcpy(secret_out + SHA512_DIGEST_LENGTH, digest, - CARDANO_SECRET_LENGTH - SHA512_DIGEST_LENGTH); - - cardano_ed25519_tweak_bits(secret_out); - - memzero(&pctx, sizeof(pctx)); - memzero(digest, sizeof(digest)); - return 1; -} - -#endif // USE_CARDANO diff --git a/trezor-crypto/crypto/chacha20poly1305/LICENSE b/trezor-crypto/crypto/chacha20poly1305/LICENSE deleted file mode 100644 index 95404966f07..00000000000 --- a/trezor-crypto/crypto/chacha20poly1305/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (C) 2016 Will Glozer - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/trezor-crypto/crypto/chacha20poly1305/chacha20poly1305.c b/trezor-crypto/crypto/chacha20poly1305/chacha20poly1305.c deleted file mode 100644 index 514c452ffb7..00000000000 --- a/trezor-crypto/crypto/chacha20poly1305/chacha20poly1305.c +++ /dev/null @@ -1,60 +0,0 @@ -// Implementations of the XChaCha20 + Poly1305 and ChaCha20 + Poly1305 -// AEAD constructions with a goal of simplicity and correctness rather -// than performance. - -#include -#include - -void hchacha20(ECRYPT_ctx *x,u8 *c); - -// Initialize the XChaCha20 + Poly1305 context for encryption or decryption -// using a 32 byte key and 24 byte nonce. The key and the first 16 bytes of -// the nonce are used as input to HChaCha20 to derive the Chacha20 key. -void xchacha20poly1305_init(chacha20poly1305_ctx *ctx, const uint8_t key[32], const uint8_t nonce[24]) { - unsigned char subkey[32] = {0}; - unsigned char block0[64] = {0}; - ECRYPT_ctx tmp = {0}; - - // Generate the Chacha20 key by applying HChaCha20 to the - // original key and the first 16 bytes of the nonce. - ECRYPT_keysetup(&tmp, key, 256, 16); - tmp.input[12] = U8TO32_LITTLE(nonce + 0); - tmp.input[13] = U8TO32_LITTLE(nonce + 4); - tmp.input[14] = U8TO32_LITTLE(nonce + 8); - tmp.input[15] = U8TO32_LITTLE(nonce + 12); - hchacha20(&tmp, subkey); - - // Initialize Chacha20 with the newly generated key and - // the last 8 bytes of the nonce. - ECRYPT_keysetup(&ctx->chacha20, subkey, 256, 16); - ECRYPT_ivsetup(&ctx->chacha20, nonce+16); - - // Encrypt 64 bytes of zeros and use the first 32 bytes - // as the Poly1305 key. - ECRYPT_encrypt_bytes(&ctx->chacha20, block0, block0, 64); - poly1305_init(&ctx->poly1305, block0); -} - -// Encrypt n bytes of plaintext where n must be evenly divisible by the -// Chacha20 blocksize of 64, except for the final n bytes of plaintext. -void chacha20poly1305_encrypt(chacha20poly1305_ctx *ctx, const uint8_t *in, uint8_t *out, size_t n) { - ECRYPT_encrypt_bytes(&ctx->chacha20, in, out, n); - poly1305_update(&ctx->poly1305, out, n); -} - -// Decrypt n bytes of ciphertext where n must be evenly divisible by the -// Chacha20 blocksize of 64, except for the final n bytes of ciphertext. -void chacha20poly1305_decrypt(chacha20poly1305_ctx *ctx, const uint8_t *in, uint8_t *out, size_t n) { - poly1305_update(&ctx->poly1305, in, n); - ECRYPT_encrypt_bytes(&ctx->chacha20, in, out, n); -} - -// Include authenticated data in the Poly1305 MAC. -void chacha20poly1305_auth(chacha20poly1305_ctx *ctx, const uint8_t *in, size_t n) { - poly1305_update(&ctx->poly1305, in, n); -} - -// Compute NaCl secretbox-style Poly1305 MAC. -void chacha20poly1305_finish(chacha20poly1305_ctx *ctx, uint8_t mac[16]) { - poly1305_finish(&ctx->poly1305, mac); -} diff --git a/trezor-crypto/crypto/chacha20poly1305/chacha_merged.c b/trezor-crypto/crypto/chacha20poly1305/chacha_merged.c deleted file mode 100644 index 3819b865a59..00000000000 --- a/trezor-crypto/crypto/chacha20poly1305/chacha_merged.c +++ /dev/null @@ -1,253 +0,0 @@ -/* -chacha-merged.c version 20080118 -D. J. Bernstein -Public domain. -*/ - -#include -#include - -#define ROTATE(v,c) (ROTL32(v,c)) -#define XOR(v,w) ((v) ^ (w)) -#define PLUS(v,w) (U32V((v) + (w))) -#define PLUSONE(v) (PLUS((v),1)) - -#define QUARTERROUND(a,b,c,d) \ - a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \ - c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \ - a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \ - c = PLUS(c,d); b = ROTATE(XOR(b,c), 7); - -void ECRYPT_init(void) -{ - return; -} - -// [wallet-core][non static] rename to avoid duplicate symbol in blake256.c -const char chacha_sigma[16] = "expand 32-byte k"; -const char tau[16] = "expand 16-byte k"; - -void ECRYPT_keysetup(ECRYPT_ctx *x,const u8 *k,u32 kbits,u32 ivbits) -{ - (void)ivbits; - const char *constants = (const char *)0; - - x->input[4] = U8TO32_LITTLE(k + 0); - x->input[5] = U8TO32_LITTLE(k + 4); - x->input[6] = U8TO32_LITTLE(k + 8); - x->input[7] = U8TO32_LITTLE(k + 12); - if (kbits == 256) { /* recommended */ - k += 16; - constants = chacha_sigma; - } else { /* kbits == 128 */ - constants = tau; - } - x->input[8] = U8TO32_LITTLE(k + 0); - x->input[9] = U8TO32_LITTLE(k + 4); - x->input[10] = U8TO32_LITTLE(k + 8); - x->input[11] = U8TO32_LITTLE(k + 12); - x->input[0] = U8TO32_LITTLE(constants + 0); - x->input[1] = U8TO32_LITTLE(constants + 4); - x->input[2] = U8TO32_LITTLE(constants + 8); - x->input[3] = U8TO32_LITTLE(constants + 12); -} - -void ECRYPT_ivsetup(ECRYPT_ctx *x,const u8 *iv) -{ - x->input[12] = 0; - x->input[13] = 0; - x->input[14] = U8TO32_LITTLE(iv + 0); - x->input[15] = U8TO32_LITTLE(iv + 4); -} - -void ECRYPT_ctrsetup(ECRYPT_ctx *x,const u8 *ctr) -{ - x->input[12] = U8TO32_LITTLE(ctr + 0); - x->input[13] = U8TO32_LITTLE(ctr + 4); -} - -void ECRYPT_encrypt_bytes(ECRYPT_ctx *x,const u8 *m,u8 *c,u32 bytes) -{ - u32 x0 = 0, x1 = 0, x2 = 0, x3 = 0, x4 = 0, x5 = 0, x6 = 0, x7 = 0, x8 = 0, x9 = 0, x10 = 0, x11 = 0, x12 = 0, x13 = 0, x14 = 0, x15 = 0; - u32 j0 = 0, j1 = 0, j2 = 0, j3 = 0, j4 = 0, j5 = 0, j6 = 0, j7 = 0, j8 = 0, j9 = 0, j10 = 0, j11 = 0, j12 = 0, j13 = 0, j14 = 0, j15 = 0; - u8 *ctarget = 0; - u8 tmp[64] = {0}; - int i = 0; - - if (!bytes) return; - - j0 = x->input[0]; - j1 = x->input[1]; - j2 = x->input[2]; - j3 = x->input[3]; - j4 = x->input[4]; - j5 = x->input[5]; - j6 = x->input[6]; - j7 = x->input[7]; - j8 = x->input[8]; - j9 = x->input[9]; - j10 = x->input[10]; - j11 = x->input[11]; - j12 = x->input[12]; - j13 = x->input[13]; - j14 = x->input[14]; - j15 = x->input[15]; - - for (;;) { - if (bytes < 64) { - for (i = 0;i < (int)bytes;++i) tmp[i] = m[i]; - m = tmp; - ctarget = c; - c = tmp; - } - x0 = j0; - x1 = j1; - x2 = j2; - x3 = j3; - x4 = j4; - x5 = j5; - x6 = j6; - x7 = j7; - x8 = j8; - x9 = j9; - x10 = j10; - x11 = j11; - x12 = j12; - x13 = j13; - x14 = j14; - x15 = j15; - for (i = 20;i > 0;i -= 2) { - QUARTERROUND( x0, x4, x8,x12) - QUARTERROUND( x1, x5, x9,x13) - QUARTERROUND( x2, x6,x10,x14) - QUARTERROUND( x3, x7,x11,x15) - QUARTERROUND( x0, x5,x10,x15) - QUARTERROUND( x1, x6,x11,x12) - QUARTERROUND( x2, x7, x8,x13) - QUARTERROUND( x3, x4, x9,x14) - } - x0 = PLUS(x0,j0); - x1 = PLUS(x1,j1); - x2 = PLUS(x2,j2); - x3 = PLUS(x3,j3); - x4 = PLUS(x4,j4); - x5 = PLUS(x5,j5); - x6 = PLUS(x6,j6); - x7 = PLUS(x7,j7); - x8 = PLUS(x8,j8); - x9 = PLUS(x9,j9); - x10 = PLUS(x10,j10); - x11 = PLUS(x11,j11); - x12 = PLUS(x12,j12); - x13 = PLUS(x13,j13); - x14 = PLUS(x14,j14); - x15 = PLUS(x15,j15); - - x0 = XOR(x0,U8TO32_LITTLE(m + 0)); - x1 = XOR(x1,U8TO32_LITTLE(m + 4)); - x2 = XOR(x2,U8TO32_LITTLE(m + 8)); - x3 = XOR(x3,U8TO32_LITTLE(m + 12)); - x4 = XOR(x4,U8TO32_LITTLE(m + 16)); - x5 = XOR(x5,U8TO32_LITTLE(m + 20)); - x6 = XOR(x6,U8TO32_LITTLE(m + 24)); - x7 = XOR(x7,U8TO32_LITTLE(m + 28)); - x8 = XOR(x8,U8TO32_LITTLE(m + 32)); - x9 = XOR(x9,U8TO32_LITTLE(m + 36)); - x10 = XOR(x10,U8TO32_LITTLE(m + 40)); - x11 = XOR(x11,U8TO32_LITTLE(m + 44)); - x12 = XOR(x12,U8TO32_LITTLE(m + 48)); - x13 = XOR(x13,U8TO32_LITTLE(m + 52)); - x14 = XOR(x14,U8TO32_LITTLE(m + 56)); - x15 = XOR(x15,U8TO32_LITTLE(m + 60)); - - j12 = PLUSONE(j12); - if (!j12) { - j13 = PLUSONE(j13); - /* stopping at 2^70 bytes per nonce is user's responsibility */ - } - - U32TO8_LITTLE(c + 0,x0); - U32TO8_LITTLE(c + 4,x1); - U32TO8_LITTLE(c + 8,x2); - U32TO8_LITTLE(c + 12,x3); - U32TO8_LITTLE(c + 16,x4); - U32TO8_LITTLE(c + 20,x5); - U32TO8_LITTLE(c + 24,x6); - U32TO8_LITTLE(c + 28,x7); - U32TO8_LITTLE(c + 32,x8); - U32TO8_LITTLE(c + 36,x9); - U32TO8_LITTLE(c + 40,x10); - U32TO8_LITTLE(c + 44,x11); - U32TO8_LITTLE(c + 48,x12); - U32TO8_LITTLE(c + 52,x13); - U32TO8_LITTLE(c + 56,x14); - U32TO8_LITTLE(c + 60,x15); - - if (bytes <= 64) { - if (bytes < 64) { - for (i = 0;i < (int)bytes;++i) ctarget[i] = c[i]; - } - x->input[12] = j12; - x->input[13] = j13; - return; - } - bytes -= 64; - c += 64; - m += 64; - } -} - -void ECRYPT_decrypt_bytes(ECRYPT_ctx *x,const u8 *c,u8 *m,u32 bytes) -{ - ECRYPT_encrypt_bytes(x,c,m,bytes); -} - -void ECRYPT_keystream_bytes(ECRYPT_ctx *x,u8 *stream,u32 bytes) -{ - u32 i = 0; - for (i = 0;i < bytes;++i) stream[i] = 0; - ECRYPT_encrypt_bytes(x,stream,stream,bytes); -} - -void hchacha20(ECRYPT_ctx *x,u8 *c) -{ - u32 x0 = 0, x1 = 0, x2 = 0, x3 = 0, x4 = 0, x5 = 0, x6 = 0, x7 = 0, x8 = 0, x9 = 0, x10 = 0, x11 = 0, x12 = 0, x13 = 0, x14 = 0, x15 = 0; - int i = 0; - - x0 = x->input[0]; - x1 = x->input[1]; - x2 = x->input[2]; - x3 = x->input[3]; - x4 = x->input[4]; - x5 = x->input[5]; - x6 = x->input[6]; - x7 = x->input[7]; - x8 = x->input[8]; - x9 = x->input[9]; - x10 = x->input[10]; - x11 = x->input[11]; - x12 = x->input[12]; - x13 = x->input[13]; - x14 = x->input[14]; - x15 = x->input[15]; - - for (i = 20;i > 0;i -= 2) { - QUARTERROUND( x0, x4, x8,x12) - QUARTERROUND( x1, x5, x9,x13) - QUARTERROUND( x2, x6,x10,x14) - QUARTERROUND( x3, x7,x11,x15) - QUARTERROUND( x0, x5,x10,x15) - QUARTERROUND( x1, x6,x11,x12) - QUARTERROUND( x2, x7, x8,x13) - QUARTERROUND( x3, x4, x9,x14) - } - - U32TO8_LITTLE(c + 0,x0); - U32TO8_LITTLE(c + 4,x1); - U32TO8_LITTLE(c + 8,x2); - U32TO8_LITTLE(c + 12,x3); - U32TO8_LITTLE(c + 16,x12); - U32TO8_LITTLE(c + 20,x13); - U32TO8_LITTLE(c + 24,x14); - U32TO8_LITTLE(c + 28,x15); -} diff --git a/trezor-crypto/crypto/chacha20poly1305/poly1305-donna.c b/trezor-crypto/crypto/chacha20poly1305/poly1305-donna.c deleted file mode 100644 index 682a2ca5443..00000000000 --- a/trezor-crypto/crypto/chacha20poly1305/poly1305-donna.c +++ /dev/null @@ -1,179 +0,0 @@ -#include -#include - -void -poly1305_update(poly1305_context *ctx, const unsigned char *m, size_t bytes) { - poly1305_state_internal_t *st = (poly1305_state_internal_t *)ctx; - size_t i = 0; - - /* handle leftover */ - if (st->leftover) { - size_t want = (poly1305_block_size - st->leftover); - if (want > bytes) - want = bytes; - for (i = 0; i < want; i++) - st->buffer[st->leftover + i] = m[i]; - bytes -= want; - m += want; - st->leftover += want; - if (st->leftover < poly1305_block_size) - return; - poly1305_blocks(st, st->buffer, poly1305_block_size); - st->leftover = 0; - } - - /* process full blocks */ - if (bytes >= poly1305_block_size) { - size_t want = (bytes & ~(poly1305_block_size - 1)); - poly1305_blocks(st, m, want); - m += want; - bytes -= want; - } - - /* store leftover */ - if (bytes) { - for (i = 0; i < bytes; i++) - st->buffer[st->leftover + i] = m[i]; - st->leftover += bytes; - } -} - -void -poly1305_auth(unsigned char mac[16], const unsigned char *m, size_t bytes, const unsigned char key[32]) { - poly1305_context ctx = {0}; - poly1305_init(&ctx, key); - poly1305_update(&ctx, m, bytes); - poly1305_finish(&ctx, mac); -} - -int -poly1305_verify(const unsigned char mac1[16], const unsigned char mac2[16]) { - size_t i = 0; - unsigned int dif = 0; - for (i = 0; i < 16; i++) - dif |= (mac1[i] ^ mac2[i]); - dif = (dif - 1) >> ((sizeof(unsigned int) * 8) - 1); - return (dif & 1); -} - - -/* test a few basic operations */ -int -poly1305_power_on_self_test(void) { - /* example from nacl */ - const unsigned char nacl_key[32] = { - 0xee,0xa6,0xa7,0x25,0x1c,0x1e,0x72,0x91, - 0x6d,0x11,0xc2,0xcb,0x21,0x4d,0x3c,0x25, - 0x25,0x39,0x12,0x1d,0x8e,0x23,0x4e,0x65, - 0x2d,0x65,0x1f,0xa4,0xc8,0xcf,0xf8,0x80, - }; - - const unsigned char nacl_msg[131] = { - 0x8e,0x99,0x3b,0x9f,0x48,0x68,0x12,0x73, - 0xc2,0x96,0x50,0xba,0x32,0xfc,0x76,0xce, - 0x48,0x33,0x2e,0xa7,0x16,0x4d,0x96,0xa4, - 0x47,0x6f,0xb8,0xc5,0x31,0xa1,0x18,0x6a, - 0xc0,0xdf,0xc1,0x7c,0x98,0xdc,0xe8,0x7b, - 0x4d,0xa7,0xf0,0x11,0xec,0x48,0xc9,0x72, - 0x71,0xd2,0xc2,0x0f,0x9b,0x92,0x8f,0xe2, - 0x27,0x0d,0x6f,0xb8,0x63,0xd5,0x17,0x38, - 0xb4,0x8e,0xee,0xe3,0x14,0xa7,0xcc,0x8a, - 0xb9,0x32,0x16,0x45,0x48,0xe5,0x26,0xae, - 0x90,0x22,0x43,0x68,0x51,0x7a,0xcf,0xea, - 0xbd,0x6b,0xb3,0x73,0x2b,0xc0,0xe9,0xda, - 0x99,0x83,0x2b,0x61,0xca,0x01,0xb6,0xde, - 0x56,0x24,0x4a,0x9e,0x88,0xd5,0xf9,0xb3, - 0x79,0x73,0xf6,0x22,0xa4,0x3d,0x14,0xa6, - 0x59,0x9b,0x1f,0x65,0x4c,0xb4,0x5a,0x74, - 0xe3,0x55,0xa5 - }; - - const unsigned char nacl_mac[16] = { - 0xf3,0xff,0xc7,0x70,0x3f,0x94,0x00,0xe5, - 0x2a,0x7d,0xfb,0x4b,0x3d,0x33,0x05,0xd9 - }; - - /* generates a final value of (2^130 - 2) == 3 */ - const unsigned char wrap_key[32] = { - 0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - }; - - const unsigned char wrap_msg[16] = { - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff - }; - - const unsigned char wrap_mac[16] = { - 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - }; - - /* - mac of the macs of messages of length 0 to 256, where the key and messages - have all their values set to the length - */ - const unsigned char total_key[32] = { - 0x01,0x02,0x03,0x04,0x05,0x06,0x07, - 0xff,0xfe,0xfd,0xfc,0xfb,0xfa,0xf9, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xff,0xff,0xff,0xff,0xff,0xff,0xff - }; - - const unsigned char total_mac[16] = { - 0x64,0xaf,0xe2,0xe8,0xd6,0xad,0x7b,0xbd, - 0xd2,0x87,0xf9,0x7c,0x44,0x62,0x3d,0x39 - }; - - poly1305_context ctx = {0}; - poly1305_context total_ctx = {0}; - unsigned char all_key[32] = {0}; - unsigned char all_msg[256] = {0}; - unsigned char mac[16] = {0}; - size_t i = 0, j = 0; - int result = 1; - - for (i = 0; i < sizeof(mac); i++) - mac[i] = 0; - poly1305_auth(mac, nacl_msg, sizeof(nacl_msg), nacl_key); - result &= poly1305_verify(nacl_mac, mac); - - for (i = 0; i < sizeof(mac); i++) - mac[i] = 0; - poly1305_init(&ctx, nacl_key); - poly1305_update(&ctx, nacl_msg + 0, 32); - poly1305_update(&ctx, nacl_msg + 32, 64); - poly1305_update(&ctx, nacl_msg + 96, 16); - poly1305_update(&ctx, nacl_msg + 112, 8); - poly1305_update(&ctx, nacl_msg + 120, 4); - poly1305_update(&ctx, nacl_msg + 124, 2); - poly1305_update(&ctx, nacl_msg + 126, 1); - poly1305_update(&ctx, nacl_msg + 127, 1); - poly1305_update(&ctx, nacl_msg + 128, 1); - poly1305_update(&ctx, nacl_msg + 129, 1); - poly1305_update(&ctx, nacl_msg + 130, 1); - poly1305_finish(&ctx, mac); - result &= poly1305_verify(nacl_mac, mac); - - for (i = 0; i < sizeof(mac); i++) - mac[i] = 0; - poly1305_auth(mac, wrap_msg, sizeof(wrap_msg), wrap_key); - result &= poly1305_verify(wrap_mac, mac); - - poly1305_init(&total_ctx, total_key); - for (i = 0; i < 256; i++) { - /* set key and message to 'i,i,i..' */ - for (j = 0; j < sizeof(all_key); j++) - all_key[j] = i; - for (j = 0; j < i; j++) - all_msg[j] = i; - poly1305_auth(mac, all_msg, i, all_key); - poly1305_update(&total_ctx, mac, 16); - } - poly1305_finish(&total_ctx, mac); - result &= poly1305_verify(total_mac, mac); - - return result; -} diff --git a/trezor-crypto/crypto/chacha20poly1305/rfc7539.c b/trezor-crypto/crypto/chacha20poly1305/rfc7539.c deleted file mode 100644 index aa6c1ab6f3e..00000000000 --- a/trezor-crypto/crypto/chacha20poly1305/rfc7539.c +++ /dev/null @@ -1,48 +0,0 @@ -// Implementation of the ChaCha20 + Poly1305 AEAD construction -// as described in RFC 7539. - -#include -#include -#include - -// Initialize the ChaCha20 + Poly1305 context for encryption or decryption -// using a 32 byte key and 12 byte nonce as in the RFC 7539 style. -void rfc7539_init(chacha20poly1305_ctx *ctx, const uint8_t key[32], const uint8_t nonce[12]) { - unsigned char block0[64] = {0}; - - ECRYPT_keysetup(&ctx->chacha20, key, 256, 16); - ctx->chacha20.input[12] = 0; - ctx->chacha20.input[13] = U8TO32_LITTLE(nonce + 0); - ctx->chacha20.input[14] = U8TO32_LITTLE(nonce + 4); - ctx->chacha20.input[15] = U8TO32_LITTLE(nonce + 8); - - // Encrypt 64 bytes of zeros and use the first 32 bytes - // as the Poly1305 key. - ECRYPT_encrypt_bytes(&ctx->chacha20, block0, block0, 64); - poly1305_init(&ctx->poly1305, block0); -} - -// Include authenticated data in the Poly1305 MAC using the RFC 7539 -// style with 16 byte padding. This must only be called once and prior -// to encryption or decryption. -void rfc7539_auth(chacha20poly1305_ctx *ctx, const uint8_t *in, size_t n) { - uint8_t padding[16] = {0}; - poly1305_update(&ctx->poly1305, in, n); - if (n % 16 != 0) - poly1305_update(&ctx->poly1305, padding, 16 - n%16); -} - -// Compute RFC 7539-style Poly1305 MAC. -void rfc7539_finish(chacha20poly1305_ctx *ctx, int64_t alen, int64_t plen, uint8_t mac[16]) { - uint8_t padding[16] = {0}; - uint8_t lengths[16] = {0}; - - memcpy(lengths, &alen, sizeof(int64_t)); - memcpy(lengths + 8, &plen, sizeof(int64_t)); - - if (plen % 16 != 0) - poly1305_update(&ctx->poly1305, padding, 16 - plen%16); - poly1305_update(&ctx->poly1305, lengths, 16); - - poly1305_finish(&ctx->poly1305, mac); -} diff --git a/trezor-crypto/crypto/chacha_drbg.c b/trezor-crypto/crypto/chacha_drbg.c deleted file mode 100644 index e8027ffe939..00000000000 --- a/trezor-crypto/crypto/chacha_drbg.c +++ /dev/null @@ -1,126 +0,0 @@ -/* - * This file is part of the Trezor project, https://trezor.io/ - * - * Copyright (c) SatoshiLabs - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include - -#include -#include -#include - -#include -#include -#include - -#define CHACHA_DRBG_KEY_LENGTH 32 -#define CHACHA_DRBG_COUNTER_LENGTH 8 -#define CHACHA_DRBG_IV_LENGTH 8 -#define CHACHA_DRBG_SEED_LENGTH \ - (CHACHA_DRBG_KEY_LENGTH + CHACHA_DRBG_COUNTER_LENGTH + CHACHA_DRBG_IV_LENGTH) - -#define MAX(a, b) (a) > (b) ? (a) : (b) - -static void derivation_function(const uint8_t *input1, size_t input1_length, - const uint8_t *input2, size_t input2_length, - uint8_t *output, size_t output_length) { - // Implementation of Hash_df from NIST SP 800-90A - uint32_t block_count = (output_length - 1) / SHA256_DIGEST_LENGTH + 1; - size_t partial_block_length = output_length % SHA256_DIGEST_LENGTH; - assert(block_count <= 255); - - uint32_t output_length_bits = output_length * 8; -#if BYTE_ORDER == LITTLE_ENDIAN - REVERSE32(output_length_bits, output_length_bits); -#endif - - SHA256_CTX ctx = {0}; - - for (uint8_t counter = 1; counter <= block_count; counter++) { - sha256_Init(&ctx); - sha256_Update(&ctx, &counter, sizeof(counter)); - sha256_Update(&ctx, (uint8_t *)&output_length_bits, - sizeof(output_length_bits)); - sha256_Update(&ctx, input1, input1_length); - sha256_Update(&ctx, input2, input2_length); - - if (counter != block_count || partial_block_length == 0) { - sha256_Final(&ctx, output); - output += SHA256_DIGEST_LENGTH; - } else { // last block is partial - uint8_t digest[SHA256_DIGEST_LENGTH] = {0}; - sha256_Final(&ctx, digest); - memcpy(output, digest, partial_block_length); - memzero(digest, sizeof(digest)); - } - } - - memzero(&ctx, sizeof(ctx)); -} - -void chacha_drbg_init(CHACHA_DRBG_CTX *ctx, const uint8_t *entropy, - size_t entropy_length, const uint8_t *nonce, - size_t nonce_length) { - uint8_t buffer[MAX(CHACHA_DRBG_KEY_LENGTH, CHACHA_DRBG_IV_LENGTH)] = {0}; - ECRYPT_keysetup(&ctx->chacha_ctx, buffer, CHACHA_DRBG_KEY_LENGTH * 8, - CHACHA_DRBG_IV_LENGTH * 8); - ECRYPT_ivsetup(&ctx->chacha_ctx, buffer); - - chacha_drbg_reseed(ctx, entropy, entropy_length, nonce, nonce_length); -} - -static void chacha_drbg_update(CHACHA_DRBG_CTX *ctx, - const uint8_t data[CHACHA_DRBG_SEED_LENGTH]) { - uint8_t seed[CHACHA_DRBG_SEED_LENGTH] = {0}; - - if (data) - ECRYPT_encrypt_bytes(&ctx->chacha_ctx, data, seed, CHACHA_DRBG_SEED_LENGTH); - else - ECRYPT_keystream_bytes(&ctx->chacha_ctx, seed, CHACHA_DRBG_SEED_LENGTH); - - ECRYPT_keysetup(&ctx->chacha_ctx, seed, CHACHA_DRBG_KEY_LENGTH * 8, - CHACHA_DRBG_IV_LENGTH * 8); - - ECRYPT_ivsetup(&ctx->chacha_ctx, - seed + CHACHA_DRBG_KEY_LENGTH + CHACHA_DRBG_COUNTER_LENGTH); - - ECRYPT_ctrsetup(&ctx->chacha_ctx, seed + CHACHA_DRBG_KEY_LENGTH); - - memzero(seed, sizeof(seed)); -} - -void chacha_drbg_generate(CHACHA_DRBG_CTX *ctx, uint8_t *output, - size_t output_length) { - assert(output_length < 65536); - assert(ctx->reseed_counter + 1 != 0); - - ECRYPT_keystream_bytes(&ctx->chacha_ctx, output, output_length); - chacha_drbg_update(ctx, NULL); - ctx->reseed_counter++; -} - -void chacha_drbg_reseed(CHACHA_DRBG_CTX *ctx, const uint8_t *entropy, - size_t entropy_length, const uint8_t *additional_input, - size_t additional_input_length) { - uint8_t seed[CHACHA_DRBG_SEED_LENGTH] = {0}; - derivation_function(entropy, entropy_length, additional_input, - additional_input_length, seed, sizeof(seed)); - chacha_drbg_update(ctx, seed); - memzero(seed, sizeof(seed)); - - ctx->reseed_counter = 1; -} diff --git a/trezor-crypto/crypto/curves.c b/trezor-crypto/crypto/curves.c deleted file mode 100644 index a6221a9943f..00000000000 --- a/trezor-crypto/crypto/curves.c +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2016 Jochen Hoenicke - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include -#include - -const char SECP256K1_NAME[] = "secp256k1"; -const char SECP256K1_DECRED_NAME[] = "secp256k1-decred"; -const char SECP256K1_GROESTL_NAME[] = "secp256k1-groestl"; -const char SECP256K1_SMART_NAME[] = "secp256k1-smart"; -const char NIST256P1_NAME[] = "nist256p1"; -const char ED25519_NAME[] = "ed25519"; -const char ED25519_SEED_NAME[] = "ed25519 seed"; -#if USE_CARDANO -const char ED25519_CARDANO_NAME[] = "ed25519 cardano seed"; -#endif -const char ED25519_SHA3_NAME[] = "ed25519-sha3"; -#if USE_KECCAK -const char ED25519_KECCAK_NAME[] = "ed25519-keccak"; -#endif -const char CURVE25519_NAME[] = "curve25519"; - -const char ED25519_BLAKE2B_NANO_NAME[] = "ed25519-blake2b-nano"; // [wallet-core] diff --git a/trezor-crypto/crypto/ecdsa.c b/trezor-crypto/crypto/ecdsa.c deleted file mode 100644 index 445f29aa549..00000000000 --- a/trezor-crypto/crypto/ecdsa.c +++ /dev/null @@ -1,1251 +0,0 @@ -/** - * Copyright (c) 2013-2014 Tomas Dzetkulic - * Copyright (c) 2013-2014 Pavol Rusnak - * Copyright (c) 2015 Jochen Hoenicke - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Set cp2 = cp1 -void point_copy(const curve_point *cp1, curve_point *cp2) { *cp2 = *cp1; } - -// cp2 = cp1 + cp2 -void point_add(const ecdsa_curve *curve, const curve_point *cp1, - curve_point *cp2) { - bignum256 lambda = {0}, inv = {0}, xr = {0}, yr = {0}; - - if (point_is_infinity(cp1)) { - return; - } - if (point_is_infinity(cp2)) { - point_copy(cp1, cp2); - return; - } - if (point_is_equal(cp1, cp2)) { - point_double(curve, cp2); - return; - } - if (point_is_negative_of(cp1, cp2)) { - point_set_infinity(cp2); - return; - } - - // lambda = (y2 - y1) / (x2 - x1) - bn_subtractmod(&(cp2->x), &(cp1->x), &inv, &curve->prime); - bn_inverse(&inv, &curve->prime); - bn_subtractmod(&(cp2->y), &(cp1->y), &lambda, &curve->prime); - bn_multiply(&inv, &lambda, &curve->prime); - - // xr = lambda^2 - x1 - x2 - xr = lambda; - bn_multiply(&xr, &xr, &curve->prime); - yr = cp1->x; - bn_addmod(&yr, &(cp2->x), &curve->prime); - bn_subtractmod(&xr, &yr, &xr, &curve->prime); - bn_fast_mod(&xr, &curve->prime); - bn_mod(&xr, &curve->prime); - - // yr = lambda (x1 - xr) - y1 - bn_subtractmod(&(cp1->x), &xr, &yr, &curve->prime); - bn_multiply(&lambda, &yr, &curve->prime); - bn_subtractmod(&yr, &(cp1->y), &yr, &curve->prime); - bn_fast_mod(&yr, &curve->prime); - bn_mod(&yr, &curve->prime); - - cp2->x = xr; - cp2->y = yr; -} - -// cp = cp + cp -void point_double(const ecdsa_curve *curve, curve_point *cp) { - bignum256 lambda = {0}, xr = {0}, yr = {0}; - - if (point_is_infinity(cp)) { - return; - } - if (bn_is_zero(&(cp->y))) { - point_set_infinity(cp); - return; - } - - // lambda = (3 x^2 + a) / (2 y) - lambda = cp->y; - bn_mult_k(&lambda, 2, &curve->prime); - bn_fast_mod(&lambda, &curve->prime); - bn_mod(&lambda, &curve->prime); - bn_inverse(&lambda, &curve->prime); - - xr = cp->x; - bn_multiply(&xr, &xr, &curve->prime); - bn_mult_k(&xr, 3, &curve->prime); - bn_subi(&xr, -curve->a, &curve->prime); - bn_multiply(&xr, &lambda, &curve->prime); - - // xr = lambda^2 - 2*x - xr = lambda; - bn_multiply(&xr, &xr, &curve->prime); - yr = cp->x; - bn_lshift(&yr); - bn_subtractmod(&xr, &yr, &xr, &curve->prime); - bn_fast_mod(&xr, &curve->prime); - bn_mod(&xr, &curve->prime); - - // yr = lambda (x - xr) - y - bn_subtractmod(&(cp->x), &xr, &yr, &curve->prime); - bn_multiply(&lambda, &yr, &curve->prime); - bn_subtractmod(&yr, &(cp->y), &yr, &curve->prime); - bn_fast_mod(&yr, &curve->prime); - bn_mod(&yr, &curve->prime); - - cp->x = xr; - cp->y = yr; -} - -// set point to internal representation of point at infinity -void point_set_infinity(curve_point *p) { - bn_zero(&(p->x)); - bn_zero(&(p->y)); -} - -// return true iff p represent point at infinity -// both coords are zero in internal representation -int point_is_infinity(const curve_point *p) { - return bn_is_zero(&(p->x)) && bn_is_zero(&(p->y)); -} - -// return true iff both points are equal -int point_is_equal(const curve_point *p, const curve_point *q) { - return bn_is_equal(&(p->x), &(q->x)) && bn_is_equal(&(p->y), &(q->y)); -} - -// returns true iff p == -q -// expects p and q be valid points on curve other than point at infinity -int point_is_negative_of(const curve_point *p, const curve_point *q) { - // if P == (x, y), then -P would be (x, -y) on this curve - if (!bn_is_equal(&(p->x), &(q->x))) { - return 0; - } - - // we shouldn't hit this for a valid point - if (bn_is_zero(&(p->y))) { - return 0; - } - - return !bn_is_equal(&(p->y), &(q->y)); -} - -typedef struct jacobian_curve_point { - bignum256 x, y, z; -} jacobian_curve_point; - -// generate random K for signing/side-channel noise -static void generate_k_random(bignum256 *k, const bignum256 *prime) { - do { - int i = 0; - for (i = 0; i < 8; i++) { - k->val[i] = random32() & ((1u << BN_BITS_PER_LIMB) - 1); - } - k->val[8] = random32() & ((1u << BN_BITS_LAST_LIMB) - 1); - // check that k is in range and not zero. - } while (bn_is_zero(k) || !bn_is_less(k, prime)); -} - -void curve_to_jacobian(const curve_point *p, jacobian_curve_point *jp, - const bignum256 *prime) { - // randomize z coordinate - generate_k_random(&jp->z, prime); - - jp->x = jp->z; - bn_multiply(&jp->z, &jp->x, prime); - // x = z^2 - jp->y = jp->x; - bn_multiply(&jp->z, &jp->y, prime); - // y = z^3 - - bn_multiply(&p->x, &jp->x, prime); - bn_multiply(&p->y, &jp->y, prime); -} - -void jacobian_to_curve(const jacobian_curve_point *jp, curve_point *p, - const bignum256 *prime) { - p->y = jp->z; - bn_inverse(&p->y, prime); - // p->y = z^-1 - p->x = p->y; - bn_multiply(&p->x, &p->x, prime); - // p->x = z^-2 - bn_multiply(&p->x, &p->y, prime); - // p->y = z^-3 - bn_multiply(&jp->x, &p->x, prime); - // p->x = jp->x * z^-2 - bn_multiply(&jp->y, &p->y, prime); - // p->y = jp->y * z^-3 - bn_mod(&p->x, prime); - bn_mod(&p->y, prime); -} - -void point_jacobian_add(const curve_point *p1, jacobian_curve_point *p2, - const ecdsa_curve *curve) { - bignum256 r = {0}, h = {0}, r2 = {0}; - bignum256 hcby = {0}, hsqx = {0}; - bignum256 xz = {0}, yz = {0}, az = {0}; - int is_doubling = 0; - const bignum256 *prime = &curve->prime; - int a = curve->a; - - assert(-3 <= a && a <= 0); - - /* First we bring p1 to the same denominator: - * x1' := x1 * z2^2 - * y1' := y1 * z2^3 - */ - /* - * lambda = ((y1' - y2)/z2^3) / ((x1' - x2)/z2^2) - * = (y1' - y2) / (x1' - x2) z2 - * x3/z3^2 = lambda^2 - (x1' + x2)/z2^2 - * y3/z3^3 = 1/2 lambda * (2x3/z3^2 - (x1' + x2)/z2^2) + (y1'+y2)/z2^3 - * - * For the special case x1=x2, y1=y2 (doubling) we have - * lambda = 3/2 ((x2/z2^2)^2 + a) / (y2/z2^3) - * = 3/2 (x2^2 + a*z2^4) / y2*z2) - * - * to get rid of fraction we write lambda as - * lambda = r / (h*z2) - * with r = is_doubling ? 3/2 x2^2 + az2^4 : (y1 - y2) - * h = is_doubling ? y1+y2 : (x1 - x2) - * - * With z3 = h*z2 (the denominator of lambda) - * we get x3 = lambda^2*z3^2 - (x1' + x2)/z2^2*z3^2 - * = r^2 - h^2 * (x1' + x2) - * and y3 = 1/2 r * (2x3 - h^2*(x1' + x2)) + h^3*(y1' + y2) - */ - - /* h = x1 - x2 - * r = y1 - y2 - * x3 = r^2 - h^3 - 2*h^2*x2 - * y3 = r*(h^2*x2 - x3) - h^3*y2 - * z3 = h*z2 - */ - - xz = p2->z; - bn_multiply(&xz, &xz, prime); // xz = z2^2 - yz = p2->z; - bn_multiply(&xz, &yz, prime); // yz = z2^3 - - if (a != 0) { - az = xz; - bn_multiply(&az, &az, prime); // az = z2^4 - bn_mult_k(&az, -a, prime); // az = -az2^4 - } - - bn_multiply(&p1->x, &xz, prime); // xz = x1' = x1*z2^2; - h = xz; - bn_subtractmod(&h, &p2->x, &h, prime); - bn_fast_mod(&h, prime); - // h = x1' - x2; - - bn_add(&xz, &p2->x); - // xz = x1' + x2 - - // check for h == 0 % prime. Note that h never normalizes to - // zero, since h = x1' + 2*prime - x2 > 0 and a positive - // multiple of prime is always normalized to prime by - // bn_fast_mod. - is_doubling = bn_is_equal(&h, prime); - - bn_multiply(&p1->y, &yz, prime); // yz = y1' = y1*z2^3; - bn_subtractmod(&yz, &p2->y, &r, prime); - // r = y1' - y2; - - bn_add(&yz, &p2->y); - // yz = y1' + y2 - - r2 = p2->x; - bn_multiply(&r2, &r2, prime); - bn_mult_k(&r2, 3, prime); - - if (a != 0) { - // subtract -a z2^4, i.e, add a z2^4 - bn_subtractmod(&r2, &az, &r2, prime); - } - bn_cmov(&r, is_doubling, &r2, &r); - bn_cmov(&h, is_doubling, &yz, &h); - - // hsqx = h^2 - hsqx = h; - bn_multiply(&hsqx, &hsqx, prime); - - // hcby = h^3 - hcby = h; - bn_multiply(&hsqx, &hcby, prime); - - // hsqx = h^2 * (x1 + x2) - bn_multiply(&xz, &hsqx, prime); - - // hcby = h^3 * (y1 + y2) - bn_multiply(&yz, &hcby, prime); - - // z3 = h*z2 - bn_multiply(&h, &p2->z, prime); - - // x3 = r^2 - h^2 (x1 + x2) - p2->x = r; - bn_multiply(&p2->x, &p2->x, prime); - bn_subtractmod(&p2->x, &hsqx, &p2->x, prime); - bn_fast_mod(&p2->x, prime); - - // y3 = 1/2 (r*(h^2 (x1 + x2) - 2x3) - h^3 (y1 + y2)) - bn_subtractmod(&hsqx, &p2->x, &p2->y, prime); - bn_subtractmod(&p2->y, &p2->x, &p2->y, prime); - bn_multiply(&r, &p2->y, prime); - bn_subtractmod(&p2->y, &hcby, &p2->y, prime); - bn_mult_half(&p2->y, prime); - bn_fast_mod(&p2->y, prime); -} - -void point_jacobian_double(jacobian_curve_point *p, const ecdsa_curve *curve) { - bignum256 az4 = {0}, m = {0}, msq = {0}, ysq = {0}, xysq = {0}; - const bignum256 *prime = &curve->prime; - - assert(-3 <= curve->a && curve->a <= 0); - /* usual algorithm: - * - * lambda = (3((x/z^2)^2 + a) / 2y/z^3) = (3x^2 + az^4)/2yz - * x3/z3^2 = lambda^2 - 2x/z^2 - * y3/z3^3 = lambda * (x/z^2 - x3/z3^2) - y/z^3 - * - * to get rid of fraction we set - * m = (3 x^2 + az^4) / 2 - * Hence, - * lambda = m / yz = m / z3 - * - * With z3 = yz (the denominator of lambda) - * we get x3 = lambda^2*z3^2 - 2*x/z^2*z3^2 - * = m^2 - 2*xy^2 - * and y3 = (lambda * (x/z^2 - x3/z3^2) - y/z^3) * z3^3 - * = m * (xy^2 - x3) - y^4 - */ - - /* m = (3*x^2 + a z^4) / 2 - * x3 = m^2 - 2*xy^2 - * y3 = m*(xy^2 - x3) - 8y^4 - * z3 = y*z - */ - - m = p->x; - bn_multiply(&m, &m, prime); - bn_mult_k(&m, 3, prime); - - az4 = p->z; - bn_multiply(&az4, &az4, prime); - bn_multiply(&az4, &az4, prime); - bn_mult_k(&az4, -curve->a, prime); - bn_subtractmod(&m, &az4, &m, prime); - bn_mult_half(&m, prime); - - // msq = m^2 - msq = m; - bn_multiply(&msq, &msq, prime); - // ysq = y^2 - ysq = p->y; - bn_multiply(&ysq, &ysq, prime); - // xysq = xy^2 - xysq = p->x; - bn_multiply(&ysq, &xysq, prime); - - // z3 = yz - bn_multiply(&p->y, &p->z, prime); - - // x3 = m^2 - 2*xy^2 - p->x = xysq; - bn_lshift(&p->x); - bn_fast_mod(&p->x, prime); - bn_subtractmod(&msq, &p->x, &p->x, prime); - bn_fast_mod(&p->x, prime); - - // y3 = m*(xy^2 - x3) - y^4 - bn_subtractmod(&xysq, &p->x, &p->y, prime); - bn_multiply(&m, &p->y, prime); - bn_multiply(&ysq, &ysq, prime); - bn_subtractmod(&p->y, &ysq, &p->y, prime); - bn_fast_mod(&p->y, prime); -} - -// res = k * p -// returns 0 on success -int point_multiply(const ecdsa_curve *curve, const bignum256 *k, - const curve_point *p, curve_point *res) { - // this algorithm is loosely based on - // Katsuyuki Okeya and Tsuyoshi Takagi, The Width-w NAF Method Provides - // Small Memory and Fast Elliptic Scalar Multiplications Secure against - // Side Channel Attacks. - if (!bn_is_less(k, &curve->order)) { - return 1; - } - - int i = 0, j = 0; - CONFIDENTIAL bignum256 a; - uint32_t *aptr = NULL; - uint32_t abits = 0; - int ashift = 0; - uint32_t is_even = (k->val[0] & 1) - 1; - uint32_t bits = {0}, sign = {0}, nsign = {0}; - CONFIDENTIAL jacobian_curve_point jres; - curve_point pmult[8] = {0}; - const bignum256 *prime = &curve->prime; - - // is_even = 0xffffffff if k is even, 0 otherwise. - - // add 2^256. - // make number odd: subtract curve->order if even - uint32_t tmp = 1; - uint32_t is_non_zero = 0; - for (j = 0; j < 8; j++) { - is_non_zero |= k->val[j]; - tmp += (BN_BASE - 1) + k->val[j] - (curve->order.val[j] & is_even); - a.val[j] = tmp & (BN_BASE - 1); - tmp >>= BN_BITS_PER_LIMB; - } - is_non_zero |= k->val[j]; - a.val[j] = tmp + 0xffffff + k->val[j] - (curve->order.val[j] & is_even); - assert((a.val[0] & 1) != 0); - - // special case 0*p: just return zero. We don't care about constant time. - if (!is_non_zero) { - point_set_infinity(res); - return 1; - } - - // Now a = k + 2^256 (mod curve->order) and a is odd. - // - // The idea is to bring the new a into the form. - // sum_{i=0..64} a[i] 16^i, where |a[i]| < 16 and a[i] is odd. - // a[0] is odd, since a is odd. If a[i] would be even, we can - // add 1 to it and subtract 16 from a[i-1]. Afterwards, - // a[64] = 1, which is the 2^256 that we added before. - // - // Since k = a - 2^256 (mod curve->order), we can compute - // k*p = sum_{i=0..63} a[i] 16^i * p - // - // We compute |a[i]| * p in advance for all possible - // values of |a[i]| * p. pmult[i] = (2*i+1) * p - // We compute p, 3*p, ..., 15*p and store it in the table pmult. - // store p^2 temporarily in pmult[7] - pmult[7] = *p; - point_double(curve, &pmult[7]); - // compute 3*p, etc by repeatedly adding p^2. - pmult[0] = *p; - for (i = 1; i < 8; i++) { - pmult[i] = pmult[7]; - point_add(curve, &pmult[i - 1], &pmult[i]); - } - - // now compute res = sum_{i=0..63} a[i] * 16^i * p step by step, - // starting with i = 63. - // initialize jres = |a[63]| * p. - // Note that a[i] = a>>(4*i) & 0xf if (a&0x10) != 0 - // and - (16 - (a>>(4*i) & 0xf)) otherwise. We can compute this as - // ((a ^ (((a >> 4) & 1) - 1)) & 0xf) >> 1 - // since a is odd. - aptr = &a.val[8]; - abits = *aptr; - ashift = 256 - (BN_BITS_PER_LIMB * 8) - 4; - bits = abits >> ashift; - sign = (bits >> 4) - 1; - bits ^= sign; - bits &= 15; - curve_to_jacobian(&pmult[bits >> 1], &jres, prime); - for (i = 62; i >= 0; i--) { - // sign = sign(a[i+1]) (0xffffffff for negative, 0 for positive) - // invariant jres = (-1)^sign sum_{j=i+1..63} (a[j] * 16^{j-i-1} * p) - // abits >> (ashift - 4) = lowbits(a >> (i*4)) - - point_jacobian_double(&jres, curve); - point_jacobian_double(&jres, curve); - point_jacobian_double(&jres, curve); - point_jacobian_double(&jres, curve); - - // get lowest 5 bits of a >> (i*4). - ashift -= 4; - if (ashift < 0) { - // the condition only depends on the iteration number and - // leaks no private information to a side-channel. - bits = abits << (-ashift); - abits = *(--aptr); - ashift += BN_BITS_PER_LIMB; - bits |= abits >> ashift; - } else { - bits = abits >> ashift; - } - bits &= 31; - nsign = (bits >> 4) - 1; - bits ^= nsign; - bits &= 15; - - // negate last result to make signs of this round and the - // last round equal. - bn_cnegate((sign ^ nsign) & 1, &jres.z, prime); - - // add odd factor - point_jacobian_add(&pmult[bits >> 1], &jres, curve); - sign = nsign; - } - bn_cnegate(sign & 1, &jres.z, prime); - jacobian_to_curve(&jres, res, prime); - memzero(&a, sizeof(a)); - memzero(&jres, sizeof(jres)); - - return 0; -} - -#if USE_PRECOMPUTED_CP - -// res = k * G -// k must be a normalized number with 0 <= k < curve->order -// returns 0 on success -int scalar_multiply(const ecdsa_curve *curve, const bignum256 *k, - curve_point *res) { - if (!bn_is_less(k, &curve->order)) { - return 1; - } - - int i = {0}, j = {0}; - CONFIDENTIAL bignum256 a; - uint32_t is_even = (k->val[0] & 1) - 1; - uint32_t lowbits = 0; - CONFIDENTIAL jacobian_curve_point jres; - const bignum256 *prime = &curve->prime; - - // is_even = 0xffffffff if k is even, 0 otherwise. - - // add 2^256. - // make number odd: subtract curve->order if even - uint32_t tmp = 1; - uint32_t is_non_zero = 0; - for (j = 0; j < 8; j++) { - is_non_zero |= k->val[j]; - tmp += (BN_BASE - 1) + k->val[j] - (curve->order.val[j] & is_even); - a.val[j] = tmp & (BN_BASE - 1); - tmp >>= BN_BITS_PER_LIMB; - } - is_non_zero |= k->val[j]; - a.val[j] = tmp + 0xffffff + k->val[j] - (curve->order.val[j] & is_even); - assert((a.val[0] & 1) != 0); - - // special case 0*G: just return zero. We don't care about constant time. - if (!is_non_zero) { - point_set_infinity(res); - return 0; - } - - // Now a = k + 2^256 (mod curve->order) and a is odd. - // - // The idea is to bring the new a into the form. - // sum_{i=0..64} a[i] 16^i, where |a[i]| < 16 and a[i] is odd. - // a[0] is odd, since a is odd. If a[i] would be even, we can - // add 1 to it and subtract 16 from a[i-1]. Afterwards, - // a[64] = 1, which is the 2^256 that we added before. - // - // Since k = a - 2^256 (mod curve->order), we can compute - // k*G = sum_{i=0..63} a[i] 16^i * G - // - // We have a big table curve->cp that stores all possible - // values of |a[i]| 16^i * G. - // curve->cp[i][j] = (2*j+1) * 16^i * G - - // now compute res = sum_{i=0..63} a[i] * 16^i * G step by step. - // initial res = |a[0]| * G. Note that a[0] = a & 0xf if (a&0x10) != 0 - // and - (16 - (a & 0xf)) otherwise. We can compute this as - // ((a ^ (((a >> 4) & 1) - 1)) & 0xf) >> 1 - // since a is odd. - lowbits = a.val[0] & ((1 << 5) - 1); - lowbits ^= (lowbits >> 4) - 1; - lowbits &= 15; - curve_to_jacobian(&curve->cp[0][lowbits >> 1], &jres, prime); - for (i = 1; i < 64; i++) { - // invariant res = sign(a[i-1]) sum_{j=0..i-1} (a[j] * 16^j * G) - - // shift a by 4 places. - for (j = 0; j < 8; j++) { - a.val[j] = - (a.val[j] >> 4) | ((a.val[j + 1] & 0xf) << (BN_BITS_PER_LIMB - 4)); - } - a.val[j] >>= 4; - // a = old(a)>>(4*i) - // a is even iff sign(a[i-1]) = -1 - - lowbits = a.val[0] & ((1 << 5) - 1); - lowbits ^= (lowbits >> 4) - 1; - lowbits &= 15; - // negate last result to make signs of this round and the - // last round equal. - bn_cnegate(~lowbits & 1, &jres.y, prime); - - // add odd factor - point_jacobian_add(&curve->cp[i][lowbits >> 1], &jres, curve); - } - bn_cnegate(~(a.val[0] >> 4) & 1, &jres.y, prime); - jacobian_to_curve(&jres, res, prime); - memzero(&a, sizeof(a)); - memzero(&jres, sizeof(jres)); - - return 0; -} - -#else - -int scalar_multiply(const ecdsa_curve *curve, const bignum256 *k, - curve_point *res) { - return point_multiply(curve, k, &curve->G, res); -} - -#endif - -int ecdh_multiply(const ecdsa_curve *curve, const uint8_t *priv_key, - const uint8_t *pub_key, uint8_t *session_key) { - curve_point point = {0}; - if (!ecdsa_read_pubkey(curve, pub_key, &point)) { - return 1; - } - - bignum256 k = {0}; - bn_read_be(priv_key, &k); - if (bn_is_zero(&k) || !bn_is_less(&k, &curve->order)) { - // Invalid private key. - return 2; - } - - point_multiply(curve, &k, &point, &point); - memzero(&k, sizeof(k)); - - session_key[0] = 0x04; - bn_write_be(&point.x, session_key + 1); - bn_write_be(&point.y, session_key + 33); - memzero(&point, sizeof(point)); - - return 0; -} - -// msg is a data to be signed -// msg_len is the message length -int ecdsa_sign(const ecdsa_curve *curve, HasherType hasher_sign, - const uint8_t *priv_key, const uint8_t *msg, uint32_t msg_len, - uint8_t *sig, uint8_t *pby, - int (*is_canonical)(uint8_t by, uint8_t sig[64])) { - uint8_t hash[32] = {0}; - hasher_Raw(hasher_sign, msg, msg_len, hash); - int res = ecdsa_sign_digest(curve, priv_key, hash, sig, pby, is_canonical); - memzero(hash, sizeof(hash)); - return res; -} - -// uses secp256k1 curve -// priv_key is a 32 byte big endian stored number -// sig is 64 bytes long array for the signature -// digest is 32 bytes of digest -// is_canonical is an optional function that checks if the signature -// conforms to additional coin-specific rules. -int ecdsa_sign_digest(const ecdsa_curve *curve, const uint8_t *priv_key, - const uint8_t *digest, uint8_t *sig, uint8_t *pby, - int (*is_canonical)(uint8_t by, uint8_t sig[64])) { - int i = 0; - curve_point R = {0}; - bignum256 k = {0}, z = {0}, randk = {0}; - bignum256 *s = &R.y; - uint8_t by; // signature recovery byte - -#if USE_RFC6979 - rfc6979_state rng = {0}; - init_rfc6979(priv_key, digest, curve, &rng); -#endif - - bn_read_be(digest, &z); - if (bn_is_zero(&z)) { - // The probability of the digest being all-zero by chance is infinitesimal, - // so this is most likely an indication of a bug. Furthermore, the signature - // has no value, because in this case it can be easily forged for any public - // key, see ecdsa_verify_digest(). - return 1; - } - - for (i = 0; i < 10000; i++) { -#if USE_RFC6979 - // generate K deterministically - generate_k_rfc6979(&k, &rng); - // if k is too big or too small, we don't like it - if (bn_is_zero(&k) || !bn_is_less(&k, &curve->order)) { - continue; - } -#else - // generate random number k - generate_k_random(&k, &curve->order); -#endif - - // compute k*G - scalar_multiply(curve, &k, &R); - by = R.y.val[0] & 1; - // r = (rx mod n) - if (!bn_is_less(&R.x, &curve->order)) { - bn_subtract(&R.x, &curve->order, &R.x); - by |= 2; - } - // if r is zero, we retry - if (bn_is_zero(&R.x)) { - continue; - } - - bn_read_be(priv_key, s); - if (bn_is_zero(s) || !bn_is_less(s, &curve->order)) { - // Invalid private key. - return 2; - } - - // randomize operations to counter side-channel attacks - generate_k_random(&randk, &curve->order); - bn_multiply(&randk, &k, &curve->order); // k*rand - bn_inverse(&k, &curve->order); // (k*rand)^-1 - bn_multiply(&R.x, s, &curve->order); // R.x*priv - bn_add(s, &z); // R.x*priv + z - bn_multiply(&k, s, &curve->order); // (k*rand)^-1 (R.x*priv + z) - bn_multiply(&randk, s, &curve->order); // k^-1 (R.x*priv + z) - bn_mod(s, &curve->order); - // if s is zero, we retry - if (bn_is_zero(s)) { - continue; - } - - // if S > order/2 => S = -S - if (bn_is_less(&curve->order_half, s)) { - bn_subtract(&curve->order, s, s); - by ^= 1; - } - // we are done, R.x and s is the result signature - bn_write_be(&R.x, sig); - bn_write_be(s, sig + 32); - - // check if the signature is acceptable or retry - if (is_canonical && !is_canonical(by, sig)) { - continue; - } - - if (pby) { - *pby = by; - } - - memzero(&k, sizeof(k)); - memzero(&randk, sizeof(randk)); -#if USE_RFC6979 - memzero(&rng, sizeof(rng)); -#endif - return 0; - } - - // Too many retries without a valid signature - // -> fail with an error - memzero(&k, sizeof(k)); - memzero(&randk, sizeof(randk)); -#if USE_RFC6979 - memzero(&rng, sizeof(rng)); -#endif - return -1; -} - -// returns 0 on success -int ecdsa_get_public_key33(const ecdsa_curve *curve, const uint8_t *priv_key, - uint8_t *pub_key) { - curve_point R = {0}; - bignum256 k = {0}; - - bn_read_be(priv_key, &k); - if (bn_is_zero(&k) || !bn_is_less(&k, &curve->order)) { - // Invalid private key. - memzero(pub_key, 33); - return -1; - } - - // compute k*G - if (scalar_multiply(curve, &k, &R) != 0) { - memzero(&k, sizeof(k)); - return 1; - } - pub_key[0] = 0x02 | (R.y.val[0] & 0x01); - bn_write_be(&R.x, pub_key + 1); - memzero(&R, sizeof(R)); - memzero(&k, sizeof(k)); - return 0; -} - -// returns 0 on success -int ecdsa_get_public_key65(const ecdsa_curve *curve, const uint8_t *priv_key, - uint8_t *pub_key) { - curve_point R = {0}; - bignum256 k = {0}; - - bn_read_be(priv_key, &k); - if (bn_is_zero(&k) || !bn_is_less(&k, &curve->order)) { - // Invalid private key. - memzero(pub_key, 65); - return -1; - } - - // compute k*G - if (scalar_multiply(curve, &k, &R) != 0) { - memzero(&k, sizeof(k)); - return 1; - } - pub_key[0] = 0x04; - bn_write_be(&R.x, pub_key + 1); - bn_write_be(&R.y, pub_key + 33); - memzero(&R, sizeof(R)); - memzero(&k, sizeof(k)); - return 0; -} - -int ecdsa_uncompress_pubkey(const ecdsa_curve *curve, const uint8_t *pub_key, - uint8_t *uncompressed) { - curve_point pub = {0}; - - if (!ecdsa_read_pubkey(curve, pub_key, &pub)) { - return 0; - } - - uncompressed[0] = 4; - bn_write_be(&pub.x, uncompressed + 1); - bn_write_be(&pub.y, uncompressed + 33); - - return 1; -} - -void ecdsa_get_pubkeyhash(const uint8_t *pub_key, HasherType hasher_pubkey, - uint8_t *pubkeyhash) { - uint8_t h[HASHER_DIGEST_LENGTH] = {0}; - if (pub_key[0] == 0x04) { // uncompressed format - hasher_Raw(hasher_pubkey, pub_key, 65, h); - } else if (pub_key[0] == 0x00) { // point at infinity - hasher_Raw(hasher_pubkey, pub_key, 1, h); - } else { // expecting compressed format - hasher_Raw(hasher_pubkey, pub_key, 33, h); - } - memcpy(pubkeyhash, h, 20); - memzero(h, sizeof(h)); -} - -void ecdsa_get_address_raw(const uint8_t *pub_key, uint32_t version, - HasherType hasher_pubkey, uint8_t *addr_raw) { - size_t prefix_len = address_prefix_bytes_len(version); - address_write_prefix_bytes(version, addr_raw); - ecdsa_get_pubkeyhash(pub_key, hasher_pubkey, addr_raw + prefix_len); -} - -void ecdsa_get_address(const uint8_t *pub_key, uint32_t version, - HasherType hasher_pubkey, HasherType hasher_base58, - char *addr, int addrsize) { - uint8_t raw[MAX_ADDR_RAW_SIZE] = {0}; - size_t prefix_len = address_prefix_bytes_len(version); - ecdsa_get_address_raw(pub_key, version, hasher_pubkey, raw); - base58_encode_check(raw, 20 + prefix_len, hasher_base58, addr, addrsize); - // not as important to clear this one, but we might as well - memzero(raw, sizeof(raw)); -} - -void ecdsa_get_address_segwit_p2sh_raw(const uint8_t *pub_key, uint32_t version, - HasherType hasher_pubkey, - uint8_t *addr_raw) { - uint8_t buf[32 + 2] = {0}; - buf[0] = 0; // version byte - buf[1] = 20; // push 20 bytes - ecdsa_get_pubkeyhash(pub_key, hasher_pubkey, buf + 2); - size_t prefix_len = address_prefix_bytes_len(version); - address_write_prefix_bytes(version, addr_raw); - hasher_Raw(hasher_pubkey, buf, 22, addr_raw + prefix_len); -} - -void ecdsa_get_address_segwit_p2sh(const uint8_t *pub_key, uint32_t version, - HasherType hasher_pubkey, - HasherType hasher_base58, char *addr, - int addrsize) { - uint8_t raw[MAX_ADDR_RAW_SIZE] = {0}; - size_t prefix_len = address_prefix_bytes_len(version); - ecdsa_get_address_segwit_p2sh_raw(pub_key, version, hasher_pubkey, raw); - base58_encode_check(raw, prefix_len + 20, hasher_base58, addr, addrsize); - memzero(raw, sizeof(raw)); -} - -void ecdsa_get_wif(const uint8_t *priv_key, uint32_t version, - HasherType hasher_base58, char *wif, int wifsize) { - uint8_t wif_raw[MAX_WIF_RAW_SIZE] = {0}; - size_t prefix_len = address_prefix_bytes_len(version); - address_write_prefix_bytes(version, wif_raw); - memcpy(wif_raw + prefix_len, priv_key, 32); - wif_raw[prefix_len + 32] = 0x01; - base58_encode_check(wif_raw, prefix_len + 32 + 1, hasher_base58, wif, - wifsize); - // private keys running around our stack can cause trouble - memzero(wif_raw, sizeof(wif_raw)); -} - -int ecdsa_address_decode(const char *addr, uint32_t version, - HasherType hasher_base58, uint8_t *out) { - if (!addr) return 0; - int prefix_len = address_prefix_bytes_len(version); - return base58_decode_check(addr, hasher_base58, out, 20 + prefix_len) == - 20 + prefix_len && - address_check_prefix(out, version); -} - -void compress_coords(const curve_point *cp, uint8_t *compressed) { - compressed[0] = bn_is_odd(&cp->y) ? 0x03 : 0x02; - bn_write_be(&cp->x, compressed + 1); -} - -void uncompress_coords(const ecdsa_curve *curve, uint8_t odd, - const bignum256 *x, bignum256 *y) { - // y^2 = x^3 + a*x + b - memcpy(y, x, sizeof(bignum256)); // y is x - bn_multiply(x, y, &curve->prime); // y is x^2 - bn_subi(y, -curve->a, &curve->prime); // y is x^2 + a - bn_multiply(x, y, &curve->prime); // y is x^3 + ax - bn_add(y, &curve->b); // y is x^3 + ax + b - bn_sqrt(y, &curve->prime); // y = sqrt(y) - if ((odd & 0x01) != (y->val[0] & 1)) { - bn_subtract(&curve->prime, y, y); // y = -y - } -} - -int ecdsa_read_pubkey(const ecdsa_curve *curve, const uint8_t *pub_key, - curve_point *pub) { - if (!curve) { - curve = &secp256k1; - } - if (pub_key[0] == 0x04) { - bn_read_be(pub_key + 1, &(pub->x)); - bn_read_be(pub_key + 33, &(pub->y)); - return ecdsa_validate_pubkey(curve, pub); - } - if (pub_key[0] == 0x02 || pub_key[0] == 0x03) { // compute missing y coords - bn_read_be(pub_key + 1, &(pub->x)); - uncompress_coords(curve, pub_key[0], &(pub->x), &(pub->y)); - return ecdsa_validate_pubkey(curve, pub); - } - // error - return 0; -} - -// Verifies that: -// - pub is not the point at infinity. -// - pub->x and pub->y are in range [0,p-1]. -// - pub is on the curve. -// We assume that all curves using this code have cofactor 1, so there is no -// need to verify that pub is a scalar multiple of G. -int ecdsa_validate_pubkey(const ecdsa_curve *curve, const curve_point *pub) { - bignum256 y_2 = {0}, x3_ax_b = {0}; - - if (point_is_infinity(pub)) { - return 0; - } - - if (!bn_is_less(&(pub->x), &curve->prime) || - !bn_is_less(&(pub->y), &curve->prime)) { - return 0; - } - - memcpy(&y_2, &(pub->y), sizeof(bignum256)); - memcpy(&x3_ax_b, &(pub->x), sizeof(bignum256)); - - // y^2 - bn_multiply(&(pub->y), &y_2, &curve->prime); - bn_mod(&y_2, &curve->prime); - - // x^3 + ax + b - bn_multiply(&(pub->x), &x3_ax_b, &curve->prime); // x^2 - bn_subi(&x3_ax_b, -curve->a, &curve->prime); // x^2 + a - bn_multiply(&(pub->x), &x3_ax_b, &curve->prime); // x^3 + ax - bn_addmod(&x3_ax_b, &curve->b, &curve->prime); // x^3 + ax + b - bn_mod(&x3_ax_b, &curve->prime); - - if (!bn_is_equal(&x3_ax_b, &y_2)) { - return 0; - } - - return 1; -} - -// uses secp256k1 curve -// pub_key - 65 bytes uncompressed key -// signature - 64 bytes signature -// msg is a data that was signed -// msg_len is the message length - -int ecdsa_verify(const ecdsa_curve *curve, HasherType hasher_sign, - const uint8_t *pub_key, const uint8_t *sig, const uint8_t *msg, - uint32_t msg_len) { - uint8_t hash[32] = {0}; - hasher_Raw(hasher_sign, msg, msg_len, hash); - int res = ecdsa_verify_digest(curve, pub_key, sig, hash); - memzero(hash, sizeof(hash)); - return res; -} - -// Compute public key from signature and recovery id. -// returns 0 if the key is successfully recovered -int ecdsa_recover_pub_from_sig(const ecdsa_curve *curve, uint8_t *pub_key, - const uint8_t *sig, const uint8_t *digest, - int recid) { - bignum256 r = {0}, s = {0}, e = {0}; - curve_point cp = {0}, cp2 = {0}; - - // read r and s - bn_read_be(sig, &r); - bn_read_be(sig + 32, &s); - if (!bn_is_less(&r, &curve->order) || bn_is_zero(&r)) { - return 1; - } - if (!bn_is_less(&s, &curve->order) || bn_is_zero(&s)) { - return 1; - } - // cp = R = k * G (k is secret nonce when signing) - memcpy(&cp.x, &r, sizeof(bignum256)); - if (recid & 2) { - bn_add(&cp.x, &curve->order); - if (!bn_is_less(&cp.x, &curve->prime)) { - return 1; - } - } - // compute y from x - uncompress_coords(curve, recid & 1, &cp.x, &cp.y); - if (!ecdsa_validate_pubkey(curve, &cp)) { - return 1; - } - // e = -digest - bn_read_be(digest, &e); - bn_mod(&e, &curve->order); - bn_subtract(&curve->order, &e, &e); - // r = r^-1 - bn_inverse(&r, &curve->order); - // e = -digest * r^-1 - bn_multiply(&r, &e, &curve->order); - bn_mod(&e, &curve->order); - // s = s * r^-1 - bn_multiply(&r, &s, &curve->order); - bn_mod(&s, &curve->order); - // cp = s * r^-1 * k * G - point_multiply(curve, &s, &cp, &cp); - // cp2 = -digest * r^-1 * G - scalar_multiply(curve, &e, &cp2); - // cp = (s * r^-1 * k - digest * r^-1) * G = Pub - point_add(curve, &cp2, &cp); - // The point at infinity is not considered to be a valid public key. - if (point_is_infinity(&cp)) { - return 1; - } - pub_key[0] = 0x04; - bn_write_be(&cp.x, pub_key + 1); - bn_write_be(&cp.y, pub_key + 33); - return 0; -} - -// returns 0 if verification succeeded -int ecdsa_verify_digest(const ecdsa_curve *curve, const uint8_t *pub_key, - const uint8_t *sig, const uint8_t *digest) { - curve_point pub = {0}, res = {0}; - bignum256 r = {0}, s = {0}, z = {0}; - int result = 0; - - if (!ecdsa_read_pubkey(curve, pub_key, &pub)) { - result = 1; - } - - if (result == 0) { - bn_read_be(sig, &r); - bn_read_be(sig + 32, &s); - bn_read_be(digest, &z); - if (bn_is_zero(&r) || bn_is_zero(&s) || (!bn_is_less(&r, &curve->order)) || - (!bn_is_less(&s, &curve->order))) { - result = 2; - } - if (bn_is_zero(&z)) { - // The digest was all-zero. The probability of this happening by chance is - // infinitesimal, but it could be induced by a fault injection. In this - // case the signature (r,s) can be forged by taking r := (t * Q).x mod n - // and s := r * t^-1 mod n for any t in [1, n-1]. We fail verification, - // because there is no guarantee that the signature was created by the - // owner of the private key. - result = 3; - } - } - - if (result == 0) { - bn_inverse(&s, &curve->order); // s = s^-1 - bn_multiply(&s, &z, &curve->order); // z = z * s [u1 = z * s^-1 mod n] - bn_mod(&z, &curve->order); - } - - if (result == 0) { - bn_multiply(&r, &s, &curve->order); // s = r * s [u2 = r * s^-1 mod n] - bn_mod(&s, &curve->order); - scalar_multiply(curve, &z, &res); // res = z * G [= u1 * G] - point_multiply(curve, &s, &pub, &pub); // pub = s * pub [= u2 * Q] - point_add(curve, &pub, &res); // res = pub + res [R = u1 * G + u2 * Q] - if (point_is_infinity(&res)) { - // R == Infinity - result = 4; - } - } - - if (result == 0) { - bn_mod(&(res.x), &curve->order); - if (!bn_is_equal(&res.x, &r)) { - // R.x != r - // signature does not match - result = 5; - } - } - - memzero(&pub, sizeof(pub)); - memzero(&res, sizeof(res)); - memzero(&r, sizeof(r)); - memzero(&s, sizeof(s)); - memzero(&z, sizeof(z)); - - // all OK - return result; -} - -int ecdsa_sig_to_der(const uint8_t *sig, uint8_t *der) { - int i = 0; - uint8_t *p = der, *len = NULL, *len1 = NULL, *len2 = NULL; - *p = 0x30; - p++; // sequence - *p = 0x00; - len = p; - p++; // len(sequence) - - *p = 0x02; - p++; // integer - *p = 0x00; - len1 = p; - p++; // len(integer) - - // process R - i = 0; - while (i < 31 && sig[i] == 0) { - i++; - } // skip leading zeroes - if (sig[i] >= 0x80) { // put zero in output if MSB set - *p = 0x00; - p++; - *len1 = *len1 + 1; - } - while (i < 32) { // copy bytes to output - *p = sig[i]; - p++; - *len1 = *len1 + 1; - i++; - } - - *p = 0x02; - p++; // integer - *p = 0x00; - len2 = p; - p++; // len(integer) - - // process S - i = 32; - while (i < 63 && sig[i] == 0) { - i++; - } // skip leading zeroes - if (sig[i] >= 0x80) { // put zero in output if MSB set - *p = 0x00; - p++; - *len2 = *len2 + 1; - } - while (i < 64) { // copy bytes to output - *p = sig[i]; - p++; - *len2 = *len2 + 1; - i++; - } - - *len = *len1 + *len2 + 4; - return *len + 2; -} - -// Parse a DER-encoded signature. We don't check whether the encoded integers -// satisfy DER requirements regarding leading zeros. -int ecdsa_sig_from_der(const uint8_t *der, size_t der_len, uint8_t sig[64]) { - memzero(sig, 64); - - // Check sequence header. - if (der_len < 2 || der_len > 72 || der[0] != 0x30 || der[1] != der_len - 2) { - return 1; - } - - // Read two DER-encoded integers. - size_t pos = 2; - for (int i = 0; i < 2; ++i) { - // Check integer header. - if (der_len < pos + 2 || der[pos] != 0x02) { - return 1; - } - - // Locate the integer. - size_t int_len = der[pos + 1]; - pos += 2; - if (pos + int_len > der_len) { - return 1; - } - - // Skip a possible leading zero. - if (int_len != 0 && der[pos] == 0) { - int_len--; - pos++; - } - - // Copy the integer to the output, making sure it fits. - if (int_len > 32) { - return 1; - } - memcpy(sig + 32 * (i + 1) - int_len, der + pos, int_len); - - // Move on to the next one. - pos += int_len; - } - - // Check that there are no trailing elements in the sequence. - if (pos != der_len) { - return 1; - } - - return 0; -} diff --git a/trezor-crypto/crypto/ed25519-donna/README.md b/trezor-crypto/crypto/ed25519-donna/README.md deleted file mode 100644 index 71b643485de..00000000000 --- a/trezor-crypto/crypto/ed25519-donna/README.md +++ /dev/null @@ -1,183 +0,0 @@ -[ed25519](https://ed25519.cr.yp.to) is an -[Elliptic Curve Digital Signature Algorithm](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm), -developed by [Dan Bernstein](https://cr.yp.to/djb.html), -[Niels Duif](https://www.nielsduif.nl), -[Tanja Lange](https://hyperelliptic.org/tanja), -[Peter Schwabe](https://cryptojedi.org/peter), -and [Bo-Yin Yang](https://www.iis.sinica.edu.tw/pages/byyang). - -This project provides performant, portable 32-bit & 64-bit implementations. All implementations are -of course constant time in regard to secret data. - -#### Performance - -SSE2 code and benches have not been updated yet. I will do those next. - -Compilers versions are gcc 4.6.3, icc 13.1.1, clang 3.4-1~exp1. - -Batch verification time (in parentheses) is the average time per 1 verification in a batch of 64 signatures. Counts are in thousands of cycles. - -Note that SSE2 performance may be less impressive on AMD & older CPUs with slower SSE ops! - -Visual Studio performance for `ge25519_scalarmult_base_niels` will lag behind a bit until optimized assembler versions of `ge25519_scalarmult_base_choose_niels` -are made. - -##### E5200 @ 2.5ghz, march=core2 - - - - - - - - - - - -
ImplementationSigngcciccclangVerifygcciccclang
ed25519-donna 64bit 100k110k137k327k (144k) 342k (163k) 422k (194k)
amd64-64-24k 102k 355k (158k)
ed25519-donna-sse2 64bit108k111k116k353k (155k) 345k (154k) 360k (161k)
amd64-51-32k 116k 380k (175k)
ed25519-donna-sse2 32bit147k147k156k380k (178k) 381k (173k) 430k (192k)
ed25519-donna 32bit 597k335k380k1693k (720k)1052k (453k)1141k (493k)
- -##### E3-1270 @ 3.4ghz, march=corei7-avx - - - - - - - - - - - -
ImplementationSigngcciccclangVerifygcciccclang
amd64-64-24k 68k 225k (104k)
ed25519-donna 64bit 71k 75k 90k226k (105k) 226k (112k) 277k (125k)
amd64-51-32k 72k 218k (107k)
ed25519-donna-sse2 64bit 79k 82k 92k252k (122k) 259k (124k) 282k (131k)
ed25519-donna-sse2 32bit 94k 95k103k296k (146k) 294k (137k) 306k (147k)
ed25519-donna 32bit 525k299k316k1502k (645k)959k (418k) 954k (416k)
- -#### Compilation - -No configuration is needed **if you are compiling against OpenSSL**. - -##### Hash Options - -If you are not compiling against OpenSSL, you will need a hash function. - -To use a simple/**slow** implementation of SHA-512, use `-DED25519_REFHASH` when compiling `ed25519.c`. -This should never be used except to verify the code works when OpenSSL is not available. - -To use a custom hash function, use `-DED25519_CUSTOMHASH` when compiling `ed25519.c` and put your -custom hash implementation in ed25519-hash-custom.h. The hash must have a 512bit digest and implement - - struct ed25519_hash_context; - - void ed25519_hash_init(ed25519_hash_context *ctx); - void ed25519_hash_update(ed25519_hash_context *ctx, const uint8_t *in, size_t inlen); - void ed25519_hash_final(ed25519_hash_context *ctx, uint8_t *hash); - void ed25519_hash(uint8_t *hash, const uint8_t *in, size_t inlen); - -##### Random Options - -If you are not compiling against OpenSSL, you will need a random function for batch verification. - -To use a custom random function, use `-DED25519_CUSTOMRANDOM` when compiling `ed25519.c` and put your -custom hash implementation in ed25519-randombytes-custom.h. The random function must implement: - - void ED25519_FN(ed25519_randombytes_unsafe) (void *p, size_t len); - -Use `-DED25519_TEST` when compiling `ed25519.c` to use a deterministically seeded, non-thread safe CSPRNG -variant of Bob Jenkins [ISAAC](https://en.wikipedia.org/wiki/ISAAC_%28cipher%29) - -##### Minor options - -Use `-DED25519_INLINE_ASM` to disable the use of custom assembler routines and instead rely on portable C. - -Use `-DED25519_FORCE_32BIT` to force the use of 32 bit routines even when compiling for 64 bit. - -##### 32-bit - - gcc ed25519.c -m32 -O3 -c - -##### 64-bit - - gcc ed25519.c -m64 -O3 -c - -##### SSE2 - - gcc ed25519.c -m32 -O3 -c -DED25519_SSE2 -msse2 - gcc ed25519.c -m64 -O3 -c -DED25519_SSE2 - -clang and icc are also supported - - -#### Usage - -To use the code, link against `ed25519.o -mbits` and: - - #include "ed25519.h" - -Add `-lssl -lcrypto` when using OpenSSL (Some systems don't need -lcrypto? It might be trial and error). - -To generate a private key, simply generate 32 bytes from a secure -cryptographic source: - - ed25519_secret_key sk; - randombytes(sk, sizeof(ed25519_secret_key)); - -To generate a public key: - - ed25519_public_key pk; - ed25519_publickey(sk, pk); - -To sign a message: - - ed25519_signature sig; - ed25519_sign(message, message_len, sk, pk, signature); - -To verify a signature: - - int valid = ed25519_sign_open(message, message_len, pk, signature) == 0; - -To batch verify signatures: - - const unsigned char *mp[num] = {message1, message2..} - size_t ml[num] = {message_len1, message_len2..} - const unsigned char *pkp[num] = {pk1, pk2..} - const unsigned char *sigp[num] = {signature1, signature2..} - int valid[num] - - /* valid[i] will be set to 1 if the individual signature was valid, 0 otherwise */ - int all_valid = ed25519_sign_open_batch(mp, ml, pkp, sigp, num, valid) == 0; - -**Note**: Batch verification uses `ed25519_randombytes_unsafe`, implemented in -`ed25519-randombytes.h`, to generate random scalars for the verification code. -The default implementation now uses OpenSSLs `RAND_bytes`. - -Unlike the [SUPERCOP](https://bench.cr.yp.to/supercop.html) version, signatures are -not appended to messages, and there is no need for padding in front of messages. -Additionally, the secret key does not contain a copy of the public key, so it is -32 bytes instead of 64 bytes, and the public key must be provided to the signing -function. - -##### Curve25519 - -Curve25519 public keys can be generated thanks to -[Adam Langley](https://www.imperialviolet.org/2013/05/10/fastercurve25519.html) -leveraging Ed25519's precomputed basepoint scalar multiplication. - - curved25519_key sk, pk; - randombytes(sk, sizeof(curved25519_key)); - curved25519_scalarmult_basepoint(pk, sk); - -Note the name is curved25519, a combination of curve and ed25519, to prevent -name clashes. Performance is slightly faster than short message ed25519 -signing due to both using the same code for the scalar multiply. - -#### Testing - -Fuzzing against reference implementations is now available. See [fuzz/README](fuzz/README.md). - -Building `ed25519.c` with `-DED25519_TEST` and linking with `test.c` will run basic sanity tests -and benchmark each function. `test-batch.c` has been incorporated in to `test.c`. - -`test-internals.c` is standalone and built the same way as `ed25519.c`. It tests the math primitives -with extreme values to ensure they function correctly. SSE2 is now supported. - -#### Papers - -[Available on the Ed25519 website](https://ed25519.cr.yp.to/papers.html) diff --git a/trezor-crypto/crypto/ed25519-donna/curve25519-donna-32bit.c b/trezor-crypto/crypto/ed25519-donna/curve25519-donna-32bit.c deleted file mode 100644 index c81e66a4fd7..00000000000 --- a/trezor-crypto/crypto/ed25519-donna/curve25519-donna-32bit.c +++ /dev/null @@ -1,681 +0,0 @@ -/* - Public domain by Andrew M. - See: https://github.com/floodyberry/curve25519-donna - - 32 bit integer curve25519 implementation -*/ - -#include - -const uint32_t reduce_mask_25 = (1 << 25) - 1; -const uint32_t reduce_mask_26 = (1 << 26) - 1; - -/* out = in */ -void curve25519_copy(bignum25519 out, const bignum25519 in) { - out[0] = in[0]; - out[1] = in[1]; - out[2] = in[2]; - out[3] = in[3]; - out[4] = in[4]; - out[5] = in[5]; - out[6] = in[6]; - out[7] = in[7]; - out[8] = in[8]; - out[9] = in[9]; -} - -/* out = a + b */ -void curve25519_add(bignum25519 out, const bignum25519 a, const bignum25519 b) { - out[0] = a[0] + b[0]; - out[1] = a[1] + b[1]; - out[2] = a[2] + b[2]; - out[3] = a[3] + b[3]; - out[4] = a[4] + b[4]; - out[5] = a[5] + b[5]; - out[6] = a[6] + b[6]; - out[7] = a[7] + b[7]; - out[8] = a[8] + b[8]; - out[9] = a[9] + b[9]; -} - -void curve25519_add_after_basic(bignum25519 out, const bignum25519 a, const bignum25519 b) { - uint32_t c = 0; - out[0] = a[0] + b[0] ; c = (out[0] >> 26); out[0] &= reduce_mask_26; - out[1] = a[1] + b[1] + c; c = (out[1] >> 25); out[1] &= reduce_mask_25; - out[2] = a[2] + b[2] + c; c = (out[2] >> 26); out[2] &= reduce_mask_26; - out[3] = a[3] + b[3] + c; c = (out[3] >> 25); out[3] &= reduce_mask_25; - out[4] = a[4] + b[4] + c; c = (out[4] >> 26); out[4] &= reduce_mask_26; - out[5] = a[5] + b[5] + c; c = (out[5] >> 25); out[5] &= reduce_mask_25; - out[6] = a[6] + b[6] + c; c = (out[6] >> 26); out[6] &= reduce_mask_26; - out[7] = a[7] + b[7] + c; c = (out[7] >> 25); out[7] &= reduce_mask_25; - out[8] = a[8] + b[8] + c; c = (out[8] >> 26); out[8] &= reduce_mask_26; - out[9] = a[9] + b[9] + c; c = (out[9] >> 25); out[9] &= reduce_mask_25; - out[0] += 19 * c; -} - -void curve25519_add_reduce(bignum25519 out, const bignum25519 a, const bignum25519 b) { - uint32_t c = 0; - out[0] = a[0] + b[0] ; c = (out[0] >> 26); out[0] &= reduce_mask_26; - out[1] = a[1] + b[1] + c; c = (out[1] >> 25); out[1] &= reduce_mask_25; - out[2] = a[2] + b[2] + c; c = (out[2] >> 26); out[2] &= reduce_mask_26; - out[3] = a[3] + b[3] + c; c = (out[3] >> 25); out[3] &= reduce_mask_25; - out[4] = a[4] + b[4] + c; c = (out[4] >> 26); out[4] &= reduce_mask_26; - out[5] = a[5] + b[5] + c; c = (out[5] >> 25); out[5] &= reduce_mask_25; - out[6] = a[6] + b[6] + c; c = (out[6] >> 26); out[6] &= reduce_mask_26; - out[7] = a[7] + b[7] + c; c = (out[7] >> 25); out[7] &= reduce_mask_25; - out[8] = a[8] + b[8] + c; c = (out[8] >> 26); out[8] &= reduce_mask_26; - out[9] = a[9] + b[9] + c; c = (out[9] >> 25); out[9] &= reduce_mask_25; - out[0] += 19 * c; -} - -/* multiples of p */ -const uint32_t twoP0 = 0x07ffffda; -const uint32_t twoP13579 = 0x03fffffe; -const uint32_t twoP2468 = 0x07fffffe; -const uint32_t fourP0 = 0x0fffffb4; -const uint32_t fourP13579 = 0x07fffffc; -const uint32_t fourP2468 = 0x0ffffffc; - -/* out = a - b */ -void curve25519_sub(bignum25519 out, const bignum25519 a, const bignum25519 b) { - uint32_t c = 0; - out[0] = twoP0 + a[0] - b[0] ; c = (out[0] >> 26); out[0] &= reduce_mask_26; - out[1] = twoP13579 + a[1] - b[1] + c; c = (out[1] >> 25); out[1] &= reduce_mask_25; - out[2] = twoP2468 + a[2] - b[2] + c; c = (out[2] >> 26); out[2] &= reduce_mask_26; - out[3] = twoP13579 + a[3] - b[3] + c; c = (out[3] >> 25); out[3] &= reduce_mask_25; - out[4] = twoP2468 + a[4] - b[4] + c; - out[5] = twoP13579 + a[5] - b[5] ; - out[6] = twoP2468 + a[6] - b[6] ; - out[7] = twoP13579 + a[7] - b[7] ; - out[8] = twoP2468 + a[8] - b[8] ; - out[9] = twoP13579 + a[9] - b[9] ; -} - -/* out = in * scalar */ -void curve25519_scalar_product(bignum25519 out, const bignum25519 in, const uint32_t scalar) { - uint64_t a = 0; - uint32_t c = 0; - a = mul32x32_64(in[0], scalar); out[0] = (uint32_t)a & reduce_mask_26; c = (uint32_t)(a >> 26); - a = mul32x32_64(in[1], scalar) + c; out[1] = (uint32_t)a & reduce_mask_25; c = (uint32_t)(a >> 25); - a = mul32x32_64(in[2], scalar) + c; out[2] = (uint32_t)a & reduce_mask_26; c = (uint32_t)(a >> 26); - a = mul32x32_64(in[3], scalar) + c; out[3] = (uint32_t)a & reduce_mask_25; c = (uint32_t)(a >> 25); - a = mul32x32_64(in[4], scalar) + c; out[4] = (uint32_t)a & reduce_mask_26; c = (uint32_t)(a >> 26); - a = mul32x32_64(in[5], scalar) + c; out[5] = (uint32_t)a & reduce_mask_25; c = (uint32_t)(a >> 25); - a = mul32x32_64(in[6], scalar) + c; out[6] = (uint32_t)a & reduce_mask_26; c = (uint32_t)(a >> 26); - a = mul32x32_64(in[7], scalar) + c; out[7] = (uint32_t)a & reduce_mask_25; c = (uint32_t)(a >> 25); - a = mul32x32_64(in[8], scalar) + c; out[8] = (uint32_t)a & reduce_mask_26; c = (uint32_t)(a >> 26); - a = mul32x32_64(in[9], scalar) + c; out[9] = (uint32_t)a & reduce_mask_25; c = (uint32_t)(a >> 25); - out[0] += c * 19; -} - -/* out = a - b, where a is the result of a basic op (add,sub) */ -void curve25519_sub_after_basic(bignum25519 out, const bignum25519 a, const bignum25519 b) { - uint32_t c = 0; - out[0] = fourP0 + a[0] - b[0] ; c = (out[0] >> 26); out[0] &= reduce_mask_26; - out[1] = fourP13579 + a[1] - b[1] + c; c = (out[1] >> 25); out[1] &= reduce_mask_25; - out[2] = fourP2468 + a[2] - b[2] + c; c = (out[2] >> 26); out[2] &= reduce_mask_26; - out[3] = fourP13579 + a[3] - b[3] + c; c = (out[3] >> 25); out[3] &= reduce_mask_25; - out[4] = fourP2468 + a[4] - b[4] + c; c = (out[4] >> 26); out[4] &= reduce_mask_26; - out[5] = fourP13579 + a[5] - b[5] + c; c = (out[5] >> 25); out[5] &= reduce_mask_25; - out[6] = fourP2468 + a[6] - b[6] + c; c = (out[6] >> 26); out[6] &= reduce_mask_26; - out[7] = fourP13579 + a[7] - b[7] + c; c = (out[7] >> 25); out[7] &= reduce_mask_25; - out[8] = fourP2468 + a[8] - b[8] + c; c = (out[8] >> 26); out[8] &= reduce_mask_26; - out[9] = fourP13579 + a[9] - b[9] + c; c = (out[9] >> 25); out[9] &= reduce_mask_25; - out[0] += 19 * c; -} - -void curve25519_sub_reduce(bignum25519 out, const bignum25519 a, const bignum25519 b) { - uint32_t c = 0; - out[0] = fourP0 + a[0] - b[0] ; c = (out[0] >> 26); out[0] &= reduce_mask_26; - out[1] = fourP13579 + a[1] - b[1] + c; c = (out[1] >> 25); out[1] &= reduce_mask_25; - out[2] = fourP2468 + a[2] - b[2] + c; c = (out[2] >> 26); out[2] &= reduce_mask_26; - out[3] = fourP13579 + a[3] - b[3] + c; c = (out[3] >> 25); out[3] &= reduce_mask_25; - out[4] = fourP2468 + a[4] - b[4] + c; c = (out[4] >> 26); out[4] &= reduce_mask_26; - out[5] = fourP13579 + a[5] - b[5] + c; c = (out[5] >> 25); out[5] &= reduce_mask_25; - out[6] = fourP2468 + a[6] - b[6] + c; c = (out[6] >> 26); out[6] &= reduce_mask_26; - out[7] = fourP13579 + a[7] - b[7] + c; c = (out[7] >> 25); out[7] &= reduce_mask_25; - out[8] = fourP2468 + a[8] - b[8] + c; c = (out[8] >> 26); out[8] &= reduce_mask_26; - out[9] = fourP13579 + a[9] - b[9] + c; c = (out[9] >> 25); out[9] &= reduce_mask_25; - out[0] += 19 * c; -} - -/* out = -a */ -void curve25519_neg(bignum25519 out, const bignum25519 a) { - uint32_t c = 0; - out[0] = twoP0 - a[0] ; c = (out[0] >> 26); out[0] &= reduce_mask_26; - out[1] = twoP13579 - a[1] + c; c = (out[1] >> 25); out[1] &= reduce_mask_25; - out[2] = twoP2468 - a[2] + c; c = (out[2] >> 26); out[2] &= reduce_mask_26; - out[3] = twoP13579 - a[3] + c; c = (out[3] >> 25); out[3] &= reduce_mask_25; - out[4] = twoP2468 - a[4] + c; c = (out[4] >> 26); out[4] &= reduce_mask_26; - out[5] = twoP13579 - a[5] + c; c = (out[5] >> 25); out[5] &= reduce_mask_25; - out[6] = twoP2468 - a[6] + c; c = (out[6] >> 26); out[6] &= reduce_mask_26; - out[7] = twoP13579 - a[7] + c; c = (out[7] >> 25); out[7] &= reduce_mask_25; - out[8] = twoP2468 - a[8] + c; c = (out[8] >> 26); out[8] &= reduce_mask_26; - out[9] = twoP13579 - a[9] + c; c = (out[9] >> 25); out[9] &= reduce_mask_25; - out[0] += 19 * c; -} - -/* out = a * b */ -#define curve25519_mul_noinline curve25519_mul -void curve25519_mul(bignum25519 out, const bignum25519 a, const bignum25519 b) { - uint32_t r0 = 0, r1 = 0, r2 = 0, r3 = 0, r4 = 0, r5 = 0, r6 = 0, r7 = 0, r8 = 0, r9 = 0; - uint32_t s0 = 0, s1 = 0, s2 = 0, s3 = 0, s4 = 0, s5 = 0, s6 = 0, s7 = 0, s8 = 0, s9 = 0; - uint64_t m0 = 0, m1 = 0, m2 = 0, m3 = 0, m4 = 0, m5 = 0, m6 = 0, m7 = 0, m8 = 0, m9 = 0, c = 0; - uint32_t p = 0; - - r0 = b[0]; - r1 = b[1]; - r2 = b[2]; - r3 = b[3]; - r4 = b[4]; - r5 = b[5]; - r6 = b[6]; - r7 = b[7]; - r8 = b[8]; - r9 = b[9]; - - s0 = a[0]; - s1 = a[1]; - s2 = a[2]; - s3 = a[3]; - s4 = a[4]; - s5 = a[5]; - s6 = a[6]; - s7 = a[7]; - s8 = a[8]; - s9 = a[9]; - - m1 = mul32x32_64(r0, s1) + mul32x32_64(r1, s0); - m3 = mul32x32_64(r0, s3) + mul32x32_64(r1, s2) + mul32x32_64(r2, s1) + mul32x32_64(r3, s0); - m5 = mul32x32_64(r0, s5) + mul32x32_64(r1, s4) + mul32x32_64(r2, s3) + mul32x32_64(r3, s2) + mul32x32_64(r4, s1) + mul32x32_64(r5, s0); - m7 = mul32x32_64(r0, s7) + mul32x32_64(r1, s6) + mul32x32_64(r2, s5) + mul32x32_64(r3, s4) + mul32x32_64(r4, s3) + mul32x32_64(r5, s2) + mul32x32_64(r6, s1) + mul32x32_64(r7, s0); - m9 = mul32x32_64(r0, s9) + mul32x32_64(r1, s8) + mul32x32_64(r2, s7) + mul32x32_64(r3, s6) + mul32x32_64(r4, s5) + mul32x32_64(r5, s4) + mul32x32_64(r6, s3) + mul32x32_64(r7, s2) + mul32x32_64(r8, s1) + mul32x32_64(r9, s0); - - r1 *= 2; - r3 *= 2; - r5 *= 2; - r7 *= 2; - - m0 = mul32x32_64(r0, s0); - m2 = mul32x32_64(r0, s2) + mul32x32_64(r1, s1) + mul32x32_64(r2, s0); - m4 = mul32x32_64(r0, s4) + mul32x32_64(r1, s3) + mul32x32_64(r2, s2) + mul32x32_64(r3, s1) + mul32x32_64(r4, s0); - m6 = mul32x32_64(r0, s6) + mul32x32_64(r1, s5) + mul32x32_64(r2, s4) + mul32x32_64(r3, s3) + mul32x32_64(r4, s2) + mul32x32_64(r5, s1) + mul32x32_64(r6, s0); - m8 = mul32x32_64(r0, s8) + mul32x32_64(r1, s7) + mul32x32_64(r2, s6) + mul32x32_64(r3, s5) + mul32x32_64(r4, s4) + mul32x32_64(r5, s3) + mul32x32_64(r6, s2) + mul32x32_64(r7, s1) + mul32x32_64(r8, s0); - - r1 *= 19; - r2 *= 19; - r3 = (r3 / 2) * 19; - r4 *= 19; - r5 = (r5 / 2) * 19; - r6 *= 19; - r7 = (r7 / 2) * 19; - r8 *= 19; - r9 *= 19; - - m1 += (mul32x32_64(r9, s2) + mul32x32_64(r8, s3) + mul32x32_64(r7, s4) + mul32x32_64(r6, s5) + mul32x32_64(r5, s6) + mul32x32_64(r4, s7) + mul32x32_64(r3, s8) + mul32x32_64(r2, s9)); - m3 += (mul32x32_64(r9, s4) + mul32x32_64(r8, s5) + mul32x32_64(r7, s6) + mul32x32_64(r6, s7) + mul32x32_64(r5, s8) + mul32x32_64(r4, s9)); - m5 += (mul32x32_64(r9, s6) + mul32x32_64(r8, s7) + mul32x32_64(r7, s8) + mul32x32_64(r6, s9)); - m7 += (mul32x32_64(r9, s8) + mul32x32_64(r8, s9)); - - r3 *= 2; - r5 *= 2; - r7 *= 2; - r9 *= 2; - - m0 += (mul32x32_64(r9, s1) + mul32x32_64(r8, s2) + mul32x32_64(r7, s3) + mul32x32_64(r6, s4) + mul32x32_64(r5, s5) + mul32x32_64(r4, s6) + mul32x32_64(r3, s7) + mul32x32_64(r2, s8) + mul32x32_64(r1, s9)); - m2 += (mul32x32_64(r9, s3) + mul32x32_64(r8, s4) + mul32x32_64(r7, s5) + mul32x32_64(r6, s6) + mul32x32_64(r5, s7) + mul32x32_64(r4, s8) + mul32x32_64(r3, s9)); - m4 += (mul32x32_64(r9, s5) + mul32x32_64(r8, s6) + mul32x32_64(r7, s7) + mul32x32_64(r6, s8) + mul32x32_64(r5, s9)); - m6 += (mul32x32_64(r9, s7) + mul32x32_64(r8, s8) + mul32x32_64(r7, s9)); - m8 += (mul32x32_64(r9, s9)); - - r0 = (uint32_t)m0 & reduce_mask_26; c = (m0 >> 26); - m1 += c; r1 = (uint32_t)m1 & reduce_mask_25; c = (m1 >> 25); - m2 += c; r2 = (uint32_t)m2 & reduce_mask_26; c = (m2 >> 26); - m3 += c; r3 = (uint32_t)m3 & reduce_mask_25; c = (m3 >> 25); - m4 += c; r4 = (uint32_t)m4 & reduce_mask_26; c = (m4 >> 26); - m5 += c; r5 = (uint32_t)m5 & reduce_mask_25; c = (m5 >> 25); - m6 += c; r6 = (uint32_t)m6 & reduce_mask_26; c = (m6 >> 26); - m7 += c; r7 = (uint32_t)m7 & reduce_mask_25; c = (m7 >> 25); - m8 += c; r8 = (uint32_t)m8 & reduce_mask_26; c = (m8 >> 26); - m9 += c; r9 = (uint32_t)m9 & reduce_mask_25; p = (uint32_t)(m9 >> 25); - m0 = r0 + mul32x32_64(p,19); r0 = (uint32_t)m0 & reduce_mask_26; p = (uint32_t)(m0 >> 26); - r1 += p; - - out[0] = r0; - out[1] = r1; - out[2] = r2; - out[3] = r3; - out[4] = r4; - out[5] = r5; - out[6] = r6; - out[7] = r7; - out[8] = r8; - out[9] = r9; -} - -/* out = in * in */ -void curve25519_square(bignum25519 out, const bignum25519 in) { - uint32_t r0 = 0, r1 = 0, r2 = 0, r3 = 0, r4 = 0, r5 = 0, r6 = 0, r7 = 0, r8 = 0, r9 = 0; - uint32_t d6 = 0, d7 = 0, d8 = 0, d9 = 0; - uint64_t m0 = 0, m1 = 0, m2 = 0, m3 = 0, m4 = 0, m5 = 0, m6 = 0, m7 = 0, m8 = 0, m9 = 0, c = 0; - uint32_t p = 0; - - r0 = in[0]; - r1 = in[1]; - r2 = in[2]; - r3 = in[3]; - r4 = in[4]; - r5 = in[5]; - r6 = in[6]; - r7 = in[7]; - r8 = in[8]; - r9 = in[9]; - - m0 = mul32x32_64(r0, r0); - r0 *= 2; - m1 = mul32x32_64(r0, r1); - m2 = mul32x32_64(r0, r2) + mul32x32_64(r1, r1 * 2); - r1 *= 2; - m3 = mul32x32_64(r0, r3) + mul32x32_64(r1, r2 ); - m4 = mul32x32_64(r0, r4) + mul32x32_64(r1, r3 * 2) + mul32x32_64(r2, r2); - r2 *= 2; - m5 = mul32x32_64(r0, r5) + mul32x32_64(r1, r4 ) + mul32x32_64(r2, r3); - m6 = mul32x32_64(r0, r6) + mul32x32_64(r1, r5 * 2) + mul32x32_64(r2, r4) + mul32x32_64(r3, r3 * 2); - r3 *= 2; - m7 = mul32x32_64(r0, r7) + mul32x32_64(r1, r6 ) + mul32x32_64(r2, r5) + mul32x32_64(r3, r4 ); - m8 = mul32x32_64(r0, r8) + mul32x32_64(r1, r7 * 2) + mul32x32_64(r2, r6) + mul32x32_64(r3, r5 * 2) + mul32x32_64(r4, r4 ); - m9 = mul32x32_64(r0, r9) + mul32x32_64(r1, r8 ) + mul32x32_64(r2, r7) + mul32x32_64(r3, r6 ) + mul32x32_64(r4, r5 * 2); - - d6 = r6 * 19; - d7 = r7 * 2 * 19; - d8 = r8 * 19; - d9 = r9 * 2 * 19; - - m0 += (mul32x32_64(d9, r1 ) + mul32x32_64(d8, r2 ) + mul32x32_64(d7, r3 ) + mul32x32_64(d6, r4 * 2) + mul32x32_64(r5, r5 * 2 * 19)); - m1 += (mul32x32_64(d9, r2 / 2) + mul32x32_64(d8, r3 ) + mul32x32_64(d7, r4 ) + mul32x32_64(d6, r5 * 2)); - m2 += (mul32x32_64(d9, r3 ) + mul32x32_64(d8, r4 * 2) + mul32x32_64(d7, r5 * 2) + mul32x32_64(d6, r6 )); - m3 += (mul32x32_64(d9, r4 ) + mul32x32_64(d8, r5 * 2) + mul32x32_64(d7, r6 )); - m4 += (mul32x32_64(d9, r5 * 2) + mul32x32_64(d8, r6 * 2) + mul32x32_64(d7, r7 )); - m5 += (mul32x32_64(d9, r6 ) + mul32x32_64(d8, r7 * 2)); - m6 += (mul32x32_64(d9, r7 * 2) + mul32x32_64(d8, r8 )); - m7 += (mul32x32_64(d9, r8 )); - m8 += (mul32x32_64(d9, r9 )); - - r0 = (uint32_t)m0 & reduce_mask_26; c = (m0 >> 26); - m1 += c; r1 = (uint32_t)m1 & reduce_mask_25; c = (m1 >> 25); - m2 += c; r2 = (uint32_t)m2 & reduce_mask_26; c = (m2 >> 26); - m3 += c; r3 = (uint32_t)m3 & reduce_mask_25; c = (m3 >> 25); - m4 += c; r4 = (uint32_t)m4 & reduce_mask_26; c = (m4 >> 26); - m5 += c; r5 = (uint32_t)m5 & reduce_mask_25; c = (m5 >> 25); - m6 += c; r6 = (uint32_t)m6 & reduce_mask_26; c = (m6 >> 26); - m7 += c; r7 = (uint32_t)m7 & reduce_mask_25; c = (m7 >> 25); - m8 += c; r8 = (uint32_t)m8 & reduce_mask_26; c = (m8 >> 26); - m9 += c; r9 = (uint32_t)m9 & reduce_mask_25; p = (uint32_t)(m9 >> 25); - m0 = r0 + mul32x32_64(p,19); r0 = (uint32_t)m0 & reduce_mask_26; p = (uint32_t)(m0 >> 26); - r1 += p; - - out[0] = r0; - out[1] = r1; - out[2] = r2; - out[3] = r3; - out[4] = r4; - out[5] = r5; - out[6] = r6; - out[7] = r7; - out[8] = r8; - out[9] = r9; -} - -/* out = in ^ (2 * count) */ -void curve25519_square_times(bignum25519 out, const bignum25519 in, int count) { - uint32_t r0 = 0, r1 = 0, r2 = 0, r3 = 0, r4 = 0, r5 = 0, r6 = 0, r7 = 0, r8 = 0, r9 = 0; - uint32_t d6 = 0, d7 = 0, d8 = 0, d9 = 0; - uint64_t m0 = 0, m1 = 0, m2 = 0, m3 = 0, m4 = 0, m5 = 0, m6 = 0, m7 = 0, m8 = 0, m9 = 0, c = 0; - uint32_t p = 0; - - r0 = in[0]; - r1 = in[1]; - r2 = in[2]; - r3 = in[3]; - r4 = in[4]; - r5 = in[5]; - r6 = in[6]; - r7 = in[7]; - r8 = in[8]; - r9 = in[9]; - - do { - m0 = mul32x32_64(r0, r0); - r0 *= 2; - m1 = mul32x32_64(r0, r1); - m2 = mul32x32_64(r0, r2) + mul32x32_64(r1, r1 * 2); - r1 *= 2; - m3 = mul32x32_64(r0, r3) + mul32x32_64(r1, r2 ); - m4 = mul32x32_64(r0, r4) + mul32x32_64(r1, r3 * 2) + mul32x32_64(r2, r2); - r2 *= 2; - m5 = mul32x32_64(r0, r5) + mul32x32_64(r1, r4 ) + mul32x32_64(r2, r3); - m6 = mul32x32_64(r0, r6) + mul32x32_64(r1, r5 * 2) + mul32x32_64(r2, r4) + mul32x32_64(r3, r3 * 2); - r3 *= 2; - m7 = mul32x32_64(r0, r7) + mul32x32_64(r1, r6 ) + mul32x32_64(r2, r5) + mul32x32_64(r3, r4 ); - m8 = mul32x32_64(r0, r8) + mul32x32_64(r1, r7 * 2) + mul32x32_64(r2, r6) + mul32x32_64(r3, r5 * 2) + mul32x32_64(r4, r4 ); - m9 = mul32x32_64(r0, r9) + mul32x32_64(r1, r8 ) + mul32x32_64(r2, r7) + mul32x32_64(r3, r6 ) + mul32x32_64(r4, r5 * 2); - - d6 = r6 * 19; - d7 = r7 * 2 * 19; - d8 = r8 * 19; - d9 = r9 * 2 * 19; - - m0 += (mul32x32_64(d9, r1 ) + mul32x32_64(d8, r2 ) + mul32x32_64(d7, r3 ) + mul32x32_64(d6, r4 * 2) + mul32x32_64(r5, r5 * 2 * 19)); - m1 += (mul32x32_64(d9, r2 / 2) + mul32x32_64(d8, r3 ) + mul32x32_64(d7, r4 ) + mul32x32_64(d6, r5 * 2)); - m2 += (mul32x32_64(d9, r3 ) + mul32x32_64(d8, r4 * 2) + mul32x32_64(d7, r5 * 2) + mul32x32_64(d6, r6 )); - m3 += (mul32x32_64(d9, r4 ) + mul32x32_64(d8, r5 * 2) + mul32x32_64(d7, r6 )); - m4 += (mul32x32_64(d9, r5 * 2) + mul32x32_64(d8, r6 * 2) + mul32x32_64(d7, r7 )); - m5 += (mul32x32_64(d9, r6 ) + mul32x32_64(d8, r7 * 2)); - m6 += (mul32x32_64(d9, r7 * 2) + mul32x32_64(d8, r8 )); - m7 += (mul32x32_64(d9, r8 )); - m8 += (mul32x32_64(d9, r9 )); - - r0 = (uint32_t)m0 & reduce_mask_26; c = (m0 >> 26); - m1 += c; r1 = (uint32_t)m1 & reduce_mask_25; c = (m1 >> 25); - m2 += c; r2 = (uint32_t)m2 & reduce_mask_26; c = (m2 >> 26); - m3 += c; r3 = (uint32_t)m3 & reduce_mask_25; c = (m3 >> 25); - m4 += c; r4 = (uint32_t)m4 & reduce_mask_26; c = (m4 >> 26); - m5 += c; r5 = (uint32_t)m5 & reduce_mask_25; c = (m5 >> 25); - m6 += c; r6 = (uint32_t)m6 & reduce_mask_26; c = (m6 >> 26); - m7 += c; r7 = (uint32_t)m7 & reduce_mask_25; c = (m7 >> 25); - m8 += c; r8 = (uint32_t)m8 & reduce_mask_26; c = (m8 >> 26); - m9 += c; r9 = (uint32_t)m9 & reduce_mask_25; p = (uint32_t)(m9 >> 25); - m0 = r0 + mul32x32_64(p,19); r0 = (uint32_t)m0 & reduce_mask_26; p = (uint32_t)(m0 >> 26); - r1 += p; - } while (--count); - - out[0] = r0; - out[1] = r1; - out[2] = r2; - out[3] = r3; - out[4] = r4; - out[5] = r5; - out[6] = r6; - out[7] = r7; - out[8] = r8; - out[9] = r9; -} - -/* Take a little-endian, 32-byte number and expand it into polynomial form */ -void curve25519_expand(bignum25519 out, const unsigned char in[32]) { - uint32_t x0 = 0, x1 = 0, x2 = 0, x3 = 0, x4 = 0, x5 = 0, x6 = 0, x7 = 0; - #define F(s) \ - ((((uint32_t)in[s + 0]) ) | \ - (((uint32_t)in[s + 1]) << 8) | \ - (((uint32_t)in[s + 2]) << 16) | \ - (((uint32_t)in[s + 3]) << 24)) - x0 = F(0); - x1 = F(4); - x2 = F(8); - x3 = F(12); - x4 = F(16); - x5 = F(20); - x6 = F(24); - x7 = F(28); - #undef F - - out[0] = ( x0 ) & reduce_mask_26; - out[1] = ((((uint64_t)x1 << 32) | x0) >> 26) & reduce_mask_25; - out[2] = ((((uint64_t)x2 << 32) | x1) >> 19) & reduce_mask_26; - out[3] = ((((uint64_t)x3 << 32) | x2) >> 13) & reduce_mask_25; - out[4] = (( x3) >> 6) & reduce_mask_26; - out[5] = ( x4 ) & reduce_mask_25; - out[6] = ((((uint64_t)x5 << 32) | x4) >> 25) & reduce_mask_26; - out[7] = ((((uint64_t)x6 << 32) | x5) >> 19) & reduce_mask_25; - out[8] = ((((uint64_t)x7 << 32) | x6) >> 12) & reduce_mask_26; - out[9] = (( x7) >> 6) & reduce_mask_25; /* ignore the top bit */ -} - -/* Take a fully reduced polynomial form number and contract it into a - * little-endian, 32-byte array - */ -void curve25519_contract(unsigned char out[32], const bignum25519 in) { - bignum25519 f = {0}; - curve25519_copy(f, in); - - #define carry_pass() \ - f[1] += f[0] >> 26; f[0] &= reduce_mask_26; \ - f[2] += f[1] >> 25; f[1] &= reduce_mask_25; \ - f[3] += f[2] >> 26; f[2] &= reduce_mask_26; \ - f[4] += f[3] >> 25; f[3] &= reduce_mask_25; \ - f[5] += f[4] >> 26; f[4] &= reduce_mask_26; \ - f[6] += f[5] >> 25; f[5] &= reduce_mask_25; \ - f[7] += f[6] >> 26; f[6] &= reduce_mask_26; \ - f[8] += f[7] >> 25; f[7] &= reduce_mask_25; \ - f[9] += f[8] >> 26; f[8] &= reduce_mask_26; - - #define carry_pass_full() \ - carry_pass() \ - f[0] += 19 * (f[9] >> 25); f[9] &= reduce_mask_25; - - #define carry_pass_final() \ - carry_pass() \ - f[9] &= reduce_mask_25; - - carry_pass_full() - carry_pass_full() - - /* now t is between 0 and 2^255-1, properly carried. */ - /* case 1: between 0 and 2^255-20. case 2: between 2^255-19 and 2^255-1. */ - f[0] += 19; - carry_pass_full() - - /* now between 19 and 2^255-1 in both cases, and offset by 19. */ - f[0] += (reduce_mask_26 + 1) - 19; - f[1] += (reduce_mask_25 + 1) - 1; - f[2] += (reduce_mask_26 + 1) - 1; - f[3] += (reduce_mask_25 + 1) - 1; - f[4] += (reduce_mask_26 + 1) - 1; - f[5] += (reduce_mask_25 + 1) - 1; - f[6] += (reduce_mask_26 + 1) - 1; - f[7] += (reduce_mask_25 + 1) - 1; - f[8] += (reduce_mask_26 + 1) - 1; - f[9] += (reduce_mask_25 + 1) - 1; - - /* now between 2^255 and 2^256-20, and offset by 2^255. */ - carry_pass_final() - - #undef carry_pass - #undef carry_full - #undef carry_final - - f[1] <<= 2; - f[2] <<= 3; - f[3] <<= 5; - f[4] <<= 6; - f[6] <<= 1; - f[7] <<= 3; - f[8] <<= 4; - f[9] <<= 6; - - #define F(i, s) \ - out[s+0] |= (unsigned char )(f[i] & 0xff); \ - out[s+1] = (unsigned char )((f[i] >> 8) & 0xff); \ - out[s+2] = (unsigned char )((f[i] >> 16) & 0xff); \ - out[s+3] = (unsigned char )((f[i] >> 24) & 0xff); - - out[0] = 0; - out[16] = 0; - F(0,0); - F(1,3); - F(2,6); - F(3,9); - F(4,12); - F(5,16); - F(6,19); - F(7,22); - F(8,25); - F(9,28); - #undef F -} - -/* if (iswap) swap(a, b) */ -void curve25519_swap_conditional(bignum25519 a, bignum25519 b, uint32_t iswap) { - const uint32_t swap = (uint32_t)(-(int32_t)iswap); - uint32_t x0 = 0, x1 = 0, x2 = 0, x3 = 0, x4 = 0, x5 = 0, x6 = 0, x7 = 0, x8 = 0, x9 = 0; - - x0 = swap & (a[0] ^ b[0]); a[0] ^= x0; b[0] ^= x0; - x1 = swap & (a[1] ^ b[1]); a[1] ^= x1; b[1] ^= x1; - x2 = swap & (a[2] ^ b[2]); a[2] ^= x2; b[2] ^= x2; - x3 = swap & (a[3] ^ b[3]); a[3] ^= x3; b[3] ^= x3; - x4 = swap & (a[4] ^ b[4]); a[4] ^= x4; b[4] ^= x4; - x5 = swap & (a[5] ^ b[5]); a[5] ^= x5; b[5] ^= x5; - x6 = swap & (a[6] ^ b[6]); a[6] ^= x6; b[6] ^= x6; - x7 = swap & (a[7] ^ b[7]); a[7] ^= x7; b[7] ^= x7; - x8 = swap & (a[8] ^ b[8]); a[8] ^= x8; b[8] ^= x8; - x9 = swap & (a[9] ^ b[9]); a[9] ^= x9; b[9] ^= x9; -} - -void curve25519_set(bignum25519 r, uint32_t x){ - r[0] = x & reduce_mask_26; x >>= 26; - r[1] = x & reduce_mask_25; - r[2] = 0; - r[3] = 0; - r[4] = 0; - r[5] = 0; - r[6] = 0; - r[7] = 0; - r[8] = 0; - r[9] = 0; -} - -void curve25519_set_d(bignum25519 r){ - curve25519_copy(r, ge25519_ecd); -} - -void curve25519_set_2d(bignum25519 r){ - curve25519_copy(r, ge25519_ec2d); -} - -void curve25519_set_sqrtneg1(bignum25519 r){ - curve25519_copy(r, ge25519_sqrtneg1); -} - -int curve25519_isnegative(const bignum25519 f) { - unsigned char s[32] = {0}; - curve25519_contract(s, f); - return s[0] & 1; -} - -int curve25519_isnonzero(const bignum25519 f) { - unsigned char s[32] = {0}; - curve25519_contract(s, f); - return ((((int) (s[0] | s[1] | s[2] | s[3] | s[4] | s[5] | s[6] | s[7] | s[8] | - s[9] | s[10] | s[11] | s[12] | s[13] | s[14] | s[15] | s[16] | s[17] | - s[18] | s[19] | s[20] | s[21] | s[22] | s[23] | s[24] | s[25] | s[26] | - s[27] | s[28] | s[29] | s[30] | s[31]) - 1) >> 8) + 1) & 0x1; -} - -void curve25519_reduce(bignum25519 out, const bignum25519 in) { - uint32_t c = 0; - out[0] = in[0] ; c = (out[0] >> 26); out[0] &= reduce_mask_26; - out[1] = in[1] + c; c = (out[1] >> 25); out[1] &= reduce_mask_25; - out[2] = in[2] + c; c = (out[2] >> 26); out[2] &= reduce_mask_26; - out[3] = in[3] + c; c = (out[3] >> 25); out[3] &= reduce_mask_25; - out[4] = in[4] + c; c = (out[4] >> 26); out[4] &= reduce_mask_26; - out[5] = in[5] + c; c = (out[5] >> 25); out[5] &= reduce_mask_25; - out[6] = in[6] + c; c = (out[6] >> 26); out[6] &= reduce_mask_26; - out[7] = in[7] + c; c = (out[7] >> 25); out[7] &= reduce_mask_25; - out[8] = in[8] + c; c = (out[8] >> 26); out[8] &= reduce_mask_26; - out[9] = in[9] + c; c = (out[9] >> 25); out[9] &= reduce_mask_25; - out[0] += 19 * c; -} - -void curve25519_divpowm1(bignum25519 r, const bignum25519 u, const bignum25519 v) { - bignum25519 v3={0}, uv7={0}, t0={0}, t1={0}, t2={0}; - int i = 0; - - curve25519_square(v3, v); - curve25519_mul(v3, v3, v); /* v3 = v^3 */ - curve25519_square(uv7, v3); - curve25519_mul(uv7, uv7, v); - curve25519_mul(uv7, uv7, u); /* uv7 = uv^7 */ - - /*fe_pow22523(uv7, uv7);*/ - /* From fe_pow22523.c */ - - curve25519_square(t0, uv7); - curve25519_square(t1, t0); - curve25519_square(t1, t1); - curve25519_mul(t1, uv7, t1); - curve25519_mul(t0, t0, t1); - curve25519_square(t0, t0); - curve25519_mul(t0, t1, t0); - curve25519_square(t1, t0); - for (i = 0; i < 4; ++i) { - curve25519_square(t1, t1); - } - curve25519_mul(t0, t1, t0); - curve25519_square(t1, t0); - for (i = 0; i < 9; ++i) { - curve25519_square(t1, t1); - } - curve25519_mul(t1, t1, t0); - curve25519_square(t2, t1); - for (i = 0; i < 19; ++i) { - curve25519_square(t2, t2); - } - curve25519_mul(t1, t2, t1); - for (i = 0; i < 10; ++i) { - curve25519_square(t1, t1); - } - curve25519_mul(t0, t1, t0); - curve25519_square(t1, t0); - for (i = 0; i < 49; ++i) { - curve25519_square(t1, t1); - } - curve25519_mul(t1, t1, t0); - curve25519_square(t2, t1); - for (i = 0; i < 99; ++i) { - curve25519_square(t2, t2); - } - curve25519_mul(t1, t2, t1); - for (i = 0; i < 50; ++i) { - curve25519_square(t1, t1); - } - curve25519_mul(t0, t1, t0); - curve25519_square(t0, t0); - curve25519_square(t0, t0); - curve25519_mul(t0, t0, uv7); - - /* End fe_pow22523.c */ - /* t0 = (uv^7)^((q-5)/8) */ - curve25519_mul(t0, t0, v3); - curve25519_mul(r, t0, u); /* u^(m+1)v^(-(m+1)) */ -} - -void curve25519_expand_reduce(bignum25519 out, const unsigned char in[32]) { - uint32_t x0 = 0, x1 = 0, x2 = 0, x3 = 0, x4 = 0, x5 = 0, x6 = 0, x7 = 0; -#define F(s) \ - ((((uint32_t)in[s + 0]) ) | \ - (((uint32_t)in[s + 1]) << 8) | \ - (((uint32_t)in[s + 2]) << 16) | \ - (((uint32_t)in[s + 3]) << 24)) - x0 = F(0); - x1 = F(4); - x2 = F(8); - x3 = F(12); - x4 = F(16); - x5 = F(20); - x6 = F(24); - x7 = F(28); -#undef F - - out[0] = ( x0 ) & reduce_mask_26; - out[1] = ((((uint64_t)x1 << 32) | x0) >> 26) & reduce_mask_25; - out[2] = ((((uint64_t)x2 << 32) | x1) >> 19) & reduce_mask_26; - out[3] = ((((uint64_t)x3 << 32) | x2) >> 13) & reduce_mask_25; - out[4] = (( x3) >> 6) & reduce_mask_26; - out[5] = ( x4 ) & reduce_mask_25; - out[6] = ((((uint64_t)x5 << 32) | x4) >> 25) & reduce_mask_26; - out[7] = ((((uint64_t)x6 << 32) | x5) >> 19) & reduce_mask_25; - out[8] = ((((uint64_t)x7 << 32) | x6) >> 12) & reduce_mask_26; - out[9] = (( x7) >> 6); // & reduce_mask_25; /* ignore the top bit */ - out[0] += 19 * (out[9] >> 25); - out[9] &= reduce_mask_25; -} diff --git a/trezor-crypto/crypto/ed25519-donna/curve25519-donna-helpers.c b/trezor-crypto/crypto/ed25519-donna/curve25519-donna-helpers.c deleted file mode 100644 index 3dadd62f48a..00000000000 --- a/trezor-crypto/crypto/ed25519-donna/curve25519-donna-helpers.c +++ /dev/null @@ -1,66 +0,0 @@ -/* - Public domain by Andrew M. - See: https://github.com/floodyberry/curve25519-donna - - Curve25519 implementation agnostic helpers -*/ - -#include - -/* - * In: b = 2^5 - 2^0 - * Out: b = 2^250 - 2^0 - */ -void curve25519_pow_two5mtwo0_two250mtwo0(bignum25519 b) { - bignum25519 ALIGN(16) t0,c; - - /* 2^5 - 2^0 */ /* b */ - /* 2^10 - 2^5 */ curve25519_square_times(t0, b, 5); - /* 2^10 - 2^0 */ curve25519_mul_noinline(b, t0, b); - /* 2^20 - 2^10 */ curve25519_square_times(t0, b, 10); - /* 2^20 - 2^0 */ curve25519_mul_noinline(c, t0, b); - /* 2^40 - 2^20 */ curve25519_square_times(t0, c, 20); - /* 2^40 - 2^0 */ curve25519_mul_noinline(t0, t0, c); - /* 2^50 - 2^10 */ curve25519_square_times(t0, t0, 10); - /* 2^50 - 2^0 */ curve25519_mul_noinline(b, t0, b); - /* 2^100 - 2^50 */ curve25519_square_times(t0, b, 50); - /* 2^100 - 2^0 */ curve25519_mul_noinline(c, t0, b); - /* 2^200 - 2^100 */ curve25519_square_times(t0, c, 100); - /* 2^200 - 2^0 */ curve25519_mul_noinline(t0, t0, c); - /* 2^250 - 2^50 */ curve25519_square_times(t0, t0, 50); - /* 2^250 - 2^0 */ curve25519_mul_noinline(b, t0, b); -} - -/* - * z^(p - 2) = z(2^255 - 21) - */ -void curve25519_recip(bignum25519 out, const bignum25519 z) { - bignum25519 ALIGN(16) a,t0,b; - - /* 2 */ curve25519_square_times(a, z, 1); /* a = 2 */ - /* 8 */ curve25519_square_times(t0, a, 2); - /* 9 */ curve25519_mul_noinline(b, t0, z); /* b = 9 */ - /* 11 */ curve25519_mul_noinline(a, b, a); /* a = 11 */ - /* 22 */ curve25519_square_times(t0, a, 1); - /* 2^5 - 2^0 = 31 */ curve25519_mul_noinline(b, t0, b); - /* 2^250 - 2^0 */ curve25519_pow_two5mtwo0_two250mtwo0(b); - /* 2^255 - 2^5 */ curve25519_square_times(b, b, 5); - /* 2^255 - 21 */ curve25519_mul_noinline(out, b, a); -} - -/* - * z^((p-5)/8) = z^(2^252 - 3) - */ -void curve25519_pow_two252m3(bignum25519 two252m3, const bignum25519 z) { - bignum25519 ALIGN(16) b,c,t0; - - /* 2 */ curve25519_square_times(c, z, 1); /* c = 2 */ - /* 8 */ curve25519_square_times(t0, c, 2); /* t0 = 8 */ - /* 9 */ curve25519_mul_noinline(b, t0, z); /* b = 9 */ - /* 11 */ curve25519_mul_noinline(c, b, c); /* c = 11 */ - /* 22 */ curve25519_square_times(t0, c, 1); - /* 2^5 - 2^0 = 31 */ curve25519_mul_noinline(b, t0, b); - /* 2^250 - 2^0 */ curve25519_pow_two5mtwo0_two250mtwo0(b); - /* 2^252 - 2^2 */ curve25519_square_times(b, b, 2); - /* 2^252 - 3 */ curve25519_mul_noinline(two252m3, b, z); -} diff --git a/trezor-crypto/crypto/ed25519-donna/curve25519-donna-scalarmult-base.c b/trezor-crypto/crypto/ed25519-donna/curve25519-donna-scalarmult-base.c deleted file mode 100644 index e6cbd6cb418..00000000000 --- a/trezor-crypto/crypto/ed25519-donna/curve25519-donna-scalarmult-base.c +++ /dev/null @@ -1,67 +0,0 @@ -#include -#include - -/* Calculates nQ where Q is the x-coordinate of a point on the curve - * - * mypublic: the packed little endian x coordinate of the resulting curve point - * n: a little endian, 32-byte number - * basepoint: a packed little endian point of the curve - */ - -void curve25519_scalarmult_donna(curve25519_key mypublic, const curve25519_key n, const curve25519_key basepoint) { - bignum25519 nqpqx = {1}, nqpqz = {0}, nqz = {1}, nqx = {0}; - bignum25519 q = {0}, qx = {0}, qpqx = {0}, qqx = {0}, zzz = {0}, zmone = {0}; - size_t bit = 0, lastbit = 0; - int32_t i = 0; - - curve25519_expand(q, basepoint); - curve25519_copy(nqx, q); - - /* bit 255 is always 0, and bit 254 is always 1, so skip bit 255 and - start pre-swapped on bit 254 */ - lastbit = 1; - - /* we are doing bits 254..3 in the loop, but are swapping in bits 253..2 */ - for (i = 253; i >= 2; i--) { - curve25519_add(qx, nqx, nqz); - curve25519_sub(nqz, nqx, nqz); - curve25519_add(qpqx, nqpqx, nqpqz); - curve25519_sub(nqpqz, nqpqx, nqpqz); - curve25519_mul(nqpqx, qpqx, nqz); - curve25519_mul(nqpqz, qx, nqpqz); - curve25519_add(qqx, nqpqx, nqpqz); - curve25519_sub(nqpqz, nqpqx, nqpqz); - curve25519_square(nqpqz, nqpqz); - curve25519_square(nqpqx, qqx); - curve25519_mul(nqpqz, nqpqz, q); - curve25519_square(qx, qx); - curve25519_square(nqz, nqz); - curve25519_mul(nqx, qx, nqz); - curve25519_sub(nqz, qx, nqz); - curve25519_scalar_product(zzz, nqz, 121665); - curve25519_add(zzz, zzz, qx); - curve25519_mul(nqz, nqz, zzz); - - bit = (n[i/8] >> (i & 7)) & 1; - curve25519_swap_conditional(nqx, nqpqx, bit ^ lastbit); - curve25519_swap_conditional(nqz, nqpqz, bit ^ lastbit); - lastbit = bit; - } - - /* the final 3 bits are always zero, so we only need to double */ - for (i = 0; i < 3; i++) { - curve25519_add(qx, nqx, nqz); - curve25519_sub(nqz, nqx, nqz); - curve25519_square(qx, qx); - curve25519_square(nqz, nqz); - curve25519_mul(nqx, qx, nqz); - curve25519_sub(nqz, qx, nqz); - curve25519_scalar_product(zzz, nqz, 121665); - curve25519_add(zzz, zzz, qx); - curve25519_mul(nqz, nqz, zzz); - } - - curve25519_recip(zmone, nqz); - curve25519_mul(nqz, nqx, zmone); - curve25519_contract(mypublic, nqz); -} diff --git a/trezor-crypto/crypto/ed25519-donna/ed25519-blake2b.c b/trezor-crypto/crypto/ed25519-donna/ed25519-blake2b.c deleted file mode 100644 index f22d8c75797..00000000000 --- a/trezor-crypto/crypto/ed25519-donna/ed25519-blake2b.c +++ /dev/null @@ -1,8 +0,0 @@ -#include - -#include -#include - -#define ED25519_SUFFIX _blake2b - -#include "ed25519.c" diff --git a/trezor-crypto/crypto/ed25519-donna/ed25519-donna-32bit-tables.c b/trezor-crypto/crypto/ed25519-donna/ed25519-donna-32bit-tables.c deleted file mode 100644 index 2d95839432d..00000000000 --- a/trezor-crypto/crypto/ed25519-donna/ed25519-donna-32bit-tables.c +++ /dev/null @@ -1,63 +0,0 @@ -#include - -const ge25519 ALIGN(16) ge25519_basepoint = { - {0x0325d51a,0x018b5823,0x00f6592a,0x0104a92d,0x01a4b31d,0x01d6dc5c,0x027118fe,0x007fd814,0x013cd6e5,0x0085a4db}, - {0x02666658,0x01999999,0x00cccccc,0x01333333,0x01999999,0x00666666,0x03333333,0x00cccccc,0x02666666,0x01999999}, - {0x00000001,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000}, - {0x01b7dda3,0x01a2ace9,0x025eadbb,0x0003ba8a,0x0083c27e,0x00abe37d,0x01274732,0x00ccacdd,0x00fd78b7,0x019e1d7c} -}; - -/* - d -*/ - -const bignum25519 ALIGN(16) ge25519_ecd = { - 0x035978a3,0x00d37284,0x03156ebd,0x006a0a0e,0x0001c029,0x0179e898,0x03a03cbb,0x01ce7198,0x02e2b6ff,0x01480db3 -}; - -const bignum25519 ALIGN(16) ge25519_ec2d = { - 0x02b2f159,0x01a6e509,0x022add7a,0x00d4141d,0x00038052,0x00f3d130,0x03407977,0x019ce331,0x01c56dff,0x00901b67 -}; - -/* - sqrt(-1) -*/ - -const bignum25519 ALIGN(16) ge25519_sqrtneg1 = { - 0x020ea0b0,0x0186c9d2,0x008f189d,0x0035697f,0x00bd0c60,0x01fbd7a7,0x02804c9e,0x01e16569,0x0004fc1d,0x00ae0c92 -}; - -const ge25519_niels ALIGN(16) ge25519_niels_sliding_multiples[32] = { - {{0x0340913e,0x000e4175,0x03d673a2,0x002e8a05,0x03f4e67c,0x008f8a09,0x00c21a34,0x004cf4b8,0x01298f81,0x0113f4be},{0x018c3b85,0x0124f1bd,0x01c325f7,0x0037dc60,0x033e4cb7,0x003d42c2,0x01a44c32,0x014ca4e1,0x03a33d4b,0x001f3e74},{0x037aaa68,0x00448161,0x0093d579,0x011e6556,0x009b67a0,0x0143598c,0x01bee5ee,0x00b50b43,0x0289f0c6,0x01bc45ed}}, - {{0x00fcd265,0x0047fa29,0x034faacc,0x01ef2e0d,0x00ef4d4f,0x014bd6bd,0x00f98d10,0x014c5026,0x007555bd,0x00aae456},{0x00ee9730,0x016c2a13,0x017155e4,0x01874432,0x00096a10,0x01016732,0x01a8014f,0x011e9823,0x01b9a80f,0x01e85938},{0x01d0d889,0x01a4cfc3,0x034c4295,0x0110e1ae,0x0162508c,0x00f2db4c,0x0072a2c6,0x0098da2e,0x02f12b9b,0x0168a09a}}, - {{0x0047d6ba,0x0060b0e9,0x0136eff2,0x008a5939,0x03540053,0x0064a087,0x02788e5c,0x00be7c67,0x033eb1b5,0x005529f9},{0x00a5bb33,0x00af1102,0x01a05442,0x001e3af7,0x02354123,0x00bfec44,0x01f5862d,0x00dd7ba3,0x03146e20,0x00a51733},{0x012a8285,0x00f6fc60,0x023f9797,0x003e85ee,0x009c3820,0x01bda72d,0x01b3858d,0x00d35683,0x0296b3bb,0x010eaaf9}}, - {{0x023221b1,0x01cb26aa,0x0074f74d,0x0099ddd1,0x01b28085,0x00192c3a,0x013b27c9,0x00fc13bd,0x01d2e531,0x0075bb75},{0x004ea3bf,0x00973425,0x001a4d63,0x01d59cee,0x01d1c0d4,0x00542e49,0x01294114,0x004fce36,0x029283c9,0x01186fa9},{0x01b8b3a2,0x00db7200,0x00935e30,0x003829f5,0x02cc0d7d,0x0077adf3,0x0220dd2c,0x0014ea53,0x01c6a0f9,0x01ea7eec}}, - {{0x039d8064,0x01885f80,0x00337e6d,0x01b7a902,0x02628206,0x015eb044,0x01e30473,0x0191f2d9,0x011fadc9,0x01270169},{0x02a8632f,0x0199e2a9,0x00d8b365,0x017a8de2,0x02994279,0x0086f5b5,0x0119e4e3,0x01eb39d6,0x0338add7,0x00d2e7b4},{0x0045af1b,0x013a2fe4,0x0245e0d6,0x014538ce,0x038bfe0f,0x01d4cf16,0x037e14c9,0x0160d55e,0x0021b008,0x01cf05c8}}, - {{0x01864348,0x01d6c092,0x0070262b,0x014bb844,0x00fb5acd,0x008deb95,0x003aaab5,0x00eff474,0x00029d5c,0x0062ad66},{0x02802ade,0x01c02122,0x01c4e5f7,0x00781181,0x039767fb,0x01703406,0x0342388b,0x01f5e227,0x022546d8,0x0109d6ab},{0x016089e9,0x00cb317f,0x00949b05,0x01099417,0x000c7ad2,0x011a8622,0x0088ccda,0x01290886,0x022b53df,0x00f71954}}, - {{0x027fbf93,0x01c04ecc,0x01ed6a0d,0x004cdbbb,0x02bbf3af,0x00ad5968,0x01591955,0x0094f3a2,0x02d17602,0x00099e20},{0x02007f6d,0x003088a8,0x03db77ee,0x00d5ade6,0x02fe12ce,0x0107ba07,0x0107097d,0x00482a6f,0x02ec346f,0x008d3f5f},{0x032ea378,0x0028465c,0x028e2a6c,0x018efc6e,0x0090df9a,0x01a7e533,0x039bfc48,0x010c745d,0x03daa097,0x0125ee9b}}, - {{0x028ccf0b,0x00f36191,0x021ac081,0x012154c8,0x034e0a6e,0x01b25192,0x00180403,0x01d7eea1,0x00218d05,0x010ed735},{0x03cfeaa0,0x01b300c4,0x008da499,0x0068c4e1,0x0219230a,0x01f2d4d0,0x02defd60,0x00e565b7,0x017f12de,0x018788a4},{0x03d0b516,0x009d8be6,0x03ddcbb3,0x0071b9fe,0x03ace2bd,0x01d64270,0x032d3ec9,0x01084065,0x0210ae4d,0x01447584}}, - {{0x0020de87,0x00e19211,0x01b68102,0x00b5ac97,0x022873c0,0x01942d25,0x01271394,0x0102073f,0x02fe2482,0x01c69ff9},{0x010e9d81,0x019dbbe5,0x0089f258,0x006e06b8,0x02951883,0x018f1248,0x019b3237,0x00bc7553,0x024ddb85,0x01b4c964},{0x01c8c854,0x0060ae29,0x01406d8e,0x01cff2f9,0x00cff451,0x01778d0c,0x03ac8c41,0x01552e59,0x036559ee,0x011d1b12}}, - {{0x00741147,0x0151b219,0x01092690,0x00e877e6,0x01f4d6bb,0x0072a332,0x01cd3b03,0x00dadff2,0x0097db5e,0x0086598d},{0x01c69a2b,0x01decf1b,0x02c2fa6e,0x013b7c4f,0x037beac8,0x013a16b5,0x028e7bda,0x01f6e8ac,0x01e34fe9,0x01726947},{0x01f10e67,0x003c73de,0x022b7ea2,0x010f32c2,0x03ff776a,0x00142277,0x01d38b88,0x00776138,0x03c60822,0x01201140}}, - {{0x0236d175,0x0008748e,0x03c6476d,0x013f4cdc,0x02eed02a,0x00838a47,0x032e7210,0x018bcbb3,0x00858de4,0x01dc7826},{0x00a37fc7,0x0127b40b,0x01957884,0x011d30ad,0x02816683,0x016e0e23,0x00b76be4,0x012db115,0x02516506,0x0154ce62},{0x00451edf,0x00bd749e,0x03997342,0x01cc2c4c,0x00eb6975,0x01a59508,0x03a516cf,0x00c228ef,0x0168ff5a,0x01697b47}}, - {{0x00527359,0x01783156,0x03afd75c,0x00ce56dc,0x00e4b970,0x001cabe9,0x029e0f6d,0x0188850c,0x0135fefd,0x00066d80},{0x02150e83,0x01448abf,0x02bb0232,0x012bf259,0x033c8268,0x00711e20,0x03fc148f,0x005e0e70,0x017d8bf9,0x0112b2e2},{0x02134b83,0x001a0517,0x0182c3cc,0x00792182,0x0313d799,0x001a3ed7,0x0344547e,0x01f24a0d,0x03de6ad2,0x00543127}}, - {{0x00dca868,0x00618f27,0x015a1709,0x00ddc38a,0x0320fd13,0x0036168d,0x0371ab06,0x01783fc7,0x0391e05f,0x01e29b5d},{0x01471138,0x00fca542,0x00ca31cf,0x01ca7bad,0x0175bfbc,0x01a708ad,0x03bce212,0x01244215,0x0075bb99,0x01acad68},{0x03a0b976,0x01dc12d1,0x011aab17,0x00aba0ba,0x029806cd,0x0142f590,0x018fd8ea,0x01a01545,0x03c4ad55,0x01c971ff}}, - {{0x00d098c0,0x000afdc7,0x006cd230,0x01276af3,0x03f905b2,0x0102994c,0x002eb8a4,0x015cfbeb,0x025f855f,0x01335518},{0x01cf99b2,0x0099c574,0x01a69c88,0x00881510,0x01cd4b54,0x0112109f,0x008abdc5,0x0074647a,0x0277cb1f,0x01e53324},{0x02ac5053,0x01b109b0,0x024b095e,0x016997b3,0x02f26bb6,0x00311021,0x00197885,0x01d0a55a,0x03b6fcc8,0x01c020d5}}, - {{0x02584a34,0x00e7eee0,0x03257a03,0x011e95a3,0x011ead91,0x00536202,0x00b1ce24,0x008516c6,0x03669d6d,0x004ea4a8},{0x00773f01,0x0019c9ce,0x019f6171,0x01d4afde,0x02e33323,0x01ad29b6,0x02ead1dc,0x01ed51a5,0x01851ad0,0x001bbdfa},{0x00577de5,0x00ddc730,0x038b9952,0x00f281ae,0x01d50390,0x0002e071,0x000780ec,0x010d448d,0x01f8a2af,0x00f0a5b7}}, - {{0x031f2541,0x00d34bae,0x0323ff9d,0x003a056d,0x02e25443,0x00a1ad05,0x00d1bee8,0x002f7f8e,0x03007477,0x002a24b1},{0x0114a713,0x01457e76,0x032255d5,0x01cc647f,0x02a4bdef,0x0153d730,0x00118bcf,0x00f755ff,0x013490c7,0x01ea674e},{0x02bda3e8,0x00bb490d,0x00f291ea,0x000abf40,0x01dea321,0x002f9ce0,0x00b2b193,0x00fa54b5,0x0128302f,0x00a19d8b}}, - {{0x022ef5bd,0x01638af3,0x038c6f8a,0x01a33a3d,0x039261b2,0x01bb89b8,0x010bcf9d,0x00cf42a9,0x023d6f17,0x01da1bca},{0x00e35b25,0x000d824f,0x0152e9cf,0x00ed935d,0x020b8460,0x01c7b83f,0x00c969e5,0x01a74198,0x0046a9d9,0x00cbc768},{0x01597c6a,0x0144a99b,0x00a57551,0x0018269c,0x023c464c,0x0009b022,0x00ee39e1,0x0114c7f2,0x038a9ad2,0x01584c17}}, - {{0x03b0c0d5,0x00b30a39,0x038a6ce4,0x01ded83a,0x01c277a6,0x01010a61,0x0346d3eb,0x018d995e,0x02f2c57c,0x000c286b},{0x0092aed1,0x0125e37b,0x027ca201,0x001a6b6b,0x03290f55,0x0047ba48,0x018d916c,0x01a59062,0x013e35d4,0x0002abb1},{0x003ad2aa,0x007ddcc0,0x00c10f76,0x0001590b,0x002cfca6,0x000ed23e,0x00ee4329,0x00900f04,0x01c24065,0x0082fa70}}, - {{0x02025e60,0x003912b8,0x0327041c,0x017e5ee5,0x02c0ecec,0x015a0d1c,0x02b1ce7c,0x0062220b,0x0145067e,0x01a5d931},{0x009673a6,0x00e1f609,0x00927c2a,0x016faa37,0x01650ef0,0x016f63b5,0x03cd40e1,0x003bc38f,0x0361f0ac,0x01d42acc},{0x02f81037,0x008ca0e8,0x017e23d1,0x011debfe,0x01bcbb68,0x002e2563,0x03e8add6,0x000816e5,0x03fb7075,0x0153e5ac}}, - {{0x02b11ecd,0x016bf185,0x008f22ef,0x00e7d2bb,0x0225d92e,0x00ece785,0x00508873,0x017e16f5,0x01fbe85d,0x01e39a0e},{0x01669279,0x017c810a,0x024941f5,0x0023ebeb,0x00eb7688,0x005760f1,0x02ca4146,0x0073cde7,0x0052bb75,0x00f5ffa7},{0x03b8856b,0x00cb7dcd,0x02f14e06,0x001820d0,0x01d74175,0x00e59e22,0x03fba550,0x00484641,0x03350088,0x01c3c9a3}}, - {{0x00dcf355,0x0104481c,0x0022e464,0x01f73fe7,0x00e03325,0x0152b698,0x02ef769a,0x00973663,0x00039b8c,0x0101395b},{0x01805f47,0x019160ec,0x03832cd0,0x008b06eb,0x03d4d717,0x004cb006,0x03a75b8f,0x013b3d30,0x01cfad88,0x01f034d1},{0x0078338a,0x01c7d2e3,0x02bc2b23,0x018b3f05,0x0280d9aa,0x005f3d44,0x0220a95a,0x00eeeb97,0x0362aaec,0x00835d51}}, - {{0x01b9f543,0x013fac4d,0x02ad93ae,0x018ef464,0x0212cdf7,0x01138ba9,0x011583ab,0x019c3d26,0x028790b4,0x00e2e2b6},{0x033bb758,0x01f0dbf1,0x03734bd1,0x0129b1e5,0x02b3950e,0x003bc922,0x01a53ec8,0x018c5532,0x006f3cee,0x00ae3c79},{0x0351f95d,0x0012a737,0x03d596b8,0x017658fe,0x00ace54a,0x008b66da,0x0036c599,0x012a63a2,0x032ceba1,0x00126bac}}, - {{0x03dcfe7e,0x019f4f18,0x01c81aee,0x0044bc2b,0x00827165,0x014f7c13,0x03b430f0,0x00bf96cc,0x020c8d62,0x01471997},{0x01fc7931,0x001f42dd,0x00ba754a,0x005bd339,0x003fbe49,0x016b3930,0x012a159c,0x009f83b0,0x03530f67,0x01e57b85},{0x02ecbd81,0x0096c294,0x01fce4a9,0x017701a5,0x0175047d,0x00ee4a31,0x012686e5,0x008efcd4,0x0349dc54,0x01b3466f}}, - {{0x02179ca3,0x01d86414,0x03f0afd0,0x00305964,0x015c7428,0x0099711e,0x015d5442,0x00c71014,0x01b40b2e,0x01d483cf},{0x01afc386,0x01984859,0x036203ff,0x0045c6a8,0x0020a8aa,0x00990baa,0x03313f10,0x007ceede,0x027429e4,0x017806ce},{0x039357a1,0x0142f8f4,0x0294a7b6,0x00eaccf4,0x0259edb3,0x01311e6e,0x004d326f,0x0130c346,0x01ccef3c,0x01c424b2}}, - {{0x0364918c,0x00148fc0,0x01638a7b,0x01a1fd5b,0x028ad013,0x0081e5a4,0x01a54f33,0x0174e101,0x003d0257,0x003a856c},{0x00051dcf,0x00f62b1d,0x0143d0ad,0x0042adbd,0x000fda90,0x01743ceb,0x0173e5e4,0x017bc749,0x03b7137a,0x0105ce96},{0x00f9218a,0x015b8c7c,0x00e102f8,0x0158d7e2,0x0169a5b8,0x00b2f176,0x018b347a,0x014cfef2,0x0214a4e3,0x017f1595}}, - {{0x006d7ae5,0x0195c371,0x0391e26d,0x0062a7c6,0x003f42ab,0x010dad86,0x024f8198,0x01542b2a,0x0014c454,0x0189c471},{0x0390988e,0x00b8799d,0x02e44912,0x0078e2e6,0x00075654,0x01923eed,0x0040cd72,0x00a37c76,0x0009d466,0x00c8531d},{0x02651770,0x00609d01,0x0286c265,0x0134513c,0x00ee9281,0x005d223c,0x035c760c,0x00679b36,0x0073ecb8,0x016faa50}}, - {{0x02c89be4,0x016fc244,0x02f38c83,0x018beb72,0x02b3ce2c,0x0097b065,0x034f017b,0x01dd957f,0x00148f61,0x00eab357},{0x0343d2f8,0x003398fc,0x011e368e,0x00782a1f,0x00019eea,0x00117b6f,0x0128d0d1,0x01a5e6bb,0x01944f1b,0x012b41e1},{0x03318301,0x018ecd30,0x0104d0b1,0x0038398b,0x03726701,0x019da88c,0x002d9769,0x00a7a681,0x031d9028,0x00ebfc32}}, - {{0x0220405e,0x0171face,0x02d930f8,0x017f6d6a,0x023b8c47,0x0129d5f9,0x02972456,0x00a3a524,0x006f4cd2,0x004439fa},{0x00c53505,0x0190c2fd,0x00507244,0x009930f9,0x01a39270,0x01d327c6,0x0399bc47,0x01cfe13d,0x0332bd99,0x00b33e7d},{0x0203f5e4,0x003627b5,0x00018af8,0x01478581,0x004a2218,0x002e3bb7,0x039384d0,0x0146ea62,0x020b9693,0x0017155f}}, - {{0x03c97e6f,0x00738c47,0x03b5db1f,0x01808fcf,0x01e8fc98,0x01ed25dd,0x01bf5045,0x00eb5c2b,0x0178fe98,0x01b85530},{0x01c20eb0,0x01aeec22,0x030b9eee,0x01b7d07e,0x0187e16f,0x014421fb,0x009fa731,0x0040b6d7,0x00841861,0x00a27fbc},{0x02d69abf,0x0058cdbf,0x0129f9ec,0x013c19ae,0x026c5b93,0x013a7fe7,0x004bb2ba,0x0063226f,0x002a95ca,0x01abefd9}}, - {{0x02f5d2c1,0x00378318,0x03734fb5,0x01258073,0x0263f0f6,0x01ad70e0,0x01b56d06,0x01188fbd,0x011b9503,0x0036d2e1},{0x0113a8cc,0x01541c3e,0x02ac2bbc,0x01d95867,0x01f47459,0x00ead489,0x00ab5b48,0x01db3b45,0x00edb801,0x004b024f},{0x00b8190f,0x011fe4c2,0x00621f82,0x010508d7,0x001a5a76,0x00c7d7fd,0x03aab96d,0x019cd9dc,0x019c6635,0x00ceaa1e}}, - {{0x01085cf2,0x01fd47af,0x03e3f5e1,0x004b3e99,0x01e3d46a,0x0060033c,0x015ff0a8,0x0150cdd8,0x029e8e21,0x008cf1bc},{0x00156cb1,0x003d623f,0x01a4f069,0x00d8d053,0x01b68aea,0x01ca5ab6,0x0316ae43,0x0134dc44,0x001c8d58,0x0084b343},{0x0318c781,0x0135441f,0x03a51a5e,0x019293f4,0x0048bb37,0x013d3341,0x0143151e,0x019c74e1,0x00911914,0x0076ddde}}, - {{0x006bc26f,0x00d48e5f,0x00227bbe,0x00629ea8,0x01ea5f8b,0x0179a330,0x027a1d5f,0x01bf8f8e,0x02d26e2a,0x00c6b65e},{0x01701ab6,0x0051da77,0x01b4b667,0x00a0ce7c,0x038ae37b,0x012ac852,0x03a0b0fe,0x0097c2bb,0x00a017d2,0x01eb8b2a},{0x0120b962,0x0005fb42,0x0353b6fd,0x0061f8ce,0x007a1463,0x01560a64,0x00e0a792,0x01907c92,0x013a6622,0x007b47f1}} -}; diff --git a/trezor-crypto/crypto/ed25519-donna/ed25519-donna-basepoint-table.c b/trezor-crypto/crypto/ed25519-donna/ed25519-donna-basepoint-table.c deleted file mode 100644 index e3b2e7ecff7..00000000000 --- a/trezor-crypto/crypto/ed25519-donna/ed25519-donna-basepoint-table.c +++ /dev/null @@ -1,261 +0,0 @@ -#include - -/* multiples of the base point in packed {ysubx, xaddy, t2d} form */ -const uint8_t ALIGN(16) ge25519_niels_base_multiples[256][96] = { - {0x3e,0x91,0x40,0xd7,0x05,0x39,0x10,0x9d,0xb3,0xbe,0x40,0xd1,0x05,0x9f,0x39,0xfd,0x09,0x8a,0x8f,0x68,0x34,0x84,0xc1,0xa5,0x67,0x12,0xf8,0x98,0x92,0x2f,0xfd,0x44,0x85,0x3b,0x8c,0xf5,0xc6,0x93,0xbc,0x2f,0x19,0x0e,0x8c,0xfb,0xc6,0x2d,0x93,0xcf,0xc2,0x42,0x3d,0x64,0x98,0x48,0x0b,0x27,0x65,0xba,0xd4,0x33,0x3a,0x9d,0xcf,0x07,0x59,0xbb,0x6f,0x4b,0x67,0x15,0xbd,0xdb,0xea,0xa5,0xa2,0xee,0x00,0x3f,0xe1,0x41,0xfa,0xc6,0x57,0xc9,0x1c,0x9d,0xd4,0xcd,0xca,0xec,0x16,0xaf,0x1f,0xbe,0x0e,0x4f}, - {0xa8,0xd5,0xb4,0x42,0x60,0xa5,0x99,0x8a,0xf6,0xac,0x60,0x4e,0x0c,0x81,0x2b,0x8f,0xaa,0x37,0x6e,0xb1,0x6b,0x23,0x9e,0xe0,0x55,0x25,0xc9,0x69,0xa6,0x95,0xb5,0x6b,0xd7,0x71,0x3c,0x93,0xfc,0xe7,0x24,0x92,0xb5,0xf5,0x0f,0x7a,0x96,0x9d,0x46,0x9f,0x02,0x07,0xd6,0xe1,0x65,0x9a,0xa6,0x5a,0x2e,0x2e,0x7d,0xa8,0x3f,0x06,0x0c,0x59,0x02,0x68,0xd3,0xda,0xaa,0x7e,0x34,0x6e,0x05,0x48,0xee,0x83,0x93,0x59,0xf3,0xba,0x26,0x68,0x07,0xe6,0x10,0xbe,0xca,0x3b,0xb8,0xd1,0x5e,0x16,0x0a,0x4f,0x31,0x49}, - {0x65,0xd2,0xfc,0xa4,0xe8,0x1f,0x61,0x56,0x7d,0xba,0xc1,0xe5,0xfd,0x53,0xd3,0x3b,0xbd,0xd6,0x4b,0x21,0x1a,0xf3,0x31,0x81,0x62,0xda,0x5b,0x55,0x87,0x15,0xb9,0x2a,0x30,0x97,0xee,0x4c,0xa8,0xb0,0x25,0xaf,0x8a,0x4b,0x86,0xe8,0x30,0x84,0x5a,0x02,0x32,0x67,0x01,0x9f,0x02,0x50,0x1b,0xc1,0xf4,0xf8,0x80,0x9a,0x1b,0x4e,0x16,0x7a,0x34,0x48,0x67,0xf1,0xf4,0x11,0xf2,0x9b,0x95,0xf8,0x2d,0xf6,0x17,0x6b,0x4e,0xb8,0x4e,0x2a,0x72,0x5b,0x07,0x6f,0xde,0xd7,0x21,0x2a,0xbb,0x63,0xb9,0x04,0x9a,0x54}, - {0xbf,0x18,0x68,0x05,0x0a,0x05,0xfe,0x95,0xa9,0xfa,0x60,0x56,0x71,0x89,0x7e,0x32,0x73,0x50,0xa0,0x06,0xcd,0xe3,0xe8,0xc3,0x9a,0xa4,0x45,0x74,0x4c,0x3f,0x93,0x27,0x9f,0x09,0xfc,0x8e,0xb9,0x51,0x73,0x28,0x38,0x25,0xfd,0x7d,0xf4,0xc6,0x65,0x67,0x65,0x92,0x0a,0xfb,0x3d,0x8d,0x34,0xca,0x27,0x87,0xe5,0x21,0x03,0x91,0x0e,0x68,0xb0,0x26,0x14,0xe5,0xec,0x45,0x1e,0xbf,0x94,0x0f,0xba,0x6d,0x3d,0xc6,0x2b,0xe3,0xc0,0x52,0xf8,0x8c,0xd5,0x74,0x29,0xe4,0x18,0x4c,0xe6,0xb0,0xb1,0x79,0xf0,0x44}, - {0xba,0xd6,0x47,0xa4,0xc3,0x82,0x91,0x7f,0xb7,0x29,0x27,0x4b,0xd1,0x14,0x00,0xd5,0x87,0xa0,0x64,0xb8,0x1c,0xf1,0x3c,0xe3,0xf3,0x55,0x1b,0xeb,0x73,0x7e,0x4a,0x15,0x33,0xbb,0xa5,0x08,0x44,0xbc,0x12,0xa2,0x02,0xed,0x5e,0xc7,0xc3,0x48,0x50,0x8d,0x44,0xec,0xbf,0x5a,0x0c,0xeb,0x1b,0xdd,0xeb,0x06,0xe2,0x46,0xf1,0xcc,0x45,0x29,0xb3,0x03,0xd0,0xe7,0x79,0xa1,0x32,0xc8,0x7e,0x4d,0x12,0x00,0x0a,0x9d,0x72,0x5f,0xf3,0x8f,0x6d,0x0e,0xa1,0xd4,0xc1,0x62,0x98,0x7a,0xb2,0x38,0x59,0xac,0xb8,0x68}, - {0xa4,0x8c,0x7d,0x7b,0xb6,0x06,0x98,0x49,0x39,0x27,0xd2,0x27,0x84,0xe2,0x5b,0x57,0xb9,0x53,0x45,0x20,0xe7,0x5c,0x08,0xbb,0x84,0x78,0x41,0xae,0x41,0x4c,0xb6,0x38,0x31,0x71,0x15,0x77,0xeb,0xee,0x0c,0x3a,0x88,0xaf,0xc8,0x00,0x89,0x15,0x27,0x9b,0x36,0xa7,0x59,0xda,0x68,0xb6,0x65,0x80,0xbd,0x38,0xcc,0xa2,0xb6,0x7b,0xe5,0x51,0xa4,0xe3,0x9d,0x68,0x91,0xad,0x9d,0x8f,0x37,0x91,0xfb,0xf8,0x28,0x24,0x5f,0x17,0x88,0xb9,0xcf,0x9f,0x32,0xb5,0x0a,0x05,0x9f,0xc0,0x54,0x13,0xa2,0xdf,0x65,0x78}, - {0xb1,0x21,0x32,0xaa,0x9a,0x2c,0x6f,0xba,0xa7,0x23,0xba,0x3b,0x53,0x21,0xa0,0x6c,0x3a,0x2c,0x19,0x92,0x4f,0x76,0xea,0x9d,0xe0,0x17,0x53,0x2e,0x5d,0xdd,0x6e,0x1d,0xbf,0xa3,0x4e,0x94,0xd0,0x5c,0x1a,0x6b,0xd2,0xc0,0x9d,0xb3,0x3a,0x35,0x70,0x74,0x49,0x2e,0x54,0x28,0x82,0x52,0xb2,0x71,0x7e,0x92,0x3c,0x28,0x69,0xea,0x1b,0x46,0x36,0xda,0x0f,0xab,0xac,0x8a,0x7a,0x21,0xc8,0x49,0x35,0x3d,0x54,0xc6,0x28,0xa5,0x68,0x75,0xab,0x13,0x8b,0x5b,0xd0,0x37,0x37,0xbc,0x2c,0x3a,0x62,0xef,0x3c,0x23}, - {0xd9,0x34,0x92,0xf3,0xed,0x5d,0xa7,0xe2,0xf9,0x58,0xb5,0xe1,0x80,0x76,0x3d,0x96,0xfb,0x23,0x3c,0x6e,0xac,0x41,0x27,0x2c,0xc3,0x01,0x0e,0x32,0xa1,0x24,0x90,0x3a,0x8f,0x3e,0xdd,0x04,0x66,0x59,0xb7,0x59,0x2c,0x70,0x88,0xe2,0x77,0x03,0xb3,0x6c,0x23,0xc3,0xd9,0x5e,0x66,0x9c,0x33,0xb1,0x2f,0xe5,0xbc,0x61,0x60,0xe7,0x15,0x09,0x7e,0xa3,0x34,0xa8,0x35,0xe8,0x7d,0xdf,0xea,0x57,0x98,0x68,0xda,0x9c,0xe1,0x8b,0x26,0xb3,0x67,0x71,0x36,0x85,0x11,0x2c,0xc2,0xd5,0xef,0xdb,0xd9,0xb3,0x9e,0x58}, - {0x5e,0x51,0xaa,0x49,0x54,0x63,0x5b,0xed,0x3a,0x82,0xc6,0x0b,0x9f,0xc4,0x65,0xa8,0xc4,0xd1,0x42,0x5b,0xe9,0x1f,0x0c,0x85,0xb9,0x15,0xd3,0x03,0x6f,0x6d,0xd7,0x30,0x1d,0x9c,0x2f,0x63,0x0e,0xdd,0xcc,0x2e,0x15,0x31,0x89,0x76,0x96,0xb6,0xd0,0x51,0x58,0x7a,0x63,0xa8,0x6b,0xb7,0xdf,0x52,0x39,0xef,0x0e,0xa0,0x49,0x7d,0xd3,0x6d,0xc7,0xe4,0x06,0x21,0x17,0x44,0x44,0x6c,0x69,0x7f,0x8d,0x92,0x80,0xd6,0x53,0xfb,0x26,0x3f,0x4d,0x69,0xa4,0x9e,0x73,0xb4,0xb0,0x4b,0x86,0x2e,0x11,0x97,0xc6,0x10}, - {0xde,0x5f,0xbe,0x7d,0x27,0xc4,0x93,0x64,0xa2,0x7e,0xad,0x19,0xad,0x4f,0x5d,0x26,0x90,0x45,0x30,0x46,0xc8,0xdf,0x00,0x0e,0x09,0xfe,0x66,0xed,0xab,0x1c,0xe6,0x25,0x05,0xc8,0x58,0x83,0xa0,0x2a,0xa6,0x0c,0x47,0x42,0x20,0x7a,0xe3,0x4a,0x3d,0x6a,0xdc,0xed,0x11,0x3b,0xa6,0xd3,0x64,0x74,0xef,0x06,0x08,0x55,0xaf,0x9b,0xbf,0x03,0x04,0x66,0x58,0xcc,0x28,0xe1,0x13,0x3f,0x7e,0x74,0x59,0xb4,0xec,0x73,0x58,0x6f,0xf5,0x68,0x12,0xcc,0xed,0x3d,0xb6,0xa0,0x2c,0xe2,0x86,0x45,0x63,0x78,0x6d,0x56}, - {0x34,0x08,0xc1,0x9c,0x9f,0xa4,0x37,0x16,0x51,0xc4,0x9b,0xa8,0xd5,0x56,0x8e,0xbc,0xdb,0xd2,0x7f,0x7f,0x0f,0xec,0xb5,0x1c,0xd9,0x35,0xcc,0x5e,0xca,0x5b,0x97,0x33,0xd0,0x2f,0x5a,0xc6,0x85,0x42,0x05,0xa1,0xc3,0x67,0x16,0xf3,0x2a,0x11,0x64,0x6c,0x58,0xee,0x1a,0x73,0x40,0xe2,0x0a,0x68,0x2a,0xb2,0x93,0x47,0xf3,0xa5,0xfb,0x14,0xd4,0xf7,0x85,0x69,0x16,0x46,0xd7,0x3c,0x57,0x00,0xc8,0xc9,0x84,0x5e,0x3e,0x59,0x1e,0x13,0x61,0x7b,0xb6,0xf2,0xc3,0x2f,0x6c,0x52,0xfc,0x83,0xea,0x9c,0x82,0x14}, - {0xc2,0x95,0xdd,0x97,0x84,0x7b,0x43,0xff,0xa7,0xb5,0x4e,0xaa,0x30,0x4e,0x74,0x6c,0x8b,0xe8,0x85,0x3c,0x61,0x5d,0x0c,0x9e,0x73,0x81,0x75,0x5f,0x1e,0xc7,0xd9,0x2f,0xb8,0xec,0x71,0x4e,0x2f,0x0b,0xe7,0x21,0xe3,0x77,0xa4,0x40,0xb9,0xdd,0x56,0xe6,0x80,0x4f,0x1d,0xce,0xce,0x56,0x65,0xbf,0x7e,0x7b,0x5d,0x53,0xc4,0x3b,0xfc,0x05,0xdd,0xde,0xaf,0x52,0xae,0xb3,0xb8,0x24,0xcf,0x30,0x3b,0xed,0x8c,0x63,0x95,0x34,0x95,0x81,0xbe,0xa9,0x83,0xbc,0xa4,0x33,0x04,0x1f,0x65,0x5c,0x47,0x67,0x37,0x37}, - {0xd9,0xad,0xd1,0x40,0xfd,0x99,0xba,0x2f,0x27,0xd0,0xf4,0x96,0x6f,0x16,0x07,0xb3,0xae,0x3b,0xf0,0x15,0x52,0xf0,0x63,0x43,0x99,0xf9,0x18,0x3b,0x6c,0xa5,0xbe,0x1f,0x90,0x65,0x24,0x14,0xcb,0x95,0x40,0x63,0x35,0x55,0xc1,0x16,0x40,0x14,0x12,0xef,0x60,0xbc,0x10,0x89,0x0c,0x14,0x38,0x9e,0x8c,0x7c,0x90,0x30,0x57,0x90,0xf5,0x6b,0x8a,0x5b,0x41,0xe1,0xf1,0x78,0xa7,0x0f,0x7e,0xa7,0xc3,0xba,0xf7,0x9f,0x40,0x06,0x50,0x9a,0xa2,0x9a,0xb8,0xd7,0x52,0x6f,0x56,0x5a,0x63,0x7a,0xf6,0x1c,0x52,0x02}, - {0x94,0x52,0x9d,0x0a,0x0b,0xee,0x3f,0x51,0x66,0x5a,0xdf,0x0f,0x5c,0xe7,0x98,0x8f,0xce,0x07,0xe1,0xbf,0x88,0x86,0x61,0xd4,0xed,0x2c,0x38,0x71,0x7e,0x0a,0xa0,0x3f,0xe4,0x5e,0x2f,0x77,0x20,0x67,0x14,0xb1,0xce,0x9a,0x07,0x96,0xb1,0x94,0xf8,0xe8,0x4a,0x82,0xac,0x00,0x4d,0x22,0xf8,0x4a,0xc4,0x6c,0xcd,0xf7,0xd9,0x53,0x17,0x00,0x34,0xdb,0x3d,0x96,0x2d,0x23,0x69,0x3c,0x58,0x38,0x97,0xb4,0xda,0x87,0xde,0x1d,0x85,0xf2,0x91,0xa0,0xf9,0xd1,0xd7,0xaa,0xb6,0xed,0x48,0xa0,0x2f,0xfe,0xb5,0x12}, - {0x4d,0xe3,0xfc,0x96,0xc4,0xfb,0xf0,0x71,0xed,0x5b,0xf3,0xad,0x6b,0x82,0xb9,0x73,0x61,0xc5,0x28,0xff,0x61,0x72,0x04,0xd2,0x6f,0x20,0xb1,0x6f,0xf9,0x76,0x9b,0x74,0x92,0x1e,0x6f,0xad,0x26,0x7c,0x2b,0xdf,0x13,0x89,0x4b,0x50,0x23,0xd3,0x66,0x4b,0xc3,0x8b,0x1c,0x75,0xc0,0x9d,0x40,0x8c,0xb8,0xc7,0x96,0x07,0xc2,0x93,0x7e,0x6f,0x05,0xae,0xa6,0xae,0x04,0xf6,0x5a,0x1f,0x99,0x9c,0xe4,0xbe,0xf1,0x51,0x23,0xc1,0x66,0x6b,0xff,0xee,0xb5,0x08,0xa8,0x61,0x51,0x21,0xe0,0x01,0x0f,0xc1,0xce,0x0f}, - {0x44,0x1e,0xfe,0x49,0xa6,0x58,0x4d,0x64,0x7e,0x77,0xad,0x31,0xa2,0xae,0xfc,0x21,0xd2,0xd0,0x7f,0x88,0x5a,0x1c,0x44,0x02,0xf3,0x11,0xc5,0x83,0x71,0xaa,0x01,0x49,0x45,0x4e,0x24,0xc4,0x9d,0xd2,0xf2,0x3d,0x0a,0xde,0xd8,0x93,0x74,0x0e,0x02,0x2b,0x4d,0x21,0x0c,0x82,0x7e,0x06,0xc8,0x6c,0x0a,0xb9,0xea,0x6f,0x16,0x79,0x37,0x41,0xf0,0xf8,0x1a,0x8c,0x54,0xb7,0xb1,0x08,0xb4,0x99,0x62,0x24,0x7c,0x7a,0x0f,0xce,0x39,0xd9,0x06,0x1e,0xf9,0xb0,0x60,0xf7,0x13,0x12,0x6d,0x72,0x7b,0x88,0xbb,0x41}, - {0xbe,0x46,0x43,0x74,0x44,0x7d,0xe8,0x40,0x25,0x2b,0xb5,0x15,0xd4,0xda,0x48,0x1d,0x3e,0x60,0x3b,0xa1,0x18,0x8a,0x3a,0x7c,0xf7,0xbd,0xcd,0x2f,0xc1,0x28,0xb7,0x4e,0xae,0x91,0x66,0x7c,0x59,0x4c,0x23,0x7e,0xc8,0xb4,0x85,0x0a,0x3d,0x9d,0x88,0x64,0xe7,0xfa,0x4a,0x35,0x0c,0xc9,0xe2,0xda,0x1d,0x9e,0x6a,0x0c,0x07,0x1e,0x87,0x0a,0x89,0x89,0xbc,0x4b,0x99,0xb5,0x01,0x33,0x60,0x42,0xdd,0x5b,0x3a,0xae,0x6b,0x73,0x3c,0x9e,0xd5,0x19,0xe2,0xad,0x61,0x0d,0x64,0xd4,0x85,0x26,0x0f,0x30,0xe7,0x3e}, - {0xb7,0xd6,0x7d,0x9e,0xe4,0x55,0xd2,0xf5,0xac,0x1e,0x0b,0x61,0x5c,0x11,0x16,0x80,0xca,0x87,0xe1,0x92,0x5d,0x97,0x99,0x3c,0xc2,0x25,0x91,0x97,0x62,0x57,0x81,0x13,0x18,0x75,0x1e,0x84,0x47,0x79,0xfa,0x43,0xd7,0x46,0x9c,0x63,0x59,0xfa,0xc6,0xe5,0x74,0x2b,0x05,0xe3,0x1d,0x5e,0x06,0xa1,0x30,0x90,0xb8,0xcf,0xa2,0xc6,0x47,0x7d,0xe0,0xd6,0xf0,0x8e,0x14,0xd0,0xda,0x3f,0x3c,0x6f,0x54,0x91,0x9a,0x74,0x3e,0x9d,0x57,0x81,0xbb,0x26,0x10,0x62,0xec,0x71,0x80,0xec,0xc9,0x34,0x8d,0xf5,0x8c,0x14}, - {0x27,0xf0,0x34,0x79,0xf6,0x92,0xa4,0x46,0xa9,0x0a,0x84,0xf6,0xbe,0x84,0x99,0x46,0x54,0x18,0x61,0x89,0x2a,0xbc,0xa1,0x5c,0xd4,0xbb,0x5d,0xbd,0x1e,0xfa,0xf2,0x3f,0x6d,0x75,0xe4,0x9a,0x7d,0x2f,0x57,0xe2,0x7f,0x48,0xf3,0x88,0xbb,0x45,0xc3,0x56,0x8d,0xa8,0x60,0x69,0x6d,0x0b,0xd1,0x9f,0xb9,0xa1,0xae,0x4e,0xad,0xeb,0x8f,0x27,0x66,0x39,0x93,0x8c,0x1f,0x68,0xaa,0xb1,0x98,0x0c,0x29,0x20,0x9c,0x94,0x21,0x8c,0x52,0x3c,0x9d,0x21,0x91,0x52,0x11,0x39,0x7b,0x67,0x9c,0xfe,0x02,0xdd,0x04,0x41}, - {0x2a,0x42,0x24,0x11,0x5e,0xbf,0xb2,0x72,0xb5,0x3a,0xa3,0x98,0x33,0x0c,0xfa,0xa1,0x66,0xb6,0x52,0xfa,0x01,0x61,0xcb,0x94,0xd5,0x53,0xaf,0xaf,0x00,0x3b,0x86,0x2c,0xb8,0x6a,0x09,0xdb,0x06,0x4e,0x21,0x81,0x35,0x4f,0xe4,0x0c,0xc9,0xb6,0xa8,0x21,0xf5,0x2a,0x9e,0x40,0x2a,0xc1,0x24,0x65,0x81,0xa4,0xfc,0x8e,0xa4,0xb5,0x65,0x01,0x76,0x6a,0x84,0xa0,0x74,0xa4,0x90,0xf1,0xc0,0x7c,0x2f,0xcd,0x84,0xf9,0xef,0x12,0x8f,0x2b,0xaa,0x58,0x06,0x29,0x5e,0x69,0xb8,0xc8,0xfe,0xbf,0xd9,0x67,0x1b,0x59}, - {0xfa,0x9b,0xb4,0x80,0x1c,0x0d,0x2f,0x31,0x8a,0xec,0xf3,0xab,0x5e,0x51,0x79,0x59,0x88,0x1c,0xf0,0x9e,0xc0,0x33,0x70,0x72,0xcb,0x7b,0x8f,0xca,0xc7,0x2e,0xe0,0x3d,0x5d,0xb5,0x18,0x9f,0x71,0xb3,0xb9,0x99,0x1e,0x64,0x8c,0xa1,0xfa,0xe5,0x65,0xe4,0xed,0x05,0x9f,0xc2,0x36,0x11,0x08,0x61,0x8b,0x12,0x30,0x70,0x86,0x4f,0x9b,0x48,0xef,0x92,0xeb,0x3a,0x2d,0x10,0x32,0xd2,0x61,0xa8,0x16,0x61,0xb4,0x53,0x62,0xe1,0x24,0xaa,0x0b,0x19,0xe7,0xab,0x7e,0x3d,0xbf,0xbe,0x6c,0x49,0xba,0xfb,0xf5,0x49}, - {0xd4,0xcf,0x5b,0x8a,0x10,0x9a,0x94,0x30,0xeb,0x73,0x64,0xbc,0x70,0xdd,0x40,0xdc,0x1c,0x0d,0x7c,0x30,0xc1,0x94,0xc2,0x92,0x74,0x6e,0xfa,0xcb,0x6d,0xa8,0x04,0x56,0x2e,0x57,0x9c,0x1e,0x8c,0x62,0x5d,0x15,0x41,0x47,0x88,0xc5,0xac,0x86,0x4d,0x8a,0xeb,0x63,0x57,0x51,0xf6,0x52,0xa3,0x91,0x5b,0x51,0x67,0x88,0xc2,0xa6,0xa1,0x06,0xb6,0x64,0x17,0x7c,0xd4,0xd1,0x88,0x72,0x51,0x8b,0x41,0xe0,0x40,0x11,0x54,0x72,0xd1,0xf6,0xac,0x18,0x60,0x1a,0x03,0x9f,0xc6,0x42,0x27,0xfe,0x89,0x9e,0x98,0x20}, - {0x7f,0xcc,0x2d,0x3a,0xfd,0x77,0x97,0x49,0x92,0xd8,0x4f,0xa5,0x2c,0x7c,0x85,0x32,0xa0,0xe3,0x07,0xd2,0x64,0xd8,0x79,0xa2,0x29,0x7e,0xa6,0x0c,0x1d,0xed,0x03,0x04,0x2e,0xec,0xea,0x85,0x8b,0x27,0x74,0x16,0xdf,0x2b,0xcb,0x7a,0x07,0xdc,0x21,0x56,0x5a,0xf4,0xcb,0x61,0x16,0x4c,0x0a,0x64,0xd3,0x95,0x05,0xf7,0x50,0x99,0x0b,0x73,0x52,0xc5,0x4e,0x87,0x35,0x2d,0x4b,0xc9,0x8d,0x6f,0x24,0x98,0xcf,0xc8,0xe6,0xc5,0xce,0x35,0xc0,0x16,0xfa,0x46,0xcb,0xf7,0xcc,0x3d,0x30,0x08,0x43,0x45,0xd7,0x5b}, - {0xc2,0x4c,0xb2,0x28,0x95,0xd1,0x9a,0x7f,0x81,0xc1,0x35,0x63,0x65,0x54,0x6b,0x7f,0x36,0x72,0xc0,0x4f,0x6e,0xb6,0xb8,0x66,0x83,0xad,0x80,0x73,0x00,0x78,0x3a,0x13,0x2a,0x79,0xe7,0x15,0x21,0x93,0xc4,0x85,0xc9,0xdd,0xcd,0xbd,0xa2,0x89,0x4c,0xc6,0x62,0xd7,0xa3,0xad,0xa8,0x3d,0x1e,0x9d,0x2c,0xf8,0x67,0x30,0x12,0xdb,0xb7,0x5b,0xbe,0x62,0xca,0xc6,0x67,0xf4,0x61,0x09,0xee,0x52,0x19,0x21,0xd6,0x21,0xec,0x04,0x70,0x47,0xd5,0x9b,0x77,0x60,0x23,0x18,0xd2,0xe0,0xf0,0x58,0x6d,0xca,0x0d,0x74}, - {0x4e,0xce,0xcf,0x52,0x07,0xee,0x48,0xdf,0xb7,0x08,0xec,0x06,0xf3,0xfa,0xff,0xc3,0xc4,0x59,0x54,0xb9,0x2a,0x0b,0x71,0x05,0x8d,0xa3,0x3e,0x96,0xfa,0x25,0x1d,0x16,0x3c,0x43,0x78,0x04,0x57,0x8c,0x1a,0x23,0x9d,0x43,0x81,0xc2,0x0e,0x27,0xb5,0xb7,0x9f,0x07,0xd9,0xe3,0xea,0x99,0xaa,0xdb,0xd9,0x03,0x2b,0x6c,0x25,0xf5,0x03,0x2c,0x7d,0xa4,0x53,0x7b,0x75,0x18,0x0f,0x79,0x79,0x58,0x0c,0xcf,0x30,0x01,0x7b,0x30,0xf9,0xf7,0x7e,0x25,0x77,0x3d,0x90,0x31,0xaf,0xbb,0x96,0xbd,0xbd,0x68,0x94,0x69}, - {0xcf,0xfe,0xda,0xf4,0x46,0x2f,0x1f,0xbd,0xf7,0xd6,0x7f,0xa4,0x14,0x01,0xef,0x7c,0x7f,0xb3,0x47,0x4a,0xda,0xfd,0x1f,0xd3,0x85,0x57,0x90,0x73,0xa4,0x19,0x52,0x52,0x48,0x19,0xa9,0x6a,0xe6,0x3d,0xdd,0xd8,0xcc,0xd2,0xc0,0x2f,0xc2,0x64,0x50,0x48,0x2f,0xea,0xfd,0x34,0x66,0x24,0x48,0x9b,0x3a,0x2e,0x4a,0x6c,0x4e,0x1c,0x3e,0x29,0xe1,0x12,0x51,0x92,0x4b,0x13,0x6e,0x37,0xa0,0x5d,0xa1,0xdc,0xb5,0x78,0x37,0x70,0x11,0x31,0x1c,0x46,0xaf,0x89,0x45,0xb0,0x23,0x28,0x03,0x7f,0x44,0x5c,0x60,0x5b}, - {0x89,0x7c,0xc4,0x20,0x59,0x80,0x65,0xb9,0xcc,0x8f,0x3b,0x92,0x0c,0x10,0xf0,0xe7,0x77,0xef,0xe2,0x02,0x65,0x25,0x01,0x00,0xee,0xb3,0xae,0xa8,0xce,0x6d,0xa7,0x24,0x4c,0xf0,0xe7,0xf0,0xc6,0xfe,0xe9,0x3b,0x62,0x49,0xe3,0x75,0x9e,0x57,0x6a,0x86,0x1a,0xe6,0x1d,0x1e,0x16,0xef,0x42,0x55,0xd5,0xbd,0x5a,0xcc,0xf4,0xfe,0x12,0x2f,0x40,0xc7,0xc0,0xdf,0xb2,0x22,0x45,0x0a,0x07,0xa4,0xc9,0x40,0x7f,0x6e,0xd0,0x10,0x68,0xf6,0xcf,0x78,0x41,0x14,0xcf,0xc6,0x90,0x37,0xa4,0x18,0x25,0x7b,0x60,0x5e}, - {0x18,0x18,0xdf,0x6c,0x8f,0x1d,0xb3,0x58,0xa2,0x58,0x62,0xc3,0x4f,0xa7,0xcf,0x35,0x6e,0x1d,0xe6,0x66,0x4f,0xff,0xb3,0xe1,0xf7,0xd5,0xcd,0x6c,0xab,0xac,0x67,0x50,0x14,0xcf,0x96,0xa5,0x1c,0x43,0x2c,0xa0,0x00,0xe4,0xd3,0xae,0x40,0x2d,0xc4,0xe3,0xdb,0x26,0x0f,0x2e,0x80,0x26,0x45,0xd2,0x68,0x70,0x45,0x9e,0x13,0x33,0x1f,0x20,0x51,0x9d,0x03,0x08,0x6b,0x7f,0x52,0xfd,0x06,0x00,0x7c,0x01,0x64,0x49,0xb1,0x18,0xa8,0xa4,0x25,0x2e,0xb0,0x0e,0x22,0xd5,0x75,0x03,0x46,0x62,0x88,0xba,0x7c,0x39}, - {0xb2,0x59,0x59,0xf0,0x93,0x30,0xc1,0x30,0x76,0x79,0xa9,0xe9,0x8d,0xa1,0x3a,0xe2,0x26,0x5e,0x1d,0x72,0x91,0xd4,0x2f,0x22,0x3a,0x6c,0x6e,0x76,0x20,0xd3,0x39,0x23,0xe7,0x79,0x13,0xc8,0xfb,0xc3,0x15,0x78,0xf1,0x2a,0xe1,0xdd,0x20,0x94,0x61,0xa6,0xd5,0xfd,0xa8,0x85,0xf8,0xc0,0xa9,0xff,0x52,0xc2,0xe1,0xc1,0x22,0x40,0x1b,0x77,0xa7,0x2f,0x3a,0x51,0x86,0xd9,0x7d,0xd8,0x08,0xcf,0xd4,0xf9,0x71,0x9b,0xac,0xf5,0xb3,0x83,0xa2,0x1e,0x1b,0xc3,0x6b,0xd0,0x76,0x1a,0x97,0x19,0x92,0x18,0x1a,0x33}, - {0xc6,0x80,0x4f,0xfb,0x45,0x6f,0x16,0xf5,0xcf,0x75,0xc7,0x61,0xde,0xc7,0x36,0x9c,0x1c,0xd9,0x41,0x90,0x1b,0xe8,0xd4,0xe3,0x21,0xfe,0xbd,0x83,0x6b,0x7c,0x16,0x31,0xaf,0x72,0x75,0x9d,0x3a,0x2f,0x51,0x26,0x9e,0x4a,0x07,0x68,0x88,0xe2,0xcb,0x5b,0xc4,0xf7,0x80,0x11,0xc1,0xc1,0xed,0x84,0x7b,0xa6,0x49,0xf6,0x9f,0x61,0xc9,0x1a,0x68,0x10,0x4b,0x52,0x42,0x38,0x2b,0xf2,0x87,0xe9,0x9c,0xee,0x3b,0x34,0x68,0x50,0xc8,0x50,0x62,0x4a,0x84,0x71,0x9d,0xfc,0x11,0xb1,0x08,0x1f,0x34,0x36,0x24,0x61}, - {0x8d,0x89,0x4e,0x87,0xdb,0x41,0x9d,0xd9,0x20,0xdc,0x07,0x6c,0xf1,0xa5,0xfe,0x09,0xbc,0x9b,0x0f,0xd0,0x67,0x2c,0x3d,0x79,0x40,0xff,0x5e,0x9e,0x30,0xe2,0xeb,0x46,0x38,0x26,0x2d,0x1a,0xe3,0x49,0x63,0x8b,0x35,0xfd,0xd3,0x9b,0x00,0xb7,0xdf,0x9d,0xa4,0x6b,0xa0,0xa3,0xb8,0xf1,0x8b,0x7f,0x45,0x04,0xd9,0x78,0x31,0xaa,0x22,0x15,0x38,0x49,0x61,0x69,0x53,0x2f,0x38,0x2c,0x10,0x6d,0x2d,0xb7,0x9a,0x40,0xfe,0xda,0x27,0xf2,0x46,0xb6,0x91,0x33,0xc8,0xe8,0x6c,0x30,0x24,0x05,0xf5,0x70,0xfe,0x45}, - {0x8c,0x0b,0x0c,0x96,0xa6,0x75,0x48,0xda,0x20,0x2f,0x0e,0xef,0x76,0xd0,0x68,0x5b,0xd4,0x8f,0x0b,0x3d,0xcf,0x51,0xfb,0x07,0xd4,0x92,0xe3,0xa0,0x23,0x16,0x8d,0x42,0x91,0x14,0x95,0xc8,0x20,0x49,0xf2,0x62,0xa2,0x0c,0x63,0x3f,0xc8,0x07,0xf0,0x05,0xb8,0xd4,0xc9,0xf5,0xd2,0x45,0xbb,0x6f,0x45,0x22,0x7a,0xb5,0x6d,0x9f,0x61,0x16,0xfd,0x08,0xa3,0x01,0x44,0x4a,0x4f,0x08,0xac,0xca,0xa5,0x76,0xc3,0x19,0x22,0xa8,0x7d,0xbc,0xd1,0x43,0x46,0xde,0xb8,0xde,0xc6,0x38,0xbd,0x60,0x2d,0x59,0x81,0x1d}, - {0x5f,0xac,0x0d,0xa6,0x56,0x87,0x36,0x61,0x57,0xdc,0xab,0xeb,0x6a,0x2f,0xe0,0x17,0x7d,0x0f,0xce,0x4c,0x2d,0x3f,0x19,0x7f,0xf0,0xdc,0xec,0x89,0x77,0x4a,0x23,0x20,0xe8,0xc5,0x85,0x7b,0x9f,0xb6,0x65,0x87,0xb2,0xba,0x68,0xd1,0x8b,0x67,0xf0,0x6f,0x9b,0x0f,0x33,0x1d,0x7c,0xe7,0x70,0x3a,0x7c,0x8e,0xaf,0xb0,0x51,0x6d,0x5f,0x3a,0x52,0xb2,0x78,0x71,0xb6,0x0d,0xd2,0x76,0x60,0xd1,0x1e,0xd5,0xf9,0x34,0x1c,0x07,0x70,0x11,0xe4,0xb3,0x20,0x4a,0x2a,0xf6,0x66,0xe3,0xff,0x3c,0x35,0x82,0xd6,0x7c}, - {0xb6,0xfa,0x87,0xd8,0x5b,0xa4,0xe1,0x0b,0x6e,0x3b,0x40,0xba,0x32,0x6a,0x84,0x2a,0x00,0x60,0x6e,0xe9,0x12,0x10,0x92,0xd9,0x43,0x09,0xdc,0x3b,0x86,0xc8,0x38,0x28,0xf3,0xf4,0xac,0x68,0x60,0xcd,0x65,0xa6,0xd3,0xe3,0xd7,0x3c,0x18,0x2d,0xd9,0x42,0xd9,0x25,0x60,0x33,0x9d,0x38,0x59,0x57,0xff,0xd8,0x2c,0x2b,0x3b,0x25,0xf0,0x3e,0x30,0x50,0x46,0x4a,0xcf,0xb0,0x6b,0xd1,0xab,0x77,0xc5,0x15,0x41,0x6b,0x49,0xfa,0x9d,0x41,0xab,0xf4,0x8a,0xae,0xcf,0x82,0x12,0x28,0xa8,0x06,0xa6,0xb8,0xdc,0x21}, - {0xc8,0x9f,0x9d,0x8c,0x46,0x04,0x60,0x5c,0xcb,0xa3,0x2a,0xd4,0x6e,0x09,0x40,0x25,0x9c,0x2f,0xee,0x12,0x4c,0x4d,0x5b,0x12,0xab,0x1d,0xa3,0x94,0x81,0xd0,0xc3,0x0b,0xba,0x31,0x77,0xbe,0xfa,0x00,0x8d,0x9a,0x89,0x18,0x9e,0x62,0x7e,0x60,0x03,0x82,0x7f,0xd9,0xf3,0x43,0x37,0x02,0xcc,0xb2,0x8b,0x67,0x6f,0x6c,0xbf,0x0d,0x84,0x5d,0x8b,0xe1,0x9f,0x30,0x0d,0x38,0x6e,0x70,0xc7,0x65,0xe1,0xb9,0xa6,0x2d,0xb0,0x6e,0xab,0x20,0xae,0x7d,0x99,0xba,0xbb,0x57,0xdd,0x96,0xc1,0x2a,0x23,0x76,0x42,0x3a}, - {0xfa,0x84,0x70,0x8a,0x2c,0x43,0x42,0x4b,0x45,0xe5,0xb9,0xdf,0xe3,0x19,0x8a,0x89,0x5d,0xe4,0x58,0x9c,0x21,0x00,0x9f,0xbe,0xd1,0xeb,0x6d,0xa1,0xce,0x77,0xf1,0x1f,0xcb,0x7e,0x44,0xdb,0x72,0xc1,0xf8,0x3b,0xbd,0x2d,0x28,0xc6,0x1f,0xc4,0xcf,0x5f,0xfe,0x15,0xaa,0x75,0xc0,0xff,0xac,0x80,0xf9,0xa9,0xe1,0x24,0xe8,0xc9,0x70,0x07,0xfd,0xb5,0xb5,0x45,0x9a,0xd9,0x61,0xcf,0x24,0x79,0x3a,0x1b,0xe9,0x84,0x09,0x86,0x89,0x3e,0x3e,0x30,0x19,0x09,0x30,0xe7,0x1e,0x0b,0x50,0x41,0xfd,0x64,0xf2,0x39}, - {0x9c,0xe2,0xe7,0xdb,0x17,0x34,0xad,0xa7,0x9c,0x13,0x9c,0x2b,0x6a,0x37,0x94,0xbd,0xa9,0x7b,0x59,0x93,0x8e,0x1b,0xe9,0xa0,0x40,0x98,0x88,0x68,0x34,0xd7,0x12,0x17,0xe1,0x7b,0x09,0xfe,0xab,0x4a,0x9b,0xd1,0x29,0x19,0xe0,0xdf,0xe1,0xfc,0x6d,0xa4,0xff,0xf1,0xa6,0x2c,0x94,0x08,0xc9,0xc3,0x4e,0xf1,0x35,0x2c,0x27,0x21,0xc6,0x65,0xdd,0x93,0x31,0xce,0xf8,0x89,0x2b,0xe7,0xbb,0xc0,0x25,0xa1,0x56,0x33,0x10,0x4d,0x83,0xfe,0x1c,0x2e,0x3d,0xa9,0x19,0x04,0x72,0xe2,0x9c,0xb1,0x0a,0x80,0xf9,0x22}, - {0xcb,0xf8,0x9e,0x3e,0x8a,0x36,0x5a,0x60,0x15,0x47,0x50,0xa5,0x22,0xc0,0xe9,0xe3,0x8f,0x24,0x24,0x5f,0xb0,0x48,0x3d,0x55,0xe5,0x26,0x76,0x64,0xcd,0x16,0xf4,0x13,0xac,0xfd,0x6e,0x9a,0xdd,0x9f,0x02,0x42,0x41,0x49,0xa5,0x34,0xbe,0xce,0x12,0xb9,0x7b,0xf3,0xbd,0x87,0xb9,0x64,0x0f,0x64,0xb4,0xca,0x98,0x85,0xd3,0xa4,0x71,0x41,0x8c,0x4c,0xc9,0x99,0xaa,0x58,0x27,0xfa,0x07,0xb8,0x00,0xb0,0x6f,0x6f,0x00,0x23,0x92,0x53,0xda,0xad,0xdd,0x91,0xd2,0xfb,0xab,0xd1,0x4b,0x57,0xfa,0x14,0x82,0x50}, - {0x4b,0xfe,0xd6,0x3e,0x15,0x69,0x02,0xc2,0xc4,0x77,0x1d,0x51,0x39,0x67,0x5a,0xa6,0x94,0xaf,0x14,0x2c,0x46,0x26,0xde,0xcb,0x4b,0xa7,0xab,0x6f,0xec,0x60,0xf9,0x22,0xd6,0x03,0xd0,0x53,0xbb,0x15,0x1a,0x46,0x65,0xc9,0xf3,0xbc,0x88,0x28,0x10,0xb2,0x5a,0x3a,0x68,0x6c,0x75,0x76,0xc5,0x27,0x47,0xb4,0x6c,0xc8,0xa4,0x58,0x77,0x3a,0x76,0x50,0xae,0x93,0xf6,0x11,0x81,0x54,0xa6,0x54,0xfd,0x1d,0xdf,0x21,0xae,0x1d,0x65,0x5e,0x11,0xf3,0x90,0x8c,0x24,0x12,0x94,0xf4,0xe7,0x8d,0x5f,0xd1,0x9f,0x5d}, - {0x7f,0x72,0x63,0x6d,0xd3,0x08,0x14,0x03,0x33,0xb5,0xc7,0xd7,0xef,0x9a,0x37,0x6a,0x4b,0xe2,0xae,0xcc,0xc5,0x8f,0xe1,0xa9,0xd3,0xbe,0x8f,0x4f,0x91,0x35,0x2f,0x33,0x1e,0x52,0xd7,0xee,0x2a,0x4d,0x24,0x3f,0x15,0x96,0x2e,0x43,0x28,0x90,0x3a,0x8e,0xd4,0x16,0x9c,0x2e,0x77,0xba,0x64,0xe1,0xd8,0x98,0xeb,0x47,0xfa,0x87,0xc1,0x3b,0x0c,0xc2,0x86,0xea,0x15,0x01,0x47,0x6d,0x25,0xd1,0x46,0x6c,0xcb,0xb7,0x8a,0x99,0x88,0x01,0x66,0x3a,0xb5,0x32,0x78,0xd7,0x03,0xba,0x6f,0x90,0xce,0x81,0x0d,0x45}, - {0x75,0x52,0x20,0xa6,0xa1,0xb6,0x7b,0x6e,0x83,0x8e,0x3c,0x41,0xd7,0x21,0x4f,0xaa,0xb2,0x5c,0x8f,0xe8,0x55,0xd1,0x56,0x6f,0xe1,0x5b,0x34,0xa6,0x4b,0x5d,0xe2,0x2d,0x3f,0x74,0xae,0x1c,0x96,0xd8,0x74,0xd0,0xed,0x63,0x1c,0xee,0xf5,0x18,0x6d,0xf8,0x29,0xed,0xf4,0xe7,0x5b,0xc5,0xbd,0x97,0x08,0xb1,0x3a,0x66,0x79,0xd2,0xba,0x4c,0xcd,0x1f,0xd7,0xa0,0x24,0x90,0xd1,0x80,0xf8,0x8a,0x28,0xfb,0x0a,0xc2,0x25,0xc5,0x19,0x64,0x3a,0x5f,0x4b,0x97,0xa3,0xb1,0x33,0x72,0x00,0xe2,0xef,0xbc,0x7f,0x7d}, - {0x01,0x28,0x6b,0x26,0x6a,0x1e,0xef,0xfa,0x16,0x9f,0x73,0xd5,0xc4,0x68,0x6c,0x86,0x2c,0x76,0x03,0x1b,0xbc,0x2f,0x8a,0xf6,0x8d,0x5a,0xb7,0x87,0x5e,0x43,0x75,0x59,0x94,0x90,0xc2,0xf3,0xc5,0x5d,0x7c,0xcd,0xab,0x05,0x91,0x2a,0x9a,0xa2,0x81,0xc7,0x58,0x30,0x1c,0x42,0x36,0x1d,0xc6,0x80,0xd7,0xd4,0xd8,0xdc,0x96,0xd1,0x9c,0x4f,0x68,0x37,0x7b,0x6a,0xd8,0x97,0x92,0x19,0x63,0x7a,0xd1,0x1a,0x24,0x58,0xd0,0xd0,0x17,0x0c,0x1c,0x5c,0xad,0x9c,0x02,0xba,0x07,0x03,0x7a,0x38,0x84,0xd0,0xcd,0x7c}, - {0x17,0x04,0x26,0x6d,0x2c,0x42,0xa6,0xdc,0xbd,0x40,0x82,0x94,0x50,0x3d,0x15,0xae,0x77,0xc6,0x68,0xfb,0xb4,0xc1,0xc0,0xa9,0x53,0xcf,0xd0,0x61,0xed,0xd0,0x8b,0x42,0x93,0xcc,0x60,0x67,0x18,0x84,0x0c,0x9b,0x99,0x2a,0xb3,0x1a,0x7a,0x00,0xae,0xcd,0x18,0xda,0x0b,0x62,0x86,0xec,0x8d,0xa8,0x44,0xca,0x90,0x81,0x84,0xca,0x93,0x35,0xa7,0x9a,0x84,0x5e,0x9a,0x18,0x13,0x92,0xcd,0xfa,0xd8,0x65,0x35,0xc3,0xd8,0xd4,0xd1,0xbb,0xfd,0x53,0x5b,0x54,0x52,0x8c,0xe6,0x63,0x2d,0xda,0x08,0x83,0x39,0x27}, - {0x13,0xd4,0x5e,0x43,0x28,0x8d,0xc3,0x42,0xc9,0xcc,0x78,0x32,0x60,0xf3,0x50,0xbd,0xef,0x03,0xda,0x79,0x1a,0xab,0x07,0xbb,0x55,0x33,0x8c,0xbe,0xae,0x97,0x95,0x26,0x53,0x24,0x70,0x0a,0x4c,0x0e,0xa1,0xb9,0xde,0x1b,0x7d,0xd5,0x66,0x58,0xa2,0x0f,0xf7,0xda,0x27,0xcd,0xb5,0xd9,0xb9,0xff,0xfd,0x33,0x2c,0x49,0x45,0x29,0x2c,0x57,0xbe,0x30,0xcd,0xd6,0x45,0xc7,0x7f,0xc7,0xfb,0xae,0xba,0xe3,0xd3,0xe8,0xdf,0xe4,0x0c,0xda,0x5d,0xaa,0x30,0x88,0x2c,0xa2,0x80,0xca,0x5b,0xc0,0x98,0x54,0x98,0x7f}, - {0x17,0xe1,0x0b,0x9f,0x88,0xce,0x49,0x38,0x88,0xa2,0x54,0x7b,0x1b,0xad,0x05,0x80,0x1c,0x92,0xfc,0x23,0x9f,0xc3,0xa3,0x3d,0x04,0xf3,0x31,0x0a,0x47,0xec,0xc2,0x76,0x63,0x63,0xbf,0x0f,0x52,0x15,0x56,0xd3,0xa6,0xfb,0x4d,0xcf,0x45,0x5a,0x04,0x08,0xc2,0xa0,0x3f,0x87,0xbc,0x4f,0xc2,0xee,0xe7,0x12,0x9b,0xd6,0x3c,0x65,0xf2,0x30,0x85,0x0c,0xc1,0xaa,0x38,0xc9,0x08,0x8a,0xcb,0x6b,0x27,0xdb,0x60,0x9b,0x17,0x46,0x70,0xac,0x6f,0x0e,0x1e,0xc0,0x20,0xa9,0xda,0x73,0x64,0x59,0xf1,0x73,0x12,0x2f}, - {0x11,0x1e,0xe0,0x8a,0x7c,0xfc,0x39,0x47,0x9f,0xab,0x6a,0x4a,0x90,0x74,0x52,0xfd,0x2e,0x8f,0x72,0x87,0x82,0x8a,0xd9,0x41,0xf2,0x69,0x5b,0xd8,0x2a,0x57,0x9e,0x5d,0xc0,0x0b,0xa7,0x55,0xd7,0x8b,0x48,0x30,0xe7,0x42,0xd4,0xf1,0xa4,0xb5,0xd6,0x06,0x62,0x61,0x59,0xbc,0x9e,0xa6,0xd1,0xea,0x84,0xf7,0xc5,0xed,0x97,0x19,0xac,0x38,0x3b,0xb1,0x51,0xa7,0x17,0xb5,0x66,0x06,0x8c,0x85,0x9b,0x7e,0x86,0x06,0x7d,0x74,0x49,0xde,0x4d,0x45,0x11,0xc0,0xac,0xac,0x9c,0xe6,0xe9,0xbf,0x9c,0xcd,0xdf,0x22}, - {0xd9,0x0c,0x0d,0xc3,0xe0,0xd2,0xdb,0x8d,0x33,0x43,0xbb,0xac,0x5f,0x66,0x8e,0xad,0x1f,0x96,0x2a,0x32,0x8c,0x25,0x6b,0x8f,0xc7,0xc1,0x48,0x54,0xc0,0x16,0x29,0x6b,0xa1,0xe0,0x3b,0x10,0xb4,0x59,0xec,0x56,0x69,0xf9,0x59,0xd2,0xec,0xba,0xe3,0x2e,0x32,0xcd,0xf5,0x13,0x94,0xb2,0x7c,0x79,0x72,0xe4,0xcd,0x24,0x78,0x87,0xe9,0x0f,0x3b,0x91,0xba,0x0a,0xd1,0x34,0xdb,0x7e,0x0e,0xac,0x6d,0x2e,0x82,0xcd,0xa3,0x4e,0x15,0xf8,0x78,0x65,0xff,0x3d,0x08,0x66,0x17,0x0a,0xf0,0x7f,0x30,0x3f,0x30,0x4c}, - {0x85,0x8c,0xb2,0x17,0xd6,0x3b,0x0a,0xd3,0xea,0x3b,0x77,0x39,0xb7,0x77,0xd3,0xc5,0xbf,0x5c,0x6a,0x1e,0x8c,0xe7,0xc6,0xc6,0xc4,0xb7,0x2a,0x8b,0xf7,0xb8,0x61,0x0d,0x00,0x45,0xd9,0x0d,0x58,0x03,0xfc,0x29,0x93,0xec,0xbb,0x6f,0xa4,0x7a,0xd2,0xec,0xf8,0xa7,0xe2,0xc2,0x5f,0x15,0x0a,0x13,0xd5,0xa1,0x06,0xb7,0x1a,0x15,0x6b,0x41,0xb0,0x36,0xc1,0xe9,0xef,0xd7,0xa8,0x56,0x20,0x4b,0xe4,0x58,0xcd,0xe5,0x07,0xbd,0xab,0xe0,0x57,0x1b,0xda,0x2f,0xe6,0xaf,0xd2,0xe8,0x77,0x42,0xf7,0x2a,0x1a,0x19}, - {0x31,0x14,0x3c,0xc5,0x4b,0xf7,0x16,0xce,0xde,0xed,0x72,0x20,0xce,0x25,0x97,0x2b,0xe7,0x3e,0xb2,0xb5,0x6f,0xc3,0xb9,0xb8,0x08,0xc9,0x5c,0x0b,0x45,0x0e,0x2e,0x7e,0xfb,0x0e,0x46,0x4f,0x43,0x2b,0xe6,0x9f,0xd6,0x07,0x36,0xa6,0xd4,0x03,0xd3,0xde,0x24,0xda,0xa0,0xb7,0x0e,0x21,0x52,0xf0,0x93,0x5b,0x54,0x00,0xbe,0x7d,0x7e,0x23,0x30,0xb4,0x01,0x67,0xed,0x75,0x35,0x01,0x10,0xfd,0x0b,0x9f,0xe6,0x94,0x10,0x23,0x22,0x7f,0xe4,0x83,0x15,0x0f,0x32,0x75,0xe3,0x55,0x11,0xb1,0x99,0xa6,0xaf,0x71}, - {0x1d,0xb6,0x53,0x39,0x9b,0x6f,0xce,0x65,0xe6,0x41,0xa1,0xaf,0xea,0x39,0x58,0xc6,0xfe,0x59,0xf7,0xa9,0xfd,0x5f,0x43,0x0f,0x8e,0xc2,0xb1,0xc2,0xe9,0x42,0x11,0x02,0xd6,0x50,0x3b,0x47,0x1c,0x3c,0x42,0xea,0x10,0xef,0x38,0x3b,0x1f,0x7a,0xe8,0x51,0x95,0xbe,0xc9,0xb2,0x5f,0xbf,0x84,0x9b,0x1c,0x9a,0xf8,0x78,0xbc,0x1f,0x73,0x00,0x80,0x18,0xf8,0x48,0x18,0xc7,0x30,0xe4,0x19,0xc1,0xce,0x5e,0x22,0x0c,0x96,0xbf,0xe3,0x15,0xba,0x6b,0x83,0xe0,0xda,0xb6,0x08,0x58,0xe1,0x47,0x33,0x6f,0x4d,0x4c}, - {0xc9,0x1f,0x7d,0xc1,0xcf,0xec,0xf7,0x18,0x14,0x3c,0x40,0x51,0xa6,0xf5,0x75,0x6c,0xdf,0x0c,0xee,0xf7,0x2b,0x71,0xde,0xdb,0x22,0x7a,0xe4,0xa7,0xaa,0xdd,0x3f,0x19,0x70,0x19,0x8f,0x98,0xfc,0xdd,0x0c,0x2f,0x1b,0xf5,0xb9,0xb0,0x27,0x62,0x91,0x6b,0xbe,0x76,0x91,0x77,0xc4,0xb6,0xc7,0x6e,0xa8,0x9f,0x8f,0xa8,0x00,0x95,0xbf,0x38,0x6f,0x87,0xe8,0x37,0x3c,0xc9,0xd2,0x1f,0x2c,0x46,0xd1,0x18,0x5a,0x1e,0xf6,0xa2,0x76,0x12,0x24,0x39,0x82,0xf5,0x80,0x50,0x69,0x49,0x0d,0xbf,0x9e,0xb9,0x6f,0x6a}, - {0xeb,0x55,0x08,0x56,0xbb,0xc1,0x46,0x6a,0x9d,0xf0,0x93,0xf8,0x38,0xbb,0x16,0x24,0xc1,0xac,0x71,0x8f,0x37,0x11,0x1d,0xd7,0xea,0x96,0x18,0xa3,0x14,0x69,0xf7,0x75,0xc6,0x23,0xe4,0xb6,0xb5,0x22,0xb1,0xee,0x8e,0xff,0x86,0xf2,0x10,0x70,0x9d,0x93,0x8c,0x5d,0xcf,0x1d,0x83,0x2a,0xa9,0x90,0x10,0xeb,0xc5,0x42,0x9f,0xda,0x6f,0x13,0xd1,0xbd,0x05,0xa3,0xb1,0xdf,0x4c,0xf9,0x08,0x2c,0xf8,0x9f,0x9d,0x4b,0x36,0x0f,0x8a,0x58,0xbb,0xc3,0xa5,0xd8,0x87,0x2a,0xba,0xdc,0xe8,0x0b,0x51,0x83,0x21,0x02}, - {0x14,0x2d,0xad,0x5e,0x38,0x66,0xf7,0x4a,0x30,0x58,0x7c,0xca,0x80,0xd8,0x8e,0xa0,0x3d,0x1e,0x21,0x10,0xe6,0xa6,0x13,0x0d,0x03,0x6c,0x80,0x7b,0xe1,0x1c,0x07,0x6a,0x7f,0x7a,0x30,0x43,0x01,0x71,0x5a,0x9d,0x5f,0xa4,0x7d,0xc4,0x9e,0xde,0x63,0xb0,0xd3,0x7a,0x92,0xbe,0x52,0xfe,0xbb,0x22,0x6c,0x42,0x40,0xfd,0x41,0xc4,0x87,0x13,0xf8,0x8a,0x97,0x87,0xd1,0xc3,0xd3,0xb5,0x13,0x44,0x0e,0x7f,0x3d,0x5a,0x2b,0x72,0xa0,0x7c,0x47,0xbb,0x48,0x48,0x7b,0x0d,0x92,0xdc,0x1e,0xaf,0x6a,0xb2,0x71,0x31}, - {0xa8,0x4c,0x56,0x97,0x90,0x31,0x2f,0xa9,0x19,0xe1,0x75,0x22,0x4c,0xb8,0x7b,0xff,0x50,0x51,0x87,0xa4,0x37,0xfe,0x55,0x4f,0x5a,0x83,0xf0,0x3c,0x87,0xd4,0x1f,0x22,0xd1,0x47,0x8a,0xb2,0xd8,0xb7,0x0d,0xa6,0xf1,0xa4,0x70,0x17,0xd6,0x14,0xbf,0xa6,0x58,0xbd,0xdd,0x53,0x93,0xf8,0xa1,0xd4,0xe9,0x43,0x42,0x34,0x63,0x4a,0x51,0x6c,0x41,0x63,0x15,0x3a,0x4f,0x20,0x22,0x23,0x2d,0x03,0x0a,0xba,0xe9,0xe0,0x73,0xfb,0x0e,0x03,0x0f,0x41,0x4c,0xdd,0xe0,0xfc,0xaa,0x4a,0x92,0xfb,0x96,0xa5,0xda,0x48}, - {0xc7,0x9c,0xa5,0x5c,0x66,0x8e,0xca,0x6e,0xa0,0xac,0x38,0x2e,0x4b,0x25,0x47,0xa8,0xce,0x17,0x1e,0xd2,0x08,0xc7,0xaf,0x31,0xf7,0x4a,0xd8,0xca,0xfc,0xd6,0x6d,0x67,0x93,0x97,0x4c,0xc8,0x5d,0x1d,0xf6,0x14,0x06,0x82,0x41,0xef,0xe3,0xf9,0x41,0x99,0xac,0x77,0x62,0x34,0x8f,0xb8,0xf5,0xcd,0xa9,0x79,0x8a,0x0e,0xfa,0x37,0xc8,0x58,0x58,0x90,0xfc,0x96,0x85,0x68,0xf9,0x0c,0x1b,0xa0,0x56,0x7b,0xf3,0xbb,0xdc,0x1d,0x6a,0xd6,0x35,0x49,0x7d,0xe7,0xc2,0xdc,0x0a,0x7f,0xa5,0xc6,0xf2,0x73,0x4f,0x1c}, - {0xbb,0xa0,0x5f,0x30,0xbd,0x4f,0x7a,0x0e,0xad,0x63,0xc6,0x54,0xe0,0x4c,0x9d,0x82,0x48,0x38,0xe3,0x2f,0x83,0xc3,0x21,0xf4,0x42,0x4c,0xf6,0x1b,0x0d,0xc8,0x5a,0x79,0x84,0x34,0x7c,0xfc,0x6e,0x70,0x6e,0xb3,0x61,0xcf,0xc1,0xc3,0xb4,0xc9,0xdf,0x73,0xe5,0xc7,0x1c,0x78,0xc9,0x79,0x1d,0xeb,0x5c,0x67,0xaf,0x7d,0xdb,0x9a,0x45,0x70,0xb3,0x2b,0xb4,0x91,0x49,0xdb,0x91,0x1b,0xca,0xdc,0x02,0x4b,0x23,0x96,0x26,0x57,0xdc,0x78,0x8c,0x1f,0xe5,0x9e,0xdf,0x9f,0xd3,0x1f,0xe2,0x8c,0x84,0x62,0xe1,0x5f}, - {0x1a,0x96,0x94,0xe1,0x4f,0x21,0x59,0x4e,0x4f,0xcd,0x71,0x0d,0xc7,0x7d,0xbe,0x49,0x2d,0xf2,0x50,0x3b,0xd2,0xcf,0x00,0x93,0x32,0x72,0x91,0xfc,0x46,0xd4,0x89,0x47,0x08,0xb2,0x7c,0x5d,0x2d,0x85,0x79,0x28,0xe7,0xf2,0x7d,0x68,0x70,0xdd,0xde,0xb8,0x91,0x78,0x68,0x21,0xab,0xff,0x0b,0xdc,0x35,0xaa,0x7d,0x67,0x43,0xc0,0x44,0x2b,0x8e,0xb7,0x4e,0x07,0xab,0x87,0x1c,0x1a,0x67,0xf4,0xda,0x99,0x8e,0xd1,0xc6,0xfa,0x67,0x90,0x4f,0x48,0xcd,0xbb,0xac,0x3e,0xe4,0xa4,0xb9,0x2b,0xef,0x2e,0xc5,0x60}, - {0xf1,0x8b,0xfd,0x3b,0xbc,0x89,0x5d,0x0b,0x1a,0x55,0xf3,0xc9,0x37,0x92,0x6b,0xb0,0xf5,0x28,0x30,0xd5,0xb0,0x16,0x4c,0x0e,0xab,0xca,0xcf,0x2c,0x31,0x9c,0xbc,0x10,0x11,0x6d,0xae,0x7c,0xc2,0xc5,0x2b,0x70,0xab,0x8c,0xa4,0x54,0x9b,0x69,0xc7,0x44,0xb2,0x2e,0x49,0xba,0x56,0x40,0xbc,0xef,0x6d,0x67,0xb6,0xd9,0x48,0x72,0xd7,0x70,0x5b,0xa0,0xc2,0x3e,0x4b,0xe8,0x8a,0xaa,0xe0,0x81,0x17,0xed,0xf4,0x9e,0x69,0x98,0xd1,0x85,0x8e,0x70,0xe4,0x13,0x45,0x79,0x13,0xf4,0x76,0xa9,0xd3,0x5b,0x75,0x63}, - {0x53,0x08,0xd1,0x2a,0x3e,0xa0,0x5f,0xb5,0x69,0x35,0xe6,0x9e,0x90,0x75,0x6f,0x35,0x90,0xb8,0x69,0xbe,0xfd,0xf1,0xf9,0x9f,0x84,0x6f,0xc1,0x8b,0xc4,0xc1,0x8c,0x0d,0xb7,0xac,0xf1,0x97,0x18,0x10,0xc7,0x3d,0xd8,0xbb,0x65,0xc1,0x5e,0x7d,0xda,0x5d,0x0f,0x02,0xa1,0x0f,0x9c,0x5b,0x8e,0x50,0x56,0x2a,0xc5,0x37,0x17,0x75,0x63,0x27,0xa9,0x19,0xb4,0x6e,0xd3,0x02,0x94,0x02,0xa5,0x60,0xb4,0x77,0x7e,0x4e,0xb4,0xf0,0x56,0x49,0x3c,0xd4,0x30,0x62,0xa8,0xcf,0xe7,0x66,0xd1,0x7a,0x8a,0xdd,0xc2,0x70}, - {0x0e,0xec,0x6f,0x9f,0x50,0x94,0x61,0x65,0x8d,0x51,0xc6,0x46,0xa9,0x7e,0x2e,0xee,0x5c,0x9b,0xe0,0x67,0xf3,0xc1,0x33,0x97,0x95,0x84,0x94,0x63,0x63,0xac,0x0f,0x2e,0x13,0x7e,0xed,0xb8,0x7d,0x96,0xd4,0x91,0x7a,0x81,0x76,0xd7,0x0a,0x2f,0x25,0x74,0x64,0x25,0x85,0x0d,0xe0,0x82,0x09,0xe4,0xe5,0x3c,0xa5,0x16,0x38,0x61,0xb8,0x32,0x64,0xcd,0x48,0xe4,0xbe,0xf7,0xe7,0x79,0xd0,0x86,0x78,0x08,0x67,0x3a,0xc8,0x6a,0x2e,0xdb,0xe4,0xa0,0xd9,0xd4,0x9f,0xf8,0x41,0x4f,0x5a,0x73,0x5c,0x21,0x79,0x41}, - {0x2a,0xed,0xdc,0xd7,0xe7,0x94,0x70,0x8c,0x70,0x9c,0xd3,0x47,0xc3,0x8a,0xfb,0x97,0x02,0xd9,0x06,0xa9,0x33,0xe0,0x3b,0xe1,0x76,0x9d,0xd9,0x0c,0xa3,0x44,0x03,0x70,0x34,0xcd,0x6b,0x28,0xb9,0x33,0xae,0xe4,0xdc,0xd6,0x9d,0x55,0xb6,0x7e,0xef,0xb7,0x1f,0x8e,0xd3,0xb3,0x1f,0x14,0x8b,0x27,0x86,0xc2,0x41,0x22,0x66,0x85,0xfa,0x31,0xf4,0x22,0x36,0x2e,0x42,0x6c,0x82,0xaf,0x2d,0x50,0x33,0x98,0x87,0x29,0x20,0xc1,0x23,0x91,0x38,0x2b,0xe1,0xb7,0xc1,0x9b,0x89,0x24,0x95,0xa9,0x12,0x23,0xbb,0x24}, - {0xc3,0x67,0xde,0x32,0x17,0xed,0xa8,0xb1,0x48,0x49,0x1b,0x46,0x18,0x94,0xb4,0x3c,0xd2,0xbc,0xcf,0x76,0x43,0x43,0xbd,0x8e,0x08,0x80,0x18,0x1e,0x87,0x3e,0xee,0x0f,0x6b,0x5c,0xf8,0xf5,0x2a,0x0c,0xf8,0x41,0x94,0x67,0xfa,0x04,0xc3,0x84,0x72,0x68,0xad,0x1b,0xba,0xa3,0x99,0xdf,0x45,0x89,0x16,0x5d,0xeb,0xff,0xf9,0x2a,0x1d,0x0d,0xdf,0x1e,0x62,0x32,0xa1,0x8a,0xda,0xa9,0x79,0x65,0x22,0x59,0xa1,0x22,0xb8,0x30,0x93,0xc1,0x9a,0xa7,0x7b,0x19,0x04,0x40,0x76,0x1d,0x53,0x18,0x97,0xd7,0xac,0x16}, - {0x3d,0x1d,0x9b,0x2d,0xaf,0x72,0xdf,0x72,0x5a,0x24,0x32,0xa4,0x36,0x2a,0x46,0x63,0x37,0x96,0xb3,0x16,0x79,0xa0,0xce,0x3e,0x09,0x23,0x30,0xb9,0xf6,0x0e,0x3e,0x12,0xad,0xb6,0x87,0x78,0xc5,0xc6,0x59,0xc9,0xba,0xfe,0x90,0x5f,0xad,0x9e,0xe1,0x94,0x04,0xf5,0x42,0xa3,0x62,0x4e,0xe2,0x16,0x00,0x17,0x16,0x18,0x4b,0xd3,0x4e,0x16,0x9a,0xe6,0x2f,0x19,0x4c,0xd9,0x7e,0x48,0x13,0x15,0x91,0x3a,0xea,0x2c,0xae,0x61,0x27,0xde,0xa4,0xb9,0xd3,0xf6,0x7b,0x87,0xeb,0xf3,0x73,0x10,0xc6,0x0f,0xda,0x78}, - {0x6a,0xc6,0x2b,0xe5,0x28,0x5d,0xf1,0x5b,0x8e,0x1a,0xf0,0x70,0x18,0xe3,0x47,0x2c,0xdd,0x8b,0xc2,0x06,0xbc,0xaf,0x19,0x24,0x3a,0x17,0x6b,0x25,0xeb,0xde,0x25,0x2d,0x94,0x3a,0x0c,0x68,0xf1,0x80,0x9f,0xa2,0xe6,0xe7,0xe9,0x1a,0x15,0x7e,0xf7,0x71,0x73,0x79,0x01,0x48,0x58,0xf1,0x00,0x11,0xdd,0x8d,0xb3,0x16,0xb3,0xa4,0x4a,0x05,0xb8,0x7c,0x26,0x19,0x8d,0x46,0xc8,0xdf,0xaf,0x4d,0xe5,0x66,0x9c,0x78,0x28,0x0b,0x17,0xec,0x6e,0x66,0x2a,0x1d,0xeb,0x2a,0x60,0xa7,0x7d,0xab,0xa6,0x10,0x46,0x13}, - {0xfe,0xb0,0xf6,0x8d,0xc7,0x8e,0x13,0x51,0x1b,0xf5,0x75,0xe5,0x89,0xda,0x97,0x53,0xb9,0xf1,0x7a,0x71,0x1d,0x7a,0x20,0x09,0x50,0xd6,0x20,0x2b,0xba,0xfd,0x02,0x21,0x15,0xf5,0xd1,0x77,0xe7,0x65,0x2a,0xcd,0xf1,0x60,0xaa,0x8f,0x87,0x91,0x89,0x54,0xe5,0x06,0xbc,0xda,0xbc,0x3b,0xb7,0xb1,0xfb,0xc9,0x7c,0xa9,0xcb,0x78,0x48,0x65,0xa1,0xe6,0x5c,0x05,0x05,0xe4,0x9e,0x96,0x29,0xad,0x51,0x12,0x68,0xa7,0xbc,0x36,0x15,0xa4,0x7d,0xaa,0x17,0xf5,0x1a,0x3a,0xba,0xb2,0xec,0x29,0xdb,0x25,0xd7,0x0a}, - {0x57,0x24,0x4e,0x83,0xb1,0x67,0x42,0xdc,0xc5,0x1b,0xce,0x70,0xb5,0x44,0x75,0xb6,0xd7,0x5e,0xd1,0xf7,0x0b,0x7a,0xf0,0x1a,0x50,0x36,0xa0,0x71,0xfb,0xcf,0xef,0x4a,0x85,0x6f,0x05,0x9b,0x0c,0xbc,0xc7,0xfe,0xd7,0xff,0xf5,0xe7,0x68,0x52,0x7d,0x53,0xfa,0xae,0x12,0x43,0x62,0xc6,0xaf,0x77,0xd9,0x9f,0x39,0x02,0x53,0x5f,0x67,0x4f,0x1e,0x17,0x15,0x04,0x36,0x36,0x2d,0xc3,0x3b,0x48,0x98,0x89,0x11,0xef,0x2b,0xcd,0x10,0x51,0x94,0xd0,0xad,0x6e,0x0a,0x87,0x61,0x65,0xa8,0xa2,0x72,0xbb,0xcc,0x0b}, - {0xc8,0xa9,0xb1,0xea,0x2f,0x96,0x5e,0x18,0xcd,0x7d,0x14,0x65,0x35,0xe6,0xe7,0x86,0xf2,0x6d,0x5b,0xbb,0x31,0xe0,0x92,0xb0,0x3e,0xb7,0xd6,0x59,0xab,0xf0,0x24,0x40,0x96,0x12,0xfe,0x50,0x4c,0x5e,0x6d,0x18,0x7e,0x9f,0xe8,0xfe,0x82,0x7b,0x39,0xe0,0xb0,0x31,0x70,0x50,0xc5,0xf6,0xc7,0x3b,0xc2,0x37,0x8f,0x10,0x69,0xfd,0x78,0x66,0xc2,0x63,0x68,0x63,0x31,0xfa,0x86,0x15,0xf2,0x33,0x2d,0x57,0x48,0x8c,0xf6,0x07,0xfc,0xae,0x9e,0x78,0x9f,0xcc,0x73,0x4f,0x01,0x47,0xad,0x8e,0x10,0xe2,0x42,0x2d}, - {0x9b,0xd2,0xdf,0x94,0x15,0x13,0xf5,0x97,0x6a,0x4c,0x3f,0x31,0x5d,0x98,0x55,0x61,0x10,0x50,0x45,0x08,0x07,0x3f,0xa1,0xeb,0x22,0xd3,0xd2,0xb8,0x08,0x26,0x6b,0x67,0x93,0x75,0x53,0x0f,0x0d,0x7b,0x71,0x21,0x4c,0x06,0x1e,0x13,0x0b,0x69,0x4e,0x91,0x9f,0xe0,0x2a,0x75,0xae,0x87,0xb6,0x1b,0x6e,0x3c,0x42,0x9b,0xa7,0xf3,0x0b,0x42,0x47,0x2b,0x5b,0x1c,0x65,0xba,0x38,0x81,0x80,0x1b,0x1b,0x31,0xec,0xb6,0x71,0x86,0xb0,0x35,0x31,0xbc,0xb1,0x0c,0xff,0x7b,0xe0,0xf1,0x0c,0x9c,0xfa,0x2f,0x5d,0x74}, - {0xbd,0xc8,0xc9,0x2b,0x1e,0x5a,0x52,0xbf,0x81,0x9d,0x47,0x26,0x08,0x26,0x5b,0xea,0xdb,0x55,0x01,0xdf,0x0e,0xc7,0x11,0xd5,0xd0,0xf5,0x0c,0x96,0xeb,0x3c,0xe2,0x1a,0x6a,0x4e,0xd3,0x21,0x57,0xdf,0x36,0x60,0xd0,0xb3,0x7b,0x99,0x27,0x88,0xdb,0xb1,0xfa,0x6a,0x75,0xc8,0xc3,0x09,0xc2,0xd3,0x39,0xc8,0x1d,0x4c,0xe5,0x5b,0xe1,0x06,0x4a,0x99,0x32,0x19,0x87,0x5d,0x72,0x5b,0xb0,0xda,0xb1,0xce,0xb5,0x1c,0x35,0x32,0x05,0xca,0xb7,0xda,0x49,0x15,0xc4,0x7d,0xf7,0xc1,0x8e,0x27,0x61,0xd8,0xde,0x58}, - {0x5c,0xc5,0x66,0xf2,0x93,0x37,0x17,0xd8,0x49,0x4e,0x45,0xcc,0xc5,0x76,0xc9,0xc8,0xa8,0xc3,0x26,0xbc,0xf8,0x82,0xe3,0x5c,0xf9,0xf6,0x85,0x54,0xe8,0x9d,0xf3,0x2f,0xa8,0xc9,0xc2,0xb6,0xa8,0x5b,0xfb,0x2d,0x8c,0x59,0x2c,0xf5,0x8e,0xef,0xee,0x48,0x73,0x15,0x2d,0xf1,0x07,0x91,0x80,0x33,0xd8,0x5b,0x1d,0x53,0x6b,0x69,0xba,0x08,0x7a,0xc5,0xef,0xc3,0xee,0x3e,0xed,0x77,0x11,0x48,0xff,0xd4,0x17,0x55,0xe0,0x04,0xcb,0x71,0xa6,0xf1,0x3f,0x7a,0x3d,0xea,0x54,0xfe,0x7c,0x94,0xb4,0x33,0x06,0x12}, - {0x42,0x00,0x61,0x91,0x78,0x98,0x94,0x0b,0xe8,0xfa,0xeb,0xec,0x3c,0xb1,0xe7,0x4e,0xc0,0xa4,0xf0,0x94,0x95,0x73,0xbe,0x70,0x85,0x91,0xd5,0xb4,0x99,0x0a,0xd3,0x35,0x0a,0x10,0x12,0x49,0x47,0x31,0xbd,0x82,0x06,0xbe,0x6f,0x7e,0x6d,0x7b,0x23,0xde,0xc6,0x79,0xea,0x11,0x19,0x76,0x1e,0xe1,0xde,0x3b,0x39,0xcb,0xe3,0x3b,0x43,0x07,0xf4,0x97,0xe9,0x5c,0xc0,0x44,0x79,0xff,0xa3,0x51,0x5c,0xb0,0xe4,0x3d,0x5d,0x57,0x7c,0x84,0x76,0x5a,0xfd,0x81,0x33,0x58,0x9f,0xda,0xf6,0x7a,0xde,0x3e,0x87,0x2d}, - {0x09,0x34,0x37,0x43,0x64,0x31,0x7a,0x15,0xd9,0x81,0xaa,0xf4,0xee,0xb7,0xb8,0xfa,0x06,0x48,0xa6,0xf5,0xe6,0xfe,0x93,0xb0,0xb6,0xa7,0x7f,0x70,0x54,0x36,0x77,0x2e,0x81,0xf9,0x5d,0x4e,0xe1,0x02,0x62,0xaa,0xf5,0xe1,0x15,0x50,0x17,0x59,0x0d,0xa2,0x6c,0x1d,0xe2,0xba,0xd3,0x75,0xa2,0x18,0x53,0x02,0x60,0x01,0x8a,0x61,0x43,0x05,0xc1,0x23,0x4c,0x97,0xf4,0xbd,0xea,0x0d,0x93,0x46,0xce,0x9d,0x25,0x0a,0x6f,0xaa,0x2c,0xba,0x9a,0xa2,0xb8,0x2c,0x20,0x04,0x0d,0x96,0x07,0x2d,0x36,0x43,0x14,0x4b}, - {0x7a,0x1f,0x6e,0xb6,0xc7,0xb7,0xc4,0xcc,0x7e,0x2f,0x0c,0xf5,0x25,0x7e,0x15,0x44,0x1c,0xaf,0x3e,0x71,0xfc,0x6d,0xf0,0x3e,0xf7,0x63,0xda,0x52,0x67,0x44,0x2f,0x58,0xcb,0x9c,0x52,0x1c,0xe9,0x54,0x7c,0x96,0xfb,0x35,0xc6,0x64,0x92,0x26,0xf6,0x30,0x65,0x19,0x12,0x78,0xf4,0xaf,0x47,0x27,0x5c,0x6f,0xf6,0xea,0x18,0x84,0x03,0x17,0xe4,0x4c,0x32,0x20,0xd3,0x7b,0x31,0xc6,0xc4,0x8b,0x48,0xa4,0xe8,0x42,0x10,0xa8,0x64,0x13,0x5a,0x4e,0x8b,0xf1,0x1e,0xb2,0xc9,0x8d,0xa2,0xcd,0x4b,0x1c,0x2a,0x0c}, - {0x47,0x04,0x1f,0x6f,0xd0,0xc7,0x4d,0xd2,0x59,0xc0,0x87,0xdb,0x3e,0x9e,0x26,0xb2,0x8f,0xd2,0xb2,0xfb,0x72,0x02,0x5b,0xd1,0x77,0x48,0xf6,0xc6,0xd1,0x8b,0x55,0x7c,0x45,0x69,0xbd,0x69,0x48,0x81,0xc4,0xed,0x22,0x8d,0x1c,0xbe,0x7d,0x90,0x6d,0x0d,0xab,0xc5,0x5c,0xd5,0x12,0xd2,0x3b,0xc6,0x83,0xdc,0x14,0xa3,0x30,0x9b,0x6a,0x5a,0x3d,0x46,0x96,0xd3,0x24,0x15,0xec,0xd0,0xf0,0x24,0x5a,0xc3,0x8a,0x62,0xbb,0x12,0xa4,0x5f,0xbc,0x1c,0x79,0x3a,0x0c,0xa5,0xc3,0xaf,0xfb,0x0a,0xca,0xa5,0x04,0x04}, - {0xd6,0x43,0xa7,0x0a,0x07,0x40,0x1f,0x8c,0xe8,0x5e,0x26,0x5b,0xcb,0xd0,0xba,0xcc,0xde,0xd2,0x8f,0x66,0x6b,0x04,0x4b,0x57,0x33,0x96,0xdd,0xca,0xfd,0x5b,0x39,0x46,0xd1,0x6f,0x41,0x2a,0x1b,0x9e,0xbc,0x62,0x8b,0x59,0x50,0xe3,0x28,0xf7,0xc6,0xb5,0x67,0x69,0x5d,0x3d,0xd8,0x3f,0x34,0x04,0x98,0xee,0xf8,0xe7,0x16,0x75,0x52,0x39,0x9c,0x9a,0x5d,0x1a,0x2d,0xdb,0x7f,0x11,0x2a,0x5c,0x00,0xd1,0xbc,0x45,0x77,0x9c,0xea,0x6f,0xd5,0x54,0xf1,0xbe,0xd4,0xef,0x16,0xd0,0x22,0xe8,0x29,0x9a,0x57,0x76}, - {0x17,0x2a,0xc0,0x49,0x7e,0x8e,0xb6,0x45,0x7f,0xa3,0xa9,0xbc,0xa2,0x51,0xcd,0x23,0x1b,0x4c,0x22,0xec,0x11,0x5f,0xd6,0x3e,0xb1,0xbd,0x05,0x9e,0xdc,0x84,0xa3,0x43,0xf2,0x34,0xb4,0x52,0x13,0xb5,0x3c,0x33,0xe1,0x80,0xde,0x93,0x49,0x28,0x32,0xd8,0xce,0x35,0x0d,0x75,0x87,0x28,0x51,0xb5,0xc1,0x77,0x27,0x2a,0xbb,0x14,0xc5,0x02,0x45,0xb6,0xf1,0x8b,0xda,0xd5,0x4b,0x68,0x53,0x4b,0xb5,0xf6,0x7e,0xd3,0x8b,0xfb,0x53,0xd2,0xb0,0xa9,0xd7,0x16,0x39,0x31,0x59,0x80,0x54,0x61,0x09,0x92,0x60,0x11}, - {0xaa,0xcf,0xda,0x29,0x69,0x16,0x4d,0xb4,0x8f,0x59,0x13,0x84,0x4c,0x9f,0x52,0xda,0x59,0x55,0x3d,0x45,0xca,0x63,0xef,0xe9,0x0b,0x8e,0x69,0xc5,0x5b,0x12,0x1e,0x35,0xcd,0x4d,0x9b,0x36,0x16,0x56,0x38,0x7a,0x63,0x35,0x5c,0x65,0xa7,0x2c,0xc0,0x75,0x21,0x80,0xf1,0xd4,0xf9,0x1b,0xc2,0x7d,0x42,0xe0,0xe6,0x91,0x74,0x7d,0x63,0x2f,0xbe,0x7b,0xf6,0x1a,0x46,0x9b,0xb4,0xd4,0x61,0x89,0xab,0xc8,0x7a,0x03,0x03,0xd6,0xfb,0x99,0xa6,0xf9,0x9f,0xe1,0xde,0x71,0x9a,0x2a,0xce,0xe7,0x06,0x2d,0x18,0x7f}, - {0xec,0x68,0x01,0xab,0x64,0x8e,0x7c,0x7a,0x43,0xc5,0xed,0x15,0x55,0x4a,0x5a,0xcb,0xda,0x0e,0xcd,0x47,0xd3,0x19,0x55,0x09,0xb0,0x93,0x3e,0x34,0x8c,0xac,0xd4,0x67,0x22,0x75,0x21,0x8e,0x72,0x4b,0x45,0x09,0xd8,0xb8,0x84,0xd4,0xf4,0xe8,0x58,0xaa,0x3c,0x90,0x46,0x7f,0x4d,0x25,0x58,0xd3,0x17,0x52,0x1c,0x24,0x43,0xc0,0xac,0x44,0x77,0x57,0x7a,0x4f,0xbb,0x6b,0x7d,0x1c,0xe1,0x13,0x83,0x91,0xd4,0xfe,0x35,0x8b,0x84,0x46,0x6b,0xc9,0xc6,0xa1,0xdc,0x4a,0xbd,0x71,0xad,0x12,0x83,0x1c,0x6d,0x55}, - {0x82,0x39,0x8d,0x0c,0xe3,0x40,0xef,0x17,0x34,0xfa,0xa3,0x15,0x3e,0x07,0xf7,0x31,0x6e,0x64,0x73,0x07,0xcb,0xf3,0x21,0x4f,0xff,0x4e,0x82,0x1d,0x6d,0x6c,0x6c,0x74,0x21,0xe8,0x1b,0xb1,0x56,0x67,0xf0,0x81,0xdd,0xf3,0xa3,0x10,0x23,0xf8,0xaf,0x0f,0x5d,0x46,0x99,0x6a,0x55,0xd0,0xb2,0xf8,0x05,0x7f,0x8c,0xcc,0x38,0xbe,0x7a,0x09,0xa4,0x2d,0xa5,0x7e,0x87,0xc9,0x49,0x0c,0x43,0x1d,0xdc,0x9b,0x55,0x69,0x43,0x4c,0xd2,0xeb,0xcc,0xf7,0x09,0x38,0x2c,0x02,0xbd,0x84,0xee,0x4b,0xa3,0x14,0x7e,0x57}, - {0x0a,0x3b,0xa7,0x61,0xac,0x68,0xe2,0xf0,0xf5,0xa5,0x91,0x37,0x10,0xfa,0xfa,0xf2,0xe9,0x00,0x6d,0x6b,0x82,0x3e,0xe1,0xc1,0x42,0x8f,0xd7,0x6f,0xe9,0x7e,0xfa,0x60,0x2b,0xd7,0x4d,0xbd,0xbe,0xce,0xfe,0x94,0x11,0x22,0x0f,0x06,0xda,0x4f,0x6a,0xf4,0xff,0xd1,0xc8,0xc0,0x77,0x59,0x4a,0x12,0x95,0x92,0x00,0xfb,0xb8,0x04,0x53,0x70,0xc6,0x6e,0x29,0x4d,0x35,0x1d,0x3d,0xb6,0xd8,0x31,0xad,0x5f,0x3e,0x05,0xc3,0xf3,0xec,0x42,0xbd,0xb4,0x8c,0x95,0x0b,0x67,0xfd,0x53,0x63,0xa1,0x0c,0x8e,0x39,0x21}, - {0xf3,0x33,0x2b,0x38,0x8a,0x05,0xf5,0x89,0xb4,0xc0,0x48,0xad,0x0b,0xba,0xe2,0x5a,0x6e,0xb3,0x3d,0xa5,0x03,0xb5,0x93,0x8f,0xe6,0x32,0xa2,0x95,0x9d,0xed,0xa3,0x5a,0x01,0x56,0xb7,0xb4,0xf9,0xaa,0x98,0x27,0x72,0xad,0x8d,0x5c,0x13,0x72,0xac,0x5e,0x23,0xa0,0xb7,0x61,0x61,0xaa,0xce,0xd2,0x4e,0x7d,0x8f,0xe9,0x84,0xb2,0xbf,0x1b,0x61,0x65,0xd9,0xc7,0xe9,0x77,0x67,0x65,0x36,0x80,0xc7,0x72,0x54,0x12,0x2b,0xcb,0xee,0x6e,0x50,0xd9,0x99,0x32,0x05,0x65,0xcc,0x57,0x89,0x5e,0x4e,0xe1,0x07,0x4a}, - {0x99,0xf9,0x0d,0x98,0xcb,0x12,0xe4,0x4e,0x71,0xc7,0x6e,0x3c,0x6f,0xd7,0x15,0xa3,0xfd,0x77,0x5c,0x92,0xde,0xed,0xa5,0xbb,0x02,0x34,0x31,0x1d,0x39,0xac,0x0b,0x3f,0x9b,0xa4,0x77,0xc4,0xcd,0x58,0x0b,0x24,0x17,0xf0,0x47,0x64,0xde,0xda,0x38,0xfd,0xad,0x6a,0xc8,0xa7,0x32,0x8d,0x92,0x19,0x81,0xa0,0xaf,0x84,0xed,0x7a,0xaf,0x50,0xe5,0x5b,0xf6,0x15,0x01,0xde,0x4f,0x6e,0xb2,0x09,0x61,0x21,0x21,0x26,0x98,0x29,0xd9,0xd6,0xad,0x0b,0x81,0x05,0x02,0x78,0x06,0xd0,0xeb,0xba,0x16,0xa3,0x21,0x19}, - {0xfc,0x70,0xb8,0xdf,0x7e,0x2f,0x42,0x89,0xbd,0xb3,0x76,0x4f,0xeb,0x6b,0x29,0x2c,0xf7,0x4d,0xc2,0x36,0xd4,0xf1,0x38,0x07,0xb0,0xae,0x73,0xe2,0x41,0xdf,0x58,0x64,0x8b,0xc1,0xf3,0xd9,0x9a,0xad,0x5a,0xd7,0x9c,0xc1,0xb1,0x60,0xef,0x0e,0x6a,0x56,0xd9,0x0e,0x5c,0x25,0xac,0x0b,0x9a,0x3e,0xf5,0xc7,0x62,0xa0,0xec,0x9d,0x04,0x7b,0x83,0x44,0x44,0x35,0x7a,0xe3,0xcb,0xdc,0x93,0xbe,0xed,0x0f,0x33,0x79,0x88,0x75,0x87,0xdd,0xc5,0x12,0xc3,0x04,0x60,0x78,0x64,0x0e,0x95,0xc2,0xcb,0xdc,0x93,0x60}, - {0x6d,0x70,0xe0,0x85,0x85,0x9a,0xf3,0x1f,0x33,0x39,0xe7,0xb3,0xd8,0xa5,0xd0,0x36,0x3b,0x45,0x8f,0x71,0xe1,0xf2,0xb9,0x43,0x7c,0xa9,0x27,0x48,0x08,0xea,0xd1,0x57,0x4b,0x03,0x84,0x60,0xbe,0xee,0xde,0x6b,0x54,0xb8,0x0f,0x78,0xb6,0xc2,0x99,0x31,0x95,0x06,0x2d,0xb6,0xab,0x76,0x33,0x97,0x90,0x7d,0x64,0x8b,0xc9,0x80,0x31,0x6e,0x71,0xb0,0x28,0xa1,0xe7,0xb6,0x7a,0xee,0xaa,0x8b,0xa8,0x93,0x6d,0x59,0xc1,0xa4,0x30,0x61,0x21,0xb2,0x82,0xde,0xb4,0xf7,0x18,0xbd,0x97,0xdd,0x9d,0x99,0x3e,0x36}, - {0xc4,0x1f,0xee,0x35,0xc1,0x43,0xa8,0x96,0xcf,0xc8,0xe4,0x08,0x55,0xb3,0x6e,0x97,0x30,0xd3,0x8c,0xb5,0x01,0x68,0x2f,0xb4,0x2b,0x05,0x3a,0x69,0x78,0x9b,0xee,0x48,0xc6,0xae,0x4b,0xe2,0xdc,0x48,0x18,0x2f,0x60,0xaf,0xbc,0xba,0x55,0x72,0x9b,0x76,0x31,0xe9,0xef,0x3c,0x6e,0x3c,0xcb,0x90,0x55,0xb3,0xf9,0xc6,0x9b,0x97,0x1f,0x23,0xc6,0xf3,0x2a,0xcc,0x4b,0xde,0x31,0x5c,0x1f,0x8d,0x20,0xfe,0x30,0xb0,0x4b,0xb0,0x66,0xb4,0x4f,0xc1,0x09,0x70,0x8d,0xb7,0x13,0x24,0x79,0x08,0x9b,0xfa,0x9b,0x07}, - {0xf4,0x0d,0x30,0xda,0x51,0x3a,0x90,0xe3,0xb0,0x5a,0xa9,0x3d,0x23,0x64,0x39,0x84,0x80,0x64,0x35,0x0b,0x2d,0xf1,0x3c,0xed,0x94,0x71,0x81,0x84,0xf6,0x77,0x8c,0x03,0x45,0x42,0xd5,0xa2,0x80,0xed,0xc9,0xf3,0x52,0x39,0xf6,0x77,0x78,0x8b,0xa0,0x0a,0x75,0x54,0x08,0xd1,0x63,0xac,0x6d,0xd7,0x6b,0x63,0x70,0x94,0x15,0xfb,0xf4,0x1e,0xec,0x7b,0x16,0x5b,0xe6,0x5e,0x4e,0x85,0xc2,0xcd,0xd0,0x96,0x42,0x0a,0x59,0x59,0x99,0x21,0x10,0x98,0x34,0xdf,0xb2,0x72,0x56,0xff,0x0b,0x4a,0x2a,0xe9,0x5e,0x57}, - {0xcf,0x2f,0x18,0x8a,0x90,0x80,0xc0,0xd4,0xbd,0x9d,0x48,0x99,0xc2,0x70,0xe1,0x30,0xde,0x33,0xf7,0x52,0x57,0xbd,0xba,0x05,0x00,0xfd,0xd3,0x2c,0x11,0xe7,0xd4,0x43,0x01,0xd8,0xa4,0x0a,0x45,0xbc,0x46,0x5d,0xd8,0xb9,0x33,0xa5,0x27,0x12,0xaf,0xc3,0xc2,0x06,0x89,0x2b,0x26,0x3b,0x9e,0x38,0x1b,0x58,0x2f,0x38,0x7e,0x1e,0x0a,0x20,0xc5,0x3a,0xf9,0xea,0x67,0xb9,0x8d,0x51,0xc0,0x52,0x66,0x05,0x9b,0x98,0xbc,0x71,0xf5,0x97,0x71,0x56,0xd9,0x85,0x2b,0xfe,0x38,0x4e,0x1e,0x65,0x52,0xca,0x0e,0x05}, - {0x9c,0x0c,0x3f,0x45,0xde,0x1a,0x43,0xc3,0x9b,0x3b,0x70,0xff,0x5e,0x04,0xf5,0xe9,0x3d,0x7b,0x84,0xed,0xc9,0x7a,0xd9,0xfc,0xc6,0xf4,0x58,0x1c,0xc2,0xe6,0x0e,0x4b,0xea,0x68,0xe6,0x60,0x76,0x39,0xac,0x97,0x97,0xb4,0x3a,0x15,0xfe,0xbb,0x19,0x9b,0x9f,0xa7,0xec,0x34,0xb5,0x79,0xb1,0x4c,0x57,0xae,0x31,0xa1,0x9f,0xc0,0x51,0x61,0x96,0x5d,0xf0,0xfd,0x0d,0x5c,0xf5,0x3a,0x7a,0xee,0xb4,0x2a,0xe0,0x2e,0x26,0xdd,0x09,0x17,0x17,0x12,0x87,0xbb,0xb2,0x11,0x0b,0x03,0x0f,0x80,0xfa,0x24,0xef,0x1f}, - {0x96,0x31,0xa7,0x1a,0xfb,0x53,0xd6,0x37,0x18,0x64,0xd7,0x3f,0x30,0x95,0x94,0x0f,0xb2,0x17,0x3a,0xfb,0x09,0x0b,0x20,0xad,0x3e,0x61,0xc8,0x2f,0x29,0x49,0x4d,0x54,0x86,0x6b,0x97,0x30,0xf5,0xaf,0xd2,0x22,0x04,0x46,0xd2,0xc2,0x06,0xb8,0x90,0x8d,0xe5,0xba,0xe5,0x4d,0x6c,0x89,0xa1,0xdc,0x17,0x0c,0x34,0xc8,0xe6,0x5f,0x00,0x28,0x88,0x86,0x52,0x34,0x9f,0xba,0xef,0x6a,0xa1,0x7d,0x10,0x25,0x94,0xff,0x1b,0x5c,0x36,0x4b,0xd9,0x66,0xcd,0xbb,0x5b,0xf7,0xfa,0x6d,0x31,0x0f,0x93,0x72,0xe4,0x72}, - {0x4f,0x08,0x81,0x97,0x8c,0x20,0x95,0x26,0xe1,0x0e,0x45,0x23,0x0b,0x2a,0x50,0xb1,0x02,0xde,0xef,0x03,0xa6,0xae,0x9d,0xfd,0x4c,0xa3,0x33,0x27,0x8c,0x2e,0x9d,0x5a,0x27,0x76,0x2a,0xd3,0x35,0xf6,0xf3,0x07,0xf0,0x66,0x65,0x5f,0x86,0x4d,0xaa,0x7a,0x50,0x44,0xd0,0x28,0x97,0xe7,0x85,0x3c,0x38,0x64,0xe0,0x0f,0x00,0x7f,0xee,0x1f,0xe5,0xf7,0xdb,0x03,0xda,0x05,0x53,0x76,0xbd,0xcd,0x34,0x14,0x49,0xf2,0xda,0xa4,0xec,0x88,0x4a,0xd2,0xcd,0xd5,0x4a,0x7b,0x43,0x05,0x04,0xee,0x51,0x40,0xf9,0x00}, - {0xb2,0x30,0xd3,0xc3,0x23,0x6b,0x35,0x8d,0x06,0x1b,0x47,0xb0,0x9b,0x8b,0x1c,0xf2,0x3c,0xb8,0x42,0x6e,0x6c,0x31,0x6c,0xb3,0x0d,0xb1,0xea,0x8b,0x7e,0x9c,0xd7,0x07,0x53,0x97,0xaf,0x07,0xbb,0x93,0xef,0xd7,0xa7,0x66,0xb7,0x3d,0xcf,0xd0,0x3e,0x58,0xc5,0x1e,0x0b,0x6e,0xbf,0x98,0x69,0xce,0x52,0x04,0xd4,0x5d,0xd2,0xff,0xb7,0x47,0x12,0xdd,0x08,0xbc,0x9c,0xfb,0xfb,0x87,0x9b,0xc2,0xee,0xe1,0x3a,0x6b,0x06,0x8a,0xbf,0xc1,0x1f,0xdb,0x2b,0x24,0x57,0x0d,0xb6,0x4b,0xa6,0x5e,0xa3,0x20,0x35,0x1c}, - {0x4a,0xa3,0xcb,0xbc,0xa6,0x53,0xd2,0x80,0x9b,0x21,0x38,0x38,0xa1,0xc3,0x61,0x3e,0x96,0xe3,0x82,0x98,0x01,0xb6,0xc3,0x90,0x6f,0xe6,0x0e,0x5d,0x77,0x05,0x3d,0x1c,0x59,0xc0,0x6b,0x21,0x40,0x6f,0xa8,0xcd,0x7e,0xd8,0xbc,0x12,0x1d,0x23,0xbb,0x1f,0x90,0x09,0xc7,0x17,0x9e,0x6a,0x95,0xb4,0x55,0x2e,0xd1,0x66,0x3b,0x0c,0x75,0x38,0x1a,0xe5,0x22,0x94,0x40,0xf1,0x2e,0x69,0x71,0xf6,0x5d,0x2b,0x3c,0xc7,0xc0,0xcb,0x29,0xe0,0x4c,0x74,0xe7,0x4f,0x01,0x21,0x7c,0x48,0x30,0xd3,0xc7,0xe2,0x21,0x06}, - {0x8d,0x83,0x59,0x82,0xcc,0x60,0x98,0xaf,0xdc,0x9a,0x9f,0xc6,0xc1,0x48,0xea,0x90,0x30,0x1e,0x58,0x65,0x37,0x48,0x26,0x65,0xbc,0xa5,0xd3,0x7b,0x09,0xd6,0x07,0x00,0xf3,0xf0,0xdb,0xb0,0x96,0x17,0xae,0xb7,0x96,0xe1,0x7c,0xe1,0xb9,0xaf,0xdf,0x54,0xb4,0xa3,0xaa,0xe9,0x71,0x30,0x92,0x25,0x9d,0x2e,0x00,0xa1,0x9c,0x58,0x8e,0x5d,0x4b,0xa9,0x42,0x08,0x95,0x1d,0xbf,0xc0,0x3e,0x2e,0x8f,0x58,0x63,0xc3,0xd3,0xb2,0xef,0xe2,0x51,0xbb,0x38,0x14,0x96,0x0a,0x86,0xbf,0x1c,0x3c,0x78,0xd7,0x83,0x15}, - {0xe1,0x7a,0xa2,0x5d,0xef,0xa2,0xee,0xec,0x74,0x01,0x67,0x55,0x14,0x3a,0x7c,0x59,0x7a,0x16,0x09,0x66,0x12,0x2a,0xa6,0xc9,0x70,0x8f,0xed,0x81,0x2e,0x5f,0x2a,0x25,0xc7,0x28,0x9d,0xcc,0x04,0x47,0x03,0x90,0x8f,0xc5,0x2c,0xf7,0x9e,0x67,0x1b,0x1d,0x26,0x87,0x5b,0xbe,0x5f,0x2b,0xe1,0x16,0x0a,0x58,0xc5,0x83,0x4e,0x06,0x58,0x49,0x0d,0xe8,0x66,0x50,0x26,0x94,0x28,0x0d,0x6b,0x8c,0x7c,0x30,0x85,0xf7,0xc3,0xfc,0xfd,0x12,0x11,0x0c,0x78,0xda,0x53,0x1b,0x88,0xb3,0x43,0xd8,0x0b,0x17,0x9c,0x07}, - {0xff,0x6f,0xfa,0x64,0xe4,0xec,0x06,0x05,0x23,0xe5,0x05,0x62,0x1e,0x43,0xe3,0xbe,0x42,0xea,0xb8,0x51,0x24,0x42,0x79,0x35,0x00,0xfb,0xc9,0x4a,0xe3,0x05,0xec,0x6d,0x56,0xd0,0xd5,0xc0,0x50,0xcd,0xd6,0xcd,0x3b,0x57,0x03,0xbb,0x6d,0x68,0xf7,0x9a,0x48,0xef,0xc3,0xf3,0x3f,0x72,0xa6,0x3c,0xcc,0x8a,0x7b,0x31,0xd7,0xc0,0x68,0x67,0xb3,0xc1,0x55,0xf1,0xe5,0x25,0xb6,0x94,0x91,0x7b,0x7b,0x99,0xa7,0xf3,0x7b,0x41,0x00,0x26,0x6b,0x6d,0xdc,0xbd,0x2c,0xc2,0xf4,0x52,0xcd,0xdd,0x14,0x5e,0x44,0x51}, - {0x51,0x49,0x14,0x3b,0x4b,0x2b,0x50,0x57,0xb3,0xbc,0x4b,0x44,0x6b,0xff,0x67,0x8e,0xdb,0x85,0x63,0x16,0x27,0x69,0xbd,0xb8,0xc8,0x95,0x92,0xe3,0x31,0x6f,0x18,0x13,0x55,0xa4,0xbe,0x2b,0xab,0x47,0x31,0x89,0x29,0x91,0x07,0x92,0x4f,0xa2,0x53,0x8c,0xa7,0xf7,0x30,0xbe,0x48,0xf9,0x49,0x4b,0x3d,0xd4,0x4f,0x6e,0x08,0x90,0xe9,0x12,0x2e,0xbb,0xdf,0x7f,0xb3,0x96,0x0c,0xf1,0xf9,0xea,0x1c,0x12,0x5e,0x93,0x9a,0x9f,0x3f,0x98,0x5b,0x3a,0xc4,0x36,0x11,0xdf,0xaf,0x99,0x3e,0x5d,0xf0,0xe3,0xb2,0x77}, - {0xde,0xc4,0x2e,0x9c,0xc5,0xa9,0x6f,0x29,0xcb,0xf3,0x84,0x4f,0xbf,0x61,0x8b,0xbc,0x08,0xf9,0xa8,0x17,0xd9,0x06,0x77,0x1c,0x5d,0x25,0xd3,0x7a,0xfc,0x95,0xb7,0x63,0xa4,0xb0,0xdd,0x12,0x9c,0x63,0x98,0xd5,0x6b,0x86,0x24,0xc0,0x30,0x9f,0xd1,0xa5,0x60,0xe4,0xfc,0x58,0x03,0x2f,0x7c,0xd1,0x8a,0x5e,0x09,0x2e,0x15,0x95,0xa1,0x07,0xc8,0x5f,0x9e,0x38,0x02,0x8f,0x36,0xa8,0x3b,0xe4,0x8d,0xcf,0x02,0x3b,0x43,0x90,0x43,0x26,0x41,0xc5,0x5d,0xfd,0xa1,0xaf,0x37,0x01,0x2f,0x03,0x3d,0xe8,0x8f,0x3e}, - {0x94,0xa2,0x70,0x05,0xb9,0x15,0x8b,0x2f,0x49,0x45,0x08,0x67,0x70,0x42,0xf2,0x94,0x84,0xfd,0xbb,0x61,0xe1,0x5a,0x1c,0xde,0x07,0x40,0xac,0x7f,0x79,0x3b,0xba,0x75,0x3c,0xd1,0xef,0xe8,0x8d,0x4c,0x70,0x08,0x31,0x37,0xe0,0x33,0x8e,0x1a,0xc5,0xdf,0xe3,0xcd,0x60,0x12,0xa5,0x5d,0x9d,0xa5,0x86,0x8c,0x25,0xa6,0x99,0x08,0xd6,0x22,0x96,0xd1,0xcd,0x70,0xc0,0xdb,0x39,0x62,0x9a,0x8a,0x7d,0x6c,0x8b,0x8a,0xfe,0x60,0x60,0x12,0x40,0xeb,0xbc,0x47,0x88,0xb3,0x5e,0x9e,0x77,0x87,0x7b,0xd0,0x04,0x09}, - {0x9c,0x91,0xba,0xdd,0xd4,0x1f,0xce,0xb4,0xaa,0x8d,0x4c,0xc7,0x3e,0xdb,0x31,0xcf,0x51,0xcc,0x86,0xad,0x63,0xcc,0x63,0x2c,0x07,0xde,0x1d,0xbc,0x3f,0x14,0xe2,0x43,0xb9,0x40,0xf9,0x48,0x66,0x2d,0x32,0xf4,0x39,0x0c,0x2d,0xbd,0x0c,0x2f,0x95,0x06,0x31,0xf9,0x81,0xa0,0xad,0x97,0x76,0x16,0x6c,0x2a,0xf7,0xba,0xce,0xaa,0x40,0x62,0xa0,0x95,0xa2,0x5b,0x9c,0x74,0x34,0xf8,0x5a,0xd2,0x37,0xca,0x5b,0x7c,0x94,0xd6,0x6a,0x31,0xc9,0xe7,0xa7,0x3b,0xf1,0x66,0xac,0x0c,0xb4,0x8d,0x23,0xaf,0xbd,0x56}, - {0xeb,0x33,0x35,0xf5,0xe3,0xb9,0x2a,0x36,0x40,0x3d,0xb9,0x6e,0xd5,0x68,0x85,0x33,0x72,0x55,0x5a,0x1d,0x52,0x14,0x0e,0x9e,0x18,0x13,0x74,0x83,0x6d,0xa8,0x24,0x1d,0xb2,0x3b,0x9d,0xc1,0x6c,0xd3,0x10,0x13,0xb9,0x86,0x23,0x62,0xb7,0x6b,0x2a,0x06,0x5c,0x4f,0xa1,0xd7,0x91,0x85,0x9b,0x7c,0x54,0x57,0x1e,0x7e,0x50,0x31,0xaa,0x03,0x1f,0xce,0xd4,0xff,0x48,0x76,0xec,0xf4,0x1c,0x8c,0xac,0x54,0xf0,0xea,0x45,0xe0,0x7c,0x35,0x09,0x1d,0x82,0x25,0xd2,0x88,0x59,0x48,0xeb,0x9a,0xdc,0x61,0xb2,0x43}, - {0xbb,0x79,0xbb,0x88,0x19,0x1e,0x5b,0xe5,0x9d,0x35,0x7a,0xc1,0x7d,0xd0,0x9e,0xa0,0x33,0xea,0x3d,0x60,0xe2,0x2e,0x2c,0xb0,0xc2,0x6b,0x27,0x5b,0xcf,0x55,0x60,0x32,0x64,0x13,0x95,0x6c,0x8b,0x3d,0x51,0x19,0x7b,0xf4,0x0b,0x00,0x26,0x71,0xfe,0x94,0x67,0x95,0x4f,0xd5,0xdd,0x10,0x8d,0x02,0x64,0x09,0x94,0x42,0xe2,0xd5,0xb4,0x02,0xf2,0x8d,0xd1,0x28,0xcb,0x55,0xa1,0xb4,0x08,0xe5,0x6c,0x18,0x46,0x46,0xcc,0xea,0x89,0x43,0x82,0x6c,0x93,0xf4,0x9c,0xc4,0x10,0x34,0x5d,0xae,0x09,0xc8,0xa6,0x27}, - {0x88,0xb1,0x0d,0x1f,0xcd,0xeb,0xa6,0x8b,0xe8,0x5b,0x5a,0x67,0x3a,0xd7,0xd3,0x37,0x5a,0x58,0xf5,0x15,0xa3,0xdf,0x2e,0xf2,0x7e,0xa1,0x60,0xff,0x74,0x71,0xb6,0x2c,0x54,0x69,0x3d,0xc4,0x0a,0x27,0x2c,0xcd,0xb2,0xca,0x66,0x6a,0x57,0x3e,0x4a,0xdd,0x6c,0x03,0xd7,0x69,0x24,0x59,0xfa,0x79,0x99,0x25,0x8c,0x3d,0x60,0x03,0x15,0x22,0xd0,0xe1,0x0b,0x39,0xf9,0xcd,0xee,0x59,0xf1,0xe3,0x8c,0x72,0x44,0x20,0x42,0xa9,0xf4,0xf0,0x94,0x7a,0x66,0x1c,0x89,0x82,0x36,0xf4,0x90,0x38,0xb7,0xf4,0x1d,0x7b}, - {0x24,0xa2,0xb2,0xb3,0xe0,0xf2,0x92,0xe4,0x60,0x11,0x55,0x2b,0x06,0x9e,0x6c,0x7c,0x0e,0x7b,0x7f,0x0d,0xe2,0x8f,0xeb,0x15,0x92,0x59,0xfc,0x58,0x26,0xef,0xfc,0x61,0x8c,0xf5,0xf8,0x07,0x18,0x22,0x2e,0x5f,0xd4,0x09,0x94,0xd4,0x9f,0x5c,0x55,0xe3,0x30,0xa6,0xb6,0x1f,0x8d,0xa8,0xaa,0xb2,0x3d,0xe0,0x52,0xd3,0x45,0x82,0x69,0x68,0x7a,0x18,0x18,0x2a,0x85,0x5d,0xb1,0xdb,0xd7,0xac,0xdd,0x86,0xd3,0xaa,0xe4,0xf3,0x82,0xc4,0xf6,0x0f,0x81,0xe2,0xba,0x44,0xcf,0x01,0xaf,0x3d,0x47,0x4c,0xcf,0x46}, - {0xf9,0xe5,0xc4,0x9e,0xed,0x25,0x65,0x42,0x03,0x33,0x90,0x16,0x01,0xda,0x5e,0x0e,0xdc,0xca,0xe5,0xcb,0xf2,0xa7,0xb1,0x72,0x40,0x5f,0xeb,0x14,0xcd,0x7b,0x38,0x29,0x40,0x81,0x49,0xf1,0xa7,0x6e,0x3c,0x21,0x54,0x48,0x2b,0x39,0xf8,0x7e,0x1e,0x7c,0xba,0xce,0x29,0x56,0x8c,0xc3,0x88,0x24,0xbb,0xc5,0x8c,0x0d,0xe5,0xaa,0x65,0x10,0x57,0x0d,0x20,0xdf,0x25,0x45,0x2c,0x1c,0x4a,0x67,0xca,0xbf,0xd6,0x2d,0x3b,0x5c,0x30,0x40,0x83,0xe1,0xb1,0xe7,0x07,0x0a,0x16,0xe7,0x1c,0x4f,0xe6,0x98,0xa1,0x69}, - {0xbc,0x78,0x1a,0xd9,0xe0,0xb2,0x62,0x90,0x67,0x96,0x50,0xc8,0x9c,0x88,0xc9,0x47,0xb8,0x70,0x50,0x40,0x66,0x4a,0xf5,0x9d,0xbf,0xa1,0x93,0x24,0xa9,0xe6,0x69,0x73,0xed,0xca,0xc5,0xdc,0x34,0x44,0x01,0xe1,0x33,0xfb,0x84,0x3c,0x96,0x5d,0xed,0x47,0xe7,0xa0,0x86,0xed,0x76,0x95,0x01,0x70,0xe4,0xf9,0x67,0xd2,0x7b,0x69,0xb2,0x25,0x64,0x68,0x98,0x13,0xfb,0x3f,0x67,0x9d,0xb8,0xc7,0x5d,0x41,0xd9,0xfb,0xa5,0x3c,0x5e,0x3b,0x27,0xdf,0x3b,0xcc,0x4e,0xe0,0xd2,0x4c,0x4e,0xb5,0x3d,0x68,0x20,0x14}, - {0x97,0xd1,0x9d,0x24,0x1e,0xbd,0x78,0xb4,0x02,0xc1,0x58,0x5e,0x00,0x35,0x0c,0x62,0x5c,0xac,0xba,0xcc,0x2f,0xd3,0x02,0xfb,0x2d,0xa7,0x08,0xf5,0xeb,0x3b,0xb6,0x60,0xd0,0x5a,0xcc,0xc1,0x6f,0xbb,0xee,0x34,0x8b,0xac,0x46,0x96,0xe9,0x0c,0x1b,0x6a,0x53,0xde,0x6b,0xa6,0x49,0xda,0xb0,0xd3,0xc1,0x81,0xd0,0x61,0x41,0x3b,0xe8,0x31,0x4f,0x2b,0x06,0x9e,0x12,0xc7,0xe8,0x97,0xd8,0x0a,0x32,0x29,0x4f,0x8f,0xe4,0x49,0x3f,0x68,0x18,0x6f,0x4b,0xe1,0xec,0x5b,0x17,0x03,0x55,0x2d,0xb6,0x1e,0xcf,0x55}, - {0x58,0x3d,0xc2,0x65,0x10,0x10,0x79,0x58,0x9c,0x81,0x94,0x50,0x6d,0x08,0x9d,0x8b,0xa7,0x5f,0xc5,0x12,0xa9,0x2f,0x40,0xe2,0xd4,0x91,0x08,0x57,0x64,0x65,0x9a,0x66,0x52,0x8c,0xf5,0x7d,0xe3,0xb5,0x76,0x30,0x36,0xcc,0x99,0xe7,0xdd,0xb9,0x3a,0xd7,0x20,0xee,0x13,0x49,0xe3,0x1c,0x83,0xbd,0x33,0x01,0xba,0x62,0xaa,0xfb,0x56,0x1a,0xec,0xc9,0x9d,0x5c,0x50,0x6b,0x3e,0x94,0x1a,0x37,0x7c,0xa7,0xbb,0x57,0x25,0x30,0x51,0x76,0x34,0x41,0x56,0xae,0x73,0x98,0x5c,0x8a,0xc5,0x99,0x67,0x83,0xc4,0x13}, - {0xb9,0xe1,0xb3,0x5a,0x46,0x5d,0x3a,0x42,0x61,0x3f,0xf1,0xc7,0x87,0xc1,0x13,0xfc,0xb6,0xb9,0xb5,0xec,0x64,0x36,0xf8,0x19,0x07,0xb6,0x37,0xa6,0x93,0x0c,0xf8,0x66,0x80,0xd0,0x8b,0x5d,0x6a,0xfb,0xdc,0xc4,0x42,0x48,0x1a,0x57,0xec,0xc4,0xeb,0xde,0x65,0x53,0xe5,0xb8,0x83,0xe8,0xb2,0xd4,0x27,0xb8,0xe5,0xc8,0x7d,0xc8,0xbd,0x50,0x11,0xe1,0xdf,0x6e,0x83,0x37,0x6d,0x60,0xd9,0xab,0x11,0xf0,0x15,0x3e,0x35,0x32,0x96,0x3b,0xb7,0x25,0xc3,0x3a,0xb0,0x64,0xae,0xd5,0x5f,0x72,0x44,0x64,0xd5,0x1d}, - {0x7d,0x12,0x62,0x33,0xf8,0x7f,0xa4,0x8f,0x15,0x7c,0xcd,0x71,0xc4,0x6a,0x9f,0xbc,0x8b,0x0c,0x22,0x49,0x43,0x45,0x71,0x6e,0x2e,0x73,0x9f,0x21,0x12,0x59,0x64,0x0e,0x9a,0xc8,0xba,0x08,0x00,0xe6,0x97,0xc2,0xe0,0xc3,0xe1,0xea,0x11,0xea,0x4c,0x7d,0x7c,0x97,0xe7,0x9f,0xe1,0x8b,0xe3,0xf3,0xcd,0x05,0xa3,0x63,0x0f,0x45,0x3a,0x3a,0x27,0x46,0x39,0xd8,0x31,0x2f,0x8f,0x07,0x10,0xa5,0x94,0xde,0x83,0x31,0x9d,0x38,0x80,0x6f,0x99,0x17,0x6d,0x6c,0xe3,0xd1,0x7b,0xa8,0xa9,0x93,0x93,0x8d,0x8c,0x31}, - {0x19,0xfe,0xff,0x2a,0x03,0x5d,0x74,0xf2,0x66,0xdb,0x24,0x7f,0x49,0x3c,0x9f,0x0c,0xef,0x98,0x85,0xba,0xe3,0xd3,0x98,0xbc,0x14,0x53,0x1d,0x9a,0x67,0x7c,0x4c,0x22,0x98,0xd3,0x1d,0xab,0x29,0x9e,0x66,0x5d,0x3b,0x9e,0x2d,0x34,0x58,0x16,0x92,0xfc,0xcd,0x73,0x59,0xf3,0xfd,0x1d,0x85,0x55,0xf6,0x0a,0x95,0x25,0xc3,0x41,0x9a,0x50,0xe9,0x25,0xf9,0xa6,0xdc,0x6e,0xc0,0xbd,0x33,0x1f,0x1b,0x64,0xf4,0xf3,0x3e,0x79,0x89,0x3e,0x83,0x9d,0x80,0x12,0xec,0x82,0x89,0x13,0xa1,0x28,0x23,0xf0,0xbf,0x05}, - {0x0b,0xe0,0xca,0x23,0x70,0x13,0x32,0x36,0x59,0xcf,0xac,0xd1,0x0a,0xcf,0x4a,0x54,0x88,0x1c,0x1a,0xd2,0x49,0x10,0x74,0x96,0xa7,0x44,0x2a,0xfa,0xc3,0x8c,0x0b,0x78,0xe4,0x12,0xc5,0x0d,0xdd,0xa0,0x81,0x68,0xfe,0xfa,0xa5,0x44,0xc8,0x0d,0xe7,0x4f,0x40,0x52,0x4a,0x8f,0x6b,0x8e,0x74,0x1f,0xea,0xa3,0x01,0xee,0xcd,0x77,0x62,0x57,0x5f,0x30,0x4f,0x23,0xbc,0x8a,0xf3,0x1e,0x08,0xde,0x05,0x14,0xbd,0x7f,0x57,0x9a,0x0d,0x2a,0xe6,0x34,0x14,0xa5,0x82,0x5e,0xa1,0xb7,0x71,0x62,0x72,0x18,0xf4,0x5f}, - {0x9d,0xdb,0x89,0x17,0x0c,0x08,0x8e,0x39,0xf5,0x78,0xe7,0xf3,0x25,0x20,0x60,0xa7,0x5d,0x03,0xbd,0x06,0x4c,0x89,0x98,0xfa,0xbe,0x66,0xa9,0x25,0xdc,0x03,0x6a,0x10,0x40,0x95,0xb6,0x13,0xe8,0x47,0xdb,0xe5,0xe1,0x10,0x26,0x43,0x3b,0x2a,0x5d,0xf3,0x76,0x12,0x78,0x38,0xe9,0x26,0x1f,0xac,0x69,0xcb,0xa0,0xa0,0x8c,0xdb,0xd4,0x29,0xd0,0x53,0x33,0x33,0xaf,0x0a,0xad,0xd9,0xe5,0x09,0xd3,0xac,0xa5,0x9d,0x66,0x38,0xf0,0xf7,0x88,0xc8,0x8a,0x65,0x57,0x3c,0xfa,0xbe,0x2c,0x05,0x51,0x8a,0xb3,0x4a}, - {0x93,0xd5,0x68,0x67,0x25,0x2b,0x7c,0xda,0x13,0xca,0x22,0x44,0x57,0xc0,0xc1,0x98,0x1d,0xce,0x0a,0xca,0xd5,0x0b,0xa8,0xf1,0x90,0xa6,0x88,0xc0,0xad,0xd1,0xcd,0x29,0x9c,0xc0,0xdd,0x5f,0xef,0xd1,0xcf,0xd6,0xce,0x5d,0x57,0xf7,0xfd,0x3e,0x2b,0xe8,0xc2,0x34,0x16,0x20,0x5d,0x6b,0xd5,0x25,0x9b,0x2b,0xed,0x04,0xbb,0xc6,0x41,0x30,0x48,0xe1,0x56,0xd9,0xf9,0xf2,0xf2,0x0f,0x2e,0x6b,0x35,0x9f,0x75,0x97,0xe7,0xad,0x5c,0x02,0x6c,0x5f,0xbb,0x98,0x46,0x1a,0x7b,0x9a,0x04,0x14,0x68,0xbd,0x4b,0x10}, - {0x67,0xed,0xf1,0x68,0x31,0xfd,0xf0,0x51,0xc2,0x3b,0x6f,0xd8,0xcd,0x1d,0x81,0x2c,0xde,0xf2,0xd2,0x04,0x43,0x5c,0xdc,0x44,0x49,0x71,0x2a,0x09,0x57,0xcc,0xe8,0x5b,0x63,0xf1,0x7f,0xd6,0x5f,0x9a,0x5d,0xa9,0x81,0x56,0xc7,0x4c,0x9d,0xe6,0x2b,0xe9,0x57,0xf2,0x20,0xde,0x4c,0x02,0xf8,0xb7,0xf5,0x2d,0x07,0xfb,0x20,0x2a,0x4f,0x20,0x79,0xb0,0xeb,0x30,0x3d,0x3b,0x14,0xc8,0x30,0x2e,0x65,0xbd,0x5a,0x15,0x89,0x75,0x31,0x5c,0x6d,0x8f,0x31,0x3c,0x3c,0x65,0x1f,0x16,0x79,0xc2,0x17,0xfb,0x70,0x25}, - {0x75,0x15,0xb6,0x2c,0x7f,0x36,0xfa,0x3e,0x6c,0x02,0xd6,0x1c,0x76,0x6f,0xf9,0xf5,0x62,0x25,0xb5,0x65,0x2a,0x14,0xc7,0xe8,0xcd,0x0a,0x03,0x53,0xea,0x65,0xcb,0x3d,0x5a,0x24,0xb8,0x0b,0x55,0xa9,0x2e,0x19,0xd1,0x50,0x90,0x8f,0xa8,0xfb,0xe6,0xc8,0x35,0xc9,0xa4,0x88,0x2d,0xea,0x86,0x79,0x68,0x86,0x01,0xde,0x91,0x5f,0x1c,0x24,0xaa,0x6c,0xde,0x40,0x29,0x17,0xd8,0x28,0x3a,0x73,0xd9,0x22,0xf0,0x2c,0xbf,0x8f,0xd1,0x01,0x5b,0x23,0xdd,0xfc,0xd7,0x16,0xe5,0xf0,0xcd,0x5f,0xdd,0x0e,0x42,0x08}, - {0x4a,0xfa,0x62,0x83,0xab,0x20,0xff,0xcd,0x6e,0x3e,0x1a,0xe2,0xd4,0x18,0xe1,0x57,0x2b,0xe6,0x39,0xfc,0x17,0x96,0x17,0xe3,0xfd,0x69,0x17,0xbc,0xef,0x53,0x9a,0x0d,0xce,0x10,0xf4,0x04,0x4e,0xc3,0x58,0x03,0x85,0x06,0x6e,0x27,0x5a,0x5b,0x13,0xb6,0x21,0x15,0xb9,0xeb,0xc7,0x70,0x96,0x5d,0x9c,0x88,0xdb,0x21,0xf3,0x54,0xd6,0x04,0xd5,0xb5,0xbd,0xdd,0x16,0xc1,0x7d,0x5e,0x2d,0xdd,0xa5,0x8d,0xb6,0xde,0x54,0x29,0x92,0xa2,0x34,0x33,0x17,0x08,0xb6,0x1c,0xd7,0x1a,0x99,0x18,0x26,0x4f,0x7a,0x4a}, - {0x95,0x5f,0xb1,0x5f,0x02,0x18,0xa7,0xf4,0x8f,0x1b,0x5c,0x6b,0x34,0x5f,0xf6,0x3d,0x12,0x11,0xe0,0x00,0x85,0xf0,0xfc,0xcd,0x48,0x18,0xd3,0xdd,0x4c,0x0c,0xb5,0x11,0x4b,0x2a,0x37,0xaf,0x91,0xb2,0xc3,0x24,0xf2,0x47,0x81,0x71,0x70,0x82,0xda,0x93,0xf2,0x9e,0x89,0x86,0x64,0x85,0x84,0xdd,0x33,0xee,0xe0,0x23,0x42,0x31,0x96,0x4a,0xd6,0xff,0xa4,0x08,0x44,0x27,0xe8,0xa6,0xd9,0x76,0x15,0x9c,0x7e,0x17,0x8e,0x73,0xf2,0xb3,0x02,0x3d,0xb6,0x48,0x33,0x77,0x51,0xcc,0x6b,0xce,0x4d,0xce,0x4b,0x4f}, - {0x84,0x25,0x24,0xe2,0x5a,0xce,0x1f,0xa7,0x9e,0x8a,0xf5,0x92,0x56,0x72,0xea,0x26,0xf4,0x3c,0xea,0x1c,0xd7,0x09,0x1a,0xd2,0xe6,0x01,0x1c,0xb7,0x14,0xdd,0xfc,0x73,0x6f,0x0b,0x9d,0xc4,0x6e,0x61,0xe2,0x30,0x17,0x23,0xec,0xca,0x8f,0x71,0x56,0xe4,0xa6,0x4f,0x6b,0xf2,0x9b,0x40,0xeb,0x48,0x37,0x5f,0x59,0x61,0xe5,0xce,0x42,0x30,0x41,0xac,0x9b,0x44,0x79,0x70,0x7e,0x42,0x0a,0x31,0xe2,0xbc,0x6d,0xe3,0x5a,0x85,0x7c,0x1a,0x84,0x5f,0x21,0x76,0xae,0x4c,0xd6,0xe1,0x9c,0x9a,0x0c,0x74,0x9e,0x38}, - {0xce,0xb9,0xdc,0x34,0xae,0xb3,0xfc,0x64,0xad,0xd0,0x48,0xe3,0x23,0x03,0x50,0x97,0x1b,0x38,0xc6,0x62,0x7d,0xf0,0xb3,0x45,0x88,0x67,0x5a,0x46,0x79,0x53,0x54,0x61,0x28,0xac,0x0e,0x57,0xf6,0x78,0xbd,0xc9,0xe1,0x9c,0x91,0x27,0x32,0x0b,0x5b,0xe5,0xed,0x91,0x9b,0xa1,0xab,0x3e,0xfc,0x65,0x90,0x36,0x26,0xd6,0xe5,0x25,0xc4,0x25,0x6e,0xde,0xd7,0xf1,0xa6,0x06,0x3e,0x3f,0x08,0x23,0x06,0x8e,0x27,0x76,0xf9,0x3e,0x77,0x6c,0x8a,0x4e,0x26,0xf6,0x14,0x8c,0x59,0x47,0x48,0x15,0x89,0xa0,0x39,0x65}, - {0x73,0xf7,0xd2,0xc3,0x74,0x1f,0xd2,0xe9,0x45,0x68,0xc4,0x25,0x41,0x54,0x50,0xc1,0x33,0x9e,0xb9,0xf9,0xe8,0x5c,0x4e,0x62,0x6c,0x18,0xcd,0xc5,0xaa,0xe4,0xc5,0x11,0x19,0x4a,0xbb,0x14,0xd4,0xdb,0xc4,0xdd,0x8e,0x4f,0x42,0x98,0x3c,0xbc,0xb2,0x19,0x69,0x71,0xca,0x36,0xd7,0x9f,0xa8,0x48,0x90,0xbd,0x19,0xf0,0x0e,0x32,0x65,0x0f,0xc6,0xe0,0xfd,0xca,0xb1,0xd1,0x86,0xd4,0x81,0x51,0x3b,0x16,0xe3,0xe6,0x3f,0x4f,0x9a,0x93,0xf2,0xfa,0x0d,0xaf,0xa8,0x59,0x2a,0x07,0x33,0xec,0xbd,0xc7,0xab,0x4c}, - {0x2e,0x0a,0x9c,0x08,0x24,0x96,0x9e,0x23,0x38,0x47,0xfe,0x3a,0xc0,0xc4,0x48,0xc7,0x2a,0xa1,0x4f,0x76,0x2a,0xed,0xdb,0x17,0x82,0x85,0x1c,0x32,0xf0,0x93,0x9b,0x63,0x89,0xd2,0x78,0x3f,0x8f,0x78,0x8f,0xc0,0x9f,0x4d,0x40,0xa1,0x2c,0xa7,0x30,0xfe,0x9d,0xcc,0x65,0xcf,0xfc,0x8b,0x77,0xf2,0x21,0x20,0xcb,0x5a,0x16,0x98,0xe4,0x7e,0xc3,0xa1,0x11,0x91,0xe3,0x08,0xd5,0x7b,0x89,0x74,0x90,0x80,0xd4,0x90,0x2b,0x2b,0x19,0xfd,0x72,0xae,0xc2,0xae,0xd2,0xe7,0xa6,0x02,0xb6,0x85,0x3c,0x49,0xdf,0x0e}, - {0x68,0x5a,0x9b,0x59,0x58,0x81,0xcc,0xae,0x0e,0xe2,0xad,0xeb,0x0f,0x4f,0x57,0xea,0x07,0x7f,0xb6,0x22,0x74,0x1d,0xe4,0x4f,0xb4,0x4f,0x9d,0x01,0xe3,0x92,0x3b,0x40,0x13,0x41,0x76,0x84,0xd2,0xc4,0x67,0x67,0x35,0xf8,0xf5,0xf7,0x3f,0x40,0x90,0xa0,0xde,0xbe,0xe6,0xca,0xfa,0xcf,0x8f,0x1c,0x69,0xa3,0xdf,0xd1,0x54,0x0c,0xc0,0x04,0xf8,0x5c,0x46,0x8b,0x81,0x2f,0xc2,0x4d,0xf8,0xef,0x80,0x14,0x5a,0xf3,0xa0,0x71,0x57,0xd6,0xc7,0x04,0xad,0xbf,0xe8,0xae,0xf4,0x76,0x61,0xb2,0x2a,0xb1,0x5b,0x35}, - {0xf4,0xbb,0x93,0x74,0xcc,0x64,0x1e,0xa7,0xc3,0xb0,0xa3,0xec,0xd9,0x84,0xbd,0xe5,0x85,0xe7,0x05,0xfa,0x0c,0xc5,0x6b,0x0a,0x12,0xc3,0x2e,0x18,0x32,0x81,0x9b,0x0f,0x18,0x73,0x8c,0x5a,0xc7,0xda,0x01,0xa3,0x11,0xaa,0xce,0xb3,0x9d,0x03,0x90,0xed,0x2d,0x3f,0xae,0x3b,0xbf,0x7c,0x07,0x6f,0x8e,0xad,0x52,0xe0,0xf8,0xea,0x18,0x75,0x32,0x6c,0x7f,0x1b,0xc4,0x59,0x88,0xa4,0x98,0x32,0x38,0xf4,0xbc,0x60,0x2d,0x0f,0xd9,0xd1,0xb1,0xc9,0x29,0xa9,0x15,0x18,0xc4,0x55,0x17,0xbb,0x1b,0x87,0xc3,0x47}, - {0x48,0x4f,0xec,0x71,0x97,0x53,0x44,0x51,0x6e,0x5d,0x8c,0xc9,0x7d,0xb1,0x05,0xf8,0x6b,0xc6,0xc3,0x47,0x1a,0xc1,0x62,0xf7,0xdc,0x99,0x46,0x76,0x85,0x9b,0xb8,0x00,0xb0,0x66,0x50,0xc8,0x50,0x5d,0xe6,0xfb,0xb0,0x99,0xa2,0xb3,0xb0,0xc4,0xec,0x62,0xe0,0xe8,0x1a,0x44,0xea,0x54,0x37,0xe5,0x5f,0x8d,0xd4,0xe8,0x2c,0xa0,0xfe,0x08,0xd0,0xea,0xde,0x68,0x76,0xdd,0x4d,0x82,0x23,0x5d,0x68,0x4b,0x20,0x45,0x64,0xc8,0x65,0xd6,0x89,0x5d,0xcd,0xcf,0x14,0xb5,0x37,0xd5,0x75,0x4f,0xa7,0x29,0x38,0x47}, - {0x18,0xc4,0x79,0x46,0x75,0xda,0xd2,0x82,0xf0,0x8d,0x61,0xb2,0xd8,0xd7,0x3b,0xe6,0x0a,0xeb,0x47,0xac,0x24,0xef,0x5e,0x35,0xb4,0xc6,0x33,0x48,0x4c,0x68,0x78,0x20,0xc9,0x02,0x39,0xad,0x3a,0x53,0xd9,0x23,0x8f,0x58,0x03,0xef,0xce,0xdd,0xc2,0x64,0xb4,0x2f,0xe1,0xcf,0x90,0x73,0x25,0x15,0x90,0xd3,0xe4,0x44,0x4d,0x8b,0x66,0x6c,0x0c,0x82,0x78,0x7a,0x21,0xcf,0x48,0x3b,0x97,0x3e,0x27,0x81,0xb2,0x0a,0x6a,0xf7,0x7b,0xed,0x8e,0x8c,0xa7,0x65,0x6c,0xa9,0x3f,0x43,0x8a,0x4f,0x05,0xa6,0x11,0x74}, - {0x6d,0xc8,0x9d,0xb9,0x32,0x9d,0x65,0x4d,0x15,0xf1,0x3a,0x60,0x75,0xdc,0x4c,0x04,0x88,0xe4,0xc2,0xdc,0x2c,0x71,0x4c,0xb3,0xff,0x34,0x81,0xfb,0x74,0x65,0x13,0x7c,0xb4,0x75,0xb1,0x18,0x3d,0xe5,0x9a,0x57,0x02,0xa1,0x92,0xf3,0x59,0x31,0x71,0x68,0xf5,0x35,0xef,0x1e,0xba,0xec,0x55,0x84,0x8f,0x39,0x8c,0x45,0x72,0xa8,0xc9,0x1e,0x9b,0x50,0xa2,0x00,0xd4,0xa4,0xe6,0xb8,0xb4,0x82,0xc8,0x0b,0x02,0xd7,0x81,0x9b,0x61,0x75,0x95,0xf1,0x9b,0xcc,0xe7,0x57,0x60,0x64,0xcd,0xc7,0xa5,0x88,0xdd,0x3a}, - {0xf2,0xdc,0x35,0xb6,0x70,0x57,0x89,0xab,0xbc,0x1f,0x6c,0xf6,0x6c,0xef,0xdf,0x02,0x87,0xd1,0xb6,0xbe,0x68,0x02,0x53,0x85,0x74,0x9e,0x87,0xcc,0xfc,0x29,0x99,0x24,0x46,0x30,0x39,0x59,0xd4,0x98,0xc2,0x85,0xec,0x59,0xf6,0x5f,0x98,0x35,0x7e,0x8f,0x3a,0x6e,0xf6,0xf2,0x2a,0xa2,0x2c,0x1d,0x20,0xa7,0x06,0xa4,0x31,0x11,0xba,0x61,0x29,0x90,0x95,0x16,0xf1,0xa0,0xd0,0xa3,0x89,0xbd,0x7e,0xba,0x6c,0x6b,0x3b,0x02,0x07,0x33,0x78,0x26,0x3e,0x5a,0xf1,0x7b,0xe7,0xec,0xd8,0xbb,0x0c,0x31,0x20,0x56}, - {0x43,0xd6,0x34,0x49,0x43,0x93,0x89,0x52,0xf5,0x22,0x12,0xa5,0x06,0xf8,0xdb,0xb9,0x22,0x1c,0xf4,0xc3,0x8f,0x87,0x6d,0x8f,0x30,0x97,0x9d,0x4d,0x2a,0x6a,0x67,0x37,0xd6,0x85,0xe2,0x77,0xf4,0xb5,0x46,0x66,0x93,0x61,0x8f,0x6c,0x67,0xff,0xe8,0x40,0xdd,0x94,0xb5,0xab,0x11,0x73,0xec,0xa6,0x4d,0xec,0x8c,0x65,0xf3,0x46,0xc8,0x7e,0xc7,0x2e,0xa2,0x1d,0x3f,0x8f,0x5e,0x9b,0x13,0xcd,0x01,0x6c,0x77,0x1d,0x0f,0x13,0xb8,0x9f,0x98,0xa2,0xcf,0x8f,0x4c,0x21,0xd5,0x9d,0x9b,0x39,0x23,0xf7,0xaa,0x6d}, - {0x47,0xbe,0x3d,0xeb,0x62,0x75,0x3a,0x5f,0xb8,0xa0,0xbd,0x8e,0x54,0x38,0xea,0xf7,0x99,0x72,0x74,0x45,0x31,0xe5,0xc3,0x00,0x51,0xd5,0x27,0x16,0xe7,0xe9,0x04,0x13,0xa2,0x8e,0xad,0xac,0xbf,0x04,0x3b,0x58,0x84,0xe8,0x8b,0x14,0xe8,0x43,0xb7,0x29,0xdb,0xc5,0x10,0x08,0x3b,0x58,0x1e,0x2b,0xaa,0xbb,0xb3,0x8e,0xe5,0x49,0x54,0x2b,0xfe,0x9c,0xdc,0x6a,0xd2,0x14,0x98,0x78,0x0b,0xdd,0x48,0x8b,0x3f,0xab,0x1b,0x3c,0x0a,0xc6,0x79,0xf9,0xff,0xe1,0x0f,0xda,0x93,0xd6,0x2d,0x7c,0x2d,0xde,0x68,0x44}, - {0x9e,0x46,0x19,0x94,0x5e,0x35,0xbb,0x51,0x54,0xc7,0xdd,0x23,0x4c,0xdc,0xe6,0x33,0x62,0x99,0x7f,0x44,0xd6,0xb6,0xa5,0x93,0x63,0xbd,0x44,0xfb,0x6f,0x7c,0xce,0x6c,0xce,0x07,0x63,0xf8,0xc6,0xd8,0x9a,0x4b,0x28,0x0c,0x5d,0x43,0x31,0x35,0x11,0x21,0x2c,0x77,0x7a,0x65,0xc5,0x66,0xa8,0xd4,0x52,0x73,0x24,0x63,0x7e,0x42,0xa6,0x5d,0xca,0x22,0xac,0xde,0x88,0xc6,0x94,0x1a,0xf8,0x1f,0xae,0xbb,0xf7,0x6e,0x06,0xb9,0x0f,0x58,0x59,0x8d,0x38,0x8c,0xad,0x88,0xa8,0x2c,0x9f,0xe7,0xbf,0x9a,0xf2,0x58}, - {0x68,0x3e,0xe7,0x8d,0xab,0xcf,0x0e,0xe9,0xa5,0x76,0x7e,0x37,0x9f,0x6f,0x03,0x54,0x82,0x59,0x01,0xbe,0x0b,0x5b,0x49,0xf0,0x36,0x1e,0xf4,0xa7,0xc4,0x29,0x76,0x57,0xf6,0xcd,0x0e,0x71,0xbf,0x64,0x5a,0x4b,0x3c,0x29,0x2c,0x46,0x38,0xe5,0x4c,0xb1,0xb9,0x3a,0x0b,0xd5,0x56,0xd0,0x43,0x36,0x70,0x48,0x5b,0x18,0x24,0x37,0xf9,0x6a,0x88,0xa8,0xc6,0x09,0x45,0x02,0x20,0x32,0x73,0x89,0x55,0x4b,0x13,0x36,0xe0,0xd2,0x9f,0x28,0x33,0x3c,0x23,0x36,0xe2,0x83,0x8f,0xc1,0xae,0x0c,0xbb,0x25,0x1f,0x70}, - {0xed,0x6c,0x61,0xe4,0xf8,0xb0,0xa8,0xc3,0x7d,0xa8,0x25,0x9e,0x0e,0x66,0x00,0xf7,0x9c,0xa5,0xbc,0xf4,0x1f,0x06,0xe3,0x61,0xe9,0x0b,0xc4,0xbd,0xbf,0x92,0x0c,0x2e,0x13,0xc1,0xbe,0x7c,0xd9,0xf6,0x18,0x9d,0xe4,0xdb,0xbf,0x74,0xe6,0x06,0x4a,0x84,0xd6,0x60,0x4e,0xac,0x22,0xb5,0xf5,0x20,0x51,0x5e,0x95,0x50,0xc0,0x5b,0x0a,0x72,0x35,0x5a,0x80,0x9b,0x43,0x09,0x3f,0x0c,0xfc,0xab,0x42,0x62,0x37,0x8b,0x4e,0xe8,0x46,0x93,0x22,0x5c,0xf3,0x17,0x14,0x69,0xec,0xf0,0x4e,0x14,0xbb,0x9c,0x9b,0x0e}, - {0xad,0x20,0x57,0xfb,0x8f,0xd4,0xba,0xfb,0x0e,0x0d,0xf9,0xdb,0x6b,0x91,0x81,0xee,0xbf,0x43,0x55,0x63,0x52,0x31,0x81,0xd4,0xd8,0x7b,0x33,0x3f,0xeb,0x04,0x11,0x22,0xee,0xbe,0xb1,0x5d,0xd5,0x9b,0xee,0x8d,0xb9,0x3f,0x72,0x0a,0x37,0xab,0xc3,0xc9,0x91,0xd7,0x68,0x1c,0xbf,0xf1,0xa8,0x44,0xde,0x3c,0xfd,0x1c,0x19,0x44,0x6d,0x36,0x14,0x8c,0xbc,0xf2,0x43,0x17,0x3c,0x9e,0x3b,0x6c,0x85,0xb5,0xfc,0x26,0xda,0x2e,0x97,0xfb,0xa7,0x68,0x0e,0x2f,0xb8,0xcc,0x44,0x32,0x59,0xbc,0xe6,0xa4,0x67,0x41}, - {0x00,0x27,0xf6,0x76,0x28,0x9d,0x3b,0x64,0xeb,0x68,0x76,0x0e,0x40,0x9d,0x1d,0x5d,0x84,0x06,0xfc,0x21,0x03,0x43,0x4b,0x1b,0x6a,0x24,0x55,0x22,0x7e,0xbb,0x38,0x79,0xee,0x8f,0xce,0xf8,0x65,0x26,0xbe,0xc2,0x2c,0xd6,0x80,0xe8,0x14,0xff,0x67,0xe9,0xee,0x4e,0x36,0x2f,0x7e,0x6e,0x2e,0xf1,0xf6,0xd2,0x7e,0xcb,0x70,0x33,0xb3,0x34,0xcc,0xd6,0x81,0x86,0xee,0x91,0xc5,0xcd,0x53,0xa7,0x85,0xed,0x9c,0x10,0x02,0xce,0x83,0x88,0x80,0x58,0xc1,0x85,0x74,0xed,0xe4,0x65,0xfe,0x2d,0x6e,0xfc,0x76,0x11}, - {0x9b,0x61,0x9c,0x5b,0xd0,0x6c,0xaf,0xb4,0x80,0x84,0xa5,0xb2,0xf4,0xc9,0xdf,0x2d,0xc4,0x4d,0xe9,0xeb,0x02,0xa5,0x4f,0x3d,0x34,0x5f,0x7d,0x67,0x4c,0x3a,0xfc,0x08,0xb8,0x0e,0x77,0x49,0x89,0xe2,0x90,0xdb,0xa3,0x40,0xf4,0xac,0x2a,0xcc,0xfb,0x98,0x9b,0x87,0xd7,0xde,0xfe,0x4f,0x35,0x21,0xb6,0x06,0x69,0xf2,0x54,0x3e,0x6a,0x1f,0xea,0x34,0x07,0xd3,0x99,0xc1,0xa4,0x60,0xd6,0x5c,0x16,0x31,0xb6,0x85,0xc0,0x40,0x95,0x82,0x59,0xf7,0x23,0x3e,0x33,0xe2,0xd1,0x00,0xb9,0x16,0x01,0xad,0x2f,0x4f}, - {0x54,0x4e,0xae,0x94,0x41,0xb2,0xbe,0x44,0x6c,0xef,0x57,0x18,0x51,0x1c,0x54,0x5f,0x98,0x04,0x8d,0x36,0x2d,0x6b,0x1e,0xa6,0xab,0xf7,0x2e,0x97,0xa4,0x84,0x54,0x44,0x38,0xb6,0x3b,0xb7,0x1d,0xd9,0x2c,0x96,0x08,0x9c,0x12,0xfc,0xaa,0x77,0x05,0xe6,0x89,0x16,0xb6,0xf3,0x39,0x9b,0x61,0x6f,0x81,0xee,0x44,0x29,0x5f,0x99,0x51,0x34,0x7c,0x7d,0xea,0x9f,0xd0,0xfc,0x52,0x91,0xf6,0x5c,0x93,0xb0,0x94,0x6c,0x81,0x4a,0x40,0x5c,0x28,0x47,0xaa,0x9a,0x8e,0x25,0xb7,0x93,0x28,0x04,0xa6,0x9c,0xb8,0x10}, - {0x9c,0x28,0x18,0x97,0x49,0x47,0x59,0x3d,0x26,0x3f,0x53,0x24,0xc5,0xf8,0xeb,0x12,0x15,0xef,0xc3,0x14,0xcb,0xbf,0x62,0x02,0x8e,0x51,0xb7,0x77,0xd5,0x78,0xb8,0x20,0x6e,0xf0,0x45,0x5a,0xbe,0x41,0x39,0x75,0x65,0x5f,0x9c,0x6d,0xed,0xae,0x7c,0xd0,0xb6,0x51,0xff,0x72,0x9c,0x6b,0x77,0x11,0xa9,0x4d,0x0d,0xef,0xd9,0xd1,0xd2,0x17,0x6a,0x3e,0x3f,0x07,0x18,0xaf,0xf2,0x27,0x69,0x10,0x52,0xd7,0x19,0xe5,0x3f,0xfd,0x22,0x00,0xa6,0x3c,0x2c,0xb7,0xe3,0x22,0xa7,0xc6,0x65,0xcc,0x63,0x4f,0x21,0x72}, - {0x93,0xa6,0x07,0x53,0x40,0x7f,0xe3,0xb4,0x95,0x67,0x33,0x2f,0xd7,0x14,0xa7,0xab,0x99,0x10,0x76,0x73,0xa7,0xd0,0xfb,0xd6,0xc9,0xcb,0x71,0x81,0xc5,0x48,0xdf,0x5f,0xc9,0x29,0x3b,0xf4,0xb9,0xb7,0x9d,0x1d,0x75,0x8f,0x51,0x4f,0x4a,0x82,0x05,0xd6,0xc4,0x9d,0x2f,0x31,0xbd,0x72,0xc0,0xf2,0xb0,0x45,0x15,0x5a,0x85,0xac,0x24,0x1f,0xaa,0x05,0x95,0x8e,0x32,0x08,0xd6,0x24,0xee,0x20,0x14,0x0c,0xd1,0xc1,0x48,0x47,0xa2,0x25,0xfb,0x06,0x5c,0xe4,0xff,0xc7,0xe6,0x95,0xe3,0x2a,0x9e,0x73,0xba,0x00}, - {0xd6,0x90,0x87,0x5c,0xde,0x98,0x2e,0x59,0xdf,0xa2,0xc2,0x45,0xd3,0xb7,0xbf,0xe5,0x22,0x99,0xb4,0xf9,0x60,0x3b,0x5a,0x11,0xf3,0x78,0xad,0x67,0x3e,0x3a,0x28,0x03,0x26,0xbb,0x88,0xea,0xf5,0x26,0x44,0xae,0xfb,0x3b,0x97,0x84,0xd9,0x79,0x06,0x36,0x50,0x4e,0x69,0x26,0x0c,0x03,0x9f,0x5c,0x26,0xd2,0x18,0xd5,0xe7,0x7d,0x29,0x72,0x39,0xb9,0x0c,0xbe,0xc7,0x1d,0x24,0x48,0x80,0x30,0x63,0x8b,0x4d,0x9b,0xf1,0x32,0x08,0x93,0x28,0x02,0x0d,0xc9,0xdf,0xd3,0x45,0x19,0x27,0x46,0x68,0x29,0xe1,0x05}, - {0x5a,0x49,0x9c,0x2d,0xb3,0xee,0x82,0xba,0x7c,0xb9,0x2b,0xf1,0xfc,0xc8,0xef,0xce,0xe0,0xd1,0xb5,0x93,0xae,0xab,0x2d,0xb0,0x9b,0x8d,0x69,0x13,0x9c,0x0c,0xc0,0x39,0x50,0x45,0x2c,0x24,0xc8,0xbb,0xbf,0xad,0xd9,0x81,0x30,0xd0,0xec,0x0c,0xc8,0xbc,0x92,0xdf,0xc8,0xf5,0xa6,0x66,0x35,0x84,0x4c,0xce,0x58,0x82,0xd3,0x25,0xcf,0x78,0x68,0x9d,0x48,0x31,0x8e,0x6b,0xae,0x15,0x87,0xf0,0x2b,0x9c,0xab,0x1c,0x85,0xaa,0x05,0xfa,0x4e,0xf0,0x97,0x5a,0xa7,0xc9,0x32,0xf8,0x3f,0x6b,0x07,0x52,0x6b,0x00}, - {0x1c,0x78,0x95,0x9d,0xe1,0xcf,0xe0,0x29,0xe2,0x10,0x63,0x96,0x18,0xdf,0x81,0xb6,0x39,0x6b,0x51,0x70,0xd3,0x39,0xdf,0x57,0x22,0x61,0xc7,0x3b,0x44,0xe3,0x57,0x4d,0x2d,0x08,0xce,0xb9,0x16,0x7e,0xcb,0xf5,0x29,0xbc,0x7a,0x41,0x4c,0xf1,0x07,0x34,0xab,0xa7,0xf4,0x2b,0xce,0x6b,0xb3,0xd4,0xce,0x75,0x9f,0x1a,0x56,0xe9,0xe2,0x7d,0xcb,0x5e,0xa5,0xb6,0xf4,0xd4,0x70,0xde,0x99,0xdb,0x85,0x5d,0x7f,0x52,0x01,0x48,0x81,0x9a,0xee,0xd3,0x40,0xc4,0xc9,0xdb,0xed,0x29,0x60,0x1a,0xaf,0x90,0x2a,0x6b}, - {0x97,0x1e,0xe6,0x9a,0xfc,0xf4,0x23,0x69,0xd1,0x5f,0x3f,0xe0,0x1d,0x28,0x35,0x57,0x2d,0xd1,0xed,0xe6,0x43,0xae,0x64,0xa7,0x4a,0x3e,0x2d,0xd1,0xe9,0xf4,0xd8,0x5f,0x0a,0xd8,0xb2,0x5b,0x24,0xf3,0xeb,0x77,0x9b,0x07,0xb9,0x2f,0x47,0x1b,0x30,0xd8,0x33,0x73,0xee,0x4c,0xf2,0xe6,0x47,0xc6,0x09,0x21,0x6c,0x27,0xc8,0x12,0x58,0x46,0xd9,0x62,0x10,0x2a,0xb2,0xbe,0x43,0x4d,0x16,0xdc,0x31,0x38,0x75,0xfb,0x65,0x70,0xd7,0x68,0x29,0xde,0x7b,0x4a,0x0d,0x18,0x90,0x67,0xb1,0x1c,0x2b,0x2c,0xb3,0x05}, - {0xfd,0xa8,0x4d,0xd2,0xcc,0x5e,0xc0,0xc8,0x83,0xef,0xdf,0x05,0xac,0x1a,0xcf,0xa1,0x61,0xcd,0xf9,0x7d,0xf2,0xef,0xbe,0xdb,0x99,0x1e,0x47,0x7b,0xa3,0x56,0x55,0x3b,0x95,0x81,0xd5,0x7a,0x2c,0xa4,0xfc,0xf7,0xcc,0xf3,0x33,0x43,0x6e,0x28,0x14,0x32,0x9d,0x97,0x0b,0x34,0x0d,0x9d,0xc2,0xb6,0xe1,0x07,0x73,0x56,0x48,0x1a,0x77,0x31,0x82,0xd4,0x4d,0xe1,0x24,0xc5,0xb0,0x32,0xb6,0xa4,0x2b,0x1a,0x54,0x51,0xb3,0xed,0xf3,0x5a,0x2b,0x28,0x48,0x60,0xd1,0xa3,0xeb,0x36,0x73,0x7a,0xd2,0x79,0xc0,0x4f}, - {0x7f,0x2f,0xbf,0x89,0xb0,0x38,0xc9,0x51,0xa7,0xe9,0xdf,0x02,0x65,0xbd,0x97,0x24,0x53,0xe4,0x80,0x78,0x9c,0xc0,0xff,0xff,0x92,0x8e,0xf9,0xca,0xce,0x67,0x45,0x12,0x0d,0xc5,0x86,0x0c,0x44,0x8b,0x34,0xdc,0x51,0xe6,0x94,0xcc,0xc9,0xcb,0x37,0x13,0xb9,0x3c,0x3e,0x64,0x4d,0xf7,0x22,0x64,0x08,0xcd,0xe3,0xba,0xc2,0x70,0x11,0x24,0xb4,0x73,0xc4,0x0a,0x86,0xab,0xf9,0x3f,0x35,0xe4,0x13,0x01,0xee,0x1d,0x91,0xf0,0xaf,0xc4,0xc6,0xeb,0x60,0x50,0xe7,0x4a,0x0d,0x00,0x87,0x6c,0x96,0x12,0x86,0x3f}, - {0xde,0x0d,0x2a,0x78,0xc9,0x0c,0x9a,0x55,0x85,0x83,0x71,0xea,0xb2,0xcd,0x1d,0x55,0x8c,0x23,0xef,0x31,0x5b,0x86,0x62,0x7f,0x3d,0x61,0x73,0x79,0x76,0xa7,0x4a,0x50,0x13,0x8d,0x04,0x36,0xfa,0xfc,0x18,0x9c,0xdd,0x9d,0x89,0x73,0xb3,0x9d,0x15,0x29,0xaa,0xd0,0x92,0x9f,0x0b,0x35,0x9f,0xdc,0xd4,0x19,0x8a,0x87,0xee,0x7e,0xf5,0x26,0xb1,0xef,0x87,0x56,0xd5,0x2c,0xab,0x0c,0x7b,0xf1,0x7a,0x24,0x62,0xd1,0x80,0x51,0x67,0x24,0x5a,0x4f,0x34,0x5a,0xc1,0x85,0x69,0x30,0xba,0x9d,0x3d,0x94,0x41,0x40}, - {0x96,0xcc,0xeb,0x43,0xba,0xee,0xc0,0xc3,0xaf,0x9c,0xea,0x26,0x9c,0x9c,0x74,0x8d,0xc6,0xcc,0x77,0x1c,0xee,0x95,0xfa,0xd9,0x0f,0x34,0x84,0x76,0xd9,0xa1,0x20,0x14,0xdd,0xaa,0x6c,0xa2,0x43,0x77,0x21,0x4b,0xce,0xb7,0x8a,0x64,0x24,0xb4,0xa6,0x47,0xe3,0xc9,0xfb,0x03,0x7a,0x4f,0x1d,0xcb,0x19,0xd0,0x00,0x98,0x42,0x31,0xd9,0x12,0x4f,0x59,0x37,0xd3,0x99,0x77,0xc6,0x00,0x7b,0xa4,0x3a,0xb2,0x40,0x51,0x3c,0x5e,0x95,0xf3,0x5f,0xe3,0x54,0x28,0x18,0x44,0x12,0xa0,0x59,0x43,0x31,0x92,0x4f,0x1b}, - {0x51,0x09,0x15,0x89,0x9d,0x10,0x5c,0x3e,0x6a,0x69,0xe9,0x2d,0x91,0xfa,0xce,0x39,0x20,0x30,0x5f,0x97,0x3f,0xe4,0xea,0x20,0xae,0x2d,0x13,0x7f,0x2a,0x57,0x9b,0x23,0xb1,0x66,0x98,0xa4,0x30,0x30,0xcf,0x33,0x59,0x48,0x5f,0x21,0xd2,0x73,0x1f,0x25,0xf6,0xf4,0xde,0x51,0x40,0xaa,0x82,0xab,0xf6,0x23,0x9a,0x6f,0xd5,0x91,0xf1,0x5f,0x68,0x90,0x2d,0xac,0x33,0xd4,0x9e,0x81,0x23,0x85,0xc9,0x5f,0x79,0xab,0x83,0x28,0x3d,0xeb,0x93,0x55,0x80,0x72,0x45,0xef,0xcb,0x36,0x8f,0x75,0x6a,0x52,0x0c,0x02}, - {0xbc,0xdb,0xd8,0x9e,0xf8,0x34,0x98,0x77,0x6c,0xa4,0x7c,0xdc,0xf9,0xaa,0xf2,0xc8,0x74,0xb0,0xe1,0xa3,0xdc,0x4c,0x52,0xa9,0x77,0x38,0x31,0x15,0x46,0xcc,0xaa,0x02,0x89,0xcc,0x42,0xf0,0x59,0xef,0x31,0xe9,0xb6,0x4b,0x12,0x8e,0x9d,0x9c,0x58,0x2c,0x97,0x59,0xc7,0xae,0x8a,0xe1,0xc8,0xad,0x0c,0xc5,0x02,0x56,0x0a,0xfe,0x2c,0x45,0xdf,0x77,0x78,0x64,0xa0,0xf7,0xa0,0x86,0x9f,0x7c,0x60,0x0e,0x27,0x64,0xc4,0xbb,0xc9,0x11,0xfb,0xf1,0x25,0xea,0x17,0xab,0x7b,0x87,0x4b,0x30,0x7b,0x7d,0xfb,0x4c}, - {0xfe,0x75,0x9b,0xb8,0x6c,0x3d,0xb4,0x72,0x80,0xdc,0x6a,0x9c,0xd9,0x94,0xc6,0x54,0x9f,0x4c,0xe3,0x3e,0x37,0xaa,0xc3,0xb8,0x64,0x53,0x07,0x39,0x2b,0x62,0xb4,0x14,0x12,0xef,0x89,0x97,0xc2,0x99,0x86,0xe2,0x0d,0x19,0x57,0xdf,0x71,0xcd,0x6e,0x2b,0xd0,0x70,0xc9,0xec,0x57,0xc8,0x43,0xc3,0xc5,0x3a,0x4d,0x43,0xbc,0x4c,0x1d,0x5b,0x26,0x9f,0x0a,0xcc,0x15,0x26,0xfb,0xb6,0xe5,0xcc,0x8d,0xb8,0x2b,0x0e,0x4f,0x3a,0x05,0xa7,0x69,0x33,0x8b,0x49,0x01,0x13,0xd1,0x2d,0x59,0x58,0x12,0xf7,0x98,0x2f}, - {0x56,0x9e,0x0f,0xb5,0x4c,0xa7,0x94,0x0c,0x20,0x13,0x8e,0x8e,0xa9,0xf4,0x1f,0x5b,0x67,0x0f,0x30,0x82,0x21,0xcc,0x2a,0x9a,0xf9,0xaa,0x06,0xd8,0x49,0xe2,0x6a,0x3a,0x01,0xa7,0x54,0x4f,0x44,0xae,0x12,0x2e,0xde,0xd7,0xcb,0xa9,0xf0,0x3e,0xfe,0xfc,0xe0,0x5d,0x83,0x75,0x0d,0x89,0xbf,0xce,0x54,0x45,0x61,0xe7,0xe9,0x62,0x80,0x1d,0x5a,0x7c,0x90,0xa9,0x85,0xda,0x7a,0x65,0x62,0x0f,0xb9,0x91,0xb5,0xa8,0x0e,0x1a,0xe9,0xb4,0x34,0xdf,0xfb,0x1d,0x0e,0x8d,0xf3,0x5f,0xf2,0xae,0xe8,0x8c,0x8b,0x29}, - {0xb2,0x0c,0xf7,0xef,0x53,0x79,0x92,0x2a,0x76,0x70,0x15,0x79,0x2a,0xc9,0x89,0x4b,0x6a,0xcf,0xa7,0x30,0x7a,0x45,0x18,0x94,0x85,0xe4,0x5c,0x4d,0x40,0xa8,0xb8,0x34,0xde,0x65,0x21,0x0a,0xea,0x72,0x7a,0x83,0xf6,0x79,0xcf,0x0b,0xb4,0x07,0xab,0x3f,0x70,0xae,0x38,0x77,0xc7,0x36,0x16,0x52,0xdc,0xd7,0xa7,0x03,0x18,0x27,0xa6,0x6b,0x35,0x33,0x69,0x83,0xb5,0xec,0x6e,0xc2,0xfd,0xfe,0xb5,0x63,0xdf,0x13,0xa8,0xd5,0x73,0x25,0xb2,0xa4,0x9a,0xaa,0x93,0xa2,0x6a,0x1c,0x5e,0x46,0xdd,0x2b,0xd6,0x71}, - {0x80,0xdf,0x78,0xd3,0x28,0xcc,0x33,0x65,0xb4,0xa4,0x0f,0x0a,0x79,0x43,0xdb,0xf6,0x5a,0xda,0x01,0xf7,0xf9,0x5f,0x64,0xe3,0xa4,0x2b,0x17,0xf3,0x17,0xf3,0xd5,0x74,0xf5,0x5e,0xf7,0xb1,0xda,0xb5,0x2d,0xcd,0xf5,0x65,0xb0,0x16,0xcf,0x95,0x7f,0xd7,0x85,0xf0,0x49,0x3f,0xea,0x1f,0x57,0x14,0x3d,0x2b,0x2b,0x26,0x21,0x36,0x33,0x1c,0x81,0xca,0xd9,0x67,0x54,0xe5,0x6f,0xa8,0x37,0x8c,0x29,0x2b,0x75,0x7c,0x8b,0x39,0x3b,0x62,0xac,0xe3,0x92,0x08,0x6d,0xda,0x8c,0xd9,0xe9,0x47,0x45,0xcc,0xeb,0x4a}, - {0xc9,0x01,0x6d,0x27,0x1b,0x07,0xf0,0x12,0x70,0x8c,0xc4,0x86,0xc5,0xba,0xb8,0xe7,0xa9,0xfb,0xd6,0x71,0x9b,0x12,0x08,0x53,0x92,0xb7,0x3d,0x5a,0xf9,0xfb,0x88,0x5d,0x10,0xb6,0x54,0x73,0x9e,0x8d,0x40,0x0b,0x6e,0x5b,0xa8,0x5b,0x53,0x32,0x6b,0x80,0x07,0xa2,0x58,0x4a,0x03,0x3a,0xe6,0xdb,0x2c,0xdf,0xa1,0xc9,0xdd,0xd9,0x3b,0x17,0xdf,0x72,0x58,0xfe,0x1e,0x0f,0x50,0x2b,0xc1,0x18,0x39,0xd4,0x2e,0x58,0xd6,0x58,0xe0,0x3a,0x67,0xc9,0x8e,0x27,0xed,0xe6,0x19,0xa3,0x9e,0xb1,0x13,0xcd,0xe1,0x06}, - {0x23,0x6f,0x16,0x6f,0x51,0xad,0xd0,0x40,0xbe,0x6a,0xab,0x1f,0x93,0x32,0x8e,0x11,0x8e,0x08,0x4d,0xa0,0x14,0x5e,0xe3,0x3f,0x66,0x62,0xe1,0x26,0x35,0x60,0x80,0x30,0x53,0x03,0x5b,0x9e,0x62,0xaf,0x2b,0x47,0x47,0x04,0x8d,0x27,0x90,0x0b,0xaa,0x3b,0x27,0xbf,0x43,0x96,0x46,0x5f,0x78,0x0c,0x13,0x7b,0x83,0x8d,0x1a,0x6a,0x3a,0x7f,0x0b,0x80,0x3d,0x5d,0x39,0x44,0xe6,0xf7,0xf6,0xed,0x01,0xc9,0x55,0xd5,0xa8,0x95,0x39,0x63,0x2c,0x59,0x30,0x78,0xcd,0x68,0x7e,0x30,0x51,0x2e,0xed,0xfd,0xd0,0x30}, - {0xb3,0x33,0x12,0xf2,0x1a,0x4d,0x59,0xe0,0x9c,0x4d,0xcc,0xf0,0x8e,0xe7,0xdb,0x1b,0x77,0x9a,0x49,0x8f,0x7f,0x18,0x65,0x69,0x68,0x98,0x09,0x2c,0x20,0x14,0x92,0x0a,0x50,0x47,0xb8,0x68,0x1e,0x97,0xb4,0x9c,0xcf,0xbb,0x64,0x66,0x29,0x72,0x95,0xa0,0x2b,0x41,0xfa,0x72,0x26,0xe7,0x8d,0x5c,0xd9,0x89,0xc5,0x51,0x43,0x08,0x15,0x46,0x2e,0xa0,0xb9,0xae,0xc0,0x19,0x90,0xbc,0xae,0x4c,0x03,0x16,0x0d,0x11,0xc7,0x55,0xec,0x32,0x99,0x65,0x01,0xf5,0x6d,0x0e,0xfe,0x5d,0xca,0x95,0x28,0x0d,0xca,0x3b}, - {0xa4,0x62,0x5d,0x3c,0xbc,0x31,0xf0,0x40,0x60,0x7a,0xf0,0xcf,0x3e,0x8b,0xfc,0x19,0x45,0xb5,0x0f,0x13,0xa2,0x3d,0x18,0x98,0xcd,0x13,0x8f,0xae,0xdd,0xde,0x31,0x56,0xbf,0x01,0xcc,0x9e,0xb6,0x8e,0x68,0x9c,0x6f,0x89,0x44,0xa6,0xad,0x83,0xbc,0xf0,0xe2,0x9f,0x7a,0x5f,0x5f,0x95,0x2d,0xca,0x41,0x82,0xf2,0x8d,0x03,0xb4,0xa8,0x4e,0x02,0xd2,0xca,0xf1,0x0a,0x46,0xed,0x2a,0x83,0xee,0x8c,0xa4,0x05,0x53,0x30,0x46,0x5f,0x1a,0xf1,0x49,0x45,0x77,0x21,0x91,0x63,0xa4,0x2c,0x54,0x30,0x09,0xce,0x24}, - {0x06,0xc1,0x06,0xfd,0xf5,0x90,0xe8,0x1f,0xf2,0x10,0x88,0x5d,0x35,0x68,0xc4,0xb5,0x3e,0xaf,0x8c,0x6e,0xfe,0x08,0x78,0x82,0x4b,0xd7,0x06,0x8a,0xc2,0xe3,0xd4,0x41,0x85,0x0b,0xf3,0xfd,0x55,0xa1,0xcf,0x3f,0xa4,0x2e,0x37,0x36,0x8e,0x16,0xf7,0xd2,0x44,0xf8,0x92,0x64,0xde,0x64,0xe0,0xb2,0x80,0x42,0x4f,0x32,0xa7,0x28,0x99,0x54,0x2e,0x1a,0xee,0x63,0xa7,0x32,0x6e,0xf2,0xea,0xfd,0x5f,0xd2,0xb7,0xe4,0x91,0xae,0x69,0x4d,0x7f,0xd1,0x3b,0xd3,0x3b,0xbc,0x6a,0xff,0xdc,0xc0,0xde,0x66,0x1b,0x49}, - {0xa7,0x32,0xea,0xc7,0x3d,0xb1,0xf5,0x98,0x98,0xdb,0x16,0x7e,0xcc,0xf8,0xd5,0xe3,0x47,0xd9,0xf8,0xcb,0x52,0xbf,0x0a,0xac,0xac,0xe4,0x5e,0xc8,0xd0,0x38,0xf3,0x08,0xa1,0x64,0xda,0xd0,0x8e,0x4a,0xf0,0x75,0x4b,0x28,0xe2,0x67,0xaf,0x2c,0x22,0xed,0xa4,0x7b,0x7b,0x1f,0x79,0xa3,0x34,0x82,0x67,0x8b,0x01,0xb7,0xb0,0xb8,0xf6,0x4c,0xbd,0x73,0x1a,0x99,0x21,0xa8,0x83,0xc3,0x7a,0x0c,0x32,0xdf,0x01,0xbc,0x27,0xab,0x63,0x70,0x77,0x84,0x1b,0x33,0x3d,0xc1,0x99,0x8a,0x07,0xeb,0x82,0x4a,0x0d,0x53}, - {0x25,0x48,0xf9,0xe1,0x30,0x36,0x4c,0x00,0x5a,0x53,0xab,0x8c,0x26,0x78,0x2d,0x7e,0x8b,0xff,0x84,0xcc,0x23,0x23,0x48,0xc7,0xb9,0x70,0x17,0x10,0x3f,0x75,0xea,0x65,0x9e,0xbf,0x9a,0x6c,0x45,0x73,0x69,0x6d,0x80,0xa8,0x00,0x49,0xfc,0xb2,0x7f,0x25,0x50,0xb8,0xcf,0xc8,0x12,0xf4,0xac,0x2b,0x5b,0xbd,0xbf,0x0c,0xe0,0xe7,0xb3,0x0d,0x63,0x63,0x09,0xe2,0x3e,0xfc,0x66,0x3d,0x6b,0xcb,0xb5,0x61,0x7f,0x2c,0xd6,0x81,0x1a,0x3b,0x44,0x13,0x42,0x04,0xbe,0x0f,0xdb,0xa1,0xe1,0x21,0x19,0xec,0xa4,0x02}, - {0xa2,0xb8,0x24,0x3b,0x9a,0x25,0xe6,0x5c,0xb8,0xa0,0xaf,0x45,0xcc,0x7a,0x57,0xb8,0x37,0x70,0xa0,0x8b,0xe8,0xe6,0xcb,0xcc,0xbf,0x09,0x78,0x12,0x51,0x3c,0x14,0x3d,0x5f,0x79,0xcf,0xf1,0x62,0x61,0xc8,0xf5,0xf2,0x57,0xee,0x26,0x19,0x86,0x8c,0x11,0x78,0x35,0x06,0x1c,0x85,0x24,0x21,0x17,0xcf,0x7f,0x06,0xec,0x5d,0x2b,0xd1,0x36,0x57,0x45,0x15,0x79,0x91,0x27,0x6d,0x12,0x0a,0x3a,0x78,0xfc,0x5c,0x8f,0xe4,0xd5,0xac,0x9b,0x17,0xdf,0xe8,0xb6,0xbd,0x36,0x59,0x28,0xa8,0x5b,0x88,0x17,0xf5,0x2e}, - {0xdc,0xae,0x58,0x8c,0x4e,0x97,0x37,0x46,0xa4,0x41,0xf0,0xab,0xfb,0x22,0xef,0xb9,0x8a,0x71,0x80,0xe9,0x56,0xd9,0x85,0xe1,0xa6,0xa8,0x43,0xb1,0xfa,0x78,0x1b,0x2f,0x51,0x2f,0x5b,0x30,0xfb,0xbf,0xee,0x96,0xb8,0x96,0x95,0x88,0xad,0x38,0xf9,0xd3,0x25,0xdd,0xd5,0x46,0xc7,0x2d,0xf5,0xf0,0x95,0x00,0x3a,0xbb,0x90,0x82,0x96,0x57,0x01,0xe1,0x20,0x0a,0x43,0xb8,0x1a,0xf7,0x47,0xec,0xf0,0x24,0x8d,0x65,0x93,0xf3,0xd1,0xee,0xe2,0x6e,0xa8,0x09,0x75,0xcf,0xe1,0xa3,0x2a,0xdc,0x35,0x3e,0xc4,0x7d}, - {0xc3,0xd9,0x7d,0x88,0x65,0x66,0x96,0x85,0x55,0x53,0xb0,0x4b,0x31,0x9b,0x0f,0xc9,0xb1,0x79,0x20,0xef,0xf8,0x8d,0xe0,0xc6,0x2f,0xc1,0x8c,0x75,0x16,0x20,0xf7,0x7e,0x18,0x97,0x3e,0x27,0x5c,0x2a,0x78,0x5a,0x94,0xfd,0x4e,0x5e,0x99,0xc6,0x76,0x35,0x3e,0x7d,0x23,0x1f,0x05,0xd8,0x2e,0x0f,0x99,0x0a,0xd5,0x82,0x1d,0xb8,0x4f,0x04,0xd9,0xe3,0x07,0xa9,0xc5,0x18,0xdf,0xc1,0x59,0x63,0x4c,0xce,0x1d,0x37,0xb3,0x57,0x49,0xbb,0x01,0xb2,0x34,0x45,0x70,0xca,0x2e,0xdd,0x30,0x9c,0x3f,0x82,0x79,0x7f}, - {0xe8,0x13,0xb5,0xa3,0x39,0xd2,0x34,0x83,0xd8,0xa8,0x1f,0xb9,0xd4,0x70,0x36,0xc1,0x33,0xbd,0x90,0xf5,0x36,0x41,0xb5,0x12,0xb4,0xd9,0x84,0xd7,0x73,0x03,0x4e,0x0a,0xba,0x87,0xf5,0x68,0xf0,0x1f,0x9c,0x6a,0xde,0xc8,0x50,0x00,0x4e,0x89,0x27,0x08,0xe7,0x5b,0xed,0x7d,0x55,0x99,0xbf,0x3c,0xf0,0xd6,0x06,0x1c,0x43,0xb0,0xa9,0x64,0x19,0x29,0x7d,0x5b,0xa1,0xd6,0xb3,0x2e,0x35,0x82,0x3a,0xd5,0xa0,0xf6,0xb4,0xb0,0x47,0x5d,0xa4,0x89,0x43,0xce,0x56,0x71,0x6c,0x34,0x18,0xce,0x0a,0x7d,0x1a,0x07}, - {0x0b,0xba,0x87,0xc8,0xaa,0x2d,0x07,0xd3,0xee,0x62,0xa5,0xbf,0x05,0x29,0x26,0x01,0x8b,0x76,0xef,0xc0,0x02,0x30,0x54,0xcf,0x9c,0x7e,0xea,0x46,0x71,0xcc,0x3b,0x2c,0x31,0x44,0xe1,0x20,0x52,0x35,0x0c,0xcc,0x41,0x51,0xb1,0x09,0x07,0x95,0x65,0x0d,0x36,0x5f,0x9d,0x20,0x1b,0x62,0xf5,0x9a,0xd3,0x55,0x77,0x61,0xf7,0xbc,0x69,0x7c,0x5f,0x29,0xe8,0x04,0xeb,0xd7,0xf0,0x07,0x7d,0xf3,0x50,0x2f,0x25,0x18,0xdb,0x10,0xd7,0x98,0x17,0x17,0xa3,0xa9,0x51,0xe9,0x1d,0xa5,0xac,0x22,0x73,0x9a,0x5a,0x6f}, - {0xc5,0xc6,0x41,0x2f,0x0c,0x00,0xa1,0x8b,0x9b,0xfb,0xfe,0x0c,0xc1,0x79,0x9f,0xc4,0x9f,0x1c,0xc5,0x3c,0x70,0x47,0xfa,0x4e,0xca,0xaf,0x47,0xe1,0xa2,0x21,0x4e,0x49,0xbe,0x44,0xd9,0xa3,0xeb,0xd4,0x29,0xe7,0x9e,0xaf,0x78,0x80,0x40,0x09,0x9e,0x8d,0x03,0x9c,0x86,0x47,0x7a,0x56,0x25,0x45,0x24,0x3b,0x8d,0xee,0x80,0x96,0xab,0x02,0x9a,0x0d,0xe5,0xdd,0x85,0x8a,0xa4,0xef,0x49,0xa2,0xb9,0x0f,0x4e,0x22,0x9a,0x21,0xd9,0xf6,0x1e,0xd9,0x1d,0x1f,0x09,0xfa,0x34,0xbb,0x46,0xea,0xcb,0x76,0x5d,0x6b}, - {0x94,0xd9,0x0c,0xec,0x6c,0x55,0x57,0x88,0xba,0x1d,0xd0,0x5c,0x6f,0xdc,0x72,0x64,0x77,0xb4,0x42,0x8f,0x14,0x69,0x01,0xaf,0x54,0x73,0x27,0x85,0xf6,0x33,0xe3,0x0a,0x22,0x25,0x78,0x1e,0x17,0x41,0xf9,0xe0,0xd3,0x36,0x69,0x03,0x74,0xae,0xe6,0xf1,0x46,0xc7,0xfc,0xd0,0xa2,0x3e,0x8b,0x40,0x3e,0x31,0xdd,0x03,0x9c,0x86,0xfb,0x16,0x62,0x09,0xb6,0x33,0x97,0x19,0x8e,0x28,0x33,0xe1,0xab,0xd8,0xb4,0x72,0xfc,0x24,0x3e,0xd0,0x91,0x09,0xed,0xf7,0x11,0x48,0x75,0xd0,0x70,0x8f,0x8b,0xe3,0x81,0x3f}, - {0xfe,0xaf,0xd9,0x7e,0xcc,0x0f,0x91,0x7f,0x4b,0x87,0x65,0x24,0xa1,0xb8,0x5c,0x54,0x04,0x47,0x0c,0x4b,0xd2,0x7e,0x39,0xa8,0x93,0x09,0xf5,0x04,0xc1,0x0f,0x51,0x50,0x24,0xc8,0x17,0x5f,0x35,0x7f,0xdb,0x0a,0xa4,0x99,0x42,0xd7,0xc3,0x23,0xb9,0x74,0xf7,0xea,0xf8,0xcb,0x8b,0x3e,0x7c,0xd5,0x3d,0xdc,0xde,0x4c,0xd3,0xe2,0xd3,0x0a,0x9d,0x24,0x6e,0x33,0xc5,0x0f,0x0c,0x6f,0xd9,0xcf,0x31,0xc3,0x19,0xde,0x5e,0x74,0x1c,0xfe,0xee,0x09,0x00,0xfd,0xd6,0xf2,0xbe,0x1e,0xfa,0xf0,0x8b,0x15,0x7c,0x12}, - {0xa2,0x79,0x98,0x2e,0x42,0x7c,0x19,0xf6,0x47,0x36,0xca,0x52,0xd4,0xdd,0x4a,0xa4,0xcb,0xac,0x4e,0x4b,0xc1,0x3f,0x41,0x9b,0x68,0x4f,0xef,0x07,0x7d,0xf8,0x4e,0x35,0x74,0xb9,0x51,0xae,0xc4,0x8f,0xa2,0xde,0x96,0xfe,0x4d,0x74,0xd3,0x73,0x99,0x1d,0xa8,0x48,0x38,0x87,0x0b,0x68,0x40,0x62,0x95,0xdf,0x67,0xd1,0x79,0x24,0xd8,0x4e,0x75,0xd9,0xc5,0x60,0x22,0xb5,0xe3,0xfe,0xb8,0xb0,0x41,0xeb,0xfc,0x2e,0x35,0x50,0x3c,0x65,0xf6,0xa9,0x30,0xac,0x08,0x88,0x6d,0x23,0x39,0x05,0xd2,0x92,0x2d,0x30}, - {0x3d,0x28,0xa4,0xbc,0xa2,0xc1,0x13,0x78,0xd9,0x3d,0x86,0xa1,0x91,0xf0,0x62,0xed,0x86,0xfa,0x68,0xc2,0xb8,0xbc,0xc7,0xae,0x4c,0xae,0x1c,0x6f,0xb7,0xd3,0xe5,0x10,0x77,0xf1,0xe0,0xe4,0xb6,0x6f,0xbc,0x2d,0x93,0x6a,0xbd,0xa4,0x29,0xbf,0xe1,0x04,0xe8,0xf6,0x7a,0x78,0xd4,0x66,0x19,0x5e,0x60,0xd0,0x26,0xb4,0x5e,0x5f,0xdc,0x0e,0x67,0x8e,0xda,0x53,0xd6,0xbf,0x53,0x54,0x41,0xf6,0xa9,0x24,0xec,0x1e,0xdc,0xe9,0x23,0x8a,0x57,0x03,0x3b,0x26,0x87,0xbf,0x72,0xba,0x1c,0x36,0x51,0x6c,0xb4,0x45}, - {0xa1,0x7f,0x4f,0x31,0xbf,0x2a,0x40,0xa9,0x50,0xf4,0x8c,0x8e,0xdc,0xf1,0x57,0xe2,0x84,0xbe,0xa8,0x23,0x4b,0xd5,0xbb,0x1d,0x3b,0x71,0xcb,0x6d,0xa3,0xbf,0x77,0x21,0xe4,0xe3,0x7f,0x8a,0xdd,0x4d,0x9d,0xce,0x30,0x0e,0x62,0x76,0x56,0x64,0x13,0xab,0x58,0x99,0x0e,0xb3,0x7b,0x4f,0x59,0x4b,0xdf,0x29,0x12,0x32,0xef,0x0a,0x1c,0x5c,0x8f,0xdb,0x79,0xfa,0xbc,0x1b,0x08,0x37,0xb3,0x59,0x5f,0xc2,0x1e,0x81,0x48,0x60,0x87,0x24,0x83,0x9c,0x65,0x76,0x7a,0x08,0xbb,0xb5,0x8a,0x7d,0x38,0x19,0xe6,0x4a}, - {0x2e,0xa3,0x44,0x53,0xaa,0xf6,0xdb,0x8d,0x78,0x40,0x1b,0xb4,0xb4,0xea,0x88,0x7d,0x60,0x0d,0x13,0x4a,0x97,0xeb,0xb0,0x5e,0x03,0x3e,0xbf,0x17,0x1b,0xd9,0x00,0x1a,0x83,0xfb,0x5b,0x98,0x44,0x7e,0x11,0x61,0x36,0x31,0x96,0x71,0x2a,0x46,0xe0,0xfc,0x4b,0x90,0x25,0xd4,0x48,0x34,0xac,0x83,0x64,0x3d,0xa4,0x5b,0xbe,0x5a,0x68,0x75,0xb2,0xf2,0x61,0xeb,0x33,0x09,0x96,0x6e,0x52,0x49,0xff,0xc9,0xa8,0x0f,0x3d,0x54,0x69,0x65,0xf6,0x7a,0x10,0x75,0x72,0xdf,0xaa,0xe6,0xb0,0x23,0xb6,0x29,0x55,0x13}, - {0x18,0xd5,0xd1,0xad,0xd7,0xdb,0xf0,0x18,0x11,0x1f,0xc1,0xcf,0x88,0x78,0x9f,0x97,0x9b,0x75,0x14,0x71,0xf0,0xe1,0x32,0x87,0x01,0x3a,0xca,0x65,0x1a,0xb8,0xb5,0x79,0xfe,0x83,0x2e,0xe2,0xbc,0x16,0xc7,0xf5,0xc1,0x85,0x09,0xe8,0x19,0xeb,0x2b,0xb4,0xae,0x4a,0x25,0x14,0x37,0xa6,0x9d,0xec,0x13,0xa6,0x90,0x15,0x05,0xea,0x72,0x59,0x11,0x78,0x8f,0xdc,0x20,0xac,0xd4,0x0f,0xa8,0x4f,0x4d,0xac,0x94,0xd2,0x9a,0x9a,0x34,0x04,0x36,0xb3,0x64,0x2d,0x1b,0xc0,0xdb,0x3b,0x5f,0x90,0x95,0x9c,0x7e,0x4f}, - {0x2e,0x30,0x81,0x57,0xbc,0x4b,0x67,0x62,0x0f,0xdc,0xad,0x89,0x39,0x0f,0x52,0xd8,0xc6,0xd9,0xfb,0x53,0xae,0x99,0x29,0x8c,0x4c,0x8e,0x63,0x2e,0xd9,0x3a,0x99,0x31,0xfe,0x99,0x52,0x35,0x3d,0x44,0xc8,0x71,0xd7,0xea,0xeb,0xdb,0x1c,0x3b,0xcd,0x8b,0x66,0x94,0xa4,0xf1,0x9e,0x49,0x92,0x80,0xc8,0xad,0x44,0xa1,0xc4,0xee,0x42,0x19,0x92,0x49,0x23,0xae,0x19,0x53,0xac,0x7d,0x92,0x3e,0xea,0x0c,0x91,0x3d,0x1b,0x2c,0x22,0x11,0x3c,0x25,0x94,0xe4,0x3c,0x55,0x75,0xca,0xf9,0x4e,0x31,0x65,0x0a,0x2a}, - {0xc2,0x27,0xf9,0xf7,0x7f,0x93,0xb7,0x2d,0x35,0xa6,0xd0,0x17,0x06,0x1f,0x74,0xdb,0x76,0xaf,0x55,0x11,0xa2,0xf3,0x82,0x59,0xed,0x2d,0x7c,0x64,0x18,0xe2,0xf6,0x4c,0x3a,0x79,0x1c,0x3c,0xcd,0x1a,0x36,0xcf,0x3b,0xbc,0x35,0x5a,0xac,0xbc,0x9e,0x2f,0xab,0xa6,0xcd,0xa8,0xe9,0x60,0xe8,0x60,0x13,0x1a,0xea,0x6d,0x9b,0xc3,0x5d,0x05,0xb6,0x5b,0x8d,0xc2,0x7c,0x22,0x19,0xb1,0xab,0xff,0x4d,0x77,0xbc,0x4e,0xe2,0x07,0x89,0x2c,0xa3,0xe4,0xce,0x78,0x3c,0xa8,0xb6,0x24,0xaa,0x10,0x77,0x30,0x1a,0x12}, - {0x97,0x4a,0x03,0x9f,0x5e,0x5d,0xdb,0xe4,0x2d,0xbc,0x34,0x30,0x09,0xfc,0x53,0xe1,0xb1,0xd3,0x51,0x95,0x91,0x46,0x05,0x46,0x2d,0xe5,0x40,0x7a,0x6c,0xc7,0x3f,0x33,0xc9,0x83,0x74,0xc7,0x3e,0x71,0x59,0xd6,0xaf,0x96,0x2b,0xb8,0x77,0xe0,0xbf,0x88,0xd3,0xbc,0x97,0x10,0x23,0x28,0x9e,0x28,0x9b,0x3a,0xed,0x6c,0x4a,0xb9,0x7b,0x52,0x2e,0x48,0x5b,0x99,0x2a,0x99,0x3d,0x56,0x01,0x38,0x38,0x6e,0x7c,0xd0,0x05,0x34,0xe5,0xd8,0x64,0x2f,0xde,0x35,0x50,0x48,0xf7,0xa9,0xa7,0x20,0x9b,0x06,0x89,0x6b}, - {0x0d,0x22,0x70,0x62,0x41,0xa0,0x2a,0x81,0x4e,0x5b,0x24,0xf9,0xfa,0x89,0x5a,0x99,0x05,0xef,0x72,0x50,0xce,0xc4,0xad,0xff,0x73,0xeb,0x73,0xaa,0x03,0x21,0xbc,0x23,0x77,0xdb,0xc7,0xb5,0x8c,0xfa,0x82,0x40,0x55,0xc1,0x34,0xc7,0xf8,0x86,0x86,0x06,0x7e,0xa5,0xe7,0xf6,0xd9,0xc8,0xe6,0x29,0xcf,0x9b,0x63,0xa7,0x08,0xd3,0x73,0x04,0x05,0x9e,0x58,0x03,0x26,0x79,0xee,0xca,0x92,0xc4,0xdc,0x46,0x12,0x42,0x4b,0x2b,0x4f,0xa9,0x01,0xe6,0x74,0xef,0xa1,0x02,0x1a,0x34,0x04,0xde,0xbf,0x73,0x2f,0x10}, - {0xc6,0x45,0x57,0x7f,0xab,0xb9,0x18,0xeb,0x90,0xc6,0x87,0x57,0xee,0x8a,0x3a,0x02,0xa9,0xaf,0xf7,0x2d,0xda,0x12,0x27,0xb7,0x3d,0x01,0x5c,0xea,0x25,0x7d,0x59,0x36,0x9a,0x1c,0x51,0xb5,0xe0,0xda,0xb4,0xa2,0x06,0xff,0xff,0x2b,0x29,0x60,0xc8,0x7a,0x34,0x42,0x50,0xf5,0x5d,0x37,0x1f,0x98,0x2d,0xa1,0x4e,0xda,0x25,0xd7,0x6b,0x3f,0xac,0x58,0x60,0x10,0x7b,0x8d,0x4d,0x73,0x5f,0x90,0xc6,0x6f,0x9e,0x57,0x40,0xd9,0x2d,0x93,0x02,0x92,0xf9,0xf8,0x66,0x64,0xd0,0xd6,0x60,0xda,0x19,0xcc,0x7e,0x7b}, - {0x0d,0x69,0x5c,0x69,0x3c,0x37,0xc2,0x78,0x6e,0x90,0x42,0x06,0x66,0x2e,0x25,0xdd,0xd2,0x2b,0xe1,0x4a,0x44,0x44,0x1d,0x95,0x56,0x39,0x74,0x01,0x76,0xad,0x35,0x42,0x9b,0xfa,0x7c,0xa7,0x51,0x4a,0xae,0x6d,0x50,0x86,0xa3,0xe7,0x54,0x36,0x26,0x82,0xdb,0x82,0x2d,0x8f,0xcd,0xff,0xbb,0x09,0xba,0xca,0xf5,0x1b,0x66,0xdc,0xbe,0x03,0xf5,0x75,0x89,0x07,0x0d,0xcb,0x58,0x62,0x98,0xf2,0x89,0x91,0x54,0x42,0x29,0x49,0xe4,0x6e,0xe3,0xe2,0x23,0xb4,0xca,0xa0,0xa1,0x66,0xf0,0xcd,0xb0,0xe2,0x7c,0x0e}, - {0xa3,0x85,0x8c,0xc4,0x3a,0x64,0x94,0xc4,0xad,0x39,0x61,0x3c,0xf4,0x1d,0x36,0xfd,0x48,0x4d,0xe9,0x3a,0xdd,0x17,0xdb,0x09,0x4a,0x67,0xb4,0x8f,0x5d,0x0a,0x6e,0x66,0xf9,0x70,0x4b,0xd9,0xdf,0xfe,0xa6,0xfe,0x2d,0xba,0xfc,0xc1,0x51,0xc0,0x30,0xf1,0x89,0xab,0x2f,0x7f,0x7e,0xd4,0x82,0x48,0xb5,0xee,0xec,0x8a,0x13,0x56,0x52,0x61,0x0d,0xcb,0x70,0x48,0x4e,0xf6,0xbb,0x2a,0x6b,0x8b,0x45,0xaa,0xf0,0xbc,0x65,0xcd,0x5d,0x98,0xe8,0x75,0xba,0x4e,0xbe,0x9a,0xe4,0xde,0x14,0xd5,0x10,0xc8,0x0b,0x7f}, - {0x6f,0x13,0xf4,0x26,0xa4,0x6b,0x00,0xb9,0x35,0x30,0xe0,0x57,0x9e,0x36,0x67,0x8d,0x28,0x3c,0x46,0x4f,0xd9,0xdf,0xc8,0xcb,0xf5,0xdb,0xee,0xf8,0xbc,0x8d,0x1f,0x0d,0xa0,0x13,0x72,0x73,0xad,0x9d,0xac,0x83,0x98,0x2e,0xf7,0x2e,0xba,0xf8,0xf6,0x9f,0x57,0x69,0xec,0x43,0xdd,0x2e,0x1e,0x31,0x75,0xab,0xc5,0xde,0x7d,0x90,0x3a,0x1d,0xdc,0x81,0xd0,0x3e,0x31,0x93,0x16,0xba,0x80,0x34,0x1b,0x85,0xad,0x9f,0x32,0x29,0xcb,0x21,0x03,0x03,0x3c,0x01,0x28,0x01,0xe3,0xfd,0x1b,0xa3,0x44,0x1b,0x01,0x00}, - {0x0c,0x6c,0xc6,0x3f,0x6c,0xa0,0xdf,0x3f,0xd2,0x0d,0xd6,0x4d,0x8e,0xe3,0x40,0x5d,0x71,0x4d,0x8e,0x26,0x38,0x8b,0xe3,0x7a,0xe1,0x57,0x83,0x6e,0x91,0x8d,0xc4,0x3a,0x5c,0xa7,0x0a,0x6a,0x69,0x1f,0x56,0x16,0x6a,0xbd,0x52,0x58,0x5c,0x72,0xbf,0xc1,0xad,0x66,0x79,0x9a,0x7f,0xdd,0xa8,0x11,0x26,0x10,0x85,0xd2,0xa2,0x88,0xd9,0x63,0x2e,0x23,0xbd,0xaf,0x53,0x07,0x12,0x00,0x83,0xf6,0xd8,0xfd,0xb8,0xce,0x2b,0xe9,0x91,0x2b,0xe7,0x84,0xb3,0x69,0x16,0xf8,0x66,0xa0,0x68,0x23,0x2b,0xd5,0xfa,0x33}, - {0x16,0x1e,0xe4,0xc5,0xc6,0x49,0x06,0x54,0x35,0x77,0x3f,0x33,0x30,0x64,0xf8,0x0a,0x46,0xe7,0x05,0xf3,0xd2,0xfc,0xac,0xb2,0xa7,0xdc,0x56,0xa2,0x29,0xf4,0xc0,0x16,0xe8,0xcf,0x22,0xc4,0xd0,0xc8,0x2c,0x8d,0xcb,0x3a,0xa1,0x05,0x7b,0x4f,0x2b,0x07,0x6f,0xa5,0xf6,0xec,0xe6,0xb6,0xfe,0xa3,0xe2,0x71,0x0a,0xb9,0xcc,0x55,0xc3,0x3c,0x31,0x91,0x3e,0x90,0x43,0x94,0xb6,0xe9,0xce,0x37,0x56,0x7a,0xcb,0x94,0xa4,0xb8,0x44,0x92,0xba,0xba,0xa4,0xd1,0x7c,0xc8,0x68,0x75,0xae,0x6b,0x42,0xaf,0x1e,0x63}, - {0x9f,0xfe,0x66,0xda,0x10,0x04,0xe9,0xb3,0xa6,0xe5,0x16,0x6c,0x52,0x4b,0xdd,0x85,0x83,0xbf,0xf9,0x1e,0x61,0x97,0x3d,0xbc,0xb5,0x19,0xa9,0x1e,0x8b,0x64,0x99,0x55,0xe8,0x0d,0x70,0xa3,0xb9,0x75,0xd9,0x47,0x52,0x05,0xf8,0xe2,0xfb,0xc5,0x80,0x72,0xe1,0x5d,0xe4,0x32,0x27,0x8f,0x65,0x53,0xb5,0x80,0x5f,0x66,0x7f,0x2c,0x1f,0x43,0x19,0x7b,0x8f,0x85,0x44,0x63,0x02,0xd6,0x4a,0x51,0xea,0xa1,0x2f,0x35,0xab,0x14,0xd7,0xa9,0x90,0x20,0x1a,0x44,0x00,0x89,0x26,0x3b,0x25,0x91,0x5f,0x71,0x04,0x7b}, - {0x43,0xae,0xf6,0xac,0x28,0xbd,0xed,0x83,0xb4,0x7a,0x5c,0x7d,0x8b,0x7c,0x35,0x86,0x44,0x2c,0xeb,0xb7,0x69,0x47,0x40,0xc0,0x3f,0x58,0xf6,0xc2,0xf5,0x7b,0xb3,0x59,0xc6,0xba,0xe6,0xc4,0x80,0xc2,0x76,0xb3,0x0b,0x9b,0x1d,0x6d,0xdd,0xd3,0x0e,0x97,0x44,0xf9,0x0b,0x45,0x58,0x95,0x9a,0xb0,0x23,0xe2,0xcd,0x57,0xfa,0xac,0xd0,0x48,0x71,0xe6,0xab,0x7d,0xe4,0x26,0x0f,0xb6,0x37,0x3a,0x2f,0x62,0x97,0xa1,0xd1,0xf1,0x94,0x03,0x96,0xe9,0x7e,0xce,0x08,0x42,0xdb,0x3b,0x6d,0x33,0x91,0x41,0x23,0x16}, - {0xf6,0x7f,0x26,0xf6,0xde,0x99,0xe4,0xb9,0x43,0x08,0x2c,0x74,0x7b,0xca,0x72,0x77,0xb1,0xf2,0xa4,0xe9,0x3f,0x15,0xa0,0x23,0x06,0x50,0xd0,0xd5,0xec,0xdf,0xdf,0x2c,0x40,0x86,0xf3,0x1f,0xd6,0x9c,0x49,0xdd,0xa0,0x25,0x36,0x06,0xc3,0x9b,0xcd,0x29,0xc3,0x3d,0xd7,0x3d,0x02,0xd8,0xe2,0x51,0x31,0x92,0x3b,0x20,0x7a,0x70,0x25,0x4a,0x6a,0xed,0xf6,0x53,0x8a,0x66,0xb7,0x2a,0xa1,0x70,0xd1,0x1d,0x58,0x42,0x42,0x30,0x61,0x01,0xe2,0x3a,0x4c,0x14,0x00,0x40,0xfc,0x49,0x8e,0x24,0x6d,0x89,0x21,0x57}, - {0xae,0x1b,0x18,0xfd,0x17,0x55,0x6e,0x0b,0xb4,0x63,0xb9,0x2b,0x9f,0x62,0x22,0x90,0x25,0x46,0x06,0x32,0xe9,0xbc,0x09,0x55,0xda,0x13,0x3c,0xf6,0x74,0xdd,0x8e,0x57,0x4e,0xda,0xd0,0xa1,0x91,0x50,0x5d,0x28,0x08,0x3e,0xfe,0xb5,0xa7,0x6f,0xaa,0x4b,0xb3,0x93,0x93,0xe1,0x7c,0x17,0xe5,0x63,0xfd,0x30,0xb0,0xc4,0xaf,0x35,0xc9,0x03,0x3d,0x0c,0x2b,0x49,0xc6,0x76,0x72,0x99,0xfc,0x05,0xe2,0xdf,0xc4,0xc2,0xcc,0x47,0x3c,0x3a,0x62,0xdd,0x84,0x9b,0xd2,0xdc,0xa2,0xc7,0x88,0x02,0x59,0xab,0xc2,0x3e}, - {0xb9,0x7b,0xd8,0xe4,0x7b,0xd2,0xa0,0xa1,0xed,0x1a,0x39,0x61,0xeb,0x4d,0x8b,0xa9,0x83,0x9b,0xcb,0x73,0xd0,0xdd,0xa0,0x99,0xce,0xca,0x0f,0x20,0x5a,0xc2,0xd5,0x2d,0xcb,0xd1,0x32,0xae,0x09,0x3a,0x21,0xa7,0xd5,0xc2,0xf5,0x40,0xdf,0x87,0x2b,0x0f,0x29,0xab,0x1e,0xe8,0xc6,0xa4,0xae,0x0b,0x5e,0xac,0xdb,0x6a,0x6c,0xf6,0x1b,0x0e,0x7e,0x88,0x2c,0x79,0xe9,0xd5,0xab,0xe2,0x5d,0x6d,0x92,0xcb,0x18,0x00,0x02,0x1a,0x1e,0x5f,0xae,0xba,0xcd,0x69,0xba,0xbf,0x5f,0x8f,0xe8,0x5a,0xb3,0x48,0x05,0x73}, - {0xee,0xb8,0xa8,0xcb,0xa3,0x51,0x35,0xc4,0x16,0x5f,0x11,0xb2,0x1d,0x6f,0xa2,0x65,0x50,0x38,0x8c,0xab,0x52,0x4f,0x0f,0x76,0xca,0xb8,0x1d,0x41,0x3b,0x44,0x43,0x30,0x34,0xe3,0xd6,0xa1,0x4b,0x09,0x5b,0x80,0x19,0x3f,0x35,0x09,0x77,0xf1,0x3e,0xbf,0x2b,0x70,0x22,0x06,0xcb,0x06,0x3f,0x42,0xdd,0x45,0x78,0xd8,0x77,0x22,0x5a,0x58,0x62,0x89,0xd4,0x33,0x82,0x5f,0x8a,0xa1,0x7f,0x25,0x78,0xec,0xb5,0xc4,0x98,0x66,0xff,0x41,0x3e,0x37,0xa5,0x6f,0x8e,0xa7,0x1f,0x98,0xef,0x50,0x89,0x27,0x56,0x76}, - {0xc0,0xc8,0x1f,0xd5,0x59,0xcf,0xc3,0x38,0xf2,0xb6,0x06,0x05,0xfd,0xd2,0xed,0x9b,0x8f,0x0e,0x57,0xab,0x9f,0x10,0xbf,0x26,0xa6,0x46,0xb8,0xc1,0xa8,0x60,0x41,0x3f,0x9d,0xcf,0x86,0xea,0xa3,0x73,0x70,0xe1,0xdc,0x5f,0x15,0x07,0xb7,0xfb,0x8c,0x3a,0x8e,0x8a,0x83,0x31,0xfc,0xe7,0x53,0x48,0x16,0xf6,0x13,0xb6,0x84,0xf4,0xbb,0x28,0x7c,0x6c,0x13,0x6f,0x5c,0x2f,0x61,0xf2,0xbe,0x11,0xdd,0xf6,0x07,0xd1,0xea,0xaf,0x33,0x6f,0xde,0x13,0xd2,0x9a,0x7e,0x52,0x5d,0xf7,0x88,0x81,0x35,0xcb,0x79,0x1e}, - {0xf1,0xe3,0xf7,0xee,0xc3,0x36,0x34,0x01,0xf8,0x10,0x9e,0xfe,0x7f,0x6a,0x8b,0x82,0xfc,0xde,0xf9,0xbc,0xe5,0x08,0xf9,0x7f,0x31,0x38,0x3b,0x3a,0x1b,0x95,0xd7,0x65,0x81,0x81,0xe0,0xf5,0xd8,0x53,0xe9,0x77,0xd9,0xde,0x9d,0x29,0x44,0x0c,0xa5,0x84,0xe5,0x25,0x45,0x86,0x0c,0x2d,0x6c,0xdc,0xf4,0xf2,0xd1,0x39,0x2d,0xb5,0x8a,0x47,0x59,0xd1,0x52,0x92,0xd3,0xa4,0xa6,0x66,0x07,0xc8,0x1a,0x87,0xbc,0xe1,0xdd,0xe5,0x6f,0xc9,0xc1,0xa6,0x40,0x6b,0x2c,0xb8,0x14,0x22,0x21,0x1a,0x41,0x7a,0xd8,0x16}, - {0x15,0x62,0x06,0x42,0x5a,0x7e,0xbd,0xb3,0xc1,0x24,0x5a,0x0c,0xcd,0xe3,0x9b,0x87,0xb7,0x94,0xf9,0xd6,0xb1,0x5d,0xc0,0x57,0xa6,0x8c,0xf3,0x65,0x81,0x7c,0xf8,0x28,0x83,0x05,0x4e,0xd5,0xe2,0xd5,0xa4,0xfb,0xfa,0x99,0xbd,0x2e,0xd7,0xaf,0x1f,0xe2,0x8f,0x77,0xe9,0x6e,0x73,0xc2,0x7a,0x49,0xde,0x6d,0x5a,0x7a,0x57,0x0b,0x99,0x1f,0xd6,0xf7,0xe8,0x1b,0xad,0x4e,0x34,0xa3,0x8f,0x79,0xea,0xac,0xeb,0x50,0x1e,0x7d,0x52,0xe0,0x0d,0x52,0x9e,0x56,0xc6,0x77,0x3e,0x6d,0x4d,0x53,0xe1,0x2f,0x88,0x45}, - {0xd6,0x83,0x79,0x75,0x5d,0x34,0x69,0x66,0xa6,0x11,0xaa,0x17,0x11,0xed,0xb6,0x62,0x8f,0x12,0x5e,0x98,0x57,0x18,0xdd,0x7d,0xdd,0xf6,0x26,0xf6,0xb8,0xe5,0x8f,0x68,0xe4,0x6f,0x3c,0x94,0x29,0x99,0xac,0xd8,0xa2,0x92,0x83,0xa3,0x61,0xf1,0xf9,0xb5,0xf3,0x9a,0xc8,0xbe,0x13,0xdb,0x99,0x26,0x74,0xf0,0x05,0xe4,0x3c,0x84,0xcf,0x7d,0xc0,0x32,0x47,0x4a,0x48,0xd6,0x90,0x6c,0x99,0x32,0x56,0xca,0xfd,0x43,0x21,0xd5,0xe1,0xc6,0x5d,0x91,0xc3,0x28,0xbe,0xb3,0x1b,0x19,0x27,0x73,0x7e,0x68,0x39,0x67}, - {0xa6,0x75,0x56,0x38,0x14,0x20,0x78,0xef,0xe8,0xa9,0xfd,0xaa,0x30,0x9f,0x64,0xa2,0xcb,0xa8,0xdf,0x5c,0x50,0xeb,0xd1,0x4c,0xb3,0xc0,0x4d,0x1d,0xba,0x5a,0x11,0x46,0xc0,0x1a,0x0c,0xc8,0x9d,0xcc,0x6d,0xa6,0x36,0xa4,0x38,0x1b,0xf4,0x5c,0xa0,0x97,0xc6,0xd7,0xdb,0x95,0xbe,0xf3,0xeb,0xa7,0xab,0x7d,0x7e,0x8d,0xf6,0xb8,0xa0,0x7d,0x76,0xda,0xb5,0xc3,0x53,0x19,0x0f,0xd4,0x9b,0x9e,0x11,0x21,0x73,0x6f,0xac,0x1d,0x60,0x59,0xb2,0xfe,0x21,0x60,0xcc,0x03,0x4b,0x4b,0x67,0x83,0x7e,0x88,0x5f,0x5a}, - {0x11,0x3d,0xa1,0x70,0xcf,0x01,0x63,0x8f,0xc4,0xd0,0x0d,0x35,0x15,0xb8,0xce,0xcf,0x7e,0xa4,0xbc,0xa4,0xd4,0x97,0x02,0xf7,0x34,0x14,0x4d,0xe4,0x56,0xb6,0x69,0x36,0xb9,0x43,0xa6,0xa0,0xd3,0x28,0x96,0x9e,0x64,0x20,0xc3,0xe6,0x00,0xcb,0xc3,0xb5,0x32,0xec,0x2d,0x7c,0x89,0x02,0x53,0x9b,0x0c,0xc7,0xd1,0xd5,0xe2,0x7a,0xe3,0x43,0x33,0xe1,0xa6,0xed,0x06,0x3f,0x7e,0x38,0xc0,0x3a,0xa1,0x99,0x51,0x1d,0x30,0x67,0x11,0x38,0x26,0x36,0xf8,0xd8,0x5a,0xbd,0xbe,0xe9,0xd5,0x4f,0xcd,0xe6,0x21,0x6a}, - {0x5f,0xe6,0x46,0x30,0x0a,0x17,0xc6,0xf1,0x24,0x35,0xd2,0x00,0x2a,0x2a,0x71,0x58,0x55,0xb7,0x82,0x8c,0x3c,0xbd,0xdb,0x69,0x57,0xff,0x95,0xa1,0xf1,0xf9,0x6b,0x58,0xe3,0xb2,0x99,0x66,0x12,0x29,0x41,0xef,0x01,0x13,0x8d,0x70,0x47,0x08,0xd3,0x71,0xbd,0xb0,0x82,0x11,0xd0,0x32,0x54,0x32,0x36,0x8b,0x1e,0x00,0x07,0x1b,0x37,0x45,0x0b,0x79,0xf8,0x5e,0x8d,0x08,0xdb,0xa6,0xe5,0x37,0x09,0x61,0xdc,0xf0,0x78,0x52,0xb8,0x6e,0xa1,0x61,0xd2,0x49,0x03,0xac,0x79,0x21,0xe5,0x90,0x37,0xb0,0xaf,0x0e}, - {0x2f,0x04,0x48,0x37,0xc1,0x55,0x05,0x96,0x11,0xaa,0x0b,0x82,0xe6,0x41,0x9a,0x21,0x0c,0x6d,0x48,0x73,0x38,0xf7,0x81,0x1c,0x61,0xc6,0x02,0x5a,0x67,0xcc,0x9a,0x30,0x1d,0xae,0x75,0x0f,0x5e,0x80,0x40,0x51,0x30,0xcc,0x62,0x26,0xe3,0xfb,0x02,0xec,0x6d,0x39,0x92,0xea,0x1e,0xdf,0xeb,0x2c,0xb3,0x5b,0x43,0xc5,0x44,0x33,0xae,0x44,0xee,0x43,0xa5,0xbb,0xb9,0x89,0xf2,0x9c,0x42,0x71,0xc9,0x5a,0x9d,0x0e,0x76,0xf3,0xaa,0x60,0x93,0x4f,0xc6,0xe5,0x82,0x1d,0x8f,0x67,0x94,0x7f,0x1b,0x22,0xd5,0x62}, - {0x6d,0x93,0xd0,0x18,0x9c,0x29,0x4c,0x52,0x0c,0x1a,0x0c,0x8a,0x6c,0xb5,0x6b,0xc8,0x31,0x86,0x4a,0xdb,0x2e,0x05,0x75,0xa3,0x62,0x45,0x75,0xbc,0xe4,0xfd,0x0e,0x5c,0x3c,0x7a,0xf7,0x3a,0x26,0xd4,0x85,0x75,0x4d,0x14,0xe9,0xfe,0x11,0x7b,0xae,0xdf,0x3d,0x19,0xf7,0x59,0x80,0x70,0x06,0xa5,0x37,0x20,0x92,0x83,0x53,0x9a,0xf2,0x14,0xf5,0xd7,0xb2,0x25,0xdc,0x7e,0x71,0xdf,0x40,0x30,0xb5,0x99,0xdb,0x70,0xf9,0x21,0x62,0x4c,0xed,0xc3,0xb7,0x34,0x92,0xda,0x3e,0x09,0xee,0x7b,0x5c,0x36,0x72,0x5e}, - {0x7f,0x21,0x71,0x45,0x07,0xfc,0x5b,0x57,0x5b,0xd9,0x94,0x06,0x5d,0x67,0x79,0x37,0x33,0x1e,0x19,0xf4,0xbb,0x37,0x0a,0x9a,0xbc,0xea,0xb4,0x47,0x4c,0x10,0xf1,0x77,0x3e,0xb3,0x08,0x2f,0x06,0x39,0x93,0x7d,0xbe,0x32,0x9f,0xdf,0xe5,0x59,0x96,0x5b,0xfd,0xbd,0x9e,0x1f,0xad,0x3d,0xff,0xac,0xb7,0x49,0x73,0xcb,0x55,0x05,0xb2,0x70,0x4c,0x2c,0x11,0x55,0xc5,0x13,0x51,0xbe,0xcd,0x1f,0x88,0x9a,0x3a,0x42,0x88,0x66,0x47,0x3b,0x50,0x5e,0x85,0x77,0x66,0x44,0x4a,0x40,0x06,0x4a,0x8f,0x39,0x34,0x0e}, - {0xe8,0xbd,0xce,0x3e,0xd9,0x22,0x7d,0xb6,0x07,0x2f,0x82,0x27,0x41,0xe8,0xb3,0x09,0x8d,0x6d,0x5b,0xb0,0x1f,0xa6,0x3f,0x74,0x72,0x23,0x36,0x8a,0x36,0x05,0x54,0x5e,0x28,0x19,0x4b,0x3e,0x09,0x0b,0x93,0x18,0x40,0xf6,0xf3,0x73,0x0e,0xe1,0xe3,0x7d,0x6f,0x5d,0x39,0x73,0xda,0x17,0x32,0xf4,0x3e,0x9c,0x37,0xca,0xd6,0xde,0x8a,0x6f,0x9a,0xb2,0xb7,0xfd,0x3d,0x12,0x40,0xe3,0x91,0xb2,0x1a,0xa2,0xe1,0x97,0x7b,0x48,0x9e,0x94,0xe6,0xfd,0x02,0x7d,0x96,0xf9,0x97,0xde,0xd3,0xc8,0x2e,0xe7,0x0d,0x78}, - {0xbc,0xe7,0x9a,0x08,0x45,0x85,0xe2,0x0a,0x06,0x4d,0x7f,0x1c,0xcf,0xde,0x8d,0x38,0xb8,0x11,0x48,0x0a,0x51,0x15,0xac,0x38,0xe4,0x8c,0x92,0x71,0xf6,0x8b,0xb2,0x0e,0x72,0x27,0xf4,0x00,0xf3,0xea,0x1f,0x67,0xaa,0x41,0x8c,0x2a,0x2a,0xeb,0x72,0x8f,0x92,0x32,0x37,0x97,0xd7,0x7f,0xa1,0x29,0xa6,0x87,0xb5,0x32,0xad,0xc6,0xef,0x1d,0xa7,0x95,0x51,0xef,0x1a,0xbe,0x5b,0xaf,0xed,0x15,0x7b,0x91,0x77,0x12,0x8c,0x14,0x2e,0xda,0xe5,0x7a,0xfb,0xf7,0x91,0x29,0x67,0x28,0xdd,0xf8,0x1b,0x20,0x7d,0x46}, - {0xad,0x4f,0xef,0x74,0x9a,0x91,0xfe,0x95,0xa2,0x08,0xa3,0xf6,0xec,0x7b,0x82,0x3a,0x01,0x7b,0xa4,0x09,0xd3,0x01,0x4e,0x96,0x97,0xc7,0xa3,0x5b,0x4f,0x3c,0xc4,0x71,0xa9,0xe7,0x7a,0x56,0xbd,0xf4,0x1e,0xbc,0xbd,0x98,0x44,0xd6,0xb2,0x4c,0x62,0x3f,0xc8,0x4e,0x1f,0x2c,0xd2,0x64,0x10,0xe4,0x01,0x40,0x38,0xba,0xa5,0xc5,0xf9,0x2e,0xcd,0x74,0x9e,0xfa,0xf6,0x6d,0xfd,0xb6,0x7a,0x26,0xaf,0xe4,0xbc,0x78,0x82,0xf1,0x0e,0x99,0xef,0xf1,0xd0,0xb3,0x55,0x82,0x93,0xf2,0xc5,0x90,0xa3,0x8c,0x75,0x5a}, - {0x95,0x24,0x46,0xd9,0x10,0x27,0xb7,0xa2,0x03,0x50,0x7d,0xd5,0xd2,0xc6,0xa8,0x3a,0xca,0x87,0xb4,0xa0,0xbf,0x00,0xd4,0xe3,0xec,0x72,0xeb,0xb3,0x44,0xe2,0xba,0x2d,0x94,0xdc,0x61,0x1d,0x8b,0x91,0xe0,0x8c,0x66,0x30,0x81,0x9a,0x46,0x36,0xed,0x8d,0xd3,0xaa,0xe8,0xaf,0x29,0xa8,0xe6,0xd4,0x3f,0xd4,0x39,0xf6,0x27,0x80,0x73,0x0a,0xcc,0xe1,0xff,0x57,0x2f,0x4a,0x0f,0x98,0x43,0x98,0x83,0xe1,0x0d,0x0d,0x67,0x00,0xfd,0x15,0xfb,0x49,0x4a,0x3f,0x5c,0x10,0x9c,0xa6,0x26,0x51,0x63,0xca,0x98,0x26}, - {0x78,0xba,0xb0,0x32,0x88,0x31,0x65,0xe7,0x8b,0xff,0x5c,0x92,0xf7,0x31,0x18,0x38,0xcc,0x1f,0x29,0xa0,0x91,0x1b,0xa8,0x08,0x07,0xeb,0xca,0x49,0xcc,0x3d,0xb4,0x1f,0x0e,0xd9,0x3d,0x5e,0x2f,0x70,0x3d,0x2e,0x86,0x53,0xd2,0xe4,0x18,0x09,0x3f,0x9e,0x6a,0xa9,0x4d,0x02,0xf6,0x3e,0x77,0x5e,0x32,0x33,0xfa,0x4a,0x0c,0x4b,0x00,0x3c,0x2b,0xb8,0xf4,0x06,0xac,0x46,0xa9,0x9a,0xf3,0xc4,0x06,0xa8,0xa5,0x84,0xa2,0x1c,0x87,0x47,0xcd,0xc6,0x5f,0x26,0xd3,0x3e,0x17,0xd2,0x1f,0xcd,0x01,0xfd,0x43,0x6b}, - {0x44,0xc5,0x97,0x46,0x4b,0x5d,0xa7,0xc7,0xbf,0xff,0x0f,0xdf,0x48,0xf8,0xfd,0x15,0x5a,0x78,0x46,0xaa,0xeb,0xb9,0x68,0x28,0x14,0xf7,0x52,0x5b,0x10,0xd7,0x68,0x5a,0xf3,0x0e,0x76,0x3e,0x58,0x42,0xc7,0xb5,0x90,0xb9,0x0a,0xee,0xb9,0x52,0xdc,0x75,0x3f,0x92,0x2b,0x07,0xc2,0x27,0x14,0xbf,0xf0,0xd9,0xf0,0x6f,0x2d,0x0b,0x42,0x73,0x06,0x1e,0x85,0x9e,0xcb,0xf6,0x2c,0xaf,0xc4,0x38,0x22,0xc6,0x13,0x39,0x59,0x8f,0x73,0xf3,0xfb,0x99,0x96,0xb8,0x8a,0xda,0x9e,0xbc,0x34,0xea,0x2f,0x63,0xb5,0x3d}, - {0xd8,0xd9,0x5d,0xf7,0x2b,0xee,0x6e,0xf4,0xa5,0x59,0x67,0x39,0xf6,0xb1,0x17,0x0d,0x73,0x72,0x9e,0x49,0x31,0xd1,0xf2,0x1b,0x13,0x5f,0xd7,0x49,0xdf,0x1a,0x32,0x04,0xd5,0x25,0x98,0x82,0xb1,0x90,0x49,0x2e,0x91,0x89,0x9a,0x3e,0x87,0xeb,0xea,0xed,0xf8,0x4a,0x70,0x4c,0x39,0x3d,0xf0,0xee,0x0e,0x2b,0xdf,0x95,0xa4,0x7e,0x19,0x59,0xae,0x5a,0xe5,0xe4,0x19,0x60,0xe1,0x04,0xe9,0x92,0x2f,0x7e,0x7a,0x43,0x7b,0xe7,0xa4,0x9a,0x15,0x6f,0xc1,0x2d,0xce,0xc7,0xc0,0x0c,0xd7,0xf4,0xc1,0xfd,0xea,0x45}, - {0x2b,0xd7,0x45,0x80,0x85,0x01,0x84,0x69,0x51,0x06,0x2f,0xcf,0xa2,0xfa,0x22,0x4c,0xc6,0x2d,0x22,0x6b,0x65,0x36,0x1a,0x94,0xde,0xda,0x62,0x03,0xc8,0xeb,0x5e,0x5a,0xed,0xb1,0xcc,0xcf,0x24,0x46,0x0e,0xb6,0x95,0x03,0x5c,0xbd,0x92,0xc2,0xdb,0x59,0xc9,0x81,0x04,0xdc,0x1d,0x9d,0xa0,0x31,0x40,0xd9,0x56,0x5d,0xea,0xce,0x73,0x3f,0xc6,0x8d,0x4e,0x0a,0xd1,0xbf,0xa7,0xb7,0x39,0xb3,0xc9,0x44,0x7e,0x00,0x57,0xbe,0xfa,0xae,0x57,0x15,0x7f,0x20,0xc1,0x60,0xdb,0x18,0x62,0x26,0x91,0x88,0x05,0x26}, - {0x04,0xff,0x60,0x83,0xa6,0x04,0xf7,0x59,0xf4,0xe6,0x61,0x76,0xde,0x3f,0xd9,0xc3,0x51,0x35,0x87,0x12,0x73,0x2a,0x1b,0x83,0x57,0x5d,0x61,0x4e,0x2e,0x0c,0xad,0x54,0x42,0xe5,0x76,0xc6,0x3c,0x8e,0x81,0x4c,0xad,0xcc,0xce,0x03,0x93,0x2c,0x42,0x5e,0x08,0x9f,0x12,0xb4,0xca,0xcc,0x07,0xec,0xb8,0x43,0x44,0xb2,0x10,0xfa,0xed,0x0d,0x2a,0x52,0x2b,0xb8,0xd5,0x67,0x3b,0xee,0xeb,0xc1,0xa5,0x9f,0x46,0x63,0xf1,0x36,0xd3,0x9f,0xc1,0x6e,0xf2,0xd2,0xb4,0xa5,0x08,0x94,0x7a,0xa7,0xba,0xb2,0xec,0x62}, - {0x3d,0x2b,0x15,0x61,0x52,0x79,0xed,0xe5,0xd1,0xd7,0xdd,0x0e,0x7d,0x35,0x62,0x49,0x71,0x4c,0x6b,0xb9,0xd0,0xc8,0x82,0x74,0xbe,0xd8,0x66,0xa9,0x19,0xf9,0x59,0x2e,0x74,0x28,0xb6,0xaf,0x36,0x28,0x07,0x92,0xa5,0x04,0xe1,0x79,0x85,0x5e,0xcd,0x5f,0x4a,0xa1,0x30,0xc6,0xad,0x01,0xad,0x5a,0x98,0x3f,0x66,0x75,0x50,0x3d,0x91,0x61,0xda,0x31,0x32,0x1a,0x36,0x2d,0xc6,0x0d,0x70,0x02,0x20,0x94,0x32,0x58,0x47,0xfa,0xce,0x94,0x95,0x3f,0x51,0x01,0xd8,0x02,0x5c,0x5d,0xc0,0x31,0xa1,0xc2,0xdb,0x3d}, - {0x4b,0xc5,0x5e,0xce,0xf9,0x0f,0xdc,0x9a,0x0d,0x13,0x2f,0x8c,0x6b,0x2a,0x9c,0x03,0x15,0x95,0xf8,0xf0,0xc7,0x07,0x80,0x02,0x6b,0xb3,0x04,0xac,0x14,0x83,0x96,0x78,0x14,0xbb,0x96,0x27,0xa2,0x57,0xaa,0xf3,0x21,0xda,0x07,0x9b,0xb7,0xba,0x3a,0x88,0x1c,0x39,0xa0,0x31,0x18,0xe2,0x4b,0xe5,0xf9,0x05,0x32,0xd8,0x38,0xfb,0xe7,0x5e,0x8e,0x6a,0x44,0x41,0xcb,0xfd,0x8d,0x53,0xf9,0x37,0x49,0x43,0xa9,0xfd,0xac,0xa5,0x78,0x8c,0x3c,0x26,0x8d,0x90,0xaf,0x46,0x09,0x0d,0xca,0x9b,0x3c,0x63,0xd0,0x61}, - {0x66,0x25,0xdb,0xff,0x35,0x49,0x74,0x63,0xbb,0x68,0x0b,0x78,0x89,0x6b,0xbd,0xc5,0x03,0xec,0x3e,0x55,0x80,0x32,0x1b,0x6f,0xf5,0xd7,0xae,0x47,0xd8,0x5f,0x96,0x6e,0xdf,0x73,0xfc,0xf8,0xbc,0x28,0xa3,0xad,0xfc,0x37,0xf0,0xa6,0x5d,0x69,0x84,0xee,0x09,0xa9,0xc2,0x38,0xdb,0xb4,0x7f,0x63,0xdc,0x7b,0x06,0xf8,0x2d,0xac,0x23,0x5b,0x7b,0x52,0x80,0xee,0x53,0xb9,0xd2,0x9a,0x8d,0x6d,0xde,0xfa,0xaa,0x19,0x8f,0xe8,0xcf,0x82,0x0e,0x15,0x04,0x17,0x71,0x0e,0xdc,0xde,0x95,0xdd,0xb9,0xbb,0xb9,0x79}, - {0xc2,0x26,0x31,0x6a,0x40,0x55,0xb3,0xeb,0x93,0xc3,0xc8,0x68,0xa8,0x83,0x63,0xd2,0x82,0x7a,0xb9,0xe5,0x29,0x64,0x0c,0x6c,0x47,0x21,0xfd,0xc9,0x58,0xf1,0x65,0x50,0x74,0x73,0x9f,0x8e,0xae,0x7d,0x99,0xd1,0x16,0x08,0xbb,0xcf,0xf8,0xa2,0x32,0xa0,0x0a,0x5f,0x44,0x6d,0x12,0xba,0x6c,0xcd,0x34,0xb8,0xcc,0x0a,0x46,0x11,0xa8,0x1b,0x54,0x99,0x42,0x0c,0xfb,0x69,0x81,0x70,0x67,0xcf,0x6e,0xd7,0xac,0x00,0x46,0xe1,0xba,0x45,0xe6,0x70,0x8a,0xb9,0xaa,0x2e,0xf2,0xfa,0xa4,0x58,0x9e,0xf3,0x81,0x39}, - {0x93,0x0a,0x23,0x59,0x75,0x8a,0xfb,0x18,0x5d,0xf4,0xe6,0x60,0x69,0x8f,0x16,0x1d,0xb5,0x3c,0xa9,0x14,0x45,0xa9,0x85,0x3a,0xfd,0xd0,0xac,0x05,0x37,0x08,0xdc,0x38,0xde,0x6f,0xe6,0x6d,0xa5,0xdf,0x45,0xc8,0x3a,0x48,0x40,0x2c,0x00,0xa5,0x52,0xe1,0x32,0xf6,0xb4,0xc7,0x63,0xe1,0xd2,0xe9,0x65,0x1b,0xbc,0xdc,0x2e,0x45,0xf4,0x30,0x40,0x97,0x75,0xc5,0x82,0x27,0x6d,0x85,0xcc,0xbe,0x9c,0xf9,0x69,0x45,0x13,0xfa,0x71,0x4e,0xea,0xc0,0x73,0xfc,0x44,0x88,0x69,0x24,0x3f,0x59,0x1a,0x9a,0x2d,0x63}, - {0xa6,0xcb,0x07,0xb8,0x15,0x6b,0xbb,0xf6,0xd7,0xf0,0x54,0xbc,0xdf,0xc7,0x23,0x18,0x0b,0x67,0x29,0x6e,0x03,0x97,0x1d,0xbb,0x57,0x4a,0xed,0x47,0x88,0xf4,0x24,0x0b,0xa7,0x84,0x0c,0xed,0x11,0xfd,0x09,0xbf,0x3a,0x69,0x9f,0x0d,0x81,0x71,0xf0,0x63,0x79,0x87,0xcf,0x57,0x2d,0x8c,0x90,0x21,0xa2,0x4b,0xf6,0x8a,0xf2,0x7d,0x5a,0x3a,0xc7,0xea,0x1b,0x51,0xbe,0xd4,0xda,0xdc,0xf2,0xcc,0x26,0xed,0x75,0x80,0x53,0xa4,0x65,0x9a,0x5f,0x00,0x9f,0xff,0x9c,0xe1,0x63,0x1f,0x48,0x75,0x44,0xf7,0xfc,0x34}, - {0xca,0x67,0x97,0x78,0x4c,0xe0,0x97,0xc1,0x7d,0x46,0xd9,0x38,0xcb,0x4d,0x71,0xb8,0xa8,0x5f,0xf9,0x83,0x82,0x88,0xde,0x55,0xf7,0x63,0xfa,0x4d,0x16,0xdc,0x3b,0x3d,0x98,0xaa,0xcf,0x78,0xab,0x1d,0xbb,0xa5,0xf2,0x72,0x0b,0x19,0x67,0xa2,0xed,0x5c,0x8e,0x60,0x92,0x0a,0x11,0xc9,0x09,0x93,0xb0,0x74,0xb3,0x2f,0x04,0xa3,0x19,0x01,0x7d,0x17,0xc2,0xe8,0x9c,0xd8,0xa2,0x67,0xc1,0xd0,0x95,0x68,0xf6,0xa5,0x9d,0x66,0xb0,0xa2,0x82,0xb2,0xe5,0x98,0x65,0xf5,0x73,0x0a,0xe2,0xed,0xf1,0x88,0xc0,0x56}, - {0x17,0x6e,0xa8,0x10,0x11,0x3d,0x6d,0x33,0xfa,0xb2,0x75,0x0b,0x32,0x88,0xf3,0xd7,0x88,0x29,0x07,0x25,0x76,0x33,0x15,0xf9,0x87,0x8b,0x10,0x99,0x6b,0x4c,0x67,0x09,0x02,0x8f,0xf3,0x24,0xac,0x5f,0x1b,0x58,0xbd,0x0c,0xe3,0xba,0xfe,0xe9,0x0b,0xa9,0xf0,0x92,0xcf,0x8a,0x02,0x69,0x21,0x9a,0x8f,0x03,0x59,0x83,0xa4,0x7e,0x8b,0x03,0xf8,0x6f,0x31,0x99,0x21,0xf8,0x4e,0x9f,0x4f,0x8d,0xa7,0xea,0x82,0xd2,0x49,0x2f,0x74,0x31,0xef,0x5a,0xab,0xa5,0x71,0x09,0x65,0xeb,0x69,0x59,0x02,0x31,0x5e,0x6e}, - {0xfb,0x93,0xe5,0x87,0xf5,0x62,0x6c,0xb1,0x71,0x3e,0x5d,0xca,0xde,0xed,0x99,0x49,0x6d,0x3e,0xcc,0x14,0xe0,0xc1,0x91,0xb4,0xa8,0xdb,0xa8,0x89,0x47,0x11,0xf5,0x08,0x22,0x62,0x06,0x63,0x0e,0xfb,0x04,0x33,0x3f,0xba,0xac,0x87,0x89,0x06,0x35,0xfb,0xa3,0x61,0x10,0x8c,0x77,0x24,0x19,0xbd,0x20,0x86,0x83,0xd1,0x43,0xad,0x58,0x30,0xd0,0x63,0x76,0xe5,0xfd,0x0f,0x3c,0x32,0x10,0xa6,0x2e,0xa2,0x38,0xdf,0xc3,0x05,0x9a,0x4f,0x99,0xac,0xbd,0x8a,0xc7,0xbd,0x99,0xdc,0xe3,0xef,0xa4,0x9f,0x54,0x26}, - {0xd6,0xf9,0x6b,0x1e,0x46,0x5a,0x1d,0x74,0x81,0xa5,0x77,0x77,0xfc,0xb3,0x05,0x23,0xd9,0xd3,0x74,0x64,0xa2,0x74,0x55,0xd4,0xff,0xe0,0x01,0x64,0xdc,0xe1,0x26,0x19,0x6e,0x66,0x3f,0xaf,0x49,0x85,0x46,0xdb,0xa5,0x0e,0x4a,0xf1,0x04,0xcf,0x7f,0xd7,0x47,0x0c,0xba,0xa4,0xf7,0x3f,0xf2,0x3d,0x85,0x3c,0xce,0x32,0xe1,0xdf,0x10,0x3a,0xa0,0xce,0x17,0xea,0x8a,0x4e,0x7f,0xe0,0xfd,0xc1,0x1f,0x3a,0x46,0x15,0xd5,0x2f,0xf1,0xc0,0xf2,0x31,0xfd,0x22,0x53,0x17,0x15,0x5d,0x1e,0x86,0x1d,0xd0,0xa1,0x1f}, - {0x32,0x98,0x59,0x7d,0x94,0x55,0x80,0xcc,0x20,0x55,0xf1,0x37,0xda,0x56,0x46,0x1e,0x20,0x93,0x05,0x4e,0x74,0xf7,0xf6,0x99,0x33,0xcf,0x75,0x6a,0xbc,0x63,0x35,0x77,0xab,0x94,0xdf,0xd1,0x00,0xac,0xdc,0x38,0xe9,0x0d,0x08,0xd1,0xdd,0x2b,0x71,0x2e,0x62,0xe2,0xd5,0xfd,0x3e,0xe9,0x13,0x7f,0xe5,0x01,0x9a,0xee,0x18,0xed,0xfc,0x73,0xb3,0x9c,0x13,0x63,0x08,0xe9,0xb1,0x06,0xcd,0x3e,0xa0,0xc5,0x67,0xda,0x93,0xa4,0x32,0x89,0x63,0xad,0xc8,0xce,0x77,0x8d,0x44,0x4f,0x86,0x1b,0x70,0x6b,0x42,0x1f}, - {0x01,0x1c,0x91,0x41,0x4c,0x26,0xc9,0xef,0x25,0x2c,0xa2,0x17,0xb8,0xb7,0xa3,0xf1,0x47,0x14,0x0f,0xf3,0x6b,0xda,0x75,0x58,0x90,0xb0,0x31,0x1d,0x27,0xf5,0x1a,0x4e,0x52,0x25,0xa1,0x91,0xc8,0x35,0x7e,0xf1,0x76,0x9c,0x5e,0x57,0x53,0x81,0x6b,0xb7,0x3e,0x72,0x9b,0x0d,0x6f,0x40,0x83,0xfa,0x38,0xe4,0xa7,0x3f,0x1b,0xbb,0x76,0x0b,0x9b,0x93,0x92,0x7f,0xf9,0xc1,0xb8,0x08,0x6e,0xab,0x44,0xd4,0xcb,0x71,0x67,0xbe,0x17,0x80,0xbb,0x99,0x63,0x64,0xe5,0x22,0x55,0xa9,0x72,0xb7,0x1e,0xd6,0x6d,0x7b}, - {0x92,0x3d,0xf3,0x50,0xe8,0xc1,0xad,0xb7,0xcf,0xd5,0x8c,0x60,0x4f,0xfa,0x98,0x79,0xdb,0x5b,0xfc,0x8d,0xbd,0x2d,0x96,0xad,0x4f,0x2f,0x1d,0xaf,0xce,0x9b,0x3e,0x70,0xc7,0xd2,0x01,0xab,0xf9,0xab,0x30,0x57,0x18,0x3b,0x14,0x40,0xdc,0x76,0xfb,0x16,0x81,0xb2,0xcb,0xa0,0x65,0xbe,0x6c,0x86,0xfe,0x6a,0xff,0x9b,0x65,0x9b,0xfa,0x53,0x55,0x54,0x88,0x94,0xe9,0xc8,0x14,0x6c,0xe5,0xd4,0xae,0x65,0x66,0x5d,0x3a,0x84,0xf1,0x5a,0xd6,0xbc,0x3e,0xb7,0x1b,0x18,0x50,0x1f,0xc6,0xc4,0xe5,0x93,0x8d,0x39}, - {0xf3,0x48,0xe2,0x33,0x67,0xd1,0x4b,0x1c,0x5f,0x0a,0xbf,0x15,0x87,0x12,0x9e,0xbd,0x76,0x03,0x0b,0xa1,0xf0,0x8c,0x3f,0xd4,0x13,0x1b,0x19,0xdf,0x5d,0x9b,0xb0,0x53,0xf2,0xe3,0xe7,0xd2,0x60,0x7c,0x87,0xc3,0xb1,0x8b,0x82,0x30,0xa0,0xaa,0x34,0x3b,0x38,0xf1,0x9e,0x73,0xe7,0x26,0x3e,0x28,0x77,0x05,0xc3,0x02,0x90,0x9c,0x9c,0x69,0xcc,0xf1,0x46,0x59,0x23,0xa7,0x06,0xf3,0x7d,0xd9,0xe5,0xcc,0xb5,0x18,0x17,0x92,0x75,0xe9,0xb4,0x81,0x47,0xd2,0xcd,0x28,0x07,0xd9,0xcd,0x6f,0x0c,0xf3,0xca,0x51}, - {0x0a,0xe0,0x74,0x76,0x42,0xa7,0x0b,0xa6,0xf3,0x7b,0x7a,0xa1,0x70,0x85,0x0e,0x63,0xcc,0x24,0x33,0xcf,0x3d,0x56,0x58,0x37,0xaa,0xfd,0x83,0x23,0x29,0xaa,0x04,0x55,0xc7,0x54,0xac,0x18,0x9a,0xf9,0x7a,0x73,0x0f,0xb3,0x1c,0xc5,0xdc,0x78,0x33,0x90,0xc7,0x0c,0xe1,0x4c,0x33,0xbc,0x89,0x2b,0x9a,0xe9,0xf8,0x89,0xc1,0x29,0xae,0x12,0xcf,0x01,0x0d,0x1f,0xcb,0xc0,0x9e,0xa9,0xae,0xf7,0x34,0x3a,0xcc,0xef,0xd1,0x0d,0x22,0x4e,0x9c,0xd0,0x21,0x75,0xca,0x55,0xea,0xa5,0xeb,0x58,0xe9,0x4f,0xd1,0x5f}, - {0x2c,0xab,0x45,0x28,0xdf,0x2d,0xdc,0xb5,0x93,0xe9,0x7f,0x0a,0xb1,0x91,0x94,0x06,0x46,0xe3,0x02,0x40,0xd6,0xf3,0xaa,0x4d,0xd1,0x74,0x64,0x58,0x6e,0xf2,0x3f,0x09,0x8e,0xcb,0x93,0xbf,0x5e,0xfe,0x42,0x3c,0x5f,0x56,0xd4,0x36,0x51,0xa8,0xdf,0xbe,0xe8,0x20,0x42,0x88,0x9e,0x85,0xf0,0xe0,0x28,0xd1,0x25,0x07,0x96,0x3f,0xd7,0x7d,0x29,0x98,0x05,0x68,0xfe,0x24,0x0d,0xb1,0xe5,0x23,0xaf,0xdb,0x72,0x06,0x73,0x75,0x29,0xac,0x57,0xb4,0x3a,0x25,0x67,0x13,0xa4,0x70,0xb4,0x86,0xbc,0xbc,0x59,0x2f}, - {0x5f,0x13,0x17,0x99,0x42,0x7d,0x84,0x83,0xd7,0x03,0x7d,0x56,0x1f,0x91,0x1b,0xad,0xd1,0xaa,0x77,0xbe,0xd9,0x48,0x77,0x7e,0x4a,0xaf,0x51,0x2e,0x2e,0xb4,0x58,0x54,0x01,0xc3,0x91,0xb6,0x60,0xd5,0x41,0x70,0x1e,0xe7,0xd7,0xad,0x3f,0x1b,0x20,0x85,0x85,0x55,0x33,0x11,0x63,0xe1,0xc2,0x16,0xb1,0x28,0x08,0x01,0x3d,0x5e,0xa5,0x2a,0x4f,0x44,0x07,0x0c,0xe6,0x92,0x51,0xed,0x10,0x1d,0x42,0x74,0x2d,0x4e,0xc5,0x42,0x64,0xc8,0xb5,0xfd,0x82,0x4c,0x2b,0x35,0x64,0x86,0x76,0x8a,0x4a,0x00,0xe9,0x13}, - {0xdb,0xce,0x2f,0x83,0x45,0x88,0x9d,0x73,0x63,0xf8,0x6b,0xae,0xc9,0xd6,0x38,0xfa,0xf7,0xfe,0x4f,0xb7,0xca,0x0d,0xbc,0x32,0x5e,0xe4,0xbc,0x14,0x88,0x7e,0x93,0x73,0x7f,0x87,0x3b,0x19,0xc9,0x00,0x2e,0xbb,0x6b,0x50,0xdc,0xe0,0x90,0xa8,0xe3,0xec,0x9f,0x64,0xde,0x36,0xc0,0xb7,0xf3,0xec,0x1a,0x9e,0xde,0x98,0x08,0x04,0x46,0x5f,0x8d,0xf4,0x7b,0x29,0x16,0x71,0x03,0xb9,0x34,0x68,0xf0,0xd4,0x22,0x3b,0xd1,0xa9,0xc6,0xbd,0x96,0x46,0x57,0x15,0x97,0xe1,0x35,0xe8,0xd5,0x91,0xe8,0xa4,0xf8,0x2c}, - {0x67,0x0f,0x11,0x07,0x87,0xfd,0x93,0x6d,0x49,0xb5,0x38,0x7c,0xd3,0x09,0x4c,0xdd,0x86,0x6a,0x73,0xc2,0x4c,0x6a,0xb1,0x7c,0x09,0x2a,0x25,0x58,0x6e,0xbd,0x49,0x20,0xa2,0x6b,0xd0,0x17,0x7e,0x48,0xb5,0x2c,0x6b,0x19,0x50,0x39,0x1c,0x38,0xd2,0x24,0x30,0x8a,0x97,0x85,0x81,0x9c,0x65,0xd7,0xf6,0xa4,0xd6,0x91,0x28,0x7f,0x6f,0x7a,0x49,0xef,0x9a,0x6a,0x8d,0xfd,0x09,0x7d,0x0b,0xb9,0x3d,0x5b,0xbe,0x60,0xee,0xf0,0xd4,0xbf,0x9e,0x51,0x2c,0xb5,0x21,0x4c,0x1d,0x94,0x45,0xc5,0xdf,0xaa,0x11,0x60}, - {0x3c,0xf8,0x95,0xcf,0x6d,0x92,0x67,0x5f,0x71,0x90,0x28,0x71,0x61,0x85,0x7e,0x7c,0x5b,0x7a,0x8f,0x99,0xf3,0xe7,0xa1,0xd6,0xe0,0xf9,0x62,0x0b,0x1b,0xcc,0xc5,0x6f,0x90,0xf8,0xcb,0x02,0xc8,0xd0,0xde,0x63,0xaa,0x6a,0xff,0x0d,0xca,0x98,0xd0,0xfb,0x99,0xed,0xb6,0xb9,0xfd,0x0a,0x4d,0x62,0x1e,0x0b,0x34,0x79,0xb7,0x18,0xce,0x69,0xcb,0x79,0x98,0xb2,0x28,0x55,0xef,0xd1,0x92,0x90,0x7e,0xd4,0x3c,0xae,0x1a,0xdd,0x52,0x23,0x9f,0x18,0x42,0x04,0x7e,0x12,0xf1,0x01,0x71,0xe5,0x3a,0x6b,0x59,0x15}, - {0xa2,0x79,0x91,0x3f,0xd2,0x39,0x27,0x46,0xcf,0xdd,0xd6,0x97,0x31,0x12,0x83,0xff,0x8a,0x14,0xf2,0x53,0xb5,0xde,0x07,0x13,0xda,0x4d,0x5f,0x7b,0x68,0x37,0x22,0x0d,0xca,0x24,0x51,0x7e,0x16,0x31,0xff,0x09,0xdf,0x45,0xc7,0xd9,0x8b,0x15,0xe4,0x0b,0xe5,0x56,0xf5,0x7e,0x22,0x7d,0x2b,0x29,0x38,0xd1,0xb6,0xaf,0x41,0xe2,0xa4,0x3a,0xf5,0x05,0x33,0x2a,0xbf,0x38,0xc1,0x2c,0xc3,0x26,0xe9,0xa2,0x8f,0x3f,0x58,0x48,0xeb,0xd2,0x49,0x55,0xa2,0xb1,0x3a,0x08,0x6c,0xa3,0x87,0x46,0x6e,0xaa,0xfc,0x32}, - {0xf5,0x9a,0x7d,0xc5,0x8d,0x6e,0xc5,0x7b,0xf2,0xbd,0xf0,0x9d,0xed,0xd2,0x0b,0x3e,0xa3,0xe4,0xef,0x22,0xde,0x14,0xc0,0xaa,0x5c,0x6a,0xbd,0xfe,0xce,0xe9,0x27,0x46,0xdf,0xcc,0x87,0x27,0x73,0xa4,0x07,0x32,0xf8,0xe3,0x13,0xf2,0x08,0x19,0xe3,0x17,0x4e,0x96,0x0d,0xf6,0xd7,0xec,0xb2,0xd5,0xe9,0x0b,0x60,0xc2,0x36,0x63,0x6f,0x74,0x1c,0x97,0x6c,0xab,0x45,0xf3,0x4a,0x3f,0x1f,0x73,0x43,0x99,0x72,0xeb,0x88,0xe2,0x6d,0x18,0x44,0x03,0x8a,0x6a,0x59,0x33,0x93,0x62,0xd6,0x7e,0x00,0x17,0x49,0x7b}, - {0x64,0xb0,0x84,0xab,0x5c,0xfb,0x85,0x2d,0x14,0xbc,0xf3,0x89,0xd2,0x10,0x78,0x49,0x0c,0xce,0x15,0x7b,0x44,0xdc,0x6a,0x47,0x7b,0xfd,0x44,0xf8,0x76,0xa3,0x2b,0x12,0xdd,0xa2,0x53,0xdd,0x28,0x1b,0x34,0x54,0x3f,0xfc,0x42,0xdf,0x5b,0x90,0x17,0xaa,0xf4,0xf8,0xd2,0x4d,0xd9,0x92,0xf5,0x0f,0x7d,0xd3,0x8c,0xe0,0x0f,0x62,0x03,0x1d,0x54,0xe5,0xb4,0xa2,0xcd,0x32,0x02,0xc2,0x7f,0x18,0x5d,0x11,0x42,0xfd,0xd0,0x9e,0xd9,0x79,0xd4,0x7d,0xbe,0xb4,0xab,0x2e,0x4c,0xec,0x68,0x2b,0xf5,0x0b,0xc7,0x02}, - {0xbb,0x2f,0x0b,0x5d,0x4b,0xec,0x87,0xa2,0xca,0x82,0x48,0x07,0x90,0x57,0x5c,0x41,0x5c,0x81,0xd0,0xc1,0x1e,0xa6,0x44,0xe0,0xe0,0xf5,0x9e,0x40,0x0a,0x4f,0x33,0x26,0xe1,0x72,0x8d,0x45,0xbf,0x32,0xe5,0xac,0xb5,0x3c,0xb7,0x7c,0xe0,0x68,0xe7,0x5b,0xe7,0xbd,0x8b,0xee,0x94,0x7d,0xcf,0x56,0x03,0x3a,0xb4,0xfe,0xe3,0x97,0x06,0x6b,0xc0,0xa3,0x62,0xdf,0x4a,0xf0,0xc8,0xb6,0x5d,0xa4,0x6d,0x07,0xef,0x00,0xf0,0x3e,0xa9,0xd2,0xf0,0x49,0x58,0xb9,0x9c,0x9c,0xae,0x2f,0x1b,0x44,0x43,0x7f,0xc3,0x1c}, - {0x4f,0x32,0xc7,0x5c,0x5a,0x56,0x8f,0x50,0x22,0xa9,0x06,0xe5,0xc0,0xc4,0x61,0xd0,0x19,0xac,0x45,0x5c,0xdb,0xab,0x18,0xfb,0x4a,0x31,0x80,0x03,0xc1,0x09,0x68,0x6c,0xb9,0xae,0xce,0xc9,0xf1,0x56,0x66,0xd7,0x6a,0x65,0xe5,0x18,0xf8,0x15,0x5b,0x1c,0x34,0x23,0x4c,0x84,0x32,0x28,0xe7,0x26,0x38,0x68,0x19,0x2f,0x77,0x6f,0x34,0x3a,0xc8,0x6a,0xda,0xe2,0x12,0x51,0xd5,0xd2,0xed,0x51,0xe8,0xb1,0x31,0x03,0xbd,0xe9,0x62,0x72,0xc6,0x8e,0xdd,0x46,0x07,0x96,0xd0,0xc5,0xf7,0x6e,0x9f,0x1b,0x91,0x05}, - {0xbb,0x0e,0xdf,0xf5,0x83,0x99,0x33,0xc1,0xac,0x4c,0x2c,0x51,0x8f,0x75,0xf3,0xc0,0xe1,0x98,0xb3,0x0b,0x0a,0x13,0xf1,0x2c,0x62,0x0c,0x27,0xaa,0xf9,0xec,0x3c,0x6b,0xef,0xea,0x2e,0x51,0xf3,0xac,0x49,0x53,0x49,0xcb,0xc1,0x1c,0xd3,0x41,0xc1,0x20,0x8d,0x68,0x9a,0xa9,0x07,0x0c,0x18,0x24,0x17,0x2d,0x4b,0xc6,0xd1,0xf9,0x5e,0x55,0x08,0xbd,0x73,0x3b,0xba,0x70,0xa7,0x36,0x0c,0xbf,0xaf,0xa3,0x08,0xef,0x4a,0x62,0xf2,0x46,0x09,0xb4,0x98,0xff,0x37,0x57,0x9d,0x74,0x81,0x33,0xe1,0x4d,0x5f,0x67}, - {0xfc,0x82,0x17,0x6b,0x03,0x52,0x2c,0x0e,0xb4,0x83,0xad,0x6c,0x81,0x6c,0x81,0x64,0x3e,0x07,0x64,0x69,0xd9,0xbd,0xdc,0xd0,0x20,0xc5,0x64,0x01,0xf7,0x9d,0xd9,0x13,0x1d,0xb3,0xda,0x3b,0xd9,0xf6,0x2f,0xa1,0xfe,0x2d,0x65,0x9d,0x0f,0xd8,0x25,0x07,0x87,0x94,0xbe,0x9a,0xf3,0x4f,0x9c,0x01,0x43,0x3c,0xcd,0x82,0xb8,0x50,0xf4,0x60,0xca,0xc0,0xe5,0x21,0xc3,0x5e,0x4b,0x01,0xa2,0xbf,0x19,0xd7,0xc9,0x69,0xcb,0x4f,0xa0,0x23,0x00,0x75,0x18,0x1c,0x5f,0x4e,0x80,0xac,0xed,0x55,0x9e,0xde,0x06,0x1c}, - {0xe2,0xc4,0x3e,0xa3,0xd6,0x7a,0x0f,0x99,0x8e,0xe0,0x2e,0xbe,0x38,0xf9,0x08,0x66,0x15,0x45,0x28,0x63,0xc5,0x43,0xa1,0x9c,0x0d,0xb6,0x2d,0xec,0x1f,0x8a,0xf3,0x4c,0xaa,0x69,0x6d,0xff,0x40,0x2b,0xd5,0xff,0xbb,0x49,0x40,0xdc,0x18,0x0b,0x53,0x34,0x97,0x98,0x4d,0xa3,0x2f,0x5c,0x4a,0x5e,0x2d,0xba,0x32,0x7d,0x8e,0x6f,0x09,0x78,0xe7,0x5c,0xfa,0x0d,0x65,0xaa,0xaa,0xa0,0x8c,0x47,0xb5,0x48,0x2a,0x9e,0xc4,0xf9,0x5b,0x72,0x03,0x70,0x7d,0xcc,0x09,0x4f,0xbe,0x1a,0x09,0x26,0x3a,0xad,0x3c,0x37}, - {0x7c,0xf5,0xc9,0x82,0x4d,0x63,0x94,0xb2,0x36,0x45,0x93,0x24,0xe1,0xfd,0xcb,0x1f,0x5a,0xdb,0x8c,0x41,0xb3,0x4d,0x9c,0x9e,0xfc,0x19,0x44,0x45,0xd9,0xf3,0x40,0x00,0xad,0xbb,0xdd,0x89,0xfb,0xa8,0xbe,0xf1,0xcb,0xae,0xae,0x61,0xbc,0x2c,0xcb,0x3b,0x9d,0x8d,0x9b,0x1f,0xbb,0xa7,0x58,0x8f,0x86,0xa6,0x12,0x51,0xda,0x7e,0x54,0x21,0xd3,0x86,0x59,0xfd,0x39,0xe9,0xfd,0xde,0x0c,0x38,0x0a,0x51,0x89,0x2c,0x27,0xf4,0xb9,0x19,0x31,0xbb,0x07,0xa4,0x2b,0xb7,0xf4,0x4d,0x25,0x4a,0x33,0x0a,0x55,0x63}, - {0x37,0xcf,0x69,0xb5,0xed,0xd6,0x07,0x65,0xe1,0x2e,0xa5,0x0c,0xb0,0x29,0x84,0x17,0x5d,0xd6,0x6b,0xeb,0x90,0x00,0x7c,0xea,0x51,0x8f,0xf7,0xda,0xc7,0x62,0xea,0x3e,0x49,0x7b,0x54,0x72,0x45,0x58,0xba,0x9b,0xe0,0x08,0xc4,0xe2,0xfa,0xc6,0x05,0xf3,0x8d,0xf1,0x34,0xc7,0x69,0xfa,0xe8,0x60,0x7a,0x76,0x7d,0xaa,0xaf,0x2b,0xa9,0x39,0x4e,0x27,0x93,0xe6,0x13,0xc7,0x24,0x9d,0x75,0xd3,0xdb,0x68,0x77,0x85,0x63,0x5f,0x9a,0xb3,0x8a,0xeb,0x60,0x55,0x52,0x70,0xcd,0xc4,0xc9,0x65,0x06,0x6a,0x43,0x68}, - {0x27,0x3f,0x2f,0x20,0xe8,0x35,0x02,0xbc,0xb0,0x75,0xf9,0x64,0xe2,0x00,0x5c,0xc7,0x16,0x24,0x8c,0xa3,0xd5,0xe9,0xa4,0x91,0xf9,0x89,0xb7,0x8a,0xf6,0xe7,0xb6,0x17,0x7c,0x10,0x20,0xe8,0x17,0xd3,0x56,0x1e,0x65,0xe9,0x0a,0x84,0x44,0x68,0x26,0xc5,0x7a,0xfc,0x0f,0x32,0xc6,0xa1,0xe0,0xc1,0x72,0x14,0x61,0x91,0x9c,0x66,0x73,0x53,0x57,0x52,0x0e,0x9a,0xab,0x14,0x28,0x5d,0xfc,0xb3,0xca,0xc9,0x84,0x20,0x8f,0x90,0xca,0x1e,0x2d,0x5b,0x88,0xf5,0xca,0xaf,0x11,0x7d,0xf8,0x78,0xa6,0xb5,0xb4,0x1c}, - {0x6c,0xfc,0x4a,0x39,0x6b,0xc0,0x64,0xb6,0xb1,0x5f,0xda,0x98,0x24,0xde,0x88,0x0c,0x34,0xd8,0xca,0x4b,0x16,0x03,0x8d,0x4f,0xa2,0x34,0x74,0xde,0x78,0xca,0x0b,0x33,0xe7,0x07,0xa0,0xa2,0x62,0xaa,0x74,0x6b,0xb1,0xc7,0x71,0xf0,0xb0,0xe0,0x11,0xf3,0x23,0xe2,0x0b,0x00,0x38,0xe4,0x07,0x57,0xac,0x6e,0xef,0x82,0x2d,0xfd,0xc0,0x2d,0x4e,0x74,0x19,0x11,0x84,0xff,0x2e,0x98,0x24,0x47,0x07,0x2b,0x96,0x5e,0x69,0xf9,0xfb,0x53,0xc9,0xbf,0x4f,0xc1,0x8a,0xc5,0xf5,0x1c,0x9f,0x36,0x1b,0xbe,0x31,0x3c}, - {0xee,0x8a,0x94,0x08,0x4d,0x86,0xf4,0xb0,0x6f,0x1c,0xba,0x91,0xee,0x19,0xdc,0x07,0x58,0xa1,0xac,0xa6,0xae,0xcd,0x75,0x79,0xbb,0xd4,0x62,0x42,0x13,0x61,0x0b,0x33,0x72,0x42,0xcb,0xf9,0x93,0xbc,0x68,0xc1,0x98,0xdb,0xce,0xc7,0x1f,0x71,0xb8,0xae,0x7a,0x8d,0xac,0x34,0xaa,0x52,0x0e,0x7f,0xbb,0x55,0x7d,0x7e,0x09,0xc1,0xce,0x41,0x8a,0x80,0x6d,0xa2,0xd7,0x19,0x96,0xf7,0x6d,0x15,0x9e,0x1d,0x9e,0xd4,0x1f,0xbb,0x27,0xdf,0xa1,0xdb,0x6c,0xc3,0xd7,0x73,0x7d,0x77,0x28,0x1f,0xd9,0x4c,0xb4,0x26}, - {0x75,0x74,0x38,0x8f,0x47,0x48,0xf0,0x51,0x3c,0xcb,0xbe,0x9c,0xf4,0xbc,0x5d,0xb2,0x55,0x20,0x9f,0xd9,0x44,0x12,0xab,0x9a,0xd6,0xa5,0x10,0x1c,0x6c,0x9e,0x70,0x2c,0x83,0x03,0x73,0x62,0x93,0xf2,0xb7,0xe1,0x2c,0x8a,0xca,0xeb,0xff,0x79,0x52,0x4b,0x14,0x13,0xd4,0xbf,0x8a,0x77,0xfc,0xda,0x0f,0x61,0x72,0x9c,0x14,0x10,0xeb,0x7d,0x7a,0xee,0x66,0x87,0x6a,0xaf,0x62,0xcb,0x0e,0xcd,0x53,0x55,0x04,0xec,0xcb,0x66,0xb5,0xe4,0x0b,0x0f,0x38,0x01,0x80,0x58,0xea,0xe2,0x2c,0xf6,0x9f,0x8e,0xe6,0x08}, - {0xad,0x30,0xc1,0x4b,0x0a,0x50,0xad,0x34,0x9c,0xd4,0x0b,0x3d,0x49,0xdb,0x38,0x8d,0xbe,0x89,0x0a,0x50,0x98,0x3d,0x5c,0xa2,0x09,0x3b,0xba,0xee,0x87,0x3f,0x1f,0x2f,0xf9,0xf2,0xb8,0x0a,0xd5,0x09,0x2d,0x2f,0xdf,0x23,0x59,0xc5,0x8d,0x21,0xb9,0xac,0xb9,0x6c,0x76,0x73,0x26,0x34,0x8f,0x4a,0xf5,0x19,0xf7,0x38,0xd7,0x3b,0xb1,0x4c,0x4a,0xb6,0x15,0xe5,0x75,0x8c,0x84,0xf7,0x38,0x90,0x4a,0xdb,0xba,0x01,0x95,0xa5,0x50,0x1b,0x75,0x3f,0x3f,0x31,0x0d,0xc2,0xe8,0x2e,0xae,0xc0,0x53,0xe3,0xa1,0x19}, - {0xc3,0x05,0xfa,0xba,0x60,0x75,0x1c,0x7d,0x61,0x5e,0xe5,0xc6,0xa0,0xa0,0xe1,0xb3,0x73,0x64,0xd6,0xc0,0x18,0x97,0x52,0xe3,0x86,0x34,0x0c,0xc2,0x11,0x6b,0x54,0x41,0xbd,0xbd,0x96,0xd5,0xcd,0x72,0x21,0xb4,0x40,0xfc,0xee,0x98,0x43,0x45,0xe0,0x93,0xb5,0x09,0x41,0xb4,0x47,0x53,0xb1,0x9f,0x34,0xae,0x66,0x02,0x99,0xd3,0x6b,0x73,0xb4,0xb3,0x34,0x93,0x50,0x2d,0x53,0x85,0x73,0x65,0x81,0x60,0x4b,0x11,0xfd,0x46,0x75,0x83,0x5c,0x42,0x30,0x5f,0x5f,0xcc,0x5c,0xab,0x7f,0xb8,0xa2,0x95,0x22,0x41}, - {0xe9,0xd6,0x7e,0xf5,0x88,0x9b,0xc9,0x19,0x25,0xc8,0xf8,0x6d,0x26,0xcb,0x93,0x53,0x73,0xd2,0x0a,0xb3,0x13,0x32,0xee,0x5c,0x34,0x2e,0x2d,0xb5,0xeb,0x53,0xe1,0x14,0xc6,0xea,0x93,0xe2,0x61,0x52,0x65,0x2e,0xdb,0xac,0x33,0x21,0x03,0x92,0x5a,0x84,0x6b,0x99,0x00,0x79,0xcb,0x75,0x09,0x46,0x80,0xdd,0x5a,0x19,0x8d,0xbb,0x60,0x07,0x8a,0x81,0xe6,0xcd,0x17,0x1a,0x3e,0x41,0x84,0xa0,0x69,0xed,0xa9,0x6d,0x15,0x57,0xb1,0xcc,0xca,0x46,0x8f,0x26,0xbf,0x2c,0xf2,0xc5,0x3a,0xc3,0x9b,0xbe,0x34,0x6b}, - {0xb2,0xc0,0x78,0x3a,0x64,0x2f,0xdf,0xf3,0x7c,0x02,0x2e,0xf2,0x1e,0x97,0x3e,0x4c,0xa3,0xb5,0xc1,0x49,0x5e,0x1c,0x7d,0xec,0x2d,0xdd,0x22,0x09,0x8f,0xc1,0x12,0x20,0xd3,0xf2,0x71,0x65,0x65,0x69,0xfc,0x11,0x7a,0x73,0x0e,0x53,0x45,0xe8,0xc9,0xc6,0x35,0x50,0xfe,0xd4,0xa2,0xe7,0x3a,0xe3,0x0b,0xd3,0x6d,0x2e,0xb6,0xc7,0xb9,0x01,0x29,0x9d,0xc8,0x5a,0xe5,0x55,0x0b,0x88,0x63,0xa7,0xa0,0x45,0x1f,0x24,0x83,0x14,0x1f,0x6c,0xe7,0xc2,0xdf,0xef,0x36,0x3d,0xe8,0xad,0x4b,0x4e,0x78,0x5b,0xaf,0x08}, - {0x33,0x25,0x1f,0x88,0xdc,0x99,0x34,0x28,0xb6,0x23,0x93,0x77,0xda,0x25,0x05,0x9d,0xf4,0x41,0x34,0x67,0xfb,0xdd,0x7a,0x89,0x8d,0x16,0x3a,0x16,0x71,0x9d,0xb7,0x32,0x4b,0x2c,0xcc,0x89,0xd2,0x14,0x73,0xe2,0x8d,0x17,0x87,0xa2,0x11,0xbd,0xe4,0x4b,0xce,0x64,0x33,0xfa,0xd6,0x28,0xd5,0x18,0x6e,0x82,0xd9,0xaf,0xd5,0xc1,0x23,0x64,0x6a,0xb3,0xfc,0xed,0xd9,0xf8,0x85,0xcc,0xf9,0xe5,0x46,0x37,0x8f,0xc2,0xbc,0x22,0xcd,0xd3,0xe5,0xf9,0x38,0xe3,0x9d,0xe4,0xcc,0x2d,0x3e,0xc1,0xfb,0x5e,0x0a,0x48}, - {0x71,0x20,0x62,0x01,0x0b,0xe7,0x51,0x0b,0xc5,0xaf,0x1d,0x8b,0xcf,0x05,0xb5,0x06,0xcd,0xab,0x5a,0xef,0x61,0xb0,0x6b,0x2c,0x31,0xbf,0xb7,0x0c,0x60,0x27,0xaa,0x47,0x1f,0x22,0xce,0x42,0xe4,0x4c,0x61,0xb6,0x28,0x39,0x05,0x4c,0xcc,0x9d,0x19,0x6e,0x03,0xbe,0x1c,0xdc,0xa4,0xb4,0x3f,0x66,0x06,0x8e,0x1c,0x69,0x47,0x1d,0xb3,0x24,0xc3,0xf8,0x15,0xc0,0xed,0x1e,0x54,0x2a,0x7c,0x3f,0x69,0x7c,0x7e,0xfe,0xa4,0x11,0xd6,0x78,0xa2,0x4e,0x13,0x66,0xaf,0xf0,0x94,0xa0,0xdd,0x14,0x5d,0x58,0x5b,0x54}, - {0x0f,0x3a,0xd4,0xa0,0x5e,0x27,0xbf,0x67,0xbe,0xee,0x9b,0x08,0x34,0x8e,0xe6,0xad,0x2e,0xe7,0x79,0xd4,0x4c,0x13,0x89,0x42,0x54,0x54,0xba,0x32,0xc3,0xf9,0x62,0x0f,0xe1,0x21,0xb3,0xe3,0xd0,0xe4,0x04,0x62,0x95,0x1e,0xff,0x28,0x7a,0x63,0xaa,0x3b,0x9e,0xbd,0x99,0x5b,0xfd,0xcf,0x0c,0x0b,0x71,0xd0,0xc8,0x64,0x3e,0xdc,0x22,0x4d,0x39,0x5f,0x3b,0xd6,0x89,0x65,0xb4,0xfc,0x61,0xcf,0xcb,0x57,0x3f,0x6a,0xae,0x5c,0x05,0xfa,0x3a,0x95,0xd2,0xc2,0xba,0xfe,0x36,0x14,0x37,0x36,0x1a,0xa0,0x0f,0x1c}, - {0xff,0x3d,0x94,0x22,0xb6,0x04,0xc6,0xd2,0xa0,0xb3,0xcf,0x44,0xce,0xbe,0x8c,0xbc,0x78,0x86,0x80,0x97,0xf3,0x4f,0x25,0x5d,0xbf,0xa6,0x1c,0x3b,0x4f,0x61,0xa3,0x0f,0x50,0x6a,0x93,0x8c,0x0e,0x2b,0x08,0x69,0xb6,0xc5,0xda,0xc1,0x35,0xa0,0xc9,0xf9,0x34,0xb6,0xdf,0xc4,0x54,0x3e,0xb7,0x6f,0x40,0xc1,0x2b,0x1d,0x9b,0x41,0x05,0x40,0xf0,0x82,0xbe,0xb9,0xbd,0xfe,0x03,0xa0,0x90,0xac,0x44,0x3a,0xaf,0xc1,0x89,0x20,0x8e,0xfa,0x54,0x19,0x91,0x9f,0x49,0xf8,0x42,0xab,0x40,0xef,0x8a,0x21,0xba,0x1f}, - {0x3e,0xf5,0xc8,0xfa,0x48,0x94,0x54,0xab,0x41,0x37,0xa6,0x7b,0x9a,0xe8,0xf6,0x81,0x01,0x5e,0x2b,0x6c,0x7d,0x6c,0xfd,0x74,0x42,0x6e,0xc8,0xa8,0xca,0x3a,0x2e,0x39,0x94,0x01,0x7b,0x3e,0x04,0x57,0x3e,0x4f,0x7f,0xaf,0xda,0x08,0xee,0x3e,0x1d,0xa8,0xf1,0xde,0xdc,0x99,0xab,0xc6,0x39,0xc8,0xd5,0x61,0x77,0xff,0x13,0x5d,0x53,0x6c,0xaf,0x35,0x8a,0x3e,0xe9,0x34,0xbd,0x4c,0x16,0xe8,0x87,0x58,0x44,0x81,0x07,0x2e,0xab,0xb0,0x9a,0xf2,0x76,0x9c,0x31,0x19,0x3b,0xc1,0x0a,0xd5,0xe4,0x7f,0xe1,0x25}, - {0x76,0xf6,0x04,0x1e,0xd7,0x9b,0x28,0x0a,0x95,0x0f,0x42,0xd6,0x52,0x1c,0x8e,0x20,0xab,0x1f,0x69,0x34,0xb0,0xd8,0x86,0x51,0x51,0xb3,0x9f,0x2a,0x44,0x51,0x57,0x25,0xa7,0x21,0xf1,0x76,0xf5,0x7f,0x5f,0x91,0xe3,0x87,0xcd,0x2f,0x27,0x32,0x4a,0xc3,0x26,0xe5,0x1b,0x4d,0xde,0x2f,0xba,0xcc,0x9b,0x89,0x69,0x89,0x8f,0x82,0xba,0x6b,0x01,0x39,0xfe,0x90,0x66,0xbc,0xd1,0xe2,0xd5,0x7a,0x99,0xa0,0x18,0x4a,0xb5,0x4c,0xd4,0x60,0x84,0xaf,0x14,0x69,0x1d,0x97,0xe4,0x7b,0x6b,0x7f,0x4f,0x50,0x9d,0x55}, - {0xd5,0x54,0xeb,0xb3,0x78,0x83,0x73,0xa7,0x7c,0x3c,0x55,0xa5,0x66,0xd3,0x69,0x1d,0xba,0x00,0x28,0xf9,0x62,0xcf,0x26,0x0a,0x17,0x32,0x7e,0x80,0xd5,0x12,0xab,0x01,0xfd,0x66,0xd2,0xf6,0xe7,0x91,0x48,0x9c,0x1b,0x78,0x07,0x03,0x9b,0xa1,0x44,0x07,0x3b,0xe2,0x61,0x60,0x1d,0x8f,0x38,0x88,0x0e,0xd5,0x4b,0x35,0xa3,0xa6,0x3e,0x12,0x96,0x2d,0xe3,0x41,0x90,0x18,0x8d,0x11,0x48,0x58,0x31,0xd8,0xc2,0xe3,0xed,0xb9,0xd9,0x45,0x32,0xd8,0x71,0x42,0xab,0x1e,0x54,0xa1,0x18,0xc9,0xe2,0x61,0x39,0x4a}, - {0xa0,0xbb,0xe6,0xf8,0xe0,0x3b,0xdc,0x71,0x0a,0xe3,0xff,0x7e,0x34,0xf8,0xce,0xd6,0x6a,0x47,0x3a,0xe1,0x5f,0x42,0x92,0xa9,0x63,0xb7,0x1d,0xfb,0xe3,0xbc,0xd6,0x2c,0x1e,0x3f,0x23,0xf3,0x44,0xd6,0x27,0x03,0x16,0xf0,0xfc,0x34,0x0e,0x26,0x9a,0x49,0x79,0xb9,0xda,0xf2,0x16,0xa7,0xb5,0x83,0x1f,0x11,0xd4,0x9b,0xad,0xee,0xac,0x68,0x10,0xc2,0xd7,0xf3,0x0e,0xc9,0xb4,0x38,0x0c,0x04,0xad,0xb7,0x24,0x6e,0x8e,0x30,0x23,0x3e,0xe7,0xb7,0xf1,0xd9,0x60,0x38,0x97,0xf5,0x08,0xb5,0xd5,0x60,0x57,0x59}, - {0x97,0x63,0xaa,0x04,0xe1,0xbf,0x29,0x61,0xcb,0xfc,0xa7,0xa4,0x08,0x00,0x96,0x8f,0x58,0x94,0x90,0x7d,0x89,0xc0,0x8b,0x3f,0xa9,0x91,0xb2,0xdc,0x3e,0xa4,0x9f,0x70,0x90,0x27,0x02,0xfd,0xeb,0xcb,0x2a,0x88,0x60,0x57,0x11,0xc4,0x05,0x33,0xaf,0x89,0xf4,0x73,0x34,0x7d,0xe3,0x92,0xf4,0x65,0x2b,0x5a,0x51,0x54,0xdf,0xc5,0xb2,0x2c,0xca,0x2a,0xfd,0x63,0x8c,0x5d,0x0a,0xeb,0xff,0x4e,0x69,0x2e,0x66,0xc1,0x2b,0xd2,0x3a,0xb0,0xcb,0xf8,0x6e,0xf3,0x23,0x27,0x1f,0x13,0xc8,0xf0,0xec,0x29,0xf0,0x70}, - {0x33,0x3e,0xed,0x2e,0xb3,0x07,0x13,0x46,0xe7,0x81,0x55,0xa4,0x33,0x2f,0x04,0xae,0x66,0x03,0x5f,0x19,0xd3,0x49,0x44,0xc9,0x58,0x48,0x31,0x6c,0x8a,0x5d,0x7d,0x0b,0xb9,0xb0,0x10,0x5e,0xaa,0xaf,0x6a,0x2a,0xa9,0x1a,0x04,0xef,0x70,0xa3,0xf0,0x78,0x1f,0xd6,0x3a,0xaa,0x77,0xfb,0x3e,0x77,0xe1,0xd9,0x4b,0xa7,0xa2,0xa5,0xec,0x44,0x43,0xd5,0x95,0x7b,0x32,0x48,0xd4,0x25,0x1d,0x0f,0x34,0xa3,0x00,0x83,0xd3,0x70,0x2b,0xc5,0xe1,0x60,0x1c,0x53,0x1c,0xde,0xe4,0xe9,0x7d,0x2c,0x51,0x24,0x22,0x27}, - {0x2e,0x34,0xc5,0x49,0xaf,0x92,0xbc,0x1a,0xd0,0xfa,0xe6,0xb2,0x11,0xd8,0xee,0xff,0x29,0x4e,0xc8,0xfc,0x8d,0x8c,0xa2,0xef,0x43,0xc5,0x4c,0xa4,0x18,0xdf,0xb5,0x11,0xfc,0x75,0xa9,0x42,0x8a,0xbb,0x7b,0xbf,0x58,0xa3,0xad,0x96,0x77,0x39,0x5c,0x8c,0x48,0xaa,0xed,0xcd,0x6f,0xc7,0x7f,0xe2,0xa6,0x20,0xbc,0xf6,0xd7,0x5f,0x73,0x19,0x66,0x42,0xc8,0x42,0xd0,0x90,0xab,0xe3,0x7e,0x54,0x19,0x7f,0x0f,0x8e,0x84,0xeb,0xb9,0x97,0xa4,0x65,0xd0,0xa1,0x03,0x25,0x5f,0x89,0xdf,0x91,0x11,0x91,0xef,0x0f} -}; diff --git a/trezor-crypto/crypto/ed25519-donna/ed25519-donna-impl-base.c b/trezor-crypto/crypto/ed25519-donna/ed25519-donna-impl-base.c deleted file mode 100644 index d43a522c5a4..00000000000 --- a/trezor-crypto/crypto/ed25519-donna/ed25519-donna-impl-base.c +++ /dev/null @@ -1,730 +0,0 @@ -#include -#include -#include - -/* sqrt(x) is such an integer y that 0 <= y <= p - 1, y % 2 = 0, and y^2 = x (mod p). */ -/* d = -121665 / 121666 */ -#if !defined(NDEBUG) -const bignum25519 ALIGN(16) fe_d = { - 0x35978a3, 0x0d37284, 0x3156ebd, 0x06a0a0e, 0x001c029, 0x179e898, 0x3a03cbb, 0x1ce7198, 0x2e2b6ff, 0x1480db3}; /* d */ -#endif -const bignum25519 ALIGN(16) fe_sqrtm1 = { - 0x20ea0b0, 0x186c9d2, 0x08f189d, 0x035697f, 0x0bd0c60, 0x1fbd7a7, 0x2804c9e, 0x1e16569, 0x004fc1d, 0x0ae0c92}; /* sqrt(-1) */ -//const bignum25519 ALIGN(16) fe_d2 = { -// 0x2b2f159, 0x1a6e509, 0x22add7a, 0x0d4141d, 0x0038052, 0x0f3d130, 0x3407977, 0x19ce331, 0x1c56dff, 0x0901b67}; /* 2 * d */ - -/* A = 2 * (1 - d) / (1 + d) = 486662 */ -const bignum25519 ALIGN(16) fe_ma2 = { - 0x33de3c9, 0x1fff236, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff}; /* -A^2 */ -const bignum25519 ALIGN(16) fe_ma = { - 0x3f892e7, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff}; /* -A */ -const bignum25519 ALIGN(16) fe_fffb1 = { - 0x1e3bdff, 0x025a2b3, 0x18e5bab, 0x0ba36ac, 0x0b9afed, 0x004e61c, 0x31d645f, 0x09d1bea, 0x102529e, 0x0063810}; /* sqrt(-2 * A * (A + 2)) */ -const bignum25519 ALIGN(16) fe_fffb2 = { - 0x383650d, 0x066df27, 0x10405a4, 0x1cfdd48, 0x2b887f2, 0x1e9a041, 0x1d7241f, 0x0612dc5, 0x35fba5d, 0x0cbe787}; /* sqrt(2 * A * (A + 2)) */ -const bignum25519 ALIGN(16) fe_fffb3 = { - 0x0cfd387, 0x1209e3a, 0x3bad4fc, 0x18ad34d, 0x2ff6c02, 0x0f25d12, 0x15cdfe0, 0x0e208ed, 0x32eb3df, 0x062d7bb}; /* sqrt(-sqrt(-1) * A * (A + 2)) */ -const bignum25519 ALIGN(16) fe_fffb4 = { - 0x2b39186, 0x14640ed, 0x14930a7, 0x04509fa, 0x3b91bf0, 0x0f7432e, 0x07a443f, 0x17f24d8, 0x031067d, 0x0690fcc}; /* sqrt(sqrt(-1) * A * (A + 2)) */ - - -/* - Timing safe memory compare -*/ -int ed25519_verify(const unsigned char *x, const unsigned char *y, size_t len) { - size_t differentbits = 0; - while (len--) - differentbits |= (*x++ ^ *y++); - return (int) (1 & ((differentbits - 1) >> 8)); -} - -/* - conversions -*/ - -void ge25519_p1p1_to_partial(ge25519 *r, const ge25519_p1p1 *p) { - curve25519_mul(r->x, p->x, p->t); - curve25519_mul(r->y, p->y, p->z); - curve25519_mul(r->z, p->z, p->t); -} - -void ge25519_p1p1_to_full(ge25519 *r, const ge25519_p1p1 *p) { - curve25519_mul(r->x, p->x, p->t); - curve25519_mul(r->y, p->y, p->z); - curve25519_mul(r->z, p->z, p->t); - curve25519_mul(r->t, p->x, p->y); -} - -void ge25519_full_to_pniels(ge25519_pniels *p, const ge25519 *r) { - curve25519_sub(p->ysubx, r->y, r->x); - curve25519_add(p->xaddy, r->y, r->x); - curve25519_copy(p->z, r->z); - curve25519_mul(p->t2d, r->t, ge25519_ec2d); -} - -/* - adding & doubling -*/ - -void ge25519_double_p1p1(ge25519_p1p1 *r, const ge25519 *p) { - bignum25519 a = {0}, b = {0}, c = {0}; - - curve25519_square(a, p->x); - curve25519_square(b, p->y); - curve25519_square(c, p->z); - curve25519_add_reduce(c, c, c); - curve25519_add(r->x, p->x, p->y); - curve25519_square(r->x, r->x); - curve25519_add(r->y, b, a); - curve25519_sub(r->z, b, a); - curve25519_sub_after_basic(r->x, r->x, r->y); - curve25519_sub_after_basic(r->t, c, r->z); -} - -#ifndef ED25519_NO_PRECOMP -void ge25519_nielsadd2_p1p1(ge25519_p1p1 *r, const ge25519 *p, const ge25519_niels *q, unsigned char signbit) { - const bignum25519 *qb = (const bignum25519 *)q; - bignum25519 *rb = (bignum25519 *)r; - bignum25519 a = {0}, b = {0}, c = {0}; - - curve25519_sub(a, p->y, p->x); - curve25519_add(b, p->y, p->x); - curve25519_mul(a, a, qb[signbit]); /* x for +, y for - */ - curve25519_mul(r->x, b, qb[signbit^1]); /* y for +, x for - */ - curve25519_add(r->y, r->x, a); - curve25519_sub(r->x, r->x, a); - curve25519_mul(c, p->t, q->t2d); - curve25519_add_reduce(r->t, p->z, p->z); - curve25519_copy(r->z, r->t); - curve25519_add(rb[2+signbit], rb[2+signbit], c); /* z for +, t for - */ - curve25519_sub(rb[2+(signbit^1)], rb[2+(signbit^1)], c); /* t for +, z for - */ -} -#endif - -void ge25519_pnielsadd_p1p1(ge25519_p1p1 *r, const ge25519 *p, const ge25519_pniels *q, unsigned char signbit) { - const bignum25519 *qb = (const bignum25519 *)q; - bignum25519 *rb = (bignum25519 *)r; - bignum25519 a = {0}, b = {0}, c = {0}; - - curve25519_sub(a, p->y, p->x); - curve25519_add(b, p->y, p->x); - curve25519_mul(a, a, qb[signbit]); /* ysubx for +, xaddy for - */ - curve25519_mul(r->x, b, qb[signbit^1]); /* xaddy for +, ysubx for - */ - curve25519_add(r->y, r->x, a); - curve25519_sub(r->x, r->x, a); - curve25519_mul(c, p->t, q->t2d); - curve25519_mul(r->t, p->z, q->z); - curve25519_add_reduce(r->t, r->t, r->t); - curve25519_copy(r->z, r->t); - curve25519_add(rb[2+signbit], rb[2+signbit], c); /* z for +, t for - */ - curve25519_sub(rb[2+(signbit^1)], rb[2+(signbit^1)], c); /* t for +, z for - */ -} - -void ge25519_double_partial(ge25519 *r, const ge25519 *p) { - ge25519_p1p1 t = {0}; - ge25519_double_p1p1(&t, p); - ge25519_p1p1_to_partial(r, &t); -} - -void ge25519_double(ge25519 *r, const ge25519 *p) { - ge25519_p1p1 t = {0}; - ge25519_double_p1p1(&t, p); - ge25519_p1p1_to_full(r, &t); -} - -void ge25519_nielsadd2(ge25519 *r, const ge25519_niels *q) { - bignum25519 a = {0}, b = {0}, c = {0}, e = {0}, f = {0}, g = {0}, h = {0}; - - curve25519_sub(a, r->y, r->x); - curve25519_add(b, r->y, r->x); - curve25519_mul(a, a, q->ysubx); - curve25519_mul(e, b, q->xaddy); - curve25519_add(h, e, a); - curve25519_sub(e, e, a); - curve25519_mul(c, r->t, q->t2d); - curve25519_add(f, r->z, r->z); - curve25519_add_after_basic(g, f, c); - curve25519_sub_after_basic(f, f, c); - curve25519_mul(r->x, e, f); - curve25519_mul(r->y, h, g); - curve25519_mul(r->z, g, f); - curve25519_mul(r->t, e, h); -} - -void ge25519_pnielsadd(ge25519_pniels *r, const ge25519 *p, const ge25519_pniels *q) { - bignum25519 a = {0}, b = {0}, c = {0}, x = {0}, y = {0}, z = {0}, t = {0}; - - curve25519_sub(a, p->y, p->x); - curve25519_add(b, p->y, p->x); - curve25519_mul(a, a, q->ysubx); - curve25519_mul(x, b, q->xaddy); - curve25519_add(y, x, a); - curve25519_sub(x, x, a); - curve25519_mul(c, p->t, q->t2d); - curve25519_mul(t, p->z, q->z); - curve25519_add(t, t, t); - curve25519_add_after_basic(z, t, c); - curve25519_sub_after_basic(t, t, c); - curve25519_mul(r->xaddy, x, t); - curve25519_mul(r->ysubx, y, z); - curve25519_mul(r->z, z, t); - curve25519_mul(r->t2d, x, y); - curve25519_copy(y, r->ysubx); - curve25519_sub(r->ysubx, r->ysubx, r->xaddy); - curve25519_add(r->xaddy, r->xaddy, y); - curve25519_mul(r->t2d, r->t2d, ge25519_ec2d); -} - - -/* - pack & unpack -*/ - -void ge25519_pack(unsigned char r[32], const ge25519 *p) { - bignum25519 tx = {0}, ty = {0}, zi = {0}; - unsigned char parity[32] = {0}; - curve25519_recip(zi, p->z); - curve25519_mul(tx, p->x, zi); - curve25519_mul(ty, p->y, zi); - curve25519_contract(r, ty); - curve25519_contract(parity, tx); - r[31] ^= ((parity[0] & 1) << 7); -} - -int ge25519_unpack_negative_vartime(ge25519 *r, const unsigned char p[32]) { - const unsigned char zero[32] = {0}; - const bignum25519 one = {1}; - unsigned char parity = p[31] >> 7; - unsigned char check[32] = {0}; - bignum25519 t = {0}, root = {0}, num = {0}, den = {0}, d3 = {0}; - - curve25519_expand(r->y, p); - curve25519_copy(r->z, one); - curve25519_square(num, r->y); /* x = y^2 */ - curve25519_mul(den, num, ge25519_ecd); /* den = dy^2 */ - curve25519_sub_reduce(num, num, r->z); /* x = y^1 - 1 */ - curve25519_add(den, den, r->z); /* den = dy^2 + 1 */ - - /* Computation of sqrt(num/den) */ - /* 1.: computation of num^((p-5)/8)*den^((7p-35)/8) = (num*den^7)^((p-5)/8) */ - curve25519_square(t, den); - curve25519_mul(d3, t, den); - curve25519_square(r->x, d3); - curve25519_mul(r->x, r->x, den); - curve25519_mul(r->x, r->x, num); - curve25519_pow_two252m3(r->x, r->x); - - /* 2. computation of r->x = num * den^3 * (num*den^7)^((p-5)/8) */ - curve25519_mul(r->x, r->x, d3); - curve25519_mul(r->x, r->x, num); - - /* 3. Check if either of the roots works: */ - curve25519_square(t, r->x); - curve25519_mul(t, t, den); - curve25519_sub_reduce(root, t, num); - curve25519_contract(check, root); - if (!ed25519_verify(check, zero, 32)) { - curve25519_add_reduce(t, t, num); - curve25519_contract(check, t); - if (!ed25519_verify(check, zero, 32)) - return 0; - curve25519_mul(r->x, r->x, ge25519_sqrtneg1); - } - - curve25519_contract(check, r->x); - if ((check[0] & 1) == parity) { - curve25519_copy(t, r->x); - curve25519_neg(r->x, t); - } - curve25519_mul(r->t, r->x, r->y); - return 1; -} - -/* - scalarmults -*/ - -void ge25519_set_neutral(ge25519 *r) -{ - memzero(r, sizeof(ge25519)); - r->y[0] = 1; - r->z[0] = 1; -} - -#define S1_SWINDOWSIZE 5 -#define S1_TABLE_SIZE (1<<(S1_SWINDOWSIZE-2)) -#ifdef ED25519_NO_PRECOMP -#define S2_SWINDOWSIZE 5 -#else -#define S2_SWINDOWSIZE 7 -#endif -#define S2_TABLE_SIZE (1<<(S2_SWINDOWSIZE-2)) - -/* computes [s1]p1 + [s2]base */ -void ge25519_double_scalarmult_vartime(ge25519 *r, const ge25519 *p1, const bignum256modm s1, const bignum256modm s2) { - signed char slide1[256] = {0}, slide2[256] = {0}; - ge25519_pniels pre1[S1_TABLE_SIZE] = {0}; -#ifdef ED25519_NO_PRECOMP - ge25519_pniels pre2[S2_TABLE_SIZE] = {0}; -#endif - ge25519 dp = {0}; - ge25519_p1p1 t = {0}; - int32_t i = 0; - - memzero(&t, sizeof(ge25519_p1p1)); - contract256_slidingwindow_modm(slide1, s1, S1_SWINDOWSIZE); - contract256_slidingwindow_modm(slide2, s2, S2_SWINDOWSIZE); - - ge25519_double(&dp, p1); - ge25519_full_to_pniels(pre1, p1); - for (i = 0; i < S1_TABLE_SIZE - 1; i++) - ge25519_pnielsadd(&pre1[i+1], &dp, &pre1[i]); - -#ifdef ED25519_NO_PRECOMP - ge25519_double(&dp, &ge25519_basepoint); - ge25519_full_to_pniels(pre2, &ge25519_basepoint); - for (i = 0; i < S2_TABLE_SIZE - 1; i++) - ge25519_pnielsadd(&pre2[i+1], &dp, &pre2[i]); -#endif - - ge25519_set_neutral(r); - - i = 255; - while ((i >= 0) && !(slide1[i] | slide2[i])) - i--; - - for (; i >= 0; i--) { - ge25519_double_p1p1(&t, r); - - if (slide1[i]) { - ge25519_p1p1_to_full(r, &t); - ge25519_pnielsadd_p1p1(&t, r, &pre1[abs(slide1[i]) / 2], (unsigned char)slide1[i] >> 7); - } - - if (slide2[i]) { - ge25519_p1p1_to_full(r, &t); -#ifdef ED25519_NO_PRECOMP - ge25519_pnielsadd_p1p1(&t, r, &pre2[abs(slide2[i]) / 2], (unsigned char)slide2[i] >> 7); -#else - ge25519_nielsadd2_p1p1(&t, r, &ge25519_niels_sliding_multiples[abs(slide2[i]) / 2], (unsigned char)slide2[i] >> 7); -#endif - } - - ge25519_p1p1_to_partial(r, &t); - } - curve25519_mul(r->t, t.x, t.y); - memzero(slide1, sizeof(slide1)); - memzero(slide2, sizeof(slide2)); -} - -/* computes [s1]p1 + [s2]p2 */ -#if USE_MONERO -void ge25519_double_scalarmult_vartime2(ge25519 *r, const ge25519 *p1, const bignum256modm s1, const ge25519 *p2, const bignum256modm s2) { - signed char slide1[256] = {0}, slide2[256] = {0}; - ge25519_pniels pre1[S1_TABLE_SIZE] = {0}; - ge25519_pniels pre2[S1_TABLE_SIZE] = {0}; - ge25519 dp = {0}; - ge25519_p1p1 t = {0}; - int32_t i = 0; - - memzero(&t, sizeof(ge25519_p1p1)); - contract256_slidingwindow_modm(slide1, s1, S1_SWINDOWSIZE); - contract256_slidingwindow_modm(slide2, s2, S1_SWINDOWSIZE); - - ge25519_double(&dp, p1); - ge25519_full_to_pniels(pre1, p1); - for (i = 0; i < S1_TABLE_SIZE - 1; i++) - ge25519_pnielsadd(&pre1[i+1], &dp, &pre1[i]); - - ge25519_double(&dp, p2); - ge25519_full_to_pniels(pre2, p2); - for (i = 0; i < S1_TABLE_SIZE - 1; i++) - ge25519_pnielsadd(&pre2[i+1], &dp, &pre2[i]); - - ge25519_set_neutral(r); - - i = 255; - while ((i >= 0) && !(slide1[i] | slide2[i])) - i--; - - for (; i >= 0; i--) { - ge25519_double_p1p1(&t, r); - - if (slide1[i]) { - ge25519_p1p1_to_full(r, &t); - ge25519_pnielsadd_p1p1(&t, r, &pre1[abs(slide1[i]) / 2], (unsigned char)slide1[i] >> 7); - } - - if (slide2[i]) { - ge25519_p1p1_to_full(r, &t); - ge25519_pnielsadd_p1p1(&t, r, &pre2[abs(slide2[i]) / 2], (unsigned char)slide2[i] >> 7); - } - - ge25519_p1p1_to_partial(r, &t); - } - curve25519_mul(r->t, t.x, t.y); - memzero(slide1, sizeof(slide1)); - memzero(slide2, sizeof(slide2)); -} -#endif - -/* - * The following conditional move stuff uses conditional moves. - * I will check on which compilers this works, and provide suitable - * workarounds for those where it doesn't. - * - * This works on gcc 4.x and above with -O3. Don't use -O2, this will - * cause the code to not generate conditional moves. Don't use any -march= - * with less than i686 on x86 - */ -static void ge25519_cmove_stride4(long * r, long * p, long * pos, long * n, int stride) { - long x0=r[0], x1=r[1], x2=r[2], x3=r[3], y0 = 0, y1 = 0, y2 = 0, y3 = 0; - for(; p= 0; i--) { - int k=abs(slide1[i]); - ge25519_double_partial(r, r); - ge25519_double_partial(r, r); - ge25519_double_partial(r, r); - ge25519_double_p1p1(&t, r); - ge25519_move_conditional_pniels_array(&pre, pre1, k, 9); - ge25519_p1p1_to_full(r, &t); - ge25519_pnielsadd_p1p1(&t, r, &pre, (unsigned char)slide1[i] >> 7); - ge25519_p1p1_to_partial(r, &t); - } - curve25519_mul(r->t, t.x, t.y); - memzero(slide1, sizeof(slide1)); -} - -void ge25519_scalarmult_base_choose_niels(ge25519_niels *t, const uint8_t table[256][96], uint32_t pos, signed char b) { - bignum25519 neg = {0}; - uint32_t sign = (uint32_t)((unsigned char)b >> 7); - uint32_t mask = ~(sign - 1); - uint32_t u = (b + mask) ^ mask; - - /* ysubx, xaddy, t2d in packed form. initialize to ysubx = 1, xaddy = 1, t2d = 0 */ - uint8_t packed[96] = {0}; - packed[0] = 1; - packed[32] = 1; - - ge25519_move_conditional_niels_array((ge25519_niels *)packed, &table[pos*8], u-1, 8); - - /* expand in to t */ - curve25519_expand(t->ysubx, packed + 0); - curve25519_expand(t->xaddy, packed + 32); - curve25519_expand(t->t2d , packed + 64); - - /* adjust for sign */ - curve25519_swap_conditional(t->ysubx, t->xaddy, sign); - curve25519_neg(neg, t->t2d); - curve25519_swap_conditional(t->t2d, neg, sign); -} - -/* computes [s]basepoint */ -void ge25519_scalarmult_base_niels(ge25519 *r, const uint8_t basepoint_table[256][96], const bignum256modm s) { - signed char b[64] = {0}; - uint32_t i = 0; - ge25519_niels t = {0}; - - contract256_window4_modm(b, s); - - ge25519_scalarmult_base_choose_niels(&t, basepoint_table, 0, b[1]); - curve25519_sub_reduce(r->x, t.xaddy, t.ysubx); - curve25519_add_reduce(r->y, t.xaddy, t.ysubx); - memzero(r->z, sizeof(bignum25519)); - curve25519_copy(r->t, t.t2d); - r->z[0] = 2; - for (i = 3; i < 64; i += 2) { - ge25519_scalarmult_base_choose_niels(&t, basepoint_table, i / 2, b[i]); - ge25519_nielsadd2(r, &t); - } - ge25519_double_partial(r, r); - ge25519_double_partial(r, r); - ge25519_double_partial(r, r); - ge25519_double(r, r); - ge25519_scalarmult_base_choose_niels(&t, basepoint_table, 0, b[0]); - curve25519_mul(t.t2d, t.t2d, ge25519_ecd); - ge25519_nielsadd2(r, &t); - for(i = 2; i < 64; i += 2) { - ge25519_scalarmult_base_choose_niels(&t, basepoint_table, i / 2, b[i]); - ge25519_nielsadd2(r, &t); - } -} - -int ge25519_check(const ge25519 *r){ - /* return (z % q != 0 and - x * y % q == z * t % q and - (y * y - x * x - z * z - ed25519.d * t * t) % q == 0) - */ - - bignum25519 z={0}, lhs={0}, rhs={0}, tmp={0}, res={0}; - curve25519_reduce(z, r->z); - - curve25519_mul(lhs, r->x, r->y); - curve25519_mul(rhs, r->z, r->t); - curve25519_sub_reduce(lhs, lhs, rhs); - - curve25519_square(res, r->y); - curve25519_square(tmp, r->x); - curve25519_sub_reduce(res, res, tmp); - curve25519_square(tmp, r->z); - curve25519_sub_reduce(res, res, tmp); - curve25519_square(tmp, r->t); - curve25519_mul(tmp, tmp, ge25519_ecd); - curve25519_sub_reduce(res, res, tmp); - - const int c1 = curve25519_isnonzero(z); - const int c2 = curve25519_isnonzero(lhs); - const int c3 = curve25519_isnonzero(res); - return c1 & (c2^0x1) & (c3^0x1); -} - -int ge25519_eq(const ge25519 *a, const ge25519 *b){ - int eq = 1; - bignum25519 t1={0}, t2={0}; - - eq &= ge25519_check(a); - eq &= ge25519_check(b); - - curve25519_mul(t1, a->x, b->z); - curve25519_mul(t2, b->x, a->z); - curve25519_sub_reduce(t1, t1, t2); - eq &= curve25519_isnonzero(t1) ^ 1; - - curve25519_mul(t1, a->y, b->z); - curve25519_mul(t2, b->y, a->z); - curve25519_sub_reduce(t1, t1, t2); - eq &= curve25519_isnonzero(t1) ^ 1; - - return eq; -} - -void ge25519_copy(ge25519 *dst, const ge25519 *src){ - curve25519_copy(dst->x, src->x); - curve25519_copy(dst->y, src->y); - curve25519_copy(dst->z, src->z); - curve25519_copy(dst->t, src->t); -} - -void ge25519_set_base(ge25519 *r){ - ge25519_copy(r, &ge25519_basepoint); -} - -void ge25519_mul8(ge25519 *r, const ge25519 *t) { - ge25519_double_partial(r, t); - ge25519_double_partial(r, r); - ge25519_double(r, r); -} - -void ge25519_neg_partial(ge25519 *r){ - curve25519_neg(r->x, r->x); -} - -void ge25519_neg_full(ge25519 *r){ - curve25519_neg(r->x, r->x); - curve25519_neg(r->t, r->t); -} - -void ge25519_reduce(ge25519 *r, const ge25519 *t){ - curve25519_reduce(r->x, t->x); - curve25519_reduce(r->y, t->y); - curve25519_reduce(r->z, t->z); - curve25519_reduce(r->t, t->t); -} - -void ge25519_norm(ge25519 *r, const ge25519 * t){ - bignum25519 zinv = {0}; - curve25519_recip(zinv, t->z); - curve25519_mul(r->x, t->x, zinv); - curve25519_mul(r->y, t->y, zinv); - curve25519_mul(r->t, r->x, r->y); - curve25519_set(r->z, 1); -} - -void ge25519_add(ge25519 *r, const ge25519 *p, const ge25519 *q, unsigned char signbit) { - ge25519_pniels P_ni = {0}; - ge25519_p1p1 P_11 = {0}; - - ge25519_full_to_pniels(&P_ni, q); - ge25519_pnielsadd_p1p1(&P_11, p, &P_ni, signbit); - ge25519_p1p1_to_full(r, &P_11); -} - -void ge25519_fromfe_frombytes_vartime(ge25519 *r, const unsigned char *s){ - bignum25519 u={0}, v={0}, w={0}, x={0}, y={0}, z={0}; - unsigned char sign = 0; - - curve25519_expand_reduce(u, s); - - curve25519_square(v, u); - curve25519_add_reduce(v, v, v); /* 2 * u^2 */ - curve25519_set(w, 1); - curve25519_add_reduce(w, v, w); /* w = 2 * u^2 + 1 */ - - curve25519_square(x, w); /* w^2 */ - curve25519_mul(y, fe_ma2, v); /* -2 * A^2 * u^2 */ - curve25519_add_reduce(x, x, y); /* x = w^2 - 2 * A^2 * u^2 */ - - curve25519_divpowm1(r->x, w, x); /* (w / x)^(m + 1) */ - curve25519_square(y, r->x); - curve25519_mul(x, y, x); - curve25519_sub_reduce(y, w, x); - curve25519_copy(z, fe_ma); - - if (curve25519_isnonzero(y)) { - curve25519_add_reduce(y, w, x); - if (curve25519_isnonzero(y)) { - goto negative; - } else { - curve25519_mul(r->x, r->x, fe_fffb1); - } - } else { - curve25519_mul(r->x, r->x, fe_fffb2); - } - curve25519_mul(r->x, r->x, u); /* u * sqrt(2 * A * (A + 2) * w / x) */ - curve25519_mul(z, z, v); /* -2 * A * u^2 */ - sign = 0; - goto setsign; -negative: - curve25519_mul(x, x, fe_sqrtm1); - curve25519_sub_reduce(y, w, x); - if (curve25519_isnonzero(y)) { - assert((curve25519_add_reduce(y, w, x), !curve25519_isnonzero(y))); - curve25519_mul(r->x, r->x, fe_fffb3); - } else { - curve25519_mul(r->x, r->x, fe_fffb4); - } - /* r->x = sqrt(A * (A + 2) * w / x) */ - /* z = -A */ - sign = 1; -setsign: - if (curve25519_isnegative(r->x) != sign) { - assert(curve25519_isnonzero(r->x)); - curve25519_neg(r->x, r->x); - } - curve25519_add_reduce(r->z, z, w); - curve25519_sub_reduce(r->y, z, w); - curve25519_mul(r->x, r->x, r->z); - - // Partial form, saving from T coord computation . - // Later is mul8 discarding T anyway. - // rt = ((rx * ry % q) * inv(rz)) % q - // curve25519_mul(x, r->x, r->y); - // curve25519_recip(z, r->z); - // curve25519_mul(r->t, x, z); - -#if !defined(NDEBUG) - { - bignum25519 check_x={0}, check_y={0}, check_iz={0}, check_v={0}; - curve25519_recip(check_iz, r->z); - curve25519_mul(check_x, r->x, check_iz); - curve25519_mul(check_y, r->y, check_iz); - curve25519_square(check_x, check_x); - curve25519_square(check_y, check_y); - curve25519_mul(check_v, check_x, check_y); - curve25519_mul(check_v, fe_d, check_v); - curve25519_add_reduce(check_v, check_v, check_x); - curve25519_sub_reduce(check_v, check_v, check_y); - curve25519_set(check_x, 1); - curve25519_add_reduce(check_v, check_v, check_x); - assert(!curve25519_isnonzero(check_v)); - } -#endif -} - -int ge25519_unpack_vartime(ge25519 *r, const unsigned char *s){ - int res = ge25519_unpack_negative_vartime(r, s); - ge25519_neg_full(r); - return res; -} - -void ge25519_scalarmult_base_wrapper(ge25519 *r, const bignum256modm s){ - ge25519_scalarmult_base_niels(r, ge25519_niels_base_multiples, s); -} diff --git a/trezor-crypto/crypto/ed25519-donna/ed25519-keccak.c b/trezor-crypto/crypto/ed25519-donna/ed25519-keccak.c deleted file mode 100644 index 4c5ca06ead0..00000000000 --- a/trezor-crypto/crypto/ed25519-donna/ed25519-keccak.c +++ /dev/null @@ -1,8 +0,0 @@ -#include - -#include -#include - -#define ED25519_SUFFIX _keccak - -#include "ed25519.c" diff --git a/trezor-crypto/crypto/ed25519-donna/ed25519-sha3.c b/trezor-crypto/crypto/ed25519-donna/ed25519-sha3.c deleted file mode 100644 index 8993ca83fff..00000000000 --- a/trezor-crypto/crypto/ed25519-donna/ed25519-sha3.c +++ /dev/null @@ -1,8 +0,0 @@ -#include - -#include -#include - -#define ED25519_SUFFIX _sha3 - -#include "ed25519.c" diff --git a/trezor-crypto/crypto/ed25519-donna/ed25519.c b/trezor-crypto/crypto/ed25519-donna/ed25519.c deleted file mode 100644 index 6e01c0c1e98..00000000000 --- a/trezor-crypto/crypto/ed25519-donna/ed25519.c +++ /dev/null @@ -1,297 +0,0 @@ -/* - Public domain by Andrew M. - - Ed25519 reference implementation using Ed25519-donna -*/ - - -/* define ED25519_SUFFIX to have it appended to the end of each public function */ -#ifdef ED25519_SUFFIX -#define ED25519_FN3(fn,suffix) fn##suffix -#define ED25519_FN2(fn,suffix) ED25519_FN3(fn,suffix) -#define ED25519_FN(fn) ED25519_FN2(fn,ED25519_SUFFIX) -#else -#define ED25519_FN(fn) fn -#endif - -#include -#include - -#include -#include - -/* - Generates a (extsk[0..31]) and aExt (extsk[32..63]) -*/ -DONNA_INLINE static void -ed25519_extsk(hash_512bits extsk, const ed25519_secret_key sk) { - ed25519_hash(extsk, sk, 32); - extsk[0] &= 248; - extsk[31] &= 127; - extsk[31] |= 64; -} - -static void -ed25519_hram(hash_512bits hram, const ed25519_public_key R, const ed25519_public_key pk, const unsigned char *m, size_t mlen) { - ed25519_hash_context ctx; - ed25519_hash_init(&ctx); - ed25519_hash_update(&ctx, R, 32); - ed25519_hash_update(&ctx, pk, 32); - ed25519_hash_update(&ctx, m, mlen); - ed25519_hash_final(&ctx, hram); -} - -void -ED25519_FN(ed25519_publickey) (const ed25519_secret_key sk, ed25519_public_key pk) { - hash_512bits extsk = {0}; - ed25519_extsk(extsk, sk); - ed25519_publickey_ext(extsk, pk); - memzero(&extsk, sizeof(extsk)); -} - -void -ED25519_FN(ed25519_cosi_sign) (const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_secret_key nonce, const ed25519_public_key R, const ed25519_public_key pk, ed25519_cosi_signature sig) { - bignum256modm r = {0}, S = {0}, a = {0}; - hash_512bits extsk = {0}, extnonce = {0}, hram = {0}; - - ed25519_extsk(extsk, sk); - ed25519_extsk(extnonce, nonce); - - /* r = nonce */ - expand256_modm(r, extnonce, 32); - memzero(&extnonce, sizeof(extnonce)); - - /* S = H(R,A,m).. */ - ed25519_hram(hram, R, pk, m, mlen); - expand256_modm(S, hram, 64); - - /* S = H(R,A,m)a */ - expand256_modm(a, extsk, 32); - memzero(&extsk, sizeof(extsk)); - mul256_modm(S, S, a); - memzero(&a, sizeof(a)); - - /* S = (r + H(R,A,m)a) */ - add256_modm(S, S, r); - memzero(&r, sizeof(r)); - - /* S = (r + H(R,A,m)a) mod L */ - contract256_modm(sig, S); -} - -void -ED25519_FN(ed25519_sign_ext) (const unsigned char *m, size_t mlen, const ed25519_secret_key sk, const ed25519_secret_key skext, ed25519_signature RS) { - ed25519_hash_context ctx; - bignum256modm r = {0}, S = {0}, a = {0}; - ge25519 ALIGN(16) R = {0}; - ge25519 ALIGN(16) A = {0}; - ed25519_public_key pk = {0}; - hash_512bits extsk = {0}, hashr = {0}, hram = {0}; - - /* we don't stretch the key through hashing first since its already 64 bytes */ - - memcpy(extsk, sk, 32); - memcpy(extsk+32, skext, 32); - - - /* r = H(aExt[32..64], m) */ - ed25519_hash_init(&ctx); - ed25519_hash_update(&ctx, extsk + 32, 32); - ed25519_hash_update(&ctx, m, mlen); - ed25519_hash_final(&ctx, hashr); - expand256_modm(r, hashr, 64); - memzero(&hashr, sizeof(hashr)); - - /* R = rB */ - ge25519_scalarmult_base_niels(&R, ge25519_niels_base_multiples, r); - ge25519_pack(RS, &R); - - /* a = aExt[0..31] */ - expand256_modm(a, extsk, 32); - memzero(&extsk, sizeof(extsk)); - - /* A = aB */ - ge25519_scalarmult_base_niels(&A, ge25519_niels_base_multiples, a); - ge25519_pack(pk, &A); - - /* S = H(R,A,m).. */ - ed25519_hram(hram, RS, pk, m, mlen); - expand256_modm(S, hram, 64); - - /* S = H(R,A,m)a */ - mul256_modm(S, S, a); - memzero(&a, sizeof(a)); - - /* S = (r + H(R,A,m)a) */ - add256_modm(S, S, r); - memzero(&r, sizeof(r)); - - /* S = (r + H(R,A,m)a) mod L */ - contract256_modm(RS + 32, S); -} - -void -ED25519_FN(ed25519_sign) (const unsigned char *m, size_t mlen, const ed25519_secret_key sk, ed25519_signature RS) { - hash_512bits extsk = {0}; - ed25519_extsk(extsk, sk); - ED25519_FN(ed25519_sign_ext)(m, mlen, extsk, extsk + 32, RS); - memzero(&extsk, sizeof(extsk)); -} - -int -ED25519_FN(ed25519_sign_open) (const unsigned char *m, size_t mlen, const ed25519_public_key pk, const ed25519_signature RS) { - ge25519 ALIGN(16) R = {0}, A = {0}; - hash_512bits hash = {0}; - bignum256modm hram = {0}, S = {0}; - unsigned char checkR[32] = {0}; - - if ((RS[63] & 224) || !ge25519_unpack_negative_vartime(&A, pk)) - return -1; - - /* hram = H(R,A,m) */ - ed25519_hram(hash, RS, pk, m, mlen); - expand256_modm(hram, hash, 64); - - /* S */ - expand_raw256_modm(S, RS + 32); - if (!is_reduced256_modm(S)) - return -1; - - /* SB - H(R,A,m)A */ - ge25519_double_scalarmult_vartime(&R, &A, hram, S); - ge25519_pack(checkR, &R); - - /* check that R = SB - H(R,A,m)A */ - return ed25519_verify(RS, checkR, 32) ? 0 : -1; -} - -int -ED25519_FN(ed25519_scalarmult) (ed25519_public_key res, const ed25519_secret_key sk, const ed25519_public_key pk) { - bignum256modm a = {0}; - ge25519 ALIGN(16) A = {0}, P = {0}; - hash_512bits extsk = {0}; - - ed25519_extsk(extsk, sk); - expand256_modm(a, extsk, 32); - memzero(&extsk, sizeof(extsk)); - - if (!ge25519_unpack_negative_vartime(&P, pk)) { - return -1; - } - - ge25519_scalarmult(&A, &P, a); - memzero(&a, sizeof(a)); - curve25519_neg(A.x, A.x); - ge25519_pack(res, &A); - return 0; -} - - -#ifndef ED25519_SUFFIX - -#include - -void -ed25519_publickey_ext(const ed25519_secret_key extsk, ed25519_public_key pk) { - bignum256modm a = {0}; - ge25519 ALIGN(16) A = {0}; - - expand256_modm(a, extsk, 32); - - /* A = aB */ - ge25519_scalarmult_base_niels(&A, ge25519_niels_base_multiples, a); - memzero(&a, sizeof(a)); - ge25519_pack(pk, &A); -} - -int -ed25519_cosi_combine_publickeys(ed25519_public_key res, CONST ed25519_public_key *pks, size_t n) { - size_t i = 0; - ge25519 P = {0}; - ge25519_pniels sump = {0}; - ge25519_p1p1 sump1 = {0}; - - if (n == 1) { - memcpy(res, pks, sizeof(ed25519_public_key)); - return 0; - } - if (!ge25519_unpack_negative_vartime(&P, pks[i++])) { - return -1; - } - ge25519_full_to_pniels(&sump, &P); - while (i < n - 1) { - if (!ge25519_unpack_negative_vartime(&P, pks[i++])) { - return -1; - } - ge25519_pnielsadd(&sump, &P, &sump); - } - if (!ge25519_unpack_negative_vartime(&P, pks[i++])) { - return -1; - } - ge25519_pnielsadd_p1p1(&sump1, &P, &sump, 0); - ge25519_p1p1_to_partial(&P, &sump1); - curve25519_neg(P.x, P.x); - ge25519_pack(res, &P); - return 0; -} - -void -ed25519_cosi_combine_signatures(ed25519_signature res, const ed25519_public_key R, CONST ed25519_cosi_signature *sigs, size_t n) { - bignum256modm s = {0}, t = {0}; - size_t i = 0; - - expand256_modm(s, sigs[i++], 32); - while (i < n) { - expand256_modm(t, sigs[i++], 32); - add256_modm(s, s, t); - } - memcpy(res, R, 32); - contract256_modm(res + 32, s); -} - -/* - Fast Curve25519 basepoint scalar multiplication -*/ -void -curve25519_scalarmult_basepoint(curve25519_key pk, const curve25519_key e) { - curve25519_key ec = {0}; - bignum256modm s = {0}; - bignum25519 ALIGN(16) yplusz = {0}, zminusy = {0}; - ge25519 ALIGN(16) p = {0}; - size_t i = 0; - - /* clamp */ - for (i = 0; i < 32; i++) ec[i] = e[i]; - ec[0] &= 248; - ec[31] &= 127; - ec[31] |= 64; - - expand_raw256_modm(s, ec); - memzero(&ec, sizeof(ec)); - - /* scalar * basepoint */ - ge25519_scalarmult_base_niels(&p, ge25519_niels_base_multiples, s); - memzero(&s, sizeof(s)); - - /* u = (y + z) / (z - y) */ - curve25519_add(yplusz, p.y, p.z); - curve25519_sub(zminusy, p.z, p.y); - curve25519_recip(zminusy, zminusy); - curve25519_mul(yplusz, yplusz, zminusy); - curve25519_contract(pk, yplusz); -} - -void -curve25519_scalarmult(curve25519_key mypublic, const curve25519_key secret, const curve25519_key basepoint) { - curve25519_key e = {0}; - size_t i = 0; - - for (i = 0;i < 32;++i) e[i] = secret[i]; - e[0] &= 0xf8; - e[31] &= 0x7f; - e[31] |= 0x40; - curve25519_scalarmult_donna(mypublic, e, basepoint); - memzero(&e, sizeof(e)); -} - -#endif // ED25519_SUFFIX diff --git a/trezor-crypto/crypto/ed25519-donna/modm-donna-32bit.c b/trezor-crypto/crypto/ed25519-donna/modm-donna-32bit.c deleted file mode 100644 index a2b49ee5c3e..00000000000 --- a/trezor-crypto/crypto/ed25519-donna/modm-donna-32bit.c +++ /dev/null @@ -1,517 +0,0 @@ -/* - Public domain by Andrew M. -*/ - -#include - -/* - Arithmetic modulo the group order n = 2^252 + 27742317777372353535851937790883648493 = 7237005577332262213973186563042994240857116359379907606001950938285454250989 - - k = 32 - b = 1 << 8 = 256 - m = 2^252 + 27742317777372353535851937790883648493 = 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed - mu = floor( b^(k*2) / m ) = 0xfffffffffffffffffffffffffffffffeb2106215d086329a7ed9ce5a30a2c131b -*/ - -const bignum256modm modm_m = { - 0x1cf5d3ed, 0x20498c69, 0x2f79cd65, 0x37be77a8, - 0x00000014, 0x00000000, 0x00000000, 0x00000000, - 0x00001000 -}; - -const bignum256modm modm_mu = { - 0x0a2c131b, 0x3673968c, 0x06329a7e, 0x01885742, - 0x3fffeb21, 0x3fffffff, 0x3fffffff, 0x3fffffff, - 0x000fffff -}; - -static bignum256modm_element_t -lt_modm(bignum256modm_element_t a, bignum256modm_element_t b) { - return (a - b) >> 31; -} - -/* see HAC, Alg. 14.42 Step 4 */ -void reduce256_modm(bignum256modm r) { - bignum256modm t = {0}; - bignum256modm_element_t b = 0, pb = 0, mask = 0; - - /* t = r - m */ - pb = 0; - pb += modm_m[0]; b = lt_modm(r[0], pb); t[0] = (r[0] - pb + (b << 30)); pb = b; - pb += modm_m[1]; b = lt_modm(r[1], pb); t[1] = (r[1] - pb + (b << 30)); pb = b; - pb += modm_m[2]; b = lt_modm(r[2], pb); t[2] = (r[2] - pb + (b << 30)); pb = b; - pb += modm_m[3]; b = lt_modm(r[3], pb); t[3] = (r[3] - pb + (b << 30)); pb = b; - pb += modm_m[4]; b = lt_modm(r[4], pb); t[4] = (r[4] - pb + (b << 30)); pb = b; - pb += modm_m[5]; b = lt_modm(r[5], pb); t[5] = (r[5] - pb + (b << 30)); pb = b; - pb += modm_m[6]; b = lt_modm(r[6], pb); t[6] = (r[6] - pb + (b << 30)); pb = b; - pb += modm_m[7]; b = lt_modm(r[7], pb); t[7] = (r[7] - pb + (b << 30)); pb = b; - pb += modm_m[8]; b = lt_modm(r[8], pb); t[8] = (r[8] - pb + (b << 16)); - - /* keep r if r was smaller than m */ - mask = b - 1; - r[0] ^= mask & (r[0] ^ t[0]); - r[1] ^= mask & (r[1] ^ t[1]); - r[2] ^= mask & (r[2] ^ t[2]); - r[3] ^= mask & (r[3] ^ t[3]); - r[4] ^= mask & (r[4] ^ t[4]); - r[5] ^= mask & (r[5] ^ t[5]); - r[6] ^= mask & (r[6] ^ t[6]); - r[7] ^= mask & (r[7] ^ t[7]); - r[8] ^= mask & (r[8] ^ t[8]); -} - -/* - Barrett reduction, see HAC, Alg. 14.42 - - Instead of passing in x, pre-process in to q1 and r1 for efficiency -*/ -void barrett_reduce256_modm(bignum256modm r, const bignum256modm q1, const bignum256modm r1) { - bignum256modm q3 = {0}, r2 = {0}; - uint64_t c = 0; - bignum256modm_element_t f = 0, b = 0, pb = 0; - - /* q1 = x >> 248 = 264 bits = 9 30 bit elements - q2 = mu * q1 - q3 = (q2 / 256(32+1)) = q2 / (2^8)^(32+1) = q2 >> 264 */ - c = mul32x32_64(modm_mu[0], q1[7]) + mul32x32_64(modm_mu[1], q1[6]) + mul32x32_64(modm_mu[2], q1[5]) + mul32x32_64(modm_mu[3], q1[4]) + mul32x32_64(modm_mu[4], q1[3]) + mul32x32_64(modm_mu[5], q1[2]) + mul32x32_64(modm_mu[6], q1[1]) + mul32x32_64(modm_mu[7], q1[0]); - c >>= 30; - c += mul32x32_64(modm_mu[0], q1[8]) + mul32x32_64(modm_mu[1], q1[7]) + mul32x32_64(modm_mu[2], q1[6]) + mul32x32_64(modm_mu[3], q1[5]) + mul32x32_64(modm_mu[4], q1[4]) + mul32x32_64(modm_mu[5], q1[3]) + mul32x32_64(modm_mu[6], q1[2]) + mul32x32_64(modm_mu[7], q1[1]) + mul32x32_64(modm_mu[8], q1[0]); - f = (bignum256modm_element_t)c; q3[0] = (f >> 24) & 0x3f; c >>= 30; - c += mul32x32_64(modm_mu[1], q1[8]) + mul32x32_64(modm_mu[2], q1[7]) + mul32x32_64(modm_mu[3], q1[6]) + mul32x32_64(modm_mu[4], q1[5]) + mul32x32_64(modm_mu[5], q1[4]) + mul32x32_64(modm_mu[6], q1[3]) + mul32x32_64(modm_mu[7], q1[2]) + mul32x32_64(modm_mu[8], q1[1]); - f = (bignum256modm_element_t)c; q3[0] |= (f << 6) & 0x3fffffff; q3[1] = (f >> 24) & 0x3f; c >>= 30; - c += mul32x32_64(modm_mu[2], q1[8]) + mul32x32_64(modm_mu[3], q1[7]) + mul32x32_64(modm_mu[4], q1[6]) + mul32x32_64(modm_mu[5], q1[5]) + mul32x32_64(modm_mu[6], q1[4]) + mul32x32_64(modm_mu[7], q1[3]) + mul32x32_64(modm_mu[8], q1[2]); - f = (bignum256modm_element_t)c; q3[1] |= (f << 6) & 0x3fffffff; q3[2] = (f >> 24) & 0x3f; c >>= 30; - c += mul32x32_64(modm_mu[3], q1[8]) + mul32x32_64(modm_mu[4], q1[7]) + mul32x32_64(modm_mu[5], q1[6]) + mul32x32_64(modm_mu[6], q1[5]) + mul32x32_64(modm_mu[7], q1[4]) + mul32x32_64(modm_mu[8], q1[3]); - f = (bignum256modm_element_t)c; q3[2] |= (f << 6) & 0x3fffffff; q3[3] = (f >> 24) & 0x3f; c >>= 30; - c += mul32x32_64(modm_mu[4], q1[8]) + mul32x32_64(modm_mu[5], q1[7]) + mul32x32_64(modm_mu[6], q1[6]) + mul32x32_64(modm_mu[7], q1[5]) + mul32x32_64(modm_mu[8], q1[4]); - f = (bignum256modm_element_t)c; q3[3] |= (f << 6) & 0x3fffffff; q3[4] = (f >> 24) & 0x3f; c >>= 30; - c += mul32x32_64(modm_mu[5], q1[8]) + mul32x32_64(modm_mu[6], q1[7]) + mul32x32_64(modm_mu[7], q1[6]) + mul32x32_64(modm_mu[8], q1[5]); - f = (bignum256modm_element_t)c; q3[4] |= (f << 6) & 0x3fffffff; q3[5] = (f >> 24) & 0x3f; c >>= 30; - c += mul32x32_64(modm_mu[6], q1[8]) + mul32x32_64(modm_mu[7], q1[7]) + mul32x32_64(modm_mu[8], q1[6]); - f = (bignum256modm_element_t)c; q3[5] |= (f << 6) & 0x3fffffff; q3[6] = (f >> 24) & 0x3f; c >>= 30; - c += mul32x32_64(modm_mu[7], q1[8]) + mul32x32_64(modm_mu[8], q1[7]); - f = (bignum256modm_element_t)c; q3[6] |= (f << 6) & 0x3fffffff; q3[7] = (f >> 24) & 0x3f; c >>= 30; - c += mul32x32_64(modm_mu[8], q1[8]); - f = (bignum256modm_element_t)c; q3[7] |= (f << 6) & 0x3fffffff; q3[8] = (bignum256modm_element_t)(c >> 24); - - /* r1 = (x mod 256^(32+1)) = x mod (2^8)(32+1) = x & ((1 << 264) - 1) - r2 = (q3 * m) mod (256^(32+1)) = (q3 * m) & ((1 << 264) - 1) */ - c = mul32x32_64(modm_m[0], q3[0]); - r2[0] = (bignum256modm_element_t)(c & 0x3fffffff); c >>= 30; - c += mul32x32_64(modm_m[0], q3[1]) + mul32x32_64(modm_m[1], q3[0]); - r2[1] = (bignum256modm_element_t)(c & 0x3fffffff); c >>= 30; - c += mul32x32_64(modm_m[0], q3[2]) + mul32x32_64(modm_m[1], q3[1]) + mul32x32_64(modm_m[2], q3[0]); - r2[2] = (bignum256modm_element_t)(c & 0x3fffffff); c >>= 30; - c += mul32x32_64(modm_m[0], q3[3]) + mul32x32_64(modm_m[1], q3[2]) + mul32x32_64(modm_m[2], q3[1]) + mul32x32_64(modm_m[3], q3[0]); - r2[3] = (bignum256modm_element_t)(c & 0x3fffffff); c >>= 30; - c += mul32x32_64(modm_m[0], q3[4]) + mul32x32_64(modm_m[1], q3[3]) + mul32x32_64(modm_m[2], q3[2]) + mul32x32_64(modm_m[3], q3[1]) + mul32x32_64(modm_m[4], q3[0]); - r2[4] = (bignum256modm_element_t)(c & 0x3fffffff); c >>= 30; - c += mul32x32_64(modm_m[0], q3[5]) + mul32x32_64(modm_m[1], q3[4]) + mul32x32_64(modm_m[2], q3[3]) + mul32x32_64(modm_m[3], q3[2]) + mul32x32_64(modm_m[4], q3[1]) + mul32x32_64(modm_m[5], q3[0]); - r2[5] = (bignum256modm_element_t)(c & 0x3fffffff); c >>= 30; - c += mul32x32_64(modm_m[0], q3[6]) + mul32x32_64(modm_m[1], q3[5]) + mul32x32_64(modm_m[2], q3[4]) + mul32x32_64(modm_m[3], q3[3]) + mul32x32_64(modm_m[4], q3[2]) + mul32x32_64(modm_m[5], q3[1]) + mul32x32_64(modm_m[6], q3[0]); - r2[6] = (bignum256modm_element_t)(c & 0x3fffffff); c >>= 30; - c += mul32x32_64(modm_m[0], q3[7]) + mul32x32_64(modm_m[1], q3[6]) + mul32x32_64(modm_m[2], q3[5]) + mul32x32_64(modm_m[3], q3[4]) + mul32x32_64(modm_m[4], q3[3]) + mul32x32_64(modm_m[5], q3[2]) + mul32x32_64(modm_m[6], q3[1]) + mul32x32_64(modm_m[7], q3[0]); - r2[7] = (bignum256modm_element_t)(c & 0x3fffffff); c >>= 30; - c += mul32x32_64(modm_m[0], q3[8]) + mul32x32_64(modm_m[1], q3[7]) + mul32x32_64(modm_m[2], q3[6]) + mul32x32_64(modm_m[3], q3[5]) + mul32x32_64(modm_m[4], q3[4]) + mul32x32_64(modm_m[5], q3[3]) + mul32x32_64(modm_m[6], q3[2]) + mul32x32_64(modm_m[7], q3[1]) + mul32x32_64(modm_m[8], q3[0]); - r2[8] = (bignum256modm_element_t)(c & 0xffffff); - - /* r = r1 - r2 - if (r < 0) r += (1 << 264) */ - pb = 0; - pb += r2[0]; b = lt_modm(r1[0], pb); r[0] = (r1[0] - pb + (b << 30)); pb = b; - pb += r2[1]; b = lt_modm(r1[1], pb); r[1] = (r1[1] - pb + (b << 30)); pb = b; - pb += r2[2]; b = lt_modm(r1[2], pb); r[2] = (r1[2] - pb + (b << 30)); pb = b; - pb += r2[3]; b = lt_modm(r1[3], pb); r[3] = (r1[3] - pb + (b << 30)); pb = b; - pb += r2[4]; b = lt_modm(r1[4], pb); r[4] = (r1[4] - pb + (b << 30)); pb = b; - pb += r2[5]; b = lt_modm(r1[5], pb); r[5] = (r1[5] - pb + (b << 30)); pb = b; - pb += r2[6]; b = lt_modm(r1[6], pb); r[6] = (r1[6] - pb + (b << 30)); pb = b; - pb += r2[7]; b = lt_modm(r1[7], pb); r[7] = (r1[7] - pb + (b << 30)); pb = b; - pb += r2[8]; b = lt_modm(r1[8], pb); r[8] = (r1[8] - pb + (b << 24)); - - reduce256_modm(r); - reduce256_modm(r); -} - -/* addition modulo m */ -void add256_modm(bignum256modm r, const bignum256modm x, const bignum256modm y) { - bignum256modm_element_t c = 0; - - c = x[0] + y[0]; r[0] = c & 0x3fffffff; c >>= 30; - c += x[1] + y[1]; r[1] = c & 0x3fffffff; c >>= 30; - c += x[2] + y[2]; r[2] = c & 0x3fffffff; c >>= 30; - c += x[3] + y[3]; r[3] = c & 0x3fffffff; c >>= 30; - c += x[4] + y[4]; r[4] = c & 0x3fffffff; c >>= 30; - c += x[5] + y[5]; r[5] = c & 0x3fffffff; c >>= 30; - c += x[6] + y[6]; r[6] = c & 0x3fffffff; c >>= 30; - c += x[7] + y[7]; r[7] = c & 0x3fffffff; c >>= 30; - c += x[8] + y[8]; r[8] = c; - - reduce256_modm(r); -} - -/* -x modulo m */ -void neg256_modm(bignum256modm r, const bignum256modm x) { - bignum256modm_element_t b = 0, pb = 0; - - /* r = m - x */ - pb = 0; - pb += x[0]; b = lt_modm(modm_m[0], pb); r[0] = (modm_m[0] - pb + (b << 30)); pb = b; - pb += x[1]; b = lt_modm(modm_m[1], pb); r[1] = (modm_m[1] - pb + (b << 30)); pb = b; - pb += x[2]; b = lt_modm(modm_m[2], pb); r[2] = (modm_m[2] - pb + (b << 30)); pb = b; - pb += x[3]; b = lt_modm(modm_m[3], pb); r[3] = (modm_m[3] - pb + (b << 30)); pb = b; - pb += x[4]; b = lt_modm(modm_m[4], pb); r[4] = (modm_m[4] - pb + (b << 30)); pb = b; - pb += x[5]; b = lt_modm(modm_m[5], pb); r[5] = (modm_m[5] - pb + (b << 30)); pb = b; - pb += x[6]; b = lt_modm(modm_m[6], pb); r[6] = (modm_m[6] - pb + (b << 30)); pb = b; - pb += x[7]; b = lt_modm(modm_m[7], pb); r[7] = (modm_m[7] - pb + (b << 30)); pb = b; - pb += x[8]; b = lt_modm(modm_m[8], pb); r[8] = (modm_m[8] - pb + (b << 16)); - - // if x==0, reduction is required - reduce256_modm(r); -} - -/* consts for subtraction, > p */ -/* Emilia Kasper trick, https://www.imperialviolet.org/2010/12/04/ecc.html */ -const uint32_t twoP[] = { - 0x5cf5d3ed, 0x60498c68, 0x6f79cd64, 0x77be77a7, 0x40000013, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0xfff}; - -/* subtraction x-y % m */ -void sub256_modm(bignum256modm r, const bignum256modm x, const bignum256modm y) { - bignum256modm_element_t c = 0; - c = twoP[0] + x[0] - y[0]; r[0] = c & 0x3fffffff; c >>= 30; - c += twoP[1] + x[1] - y[1]; r[1] = c & 0x3fffffff; c >>= 30; - c += twoP[2] + x[2] - y[2]; r[2] = c & 0x3fffffff; c >>= 30; - c += twoP[3] + x[3] - y[3]; r[3] = c & 0x3fffffff; c >>= 30; - c += twoP[4] + x[4] - y[4]; r[4] = c & 0x3fffffff; c >>= 30; - c += twoP[5] + x[5] - y[5]; r[5] = c & 0x3fffffff; c >>= 30; - c += twoP[6] + x[6] - y[6]; r[6] = c & 0x3fffffff; c >>= 30; - c += twoP[7] + x[7] - y[7]; r[7] = c & 0x3fffffff; c >>= 30; - c += twoP[8] + x[8] - y[8]; r[8] = c; - reduce256_modm(r); -} - -/* multiplication modulo m */ -void mul256_modm(bignum256modm r, const bignum256modm x, const bignum256modm y) { - bignum256modm r1 = {0}, q1 = {0}; - uint64_t c = 0; - bignum256modm_element_t f = 0; - - /* r1 = (x mod 256^(32+1)) = x mod (2^8)(31+1) = x & ((1 << 264) - 1) - q1 = x >> 248 = 264 bits = 9 30 bit elements */ - c = mul32x32_64(x[0], y[0]); - f = (bignum256modm_element_t)c; r1[0] = (f & 0x3fffffff); c >>= 30; - c += mul32x32_64(x[0], y[1]) + mul32x32_64(x[1], y[0]); - f = (bignum256modm_element_t)c; r1[1] = (f & 0x3fffffff); c >>= 30; - c += mul32x32_64(x[0], y[2]) + mul32x32_64(x[1], y[1]) + mul32x32_64(x[2], y[0]); - f = (bignum256modm_element_t)c; r1[2] = (f & 0x3fffffff); c >>= 30; - c += mul32x32_64(x[0], y[3]) + mul32x32_64(x[1], y[2]) + mul32x32_64(x[2], y[1]) + mul32x32_64(x[3], y[0]); - f = (bignum256modm_element_t)c; r1[3] = (f & 0x3fffffff); c >>= 30; - c += mul32x32_64(x[0], y[4]) + mul32x32_64(x[1], y[3]) + mul32x32_64(x[2], y[2]) + mul32x32_64(x[3], y[1]) + mul32x32_64(x[4], y[0]); - f = (bignum256modm_element_t)c; r1[4] = (f & 0x3fffffff); c >>= 30; - c += mul32x32_64(x[0], y[5]) + mul32x32_64(x[1], y[4]) + mul32x32_64(x[2], y[3]) + mul32x32_64(x[3], y[2]) + mul32x32_64(x[4], y[1]) + mul32x32_64(x[5], y[0]); - f = (bignum256modm_element_t)c; r1[5] = (f & 0x3fffffff); c >>= 30; - c += mul32x32_64(x[0], y[6]) + mul32x32_64(x[1], y[5]) + mul32x32_64(x[2], y[4]) + mul32x32_64(x[3], y[3]) + mul32x32_64(x[4], y[2]) + mul32x32_64(x[5], y[1]) + mul32x32_64(x[6], y[0]); - f = (bignum256modm_element_t)c; r1[6] = (f & 0x3fffffff); c >>= 30; - c += mul32x32_64(x[0], y[7]) + mul32x32_64(x[1], y[6]) + mul32x32_64(x[2], y[5]) + mul32x32_64(x[3], y[4]) + mul32x32_64(x[4], y[3]) + mul32x32_64(x[5], y[2]) + mul32x32_64(x[6], y[1]) + mul32x32_64(x[7], y[0]); - f = (bignum256modm_element_t)c; r1[7] = (f & 0x3fffffff); c >>= 30; - c += mul32x32_64(x[0], y[8]) + mul32x32_64(x[1], y[7]) + mul32x32_64(x[2], y[6]) + mul32x32_64(x[3], y[5]) + mul32x32_64(x[4], y[4]) + mul32x32_64(x[5], y[3]) + mul32x32_64(x[6], y[2]) + mul32x32_64(x[7], y[1]) + mul32x32_64(x[8], y[0]); - f = (bignum256modm_element_t)c; r1[8] = (f & 0x00ffffff); q1[0] = (f >> 8) & 0x3fffff; c >>= 30; - c += mul32x32_64(x[1], y[8]) + mul32x32_64(x[2], y[7]) + mul32x32_64(x[3], y[6]) + mul32x32_64(x[4], y[5]) + mul32x32_64(x[5], y[4]) + mul32x32_64(x[6], y[3]) + mul32x32_64(x[7], y[2]) + mul32x32_64(x[8], y[1]); - f = (bignum256modm_element_t)c; q1[0] = (q1[0] | (f << 22)) & 0x3fffffff; q1[1] = (f >> 8) & 0x3fffff; c >>= 30; - c += mul32x32_64(x[2], y[8]) + mul32x32_64(x[3], y[7]) + mul32x32_64(x[4], y[6]) + mul32x32_64(x[5], y[5]) + mul32x32_64(x[6], y[4]) + mul32x32_64(x[7], y[3]) + mul32x32_64(x[8], y[2]); - f = (bignum256modm_element_t)c; q1[1] = (q1[1] | (f << 22)) & 0x3fffffff; q1[2] = (f >> 8) & 0x3fffff; c >>= 30; - c += mul32x32_64(x[3], y[8]) + mul32x32_64(x[4], y[7]) + mul32x32_64(x[5], y[6]) + mul32x32_64(x[6], y[5]) + mul32x32_64(x[7], y[4]) + mul32x32_64(x[8], y[3]); - f = (bignum256modm_element_t)c; q1[2] = (q1[2] | (f << 22)) & 0x3fffffff; q1[3] = (f >> 8) & 0x3fffff; c >>= 30; - c += mul32x32_64(x[4], y[8]) + mul32x32_64(x[5], y[7]) + mul32x32_64(x[6], y[6]) + mul32x32_64(x[7], y[5]) + mul32x32_64(x[8], y[4]); - f = (bignum256modm_element_t)c; q1[3] = (q1[3] | (f << 22)) & 0x3fffffff; q1[4] = (f >> 8) & 0x3fffff; c >>= 30; - c += mul32x32_64(x[5], y[8]) + mul32x32_64(x[6], y[7]) + mul32x32_64(x[7], y[6]) + mul32x32_64(x[8], y[5]); - f = (bignum256modm_element_t)c; q1[4] = (q1[4] | (f << 22)) & 0x3fffffff; q1[5] = (f >> 8) & 0x3fffff; c >>= 30; - c += mul32x32_64(x[6], y[8]) + mul32x32_64(x[7], y[7]) + mul32x32_64(x[8], y[6]); - f = (bignum256modm_element_t)c; q1[5] = (q1[5] | (f << 22)) & 0x3fffffff; q1[6] = (f >> 8) & 0x3fffff; c >>= 30; - c += mul32x32_64(x[7], y[8]) + mul32x32_64(x[8], y[7]); - f = (bignum256modm_element_t)c; q1[6] = (q1[6] | (f << 22)) & 0x3fffffff; q1[7] = (f >> 8) & 0x3fffff; c >>= 30; - c += mul32x32_64(x[8], y[8]); - f = (bignum256modm_element_t)c; q1[7] = (q1[7] | (f << 22)) & 0x3fffffff; q1[8] = (f >> 8) & 0x3fffff; - - barrett_reduce256_modm(r, q1, r1); -} - -void expand256_modm(bignum256modm out, const unsigned char *in, size_t len) { - unsigned char work[64] = {0}; - bignum256modm_element_t x[16] = {0}; - bignum256modm q1 = {0}; - - memcpy(work, in, len); - x[0] = U8TO32_LE(work + 0); - x[1] = U8TO32_LE(work + 4); - x[2] = U8TO32_LE(work + 8); - x[3] = U8TO32_LE(work + 12); - x[4] = U8TO32_LE(work + 16); - x[5] = U8TO32_LE(work + 20); - x[6] = U8TO32_LE(work + 24); - x[7] = U8TO32_LE(work + 28); - x[8] = U8TO32_LE(work + 32); - x[9] = U8TO32_LE(work + 36); - x[10] = U8TO32_LE(work + 40); - x[11] = U8TO32_LE(work + 44); - x[12] = U8TO32_LE(work + 48); - x[13] = U8TO32_LE(work + 52); - x[14] = U8TO32_LE(work + 56); - x[15] = U8TO32_LE(work + 60); - - /* r1 = (x mod 256^(32+1)) = x mod (2^8)(31+1) = x & ((1 << 264) - 1) */ - out[0] = ( x[0]) & 0x3fffffff; - out[1] = ((x[ 0] >> 30) | (x[ 1] << 2)) & 0x3fffffff; - out[2] = ((x[ 1] >> 28) | (x[ 2] << 4)) & 0x3fffffff; - out[3] = ((x[ 2] >> 26) | (x[ 3] << 6)) & 0x3fffffff; - out[4] = ((x[ 3] >> 24) | (x[ 4] << 8)) & 0x3fffffff; - out[5] = ((x[ 4] >> 22) | (x[ 5] << 10)) & 0x3fffffff; - out[6] = ((x[ 5] >> 20) | (x[ 6] << 12)) & 0x3fffffff; - out[7] = ((x[ 6] >> 18) | (x[ 7] << 14)) & 0x3fffffff; - out[8] = ((x[ 7] >> 16) | (x[ 8] << 16)) & 0x00ffffff; - - /* 8*31 = 248 bits, no need to reduce */ - if (len < 32) - return; - - /* q1 = x >> 248 = 264 bits = 9 30 bit elements */ - q1[0] = ((x[ 7] >> 24) | (x[ 8] << 8)) & 0x3fffffff; - q1[1] = ((x[ 8] >> 22) | (x[ 9] << 10)) & 0x3fffffff; - q1[2] = ((x[ 9] >> 20) | (x[10] << 12)) & 0x3fffffff; - q1[3] = ((x[10] >> 18) | (x[11] << 14)) & 0x3fffffff; - q1[4] = ((x[11] >> 16) | (x[12] << 16)) & 0x3fffffff; - q1[5] = ((x[12] >> 14) | (x[13] << 18)) & 0x3fffffff; - q1[6] = ((x[13] >> 12) | (x[14] << 20)) & 0x3fffffff; - q1[7] = ((x[14] >> 10) | (x[15] << 22)) & 0x3fffffff; - q1[8] = ((x[15] >> 8) ); - - barrett_reduce256_modm(out, q1, out); -} - -void expand_raw256_modm(bignum256modm out, const unsigned char in[32]) { - bignum256modm_element_t x[8] = {0}; - - x[0] = U8TO32_LE(in + 0); - x[1] = U8TO32_LE(in + 4); - x[2] = U8TO32_LE(in + 8); - x[3] = U8TO32_LE(in + 12); - x[4] = U8TO32_LE(in + 16); - x[5] = U8TO32_LE(in + 20); - x[6] = U8TO32_LE(in + 24); - x[7] = U8TO32_LE(in + 28); - - out[0] = ( x[0]) & 0x3fffffff; - out[1] = ((x[ 0] >> 30) | (x[ 1] << 2)) & 0x3fffffff; - out[2] = ((x[ 1] >> 28) | (x[ 2] << 4)) & 0x3fffffff; - out[3] = ((x[ 2] >> 26) | (x[ 3] << 6)) & 0x3fffffff; - out[4] = ((x[ 3] >> 24) | (x[ 4] << 8)) & 0x3fffffff; - out[5] = ((x[ 4] >> 22) | (x[ 5] << 10)) & 0x3fffffff; - out[6] = ((x[ 5] >> 20) | (x[ 6] << 12)) & 0x3fffffff; - out[7] = ((x[ 6] >> 18) | (x[ 7] << 14)) & 0x3fffffff; - out[8] = ((x[ 7] >> 16) ) & 0x0000ffff; -} - -int is_reduced256_modm(const bignum256modm in) -{ - int i = 0; - uint32_t res1 = 0; - uint32_t res2 = 0; - for (i = 8; i >= 0; i--) { - res1 = (res1 << 1) | (in[i] < modm_m[i]); - res2 = (res2 << 1) | (in[i] > modm_m[i]); - } - return res1 > res2; -} - -void contract256_modm(unsigned char out[32], const bignum256modm in) { - U32TO8_LE(out + 0, (in[0] ) | (in[1] << 30)); - U32TO8_LE(out + 4, (in[1] >> 2) | (in[2] << 28)); - U32TO8_LE(out + 8, (in[2] >> 4) | (in[3] << 26)); - U32TO8_LE(out + 12, (in[3] >> 6) | (in[4] << 24)); - U32TO8_LE(out + 16, (in[4] >> 8) | (in[5] << 22)); - U32TO8_LE(out + 20, (in[5] >> 10) | (in[6] << 20)); - U32TO8_LE(out + 24, (in[6] >> 12) | (in[7] << 18)); - U32TO8_LE(out + 28, (in[7] >> 14) | (in[8] << 16)); -} - -void contract256_window4_modm(signed char r[64], const bignum256modm in) { - char carry = 0; - signed char *quads = r; - bignum256modm_element_t i = 0, j = 0, v = 0; - - for (i = 0; i < 8; i += 2) { - v = in[i]; - for (j = 0; j < 7; j++) { - *quads++ = (v & 15); - v >>= 4; - } - v |= (in[i+1] << 2); - for (j = 0; j < 8; j++) { - *quads++ = (v & 15); - v >>= 4; - } - } - v = in[8]; - *quads++ = (v & 15); v >>= 4; - *quads++ = (v & 15); v >>= 4; - *quads++ = (v & 15); v >>= 4; - *quads++ = (v & 15); v >>= 4; - - /* making it signed */ - carry = 0; - for(i = 0; i < 63; i++) { - r[i] += carry; - r[i+1] += (r[i] >> 4); - r[i] &= 15; - carry = (r[i] >> 3); - r[i] -= (carry << 4); - } - r[63] += carry; -} - -void contract256_slidingwindow_modm(signed char r[256], const bignum256modm s, int windowsize) { - int i = 0, j = 0, k = 0, b = 0; - int m = (1 << (windowsize - 1)) - 1, soplen = 256; - signed char *bits = r; - bignum256modm_element_t v = 0; - - /* first put the binary expansion into r */ - for (i = 0; i < 8; i++) { - v = s[i]; - for (j = 0; j < 30; j++, v >>= 1) - *bits++ = (v & 1); - } - v = s[8]; - for (j = 0; j < 16; j++, v >>= 1) - *bits++ = (v & 1); - - /* Making it sliding window */ - for (j = 0; j < soplen; j++) { - if (!r[j]) - continue; - - for (b = 1; (b < (soplen - j)) && (b <= 6); b++) { - if ((r[j] + (r[j + b] << b)) <= m) { - r[j] += r[j + b] << b; - r[j + b] = 0; - } else if ((r[j] - (r[j + b] << b)) >= -m) { - r[j] -= r[j + b] << b; - for (k = j + b; k < soplen; k++) { - if (!r[k]) { - r[k] = 1; - break; - } - r[k] = 0; - } - } else if (r[j + b]) { - break; - } - } - } -} - -void set256_modm(bignum256modm r, uint64_t v) { - r[0] = (bignum256modm_element_t) (v & 0x3fffffff); v >>= 30; - r[1] = (bignum256modm_element_t) (v & 0x3fffffff); v >>= 30; - r[2] = (bignum256modm_element_t) (v & 0x3fffffff); - r[3] = 0; - r[4] = 0; - r[5] = 0; - r[6] = 0; - r[7] = 0; - r[8] = 0; -} - -int get256_modm(uint64_t * v, const bignum256modm r){ - *v = 0; - int con1 = 0; - -#define NONZ(x) ((((((int64_t)(x)) - 1) >> 32) + 1) & 1) - bignum256modm_element_t c = 0; - c = r[0]; *v += (uint64_t)c & 0x3fffffff; c >>= 30; // 30 - c += r[1]; *v += ((uint64_t)c & 0x3fffffff) << 30; c >>= 30; // 60 - c += r[2]; *v += ((uint64_t)c & 0xf) << 60; con1 |= NONZ(c>>4); c >>= 30; // 64 bits - c += r[3]; con1 |= NONZ(c); c >>= 30; - c += r[4]; con1 |= NONZ(c); c >>= 30; - c += r[5]; con1 |= NONZ(c); c >>= 30; - c += r[6]; con1 |= NONZ(c); c >>= 30; - c += r[7]; con1 |= NONZ(c); c >>= 30; - c += r[8]; con1 |= NONZ(c); c >>= 30; - con1 |= NONZ(c); -#undef NONZ - - return con1 ^ 1; -} - -int eq256_modm(const bignum256modm x, const bignum256modm y){ - size_t differentbits = 0; - int len = bignum256modm_limb_size; - while (len--) { - differentbits |= (*x++ ^ *y++); - } - return (int) (1 & ((differentbits - 1) >> bignum256modm_bits_per_limb)); -} - -int cmp256_modm(const bignum256modm x, const bignum256modm y){ - int len = 2*bignum256modm_limb_size; - uint32_t a_gt = 0; - uint32_t b_gt = 0; - - // 16B chunks - while (len--) { - const uint32_t ln = (const uint32_t) len; - const uint32_t a = (x[ln>>1] >> 16*(ln & 1)) & 0xffff; - const uint32_t b = (y[ln>>1] >> 16*(ln & 1)) & 0xffff; - - const uint32_t limb_a_gt = ((b - a) >> 16) & 1; - const uint32_t limb_b_gt = ((a - b) >> 16) & 1; - a_gt |= limb_a_gt & ~b_gt; - b_gt |= limb_b_gt & ~a_gt; - } - - return a_gt - b_gt; -} - -int iszero256_modm(const bignum256modm x){ - size_t differentbits = 0; - int len = bignum256modm_limb_size; - while (len--) { - differentbits |= (*x++); - } - return (int) (1 & ((differentbits - 1) >> bignum256modm_bits_per_limb)); -} - -void copy256_modm(bignum256modm r, const bignum256modm x){ - r[0] = x[0]; - r[1] = x[1]; - r[2] = x[2]; - r[3] = x[3]; - r[4] = x[4]; - r[5] = x[5]; - r[6] = x[6]; - r[7] = x[7]; - r[8] = x[8]; -} - -int check256_modm(const bignum256modm x){ - int ok = 1; - bignum256modm t={0}, z={0}; - - ok &= iszero256_modm(x) ^ 1; - barrett_reduce256_modm(t, z, x); - ok &= eq256_modm(t, x); - return ok; -} - -void mulsub256_modm(bignum256modm r, const bignum256modm a, const bignum256modm b, const bignum256modm c){ - //(cc - aa * bb) % l - bignum256modm t={0}; - mul256_modm(t, a, b); - sub256_modm(r, c, t); -} - -void muladd256_modm(bignum256modm r, const bignum256modm a, const bignum256modm b, const bignum256modm c){ - //(cc + aa * bb) % l - bignum256modm t={0}; - mul256_modm(t, a, b); - add256_modm(r, c, t); -} diff --git a/trezor-crypto/crypto/groestl.c b/trezor-crypto/crypto/groestl.c deleted file mode 100644 index 12d88d877b7..00000000000 --- a/trezor-crypto/crypto/groestl.c +++ /dev/null @@ -1,783 +0,0 @@ -/* Groestl hash from https://github.com/Groestlcoin/vanitygen - * Trezor adaptation by Yura Pakhuchiy . */ -/* - * Groestl implementation. - * - * ==========================(LICENSE BEGIN)============================ - * - * Copyright (c) 2007-2010 Projet RNRT SAPHIR - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * ===========================(LICENSE END)============================= - * - * @author Thomas Pornin - */ - -#include -#include - -#include -#include -#include - - -#define C32e(x) ((SPH_C32(x) >> 24) \ - | ((SPH_C32(x) >> 8) & SPH_C32(0x0000FF00)) \ - | ((SPH_C32(x) << 8) & SPH_C32(0x00FF0000)) \ - | ((SPH_C32(x) << 24) & SPH_C32(0xFF000000))) -#define dec32e_aligned sph_dec32le_aligned -#define enc32e sph_enc32le -#define B32_0(x) ((x) & 0xFF) -#define B32_1(x) (((x) >> 8) & 0xFF) -#define B32_2(x) (((x) >> 16) & 0xFF) -#define B32_3(x) ((x) >> 24) - -#define R32u(u, d) SPH_T32(((u) << 16) | ((d) >> 16)) -#define R32d(u, d) SPH_T32(((u) >> 16) | ((d) << 16)) - -#define PC32up(j, r) ((sph_u32)((j) + (r))) -#define PC32dn(j, r) 0 -#define QC32up(j, r) SPH_C32(0xFFFFFFFF) -#define QC32dn(j, r) (((sph_u32)(r) << 24) ^ SPH_T32(~((sph_u32)(j) << 24))) - -#define C64e(x) ((SPH_C64(x) >> 56) \ - | ((SPH_C64(x) >> 40) & SPH_C64(0x000000000000FF00)) \ - | ((SPH_C64(x) >> 24) & SPH_C64(0x0000000000FF0000)) \ - | ((SPH_C64(x) >> 8) & SPH_C64(0x00000000FF000000)) \ - | ((SPH_C64(x) << 8) & SPH_C64(0x000000FF00000000)) \ - | ((SPH_C64(x) << 24) & SPH_C64(0x0000FF0000000000)) \ - | ((SPH_C64(x) << 40) & SPH_C64(0x00FF000000000000)) \ - | ((SPH_C64(x) << 56) & SPH_C64(0xFF00000000000000))) -#define dec64e_aligned sph_dec64le_aligned -#define enc64e sph_enc64le -#define B64_0(x) ((x) & 0xFF) -#define B64_1(x) (((x) >> 8) & 0xFF) -#define B64_2(x) (((x) >> 16) & 0xFF) -#define B64_3(x) (((x) >> 24) & 0xFF) -#define B64_4(x) (((x) >> 32) & 0xFF) -#define B64_5(x) (((x) >> 40) & 0xFF) -#define B64_6(x) (((x) >> 48) & 0xFF) -#define B64_7(x) ((x) >> 56) -#define R64 SPH_ROTL64 -#define PC64(j, r) ((sph_u64)((j) + (r))) -#define QC64(j, r) (((sph_u64)(r) << 56) ^ SPH_T64(~((sph_u64)(j) << 56))) - - -const sph_u32 T0up[] = { - C32e(0xc632f4a5), C32e(0xf86f9784), C32e(0xee5eb099), C32e(0xf67a8c8d), - C32e(0xffe8170d), C32e(0xd60adcbd), C32e(0xde16c8b1), C32e(0x916dfc54), - C32e(0x6090f050), C32e(0x02070503), C32e(0xce2ee0a9), C32e(0x56d1877d), - C32e(0xe7cc2b19), C32e(0xb513a662), C32e(0x4d7c31e6), C32e(0xec59b59a), - C32e(0x8f40cf45), C32e(0x1fa3bc9d), C32e(0x8949c040), C32e(0xfa689287), - C32e(0xefd03f15), C32e(0xb29426eb), C32e(0x8ece40c9), C32e(0xfbe61d0b), - C32e(0x416e2fec), C32e(0xb31aa967), C32e(0x5f431cfd), C32e(0x456025ea), - C32e(0x23f9dabf), C32e(0x535102f7), C32e(0xe445a196), C32e(0x9b76ed5b), - C32e(0x75285dc2), C32e(0xe1c5241c), C32e(0x3dd4e9ae), C32e(0x4cf2be6a), - C32e(0x6c82ee5a), C32e(0x7ebdc341), C32e(0xf5f30602), C32e(0x8352d14f), - C32e(0x688ce45c), C32e(0x515607f4), C32e(0xd18d5c34), C32e(0xf9e11808), - C32e(0xe24cae93), C32e(0xab3e9573), C32e(0x6297f553), C32e(0x2a6b413f), - C32e(0x081c140c), C32e(0x9563f652), C32e(0x46e9af65), C32e(0x9d7fe25e), - C32e(0x30487828), C32e(0x37cff8a1), C32e(0x0a1b110f), C32e(0x2febc4b5), - C32e(0x0e151b09), C32e(0x247e5a36), C32e(0x1badb69b), C32e(0xdf98473d), - C32e(0xcda76a26), C32e(0x4ef5bb69), C32e(0x7f334ccd), C32e(0xea50ba9f), - C32e(0x123f2d1b), C32e(0x1da4b99e), C32e(0x58c49c74), C32e(0x3446722e), - C32e(0x3641772d), C32e(0xdc11cdb2), C32e(0xb49d29ee), C32e(0x5b4d16fb), - C32e(0xa4a501f6), C32e(0x76a1d74d), C32e(0xb714a361), C32e(0x7d3449ce), - C32e(0x52df8d7b), C32e(0xdd9f423e), C32e(0x5ecd9371), C32e(0x13b1a297), - C32e(0xa6a204f5), C32e(0xb901b868), C32e(0x00000000), C32e(0xc1b5742c), - C32e(0x40e0a060), C32e(0xe3c2211f), C32e(0x793a43c8), C32e(0xb69a2ced), - C32e(0xd40dd9be), C32e(0x8d47ca46), C32e(0x671770d9), C32e(0x72afdd4b), - C32e(0x94ed79de), C32e(0x98ff67d4), C32e(0xb09323e8), C32e(0x855bde4a), - C32e(0xbb06bd6b), C32e(0xc5bb7e2a), C32e(0x4f7b34e5), C32e(0xedd73a16), - C32e(0x86d254c5), C32e(0x9af862d7), C32e(0x6699ff55), C32e(0x11b6a794), - C32e(0x8ac04acf), C32e(0xe9d93010), C32e(0x040e0a06), C32e(0xfe669881), - C32e(0xa0ab0bf0), C32e(0x78b4cc44), C32e(0x25f0d5ba), C32e(0x4b753ee3), - C32e(0xa2ac0ef3), C32e(0x5d4419fe), C32e(0x80db5bc0), C32e(0x0580858a), - C32e(0x3fd3ecad), C32e(0x21fedfbc), C32e(0x70a8d848), C32e(0xf1fd0c04), - C32e(0x63197adf), C32e(0x772f58c1), C32e(0xaf309f75), C32e(0x42e7a563), - C32e(0x20705030), C32e(0xe5cb2e1a), C32e(0xfdef120e), C32e(0xbf08b76d), - C32e(0x8155d44c), C32e(0x18243c14), C32e(0x26795f35), C32e(0xc3b2712f), - C32e(0xbe8638e1), C32e(0x35c8fda2), C32e(0x88c74fcc), C32e(0x2e654b39), - C32e(0x936af957), C32e(0x55580df2), C32e(0xfc619d82), C32e(0x7ab3c947), - C32e(0xc827efac), C32e(0xba8832e7), C32e(0x324f7d2b), C32e(0xe642a495), - C32e(0xc03bfba0), C32e(0x19aab398), C32e(0x9ef668d1), C32e(0xa322817f), - C32e(0x44eeaa66), C32e(0x54d6827e), C32e(0x3bdde6ab), C32e(0x0b959e83), - C32e(0x8cc945ca), C32e(0xc7bc7b29), C32e(0x6b056ed3), C32e(0x286c443c), - C32e(0xa72c8b79), C32e(0xbc813de2), C32e(0x1631271d), C32e(0xad379a76), - C32e(0xdb964d3b), C32e(0x649efa56), C32e(0x74a6d24e), C32e(0x1436221e), - C32e(0x92e476db), C32e(0x0c121e0a), C32e(0x48fcb46c), C32e(0xb88f37e4), - C32e(0x9f78e75d), C32e(0xbd0fb26e), C32e(0x43692aef), C32e(0xc435f1a6), - C32e(0x39dae3a8), C32e(0x31c6f7a4), C32e(0xd38a5937), C32e(0xf274868b), - C32e(0xd5835632), C32e(0x8b4ec543), C32e(0x6e85eb59), C32e(0xda18c2b7), - C32e(0x018e8f8c), C32e(0xb11dac64), C32e(0x9cf16dd2), C32e(0x49723be0), - C32e(0xd81fc7b4), C32e(0xacb915fa), C32e(0xf3fa0907), C32e(0xcfa06f25), - C32e(0xca20eaaf), C32e(0xf47d898e), C32e(0x476720e9), C32e(0x10382818), - C32e(0x6f0b64d5), C32e(0xf0738388), C32e(0x4afbb16f), C32e(0x5cca9672), - C32e(0x38546c24), C32e(0x575f08f1), C32e(0x732152c7), C32e(0x9764f351), - C32e(0xcbae6523), C32e(0xa125847c), C32e(0xe857bf9c), C32e(0x3e5d6321), - C32e(0x96ea7cdd), C32e(0x611e7fdc), C32e(0x0d9c9186), C32e(0x0f9b9485), - C32e(0xe04bab90), C32e(0x7cbac642), C32e(0x712657c4), C32e(0xcc29e5aa), - C32e(0x90e373d8), C32e(0x06090f05), C32e(0xf7f40301), C32e(0x1c2a3612), - C32e(0xc23cfea3), C32e(0x6a8be15f), C32e(0xaebe10f9), C32e(0x69026bd0), - C32e(0x17bfa891), C32e(0x9971e858), C32e(0x3a536927), C32e(0x27f7d0b9), - C32e(0xd9914838), C32e(0xebde3513), C32e(0x2be5ceb3), C32e(0x22775533), - C32e(0xd204d6bb), C32e(0xa9399070), C32e(0x07878089), C32e(0x33c1f2a7), - C32e(0x2decc1b6), C32e(0x3c5a6622), C32e(0x15b8ad92), C32e(0xc9a96020), - C32e(0x875cdb49), C32e(0xaab01aff), C32e(0x50d88878), C32e(0xa52b8e7a), - C32e(0x03898a8f), C32e(0x594a13f8), C32e(0x09929b80), C32e(0x1a233917), - C32e(0x651075da), C32e(0xd7845331), C32e(0x84d551c6), C32e(0xd003d3b8), - C32e(0x82dc5ec3), C32e(0x29e2cbb0), C32e(0x5ac39977), C32e(0x1e2d3311), - C32e(0x7b3d46cb), C32e(0xa8b71ffc), C32e(0x6d0c61d6), C32e(0x2c624e3a) -}; - -const sph_u32 T0dn[] = { - C32e(0xf497a5c6), C32e(0x97eb84f8), C32e(0xb0c799ee), C32e(0x8cf78df6), - C32e(0x17e50dff), C32e(0xdcb7bdd6), C32e(0xc8a7b1de), C32e(0xfc395491), - C32e(0xf0c05060), C32e(0x05040302), C32e(0xe087a9ce), C32e(0x87ac7d56), - C32e(0x2bd519e7), C32e(0xa67162b5), C32e(0x319ae64d), C32e(0xb5c39aec), - C32e(0xcf05458f), C32e(0xbc3e9d1f), C32e(0xc0094089), C32e(0x92ef87fa), - C32e(0x3fc515ef), C32e(0x267febb2), C32e(0x4007c98e), C32e(0x1ded0bfb), - C32e(0x2f82ec41), C32e(0xa97d67b3), C32e(0x1cbefd5f), C32e(0x258aea45), - C32e(0xda46bf23), C32e(0x02a6f753), C32e(0xa1d396e4), C32e(0xed2d5b9b), - C32e(0x5deac275), C32e(0x24d91ce1), C32e(0xe97aae3d), C32e(0xbe986a4c), - C32e(0xeed85a6c), C32e(0xc3fc417e), C32e(0x06f102f5), C32e(0xd11d4f83), - C32e(0xe4d05c68), C32e(0x07a2f451), C32e(0x5cb934d1), C32e(0x18e908f9), - C32e(0xaedf93e2), C32e(0x954d73ab), C32e(0xf5c45362), C32e(0x41543f2a), - C32e(0x14100c08), C32e(0xf6315295), C32e(0xaf8c6546), C32e(0xe2215e9d), - C32e(0x78602830), C32e(0xf86ea137), C32e(0x11140f0a), C32e(0xc45eb52f), - C32e(0x1b1c090e), C32e(0x5a483624), C32e(0xb6369b1b), C32e(0x47a53ddf), - C32e(0x6a8126cd), C32e(0xbb9c694e), C32e(0x4cfecd7f), C32e(0xbacf9fea), - C32e(0x2d241b12), C32e(0xb93a9e1d), C32e(0x9cb07458), C32e(0x72682e34), - C32e(0x776c2d36), C32e(0xcda3b2dc), C32e(0x2973eeb4), C32e(0x16b6fb5b), - C32e(0x0153f6a4), C32e(0xd7ec4d76), C32e(0xa37561b7), C32e(0x49face7d), - C32e(0x8da47b52), C32e(0x42a13edd), C32e(0x93bc715e), C32e(0xa2269713), - C32e(0x0457f5a6), C32e(0xb86968b9), C32e(0x00000000), C32e(0x74992cc1), - C32e(0xa0806040), C32e(0x21dd1fe3), C32e(0x43f2c879), C32e(0x2c77edb6), - C32e(0xd9b3bed4), C32e(0xca01468d), C32e(0x70ced967), C32e(0xdde44b72), - C32e(0x7933de94), C32e(0x672bd498), C32e(0x237be8b0), C32e(0xde114a85), - C32e(0xbd6d6bbb), C32e(0x7e912ac5), C32e(0x349ee54f), C32e(0x3ac116ed), - C32e(0x5417c586), C32e(0x622fd79a), C32e(0xffcc5566), C32e(0xa7229411), - C32e(0x4a0fcf8a), C32e(0x30c910e9), C32e(0x0a080604), C32e(0x98e781fe), - C32e(0x0b5bf0a0), C32e(0xccf04478), C32e(0xd54aba25), C32e(0x3e96e34b), - C32e(0x0e5ff3a2), C32e(0x19bafe5d), C32e(0x5b1bc080), C32e(0x850a8a05), - C32e(0xec7ead3f), C32e(0xdf42bc21), C32e(0xd8e04870), C32e(0x0cf904f1), - C32e(0x7ac6df63), C32e(0x58eec177), C32e(0x9f4575af), C32e(0xa5846342), - C32e(0x50403020), C32e(0x2ed11ae5), C32e(0x12e10efd), C32e(0xb7656dbf), - C32e(0xd4194c81), C32e(0x3c301418), C32e(0x5f4c3526), C32e(0x719d2fc3), - C32e(0x3867e1be), C32e(0xfd6aa235), C32e(0x4f0bcc88), C32e(0x4b5c392e), - C32e(0xf93d5793), C32e(0x0daaf255), C32e(0x9de382fc), C32e(0xc9f4477a), - C32e(0xef8bacc8), C32e(0x326fe7ba), C32e(0x7d642b32), C32e(0xa4d795e6), - C32e(0xfb9ba0c0), C32e(0xb3329819), C32e(0x6827d19e), C32e(0x815d7fa3), - C32e(0xaa886644), C32e(0x82a87e54), C32e(0xe676ab3b), C32e(0x9e16830b), - C32e(0x4503ca8c), C32e(0x7b9529c7), C32e(0x6ed6d36b), C32e(0x44503c28), - C32e(0x8b5579a7), C32e(0x3d63e2bc), C32e(0x272c1d16), C32e(0x9a4176ad), - C32e(0x4dad3bdb), C32e(0xfac85664), C32e(0xd2e84e74), C32e(0x22281e14), - C32e(0x763fdb92), C32e(0x1e180a0c), C32e(0xb4906c48), C32e(0x376be4b8), - C32e(0xe7255d9f), C32e(0xb2616ebd), C32e(0x2a86ef43), C32e(0xf193a6c4), - C32e(0xe372a839), C32e(0xf762a431), C32e(0x59bd37d3), C32e(0x86ff8bf2), - C32e(0x56b132d5), C32e(0xc50d438b), C32e(0xebdc596e), C32e(0xc2afb7da), - C32e(0x8f028c01), C32e(0xac7964b1), C32e(0x6d23d29c), C32e(0x3b92e049), - C32e(0xc7abb4d8), C32e(0x1543faac), C32e(0x09fd07f3), C32e(0x6f8525cf), - C32e(0xea8fafca), C32e(0x89f38ef4), C32e(0x208ee947), C32e(0x28201810), - C32e(0x64ded56f), C32e(0x83fb88f0), C32e(0xb1946f4a), C32e(0x96b8725c), - C32e(0x6c702438), C32e(0x08aef157), C32e(0x52e6c773), C32e(0xf3355197), - C32e(0x658d23cb), C32e(0x84597ca1), C32e(0xbfcb9ce8), C32e(0x637c213e), - C32e(0x7c37dd96), C32e(0x7fc2dc61), C32e(0x911a860d), C32e(0x941e850f), - C32e(0xabdb90e0), C32e(0xc6f8427c), C32e(0x57e2c471), C32e(0xe583aacc), - C32e(0x733bd890), C32e(0x0f0c0506), C32e(0x03f501f7), C32e(0x3638121c), - C32e(0xfe9fa3c2), C32e(0xe1d45f6a), C32e(0x1047f9ae), C32e(0x6bd2d069), - C32e(0xa82e9117), C32e(0xe8295899), C32e(0x6974273a), C32e(0xd04eb927), - C32e(0x48a938d9), C32e(0x35cd13eb), C32e(0xce56b32b), C32e(0x55443322), - C32e(0xd6bfbbd2), C32e(0x904970a9), C32e(0x800e8907), C32e(0xf266a733), - C32e(0xc15ab62d), C32e(0x6678223c), C32e(0xad2a9215), C32e(0x608920c9), - C32e(0xdb154987), C32e(0x1a4fffaa), C32e(0x88a07850), C32e(0x8e517aa5), - C32e(0x8a068f03), C32e(0x13b2f859), C32e(0x9b128009), C32e(0x3934171a), - C32e(0x75cada65), C32e(0x53b531d7), C32e(0x5113c684), C32e(0xd3bbb8d0), - C32e(0x5e1fc382), C32e(0xcb52b029), C32e(0x99b4775a), C32e(0x333c111e), - C32e(0x46f6cb7b), C32e(0x1f4bfca8), C32e(0x61dad66d), C32e(0x4e583a2c) -}; - -const sph_u32 T1up[] = { - C32e(0xc6c632f4), C32e(0xf8f86f97), C32e(0xeeee5eb0), C32e(0xf6f67a8c), - C32e(0xffffe817), C32e(0xd6d60adc), C32e(0xdede16c8), C32e(0x91916dfc), - C32e(0x606090f0), C32e(0x02020705), C32e(0xcece2ee0), C32e(0x5656d187), - C32e(0xe7e7cc2b), C32e(0xb5b513a6), C32e(0x4d4d7c31), C32e(0xecec59b5), - C32e(0x8f8f40cf), C32e(0x1f1fa3bc), C32e(0x898949c0), C32e(0xfafa6892), - C32e(0xefefd03f), C32e(0xb2b29426), C32e(0x8e8ece40), C32e(0xfbfbe61d), - C32e(0x41416e2f), C32e(0xb3b31aa9), C32e(0x5f5f431c), C32e(0x45456025), - C32e(0x2323f9da), C32e(0x53535102), C32e(0xe4e445a1), C32e(0x9b9b76ed), - C32e(0x7575285d), C32e(0xe1e1c524), C32e(0x3d3dd4e9), C32e(0x4c4cf2be), - C32e(0x6c6c82ee), C32e(0x7e7ebdc3), C32e(0xf5f5f306), C32e(0x838352d1), - C32e(0x68688ce4), C32e(0x51515607), C32e(0xd1d18d5c), C32e(0xf9f9e118), - C32e(0xe2e24cae), C32e(0xabab3e95), C32e(0x626297f5), C32e(0x2a2a6b41), - C32e(0x08081c14), C32e(0x959563f6), C32e(0x4646e9af), C32e(0x9d9d7fe2), - C32e(0x30304878), C32e(0x3737cff8), C32e(0x0a0a1b11), C32e(0x2f2febc4), - C32e(0x0e0e151b), C32e(0x24247e5a), C32e(0x1b1badb6), C32e(0xdfdf9847), - C32e(0xcdcda76a), C32e(0x4e4ef5bb), C32e(0x7f7f334c), C32e(0xeaea50ba), - C32e(0x12123f2d), C32e(0x1d1da4b9), C32e(0x5858c49c), C32e(0x34344672), - C32e(0x36364177), C32e(0xdcdc11cd), C32e(0xb4b49d29), C32e(0x5b5b4d16), - C32e(0xa4a4a501), C32e(0x7676a1d7), C32e(0xb7b714a3), C32e(0x7d7d3449), - C32e(0x5252df8d), C32e(0xdddd9f42), C32e(0x5e5ecd93), C32e(0x1313b1a2), - C32e(0xa6a6a204), C32e(0xb9b901b8), C32e(0x00000000), C32e(0xc1c1b574), - C32e(0x4040e0a0), C32e(0xe3e3c221), C32e(0x79793a43), C32e(0xb6b69a2c), - C32e(0xd4d40dd9), C32e(0x8d8d47ca), C32e(0x67671770), C32e(0x7272afdd), - C32e(0x9494ed79), C32e(0x9898ff67), C32e(0xb0b09323), C32e(0x85855bde), - C32e(0xbbbb06bd), C32e(0xc5c5bb7e), C32e(0x4f4f7b34), C32e(0xededd73a), - C32e(0x8686d254), C32e(0x9a9af862), C32e(0x666699ff), C32e(0x1111b6a7), - C32e(0x8a8ac04a), C32e(0xe9e9d930), C32e(0x04040e0a), C32e(0xfefe6698), - C32e(0xa0a0ab0b), C32e(0x7878b4cc), C32e(0x2525f0d5), C32e(0x4b4b753e), - C32e(0xa2a2ac0e), C32e(0x5d5d4419), C32e(0x8080db5b), C32e(0x05058085), - C32e(0x3f3fd3ec), C32e(0x2121fedf), C32e(0x7070a8d8), C32e(0xf1f1fd0c), - C32e(0x6363197a), C32e(0x77772f58), C32e(0xafaf309f), C32e(0x4242e7a5), - C32e(0x20207050), C32e(0xe5e5cb2e), C32e(0xfdfdef12), C32e(0xbfbf08b7), - C32e(0x818155d4), C32e(0x1818243c), C32e(0x2626795f), C32e(0xc3c3b271), - C32e(0xbebe8638), C32e(0x3535c8fd), C32e(0x8888c74f), C32e(0x2e2e654b), - C32e(0x93936af9), C32e(0x5555580d), C32e(0xfcfc619d), C32e(0x7a7ab3c9), - C32e(0xc8c827ef), C32e(0xbaba8832), C32e(0x32324f7d), C32e(0xe6e642a4), - C32e(0xc0c03bfb), C32e(0x1919aab3), C32e(0x9e9ef668), C32e(0xa3a32281), - C32e(0x4444eeaa), C32e(0x5454d682), C32e(0x3b3bdde6), C32e(0x0b0b959e), - C32e(0x8c8cc945), C32e(0xc7c7bc7b), C32e(0x6b6b056e), C32e(0x28286c44), - C32e(0xa7a72c8b), C32e(0xbcbc813d), C32e(0x16163127), C32e(0xadad379a), - C32e(0xdbdb964d), C32e(0x64649efa), C32e(0x7474a6d2), C32e(0x14143622), - C32e(0x9292e476), C32e(0x0c0c121e), C32e(0x4848fcb4), C32e(0xb8b88f37), - C32e(0x9f9f78e7), C32e(0xbdbd0fb2), C32e(0x4343692a), C32e(0xc4c435f1), - C32e(0x3939dae3), C32e(0x3131c6f7), C32e(0xd3d38a59), C32e(0xf2f27486), - C32e(0xd5d58356), C32e(0x8b8b4ec5), C32e(0x6e6e85eb), C32e(0xdada18c2), - C32e(0x01018e8f), C32e(0xb1b11dac), C32e(0x9c9cf16d), C32e(0x4949723b), - C32e(0xd8d81fc7), C32e(0xacacb915), C32e(0xf3f3fa09), C32e(0xcfcfa06f), - C32e(0xcaca20ea), C32e(0xf4f47d89), C32e(0x47476720), C32e(0x10103828), - C32e(0x6f6f0b64), C32e(0xf0f07383), C32e(0x4a4afbb1), C32e(0x5c5cca96), - C32e(0x3838546c), C32e(0x57575f08), C32e(0x73732152), C32e(0x979764f3), - C32e(0xcbcbae65), C32e(0xa1a12584), C32e(0xe8e857bf), C32e(0x3e3e5d63), - C32e(0x9696ea7c), C32e(0x61611e7f), C32e(0x0d0d9c91), C32e(0x0f0f9b94), - C32e(0xe0e04bab), C32e(0x7c7cbac6), C32e(0x71712657), C32e(0xcccc29e5), - C32e(0x9090e373), C32e(0x0606090f), C32e(0xf7f7f403), C32e(0x1c1c2a36), - C32e(0xc2c23cfe), C32e(0x6a6a8be1), C32e(0xaeaebe10), C32e(0x6969026b), - C32e(0x1717bfa8), C32e(0x999971e8), C32e(0x3a3a5369), C32e(0x2727f7d0), - C32e(0xd9d99148), C32e(0xebebde35), C32e(0x2b2be5ce), C32e(0x22227755), - C32e(0xd2d204d6), C32e(0xa9a93990), C32e(0x07078780), C32e(0x3333c1f2), - C32e(0x2d2decc1), C32e(0x3c3c5a66), C32e(0x1515b8ad), C32e(0xc9c9a960), - C32e(0x87875cdb), C32e(0xaaaab01a), C32e(0x5050d888), C32e(0xa5a52b8e), - C32e(0x0303898a), C32e(0x59594a13), C32e(0x0909929b), C32e(0x1a1a2339), - C32e(0x65651075), C32e(0xd7d78453), C32e(0x8484d551), C32e(0xd0d003d3), - C32e(0x8282dc5e), C32e(0x2929e2cb), C32e(0x5a5ac399), C32e(0x1e1e2d33), - C32e(0x7b7b3d46), C32e(0xa8a8b71f), C32e(0x6d6d0c61), C32e(0x2c2c624e) -}; - -const sph_u32 T1dn[] = { - C32e(0xa5f497a5), C32e(0x8497eb84), C32e(0x99b0c799), C32e(0x8d8cf78d), - C32e(0x0d17e50d), C32e(0xbddcb7bd), C32e(0xb1c8a7b1), C32e(0x54fc3954), - C32e(0x50f0c050), C32e(0x03050403), C32e(0xa9e087a9), C32e(0x7d87ac7d), - C32e(0x192bd519), C32e(0x62a67162), C32e(0xe6319ae6), C32e(0x9ab5c39a), - C32e(0x45cf0545), C32e(0x9dbc3e9d), C32e(0x40c00940), C32e(0x8792ef87), - C32e(0x153fc515), C32e(0xeb267feb), C32e(0xc94007c9), C32e(0x0b1ded0b), - C32e(0xec2f82ec), C32e(0x67a97d67), C32e(0xfd1cbefd), C32e(0xea258aea), - C32e(0xbfda46bf), C32e(0xf702a6f7), C32e(0x96a1d396), C32e(0x5bed2d5b), - C32e(0xc25deac2), C32e(0x1c24d91c), C32e(0xaee97aae), C32e(0x6abe986a), - C32e(0x5aeed85a), C32e(0x41c3fc41), C32e(0x0206f102), C32e(0x4fd11d4f), - C32e(0x5ce4d05c), C32e(0xf407a2f4), C32e(0x345cb934), C32e(0x0818e908), - C32e(0x93aedf93), C32e(0x73954d73), C32e(0x53f5c453), C32e(0x3f41543f), - C32e(0x0c14100c), C32e(0x52f63152), C32e(0x65af8c65), C32e(0x5ee2215e), - C32e(0x28786028), C32e(0xa1f86ea1), C32e(0x0f11140f), C32e(0xb5c45eb5), - C32e(0x091b1c09), C32e(0x365a4836), C32e(0x9bb6369b), C32e(0x3d47a53d), - C32e(0x266a8126), C32e(0x69bb9c69), C32e(0xcd4cfecd), C32e(0x9fbacf9f), - C32e(0x1b2d241b), C32e(0x9eb93a9e), C32e(0x749cb074), C32e(0x2e72682e), - C32e(0x2d776c2d), C32e(0xb2cda3b2), C32e(0xee2973ee), C32e(0xfb16b6fb), - C32e(0xf60153f6), C32e(0x4dd7ec4d), C32e(0x61a37561), C32e(0xce49face), - C32e(0x7b8da47b), C32e(0x3e42a13e), C32e(0x7193bc71), C32e(0x97a22697), - C32e(0xf50457f5), C32e(0x68b86968), C32e(0x00000000), C32e(0x2c74992c), - C32e(0x60a08060), C32e(0x1f21dd1f), C32e(0xc843f2c8), C32e(0xed2c77ed), - C32e(0xbed9b3be), C32e(0x46ca0146), C32e(0xd970ced9), C32e(0x4bdde44b), - C32e(0xde7933de), C32e(0xd4672bd4), C32e(0xe8237be8), C32e(0x4ade114a), - C32e(0x6bbd6d6b), C32e(0x2a7e912a), C32e(0xe5349ee5), C32e(0x163ac116), - C32e(0xc55417c5), C32e(0xd7622fd7), C32e(0x55ffcc55), C32e(0x94a72294), - C32e(0xcf4a0fcf), C32e(0x1030c910), C32e(0x060a0806), C32e(0x8198e781), - C32e(0xf00b5bf0), C32e(0x44ccf044), C32e(0xbad54aba), C32e(0xe33e96e3), - C32e(0xf30e5ff3), C32e(0xfe19bafe), C32e(0xc05b1bc0), C32e(0x8a850a8a), - C32e(0xadec7ead), C32e(0xbcdf42bc), C32e(0x48d8e048), C32e(0x040cf904), - C32e(0xdf7ac6df), C32e(0xc158eec1), C32e(0x759f4575), C32e(0x63a58463), - C32e(0x30504030), C32e(0x1a2ed11a), C32e(0x0e12e10e), C32e(0x6db7656d), - C32e(0x4cd4194c), C32e(0x143c3014), C32e(0x355f4c35), C32e(0x2f719d2f), - C32e(0xe13867e1), C32e(0xa2fd6aa2), C32e(0xcc4f0bcc), C32e(0x394b5c39), - C32e(0x57f93d57), C32e(0xf20daaf2), C32e(0x829de382), C32e(0x47c9f447), - C32e(0xacef8bac), C32e(0xe7326fe7), C32e(0x2b7d642b), C32e(0x95a4d795), - C32e(0xa0fb9ba0), C32e(0x98b33298), C32e(0xd16827d1), C32e(0x7f815d7f), - C32e(0x66aa8866), C32e(0x7e82a87e), C32e(0xabe676ab), C32e(0x839e1683), - C32e(0xca4503ca), C32e(0x297b9529), C32e(0xd36ed6d3), C32e(0x3c44503c), - C32e(0x798b5579), C32e(0xe23d63e2), C32e(0x1d272c1d), C32e(0x769a4176), - C32e(0x3b4dad3b), C32e(0x56fac856), C32e(0x4ed2e84e), C32e(0x1e22281e), - C32e(0xdb763fdb), C32e(0x0a1e180a), C32e(0x6cb4906c), C32e(0xe4376be4), - C32e(0x5de7255d), C32e(0x6eb2616e), C32e(0xef2a86ef), C32e(0xa6f193a6), - C32e(0xa8e372a8), C32e(0xa4f762a4), C32e(0x3759bd37), C32e(0x8b86ff8b), - C32e(0x3256b132), C32e(0x43c50d43), C32e(0x59ebdc59), C32e(0xb7c2afb7), - C32e(0x8c8f028c), C32e(0x64ac7964), C32e(0xd26d23d2), C32e(0xe03b92e0), - C32e(0xb4c7abb4), C32e(0xfa1543fa), C32e(0x0709fd07), C32e(0x256f8525), - C32e(0xafea8faf), C32e(0x8e89f38e), C32e(0xe9208ee9), C32e(0x18282018), - C32e(0xd564ded5), C32e(0x8883fb88), C32e(0x6fb1946f), C32e(0x7296b872), - C32e(0x246c7024), C32e(0xf108aef1), C32e(0xc752e6c7), C32e(0x51f33551), - C32e(0x23658d23), C32e(0x7c84597c), C32e(0x9cbfcb9c), C32e(0x21637c21), - C32e(0xdd7c37dd), C32e(0xdc7fc2dc), C32e(0x86911a86), C32e(0x85941e85), - C32e(0x90abdb90), C32e(0x42c6f842), C32e(0xc457e2c4), C32e(0xaae583aa), - C32e(0xd8733bd8), C32e(0x050f0c05), C32e(0x0103f501), C32e(0x12363812), - C32e(0xa3fe9fa3), C32e(0x5fe1d45f), C32e(0xf91047f9), C32e(0xd06bd2d0), - C32e(0x91a82e91), C32e(0x58e82958), C32e(0x27697427), C32e(0xb9d04eb9), - C32e(0x3848a938), C32e(0x1335cd13), C32e(0xb3ce56b3), C32e(0x33554433), - C32e(0xbbd6bfbb), C32e(0x70904970), C32e(0x89800e89), C32e(0xa7f266a7), - C32e(0xb6c15ab6), C32e(0x22667822), C32e(0x92ad2a92), C32e(0x20608920), - C32e(0x49db1549), C32e(0xff1a4fff), C32e(0x7888a078), C32e(0x7a8e517a), - C32e(0x8f8a068f), C32e(0xf813b2f8), C32e(0x809b1280), C32e(0x17393417), - C32e(0xda75cada), C32e(0x3153b531), C32e(0xc65113c6), C32e(0xb8d3bbb8), - C32e(0xc35e1fc3), C32e(0xb0cb52b0), C32e(0x7799b477), C32e(0x11333c11), - C32e(0xcb46f6cb), C32e(0xfc1f4bfc), C32e(0xd661dad6), C32e(0x3a4e583a) -}; - -#define DECL_STATE_SMALL \ - sph_u32 H[16] = {0}; - -#define READ_STATE_SMALL(sc) do { \ - memcpy(H, (sc)->state.narrow, sizeof H); \ - } while (0) - -#define WRITE_STATE_SMALL(sc) do { \ - memcpy((sc)->state.narrow, H, sizeof H); \ - } while (0) - -#define XCAT(x, y) XCAT_(x, y) -#define XCAT_(x, y) x ## y - -#define RSTT(d0, d1, a, b0, b1, b2, b3, b4, b5, b6, b7) do { \ - t[d0] = T0up[B32_0(a[b0])] \ - ^ T1up[B32_1(a[b1])] \ - ^ T2up[B32_2(a[b2])] \ - ^ T3up[B32_3(a[b3])] \ - ^ T0dn[B32_0(a[b4])] \ - ^ T1dn[B32_1(a[b5])] \ - ^ T2dn[B32_2(a[b6])] \ - ^ T3dn[B32_3(a[b7])]; \ - t[d1] = T0dn[B32_0(a[b0])] \ - ^ T1dn[B32_1(a[b1])] \ - ^ T2dn[B32_2(a[b2])] \ - ^ T3dn[B32_3(a[b3])] \ - ^ T0up[B32_0(a[b4])] \ - ^ T1up[B32_1(a[b5])] \ - ^ T2up[B32_2(a[b6])] \ - ^ T3up[B32_3(a[b7])]; \ - } while (0) - -#define ROUND_SMALL_P(a, r) do { \ - sph_u32 t[16]; \ - a[0x0] ^= PC32up(0x00, r); \ - a[0x1] ^= PC32dn(0x00, r); \ - a[0x2] ^= PC32up(0x10, r); \ - a[0x3] ^= PC32dn(0x10, r); \ - a[0x4] ^= PC32up(0x20, r); \ - a[0x5] ^= PC32dn(0x20, r); \ - a[0x6] ^= PC32up(0x30, r); \ - a[0x7] ^= PC32dn(0x30, r); \ - a[0x8] ^= PC32up(0x40, r); \ - a[0x9] ^= PC32dn(0x40, r); \ - a[0xA] ^= PC32up(0x50, r); \ - a[0xB] ^= PC32dn(0x50, r); \ - a[0xC] ^= PC32up(0x60, r); \ - a[0xD] ^= PC32dn(0x60, r); \ - a[0xE] ^= PC32up(0x70, r); \ - a[0xF] ^= PC32dn(0x70, r); \ - RSTT(0x0, 0x1, a, 0x0, 0x2, 0x4, 0x6, 0x9, 0xB, 0xD, 0xF); \ - RSTT(0x2, 0x3, a, 0x2, 0x4, 0x6, 0x8, 0xB, 0xD, 0xF, 0x1); \ - RSTT(0x4, 0x5, a, 0x4, 0x6, 0x8, 0xA, 0xD, 0xF, 0x1, 0x3); \ - RSTT(0x6, 0x7, a, 0x6, 0x8, 0xA, 0xC, 0xF, 0x1, 0x3, 0x5); \ - RSTT(0x8, 0x9, a, 0x8, 0xA, 0xC, 0xE, 0x1, 0x3, 0x5, 0x7); \ - RSTT(0xA, 0xB, a, 0xA, 0xC, 0xE, 0x0, 0x3, 0x5, 0x7, 0x9); \ - RSTT(0xC, 0xD, a, 0xC, 0xE, 0x0, 0x2, 0x5, 0x7, 0x9, 0xB); \ - RSTT(0xE, 0xF, a, 0xE, 0x0, 0x2, 0x4, 0x7, 0x9, 0xB, 0xD); \ - memcpy(a, t, sizeof t); \ - } while (0) - -#define ROUND_SMALL_Q(a, r) do { \ - sph_u32 t[16]; \ - a[0x0] ^= QC32up(0x00, r); \ - a[0x1] ^= QC32dn(0x00, r); \ - a[0x2] ^= QC32up(0x10, r); \ - a[0x3] ^= QC32dn(0x10, r); \ - a[0x4] ^= QC32up(0x20, r); \ - a[0x5] ^= QC32dn(0x20, r); \ - a[0x6] ^= QC32up(0x30, r); \ - a[0x7] ^= QC32dn(0x30, r); \ - a[0x8] ^= QC32up(0x40, r); \ - a[0x9] ^= QC32dn(0x40, r); \ - a[0xA] ^= QC32up(0x50, r); \ - a[0xB] ^= QC32dn(0x50, r); \ - a[0xC] ^= QC32up(0x60, r); \ - a[0xD] ^= QC32dn(0x60, r); \ - a[0xE] ^= QC32up(0x70, r); \ - a[0xF] ^= QC32dn(0x70, r); \ - RSTT(0x0, 0x1, a, 0x2, 0x6, 0xA, 0xE, 0x1, 0x5, 0x9, 0xD); \ - RSTT(0x2, 0x3, a, 0x4, 0x8, 0xC, 0x0, 0x3, 0x7, 0xB, 0xF); \ - RSTT(0x4, 0x5, a, 0x6, 0xA, 0xE, 0x2, 0x5, 0x9, 0xD, 0x1); \ - RSTT(0x6, 0x7, a, 0x8, 0xC, 0x0, 0x4, 0x7, 0xB, 0xF, 0x3); \ - RSTT(0x8, 0x9, a, 0xA, 0xE, 0x2, 0x6, 0x9, 0xD, 0x1, 0x5); \ - RSTT(0xA, 0xB, a, 0xC, 0x0, 0x4, 0x8, 0xB, 0xF, 0x3, 0x7); \ - RSTT(0xC, 0xD, a, 0xE, 0x2, 0x6, 0xA, 0xD, 0x1, 0x5, 0x9); \ - RSTT(0xE, 0xF, a, 0x0, 0x4, 0x8, 0xC, 0xF, 0x3, 0x7, 0xB); \ - memcpy(a, t, sizeof t); \ - } while (0) - -#define PERM_SMALL_P(a) do { \ - int r; \ - for (r = 0; r < 10; r ++) \ - ROUND_SMALL_P(a, r); \ - } while (0) - -#define PERM_SMALL_Q(a) do { \ - int r; \ - for (r = 0; r < 10; r ++) \ - ROUND_SMALL_Q(a, r); \ - } while (0) - - -#define COMPRESS_SMALL do { \ - sph_u32 g[16], m[16]; \ - size_t u; \ - for (u = 0; u < 16; u ++) { \ - m[u] = dec32e_aligned(buf + (u << 2)); \ - g[u] = m[u] ^ H[u]; \ - } \ - PERM_SMALL_P(g); \ - PERM_SMALL_Q(m); \ - for (u = 0; u < 16; u ++) \ - H[u] ^= g[u] ^ m[u]; \ - } while (0) - -#define FINAL_SMALL do { \ - sph_u32 x[16]; \ - size_t u; \ - memcpy(x, H, sizeof x); \ - PERM_SMALL_P(x); \ - for (u = 0; u < 16; u ++) \ - H[u] ^= x[u]; \ - } while (0) - -#define DECL_STATE_BIG \ - sph_u32 H[32] = {0}; - -#define READ_STATE_BIG(sc) do { \ - memcpy(H, (sc)->state.narrow, sizeof H); \ - } while (0) - -#define WRITE_STATE_BIG(sc) do { \ - memcpy((sc)->state.narrow, H, sizeof H); \ - } while (0) - - -#define RBTT(d0, d1, a, b0, b1, b2, b3, b4, b5, b6, b7) do { \ - sph_u32 fu2 = T0up[B32_2(a[b2])]; \ - sph_u32 fd2 = T0dn[B32_2(a[b2])]; \ - sph_u32 fu3 = T1up[B32_3(a[b3])]; \ - sph_u32 fd3 = T1dn[B32_3(a[b3])]; \ - sph_u32 fu6 = T0up[B32_2(a[b6])]; \ - sph_u32 fd6 = T0dn[B32_2(a[b6])]; \ - sph_u32 fu7 = T1up[B32_3(a[b7])]; \ - sph_u32 fd7 = T1dn[B32_3(a[b7])]; \ - t[d0] = T0up[B32_0(a[b0])] \ - ^ T1up[B32_1(a[b1])] \ - ^ R32u(fu2, fd2) \ - ^ R32u(fu3, fd3) \ - ^ T0dn[B32_0(a[b4])] \ - ^ T1dn[B32_1(a[b5])] \ - ^ R32d(fu6, fd6) \ - ^ R32d(fu7, fd7); \ - t[d1] = T0dn[B32_0(a[b0])] \ - ^ T1dn[B32_1(a[b1])] \ - ^ R32d(fu2, fd2) \ - ^ R32d(fu3, fd3) \ - ^ T0up[B32_0(a[b4])] \ - ^ T1up[B32_1(a[b5])] \ - ^ R32u(fu6, fd6) \ - ^ R32u(fu7, fd7); \ - } while (0) - - -#define ROUND_BIG_P(a, r) do { \ - sph_u32 t[32]; \ - size_t u; \ - a[0x00] ^= PC32up(0x00, r); \ - a[0x01] ^= PC32dn(0x00, r); \ - a[0x02] ^= PC32up(0x10, r); \ - a[0x03] ^= PC32dn(0x10, r); \ - a[0x04] ^= PC32up(0x20, r); \ - a[0x05] ^= PC32dn(0x20, r); \ - a[0x06] ^= PC32up(0x30, r); \ - a[0x07] ^= PC32dn(0x30, r); \ - a[0x08] ^= PC32up(0x40, r); \ - a[0x09] ^= PC32dn(0x40, r); \ - a[0x0A] ^= PC32up(0x50, r); \ - a[0x0B] ^= PC32dn(0x50, r); \ - a[0x0C] ^= PC32up(0x60, r); \ - a[0x0D] ^= PC32dn(0x60, r); \ - a[0x0E] ^= PC32up(0x70, r); \ - a[0x0F] ^= PC32dn(0x70, r); \ - a[0x10] ^= PC32up(0x80, r); \ - a[0x11] ^= PC32dn(0x80, r); \ - a[0x12] ^= PC32up(0x90, r); \ - a[0x13] ^= PC32dn(0x90, r); \ - a[0x14] ^= PC32up(0xA0, r); \ - a[0x15] ^= PC32dn(0xA0, r); \ - a[0x16] ^= PC32up(0xB0, r); \ - a[0x17] ^= PC32dn(0xB0, r); \ - a[0x18] ^= PC32up(0xC0, r); \ - a[0x19] ^= PC32dn(0xC0, r); \ - a[0x1A] ^= PC32up(0xD0, r); \ - a[0x1B] ^= PC32dn(0xD0, r); \ - a[0x1C] ^= PC32up(0xE0, r); \ - a[0x1D] ^= PC32dn(0xE0, r); \ - a[0x1E] ^= PC32up(0xF0, r); \ - a[0x1F] ^= PC32dn(0xF0, r); \ - for (u = 0; u < 32; u += 8) { \ - RBTT(u + 0x00, (u + 0x01) & 0x1F, a, \ - u + 0x00, (u + 0x02) & 0x1F, \ - (u + 0x04) & 0x1F, (u + 0x06) & 0x1F, \ - (u + 0x09) & 0x1F, (u + 0x0B) & 0x1F, \ - (u + 0x0D) & 0x1F, (u + 0x17) & 0x1F); \ - RBTT(u + 0x02, (u + 0x03) & 0x1F, a, \ - u + 0x02, (u + 0x04) & 0x1F, \ - (u + 0x06) & 0x1F, (u + 0x08) & 0x1F, \ - (u + 0x0B) & 0x1F, (u + 0x0D) & 0x1F, \ - (u + 0x0F) & 0x1F, (u + 0x19) & 0x1F); \ - RBTT(u + 0x04, (u + 0x05) & 0x1F, a, \ - u + 0x04, (u + 0x06) & 0x1F, \ - (u + 0x08) & 0x1F, (u + 0x0A) & 0x1F, \ - (u + 0x0D) & 0x1F, (u + 0x0F) & 0x1F, \ - (u + 0x11) & 0x1F, (u + 0x1B) & 0x1F); \ - RBTT(u + 0x06, (u + 0x07) & 0x1F, a, \ - u + 0x06, (u + 0x08) & 0x1F, \ - (u + 0x0A) & 0x1F, (u + 0x0C) & 0x1F, \ - (u + 0x0F) & 0x1F, (u + 0x11) & 0x1F, \ - (u + 0x13) & 0x1F, (u + 0x1D) & 0x1F); \ - } \ - memcpy(a, t, sizeof t); \ - } while (0) - -#define ROUND_BIG_Q(a, r) do { \ - sph_u32 t[32]; \ - size_t u; \ - a[0x00] ^= QC32up(0x00, r); \ - a[0x01] ^= QC32dn(0x00, r); \ - a[0x02] ^= QC32up(0x10, r); \ - a[0x03] ^= QC32dn(0x10, r); \ - a[0x04] ^= QC32up(0x20, r); \ - a[0x05] ^= QC32dn(0x20, r); \ - a[0x06] ^= QC32up(0x30, r); \ - a[0x07] ^= QC32dn(0x30, r); \ - a[0x08] ^= QC32up(0x40, r); \ - a[0x09] ^= QC32dn(0x40, r); \ - a[0x0A] ^= QC32up(0x50, r); \ - a[0x0B] ^= QC32dn(0x50, r); \ - a[0x0C] ^= QC32up(0x60, r); \ - a[0x0D] ^= QC32dn(0x60, r); \ - a[0x0E] ^= QC32up(0x70, r); \ - a[0x0F] ^= QC32dn(0x70, r); \ - a[0x10] ^= QC32up(0x80, r); \ - a[0x11] ^= QC32dn(0x80, r); \ - a[0x12] ^= QC32up(0x90, r); \ - a[0x13] ^= QC32dn(0x90, r); \ - a[0x14] ^= QC32up(0xA0, r); \ - a[0x15] ^= QC32dn(0xA0, r); \ - a[0x16] ^= QC32up(0xB0, r); \ - a[0x17] ^= QC32dn(0xB0, r); \ - a[0x18] ^= QC32up(0xC0, r); \ - a[0x19] ^= QC32dn(0xC0, r); \ - a[0x1A] ^= QC32up(0xD0, r); \ - a[0x1B] ^= QC32dn(0xD0, r); \ - a[0x1C] ^= QC32up(0xE0, r); \ - a[0x1D] ^= QC32dn(0xE0, r); \ - a[0x1E] ^= QC32up(0xF0, r); \ - a[0x1F] ^= QC32dn(0xF0, r); \ - for (u = 0; u < 32; u += 8) { \ - RBTT(u + 0x00, (u + 0x01) & 0x1F, a, \ - (u + 0x02) & 0x1F, (u + 0x06) & 0x1F, \ - (u + 0x0A) & 0x1F, (u + 0x16) & 0x1F, \ - (u + 0x01) & 0x1F, (u + 0x05) & 0x1F, \ - (u + 0x09) & 0x1F, (u + 0x0D) & 0x1F); \ - RBTT(u + 0x02, (u + 0x03) & 0x1F, a, \ - (u + 0x04) & 0x1F, (u + 0x08) & 0x1F, \ - (u + 0x0C) & 0x1F, (u + 0x18) & 0x1F, \ - (u + 0x03) & 0x1F, (u + 0x07) & 0x1F, \ - (u + 0x0B) & 0x1F, (u + 0x0F) & 0x1F); \ - RBTT(u + 0x04, (u + 0x05) & 0x1F, a, \ - (u + 0x06) & 0x1F, (u + 0x0A) & 0x1F, \ - (u + 0x0E) & 0x1F, (u + 0x1A) & 0x1F, \ - (u + 0x05) & 0x1F, (u + 0x09) & 0x1F, \ - (u + 0x0D) & 0x1F, (u + 0x11) & 0x1F); \ - RBTT(u + 0x06, (u + 0x07) & 0x1F, a, \ - (u + 0x08) & 0x1F, (u + 0x0C) & 0x1F, \ - (u + 0x10) & 0x1F, (u + 0x1C) & 0x1F, \ - (u + 0x07) & 0x1F, (u + 0x0B) & 0x1F, \ - (u + 0x0F) & 0x1F, (u + 0x13) & 0x1F); \ - } \ - memcpy(a, t, sizeof t); \ - } while (0) - - -#define PERM_BIG_P(a) do { \ - int r; \ - for (r = 0; r < 14; r ++) \ - ROUND_BIG_P(a, r); \ - } while (0) - -#define PERM_BIG_Q(a) do { \ - int r; \ - for (r = 0; r < 14; r ++) \ - ROUND_BIG_Q(a, r); \ - } while (0) - - -#define COMPRESS_BIG do { \ - sph_u32 g[32], m[32]; \ - size_t uu; \ - for (uu = 0; uu < 32; uu ++) { \ - m[uu] = dec32e_aligned(buf + (uu << 2)); \ - g[uu] = m[uu] ^ H[uu]; \ - } \ - PERM_BIG_P(g); \ - PERM_BIG_Q(m); \ - for (uu = 0; uu < 32; uu ++) \ - H[uu] ^= g[uu] ^ m[uu]; \ - } while (0) - -#define FINAL_BIG do { \ - sph_u32 x[32]; \ - size_t uu; \ - memcpy(x, H, sizeof x); \ - PERM_BIG_P(x); \ - for (uu = 0; uu < 32; uu ++) \ - H[uu] ^= x[uu]; \ - } while (0) - - -static void -groestl_big_init(sph_groestl_big_context *sc, unsigned out_size) -{ - size_t u = 0; - - sc->ptr = 0; - for (u = 0; u < 31; u ++) - sc->state.narrow[u] = 0; - sc->state.narrow[31] = ((sph_u32)(out_size & 0xFF) << 24) - | ((sph_u32)(out_size & 0xFF00) << 8); - sc->count = 0; -} - -static void -groestl_big_core(sph_groestl_big_context *sc, const void *data, size_t len) -{ - unsigned char *buf = NULL; - size_t ptr = 0; - DECL_STATE_BIG - - buf = sc->buf; - ptr = sc->ptr; - if (len < (sizeof sc->buf) - ptr) { - memcpy(buf + ptr, data, len); - ptr += len; - sc->ptr = ptr; - return; - } - - READ_STATE_BIG(sc); - while (len > 0) { - size_t clen = 0; - - clen = (sizeof sc->buf) - ptr; - if (clen > len) - clen = len; - memcpy(buf + ptr, data, clen); - ptr += clen; - data = (const unsigned char *)data + clen; - len -= clen; - if (ptr == sizeof sc->buf) { - COMPRESS_BIG; - sc->count ++; - ptr = 0; - } - } - WRITE_STATE_BIG(sc); - sc->ptr = ptr; -} - -static void -groestl_big_close(sph_groestl_big_context *sc, - unsigned ub, unsigned n, void *dst, size_t out_len) -{ - unsigned char pad[136] = {0}; - size_t ptr = 0, pad_len = 0, u2 = 0; - sph_u64 count = 0; - unsigned z = 0; - DECL_STATE_BIG - - ptr = sc->ptr; - z = 0x80 >> n; - pad[0] = ((ub & -z) | z) & 0xFF; - if (ptr < 120) { - pad_len = 128 - ptr; - count = SPH_T64(sc->count + 1); - } else { - pad_len = 256 - ptr; - count = SPH_T64(sc->count + 2); - } - memzero(pad + 1, pad_len - 9); - sph_enc64be(pad + pad_len - 8, count); - groestl_big_core(sc, pad, pad_len); - READ_STATE_BIG(sc); - FINAL_BIG; - for (u2 = 0; u2 < 16; u2 ++) - enc32e(pad + (u2 << 2), H[u2 + 16]); - memcpy(dst, pad + 64 - out_len, out_len); - groestl_big_init(sc, (unsigned)out_len << 3); -} - -void -groestl512_Init(void *cc) -{ - groestl_big_init((sph_groestl_big_context *)cc, 512); -} - -void -groestl512_Update(void *cc, const void *data, size_t len) -{ - groestl_big_core((sph_groestl_big_context *)cc, data, len); -} - -void -groestl512_Final(void *cc, void *dst) -{ - groestl_big_close((sph_groestl_big_context *)cc, 0, 0, dst, 64); -} - -void -groestl512_DoubleTrunc(void *cc, void *dst) -{ - char buf[64] = {0}; - - groestl512_Final(cc, buf); - groestl512_Update(cc, buf, sizeof(buf)); - groestl512_Final(cc, buf); - memcpy(dst, buf, 32); -} diff --git a/trezor-crypto/crypto/hasher.c b/trezor-crypto/crypto/hasher.c deleted file mode 100644 index 59c10181497..00000000000 --- a/trezor-crypto/crypto/hasher.c +++ /dev/null @@ -1,147 +0,0 @@ -/** - * Copyright (c) 2017 Saleem Rashid - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include -#include - -void hasher_InitParam(Hasher *hasher, HasherType type, const void *param, - uint32_t param_size) { - hasher->type = type; - hasher->param = param; - hasher->param_size = param_size; - - switch (hasher->type) { - case HASHER_SHA2: - case HASHER_SHA2D: - case HASHER_SHA2_RIPEMD: - sha256_Init(&hasher->ctx.sha2); - break; - case HASHER_SHA3: -#if USE_KECCAK - case HASHER_SHA3K: -#endif - sha3_256_Init(&hasher->ctx.sha3); - break; - case HASHER_BLAKE: - case HASHER_BLAKED: - case HASHER_BLAKE_RIPEMD: - blake256_Init(&hasher->ctx.blake); - break; - case HASHER_GROESTLD_TRUNC: - groestl512_Init(&hasher->ctx.groestl); - break; - case HASHER_BLAKE2B: - tc_blake2b_Init(&hasher->ctx.blake2b, 32); - break; - case HASHER_BLAKE2B_PERSONAL: - tc_blake2b_InitPersonal(&hasher->ctx.blake2b, 32, hasher->param, - hasher->param_size); - break; - } -} - -void hasher_Init(Hasher *hasher, HasherType type) { - hasher_InitParam(hasher, type, NULL, 0); -} - -void hasher_Reset(Hasher *hasher) { - hasher_InitParam(hasher, hasher->type, hasher->param, hasher->param_size); -} - -void hasher_Update(Hasher *hasher, const uint8_t *data, size_t length) { - switch (hasher->type) { - case HASHER_SHA2: - case HASHER_SHA2D: - case HASHER_SHA2_RIPEMD: - sha256_Update(&hasher->ctx.sha2, data, length); - break; - case HASHER_SHA3: -#if USE_KECCAK - case HASHER_SHA3K: -#endif - sha3_Update(&hasher->ctx.sha3, data, length); - break; - case HASHER_BLAKE: - case HASHER_BLAKED: - case HASHER_BLAKE_RIPEMD: - blake256_Update(&hasher->ctx.blake, data, length); - break; - case HASHER_GROESTLD_TRUNC: - groestl512_Update(&hasher->ctx.groestl, data, length); - break; - case HASHER_BLAKE2B: - case HASHER_BLAKE2B_PERSONAL: - tc_blake2b_Update(&hasher->ctx.blake2b, data, length); - break; - } -} - -void hasher_Final(Hasher *hasher, uint8_t hash[HASHER_DIGEST_LENGTH]) { - switch (hasher->type) { - case HASHER_SHA2: - sha256_Final(&hasher->ctx.sha2, hash); - break; - case HASHER_SHA2D: - sha256_Final(&hasher->ctx.sha2, hash); - hasher_Raw(HASHER_SHA2, hash, HASHER_DIGEST_LENGTH, hash); - break; - case HASHER_SHA2_RIPEMD: - sha256_Final(&hasher->ctx.sha2, hash); - ripemd160(hash, HASHER_DIGEST_LENGTH, hash); - break; - case HASHER_SHA3: - sha3_Final(&hasher->ctx.sha3, hash); - break; -#if USE_KECCAK - case HASHER_SHA3K: - keccak_Final(&hasher->ctx.sha3, hash); - break; -#endif - case HASHER_BLAKE: - blake256_Final(&hasher->ctx.blake, hash); - break; - case HASHER_BLAKED: - blake256_Final(&hasher->ctx.blake, hash); - hasher_Raw(HASHER_BLAKE, hash, HASHER_DIGEST_LENGTH, hash); - break; - case HASHER_BLAKE_RIPEMD: - blake256_Final(&hasher->ctx.blake, hash); - ripemd160(hash, HASHER_DIGEST_LENGTH, hash); - break; - case HASHER_GROESTLD_TRUNC: - groestl512_DoubleTrunc(&hasher->ctx.groestl, hash); - break; - case HASHER_BLAKE2B: - case HASHER_BLAKE2B_PERSONAL: - tc_blake2b_Final(&hasher->ctx.blake2b, hash, 32); - break; - } -} - -void hasher_Raw(HasherType type, const uint8_t *data, size_t length, - uint8_t hash[HASHER_DIGEST_LENGTH]) { - Hasher hasher = {0}; - - hasher_Init(&hasher, type); - hasher_Update(&hasher, data, length); - hasher_Final(&hasher, hash); -} diff --git a/trezor-crypto/crypto/hmac.c b/trezor-crypto/crypto/hmac.c deleted file mode 100644 index 6d77e8a9317..00000000000 --- a/trezor-crypto/crypto/hmac.c +++ /dev/null @@ -1,176 +0,0 @@ -/** - * Copyright (c) 2013-2014 Tomas Dzetkulic - * Copyright (c) 2013-2014 Pavol Rusnak - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include - -#include -#include -#include - -void hmac_sha256_Init(HMAC_SHA256_CTX *hctx, const uint8_t *key, - const uint32_t keylen) { - CONFIDENTIAL uint8_t i_key_pad[SHA256_BLOCK_LENGTH]; - memzero(i_key_pad, SHA256_BLOCK_LENGTH); - if (keylen > SHA256_BLOCK_LENGTH) { - sha256_Raw(key, keylen, i_key_pad); - } else { - memcpy(i_key_pad, key, keylen); - } - for (int i = 0; i < SHA256_BLOCK_LENGTH; i++) { - hctx->o_key_pad[i] = i_key_pad[i] ^ 0x5c; - i_key_pad[i] ^= 0x36; - } - sha256_Init(&(hctx->ctx)); - sha256_Update(&(hctx->ctx), i_key_pad, SHA256_BLOCK_LENGTH); - memzero(i_key_pad, sizeof(i_key_pad)); -} - -void hmac_sha256_Update(HMAC_SHA256_CTX *hctx, const uint8_t *msg, - const uint32_t msglen) { - sha256_Update(&(hctx->ctx), msg, msglen); -} - -void hmac_sha256_Final(HMAC_SHA256_CTX *hctx, uint8_t *hmac) { - sha256_Final(&(hctx->ctx), hmac); - sha256_Init(&(hctx->ctx)); - sha256_Update(&(hctx->ctx), hctx->o_key_pad, SHA256_BLOCK_LENGTH); - sha256_Update(&(hctx->ctx), hmac, SHA256_DIGEST_LENGTH); - sha256_Final(&(hctx->ctx), hmac); - memzero(hctx, sizeof(HMAC_SHA256_CTX)); -} - -void hmac_sha256(const uint8_t *key, const uint32_t keylen, const uint8_t *msg, - const uint32_t msglen, uint8_t *hmac) { - CONFIDENTIAL HMAC_SHA256_CTX hctx; - hmac_sha256_Init(&hctx, key, keylen); - hmac_sha256_Update(&hctx, msg, msglen); - hmac_sha256_Final(&hctx, hmac); -} - -void hmac_sha256_prepare(const uint8_t *key, const uint32_t keylen, - uint32_t *opad_digest, uint32_t *ipad_digest) { - CONFIDENTIAL uint32_t key_pad[SHA256_BLOCK_LENGTH / sizeof(uint32_t)]; - - memzero(key_pad, sizeof(key_pad)); - if (keylen > SHA256_BLOCK_LENGTH) { - CONFIDENTIAL SHA256_CTX context; - sha256_Init(&context); - sha256_Update(&context, key, keylen); - sha256_Final(&context, (uint8_t *)key_pad); - } else { - memcpy(key_pad, key, keylen); - } - - /* compute o_key_pad and its digest */ - for (int i = 0; i < SHA256_BLOCK_LENGTH / (int)sizeof(uint32_t); i++) { - uint32_t data = 0; -#if BYTE_ORDER == LITTLE_ENDIAN - REVERSE32(key_pad[i], data); -#else - data = key_pad[i]; -#endif - key_pad[i] = data ^ 0x5c5c5c5c; - } - sha256_Transform(sha256_initial_hash_value, key_pad, opad_digest); - - /* convert o_key_pad to i_key_pad and compute its digest */ - for (int i = 0; i < SHA256_BLOCK_LENGTH / (int)sizeof(uint32_t); i++) { - key_pad[i] = key_pad[i] ^ 0x5c5c5c5c ^ 0x36363636; - } - sha256_Transform(sha256_initial_hash_value, key_pad, ipad_digest); - memzero(key_pad, sizeof(key_pad)); -} - -void hmac_sha512_Init(HMAC_SHA512_CTX *hctx, const uint8_t *key, - const uint32_t keylen) { - CONFIDENTIAL uint8_t i_key_pad[SHA512_BLOCK_LENGTH]; - memzero(i_key_pad, SHA512_BLOCK_LENGTH); - if (keylen > SHA512_BLOCK_LENGTH) { - sha512_Raw(key, keylen, i_key_pad); - } else { - memcpy(i_key_pad, key, keylen); - } - for (int i = 0; i < SHA512_BLOCK_LENGTH; i++) { - hctx->o_key_pad[i] = i_key_pad[i] ^ 0x5c; - i_key_pad[i] ^= 0x36; - } - sha512_Init(&(hctx->ctx)); - sha512_Update(&(hctx->ctx), i_key_pad, SHA512_BLOCK_LENGTH); - memzero(i_key_pad, sizeof(i_key_pad)); -} - -void hmac_sha512_Update(HMAC_SHA512_CTX *hctx, const uint8_t *msg, - const uint32_t msglen) { - sha512_Update(&(hctx->ctx), msg, msglen); -} - -void hmac_sha512_Final(HMAC_SHA512_CTX *hctx, uint8_t *hmac) { - sha512_Final(&(hctx->ctx), hmac); - sha512_Init(&(hctx->ctx)); - sha512_Update(&(hctx->ctx), hctx->o_key_pad, SHA512_BLOCK_LENGTH); - sha512_Update(&(hctx->ctx), hmac, SHA512_DIGEST_LENGTH); - sha512_Final(&(hctx->ctx), hmac); - memzero(hctx, sizeof(HMAC_SHA512_CTX)); -} - -void hmac_sha512(const uint8_t *key, const uint32_t keylen, const uint8_t *msg, - const uint32_t msglen, uint8_t *hmac) { - HMAC_SHA512_CTX hctx = {0}; - hmac_sha512_Init(&hctx, key, keylen); - hmac_sha512_Update(&hctx, msg, msglen); - hmac_sha512_Final(&hctx, hmac); -} - -void hmac_sha512_prepare(const uint8_t *key, const uint32_t keylen, - uint64_t *opad_digest, uint64_t *ipad_digest) { - CONFIDENTIAL uint64_t key_pad[SHA512_BLOCK_LENGTH / sizeof(uint64_t)]; - - memzero(key_pad, sizeof(key_pad)); - if (keylen > SHA512_BLOCK_LENGTH) { - CONFIDENTIAL SHA512_CTX context; - sha512_Init(&context); - sha512_Update(&context, key, keylen); - sha512_Final(&context, (uint8_t *)key_pad); - } else { - memcpy(key_pad, key, keylen); - } - - /* compute o_key_pad and its digest */ - for (int i = 0; i < SHA512_BLOCK_LENGTH / (int)sizeof(uint64_t); i++) { - uint64_t data = 0; -#if BYTE_ORDER == LITTLE_ENDIAN - REVERSE64(key_pad[i], data); -#else - data = key_pad[i]; -#endif - key_pad[i] = data ^ 0x5c5c5c5c5c5c5c5c; - } - sha512_Transform(sha512_initial_hash_value, key_pad, opad_digest); - - /* convert o_key_pad to i_key_pad and compute its digest */ - for (int i = 0; i < SHA512_BLOCK_LENGTH / (int)sizeof(uint64_t); i++) { - key_pad[i] = key_pad[i] ^ 0x5c5c5c5c5c5c5c5c ^ 0x3636363636363636; - } - sha512_Transform(sha512_initial_hash_value, key_pad, ipad_digest); - memzero(key_pad, sizeof(key_pad)); -} diff --git a/trezor-crypto/crypto/hmac_drbg.c b/trezor-crypto/crypto/hmac_drbg.c deleted file mode 100644 index 08032bfd74d..00000000000 --- a/trezor-crypto/crypto/hmac_drbg.c +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Copyright (c) 2019 Andrew R. Kozlik - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include - -static void update_k(HMAC_DRBG_CTX *ctx, uint8_t domain, const uint8_t *data1, - size_t len1, const uint8_t *data2, size_t len2) { - // Computes K = HMAC(K, V || domain || data1 || data 2). - - // First hash operation of HMAC. - uint32_t h[SHA256_BLOCK_LENGTH / sizeof(uint32_t)] = {0}; - if (len1 + len2 == 0) { - ctx->v[8] = 0x00800000; - ctx->v[15] = (SHA256_BLOCK_LENGTH + SHA256_DIGEST_LENGTH + 1) * 8; - sha256_Transform(ctx->idig, ctx->v, h); - ctx->v[8] = 0x80000000; - ctx->v[15] = (SHA256_BLOCK_LENGTH + SHA256_DIGEST_LENGTH) * 8; - } else { - SHA256_CTX sha_ctx = {0}; - memcpy(sha_ctx.state, ctx->idig, SHA256_DIGEST_LENGTH); - for (size_t i = 0; i < SHA256_DIGEST_LENGTH / sizeof(uint32_t); i++) { -#if BYTE_ORDER == LITTLE_ENDIAN - REVERSE32(ctx->v[i], sha_ctx.buffer[i]); -#else - sha_ctx.buffer[i] = ctx->v[i]; -#endif - } - ((uint8_t *)sha_ctx.buffer)[SHA256_DIGEST_LENGTH] = domain; - sha_ctx.bitcount = (SHA256_BLOCK_LENGTH + SHA256_DIGEST_LENGTH + 1) * 8; - sha256_Update(&sha_ctx, data1, len1); - sha256_Update(&sha_ctx, data2, len2); - sha256_Final(&sha_ctx, (uint8_t *)h); -#if BYTE_ORDER == LITTLE_ENDIAN - for (size_t i = 0; i < SHA256_DIGEST_LENGTH / sizeof(uint32_t); i++) - REVERSE32(h[i], h[i]); -#endif - } - - // Second hash operation of HMAC. - h[8] = 0x80000000; - h[15] = (SHA256_BLOCK_LENGTH + SHA256_DIGEST_LENGTH) * 8; - sha256_Transform(ctx->odig, h, h); - - // Precompute the inner digest and outer digest of K. - h[8] = 0; - h[15] = 0; - for (size_t i = 0; i < SHA256_BLOCK_LENGTH / sizeof(uint32_t); i++) { - h[i] ^= 0x36363636; - } - sha256_Transform(sha256_initial_hash_value, h, ctx->idig); - - for (size_t i = 0; i < SHA256_BLOCK_LENGTH / sizeof(uint32_t); i++) { - h[i] = h[i] ^ 0x36363636 ^ 0x5c5c5c5c; - } - sha256_Transform(sha256_initial_hash_value, h, ctx->odig); - memzero(h, sizeof(h)); -} - -static void update_v(HMAC_DRBG_CTX *ctx) { - sha256_Transform(ctx->idig, ctx->v, ctx->v); - sha256_Transform(ctx->odig, ctx->v, ctx->v); -} - -void hmac_drbg_init(HMAC_DRBG_CTX *ctx, const uint8_t *entropy, - size_t entropy_len, const uint8_t *nonce, - size_t nonce_len) { - uint32_t h[SHA256_BLOCK_LENGTH / sizeof(uint32_t)] = {0}; - - // Precompute the inner digest and outer digest of K = 0x00 ... 0x00. - memset(h, 0x36, sizeof(h)); - sha256_Transform(sha256_initial_hash_value, h, ctx->idig); - memset(h, 0x5c, sizeof(h)); - sha256_Transform(sha256_initial_hash_value, h, ctx->odig); - - // Let V = 0x01 ... 0x01. - memset(ctx->v, 1, SHA256_DIGEST_LENGTH); - for (size_t i = 9; i < 15; i++) ctx->v[i] = 0; - ctx->v[8] = 0x80000000; - ctx->v[15] = (SHA256_BLOCK_LENGTH + SHA256_DIGEST_LENGTH) * 8; - - hmac_drbg_reseed(ctx, entropy, entropy_len, nonce, nonce_len); - - memzero(h, sizeof(h)); -} - -void hmac_drbg_reseed(HMAC_DRBG_CTX *ctx, const uint8_t *entropy, size_t len, - const uint8_t *addin, size_t addin_len) { - update_k(ctx, 0, entropy, len, addin, addin_len); - update_v(ctx); - if (len == 0) return; - update_k(ctx, 1, entropy, len, addin, addin_len); - update_v(ctx); -} - -void hmac_drbg_generate(HMAC_DRBG_CTX *ctx, uint8_t *buf, size_t len) { - size_t i = 0; - while (i < len) { - update_v(ctx); - for (size_t j = 0; j < 8 && i < len; j++) { - uint32_t r = ctx->v[j]; - for (int k = 24; k >= 0 && i < len; k -= 8) { - buf[i++] = (r >> k) & 0xFF; - } - } - } - update_k(ctx, 0, NULL, 0, NULL, 0); - update_v(ctx); -} diff --git a/trezor-crypto/crypto/monero/base58.c b/trezor-crypto/crypto/monero/base58.c deleted file mode 100644 index 422e32885b1..00000000000 --- a/trezor-crypto/crypto/monero/base58.c +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright (c) 2014-2018, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#include -#include -#include -#include -#include -#include "int-util.h" -#include - -const size_t alphabet_size = 58; // sizeof(b58digits_ordered) - 1; -const size_t encoded_block_sizes[] = {0, 2, 3, 5, 6, 7, 9, 10, 11}; -const size_t full_block_size = sizeof(encoded_block_sizes) / sizeof(encoded_block_sizes[0]) - 1; -const size_t full_encoded_block_size = 11; // encoded_block_sizes[full_block_size]; -const size_t addr_checksum_size = 4; -const int decoded_block_sizes[] = {0, -1, 1, 2, -1, 3, 4, 5, -1, 6, 7, 8}; -#define reverse_alphabet(letter) ((int8_t) b58digits_map[(int)letter]) - - -uint64_t uint_8be_to_64(const uint8_t* data, size_t size) -{ - assert(1 <= size && size <= sizeof(uint64_t)); - - uint64_t res = 0; - switch (9 - size) - { - case 1: res |= *data++; /* FALLTHRU */ - case 2: res <<= 8; res |= *data++; /* FALLTHRU */ - case 3: res <<= 8; res |= *data++; /* FALLTHRU */ - case 4: res <<= 8; res |= *data++; /* FALLTHRU */ - case 5: res <<= 8; res |= *data++; /* FALLTHRU */ - case 6: res <<= 8; res |= *data++; /* FALLTHRU */ - case 7: res <<= 8; res |= *data++; /* FALLTHRU */ - case 8: res <<= 8; res |= *data; break; - default: assert(false); - } - - return res; -} - -void uint_64_to_8be(uint64_t num, size_t size, uint8_t* data) -{ - assert(1 <= size && size <= sizeof(uint64_t)); - - uint64_t num_be = SWAP64(num); - memcpy(data, (uint8_t*)(&num_be) + sizeof(uint64_t) - size, size); -} - -void encode_block(const char* block, size_t size, char* res) -{ - assert(1 <= size && size <= full_block_size); - - uint64_t num = uint_8be_to_64((uint8_t*)(block), size); - int i = ((int)(encoded_block_sizes[size])) - 1; - while (0 <= i) - { - uint64_t remainder = num % alphabet_size; - num /= alphabet_size; - res[i] = b58digits_ordered[remainder]; - --i; - } -} - -bool decode_block(const char* block, size_t size, char* res) -{ - assert(1 <= size && size <= full_encoded_block_size); - - int res_size = decoded_block_sizes[size]; - if (res_size <= 0) - return false; // Invalid block size - - uint64_t res_num = 0; - uint64_t order = 1; - for (size_t i = size - 1; i < size; --i) - { - if (block[i] & 0x80) - return false; // Invalid symbol - int digit = reverse_alphabet(block[i]); - if (digit < 0) - return false; // Invalid symbol - - uint64_t product_hi = 0; - uint64_t tmp = res_num + mul128(order, (uint64_t) digit, &product_hi); - if (tmp < res_num || 0 != product_hi) - return false; // Overflow - - res_num = tmp; - order *= alphabet_size; // Never overflows, 58^10 < 2^64 - } - - if ((size_t)res_size < full_block_size && (UINT64_C(1) << (8 * res_size)) <= res_num) - return false; // Overflow - - uint_64_to_8be(res_num, res_size, (uint8_t*)(res)); - - return true; -} - - -bool xmr_base58_encode(char *b58, size_t *b58sz, const void *data, size_t binsz) -{ - if (binsz==0) - return true; - - const char * data_bin = data; - size_t full_block_count = binsz / full_block_size; - size_t last_block_size = binsz % full_block_size; - size_t res_size = full_block_count * full_encoded_block_size + encoded_block_sizes[last_block_size]; - - if (b58sz){ - if (res_size >= *b58sz){ - return false; - } - *b58sz = res_size; - } - - for (size_t i = 0; i < full_block_count; ++i) - { - encode_block(data_bin + i * full_block_size, full_block_size, b58 + i * full_encoded_block_size); - } - - if (0 < last_block_size) - { - encode_block(data_bin + full_block_count * full_block_size, last_block_size, b58 + full_block_count * full_encoded_block_size); - } - - return true; -} - -bool xmr_base58_decode(const char *b58, size_t b58sz, void *data, size_t *binsz) -{ - if (b58sz == 0) { - *binsz = 0; - return true; - } - - size_t full_block_count = b58sz / full_encoded_block_size; - size_t last_block_size = b58sz % full_encoded_block_size; - int last_block_decoded_size = decoded_block_sizes[last_block_size]; - if (last_block_decoded_size < 0) { - *binsz = 0; - return false; // Invalid enc length - } - - size_t data_size = full_block_count * full_block_size + last_block_decoded_size; - if (*binsz < data_size){ - *binsz = 0; - return false; - } - - char * data_bin = data; - for (size_t i = 0; i < full_block_count; ++i) - { - if (!decode_block(b58 + i * full_encoded_block_size, full_encoded_block_size, data_bin + i * full_block_size)) - return false; - } - - if (0 < last_block_size) - { - if (!decode_block(b58 + full_block_count * full_encoded_block_size, last_block_size, - data_bin + full_block_count * full_block_size)) - return false; - } - - return true; -} - -int xmr_base58_addr_encode_check(uint64_t tag, const uint8_t *data, size_t binsz, char *b58, size_t b58sz) -{ - if (binsz > 128 || tag > 127) { // tag varint - return false; - } - - size_t b58size = b58sz; - uint8_t buf[(binsz + 1) + HASHER_DIGEST_LENGTH]; - memset(buf, 0, sizeof(buf)); - uint8_t *hash = buf + binsz + 1; - buf[0] = (uint8_t) tag; - memcpy(buf + 1, data, binsz); - hasher_Raw(HASHER_SHA3K, buf, binsz + 1, hash); - - bool r = xmr_base58_encode(b58, &b58size, buf, binsz + 1 + addr_checksum_size); - return (int) (!r ? 0 : b58size); -} - -int xmr_base58_addr_decode_check(const char *addr, size_t sz, uint64_t *tag, void *data, size_t datalen) -{ - size_t buflen = 1 + 64 + addr_checksum_size; - uint8_t buf[buflen]; - memset(buf, 0, sizeof(buf)); - uint8_t hash[HASHER_DIGEST_LENGTH] = {0}; - - if (!xmr_base58_decode(addr, sz, buf, &buflen)){ - return 0; - } - - size_t res_size = buflen - addr_checksum_size - 1; - if (datalen < res_size){ - return 0; - } - - if (buflen <= addr_checksum_size+1) { - return 0; - } - - hasher_Raw(HASHER_SHA3K, buf, buflen - addr_checksum_size, hash); - if (memcmp(hash, buf + buflen - addr_checksum_size, addr_checksum_size) != 0){ - return 0; - } - - *tag = buf[0]; - if (*tag > 127){ - return false; // varint - } - - memcpy(data, buf+1, res_size); - return (int) res_size; -} diff --git a/trezor-crypto/crypto/monero/base58.h b/trezor-crypto/crypto/monero/base58.h deleted file mode 100644 index 66a08f8f5ae..00000000000 --- a/trezor-crypto/crypto/monero/base58.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2014-2018, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#ifndef __XMR_BASE58_H__ -#define __XMR_BASE58_H__ - -#include -#include -#include - -int xmr_base58_addr_encode_check(uint64_t tag, const uint8_t *data, size_t binsz, char *b58, size_t b58sz); -int xmr_base58_addr_decode_check(const char *addr, size_t sz, uint64_t *tag, void *data, size_t datalen); -bool xmr_base58_encode(char *b58, size_t *b58sz, const void *data, size_t binsz); -bool xmr_base58_decode(const char *b58, size_t b58sz, void *data, size_t *binsz); - -#endif diff --git a/trezor-crypto/crypto/monero/int-util.h b/trezor-crypto/crypto/monero/int-util.h deleted file mode 100644 index 9cba47a8ba5..00000000000 --- a/trezor-crypto/crypto/monero/int-util.h +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) 2014-2018, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#pragma once - -#include -#include - -static inline uint64_t hi_dword(uint64_t val) { - return val >> 32; -} - -static inline uint64_t lo_dword(uint64_t val) { - return val & 0xFFFFFFFF; -} - -static inline uint64_t mul128(uint64_t multiplier, uint64_t multiplicand, uint64_t* product_hi) { - // multiplier = ab = a * 2^32 + b - // multiplicand = cd = c * 2^32 + d - // ab * cd = a * c * 2^64 + (a * d + b * c) * 2^32 + b * d - uint64_t a = hi_dword(multiplier); - uint64_t b = lo_dword(multiplier); - uint64_t c = hi_dword(multiplicand); - uint64_t d = lo_dword(multiplicand); - - uint64_t ac = a * c; - uint64_t ad = a * d; - uint64_t bc = b * c; - uint64_t bd = b * d; - - uint64_t adbc = ad + bc; - uint64_t adbc_carry = adbc < ad ? 1 : 0; - - // multiplier * multiplicand = product_hi * 2^64 + product_lo - uint64_t product_lo = bd + (adbc << 32); - uint64_t product_lo_carry = product_lo < bd ? 1 : 0; - *product_hi = ac + (adbc >> 32) + (adbc_carry << 32) + product_lo_carry; - assert(ac <= *product_hi); - - return product_lo; -} - -#define SWAP64(x) ((((uint64_t) (x) & 0x00000000000000ff) << 56) | \ - (((uint64_t) (x) & 0x000000000000ff00) << 40) | \ - (((uint64_t) (x) & 0x0000000000ff0000) << 24) | \ - (((uint64_t) (x) & 0x00000000ff000000) << 8) | \ - (((uint64_t) (x) & 0x000000ff00000000) >> 8) | \ - (((uint64_t) (x) & 0x0000ff0000000000) >> 24) | \ - (((uint64_t) (x) & 0x00ff000000000000) >> 40) | \ - (((uint64_t) (x) & 0xff00000000000000) >> 56)) diff --git a/trezor-crypto/crypto/monero/monero.h b/trezor-crypto/crypto/monero/monero.h deleted file mode 100644 index 7b088f21670..00000000000 --- a/trezor-crypto/crypto/monero/monero.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// Created by Dusan Klinec on 10/05/2018. -// - -#ifndef TREZOR_CRYPTO_MONERO_H -#define TREZOR_CRYPTO_MONERO_H - -#if !USE_MONERO -#error "Compile with -DUSE_MONERO=1" -#endif - -#if !USE_KECCAK -#error "Compile with -DUSE_KECCAK=1" -#endif - -#include -#include "range_proof.h" -#include "serialize.h" -#include "xmr.h" - -#endif // TREZOR_CRYPTO_MONERO_H diff --git a/trezor-crypto/crypto/monero/range_proof.c b/trezor-crypto/crypto/monero/range_proof.c deleted file mode 100644 index e3fd9b6a278..00000000000 --- a/trezor-crypto/crypto/monero/range_proof.c +++ /dev/null @@ -1,115 +0,0 @@ -// -// Created by Dusan Klinec on 10/05/2018. -// - -#include "range_proof.h" - -static void xmr_hash_ge25519_to_scalar(bignum256modm r, const ge25519 *p) { - unsigned char buff[32] = {0}; - ge25519_pack(buff, p); - xmr_hash_to_scalar(r, buff, sizeof(buff)); -} - -void xmr_gen_range_sig(xmr_range_sig_t *sig, ge25519 *C, bignum256modm mask, - xmr_amount amount, bignum256modm *last_mask) { - bignum256modm ai[64] = {0}; - bignum256modm alpha[64] = {0}; - xmr_gen_range_sig_ex(sig, C, mask, amount, last_mask, ai, alpha); -} - -void xmr_gen_range_sig_ex(xmr_range_sig_t *sig, ge25519 *C, bignum256modm mask, - xmr_amount amount, bignum256modm *last_mask, - bignum256modm ai[64], bignum256modm alpha[64]) { - const unsigned n = XMR_ATOMS; - bignum256modm a = {0}; - bignum256modm si = {0}; - bignum256modm c = {0}; - bignum256modm ee = {0}; - unsigned char buff[32] = {0}; - - Hasher kck = {0}; - xmr_hasher_init(&kck); - - ge25519 C_acc = {0}; - ge25519 C_h = {0}; - ge25519 C_tmp = {0}; - ge25519 L = {0}; - ge25519 Zero = {0}; - - ge25519_set_neutral(&Zero); - ge25519_set_neutral(&C_acc); - ge25519_set_xmr_h(&C_h); - set256_modm(a, 0); - -#define BB(i) ((amount >> (i)) & 1) - - // First pass, generates: ai, alpha, Ci, ee, s1 - for (unsigned ii = 0; ii < n; ++ii) { - xmr_random_scalar(ai[ii]); - if (last_mask != NULL && ii == n - 1) { - sub256_modm(ai[ii], *last_mask, a); - } - - add256_modm(a, a, ai[ii]); // creating the total mask since you have to - // pass this to receiver... - xmr_random_scalar(alpha[ii]); - - ge25519_scalarmult_base_niels(&L, ge25519_niels_base_multiples, alpha[ii]); - ge25519_scalarmult_base_niels(&C_tmp, ge25519_niels_base_multiples, ai[ii]); - - // C_tmp += &Zero if BB(ii) == 0 else &C_h - ge25519_add(&C_tmp, &C_tmp, BB(ii) == 0 ? &Zero : &C_h, 0); - ge25519_add(&C_acc, &C_acc, &C_tmp, 0); - - // Set Ci[ii] to sigs - ge25519_pack(sig->Ci[ii], &C_tmp); - - if (BB(ii) == 0) { - xmr_random_scalar(si); - xmr_hash_ge25519_to_scalar(c, &L); - - ge25519_add(&C_tmp, &C_tmp, &C_h, 1); // Ci[ii] -= c_h - xmr_add_keys2_vartime(&L, si, c, &C_tmp); - - // Set s1[ii] to sigs - contract256_modm(sig->asig.s1[ii], si); - } - - ge25519_pack(buff, &L); - xmr_hasher_update(&kck, buff, sizeof(buff)); - - ge25519_double(&C_h, &C_h); // c_H = crypto.scalarmult(c_H, 2) - } - - // Compute ee - xmr_hasher_final(&kck, buff); - expand256_modm(ee, buff, sizeof(buff)); - - ge25519_set_xmr_h(&C_h); - - // Second pass, s0, s1 - for (unsigned ii = 0; ii < n; ++ii) { - if (BB(ii) == 0) { - mulsub256_modm(si, ai[ii], ee, alpha[ii]); - contract256_modm(sig->asig.s0[ii], si); - - } else { - xmr_random_scalar(si); - contract256_modm(sig->asig.s0[ii], si); - - ge25519_unpack_vartime(&C_tmp, sig->Ci[ii]); - xmr_add_keys2_vartime(&L, si, ee, &C_tmp); - xmr_hash_ge25519_to_scalar(c, &L); - - mulsub256_modm(si, ai[ii], c, alpha[ii]); - contract256_modm(sig->asig.s1[ii], si); - } - - ge25519_double(&C_h, &C_h); // c_H = crypto.scalarmult(c_H, 2) - } - - ge25519_copy(C, &C_acc); - copy256_modm(mask, a); - contract256_modm(sig->asig.ee, ee); -#undef BB -} diff --git a/trezor-crypto/crypto/monero/range_proof.h b/trezor-crypto/crypto/monero/range_proof.h deleted file mode 100644 index f614ab04e76..00000000000 --- a/trezor-crypto/crypto/monero/range_proof.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// Created by Dusan Klinec on 10/05/2018. -// - -#ifndef TREZOR_CRYPTO_RANGE_PROOF_H -#define TREZOR_CRYPTO_RANGE_PROOF_H - -#include "xmr.h" -#define XMR_ATOMS 64 - -typedef uint64_t xmr_amount; -typedef xmr_key_t xmr_key64_t[64]; - -typedef struct xmr_boro_sig { - xmr_key64_t s0; - xmr_key64_t s1; - xmr_key_t ee; -} xmr_boro_sig_t; - -typedef struct range_sig { - xmr_boro_sig_t asig; - xmr_key64_t Ci; -} xmr_range_sig_t; - -void xmr_gen_range_sig(xmr_range_sig_t* sig, ge25519* C, bignum256modm mask, - xmr_amount amount, bignum256modm* last_mask); -void xmr_gen_range_sig_ex(xmr_range_sig_t* sig, ge25519* C, bignum256modm mask, - xmr_amount amount, bignum256modm* last_mask, - bignum256modm ai[64], bignum256modm alpha[64]); - -#endif // TREZOR_CRYPTO_RANGE_PROOF_H diff --git a/trezor-crypto/crypto/monero/serialize.c b/trezor-crypto/crypto/monero/serialize.c deleted file mode 100644 index 4d4cbd8bbeb..00000000000 --- a/trezor-crypto/crypto/monero/serialize.c +++ /dev/null @@ -1,53 +0,0 @@ -// -// Created by Dusan Klinec on 02/05/2018. -// - -#include "serialize.h" - -int xmr_size_varint(uint64_t num) { - int ctr = 1; - while (num >= 0x80) { - ++ctr; - num >>= 7; - } - return ctr; -} - -int xmr_write_varint(uint8_t *buff, size_t buff_size, uint64_t num) { - unsigned ctr = 0; - while (num >= 0x80 && ctr < buff_size) { - *buff = (uint8_t)(((num)&0x7f) | 0x80); - ++buff; - ++ctr; - num >>= 7; - } - - /* writes the last one to dest */ - if (ctr < buff_size) { - *buff = (uint8_t)num; - ++ctr; - } - return ctr <= buff_size ? (int)ctr : -1; -} - -int xmr_read_varint(uint8_t *buff, size_t buff_size, uint64_t *val) { - unsigned read = 0; - int finished_ok = 0; - *val = 0; - - for (int shift = 0; read < buff_size; shift += 7, ++read) { - uint8_t byte = buff[read]; - if ((byte == 0 && shift != 0) || (shift >= 63 && byte > 1)) { - return -1; - } - - *val |= (uint64_t)(byte & 0x7f) << shift; - - /* If there is no next */ - if ((byte & 0x80) == 0) { - finished_ok = 1; - break; - } - } - return finished_ok ? (int)read + 1 : -2; -} diff --git a/trezor-crypto/crypto/monero/serialize.h b/trezor-crypto/crypto/monero/serialize.h deleted file mode 100644 index 8d3499007fa..00000000000 --- a/trezor-crypto/crypto/monero/serialize.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Created by Dusan Klinec on 02/05/2018. -// - -#ifndef TREZOR_XMR_SERIALIZE_H -#define TREZOR_XMR_SERIALIZE_H - -#include -#include - -int xmr_size_varint(uint64_t num); -int xmr_write_varint(uint8_t *buff, size_t buff_size, uint64_t num); -int xmr_read_varint(uint8_t *buff, size_t buff_size, uint64_t *val); - -#endif // TREZOR_XMR_SERIALIZE_H diff --git a/trezor-crypto/crypto/monero/xmr.c b/trezor-crypto/crypto/monero/xmr.c deleted file mode 100644 index 45f7ea05223..00000000000 --- a/trezor-crypto/crypto/monero/xmr.c +++ /dev/null @@ -1,143 +0,0 @@ -// -// Created by Dusan Klinec on 10/05/2018. -// - -#include "xmr.h" -#include "int-util.h" -#include -#include "serialize.h" - -const ge25519 ALIGN(16) xmr_h = { - {0x1861ec7, 0x1ceac77, 0x2f11626, 0x1f261d3, 0x346107c, 0x06d8c4a, - 0x254201d, 0x1675c09, 0x1301c3f, 0x0211d73}, - {0x326feb4, 0x12e30cc, 0x0cf54b4, 0x1117305, 0x318f5d5, 0x06cf754, - 0x2e578a1, 0x1daf058, 0x34430a1, 0x04410e9}, - {0x0fde4d2, 0x0774049, 0x22ca951, 0x05aec2b, 0x07a36a5, 0x1394f13, - 0x3c5385c, 0x1adb924, 0x2b6c581, 0x0a55fa4}, - {0x24517f7, 0x05ee936, 0x3acf5d9, 0x14b08aa, 0x3363738, 0x1051745, - 0x360601e, 0x0f3f2c9, 0x1ead2cd, 0x1d3e3df}}; - -void ge25519_set_xmr_h(ge25519 *r) { ge25519_copy(r, &xmr_h); } - -void xmr_random_scalar(bignum256modm m) { - unsigned char buff[32] = {0}; - random_buffer(buff, sizeof(buff)); - expand256_modm(m, buff, sizeof(buff)); -} - -void xmr_fast_hash(uint8_t *hash, const void *data, size_t length) { - hasher_Raw(HASHER_SHA3K, data, length, hash); -} - -void xmr_hasher_init(Hasher *hasher) { hasher_Init(hasher, HASHER_SHA3K); } - -void xmr_hasher_update(Hasher *hasher, const void *data, size_t length) { - hasher_Update(hasher, data, length); -} - -void xmr_hasher_final(Hasher *hasher, uint8_t *hash) { - hasher_Final(hasher, hash); -} - -void xmr_hasher_copy(Hasher *dst, const Hasher *src) { - memcpy(dst, src, sizeof(Hasher)); -} - -void xmr_hash_to_scalar(bignum256modm r, const void *data, size_t length) { - uint8_t hash[HASHER_DIGEST_LENGTH] = {0}; - hasher_Raw(HASHER_SHA3K, data, length, hash); - expand256_modm(r, hash, HASHER_DIGEST_LENGTH); -} - -void xmr_hash_to_ec(ge25519 *P, const void *data, size_t length) { - ge25519 point2 = {0}; - uint8_t hash[HASHER_DIGEST_LENGTH] = {0}; - hasher_Raw(HASHER_SHA3K, data, length, hash); - - ge25519_fromfe_frombytes_vartime(&point2, hash); - ge25519_mul8(P, &point2); -} - -void xmr_derivation_to_scalar(bignum256modm s, const ge25519 *p, - uint32_t output_index) { - uint8_t buff[32 + 8] = {0}; - ge25519_pack(buff, p); - int written = xmr_write_varint(buff + 32, 8, output_index); - xmr_hash_to_scalar(s, buff, 32u + written); -} - -void xmr_generate_key_derivation(ge25519 *r, const ge25519 *A, - const bignum256modm b) { - ge25519 bA = {0}; - ge25519_scalarmult(&bA, A, b); - ge25519_mul8(r, &bA); -} - -void xmr_derive_private_key(bignum256modm s, const ge25519 *deriv, uint32_t idx, - const bignum256modm base) { - xmr_derivation_to_scalar(s, deriv, idx); - add256_modm(s, s, base); -} - -void xmr_derive_public_key(ge25519 *r, const ge25519 *deriv, uint32_t idx, - const ge25519 *base) { - bignum256modm s = {0}; - ge25519 p2 = {0}; - - xmr_derivation_to_scalar(s, deriv, idx); - ge25519_scalarmult_base_niels(&p2, ge25519_niels_base_multiples, s); - ge25519_add(r, base, &p2, 0); -} - -void xmr_add_keys2(ge25519 *r, const bignum256modm a, const bignum256modm b, - const ge25519 *B) { - // aG + bB, G is basepoint - ge25519 aG = {0}, bB = {0}; - ge25519_scalarmult_base_niels(&aG, ge25519_niels_base_multiples, a); - ge25519_scalarmult(&bB, B, b); - ge25519_add(r, &aG, &bB, 0); -} - -void xmr_add_keys2_vartime(ge25519 *r, const bignum256modm a, - const bignum256modm b, const ge25519 *B) { - // aG + bB, G is basepoint - ge25519_double_scalarmult_vartime(r, B, b, a); -} - -void xmr_add_keys3(ge25519 *r, const bignum256modm a, const ge25519 *A, - const bignum256modm b, const ge25519 *B) { - // aA + bB - ge25519 aA = {0}, bB = {0}; - ge25519_scalarmult(&aA, A, a); - ge25519_scalarmult(&bB, B, b); - ge25519_add(r, &aA, &bB, 0); -} - -void xmr_add_keys3_vartime(ge25519 *r, const bignum256modm a, const ge25519 *A, - const bignum256modm b, const ge25519 *B) { - // aA + bB - ge25519_double_scalarmult_vartime2(r, A, a, B, b); -} - -void xmr_get_subaddress_secret_key(bignum256modm r, uint32_t major, - uint32_t minor, const bignum256modm m) { - const char prefix[] = "SubAddr"; - unsigned char buff[32] = {0}; - contract256_modm(buff, m); - - char data[sizeof(prefix) + sizeof(buff) + 2 * sizeof(uint32_t)] = {0}; - memcpy(data, prefix, sizeof(prefix)); - memcpy(data + sizeof(prefix), buff, sizeof(buff)); - memcpy(data + sizeof(prefix) + sizeof(buff), &major, sizeof(uint32_t)); - memcpy(data + sizeof(prefix) + sizeof(buff) + sizeof(uint32_t), &minor, - sizeof(uint32_t)); - - xmr_hash_to_scalar(r, data, sizeof(data)); -} - -void xmr_gen_c(ge25519 *r, const bignum256modm a, uint64_t amount) { - // C = aG + bH - bignum256modm b = {0}; - set256_modm(b, amount); - xmr_add_keys2(r, a, b, &xmr_h); -} diff --git a/trezor-crypto/crypto/monero/xmr.h b/trezor-crypto/crypto/monero/xmr.h deleted file mode 100644 index 18654126465..00000000000 --- a/trezor-crypto/crypto/monero/xmr.h +++ /dev/null @@ -1,76 +0,0 @@ -// -// Created by Dusan Klinec on 10/05/2018. -// - -#ifndef TREZOR_CRYPTO_XMR_H -#define TREZOR_CRYPTO_XMR_H - -#include -#include - -extern const ge25519 ALIGN(16) xmr_h; - -typedef unsigned char xmr_key_t[32]; - -typedef struct xmr_ctkey { - xmr_key_t dest; - xmr_key_t mask; -} xmr_ctkey_t; - -/* sets H point to r */ -void ge25519_set_xmr_h(ge25519 *r); - -/* random scalar value */ -void xmr_random_scalar(bignum256modm m); - -/* cn_fast_hash */ -void xmr_fast_hash(uint8_t *hash, const void *data, size_t length); - -/* incremental hashing wrappers */ -void xmr_hasher_init(Hasher *hasher); -void xmr_hasher_update(Hasher *hasher, const void *data, size_t length); -void xmr_hasher_final(Hasher *hasher, uint8_t *hash); -void xmr_hasher_copy(Hasher *dst, const Hasher *src); - -/* H_s(buffer) */ -void xmr_hash_to_scalar(bignum256modm r, const void *data, size_t length); - -/* H_p(buffer) */ -void xmr_hash_to_ec(ge25519 *P, const void *data, size_t length); - -/* derivation to scalar value */ -void xmr_derivation_to_scalar(bignum256modm s, const ge25519 *p, - uint32_t output_index); - -/* derivation */ -void xmr_generate_key_derivation(ge25519 *r, const ge25519 *A, - const bignum256modm b); - -/* H_s(derivation || varint(output_index)) + base */ -void xmr_derive_private_key(bignum256modm s, const ge25519 *deriv, uint32_t idx, - const bignum256modm base); - -/* H_s(derivation || varint(output_index))G + base */ -void xmr_derive_public_key(ge25519 *r, const ge25519 *deriv, uint32_t idx, - const ge25519 *base); - -/* aG + bB, G is basepoint */ -void xmr_add_keys2(ge25519 *r, const bignum256modm a, const bignum256modm b, - const ge25519 *B); -void xmr_add_keys2_vartime(ge25519 *r, const bignum256modm a, - const bignum256modm b, const ge25519 *B); - -/* aA + bB */ -void xmr_add_keys3(ge25519 *r, const bignum256modm a, const ge25519 *A, - const bignum256modm b, const ge25519 *B); -void xmr_add_keys3_vartime(ge25519 *r, const bignum256modm a, const ge25519 *A, - const bignum256modm b, const ge25519 *B); - -/* subaddress secret */ -void xmr_get_subaddress_secret_key(bignum256modm r, uint32_t major, - uint32_t minor, const bignum256modm m); - -/* Generates Pedersen commitment C = aG + bH */ -void xmr_gen_c(ge25519 *r, const bignum256modm a, uint64_t amount); - -#endif // TREZOR_CRYPTO_XMR_H diff --git a/trezor-crypto/crypto/nano.c b/trezor-crypto/crypto/nano.c deleted file mode 100644 index 54f4968d1ef..00000000000 --- a/trezor-crypto/crypto/nano.c +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Copyright (c) 2019 Mart Roosmaa - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, E1PRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include - -#include - -const char *BASE32_ALPHABET_NANO = "13456789abcdefghijkmnopqrstuwxyz"; - -#define NANO_ADDRESS_BASE_LENGTH 60 -#define NANO_CHECKSUM_LEN 5 - -typedef union { - uint8_t bytes[40]; - struct { - uint8_t padding[3]; - ed25519_public_key public_key; - uint8_t checksum[NANO_CHECKSUM_LEN]; - } data; -} nano_address_raw; - -typedef union { - char chars[65]; - struct { - char padding[4]; // 1111 - char address[NANO_ADDRESS_BASE_LENGTH]; - char terminator; // \0 - } data; -} nano_address_encoded; - -size_t nano_get_address( - const ed25519_public_key public_key, - const char *prefix, - const size_t prefix_len, - char *out, - size_t out_len -) { - if (out_len < prefix_len + NANO_ADDRESS_BASE_LENGTH + 1) { - return 0; - } - - // Construct raw address which is going to be base32 encoded - nano_address_raw raw; - memset(&raw, 0, sizeof(nano_address_raw)); - - uint8_t checksum[NANO_CHECKSUM_LEN]; - blake2b_state hash; - tc_blake2b_Init(&hash, NANO_CHECKSUM_LEN); - tc_blake2b_Update(&hash, public_key, sizeof(ed25519_public_key)); - tc_blake2b_Final(&hash, checksum, NANO_CHECKSUM_LEN); - - for (int i = 0; i < NANO_CHECKSUM_LEN; i++) { - raw.data.checksum[NANO_CHECKSUM_LEN - (i + 1)] = checksum[i]; - } - memcpy(raw.data.public_key, public_key, sizeof(ed25519_public_key)); - - // Encode the address into a buffer and compose the final output - nano_address_encoded encoded; - memset(&encoded, 0, sizeof(nano_address_encoded)); - char *ret = base32_encode( - raw.bytes, sizeof(nano_address_raw), - encoded.chars, sizeof(encoded.chars), - BASE32_ALPHABET_NANO); - if (ret == NULL) { - return 0; - } - - size_t w = 0; - memcpy(&out[w], prefix, prefix_len); - w += prefix_len; - memcpy(&out[w], encoded.data.address, NANO_ADDRESS_BASE_LENGTH); - w += NANO_ADDRESS_BASE_LENGTH; - out[w] = 0; - w += 1; - return w; -} - -bool nano_validate_address( - const char *prefix, - const size_t prefix_len, - const char *address, - const size_t address_len, - ed25519_public_key out_public_key -) { - if (address_len != prefix_len + NANO_ADDRESS_BASE_LENGTH) { - return false; - } - - // Validate that the prefix matches - if (memcmp(address, prefix, prefix_len) != 0) { - return false; - } - - // Try to decode the address - nano_address_encoded encoded; - memcpy(encoded.data.padding, "1111", sizeof(encoded.data.padding)); - memcpy(encoded.data.address, &address[prefix_len], NANO_ADDRESS_BASE_LENGTH); - encoded.data.terminator = '\0'; - - nano_address_raw raw; - memset(&raw, 0, sizeof(nano_address_raw)); - uint8_t *ret = base32_decode( - encoded.chars, strlen(encoded.chars), - raw.bytes, sizeof(nano_address_raw), - BASE32_ALPHABET_NANO - ); - if (ret == NULL) { - return false; - } - - // Validate the checksum - uint8_t checksum[NANO_CHECKSUM_LEN]; - blake2b_state hash; - tc_blake2b_Init(&hash, NANO_CHECKSUM_LEN); - tc_blake2b_Update(&hash, raw.data.public_key, sizeof(ed25519_public_key)); - tc_blake2b_Final(&hash, checksum, NANO_CHECKSUM_LEN); - - for (int i = 0; i < NANO_CHECKSUM_LEN; i++) { - if (raw.data.checksum[NANO_CHECKSUM_LEN - (i + 1)] != checksum[i]) { - return false; - } - } - - // Output the public key if the caller is interested in it - if (out_public_key != NULL) { - memcpy(out_public_key, raw.data.public_key, sizeof(ed25519_public_key)); - } - - return true; -} diff --git a/trezor-crypto/crypto/nem.c b/trezor-crypto/crypto/nem.c deleted file mode 100644 index 66de5cfa9e6..00000000000 --- a/trezor-crypto/crypto/nem.c +++ /dev/null @@ -1,507 +0,0 @@ -/** - * Copyright (c) 2017 Saleem Rashid - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, E1PRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include - -#include - -#include -#include -#include -#include -#include - -#define CAN_WRITE(NEEDED) ((ctx->offset + (NEEDED)) <= ctx->size) - -#define SERIALIZE_U32(DATA) \ - do { \ - if (!nem_write_u32(ctx, (DATA))) return false; \ - } while (0) -#define SERIALIZE_U64(DATA) \ - do { \ - if (!nem_write_u64(ctx, (DATA))) return false; \ - } while (0) -#define SERIALIZE_TAGGED(DATA, LENGTH) \ - do { \ - if (!nem_write_tagged(ctx, (DATA), (LENGTH))) return false; \ - } while (0) - -const char *nem_network_name(uint8_t network) { - switch (network) { - case NEM_NETWORK_MAINNET: - return "NEM Mainnet"; - case NEM_NETWORK_TESTNET: - return "NEM Testnet"; - case NEM_NETWORK_MIJIN: - return "Mijin"; - default: - return NULL; - } -} - -static inline bool nem_write_checked(nem_transaction_ctx *ctx, - const uint8_t *data, uint32_t length) { - if (!CAN_WRITE(length)) { - return false; - } - - memcpy(&ctx->buffer[ctx->offset], data, length); - ctx->offset += length; - return true; -} - -static inline bool nem_write_u32(nem_transaction_ctx *ctx, uint32_t data) { - if (!CAN_WRITE(4)) { - return false; - } - - ctx->buffer[ctx->offset++] = (data >> 0) & 0xff; - ctx->buffer[ctx->offset++] = (data >> 8) & 0xff; - ctx->buffer[ctx->offset++] = (data >> 16) & 0xff; - ctx->buffer[ctx->offset++] = (data >> 24) & 0xff; - - return true; -} - -static inline bool nem_write_u64(nem_transaction_ctx *ctx, uint64_t data) { - SERIALIZE_U32((data >> 0) & 0xffffffff); - SERIALIZE_U32((data >> 32) & 0xffffffff); - - return true; -} - -static inline bool nem_write_tagged(nem_transaction_ctx *ctx, - const uint8_t *data, uint32_t length) { - SERIALIZE_U32(length); - - return nem_write_checked(ctx, data, length); -} - -static inline bool nem_write_mosaic_str(nem_transaction_ctx *ctx, - const char *name, const char *value) { - uint32_t name_length = strlen(name); - uint32_t value_length = strlen(value); - - SERIALIZE_U32(sizeof(uint32_t) + name_length + sizeof(uint32_t) + - value_length); - SERIALIZE_TAGGED((const uint8_t *)name, name_length); - SERIALIZE_TAGGED((const uint8_t *)value, value_length); - - return true; -} - -static inline bool nem_write_mosaic_bool(nem_transaction_ctx *ctx, - const char *name, bool value) { - return nem_write_mosaic_str(ctx, name, value ? "true" : "false"); -} - -static inline bool nem_write_mosaic_u64(nem_transaction_ctx *ctx, - const char *name, uint64_t value) { - char buffer[21] = {0}; - - if (bn_format_uint64(value, NULL, NULL, 0, 0, false, buffer, - sizeof(buffer)) == 0) { - return false; - } - - return nem_write_mosaic_str(ctx, name, buffer); -} - -void nem_get_address_raw(const ed25519_public_key public_key, uint8_t version, - uint8_t *address) { - uint8_t hash[SHA3_256_DIGEST_LENGTH] = {0}; - - /* 1. Perform 256-bit Sha3 on the public key */ - keccak_256(public_key, sizeof(ed25519_public_key), hash); - - /* 2. Perform 160-bit Ripemd of hash resulting from step 1. */ - ripemd160(hash, SHA3_256_DIGEST_LENGTH, &address[1]); - - /* 3. Prepend version byte to Ripemd hash (either 0x68 or 0x98) */ - address[0] = version; - - /* 4. Perform 256-bit Sha3 on the result, take the first four bytes as a - * checksum */ - keccak_256(address, 1 + RIPEMD160_DIGEST_LENGTH, hash); - - /* 5. Concatenate output of step 3 and the checksum from step 4 */ - memcpy(&address[1 + RIPEMD160_DIGEST_LENGTH], hash, 4); - - memzero(hash, sizeof(hash)); -} - -bool nem_get_address(const ed25519_public_key public_key, uint8_t version, - char *address) { - uint8_t pubkeyhash[NEM_ADDRESS_SIZE_RAW] = {0}; - - nem_get_address_raw(public_key, version, pubkeyhash); - - char *ret = base32_encode(pubkeyhash, sizeof(pubkeyhash), address, - NEM_ADDRESS_SIZE + 1, BASE32_ALPHABET_RFC4648); - - memzero(pubkeyhash, sizeof(pubkeyhash)); - return (ret != NULL); -} - -bool nem_validate_address_raw(const uint8_t *address, uint8_t network) { - if (!nem_network_name(network) || address[0] != network) { - return false; - } - - uint8_t hash[SHA3_256_DIGEST_LENGTH] = {0}; - - keccak_256(address, 1 + RIPEMD160_DIGEST_LENGTH, hash); - bool valid = (memcmp(&address[1 + RIPEMD160_DIGEST_LENGTH], hash, 4) == 0); - - memzero(hash, sizeof(hash)); - return valid; -} - -bool nem_validate_address(const char *address, uint8_t network) { - uint8_t pubkeyhash[NEM_ADDRESS_SIZE_RAW] = {0}; - - if (strlen(address) != NEM_ADDRESS_SIZE) { - return false; - } - - uint8_t *ret = base32_decode(address, NEM_ADDRESS_SIZE, pubkeyhash, - sizeof(pubkeyhash), BASE32_ALPHABET_RFC4648); - bool valid = (ret != NULL) && nem_validate_address_raw(pubkeyhash, network); - - memzero(pubkeyhash, sizeof(pubkeyhash)); - return valid; -} - -void nem_transaction_start(nem_transaction_ctx *ctx, - const ed25519_public_key public_key, uint8_t *buffer, - size_t size) { - memcpy(ctx->public_key, public_key, sizeof(ctx->public_key)); - - ctx->buffer = buffer; - ctx->offset = 0; - ctx->size = size; -} - -size_t nem_transaction_end(nem_transaction_ctx *ctx, - const ed25519_secret_key private_key, - ed25519_signature signature) { - if (private_key != NULL && signature != NULL) { - ed25519_sign_keccak(ctx->buffer, ctx->offset, private_key, signature); - } - - return ctx->offset; -} - -bool nem_transaction_write_common(nem_transaction_ctx *ctx, uint32_t type, - uint32_t version, uint32_t timestamp, - const ed25519_public_key signer, uint64_t fee, - uint32_t deadline) { - SERIALIZE_U32(type); - SERIALIZE_U32(version); - SERIALIZE_U32(timestamp); - SERIALIZE_TAGGED(signer, sizeof(ed25519_public_key)); - SERIALIZE_U64(fee); - SERIALIZE_U32(deadline); - - return true; -} - -bool nem_transaction_create_transfer(nem_transaction_ctx *ctx, uint8_t network, - uint32_t timestamp, - const ed25519_public_key signer, - uint64_t fee, uint32_t deadline, - const char *recipient, uint64_t amount, - const uint8_t *payload, uint32_t length, - bool encrypted, uint32_t mosaics) { - if (!signer) { - signer = ctx->public_key; - } - - if (!payload) { - length = 0; - } - - bool ret = - nem_transaction_write_common(ctx, NEM_TRANSACTION_TYPE_TRANSFER, - (uint32_t)network << 24 | (mosaics ? 2 : 1), - timestamp, signer, fee, deadline); - if (!ret) return false; - - SERIALIZE_TAGGED((const uint8_t *)recipient, NEM_ADDRESS_SIZE); - SERIALIZE_U64(amount); - - if (length) { - SERIALIZE_U32(sizeof(uint32_t) + sizeof(uint32_t) + length); - SERIALIZE_U32(encrypted ? 0x02 : 0x01); - SERIALIZE_TAGGED(payload, length); - } else { - SERIALIZE_U32(0); - } - - if (mosaics) { - SERIALIZE_U32(mosaics); - } - - return true; -} - -bool nem_transaction_write_mosaic(nem_transaction_ctx *ctx, - const char *namespace, const char *mosaic, - uint64_t quantity) { - size_t namespace_length = strlen(namespace); - size_t mosaic_length = strlen(mosaic); - size_t identifier_length = - sizeof(uint32_t) + namespace_length + sizeof(uint32_t) + mosaic_length; - - SERIALIZE_U32(sizeof(uint32_t) + sizeof(uint64_t) + identifier_length); - SERIALIZE_U32(identifier_length); - SERIALIZE_TAGGED((const uint8_t *)namespace, namespace_length); - SERIALIZE_TAGGED((const uint8_t *)mosaic, mosaic_length); - SERIALIZE_U64(quantity); - - return true; -} - -bool nem_transaction_create_multisig(nem_transaction_ctx *ctx, uint8_t network, - uint32_t timestamp, - const ed25519_public_key signer, - uint64_t fee, uint32_t deadline, - const nem_transaction_ctx *inner) { - if (!signer) { - signer = ctx->public_key; - } - - bool ret = nem_transaction_write_common(ctx, NEM_TRANSACTION_TYPE_MULTISIG, - (uint32_t)network << 24 | 1, - timestamp, signer, fee, deadline); - if (!ret) return false; - - SERIALIZE_TAGGED(inner->buffer, inner->offset); - - return true; -} - -bool nem_transaction_create_multisig_signature( - nem_transaction_ctx *ctx, uint8_t network, uint32_t timestamp, - const ed25519_public_key signer, uint64_t fee, uint32_t deadline, - const nem_transaction_ctx *inner) { - if (!signer) { - signer = ctx->public_key; - } - - bool ret = nem_transaction_write_common( - ctx, NEM_TRANSACTION_TYPE_MULTISIG_SIGNATURE, (uint32_t)network << 24 | 1, - timestamp, signer, fee, deadline); - if (!ret) return false; - - char address[NEM_ADDRESS_SIZE + 1] = {0}; - nem_get_address(inner->public_key, network, address); - - uint8_t hash[SHA3_256_DIGEST_LENGTH] = {0}; - keccak_256(inner->buffer, inner->offset, hash); - - SERIALIZE_U32(sizeof(uint32_t) + SHA3_256_DIGEST_LENGTH); - SERIALIZE_TAGGED(hash, SHA3_256_DIGEST_LENGTH); - SERIALIZE_TAGGED((const uint8_t *)address, NEM_ADDRESS_SIZE); - - return true; -} - -bool nem_transaction_create_provision_namespace( - nem_transaction_ctx *ctx, uint8_t network, uint32_t timestamp, - const ed25519_public_key signer, uint64_t fee, uint32_t deadline, - const char *namespace, const char *parent, const char *rental_sink, - uint64_t rental_fee) { - if (!signer) { - signer = ctx->public_key; - } - - bool ret = nem_transaction_write_common( - ctx, NEM_TRANSACTION_TYPE_PROVISION_NAMESPACE, - (uint32_t)network << 24 | 1, timestamp, signer, fee, deadline); - if (!ret) return false; - - if (parent) { - SERIALIZE_TAGGED((const uint8_t *)rental_sink, NEM_ADDRESS_SIZE); - SERIALIZE_U64(rental_fee); - SERIALIZE_TAGGED((const uint8_t *)namespace, strlen(namespace)); - SERIALIZE_TAGGED((const uint8_t *)parent, strlen(parent)); - } else { - SERIALIZE_TAGGED((const uint8_t *)rental_sink, NEM_ADDRESS_SIZE); - SERIALIZE_U64(rental_fee); - SERIALIZE_TAGGED((const uint8_t *)namespace, strlen(namespace)); - SERIALIZE_U32(0xffffffff); - } - - return true; -} - -bool nem_transaction_create_mosaic_creation( - nem_transaction_ctx *ctx, uint8_t network, uint32_t timestamp, - const ed25519_public_key signer, uint64_t fee, uint32_t deadline, - const char *namespace, const char *mosaic, const char *description, - uint32_t divisibility, uint64_t supply, bool mutable_supply, - bool transferable, uint32_t levy_type, uint64_t levy_fee, - const char *levy_address, const char *levy_namespace, - const char *levy_mosaic, const char *creation_sink, uint64_t creation_fee) { - if (!signer) { - signer = ctx->public_key; - } - - bool ret = nem_transaction_write_common( - ctx, NEM_TRANSACTION_TYPE_MOSAIC_CREATION, (uint32_t)network << 24 | 1, - timestamp, signer, fee, deadline); - if (!ret) return false; - - size_t namespace_length = strlen(namespace); - size_t mosaic_length = strlen(mosaic); - size_t identifier_length = - sizeof(uint32_t) + namespace_length + sizeof(uint32_t) + mosaic_length; - - // This length will be rewritten later on - nem_transaction_ctx state = {0}; - memcpy(&state, ctx, sizeof(state)); - - SERIALIZE_U32(0); - SERIALIZE_TAGGED(signer, sizeof(ed25519_public_key)); - SERIALIZE_U32(identifier_length); - SERIALIZE_TAGGED((const uint8_t *)namespace, namespace_length); - SERIALIZE_TAGGED((const uint8_t *)mosaic, mosaic_length); - SERIALIZE_TAGGED((const uint8_t *)description, strlen(description)); - SERIALIZE_U32(4); // Number of properties - - if (!nem_write_mosaic_u64(ctx, "divisibility", divisibility)) return false; - if (!nem_write_mosaic_u64(ctx, "initialSupply", supply)) return false; - if (!nem_write_mosaic_bool(ctx, "supplyMutable", mutable_supply)) - return false; - if (!nem_write_mosaic_bool(ctx, "transferable", transferable)) return false; - - if (levy_type) { - size_t levy_namespace_length = strlen(levy_namespace); - size_t levy_mosaic_length = strlen(levy_mosaic); - size_t levy_identifier_length = sizeof(uint32_t) + levy_namespace_length + - sizeof(uint32_t) + levy_mosaic_length; - - SERIALIZE_U32(sizeof(uint32_t) + sizeof(uint32_t) + NEM_ADDRESS_SIZE + - sizeof(uint32_t) + levy_identifier_length + sizeof(uint64_t)); - SERIALIZE_U32(levy_type); - SERIALIZE_TAGGED((const uint8_t *)levy_address, NEM_ADDRESS_SIZE); - SERIALIZE_U32(levy_identifier_length); - SERIALIZE_TAGGED((const uint8_t *)levy_namespace, levy_namespace_length); - SERIALIZE_TAGGED((const uint8_t *)levy_mosaic, levy_mosaic_length); - SERIALIZE_U64(levy_fee); - } else { - SERIALIZE_U32(0); - } - - // Rewrite length - nem_write_u32(&state, ctx->offset - state.offset - sizeof(uint32_t)); - - SERIALIZE_TAGGED((const uint8_t *)creation_sink, NEM_ADDRESS_SIZE); - SERIALIZE_U64(creation_fee); - - return true; -} - -bool nem_transaction_create_mosaic_supply_change( - nem_transaction_ctx *ctx, uint8_t network, uint32_t timestamp, - const ed25519_public_key signer, uint64_t fee, uint32_t deadline, - const char *namespace, const char *mosaic, uint32_t type, uint64_t delta) { - if (!signer) { - signer = ctx->public_key; - } - - bool ret = nem_transaction_write_common( - ctx, NEM_TRANSACTION_TYPE_MOSAIC_SUPPLY_CHANGE, - (uint32_t)network << 24 | 1, timestamp, signer, fee, deadline); - if (!ret) return false; - - size_t namespace_length = strlen(namespace); - size_t mosaic_length = strlen(mosaic); - size_t identifier_length = - sizeof(uint32_t) + namespace_length + sizeof(uint32_t) + mosaic_length; - - SERIALIZE_U32(identifier_length); - SERIALIZE_TAGGED((const uint8_t *)namespace, namespace_length); - SERIALIZE_TAGGED((const uint8_t *)mosaic, mosaic_length); - SERIALIZE_U32(type); - SERIALIZE_U64(delta); - - return true; -} - -bool nem_transaction_create_aggregate_modification( - nem_transaction_ctx *ctx, uint8_t network, uint32_t timestamp, - const ed25519_public_key signer, uint64_t fee, uint32_t deadline, - uint32_t modifications, bool relative_change) { - if (!signer) { - signer = ctx->public_key; - } - - bool ret = nem_transaction_write_common( - ctx, NEM_TRANSACTION_TYPE_AGGREGATE_MODIFICATION, - (uint32_t)network << 24 | (relative_change ? 2 : 1), timestamp, signer, - fee, deadline); - if (!ret) return false; - - SERIALIZE_U32(modifications); - - return true; -} - -bool nem_transaction_write_cosignatory_modification( - nem_transaction_ctx *ctx, uint32_t type, - const ed25519_public_key cosignatory) { - SERIALIZE_U32(sizeof(uint32_t) + sizeof(uint32_t) + - sizeof(ed25519_public_key)); - SERIALIZE_U32(type); - SERIALIZE_TAGGED(cosignatory, sizeof(ed25519_public_key)); - - return true; -} - -bool nem_transaction_write_minimum_cosignatories(nem_transaction_ctx *ctx, - int32_t relative_change) { - SERIALIZE_U32(sizeof(uint32_t)); - SERIALIZE_U32((uint32_t)relative_change); - - return true; -} - -bool nem_transaction_create_importance_transfer( - nem_transaction_ctx *ctx, uint8_t network, uint32_t timestamp, - const ed25519_public_key signer, uint64_t fee, uint32_t deadline, - uint32_t mode, const ed25519_public_key remote) { - if (!signer) { - signer = ctx->public_key; - } - - bool ret = nem_transaction_write_common( - ctx, NEM_TRANSACTION_TYPE_IMPORTANCE_TRANSFER, - (uint32_t)network << 24 | 1, timestamp, signer, fee, deadline); - if (!ret) return false; - - SERIALIZE_U32(mode); - SERIALIZE_TAGGED(remote, sizeof(ed25519_public_key)); - - return true; -} diff --git a/trezor-crypto/crypto/nist256p1.c b/trezor-crypto/crypto/nist256p1.c deleted file mode 100644 index 3d7246d228e..00000000000 --- a/trezor-crypto/crypto/nist256p1.c +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright (c) 2013-2014 Tomas Dzetkulic - * Copyright (c) 2013-2014 Pavol Rusnak - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include - -const ecdsa_curve nist256p1 = { - /* .prime */ {/*.val =*/{0x1fffffff, 0x1fffffff, 0x1fffffff, 0x000001ff, - 0x00000000, 0x00000000, 0x00040000, 0x1fe00000, - 0xffffff}}, - - /* G */ - {/*.x =*/{/*.val =*/{0x1898c296, 0x0509ca2e, 0x1acce83d, 0x06fb025b, - 0x040f2770, 0x1372b1d2, 0x091fe2f3, 0x1e5c2588, - 0x6b17d1}}, - /*.y =*/{/*.val =*/{0x17bf51f5, 0x1db20341, 0x0c57b3b2, 0x1c66aed6, - 0x19e162bc, 0x15a53e07, 0x1e6e3b9f, 0x1c5fc34f, - 0x4fe342}}}, - - /* order */ - {/*.val =*/{0x1c632551, 0x1dce5617, 0x05e7a13c, 0x0df55b4e, 0x1ffffbce, - 0x1fffffff, 0x0003ffff, 0x1fe00000, 0xffffff}}, - - /* order_half */ - {/*.val =*/{0x1e3192a8, 0x0ee72b0b, 0x02f3d09e, 0x06faada7, 0x1ffffde7, - 0x1fffffff, 0x0001ffff, 0x1ff00000, 0x7fffff}}, - - /* a */ -3, - - /* b */ - {/*.val =*/{0x07d2604b, 0x1e71e1f1, 0x14ec3d8e, 0x1a0d6198, 0x086bc651, - 0x1eaabb4c, 0x0f9ecfae, 0x1b154752, 0x005ac635}} - -#if USE_PRECOMPUTED_CP - , - /* cp */ - { -#include "nist256p1.table" - } -#endif -}; - -const curve_info nist256p1_info = { - .bip32_name = "Nist256p1 seed", - .params = &nist256p1, - .hasher_base58 = HASHER_SHA2D, - .hasher_sign = HASHER_SHA2D, - .hasher_pubkey = HASHER_SHA2_RIPEMD, - .hasher_script = HASHER_SHA2, -}; diff --git a/trezor-crypto/crypto/nist256p1.table b/trezor-crypto/crypto/nist256p1.table deleted file mode 100644 index 6be01b4d625..00000000000 --- a/trezor-crypto/crypto/nist256p1.table +++ /dev/null @@ -1,1664 +0,0 @@ - { - /* 1*16^0*G: */ - {{{0x1898c296, 0x0509ca2e, 0x1acce83d, 0x06fb025b, 0x040f2770, 0x1372b1d2, 0x091fe2f3, 0x1e5c2588, 0x6b17d1}}, - {{0x17bf51f5, 0x1db20341, 0x0c57b3b2, 0x1c66aed6, 0x19e162bc, 0x15a53e07, 0x1e6e3b9f, 0x1c5fc34f, 0x4fe342}}}, - /* 3*16^0*G: */ - {{{0x06e7fd6c, 0x1a0b30de, 0x0b6a617e, 0x0d6e43df, 0x1f165e6c, 0x17ca8ea5, 0x091323df, 0x1a34c661, 0x5ecbe4}}, - {{0x027d5032, 0x13cd893d, 0x13ee0f66, 0x15606c70, 0x0a2ecd82, 0x03670d32, 0x1df8dd2c, 0x0189331f, 0x873464}}}, - /* 5*16^0*G: */ - {{{0x03d033ed, 0x0aaa506e, 0x16f94908, 0x1905fa3e, 0x08fdfef8, 0x042b0433, 0x034b5e13, 0x0f4a2a28, 0x51590b}}, - {{0x1da16da4, 0x0e85da27, 0x16022234, 0x025e01a9, 0x079260d0, 0x1f9b5fc5, 0x09f62b86, 0x1512094e, 0xe0c17d}}}, - /* 7*16^0*G: */ - {{{0x1187b2a3, 0x00314381, 0x03fbd6cc, 0x13f17150, 0x1fb607ef, 0x18333e00, 0x0d1896ec, 0x0df417ef, 0x8e533b}}, - {{0x01f400b4, 0x0af0d436, 0x0106c871, 0x0e6c6796, 0x1900053c, 0x0fc1d37a, 0x00d9b41a, 0x17bc0663, 0x73eb1d}}}, - /* 9*16^0*G: */ - {{{0x10949ee0, 0x1cf4525c, 0x1b7e2cf5, 0x15971858, 0x1f8729e0, 0x1c6a8eb8, 0x0dc61e24, 0x16dfdbe1, 0xea68d7}}, - {{0x0dd048fa, 0x02d11252, 0x17a08ffa, 0x029fd549, 0x0a0c84d7, 0x054b2547, 0x139e1c05, 0x192e593f, 0x2a2744}}}, - /* 11*16^0*G: */ - {{{0x14bc21d1, 0x199c8e9b, 0x14122fd0, 0x085da04a, 0x01cda167, 0x1bced861, 0x116418e0, 0x16f10769, 0x3ed113}}, - {{0x082a3740, 0x17c777e7, 0x062276b8, 0x1a09b4bd, 0x0c68a090, 0x01d7d27a, 0x02889321, 0x13599899, 0x909920}}}, - /* 13*16^0*G: */ - {{{0x06072c01, 0x070aecea, 0x1ab562a6, 0x1c5096cb, 0x0e2fc792, 0x0ef96c2f, 0x05698601, 0x0f5c1589, 0x177c83}}, - {{0x0fc7bfd8, 0x021ddf17, 0x1ed37ce7, 0x1c298743, 0x14e7226e, 0x08d6da07, 0x15628902, 0x19a9d7d4, 0x63bb58}}}, - /* 15*16^0*G: */ - {{{0x059b9d5f, 0x1b34631f, 0x0e83bc58, 0x075f25bc, 0x08265ae0, 0x1bc4ccc4, 0x0b9eb7ec, 0x18d2e357, 0xf0454d}}, - {{0x0d034f36, 0x1f2ce6f0, 0x0d7e8fd1, 0x16439ceb, 0x043e62a3, 0x0a728fcb, 0x147d3996, 0x1c6b25c5, 0xb5b93e}}} - }, - { - /* 1*16^1*G: */ - {{{0x01277c6e, 0x0f5a3c3f, 0x1b280e29, 0x10725dfe, 0x0315fcd2, 0x0e314c1b, 0x06162e08, 0x02714d68, 0x76a94d}}, - {{0x0b8c5110, 0x14eeeb92, 0x11e2ea83, 0x1340081f, 0x08720859, 0x10daf08f, 0x1839b2c2, 0x0c2683e4, 0xa985fe}}}, - /* 3*16^1*G: */ - {{{0x1e4536ca, 0x1fdcee34, 0x0806986a, 0x1d252c14, 0x0cda11c2, 0x1a2df038, 0x07b23339, 0x01c924a7, 0x9482fb}}, - {{0x158cc1c8, 0x1d6c31df, 0x01efeec7, 0x1abc52ae, 0x14e63f63, 0x11c653a9, 0x1fe46975, 0x14e8be2a, 0x351d9c}}}, - /* 5*16^1*G: */ - {{{0x15ecff13, 0x0d8ed714, 0x1418cf12, 0x0c9439b7, 0x01eeb637, 0x0d28a984, 0x04656e0d, 0x182f5d26, 0xb2e1b7}}, - {{0x12187d44, 0x1dd24c4d, 0x17a8b9a8, 0x1435302f, 0x158c387d, 0x10d1556b, 0x0f33c8ce, 0x0262747d, 0xe6c044}}}, - /* 7*16^1*G: */ - {{{0x0ef87286, 0x05dd99a7, 0x08c2790e, 0x07d0fbf8, 0x0ee34070, 0x0bbee5e3, 0x0f0b2cd0, 0x05cfe36d, 0xb43346}}, - {{0x041496d9, 0x16023fb4, 0x0b3a92de, 0x1c4100d4, 0x12e6f9dc, 0x073960b4, 0x17f8e0fd, 0x0a4dda56, 0xa0da54}}}, - /* 9*16^1*G: */ - {{{0x19794381, 0x1cc35ad8, 0x0c31d806, 0x02b10ebd, 0x07b19189, 0x1482daab, 0x0933061b, 0x19c9a7b4, 0xa47420}}, - {{0x02769c52, 0x0b671d3f, 0x1dab1e1a, 0x0b811b73, 0x07afff3a, 0x1c9b0ccb, 0x1f839cf7, 0x1bef7c37, 0x2ebbff}}}, - /* 11*16^1*G: */ - {{{0x09a5185a, 0x03a23324, 0x179bcafb, 0x0e15913e, 0x1a3195b8, 0x0a009586, 0x05217fb3, 0x03373a12, 0x5ba7a}}, - {{0x01de3a4e, 0x0a1e4470, 0x016b8005, 0x0c441792, 0x1d48e760, 0x011f1271, 0x025b919e, 0x16bd0583, 0x30e0d2}}}, - /* 13*16^1*G: */ - {{{0x199b85fe, 0x151cc5af, 0x0e977f56, 0x1dfd355c, 0x1e0b79c0, 0x06c8c041, 0x0bf5a5e6, 0x193a1bfc, 0xb29f74}}, - {{0x0da604dc, 0x1d3214bb, 0x1d67a036, 0x1ec7a617, 0x1c6d92de, 0x18974b6b, 0x1e175741, 0x1874d852, 0xa3ce5b}}}, - /* 15*16^1*G: */ - {{{0x07e030ef, 0x1b3a2b74, 0x1543a589, 0x16560af9, 0x02b08576, 0x1f81f924, 0x13d41db2, 0x15517237, 0x99299b}}, - {{0x18d9aa9a, 0x0c735bee, 0x10021113, 0x0258933e, 0x0aa7957c, 0x0080d367, 0x1862fcce, 0x1a6667f1, 0x5cc2e3}}} - }, - { - /* 1*16^2*G: */ - {{{0x12d0441b, 0x0d971af8, 0x1a95930b, 0x1a16e21a, 0x1ed61190, 0x08a94301, 0x19661fff, 0x14760122, 0x34a2d4}}, - {{0x1e93b146, 0x01273c22, 0x1776076d, 0x0ecd73bd, 0x17fc0e7d, 0x1882373b, 0x0f08af29, 0x0d4a743c, 0xbeaaed}}}, - /* 3*16^2*G: */ - {{{0x1da08424, 0x0725719d, 0x1d912a77, 0x1f6f56e9, 0x123ed3ab, 0x02ffd4ae, 0x06a86d63, 0x00f5b86b, 0xa98b0a}}, - {{0x0831800c, 0x10943d07, 0x17d60b4d, 0x17d01741, 0x11320752, 0x0f46470e, 0x0d9b94c6, 0x08e44933, 0x4754c6}}}, - /* 5*16^2*G: */ - {{{0x083c2d6a, 0x096bf2f4, 0x0f4a6ced, 0x1f231b03, 0x0696dd22, 0x15bc642d, 0x0b17486d, 0x013911b5, 0xa034f0}}, - {{0x02c30e72, 0x019b7796, 0x099f9f3d, 0x0c06be2c, 0x0eb06be0, 0x1c2a2e76, 0x107ec336, 0x06d95133, 0xfe1b00}}}, - /* 7*16^2*G: */ - {{{0x08be8c78, 0x0b528ee9, 0x1e4f935c, 0x12d9f056, 0x0e9a14a0, 0x115a04b1, 0x100681db, 0x1fe633a8, 0xb52226}}, - {{0x11686903, 0x019b3e52, 0x0d63fcc8, 0x1eff91cb, 0x0851a819, 0x1b34bf49, 0x1503286c, 0x193ea73e, 0x701ee0}}}, - /* 9*16^2*G: */ - {{{0x0058b5a4, 0x0207f205, 0x0c65e6dc, 0x08c9f0f2, 0x04713de4, 0x07667e66, 0x16cf65a3, 0x0c30d1c0, 0xa3e388}}, - {{0x07113aef, 0x19836c87, 0x1c68a523, 0x1d08b76e, 0x1e68f3ce, 0x1359d74b, 0x04b0e123, 0x110fbe1a, 0xcfd457}}}, - /* 11*16^2*G: */ - {{{0x04a08314, 0x02faf648, 0x11f7fde8, 0x1c6db65b, 0x1197a82f, 0x055cd110, 0x14e4ccf3, 0x094263f4, 0x209954}}, - {{0x07573ccc, 0x1fc64e2f, 0x1f3fa5b9, 0x0dcb4e6d, 0x048dfd40, 0x1472e5c8, 0x0c6243ee, 0x1fa55467, 0xa5cf3d}}}, - /* 13*16^2*G: */ - {{{0x09078c18, 0x017fca57, 0x1b7a4a54, 0x018795dd, 0x1d39b99e, 0x1739556f, 0x11c0c23d, 0x0275981d, 0x751d65}}, - {{0x0ece0d67, 0x0c6b74ae, 0x1816b1e6, 0x12918397, 0x02a4ef00, 0x05d7b515, 0x056214d5, 0x1fb94948, 0xba9c3f}}}, - /* 15*16^2*G: */ - {{{0x1e86d900, 0x167e6ae4, 0x08f6d124, 0x1dc13a2f, 0x03b36405, 0x0184501d, 0x00445ed2, 0x044b88f0, 0x5be0f5}}, - {{0x158a5755, 0x066eb4df, 0x0cd3d6b1, 0x02e4a483, 0x1423df92, 0x11d273da, 0x108b4d1f, 0x1cdbfe58, 0x83d1d9}}} - }, - { - /* 1*16^3*G: */ - {{{0x0245573f, 0x03bf46d5, 0x1f42993c, 0x0cede5e9, 0x16bd2508, 0x04b39736, 0x193665de, 0x1a59e0d3, 0xe716ae}}, - {{0x069218d1, 0x02fe135a, 0x01f26cd4, 0x1a1be5f4, 0x1a851d13, 0x183343dc, 0x0aad644a, 0x1cd29f8e, 0x353663}}}, - /* 3*16^3*G: */ - {{{0x057008ac, 0x1cc3c983, 0x0a7b7edf, 0x1c3d9dc6, 0x1a22d19c, 0x0ac5260e, 0x13a19395, 0x11adea0f, 0xd2bf89}}, - {{0x127beb55, 0x088a9a53, 0x1fc1e620, 0x19c2156b, 0x10c56679, 0x0aed9fd4, 0x1ea8fe06, 0x196b4d6e, 0x69c0b1}}}, - /* 5*16^3*G: */ - {{{0x182f1580, 0x086be54b, 0x080fadff, 0x09bbdc48, 0x064f8b0e, 0x0b9c8f98, 0x19821fe9, 0x09dcb079, 0x4d8830}}, - {{0x1616edd7, 0x007d8b6a, 0x1ac78443, 0x19477ad1, 0x16518aaf, 0x00044c7e, 0x0db3a35a, 0x0e13560c, 0x28a94e}}}, - /* 7*16^3*G: */ - {{{0x00c38e11, 0x1c261ed7, 0x1b89ae97, 0x0c8437d4, 0x1af9e715, 0x1cc90172, 0x1bacc99c, 0x04f91f81, 0x17c727}}, - {{0x0e90decd, 0x0c1b35dd, 0x0336b8f3, 0x07328b4c, 0x1802e7e5, 0x17e67480, 0x0cf10180, 0x1e9fccc9, 0x95c5a4}}}, - /* 9*16^3*G: */ - {{{0x032f36ea, 0x0aafec57, 0x0d5070f6, 0x1e8d4e52, 0x0b8b3cd4, 0x18e7e426, 0x02a2118e, 0x15588e8a, 0x7e03fd}}, - {{0x0a68b8af, 0x16611fe4, 0x140275a2, 0x1d2f3498, 0x0af99419, 0x158b80c2, 0x03699a39, 0x1e240245, 0xdcf6d5}}}, - /* 11*16^3*G: */ - {{{0x1953baa9, 0x01f29536, 0x18637a35, 0x0b5d0c93, 0x0b214261, 0x1d60ee50, 0x1c1d858d, 0x0d096379, 0x8ce6da}}, - {{0x152689e5, 0x073ae147, 0x0a4d075e, 0x0730d76d, 0x1b56807f, 0x1de48030, 0x082f9a6c, 0x171f5262, 0x3d9f4f}}}, - /* 13*16^3*G: */ - {{{0x0f22a96a, 0x15c6c29d, 0x1aae3e52, 0x03753b38, 0x0e2b008b, 0x1aa4badd, 0x15288294, 0x06081b80, 0xb570e7}}, - {{0x085d1b00, 0x199fb30e, 0x15732732, 0x1376f4b0, 0x0cfbda7c, 0x1bb4dae0, 0x1dfc0a30, 0x0c71ee3c, 0xdcd8a2}}}, - /* 15*16^3*G: */ - {{{0x189a7509, 0x12075ca2, 0x1ea01527, 0x0e172c83, 0x1f20de12, 0x0d1f3ae3, 0x10ec1284, 0x04797572, 0x44d943}}, - {{0x00437234, 0x10515cdb, 0x137449b8, 0x07c3fbe2, 0x193586f9, 0x1de693bd, 0x1c280d08, 0x10010803, 0xb2c5b3}}} - }, - { - /* 1*16^4*G: */ - {{{0x0eade3c4, 0x1f4232e3, 0x014a8140, 0x156e9392, 0x186b4714, 0x1219a072, 0x04363971, 0x0de9d23d, 0xa01836}}, - {{0x0b26e8d0, 0x02e21013, 0x1853cdfd, 0x0e509c88, 0x18369d5f, 0x12bc1a4e, 0x0c59f1b3, 0x02e28221, 0xe2bbec}}}, - /* 3*16^4*G: */ - {{{0x19e0af06, 0x10e6f562, 0x1b65635b, 0x07314ac5, 0x1055c92f, 0x1dad36a2, 0x059142f4, 0x001711b1, 0x9cdf1f}}, - {{0x0216ade7, 0x0bd196db, 0x1f2951a3, 0x0a45b832, 0x194b0828, 0x05de6e46, 0x15993656, 0x1fda7e55, 0x916d25}}}, - /* 5*16^4*G: */ - {{{0x1305097e, 0x09b95c9c, 0x0ee94660, 0x0c2e1d08, 0x0448338d, 0x0d9682a0, 0x0fb6adac, 0x1aba34cc, 0x45bfd9}}, - {{0x108ddc04, 0x05d02c74, 0x14b05655, 0x173a677f, 0x0e908683, 0x0182a386, 0x14aeeb62, 0x0250ab5f, 0x9d1dc}}}, - /* 7*16^4*G: */ - {{{0x163bd6df, 0x098f8d05, 0x19abb0f3, 0x184785ba, 0x15e6fd1f, 0x0fce0144, 0x04ed9c7a, 0x02ac8ecd, 0xb599ad}}, - {{0x0efa09ac, 0x150e751c, 0x04d2d163, 0x0189ed22, 0x1c4166a4, 0x1b05c591, 0x0cc918dc, 0x1bd89f56, 0x6979d1}}}, - /* 9*16^4*G: */ - {{{0x07142d62, 0x1c309f99, 0x0ef09b6b, 0x14d46bf0, 0x054c11d9, 0x158913f4, 0x103a4998, 0x1e2ce655, 0xf4dca1}}, - {{0x1a618fc9, 0x04f66603, 0x134f5feb, 0x0f359dfb, 0x1c659bb9, 0x1c0dc8d5, 0x151b527c, 0x1cb69699, 0x551167}}}, - /* 11*16^4*G: */ - {{{0x015fae61, 0x1658ffa3, 0x0e57e087, 0x0b8fb89e, 0x0bb5a7f1, 0x1120738b, 0x0d9b6713, 0x141cdc13, 0xf07278}}, - {{0x096376f9, 0x1e865ba4, 0x0dfaaf3d, 0x0e0e2b70, 0x0f7d42c2, 0x10410c4c, 0x1a97df5e, 0x056294e3, 0x1a681b}}}, - /* 13*16^4*G: */ - {{{0x0466591f, 0x027020a9, 0x11a35a83, 0x0e5d51ff, 0x058ccbe1, 0x15a99dd4, 0x16c3c50e, 0x0af5e141, 0x1fc524}}, - {{0x196f8cdb, 0x077f7ab9, 0x1b1e0891, 0x11958465, 0x14e1629f, 0x1b7f2da1, 0x1047a7b1, 0x070113d7, 0x489c18}}}, - /* 15*16^4*G: */ - {{{0x122a8ebc, 0x12e85c61, 0x1e59c544, 0x039b6e31, 0x056d5a29, 0x05ad9865, 0x17d3f5fc, 0x16634918, 0xfa2501}}, - {{0x0a8a775b, 0x06c93dad, 0x0f20cc60, 0x11c11cd9, 0x1a8cbea5, 0x0f330d37, 0x0588ce14, 0x1629f0b1, 0x71c932}}} - }, - { - /* 1*16^5*G: */ - {{{0x1d96dff1, 0x1bee765b, 0x157f3fa3, 0x08638355, 0x198d530e, 0x105ab866, 0x153ffbda, 0x10a283fc, 0xec738}}, - {{0x1c7b552c, 0x16420d63, 0x1b5e2aa7, 0x04c99d0f, 0x052511d5, 0x0277ac03, 0x1d7646b3, 0x09d0f5d0, 0xd6224f}}}, - /* 3*16^5*G: */ - {{{0x088d58e9, 0x0e192558, 0x18c60e14, 0x14b838c9, 0x0a7b6e94, 0x12353e21, 0x0a1ba64a, 0x1fb8e0c9, 0x96dac3}}, - {{0x0ebebc5e, 0x01a49895, 0x01f9b8e0, 0x17d13729, 0x0c439685, 0x024a49c1, 0x06b615b3, 0x1e75a8d8, 0xcb1faf}}}, - /* 5*16^5*G: */ - {{{0x0db29f92, 0x0a956899, 0x11ecb162, 0x03a4e372, 0x18f811d2, 0x0e1bc575, 0x0c4a8417, 0x079d629e, 0xe297b2}}, - {{0x05e58ddb, 0x0794a645, 0x1b505058, 0x079d770b, 0x19149122, 0x0dd5dc66, 0x02d2d203, 0x041f196e, 0xe13725}}}, - /* 7*16^5*G: */ - {{{0x0ad88c33, 0x0ca1dbdc, 0x1d1af2bf, 0x15c729b2, 0x0da97d91, 0x1e490692, 0x12d9ac1a, 0x071f6572, 0x1cd223}}, - {{0x048fb1b2, 0x14753c21, 0x12879258, 0x1ca262bd, 0x0bc2713f, 0x1205589b, 0x02c25b21, 0x1f071569, 0xfc3acd}}}, - /* 9*16^5*G: */ - {{{0x1b26aa73, 0x09d644e1, 0x18e8383d, 0x0fc23618, 0x11ee0cdf, 0x16986ffd, 0x0eff2c72, 0x15b73d3f, 0xf462d7}}, - {{0x18479e73, 0x02f560bb, 0x140b3289, 0x11c14600, 0x13c7a49e, 0x1d253439, 0x0c50354e, 0x034f068a, 0x406a0d}}}, - /* 11*16^5*G: */ - {{{0x1cd015e3, 0x170f0155, 0x194089cf, 0x01d2b2fc, 0x15168af9, 0x1f59e544, 0x12bdd6f6, 0x04ba7ee1, 0xe0f689}}, - {{0x12157cce, 0x15126a16, 0x0a4daef6, 0x116a723c, 0x0c77c55b, 0x14b6393a, 0x0aa54d89, 0x0621c907, 0x8531e}}}, - /* 13*16^5*G: */ - {{{0x0bb76b12, 0x1362188a, 0x0649da47, 0x1cecee7c, 0x15a00ea8, 0x1598957b, 0x15ff0760, 0x182aa57e, 0x28e4ad}}, - {{0x0c4747bd, 0x1f229d3f, 0x058a3fd5, 0x014c1e2e, 0x0a3f703a, 0x1b2db5cf, 0x06cfd392, 0x09dfb340, 0x14d74c}}}, - /* 15*16^5*G: */ - {{{0x076ff697, 0x1fac00ff, 0x01d918a2, 0x16d10ca4, 0x097c6369, 0x16d5d9d0, 0x017b49c7, 0x04f29750, 0x85a0ba}}, - {{0x12142721, 0x04f6a6d2, 0x02962e4c, 0x12fff4f2, 0x1aa551de, 0x0869ee76, 0x0929551e, 0x0c3d587c, 0xadf32e}}} - }, - { - /* 1*16^6*G: */ - {{{0x0392d805, 0x192e2ee7, 0x1501750d, 0x1500d30e, 0x1449aa87, 0x06d57d51, 0x0f5e9295, 0x19e98d52, 0xf8f5dc}}, - {{0x0cda02fa, 0x1330ca8b, 0x0850ee80, 0x07b4c94a, 0x1327351f, 0x1f19b230, 0x0150e274, 0x19ecdac6, 0xe58176}}}, - /* 3*16^6*G: */ - {{{0x1fa046f7, 0x0ea598b3, 0x01cc2746, 0x021e7204, 0x02d45171, 0x05644a37, 0x0ea53821, 0x0950cb10, 0xe12c8e}}, - {{0x012646ad, 0x1d2ad145, 0x0c464d14, 0x1809c226, 0x126f6dd0, 0x1f6a9c98, 0x0bcd0cec, 0x0c21fb34, 0x7cec04}}}, - /* 5*16^6*G: */ - {{{0x02853c43, 0x0d893f46, 0x08b919ae, 0x0cd8af5c, 0x13236481, 0x1177f1e8, 0x0824f423, 0x0e82f2d2, 0x394bd4}}, - {{0x0064469d, 0x0bc14665, 0x03f3c32c, 0x1ece25b2, 0x00767d52, 0x1fe178eb, 0x1ae481f8, 0x0a42a3b8, 0x2b4d6d}}}, - /* 7*16^6*G: */ - {{{0x1c722e94, 0x016eb9cf, 0x0162587b, 0x102072da, 0x004334ed, 0x132b62ca, 0x0ba51171, 0x1be71bd0, 0xca8538}}, - {{0x1049a527, 0x105316f8, 0x02c9a90e, 0x12a75149, 0x19f12f20, 0x189350fd, 0x170c479c, 0x085d73c0, 0x3b27fc}}}, - /* 9*16^6*G: */ - {{{0x1cad6309, 0x18ba314e, 0x0e7fd221, 0x143f85e4, 0x07b3dd31, 0x1a312653, 0x0dd686ed, 0x0b3e46af, 0x663e1a}}, - {{0x09981f28, 0x19435d1c, 0x1ad4af54, 0x0fa88805, 0x0f918b90, 0x00c1e58e, 0x030f040b, 0x07700cd5, 0x292fb7}}}, - /* 11*16^6*G: */ - {{{0x02c23d03, 0x13ad9229, 0x11ffc924, 0x03609b1f, 0x1eeab3ba, 0x1611b83d, 0x19c25b0d, 0x1ce60c6c, 0xa2d9ef}}, - {{0x1085db74, 0x01726027, 0x0e77d144, 0x134fd2b0, 0x01b92ea0, 0x021b6388, 0x0e3c554c, 0x05199083, 0x1cc852}}}, - /* 13*16^6*G: */ - {{{0x07d4c6d1, 0x1aafcb38, 0x167ffec5, 0x059aa335, 0x135d0f66, 0x085a939f, 0x07bf82e4, 0x05691635, 0x5657e1}}, - {{0x0106bdec, 0x0181f94c, 0x14b05062, 0x1346b428, 0x06a0abff, 0x1c8799d7, 0x07e6b3ec, 0x1e971ba9, 0x72b5be}}}, - /* 15*16^6*G: */ - {{{0x1933564a, 0x032a6eaa, 0x18ebd13e, 0x1169f5db, 0x18d2b7a6, 0x16e333b6, 0x00042193, 0x02ba815c, 0x5b6862}}, - {{0x0204ef63, 0x1af822c4, 0x1e21a7ce, 0x15dc4510, 0x195de392, 0x062a68ce, 0x19154b4c, 0x0d39e744, 0x76b5ea}}} - }, - { - /* 1*16^7*G: */ - {{{0x0f922dbd, 0x025118bb, 0x064863e6, 0x0c622c3f, 0x171b91ca, 0x1556c726, 0x1cc4fe17, 0x17ffa9b5, 0x6d28b6}}, - {{0x00ef3cff, 0x055ff815, 0x1368a651, 0x09d3e170, 0x14a1c4c2, 0x10d30e65, 0x0be903ef, 0x00a283ba, 0xaf39d9}}}, - /* 3*16^7*G: */ - {{{0x1b168b64, 0x0c86ffcc, 0x01b2102d, 0x07a0558b, 0x0467ea15, 0x0482fe0a, 0x0f774f7c, 0x0f4ff7fc, 0x7cd315}}, - {{0x1abbb16b, 0x0b60695a, 0x180b8d61, 0x189b36e5, 0x1fafb13a, 0x073c5e6d, 0x1188815a, 0x0422e525, 0x9ca08d}}}, - /* 5*16^7*G: */ - {{{0x06ef29e8, 0x158e37d0, 0x166bb8ec, 0x18aa9613, 0x1356264d, 0x018ccea2, 0x03115ac9, 0x1b6be5bf, 0xed860b}}, - {{0x19b90f8f, 0x00d0e24d, 0x036a8cd4, 0x061eb85b, 0x17bb1e76, 0x1b59a020, 0x1ff5f1af, 0x086ebab4, 0xb837e7}}}, - /* 7*16^7*G: */ - {{{0x1636e617, 0x0fc8c59f, 0x1c73d667, 0x19a07783, 0x12c9d47d, 0x0e173f13, 0x090dcd78, 0x11fa3a42, 0x6868cc}}, - {{0x1cbb3d27, 0x046ee79f, 0x0e1335c7, 0x1c619148, 0x13f0a60f, 0x081c3c98, 0x079acf16, 0x14b7237f, 0xeed008}}}, - /* 9*16^7*G: */ - {{{0x113e2a34, 0x1ced1530, 0x0e8478b1, 0x1a5ccb9c, 0x1d318291, 0x1a4f8857, 0x1880b172, 0x0c1d591f, 0xec03b3}}, - {{0x184566d6, 0x01121f75, 0x084dade9, 0x102adccb, 0x122728aa, 0x0f108090, 0x010defbf, 0x1f6c140b, 0x86020f}}}, - /* 11*16^7*G: */ - {{{0x087c1b18, 0x1570672e, 0x12646897, 0x1c596a90, 0x056fa101, 0x01ad2c88, 0x04379a8c, 0x1473cf17, 0xa79464}}, - {{0x1b94809a, 0x15ac284b, 0x158a086f, 0x11bd38e2, 0x089f60d9, 0x0bc3cf4d, 0x03309269, 0x1c7ee50b, 0x1361f4}}}, - /* 13*16^7*G: */ - {{{0x1ed10d26, 0x0a714636, 0x19657d96, 0x157df053, 0x10ccc5a9, 0x0fdb4a79, 0x131e43a4, 0x101551f7, 0x544ef2}}, - {{0x16dafcc2, 0x1e127b93, 0x0b01ab8b, 0x1adf2edc, 0x170d3683, 0x0eff46c6, 0x0b3a75ac, 0x13b88b58, 0x95b91c}}}, - /* 15*16^7*G: */ - {{{0x17ad18e6, 0x04a3cfc5, 0x0f2b5789, 0x1a7cf31a, 0x0ff9b57a, 0x1e4235a5, 0x18853794, 0x110be698, 0xe2c26c}}, - {{0x0554cfad, 0x0d7f28d5, 0x0607792b, 0x0a2b94e3, 0x1ba113bc, 0x1337508b, 0x06214738, 0x0509e910, 0xb2121b}}} - }, - { - /* 1*16^8*G: */ - {{{0x185a5943, 0x12d4f110, 0x1977ed8e, 0x12326cb8, 0x071da1ab, 0x15991316, 0x1e248595, 0x0815e455, 0x7fe36b}}, - {{0x099ca101, 0x0868a963, 0x02bc84b5, 0x07ab0cf7, 0x0a6f174b, 0x1a0203ee, 0x18927c27, 0x0b04b6c6, 0xe697d4}}}, - /* 3*16^8*G: */ - {{{0x1f0922a8, 0x153c048e, 0x173e365d, 0x01d3a4a8, 0x18b808e9, 0x072b0117, 0x15ce0249, 0x080b69c5, 0x4b656a}}, - {{0x199a80bb, 0x0d6e562c, 0x0143da53, 0x0773c573, 0x0c4d6d47, 0x06612aec, 0x00e48341, 0x03a25cef, 0xee1ea3}}}, - /* 5*16^8*G: */ - {{{0x09d07e9e, 0x0827c443, 0x09830dad, 0x1cf943ed, 0x18e5ea09, 0x15f07d9e, 0x04f64f6c, 0x0dd7acbc, 0x9d7895}}, - {{0x03d8c8ee, 0x06eb0ad6, 0x0531ecbd, 0x0145b477, 0x010ce746, 0x144e0e99, 0x0335950a, 0x0dde91d5, 0xd5149e}}}, - /* 7*16^8*G: */ - {{{0x1dc4950f, 0x13ba8433, 0x0e5081d9, 0x03655b7a, 0x16316574, 0x1423726d, 0x1ea55c8d, 0x0e5c26a7, 0x4acb12}}, - {{0x03d8b4df, 0x16372223, 0x019ab159, 0x1c85016b, 0x1e29fdf9, 0x0c7b6638, 0x16b633ca, 0x01f6e879, 0x726cd2}}}, - /* 9*16^8*G: */ - {{{0x0d7e8cbd, 0x0063d3df, 0x01f605bd, 0x0cff15e0, 0x1464a2a6, 0x09aa8640, 0x1ed08d11, 0x17e34d62, 0x640c5e}}, - {{0x1ec5a5db, 0x171481e5, 0x1fe6c081, 0x0d3f6319, 0x0ab9eba0, 0x0375b80f, 0x14bcaba7, 0x1fb0539f, 0xd6ae88}}}, - /* 11*16^8*G: */ - {{{0x1e86bddc, 0x18502583, 0x1738f57d, 0x04d72f2e, 0x16b9bf1d, 0x116486ed, 0x0cc91455, 0x16368509, 0x8bba04}}, - {{0x09e349c4, 0x111e2d0f, 0x065b97d4, 0x19fbc9e8, 0x1437ac6a, 0x1c90b34c, 0x1afd966f, 0x18138411, 0xc34099}}}, - /* 13*16^8*G: */ - {{{0x0257f582, 0x0b873c02, 0x10e89634, 0x1213d5bd, 0x05fe41f9, 0x0a1fbe2d, 0x1f88429a, 0x089fd9a6, 0x8b0a57}}, - {{0x17372d4b, 0x00bff6f9, 0x0581d992, 0x168d74f6, 0x17d4656d, 0x0ad09bf1, 0x1ce598a8, 0x09d81ad8, 0x9e57a4}}}, - /* 15*16^8*G: */ - {{{0x185e797e, 0x18a24fcc, 0x0f0c5a0f, 0x1c449e5c, 0x078a6f06, 0x17a49ecb, 0x025c7e15, 0x12f84c8f, 0xa13ff}}, - {{0x139d666f, 0x193d5cfa, 0x0030e4a0, 0x1ede14eb, 0x1dc3ff27, 0x00c747d0, 0x0f5645f6, 0x09644b90, 0xeb1a96}}} - }, - { - /* 1*16^9*G: */ - {{{0x1dde3445, 0x17fa7f0f, 0x0ba9e05e, 0x1753bb45, 0x0519e76b, 0x0ff3ff93, 0x079a14dc, 0x0709ae0c, 0x6965b6}}, - {{0x18855113, 0x0621c4f0, 0x17e6765f, 0x19cc0a34, 0x12dbef47, 0x04b31c39, 0x1444c979, 0x1c738904, 0xd1bcda}}}, - /* 3*16^9*G: */ - {{{0x088acfc5, 0x0f207cb4, 0x1a25e27d, 0x1eb1366c, 0x11a71b1e, 0x1eedea04, 0x1f6809f9, 0x03bbceb8, 0xe1cbc5}}, - {{0x0249a225, 0x1e2ff6ad, 0x0fee948e, 0x065257dd, 0x1e23ce7d, 0x1b27fedc, 0x0bb6cc49, 0x12f9bcfb, 0xec6783}}}, - /* 5*16^9*G: */ - {{{0x0107ccc6, 0x0c7529ff, 0x0f208a1d, 0x112fc2a8, 0x100481d3, 0x0e9422d4, 0x0a85cc7a, 0x0f2e2271, 0x9c3f26}}, - {{0x1dea9152, 0x003ca669, 0x1e6c61a1, 0x11fa9d2b, 0x06eca51a, 0x1afa5f9f, 0x16722f8d, 0x036c96a3, 0xdaee0c}}}, - /* 7*16^9*G: */ - {{{0x15a449e7, 0x0e2f5a1c, 0x0f0af908, 0x054069ca, 0x0922b2fa, 0x160bd8a0, 0x15c9e5be, 0x0aef8d65, 0x5c30d9}}, - {{0x0e2677d0, 0x1875240d, 0x139e439d, 0x1c80a787, 0x192d0b75, 0x12fbcce2, 0x0e48592d, 0x16b611c3, 0x8025e3}}}, - /* 9*16^9*G: */ - {{{0x0b8ceb07, 0x12ac6014, 0x1b89c73e, 0x124a57ff, 0x0d995537, 0x11583f54, 0x1a0abf93, 0x046191d2, 0xaa3bbf}}, - {{0x1fd83984, 0x0d423174, 0x0cfe7ae6, 0x036578f5, 0x162ec3b7, 0x187f2648, 0x09a11372, 0x161383d8, 0xe6320c}}}, - /* 11*16^9*G: */ - {{{0x03bfd1f1, 0x00752aac, 0x1595ccb9, 0x0db86a2b, 0x001943a3, 0x07e4c8a0, 0x0605e798, 0x0a226668, 0xab4a32}}, - {{0x08e1c233, 0x089cb086, 0x07a633c8, 0x1b26484f, 0x1e057a57, 0x1de82097, 0x19a2d3ac, 0x17606d90, 0xea6c64}}}, - /* 13*16^9*G: */ - {{{0x0b1d6d3f, 0x02036bc0, 0x1baee242, 0x0ecd149d, 0x1a8105f3, 0x1fae6c52, 0x15dfdccd, 0x0b3bbdab, 0xdba9b2}}, - {{0x09cd8133, 0x01749414, 0x0af64f31, 0x1b3788a2, 0x1871448b, 0x0ca7a7f5, 0x0ded6b2a, 0x07eaea6b, 0xb3862d}}}, - /* 15*16^9*G: */ - {{{0x11fa2a6a, 0x1cd01c01, 0x0392c959, 0x183ab0b6, 0x1cdfe34f, 0x0ba158d1, 0x1d141eb7, 0x1885d304, 0x1998f4}}, - {{0x06415142, 0x03f6da5e, 0x1c962d58, 0x180470f6, 0x0a1e94c6, 0x1c0045bc, 0x08425c03, 0x0da3215f, 0x2ebb41}}} - }, - { - /* 1*16^10*G: */ - {{{0x0d9aefbd, 0x0c7e5362, 0x0e55dd49, 0x1c8f64e7, 0x043dc1ef, 0x0f86a0de, 0x15d8cb2a, 0x03918cd3, 0xfbc34}}, - {{0x13e71ca0, 0x09db754e, 0x02a7fc7b, 0x160d3c40, 0x1d3d3950, 0x058ea4ab, 0x18ff9005, 0x0c65e6c1, 0xbd8022}}}, - /* 3*16^10*G: */ - {{{0x09434837, 0x1cb6fff7, 0x08306ee1, 0x0628170c, 0x1b06dadb, 0x0e37fda7, 0x0bb6c4d0, 0x1578950f, 0xd76d18}}, - {{0x1b215b4c, 0x1d5027cd, 0x0df33093, 0x08b1ceeb, 0x1933290a, 0x010a7bd5, 0x19137839, 0x1465db2c, 0xf6bc4d}}}, - /* 5*16^10*G: */ - {{{0x1c07f222, 0x1744f90c, 0x183f9f40, 0x0438c758, 0x153d1c5e, 0x0e8d0f8b, 0x1d813d20, 0x0a0c2cff, 0xd5c0c6}}, - {{0x0c6ffd57, 0x177b48f0, 0x004ea1b8, 0x07ea34f1, 0x175b9baf, 0x063bfa4f, 0x02143378, 0x10c102f7, 0x15a30b}}}, - /* 7*16^10*G: */ - {{{0x0e93bfa6, 0x1238b512, 0x084d8a92, 0x1a52b413, 0x09fe0d39, 0x05d335a6, 0x18b39527, 0x09c948de, 0x734c36}}, - {{0x18d10774, 0x037d3ccc, 0x00a5f13f, 0x026c4112, 0x05f48eca, 0x00f1a906, 0x141277a6, 0x007554f3, 0x99515}}}, - /* 9*16^10*G: */ - {{{0x1fa194ae, 0x075a8bfa, 0x0152bb3c, 0x00523b34, 0x0b149064, 0x0ece954f, 0x0a24045d, 0x1b40f6cd, 0x79a3d9}}, - {{0x1fcf634b, 0x1e32f4e4, 0x1e6f1353, 0x084be65e, 0x103d86bd, 0x18dc2c57, 0x06cd2cd9, 0x194e4a96, 0x84e1db}}}, - /* 11*16^10*G: */ - {{{0x01e8b7fe, 0x16483001, 0x016c9a9a, 0x01c5c2ef, 0x098e05dd, 0x06556e7e, 0x160a28f9, 0x0129ab60, 0x3393fd}}, - {{0x023c6821, 0x00a12210, 0x06e52dc3, 0x0d661515, 0x0668d8e5, 0x1576ce2d, 0x1ae0babf, 0x17d90cf7, 0x130437}}}, - /* 13*16^10*G: */ - {{{0x01528cf6, 0x04abceab, 0x141a53a8, 0x004a15ec, 0x10a52e6a, 0x02c3772d, 0x1f85786d, 0x10c268c4, 0xa16b28}}, - {{0x1ce93f3b, 0x05c907cf, 0x132004e0, 0x07f79027, 0x150f4349, 0x16bcec08, 0x166644f3, 0x15d0a6f5, 0xf40598}}}, - /* 15*16^10*G: */ - {{{0x0c8db2e0, 0x0885335c, 0x035cd60d, 0x197b82be, 0x074f6473, 0x04d5d4ad, 0x1d32fdb2, 0x04031c68, 0xd317ce}}, - {{0x074bc9a9, 0x0d9a5159, 0x0e183ce9, 0x04e9a045, 0x19136caa, 0x01f471cb, 0x112e670b, 0x01521270, 0xd61a21}}} - }, - { - /* 1*16^11*G: */ - {{{0x03fe67b2, 0x1718cbd5, 0x0b8cce31, 0x117fce14, 0x123b234a, 0x15cdd4b9, 0x1772f199, 0x086ee790, 0x6608c2}}, - {{0x05b47a28, 0x091ff2e7, 0x1150c206, 0x162b3e81, 0x0e03fc22, 0x1206d3a3, 0x05a211bd, 0x17d8a438, 0xa1a916}}}, - /* 3*16^11*G: */ - {{{0x0bc73e02, 0x1f375569, 0x194c4b04, 0x0cc29dd6, 0x18631849, 0x0c08beab, 0x108c81aa, 0x1c54d8bb, 0xf0956a}}, - {{0x0611219d, 0x0967b4cc, 0x0ba515cb, 0x1a091b77, 0x19356672, 0x02c57941, 0x00f1db88, 0x0c02289c, 0x433fe6}}}, - /* 5*16^11*G: */ - {{{0x1c5fe45c, 0x0f116f28, 0x1f03d65f, 0x05035070, 0x1283488b, 0x124ebe7b, 0x183294bc, 0x1d479454, 0xdb98eb}}, - {{0x1ef45a22, 0x05380e21, 0x12038711, 0x06409f54, 0x12c8ce98, 0x094eec48, 0x168b7854, 0x016eb7b0, 0xe0c0f3}}}, - /* 7*16^11*G: */ - {{{0x02128eac, 0x1acf306f, 0x0bc732eb, 0x0eb1ab99, 0x051690c4, 0x159dbeb5, 0x17d086e9, 0x02c15bb4, 0xa73c0}}, - {{0x04f1180d, 0x02c948eb, 0x1576728e, 0x0eb3e2ee, 0x10a7c793, 0x1b6eca7a, 0x0e329572, 0x0ac089d0, 0x443fda}}}, - /* 9*16^11*G: */ - {{{0x03ccbc30, 0x12d4998b, 0x0d258631, 0x1fcba43a, 0x197249ed, 0x1bb33d04, 0x1c8dc3b4, 0x1868f969, 0x378720}}, - {{0x1315729e, 0x110193f8, 0x0d23e11a, 0x19f2aba2, 0x19b6d16b, 0x05605553, 0x0e324c46, 0x1739ee4a, 0x8f1bdd}}}, - /* 11*16^11*G: */ - {{{0x03bb1b6b, 0x12546f2a, 0x1f0afea9, 0x1fde65e8, 0x1a621575, 0x08c6082a, 0x09867660, 0x03b6f163, 0xfe8fd2}}, - {{0x10eaaf93, 0x1daecdd1, 0x0afe2265, 0x09bb9fe1, 0x113c00f1, 0x074afad6, 0x18dda78a, 0x15d864ad, 0x791e22}}}, - /* 13*16^11*G: */ - {{{0x144b7cea, 0x002acce5, 0x1b3e1f7a, 0x08d28122, 0x1dcca474, 0x00113c59, 0x18bba081, 0x1aaf3f6f, 0x2c331f}}, - {{0x15d1c9c2, 0x1e15a356, 0x0c2501df, 0x04a23fe2, 0x1d650619, 0x19ec61de, 0x19dbf3cf, 0x0399c0c3, 0x20567b}}}, - /* 15*16^11*G: */ - {{{0x0112278c, 0x1c94d9bc, 0x03668563, 0x02c93a15, 0x04b66dd0, 0x1a643fc2, 0x0c828d22, 0x0d88ca34, 0x509bc0}}, - {{0x0e178f66, 0x043ae4c4, 0x0d7e517b, 0x0bfd0dce, 0x164570f6, 0x1a78bdc7, 0x1bb05d83, 0x1856e448, 0xb4b168}}} - }, - { - /* 1*16^12*G: */ - {{{0x17e55104, 0x175d7c00, 0x03a71c70, 0x1506bf77, 0x1561cf73, 0x09e1a6c5, 0x1cdd8f7a, 0x0a44f6f0, 0xd8de76}}, - {{0x052e08cd, 0x10177c07, 0x1036c6ca, 0x0e7f9c32, 0x0f924c2f, 0x114568ee, 0x0b457131, 0x0cb7c27e, 0x2fd294}}}, - /* 3*16^12*G: */ - {{{0x0987bfd3, 0x09ce163f, 0x110a131a, 0x0a8e5fe9, 0x1d19187c, 0x1df57d6e, 0x18cd5477, 0x0df7aa31, 0xe3a578}}, - {{0x0cc23e7d, 0x1698db64, 0x0a38205f, 0x19e5d9b2, 0x0210ca3d, 0x1cffa56c, 0x039a11de, 0x012a6b08, 0xf74bad}}}, - /* 5*16^12*G: */ - {{{0x02d5056e, 0x13ca8e91, 0x11e81f2d, 0x196b4fc4, 0x05e480ed, 0x0ffe74b7, 0x03504549, 0x083d94a4, 0x42a970}}, - {{0x10f7db11, 0x020a6e45, 0x07f47f52, 0x1c907add, 0x03b26b64, 0x16e935ab, 0x1c19bb0d, 0x0cd9e010, 0xf2fc7f}}}, - /* 7*16^12*G: */ - {{{0x13dafd3b, 0x13a2fc8e, 0x12ce55b1, 0x1133e05b, 0x179d66fb, 0x16176b29, 0x1933f88c, 0x036384f2, 0xd6ca7}}, - {{0x1a6fe6e8, 0x156f8a10, 0x021dc6b5, 0x049157dd, 0x1de58a02, 0x0a501383, 0x04f67f92, 0x1671fbc5, 0xa85130}}}, - /* 9*16^12*G: */ - {{{0x11131f8b, 0x15fce3ff, 0x0883537d, 0x08d2c19f, 0x11fb4696, 0x116fce21, 0x0f65f530, 0x0f5b0cb4, 0x5edca}}, - {{0x1a6f779f, 0x167858cd, 0x1a275243, 0x04a00be1, 0x071be335, 0x0b8daf81, 0x1af00727, 0x03c91d10, 0x9e5888}}}, - /* 11*16^12*G: */ - {{{0x0b597e4d, 0x047cfa0f, 0x1f468c67, 0x09e64bce, 0x11c11497, 0x02d19dbb, 0x0290486d, 0x0ac178f4, 0x5a13c0}}, - {{0x041856e0, 0x1bf2434f, 0x04d9992b, 0x1d0f56b2, 0x173ce7bd, 0x0c507916, 0x1ffd47f4, 0x027121f2, 0xc9e73c}}}, - /* 13*16^12*G: */ - {{{0x0260faf0, 0x153c4b0a, 0x1a564d76, 0x17a68b2f, 0x1272ea2b, 0x070d3407, 0x19d50c51, 0x0ac02f6d, 0x96b256}}, - {{0x0366d412, 0x07212907, 0x1f53d6b0, 0x177f30e0, 0x199e1890, 0x072b99df, 0x12b002b6, 0x1400366f, 0xdcf8a6}}}, - /* 15*16^12*G: */ - {{{0x0ad13276, 0x04d0df25, 0x1010f1e9, 0x06a1d5d3, 0x171a3ca6, 0x15959c3b, 0x18909bc4, 0x0218839b, 0xb9719}}, - {{0x0a84dadb, 0x19c79a10, 0x1c3eb8ec, 0x1af25304, 0x0811f593, 0x122d9dfb, 0x1bef538b, 0x0dde00dd, 0xac2848}}} - }, - { - /* 1*16^13*G: */ - {{{0x071e5c83, 0x1535e490, 0x10a82fbb, 0x04fe330a, 0x0e5b18bd, 0x02db952c, 0x1cfc82a1, 0x082a04da, 0x54ccc9}}, - {{0x140916a1, 0x1e8477b8, 0x03b925b3, 0x1c1798bb, 0x0bf22929, 0x038aed69, 0x14c8ea3e, 0x08b68a28, 0x1c433f}}}, - /* 3*16^13*G: */ - {{{0x0f76167c, 0x02f1b115, 0x1d19ce53, 0x1e845988, 0x0d9f22f2, 0x163496e6, 0x1e54e708, 0x1b96e7df, 0x87582e}}, - {{0x0f4d7ff6, 0x037ddb0b, 0x065efa1b, 0x051e0cee, 0x036a8880, 0x0161e1b9, 0x09eff784, 0x06a15ac9, 0x75b94}}}, - /* 5*16^13*G: */ - {{{0x14de6611, 0x1391f743, 0x0087ec11, 0x0cae7297, 0x0f11e33f, 0x1b9b1ab3, 0x1b6c7096, 0x1943e4e6, 0x4a107a}}, - {{0x18a805fe, 0x1ac75f72, 0x084cab00, 0x1aa8f4b8, 0x0052e075, 0x12747b80, 0x1ce4d339, 0x0a200c7d, 0x809b26}}}, - /* 7*16^13*G: */ - {{{0x0dcfecb0, 0x05d27683, 0x0a3611bf, 0x07fa3a61, 0x149f6e98, 0x0706a4df, 0x1f259528, 0x166e555b, 0xfd402e}}, - {{0x0da08de8, 0x02c9f9ef, 0x19979e44, 0x1698497b, 0x1a86b1a4, 0x131c4dd5, 0x088ba698, 0x061bb4c7, 0xd6b340}}}, - /* 9*16^13*G: */ - {{{0x154659ed, 0x1a6eb07a, 0x17659b96, 0x1c7a4432, 0x1d0a07fa, 0x0703ff64, 0x145577ee, 0x08c8c30e, 0x467bb3}}, - {{0x0800cf83, 0x1903e859, 0x004f8026, 0x17821b9a, 0x02cd3701, 0x06ac36dc, 0x03a287cb, 0x1a0b552f, 0xd01eff}}}, - /* 11*16^13*G: */ - {{{0x1c97e515, 0x1324c256, 0x021697fb, 0x09ad8673, 0x0c7e5691, 0x0db1874f, 0x0391b21e, 0x11f19bb8, 0xcf66b9}}, - {{0x006c8a51, 0x0c10a754, 0x0400cea5, 0x00e8b1b3, 0x06a7b33f, 0x1a081a76, 0x04847096, 0x0f72088c, 0xa7071}}}, - /* 13*16^13*G: */ - {{{0x186154e5, 0x1d295080, 0x0be8374e, 0x0054cd35, 0x06770864, 0x04a9d6a4, 0x08d1d472, 0x18c1f7f5, 0x3d9252}}, - {{0x1603a8a2, 0x1fe49fb4, 0x1f75c1dd, 0x03f6faf8, 0x14cf3aea, 0x19a144b5, 0x15ff1124, 0x1b9a5f53, 0x3d0d01}}}, - /* 15*16^13*G: */ - {{{0x0a4a8daa, 0x1db1db15, 0x029728f4, 0x00c01de5, 0x0968ce51, 0x1ada47a5, 0x1a71d83a, 0x164d112e, 0x2bbe38}}, - {{0x1236e49b, 0x1043ba74, 0x1adb9f5f, 0x056749c7, 0x00850990, 0x1f7c8fcb, 0x0dbd78b3, 0x1ec01a76, 0xf85707}}} - }, - { - /* 1*16^14*G: */ - {{{0x02d32936, 0x187dbb89, 0x011fc070, 0x19a05c65, 0x17f38e40, 0x03e8a89e, 0x11f67db3, 0x0b2f0294, 0xc5440c}}, - {{0x07cabfd4, 0x1217d1ba, 0x19079766, 0x0da50c63, 0x0f4acd8a, 0x0f0e91f6, 0x1e9cbb70, 0x174707c3, 0xd27ee9}}}, - /* 3*16^14*G: */ - {{{0x1989a8e1, 0x0c280f63, 0x1b92f75f, 0x1b2de6e9, 0x0fe0a7c1, 0x1023cdce, 0x1e6dd403, 0x188b9bf4, 0x3e4d03}}, - {{0x041c240a, 0x03c89c42, 0x0f59d026, 0x1e31abe7, 0x0a719ffa, 0x0be56be5, 0x11d6ab04, 0x1a102b4c, 0x12f744}}}, - /* 5*16^14*G: */ - {{{0x19bb7902, 0x09cd4686, 0x0df1cfda, 0x04d8e50d, 0x05e9fd8c, 0x124f4a24, 0x00d66a68, 0x09367ac2, 0x1b684}}, - {{0x003ee653, 0x0bfa258f, 0x00d9a0b3, 0x164c08e3, 0x11581fce, 0x0c72e1b6, 0x10f82fc6, 0x143d26f3, 0xe59063}}}, - /* 7*16^14*G: */ - {{{0x0371d9fe, 0x14dab6a6, 0x0675aef0, 0x166ad833, 0x13a4cf04, 0x0ad3e1d5, 0x1288cd65, 0x16359993, 0x952c3c}}, - {{0x0189a13f, 0x1b673692, 0x0231d7b5, 0x08c9e230, 0x0e5d0664, 0x089b7b76, 0x1c1a9f7e, 0x08defac5, 0x59b985}}}, - /* 9*16^14*G: */ - {{{0x0bc2c885, 0x03ffe3b4, 0x19395f21, 0x03dceea4, 0x1cde5155, 0x002285cb, 0x1ab21626, 0x1c2b62cd, 0xdfcb4e}}, - {{0x1345d92e, 0x0ea53218, 0x0afa5d5d, 0x0e7128f7, 0x00f411c7, 0x1e136416, 0x0e854f13, 0x12aaa0c2, 0x536a23}}}, - /* 11*16^14*G: */ - {{{0x1510208e, 0x1c5c295f, 0x15e50e1c, 0x199c5b09, 0x097bbdb8, 0x179d023e, 0x00322a1e, 0x18137cca, 0x157966}}, - {{0x0d207357, 0x10777128, 0x1690f7f5, 0x1f8c8865, 0x0be07008, 0x1bdbddc6, 0x193aaf7f, 0x0b56e244, 0x40446d}}}, - /* 13*16^14*G: */ - {{{0x0b96fe13, 0x04fe65f4, 0x014baa07, 0x0495a769, 0x16e23e49, 0x147fc09f, 0x042b5b86, 0x078963d3, 0xc6b21e}}, - {{0x17bb1417, 0x0035cb57, 0x19b4b5a8, 0x0aa122f3, 0x128a2ff6, 0x1c0f1a75, 0x1523952d, 0x1b186669, 0x335bcd}}}, - /* 15*16^14*G: */ - {{{0x03936565, 0x1b2c0cd6, 0x1fe931f0, 0x0f66db2e, 0x122d997a, 0x054ea9ca, 0x05bc2d2f, 0x188f18e5, 0xa231e4}}, - {{0x15ec5f19, 0x172b5031, 0x1c5509a2, 0x0adc012d, 0x1cf942ba, 0x07634401, 0x1a470365, 0x117d8ff7, 0x80e0c9}}} - }, - { - /* 1*16^15*G: */ - {{{0x1066fd48, 0x1cfa0b95, 0x05c560ef, 0x0e203971, 0x0dca61c3, 0x0dcbd35d, 0x07141b1e, 0x0f4844fe, 0x241c56}}, - {{0x12857b08, 0x1be9633c, 0x08d9815f, 0x10e2715d, 0x003a48ea, 0x00be0219, 0x152e4d8e, 0x127a8605, 0x40a62d}}}, - /* 3*16^15*G: */ - {{{0x0df9591d, 0x10fbedc8, 0x0d320aa1, 0x18758485, 0x07218dce, 0x09e25599, 0x03a72e83, 0x07704d2f, 0xde2fd2}}, - {{0x0457ad84, 0x070cb9e8, 0x100da92d, 0x15143f11, 0x12ebbda9, 0x1bf6425c, 0x0fcc17b2, 0x02676c48, 0x400d71}}}, - /* 5*16^15*G: */ - {{{0x1562282c, 0x15412a57, 0x1ef0ddcb, 0x1e75f271, 0x11340f02, 0x04581270, 0x0f7664e5, 0x16060999, 0x8df889}}, - {{0x01d195cd, 0x12b55ecb, 0x1e6ec55c, 0x1ee0899d, 0x0f35e247, 0x0f318c45, 0x1bb5b1d0, 0x0ce640b9, 0x74525b}}}, - /* 7*16^15*G: */ - {{{0x0dddb2dd, 0x0ea944a8, 0x0b0369be, 0x10c99b98, 0x0f245078, 0x1c0678a9, 0x03e0007e, 0x119b3170, 0xa0fd75}}, - {{0x13ede6b2, 0x1eca7fc3, 0x10269f1f, 0x19d2df12, 0x08f311c8, 0x0fe989d8, 0x0357e1a4, 0x06b8266d, 0x53e5d8}}}, - /* 9*16^15*G: */ - {{{0x0f542e36, 0x0465d502, 0x0d0570b8, 0x05ff5f42, 0x135d84e2, 0x0933ca31, 0x03d9f796, 0x108e5a34, 0x3170c5}}, - {{0x163288b5, 0x1623ad77, 0x066f86f1, 0x1eead2b3, 0x1773d006, 0x0849ff5c, 0x1f88dd45, 0x025f874f, 0xb20836}}}, - /* 11*16^15*G: */ - {{{0x1c7548e9, 0x0cd91d2e, 0x04b5531e, 0x1b500e11, 0x03fe5d8d, 0x00b4a783, 0x180a76d2, 0x152145a7, 0x92fab0}}, - {{0x0917758f, 0x03896682, 0x0b421223, 0x01b8d1de, 0x079ffc8a, 0x18a70613, 0x1af3d0c5, 0x0d019648, 0x55e7b4}}}, - /* 13*16^15*G: */ - {{{0x1498f7f8, 0x06c0285c, 0x104588b8, 0x1ecfa64c, 0x08712c4d, 0x108d8c96, 0x145e742f, 0x17c3006a, 0x91b065}}, - {{0x1f23195b, 0x03a06cf1, 0x0258e78f, 0x18f684af, 0x1e264df2, 0x19a4800b, 0x1883fe7f, 0x0eff6ce2, 0x35b6f}}}, - /* 15*16^15*G: */ - {{{0x0060e322, 0x0ee5f712, 0x113452d4, 0x1b8e6f53, 0x0b9923ec, 0x034ba44d, 0x0cea70e4, 0x09995939, 0x8e4a1f}}, - {{0x104619d7, 0x110c1e6c, 0x13eff813, 0x01531b2a, 0x07bc4fb0, 0x0f692037, 0x1dd4bec1, 0x0bd6651a, 0x4936b7}}} - }, - { - /* 1*16^16*G: */ - {{{0x0e14db63, 0x073ae5a4, 0x1947dfa4, 0x1277555a, 0x025de294, 0x0c971937, 0x0a961249, 0x17850235, 0xfa822}}, - {{0x1f462ee7, 0x008922a2, 0x1fa0bd79, 0x034ca0a1, 0x1188b34b, 0x0a5e59ef, 0x0035bd2b, 0x1d1ebb75, 0xbff44a}}}, - /* 3*16^16*G: */ - {{{0x1db3cdec, 0x096229fb, 0x0a3afd5d, 0x1e742564, 0x04bc8dbe, 0x122f3df5, 0x1d739659, 0x0c9ff225, 0x85b2c0}}, - {{0x0a03f81f, 0x1a684102, 0x133e3823, 0x101669cc, 0x06d00dc9, 0x1d1697e6, 0x14d98f1f, 0x11e73a22, 0xf64b27}}}, - /* 5*16^16*G: */ - {{{0x0607b030, 0x0452d724, 0x01359cca, 0x15fec7cb, 0x0f29c24d, 0x1dd6760b, 0x119de147, 0x0ed88042, 0x110b03}}, - {{0x13617c3a, 0x01d50895, 0x0a61d27d, 0x1c2aadf6, 0x1c70c87b, 0x1c4fc230, 0x19cd31ba, 0x10a631dc, 0x84432b}}}, - /* 7*16^16*G: */ - {{{0x15a76f08, 0x076ec0e3, 0x0efb5395, 0x0be4a717, 0x0aaf8329, 0x1092158e, 0x075c53db, 0x0893ec8e, 0x18784e}}, - {{0x0b824993, 0x19cb02eb, 0x02894c82, 0x1ae94f4c, 0x1e671fc9, 0x147ed638, 0x0b9c5dde, 0x0fe943c3, 0x8d76ed}}}, - /* 9*16^16*G: */ - {{{0x1a6fad81, 0x14719e4f, 0x032c0fb3, 0x06cae918, 0x0037d9c3, 0x16ebb81d, 0x1ae6bbd5, 0x1c0fa0bc, 0x58a2f9}}, - {{0x1b109594, 0x00030af1, 0x0c02e095, 0x1765a65a, 0x1a6bd798, 0x017c38bf, 0x038306da, 0x18b58aac, 0x6ab64}}}, - /* 11*16^16*G: */ - {{{0x1eb52583, 0x191788fc, 0x03304ebe, 0x15339d82, 0x1676fea8, 0x16dd93c7, 0x0e8903b9, 0x1b99cfd7, 0x1ef020}}, - {{0x1a3f17ae, 0x0d93c0cb, 0x04b532d5, 0x0601f2e0, 0x095306ac, 0x1607a5cb, 0x12c025fd, 0x164b4f42, 0x594476}}}, - /* 13*16^16*G: */ - {{{0x10a1958f, 0x0e11f60f, 0x0d5990ed, 0x136a48df, 0x1a32c4f3, 0x0a267d5f, 0x084e5774, 0x0c783ad4, 0x4e4a81}}, - {{0x08636f8a, 0x01d9fa8a, 0x110f59a9, 0x0810bf65, 0x06fa8400, 0x051f714e, 0x03440cfd, 0x0b6f3d19, 0x2574a4}}}, - /* 15*16^16*G: */ - {{{0x04935db5, 0x013ad423, 0x1af6c3ba, 0x0f304c38, 0x0519e281, 0x1f076aca, 0x04138cc9, 0x02d5bac2, 0xf6966a}}, - {{0x1d166838, 0x11a41b60, 0x006fc04e, 0x08a74688, 0x0755e2d7, 0x172b961e, 0x16cb0697, 0x00f52063, 0xba0de3}}} - }, - { - /* 1*16^17*G: */ - {{{0x176a6987, 0x07a0982d, 0x1690ffda, 0x10356887, 0x01b3f2b4, 0x14c46bf7, 0x0551f771, 0x1af53313, 0x54bc18}}, - {{0x1b9aae49, 0x122be682, 0x1cec467f, 0x171da976, 0x1e2fd52e, 0x1c28dd39, 0x0bcdce46, 0x02423cdd, 0x4b2c8c}}}, - /* 3*16^17*G: */ - {{{0x1d1fd820, 0x05fa3faf, 0x1d9d400e, 0x0f0a8c90, 0x1271b788, 0x08113158, 0x14aaa18d, 0x1ed01838, 0xbafbbc}}, - {{0x1bf074f0, 0x176d6a90, 0x07f2b5ba, 0x18694246, 0x1c0fde81, 0x1fad644d, 0x1604b39e, 0x0f93b69b, 0x148799}}}, - /* 5*16^17*G: */ - {{{0x14299b7e, 0x08b84094, 0x01b8343a, 0x0f3e5a13, 0x020ec24e, 0x1dfe3ae1, 0x07f8a8f0, 0x1ee20671, 0x9b1938}}, - {{0x047b84de, 0x0bd942ba, 0x1ae08cb1, 0x1bd0a3f0, 0x03ec90ac, 0x14c70e55, 0x0a0cc503, 0x0dde2e20, 0xfb12aa}}}, - /* 7*16^17*G: */ - {{{0x110cc4c4, 0x0a083a06, 0x094f683f, 0x1a6b4589, 0x01b2cb71, 0x0fee033b, 0x1641f0e0, 0x10b9802e, 0xa67719}}, - {{0x1c976570, 0x1079d91b, 0x1fcc2530, 0x08aee74d, 0x19c3dbc7, 0x0b300f2e, 0x1663ca6f, 0x10beea1b, 0x855061}}}, - /* 9*16^17*G: */ - {{{0x14242a28, 0x0084016a, 0x0f29dc55, 0x1796424a, 0x14eca455, 0x17bc25bb, 0x1f0427a2, 0x0a6d61d5, 0x88b7ed}}, - {{0x194ed8ce, 0x1cb40f63, 0x00f8d1fe, 0x0cd4391f, 0x1bd934e3, 0x1cee9ac3, 0x11791fef, 0x040c48a6, 0x22b053}}}, - /* 11*16^17*G: */ - {{{0x1bc1a94c, 0x0b0a2764, 0x1c76be00, 0x0e3a567c, 0x08757516, 0x1958112f, 0x0f4814b5, 0x002550d6, 0x37114}}, - {{0x0fc2073c, 0x00605b67, 0x110920e3, 0x03186c55, 0x05335c85, 0x10d2a568, 0x0e43be30, 0x07aa3bae, 0x4d1d69}}}, - /* 13*16^17*G: */ - {{{0x077f88ba, 0x1de8f04c, 0x06fb4dbb, 0x100ef3dd, 0x02cc3509, 0x11974275, 0x04f8d2d6, 0x09913085, 0xcc9821}}, - {{0x07885278, 0x123177e4, 0x0d531382, 0x0c1cfdde, 0x163abeff, 0x053233be, 0x1ea44531, 0x1d1ca4b5, 0x4ebb4}}}, - /* 15*16^17*G: */ - {{{0x1d70187c, 0x0b98ca6f, 0x19031fc5, 0x1590c536, 0x15bf8751, 0x12f01487, 0x043e6053, 0x00534854, 0xad3ae}}, - {{0x1fc775a7, 0x0ffe7a59, 0x18a83d9f, 0x09f6102c, 0x1b33dd03, 0x0af82fd5, 0x1b8de171, 0x1a8eb108, 0x2af040}}} - }, - { - /* 1*16^18*G: */ - {{{0x10aae231, 0x0557d68b, 0x1e5adf18, 0x0970a4f3, 0x1756f519, 0x0411c933, 0x0fca17c9, 0x0d32ec3c, 0x1d35c9}}, - {{0x0cd6ac71, 0x033831d6, 0x0699bc56, 0x0b1548e5, 0x1f810edf, 0x055b1175, 0x008c7598, 0x16c5bec1, 0xc7226c}}}, - /* 3*16^18*G: */ - {{{0x0cfa3fac, 0x1069ff0c, 0x0ae064f3, 0x1b0d3f86, 0x1803023a, 0x1da2eb06, 0x0d338b3a, 0x08f4da44, 0xdf3d40}}, - {{0x0fc7d419, 0x03136c9c, 0x020a5d7d, 0x0c79c92d, 0x19dbfafd, 0x10f94e07, 0x036c92a8, 0x0a453fa8, 0x48fa6f}}}, - /* 5*16^18*G: */ - {{{0x1f1322d4, 0x16c92d70, 0x13372eeb, 0x14d28095, 0x1f822cb0, 0x16265188, 0x0513a879, 0x0d563f2c, 0xdbb2af}}, - {{0x0a8c5311, 0x12495a7a, 0x1f8e97ea, 0x1f92f0f6, 0x13f9d9a9, 0x068f6b21, 0x186a86d8, 0x1725e26a, 0xebae75}}}, - /* 7*16^18*G: */ - {{{0x01138f7b, 0x034d19e5, 0x1cba3cbc, 0x03927c5b, 0x15f0bc4c, 0x05133fcd, 0x1f532bea, 0x0b2fe4c3, 0x1dba7b}}, - {{0x039bf268, 0x0416c63d, 0x008cb037, 0x0dcd6f33, 0x007f5813, 0x08035dc0, 0x1f18eb86, 0x07cbbcfd, 0x6aaf7d}}}, - /* 9*16^18*G: */ - {{{0x179a70af, 0x0d43bb80, 0x1937ca23, 0x1a853536, 0x105c6ec9, 0x1416afa9, 0x0674c6ef, 0x0884c4d2, 0x348a2e}}, - {{0x07b66e82, 0x142430a4, 0x184f96b2, 0x0f858b6f, 0x10105b2a, 0x1de70011, 0x1a0231d1, 0x0f3eab47, 0xa1b9fa}}}, - /* 11*16^18*G: */ - {{{0x04d3a201, 0x1a3b6bb6, 0x06e7bbf1, 0x124d49fb, 0x0df5f36e, 0x07dfc98d, 0x1872a1a9, 0x0a333393, 0x3a4ec8}}, - {{0x08635f89, 0x0c2cb7bb, 0x0f3556f7, 0x063cb5ee, 0x14a8b27d, 0x1e08d23b, 0x0b79a780, 0x1f05ec4c, 0xc99a2e}}}, - /* 13*16^18*G: */ - {{{0x1c978567, 0x17d7397e, 0x1707e607, 0x1b4d1081, 0x1f60be15, 0x1aeb17f8, 0x115e13a4, 0x1b10669a, 0xb1ba52}}, - {{0x162dc291, 0x1fd24151, 0x1f35029d, 0x08d96175, 0x1b78fd9a, 0x1e7b89ed, 0x0e9df17d, 0x18f50d28, 0xa46bae}}}, - /* 15*16^18*G: */ - {{{0x12350da1, 0x11477528, 0x1d7c10f4, 0x0298ef82, 0x12c2f194, 0x1bdcb4b0, 0x0bf49c62, 0x07b1de55, 0xae6eb}}, - {{0x10a14bc3, 0x19f04f9b, 0x10f692f1, 0x08d1e1da, 0x189b566d, 0x1a7bfe20, 0x12b8b740, 0x13f6c00d, 0x99cdee}}} - }, - { - /* 1*16^19*G: */ - {{{0x150e4f5f, 0x0b957543, 0x19f83995, 0x0b95354a, 0x0f29acbf, 0x187bd501, 0x0bbce23f, 0x0b30896b, 0x55d9a9}}, - {{0x1ca97db0, 0x02c75bb5, 0x0b46c572, 0x10218c67, 0x0ec524c9, 0x0ba7de64, 0x080dd9b5, 0x1354bb5a, 0x69cb7f}}}, - /* 3*16^19*G: */ - {{{0x1eb142e9, 0x0354c43f, 0x01ff3ef0, 0x0dd60c8c, 0x0d480c17, 0x0341c7d7, 0x1845e536, 0x1d7c8de7, 0x4b0043}}, - {{0x02c68552, 0x0596296b, 0x04962201, 0x06521e74, 0x02d870f2, 0x04231b11, 0x106b6c5e, 0x047461b5, 0x173ccf}}}, - /* 5*16^19*G: */ - {{{0x17f1aa96, 0x16ea5738, 0x1fd9207d, 0x0f4bee69, 0x063c513d, 0x06e5db9a, 0x1f08c9ca, 0x0f3255ad, 0x79ead2}}, - {{0x0b0a3fb7, 0x05d38e72, 0x0ce556a0, 0x09ae2223, 0x16542de8, 0x01c9ab12, 0x0afc69b9, 0x19ff755b, 0xa95de9}}}, - /* 7*16^19*G: */ - {{{0x193a9ae6, 0x002e5397, 0x1dfe2b72, 0x01540b38, 0x1743f8ee, 0x0f1b18d6, 0x073ad49e, 0x0a1e49b4, 0x318e00}}, - {{0x1c447d79, 0x025ab4a6, 0x14ea3b86, 0x06dfb75d, 0x04bd0945, 0x1e528f28, 0x0a67d345, 0x022339d2, 0x2792ee}}}, - /* 9*16^19*G: */ - {{{0x1d30585a, 0x1bca2da6, 0x14052544, 0x1f143546, 0x0a4b495f, 0x197a4860, 0x10eee200, 0x0e3a5e3c, 0x6bbc54}}, - {{0x148b05f3, 0x17cbf779, 0x1e7ab65a, 0x00850205, 0x026be09f, 0x1158af91, 0x07c12a7b, 0x136182a5, 0x467b18}}}, - /* 11*16^19*G: */ - {{{0x00136275, 0x1127c046, 0x01dd4d49, 0x0f8002e7, 0x00d78a64, 0x0d487bbb, 0x144626d4, 0x18c2183f, 0x940148}}, - {{0x1997aa47, 0x0b1d088a, 0x13869097, 0x1675cac0, 0x1d6695d4, 0x06255a82, 0x13ddea89, 0x0be26cf4, 0x6967f9}}}, - /* 13*16^19*G: */ - {{{0x1f0fe850, 0x1d09ee9c, 0x0469ef70, 0x1a4617ca, 0x1f98e864, 0x164946d2, 0x127246d5, 0x124c0736, 0xc41833}}, - {{0x1d38666d, 0x1ac7a235, 0x0682bc04, 0x14fd93c0, 0x1d558065, 0x0aea1357, 0x1debae02, 0x19391a81, 0x3537fe}}}, - /* 15*16^19*G: */ - {{{0x14937fbe, 0x1f2209bb, 0x146e9bb3, 0x009bbbe0, 0x0f546c0b, 0x0000d7cb, 0x15c38305, 0x1ec4f8cf, 0x2baba3}}, - {{0x173a1df4, 0x1c872aba, 0x0b204424, 0x1b896321, 0x0abc9ec2, 0x1866c927, 0x0e235e0e, 0x0cb64411, 0x77e5c7}}} - }, - { - /* 1*16^20*G: */ - {{{0x0351964c, 0x069e96f2, 0x1504b075, 0x12486ede, 0x15c08346, 0x1e50c2ba, 0x11feb96a, 0x0b37c518, 0x6e29f9}}, - {{0x163fd88f, 0x0c4125ea, 0x02fed8c4, 0x0818a4f4, 0x0246def6, 0x163660c2, 0x0bd9414b, 0x13ea01e6, 0x34565d}}}, - /* 3*16^20*G: */ - {{{0x0c0e49cc, 0x1ca8081c, 0x1150034f, 0x065b50b7, 0x140ed412, 0x046f65db, 0x1dbb760a, 0x152f12e1, 0xd691d4}}, - {{0x100f4152, 0x085da60e, 0x1410fcb7, 0x17c3a055, 0x00c52ac5, 0x1edabb1f, 0x0e5fdfee, 0x10e96f1e, 0x7a79e7}}}, - /* 5*16^20*G: */ - {{{0x0b9b930a, 0x0c3b5cf9, 0x0c3d63cf, 0x026a548a, 0x1bc49deb, 0x1befbd3d, 0x1f177b96, 0x08d45c1a, 0x2a5d68}}, - {{0x1b2caeca, 0x17f9a2f9, 0x09c5a161, 0x16e686bc, 0x0ab58ea5, 0x181c81e2, 0x1db79733, 0x012d0ec8, 0xdc3d7c}}}, - /* 7*16^20*G: */ - {{{0x016959ef, 0x13ee5e94, 0x076d66be, 0x13a0ace8, 0x15df8767, 0x18a09713, 0x1498bc10, 0x0d471376, 0x876449}}, - {{0x00cd5010, 0x10188e5f, 0x1e78fc59, 0x0d5a82e5, 0x1961f285, 0x0093cb76, 0x1ff6782d, 0x1ac3a005, 0x599b69}}}, - /* 9*16^20*G: */ - {{{0x1aef0f84, 0x1ca04e71, 0x071d6e58, 0x16a0d50e, 0x1b8cab0b, 0x1fd60bd6, 0x06c4cf78, 0x1790248e, 0x94d393}}, - {{0x178ba7c1, 0x0d730dc9, 0x0b3c4aa1, 0x1e804ca1, 0x19a07dd3, 0x1e1c3591, 0x0fc87872, 0x169d696c, 0x5a826a}}}, - /* 11*16^20*G: */ - {{{0x0ffed1b7, 0x0a2abc27, 0x12a8ed3b, 0x17a24cac, 0x0bd2ee2d, 0x04b8169a, 0x18b745d4, 0x141113c9, 0x4a72b5}}, - {{0x1601dc5f, 0x0f94fec4, 0x1366116d, 0x0c971d8e, 0x0ea9685e, 0x1fe023e4, 0x038b230c, 0x1d4943a4, 0x3531e6}}}, - /* 13*16^20*G: */ - {{{0x10467317, 0x1021f92e, 0x16461a80, 0x03b883b1, 0x07900914, 0x13797d6f, 0x18569e19, 0x1f8b46e3, 0xd7f01c}}, - {{0x0f7d014e, 0x05cabeae, 0x1fef6257, 0x002e86d2, 0x1ef5728a, 0x10a0360a, 0x109bb1cd, 0x1b30ee4d, 0x888dbb}}}, - /* 15*16^20*G: */ - {{{0x1dea02c1, 0x1ebac853, 0x1d021f0e, 0x17736f8e, 0x11206e4f, 0x1fcec8f1, 0x1c6efa02, 0x173eef86, 0x7e50a0}}, - {{0x0d45c201, 0x1e4a36ff, 0x0386ca0c, 0x07269e2b, 0x19517742, 0x178eedc5, 0x0a7185b1, 0x0789c1fc, 0xc3405d}}} - }, - { - /* 1*16^21*G: */ - {{{0x0cb71280, 0x053ea088, 0x1158c44a, 0x0384b350, 0x1458ec14, 0x17783cb7, 0x1b67003c, 0x13d657fd, 0xff046a}}, - {{0x1ec33919, 0x161938fa, 0x0a121e9f, 0x16dcdd7a, 0x0fcc9012, 0x16edeea6, 0x085c2807, 0x159812a7, 0x432f55}}}, - /* 3*16^21*G: */ - {{{0x13ca3ce3, 0x193c1551, 0x1a9df897, 0x1630cb56, 0x0231fef2, 0x1e0fa989, 0x134c6a56, 0x0ba29a7b, 0xf717c4}}, - {{0x10ed88e9, 0x04a666da, 0x05db7c1a, 0x07c57bb8, 0x1ee55af5, 0x0a768556, 0x04b72a6d, 0x0ed1932a, 0x422c40}}}, - /* 5*16^21*G: */ - {{{0x1b28cd88, 0x1b42186a, 0x0a6f66b0, 0x1c12ea05, 0x1b32c738, 0x1ab13fc8, 0x0c830bfa, 0x169e66e1, 0x60e269}}, - {{0x0be2066c, 0x14dd4521, 0x1211ac38, 0x0ac2718d, 0x0df76024, 0x08c44b94, 0x02a58248, 0x0b4caf3e, 0xfe0017}}}, - /* 7*16^21*G: */ - {{{0x061cc594, 0x09d8c994, 0x1414dac5, 0x17ba2e09, 0x06855964, 0x1d02fe5a, 0x05242a96, 0x143cce73, 0x799297}}, - {{0x0f8d309e, 0x14888490, 0x0b1ba427, 0x150d5c17, 0x1e50127d, 0x088c3c4f, 0x06c3841a, 0x119aa479, 0x132ff1}}}, - /* 9*16^21*G: */ - {{{0x172d0aba, 0x04623d12, 0x1f86c5b7, 0x03215b8e, 0x1241dd8d, 0x0775aa80, 0x01c1e7b8, 0x09dfc850, 0x2d9be9}}, - {{0x11fa0876, 0x07b98fd9, 0x0dc439be, 0x038cf311, 0x1d91bbad, 0x1c9a5b0c, 0x0288a57b, 0x16bd3d13, 0xa9708d}}}, - /* 11*16^21*G: */ - {{{0x155e8732, 0x0603da41, 0x1b478c78, 0x0c5dbca0, 0x0c243cfa, 0x03298a49, 0x0859832f, 0x13d91048, 0x212703}}, - {{0x00cacc07, 0x0c91afa3, 0x15ef5a35, 0x07e7e775, 0x10a5cd69, 0x1cedddbf, 0x16a80652, 0x0de475c6, 0x85219c}}}, - /* 13*16^21*G: */ - {{{0x12083347, 0x0489a067, 0x0694c898, 0x15c7ccee, 0x0b42b9e1, 0x07ad5d01, 0x03ae5eb4, 0x09657b1c, 0xfab5b5}}, - {{0x1d38fd2f, 0x0664e6c1, 0x09a6bea7, 0x1c6f4c2d, 0x03eafc05, 0x15791305, 0x0540f21e, 0x02c30da8, 0x5758d1}}}, - /* 15*16^21*G: */ - {{{0x1f572554, 0x1b3930a7, 0x1d36adcf, 0x085c8e31, 0x0a56ff38, 0x0b3b85bf, 0x1f295e42, 0x0ae4f2e3, 0x685d27}}, - {{0x00979e03, 0x18d7b685, 0x1d33292d, 0x15545581, 0x0377cc58, 0x0e4020f0, 0x17f01b8e, 0x18441568, 0xa82f89}}} - }, - { - /* 1*16^22*G: */ - {{{0x05852e50, 0x1e859266, 0x15c33171, 0x18fcc79e, 0x07eff8b2, 0x1511a4f7, 0x016307e6, 0x1bffd576, 0xe486c7}}, - {{0x0ecf107d, 0x15d1e56d, 0x04baf619, 0x08c7ac67, 0x1e05a694, 0x052e4fa8, 0x04ba7ba2, 0x1daac0d4, 0x51fd75}}}, - /* 3*16^22*G: */ - {{{0x1c881907, 0x1355322a, 0x002c76b1, 0x190ce6b6, 0x00415142, 0x02e63357, 0x0df25904, 0x1771c5ff, 0x4acf44}}, - {{0x163cdafc, 0x0935a2b7, 0x1612c94a, 0x0a565ef9, 0x05457993, 0x142d6885, 0x01ecd510, 0x1fc1ffab, 0xad99a8}}}, - /* 5*16^22*G: */ - {{{0x1439383f, 0x15d7034b, 0x0068aaa0, 0x1f4ac56c, 0x14eec34f, 0x11496411, 0x192775ea, 0x10683114, 0x4ddf22}}, - {{0x03d52337, 0x197d40c1, 0x07e09a58, 0x0ba08a26, 0x00c6b3dd, 0x174be9c6, 0x00f5d2ae, 0x0f61b231, 0xe71c62}}}, - /* 7*16^22*G: */ - {{{0x1822b4f4, 0x16e440aa, 0x1977e3c9, 0x0a75f9f0, 0x1f39ba53, 0x14f03cbd, 0x1e784cd3, 0x0f226f30, 0x58b065}}, - {{0x0c60fbca, 0x103d4108, 0x13949659, 0x0940edf8, 0x0836c1ca, 0x12af8131, 0x10dc09f2, 0x14828b45, 0x55a98a}}}, - /* 9*16^22*G: */ - {{{0x14fe39b0, 0x195e8861, 0x0d37b452, 0x02e79ae9, 0x01f358a3, 0x020e119c, 0x14a5d8e9, 0x0b14c966, 0xa345e0}}, - {{0x0baf79dc, 0x184a405d, 0x0da77d4d, 0x18d4eb56, 0x1d949ce8, 0x02ad947c, 0x1c3e47c2, 0x14dab954, 0x7c6dd5}}}, - /* 11*16^22*G: */ - {{{0x126695fb, 0x1df03dcc, 0x0eb7b84f, 0x01773fc8, 0x0ccb6afd, 0x05c3a36b, 0x0b20806b, 0x10603214, 0x3a10bf}}, - {{0x04eb7e47, 0x0ae39595, 0x1514a21d, 0x1fd4d887, 0x065c5f28, 0x1c243445, 0x1c55d880, 0x167d8344, 0xb5d585}}}, - /* 13*16^22*G: */ - {{{0x1d2c8614, 0x00d474b3, 0x16116589, 0x012c14c0, 0x12876a7b, 0x1ad61848, 0x10cf2973, 0x1f592020, 0x12fbdd}}, - {{0x18b0b174, 0x03bde0ea, 0x067d257c, 0x04b04738, 0x0cfb54d4, 0x08a14669, 0x01a0cff6, 0x1a5204f0, 0xa5ff20}}}, - /* 15*16^22*G: */ - {{{0x0d9aeeaf, 0x1fbb2181, 0x16bf2194, 0x00f29940, 0x19a3005d, 0x11d0fd7c, 0x01ddee2b, 0x0b34572f, 0x8bece8}}, - {{0x05f9eb5e, 0x0d06d1e4, 0x02603dd5, 0x04071edf, 0x1c368bb2, 0x0885877f, 0x07f3eee2, 0x1898a0ca, 0x35f933}}} - }, - { - /* 1*16^23*G: */ - {{{0x0b519178, 0x05b5c761, 0x0f47f161, 0x17333148, 0x0ee0ab63, 0x067c5af1, 0x10c33f82, 0x0976bca0, 0xf41d7f}}, - {{0x0a6a3551, 0x1d0b32ee, 0x06787e69, 0x135986d2, 0x02347ab4, 0x16f1fd54, 0x035bc411, 0x17d7b35f, 0xe6a669}}}, - /* 3*16^23*G: */ - {{{0x1e3501d9, 0x138989f0, 0x187f22c6, 0x1059c376, 0x13e611be, 0x0b48eb16, 0x19fccccb, 0x0fdac5a9, 0xd65f82}}, - {{0x0b55759e, 0x1ca23637, 0x1cc2f86a, 0x02755aba, 0x187fc61c, 0x1e528b35, 0x03f94ded, 0x1f9e1352, 0xf24b60}}}, - /* 5*16^23*G: */ - {{{0x0f2ed84e, 0x1b17f120, 0x18ba84b2, 0x129be6bf, 0x0c0ade79, 0x045abc16, 0x10e17b9b, 0x16dbd60b, 0x55a9b5}}, - {{0x146495a3, 0x1cee2a82, 0x08363c15, 0x1f69ae25, 0x1f43ae8f, 0x068c876b, 0x06aca45d, 0x12d593e3, 0xc4f221}}}, - /* 7*16^23*G: */ - {{{0x075495a2, 0x1e967c38, 0x0f002ca6, 0x1c3d6c91, 0x08bc49fd, 0x0b05312a, 0x07687dfb, 0x0db8fb6e, 0x79de57}}, - {{0x0aec6fb8, 0x01646355, 0x04e9d1a0, 0x09ea12c9, 0x079c6831, 0x16d723ee, 0x0809e179, 0x01cd630d, 0x3b2d3d}}}, - /* 9*16^23*G: */ - {{{0x02d1e7bb, 0x17495664, 0x0465933b, 0x07ff206a, 0x1e55524c, 0x19b4acab, 0x12db8992, 0x0642c480, 0x8fe04e}}, - {{0x1a62e84f, 0x13acd686, 0x011aa336, 0x02c8c8de, 0x1b34e5b9, 0x0752bd46, 0x163f975b, 0x0a045ec6, 0x3987db}}}, - /* 11*16^23*G: */ - {{{0x09f7c360, 0x00d25763, 0x00217084, 0x19aaefc9, 0x024e24ef, 0x0531b469, 0x03c542a5, 0x012afdbf, 0x3af499}}, - {{0x087d7a7b, 0x1cee1553, 0x18225734, 0x0a9bac4e, 0x13c33485, 0x03241739, 0x04754c7e, 0x061a763b, 0xd17060}}}, - /* 13*16^23*G: */ - {{{0x170373be, 0x0d291d73, 0x18f4ed4e, 0x1a0c4424, 0x07c4e213, 0x0179ff11, 0x01db0696, 0x0b8c4790, 0x31a7e0}}, - {{0x090950a8, 0x16bc3c87, 0x08950c23, 0x105732a3, 0x1db1e8ce, 0x05571a11, 0x199c086c, 0x1e7f1a3b, 0xc23d2e}}}, - /* 15*16^23*G: */ - {{{0x0b923b45, 0x0130876e, 0x0f892f6d, 0x153f7fe2, 0x18458a33, 0x09a316ec, 0x08fb4554, 0x09026ada, 0x740a66}}, - {{0x11ecaa5f, 0x0609f1c2, 0x121202ea, 0x0f32720f, 0x006cfa89, 0x03c453ab, 0x08fcbd39, 0x1184b9aa, 0x7f6d04}}} - }, - { - /* 1*16^24*G: */ - {{{0x1512218e, 0x025549cb, 0x1280506a, 0x0a4360e9, 0x0e902e9a, 0x059d0c51, 0x1e995e20, 0x0cc254ce, 0x4a5b50}}, - {{0x0c4f3840, 0x1f56d3d2, 0x189b6742, 0x1b62a833, 0x07d40626, 0x027df0b1, 0x07c71098, 0x039d5811, 0xeb1346}}}, - /* 3*16^24*G: */ - {{{0x118473fd, 0x177a0712, 0x05cce454, 0x1204d78d, 0x0a9e3bbb, 0x155ccb94, 0x0e8214a4, 0x06466631, 0x106406}}, - {{0x185cf805, 0x1d30b173, 0x08740e6b, 0x1c321a18, 0x1883ccfb, 0x014f32c2, 0x0b63239e, 0x10477174, 0x9c6832}}}, - /* 5*16^24*G: */ - {{{0x053f30f3, 0x0b3f7643, 0x088e374d, 0x047da5db, 0x03f212b4, 0x0086aa04, 0x1efd7b69, 0x11fe0be4, 0x38c8ad}}, - {{0x0be9217b, 0x03073a4f, 0x1677cde8, 0x07f53052, 0x1641f658, 0x0b2a6ede, 0x045bce42, 0x0f0edb96, 0x83c261}}}, - /* 7*16^24*G: */ - {{{0x16ef1584, 0x01f3488d, 0x04832fd5, 0x1345471f, 0x1184277a, 0x1df1fe9a, 0x0c256163, 0x179d0c17, 0x6fcb8c}}, - {{0x1d3110f2, 0x1e4fbe10, 0x0290c970, 0x05edad25, 0x0459d599, 0x0411d618, 0x15db5530, 0x07b16e43, 0xa8f1a8}}}, - /* 9*16^24*G: */ - {{{0x062255ef, 0x07a89a02, 0x1901e33a, 0x0e3a68ce, 0x1b0ee50d, 0x0e486f22, 0x0af5cd45, 0x1727eedc, 0x7811d0}}, - {{0x1be3252e, 0x0c5f7cd8, 0x150dda29, 0x1da0a275, 0x05132f51, 0x174a00dc, 0x11e95300, 0x06e176b5, 0x99bb76}}}, - /* 11*16^24*G: */ - {{{0x153d2ea7, 0x1f088789, 0x1ea19424, 0x1a104747, 0x16f2a9ee, 0x194eafc0, 0x033831ed, 0x1289516a, 0xc3583d}}, - {{0x0b667bbd, 0x18c1f4f0, 0x17c710ca, 0x124b4ca2, 0x1cccdd6c, 0x113f7c4f, 0x043d6591, 0x1812723d, 0x53967a}}}, - /* 13*16^24*G: */ - {{{0x102e3099, 0x16ef8702, 0x01005963, 0x04274cc5, 0x0d00d53f, 0x180c5dbd, 0x1407ce83, 0x05079bdc, 0xd591cf}}, - {{0x055ac5af, 0x1de13b2a, 0x045bf58c, 0x05dff116, 0x1c5825cf, 0x19869a85, 0x1b913944, 0x19f88f1c, 0x87ebe5}}}, - /* 15*16^24*G: */ - {{{0x12a940a5, 0x1932b231, 0x0539ac4a, 0x06bd6b48, 0x1e8714a4, 0x00f0d27c, 0x17130049, 0x0c25e646, 0xa0aa0d}}, - {{0x0b950422, 0x1fc41534, 0x146fe4e4, 0x1239bb4f, 0x01f3e6c6, 0x067cd00c, 0x10a066cd, 0x174edeec, 0x22340e}}} - }, - { - /* 1*16^25*G: */ - {{{0x008e2df0, 0x1146c1b8, 0x0a397dd9, 0x0764ae86, 0x00f5032c, 0x14efc5df, 0x065404b0, 0x017bc557, 0x2eb391}}, - {{0x1274eaae, 0x08866276, 0x1d97d242, 0x01a241d9, 0x0999f954, 0x1e9a46d2, 0x0ce9df4d, 0x0466e8e9, 0x3f29c0}}}, - /* 3*16^25*G: */ - {{{0x082867f0, 0x1815c25b, 0x06428e6f, 0x084dc436, 0x100f0a21, 0x08b53c04, 0x1388aaaf, 0x111cc98f, 0xf6e9db}}, - {{0x0f1861ea, 0x1ad9d788, 0x1c2d88d1, 0x08374bf2, 0x0d5b1270, 0x1dbb7460, 0x0dd20764, 0x016f5a55, 0x53ca79}}}, - /* 5*16^25*G: */ - {{{0x120d6102, 0x16e821c7, 0x114e5026, 0x1aa6f146, 0x19a5ef06, 0x0adcdb0c, 0x1275e170, 0x070ec1c8, 0xfd1ddb}}, - {{0x0e003b7c, 0x1053248d, 0x144e60f6, 0x1c322422, 0x1b700163, 0x0f8fbc41, 0x0e2bb6a8, 0x0e720a0c, 0xcb54b8}}}, - /* 7*16^25*G: */ - {{{0x02c77cb7, 0x1fb9a0ee, 0x17056dbc, 0x1b281205, 0x0698fef6, 0x139f32f7, 0x00767f92, 0x1844b332, 0x61a273}}, - {{0x1ddb25ed, 0x0a308fc0, 0x0b87dd21, 0x0b5b34e1, 0x10cc9c5c, 0x10cfaf75, 0x0f4fd3a8, 0x0669a75a, 0x5bbacd}}}, - /* 9*16^25*G: */ - {{{0x177d8976, 0x0cc6ab71, 0x03ca4b6e, 0x0e7471c6, 0x104b55e1, 0x164c114e, 0x06d932c0, 0x0cbdeec0, 0xcd2e8e}}, - {{0x0867bc22, 0x0b4b7a0e, 0x10a30144, 0x1dbc1e6a, 0x0ff68f60, 0x074796a3, 0x0c7ff0c7, 0x06c46854, 0xf58ead}}}, - /* 11*16^25*G: */ - {{{0x0c67c998, 0x04d98361, 0x13c6e198, 0x160fd547, 0x04c259a9, 0x0f545218, 0x1bed0089, 0x13870447, 0x9bd61f}}, - {{0x08199514, 0x1a057ce1, 0x1092c630, 0x1b383d20, 0x050fa927, 0x104b4b4a, 0x1d71723c, 0x01322d8d, 0x77b204}}}, - /* 13*16^25*G: */ - {{{0x0aafa568, 0x122f0bdf, 0x07889d9a, 0x1af52ee0, 0x1a016b4c, 0x13d2088b, 0x1dd44ab8, 0x09ef2e0e, 0x7afaeb}}, - {{0x01c1f2df, 0x16a9d17c, 0x14e408cf, 0x1cd28653, 0x1365a972, 0x0a09a820, 0x09f62574, 0x03267f7a, 0xc6efe6}}}, - /* 15*16^25*G: */ - {{{0x0e59ddeb, 0x1f381f28, 0x07a62a2d, 0x1cc5395a, 0x10c3b483, 0x0a60a4b5, 0x0be41876, 0x044fc482, 0xd9a002}}, - {{0x0f0af5a4, 0x19c9ffc0, 0x17c63397, 0x05517956, 0x10581856, 0x07c521b3, 0x08b10f18, 0x1f276f40, 0x975eb2}}} - }, - { - /* 1*16^26*G: */ - {{{0x1ecca7e0, 0x19cd2f51, 0x10cccfb1, 0x05931ece, 0x19428a7d, 0x119a9126, 0x08303fbd, 0x078b8f25, 0x7ef2ee}}, - {{0x152ac094, 0x015916ea, 0x0f4f480c, 0x0428a1bf, 0x009db81b, 0x1fa8eaf3, 0x004693d9, 0x04e61598, 0xafb686}}}, - /* 3*16^26*G: */ - {{{0x033f0ffa, 0x179ac1ed, 0x001e905c, 0x09958c23, 0x1c641cae, 0x12bb64ec, 0x0b35d892, 0x04e2d10f, 0x370457}}, - {{0x00fa35ac, 0x1f69fe33, 0x02fcbf17, 0x147d6f55, 0x1ff4c57b, 0x1a5dc44a, 0x1d3e106e, 0x18c34e3c, 0x5c6408}}}, - /* 5*16^26*G: */ - {{{0x12603513, 0x037cc329, 0x1280254f, 0x0c9b8d02, 0x1b697f6c, 0x15216045, 0x0dc8b47a, 0x1ca8531b, 0xc2a979}}, - {{0x1c3505b9, 0x0baad961, 0x05451265, 0x0ccdbe90, 0x1ce7e8cc, 0x0040c9ad, 0x1721509e, 0x0943cdd6, 0xef9dd7}}}, - /* 7*16^26*G: */ - {{{0x19fdee3e, 0x1237fd8e, 0x0c0b8079, 0x1f2ee2d8, 0x1afb9681, 0x18ae6d8b, 0x07845cae, 0x0d563f8b, 0xc9f418}}, - {{0x0ced22e7, 0x0f33662e, 0x1637d3d4, 0x02a638f6, 0x1214344e, 0x18a15c41, 0x0d0437dc, 0x0fe79674, 0x9420a}}}, - /* 9*16^26*G: */ - {{{0x0814f217, 0x0659f33a, 0x15b3febc, 0x147ef70e, 0x1b6a4a9d, 0x00127c1d, 0x095ac228, 0x0b700900, 0x27dcf5}}, - {{0x0cf2ed4c, 0x064a5ac4, 0x07f0efc5, 0x1d1f5632, 0x02a380a4, 0x1d584a50, 0x17b82f22, 0x09ea91ab, 0xbe18f5}}}, - /* 11*16^26*G: */ - {{{0x1b9e310c, 0x0d1527ea, 0x133f529d, 0x19cb7370, 0x19187313, 0x125619ca, 0x092e7234, 0x00764add, 0x3b8b02}}, - {{0x0d19fc9b, 0x0a0b302d, 0x0cc160e6, 0x1f009705, 0x117a6a8f, 0x171bbe20, 0x056f585d, 0x09d6066c, 0x91117c}}}, - /* 13*16^26*G: */ - {{{0x07615b36, 0x18f0f15d, 0x1cf057e3, 0x1fe3d18f, 0x0c3372a4, 0x12037c8a, 0x00e84170, 0x118b7982, 0xa5e739}}, - {{0x09136d80, 0x0cc171ca, 0x0d84f013, 0x1f8326e1, 0x1f8c547f, 0x06f7950a, 0x1339190e, 0x11b92e9c, 0x1f3ff5}}}, - /* 15*16^26*G: */ - {{{0x0d3e10db, 0x1bd82197, 0x0e1c566e, 0x065c3591, 0x161fa477, 0x021ffa75, 0x0caf7b81, 0x06ab592a, 0xaad218}}, - {{0x0fa05fb3, 0x1a9ad5eb, 0x072e89d8, 0x064ee566, 0x18b4a720, 0x0f147eaf, 0x18ece9f7, 0x1dd75f05, 0x793b1e}}} - }, - { - /* 1*16^27*G: */ - {{{0x0c49e853, 0x180f8487, 0x1d781299, 0x16a6a532, 0x1a77aa9c, 0x12aa75af, 0x0bad5e00, 0x0c842c81, 0xe5141}}, - {{0x16405cb2, 0x1b71e89b, 0x127b8d8c, 0x10728321, 0x1030df56, 0x1a529d48, 0x11a49e3a, 0x1d4cb20a, 0xcf331c}}}, - /* 3*16^27*G: */ - {{{0x1dc6afad, 0x0511f8e0, 0x1e03d1b1, 0x07b2452b, 0x0c6771bd, 0x092fb260, 0x1c977a3b, 0x0cecb959, 0x977ba0}}, - {{0x1d7faa09, 0x0b166e28, 0x16868553, 0x0b42483b, 0x1d9992f7, 0x1d506840, 0x1e8a20cf, 0x0875db71, 0x1bc3bc}}}, - /* 5*16^27*G: */ - {{{0x1d81983b, 0x0fba2ebe, 0x03aade19, 0x041fa05f, 0x1facc102, 0x1b03b8ff, 0x0af930f2, 0x0c254247, 0x3bc328}}, - {{0x11a637b6, 0x0ccf9428, 0x0ee1236f, 0x0910b51f, 0x00a7f433, 0x182e5578, 0x0880942c, 0x0dbda191, 0xa6b582}}}, - /* 7*16^27*G: */ - {{{0x17c8537b, 0x0fbb60ff, 0x07e0185d, 0x19596e88, 0x16c21ca8, 0x17f0e57c, 0x12b146eb, 0x1d1d3d80, 0x67cba4}}, - {{0x185726da, 0x075a82b9, 0x100fdb87, 0x1e087da0, 0x1cc047bd, 0x030f97d8, 0x06128787, 0x1dbd4391, 0x5b65ed}}}, - /* 9*16^27*G: */ - {{{0x0b6a1226, 0x14114672, 0x15194e4d, 0x13d39156, 0x0f3922af, 0x1eb43234, 0x064c681c, 0x14daae93, 0xc5958e}}, - {{0x066961d4, 0x00803446, 0x08422e47, 0x0edb7d74, 0x1bd82968, 0x0cb8cea2, 0x175fb2f8, 0x105ee1ef, 0xa031a8}}}, - /* 11*16^27*G: */ - {{{0x13713a52, 0x06f1fc2d, 0x0f1505f9, 0x0eb718d0, 0x05fa025c, 0x0061e3a5, 0x129f07f3, 0x1f05c84b, 0x83f07b}}, - {{0x1fb0ffb6, 0x088939fc, 0x1ed174a8, 0x0b6ec1c3, 0x1d3cfcab, 0x0a648963, 0x0ee4125e, 0x02144b71, 0xf2ec4f}}}, - /* 13*16^27*G: */ - {{{0x01ba21b0, 0x16cd0489, 0x109954e6, 0x0bb1a401, 0x00076d85, 0x00343757, 0x0b539bda, 0x09f36b48, 0x938dba}}, - {{0x0d5b4325, 0x1a6ccaf2, 0x0ada5f0d, 0x0377dc7e, 0x0b90bcbf, 0x16dee89b, 0x1959ba6a, 0x15135eea, 0x48afc0}}}, - /* 15*16^27*G: */ - {{{0x1f2b24f1, 0x09f24d31, 0x19daf3ed, 0x1d8c9ebf, 0x09779cfa, 0x0d5679da, 0x079cea07, 0x16ed9406, 0x7efa89}}, - {{0x0f8e4b83, 0x12ee2ea5, 0x15291575, 0x15be758a, 0x0ab211db, 0x09117e0b, 0x1bfde33b, 0x1d8c3e95, 0xa99ff3}}} - }, - { - /* 1*16^28*G: */ - {{{0x02f2b734, 0x034cdfcf, 0x007499fc, 0x0776b6aa, 0x0445779c, 0x13c3788b, 0x066818d2, 0x0533dd99, 0x224a02}}, - {{0x11ec7fdf, 0x007ac2a4, 0x11ebf421, 0x0e096ce7, 0x17fff07b, 0x0456c38e, 0x0ad05268, 0x1a536da4, 0xfa41a8}}}, - /* 3*16^28*G: */ - {{{0x1d254033, 0x0f497a3a, 0x091c1eb2, 0x0d050185, 0x0f240783, 0x0fd3445b, 0x1167f36b, 0x1dc2a79d, 0xe63502}}, - {{0x08f05e3f, 0x1455444b, 0x1f3cd81f, 0x02b41897, 0x1ce482c9, 0x14078384, 0x1846b5d3, 0x0f63ec43, 0xa7c583}}}, - /* 5*16^28*G: */ - {{{0x01e08f33, 0x1cb04d7f, 0x060b328a, 0x1ac63b97, 0x000e2b1f, 0x02e299f8, 0x1a1c4aad, 0x184e5d72, 0x5648de}}, - {{0x0269f1c9, 0x1776cd62, 0x045e5449, 0x0577e5c0, 0x1a2693e7, 0x1ae326bb, 0x010fd1e3, 0x0e0e11ee, 0x95fddc}}}, - /* 7*16^28*G: */ - {{{0x1ab98c43, 0x1dc1005f, 0x0fa2a938, 0x14aeeafc, 0x08f03c5b, 0x17c765f6, 0x149a5454, 0x13e6701d, 0x1941c1}}, - {{0x02abe217, 0x1d1086d1, 0x17d58e63, 0x0c5a7b71, 0x18c53d8f, 0x0d95247d, 0x0adb8adb, 0x068b2bb6, 0x8e66b8}}}, - /* 9*16^28*G: */ - {{{0x02d8c2f4, 0x13386882, 0x07006279, 0x064373ef, 0x18a0bd73, 0x1def1b26, 0x0f59fe9c, 0x063a2169, 0xfc5864}}, - {{0x1cfd274a, 0x08c6447f, 0x10ce2d00, 0x05960bd7, 0x1b4ab29e, 0x013cf35e, 0x055928cf, 0x0a59d15b, 0x99fade}}}, - /* 11*16^28*G: */ - {{{0x11ff0f18, 0x1583bf8e, 0x12690b44, 0x0179ce0a, 0x0d6c207e, 0x1d67cd79, 0x077aa6fe, 0x14d6189e, 0xee993b}}, - {{0x050782ad, 0x009c3b65, 0x03cb2c60, 0x0b901bee, 0x1bcfeb43, 0x0eac6742, 0x0cb07db1, 0x1b28af1b, 0xd87fd4}}}, - /* 13*16^28*G: */ - {{{0x0fdde3fc, 0x03d3fbaa, 0x18a33279, 0x11113c0a, 0x131fbe5a, 0x0e20d297, 0x090a3683, 0x0f55d3fc, 0xba4951}}, - {{0x0fcb7d01, 0x1f7e9197, 0x0a4b2aa8, 0x1c3903c6, 0x1359adfd, 0x121bb009, 0x16542d3b, 0x1c39528a, 0xa88fb6}}}, - /* 15*16^28*G: */ - {{{0x1d14da68, 0x0449517b, 0x14805cde, 0x0983f09b, 0x012b54a6, 0x07c139bb, 0x06e9378b, 0x02e8455e, 0xd3398f}}, - {{0x15a90e3d, 0x183b18da, 0x10514e8f, 0x0f4f245a, 0x0b020afc, 0x0968f451, 0x1583adc9, 0x1a9a3fa7, 0xa34ad4}}} - }, - { - /* 1*16^29*G: */ - {{{0x1789b84d, 0x13032d6b, 0x010738ba, 0x1abdc9a2, 0x093fe167, 0x0888dab2, 0x0d3336d7, 0x028ae6e9, 0x4a89a6}}, - {{0x018e3ea8, 0x1cdbebf8, 0x1c46667b, 0x1c500c68, 0x077e6a72, 0x154bcff3, 0x1570230b, 0x10fda901, 0x45b04e}}}, - /* 3*16^29*G: */ - {{{0x02e14c34, 0x13b591ba, 0x0c9d64db, 0x18bd7c0e, 0x177c9834, 0x19873c09, 0x1e4a8aa6, 0x09150e1d, 0xcc9f96}}, - {{0x14be556b, 0x0490abd4, 0x031f8162, 0x122fe305, 0x089db299, 0x1319edf3, 0x05bd0c45, 0x0596bf19, 0x829bff}}}, - /* 5*16^29*G: */ - {{{0x15de4837, 0x1ef3642c, 0x1979d3a1, 0x050d905b, 0x18a758d4, 0x060e856c, 0x106bd3d6, 0x143f55a0, 0x5c618d}}, - {{0x034a7dee, 0x05c2c1db, 0x089e32b0, 0x06cc92c8, 0x19671ecd, 0x071c24fe, 0x067622e0, 0x02f79e74, 0x4c7191}}}, - /* 7*16^29*G: */ - {{{0x16635396, 0x0c49a132, 0x029b0309, 0x15c2b46d, 0x1c11ef2c, 0x116ef338, 0x1a8b9617, 0x0d522b40, 0xab8a88}}, - {{0x1fc92c23, 0x11c7d351, 0x14b5ab5f, 0x0c0b07ef, 0x0d5abb93, 0x1fc1245e, 0x0c6559a3, 0x0f8fb508, 0xe773cf}}}, - /* 9*16^29*G: */ - {{{0x01df49c3, 0x0121f2c4, 0x01a68d6b, 0x11210cda, 0x1604aa9e, 0x125c5b4a, 0x0e337b19, 0x0a284830, 0x62b7c8}}, - {{0x16341c21, 0x1f1b407f, 0x1f2d5426, 0x0a76c270, 0x122db8fb, 0x0b5746a4, 0x0cd56522, 0x042a55c0, 0xa71ed6}}}, - /* 11*16^29*G: */ - {{{0x044f47d0, 0x178a3717, 0x07c00bce, 0x13ebbd29, 0x1c9791b5, 0x0f8434ef, 0x0717e5d4, 0x17f48b91, 0x4897bd}}, - {{0x0e1320bf, 0x0f280d56, 0x1c046560, 0x1b400351, 0x1093f526, 0x1654a0bd, 0x0e30d3a3, 0x01ef5f82, 0xe77dc2}}}, - /* 13*16^29*G: */ - {{{0x11581bd3, 0x10de74db, 0x0641e1b9, 0x14631bec, 0x02f2ecbf, 0x103ded39, 0x15db1147, 0x16aac90c, 0xeb1cfc}}, - {{0x1cd98a09, 0x1e746cc2, 0x0f469916, 0x19e6338b, 0x16e3cb39, 0x002f5f26, 0x14814110, 0x0eae3f29, 0xfd47ce}}}, - /* 15*16^29*G: */ - {{{0x073a3111, 0x00915edb, 0x132063e4, 0x105c82f6, 0x176a018b, 0x05617392, 0x18270355, 0x177c91c3, 0xa1fd8b}}, - {{0x18c704ef, 0x043f3285, 0x15445a6e, 0x18928b7f, 0x116c0c7e, 0x1f03de04, 0x015a6f5d, 0x11469461, 0xad215d}}} - }, - { - /* 1*16^30*G: */ - {{{0x12bd05a0, 0x01c64253, 0x07f2034d, 0x0466fa16, 0x11f90ba8, 0x1ccaf9b6, 0x0173b70b, 0x06c74631, 0xe5e892}}, - {{0x01a69f5d, 0x09b6f15f, 0x14266bb2, 0x0732b739, 0x15c3eca7, 0x1580f3cd, 0x1f484c07, 0x1c9b4370, 0x77439d}}}, - /* 3*16^30*G: */ - {{{0x01467d6b, 0x184a9408, 0x0892d453, 0x1ae252a5, 0x0f1d8357, 0x0308b216, 0x13d74406, 0x1bf286b9, 0x5d2393}}, - {{0x11bc5458, 0x1e339e35, 0x011cea01, 0x0e0f4ea2, 0x0f46d72a, 0x0c2d96ad, 0x1df5eb2f, 0x1e4c7fa1, 0xe66e63}}}, - /* 5*16^30*G: */ - {{{0x1d159f7a, 0x058f49e4, 0x10b9643c, 0x127539e4, 0x1873fecf, 0x1d95e97f, 0x04fceb73, 0x14a75571, 0x453657}}, - {{0x0a02fb78, 0x0e115b84, 0x07769766, 0x0937a9d0, 0x1c7286f9, 0x18489d00, 0x171768bb, 0x1ff10047, 0xbfb5ae}}}, - /* 7*16^30*G: */ - {{{0x146cb42a, 0x0f6f6f9e, 0x08e424cc, 0x0a50a74e, 0x173e7bc0, 0x16f5509e, 0x11193452, 0x1960f609, 0x435b54}}, - {{0x1af72dd0, 0x1f126f6e, 0x0e5269ad, 0x1898f286, 0x0585d5ed, 0x12a660f0, 0x086927d2, 0x063c8e31, 0xd726c0}}}, - /* 9*16^30*G: */ - {{{0x1d2fc263, 0x0beca0d8, 0x19755eea, 0x1f027cb6, 0x1da0e89c, 0x023e2709, 0x15f867ef, 0x033c29db, 0x1805b8}}, - {{0x10870ec7, 0x132385a7, 0x147b2bc9, 0x1835f1ca, 0x131489c8, 0x0d5435e5, 0x05c56163, 0x05012870, 0x101f64}}}, - /* 11*16^30*G: */ - {{{0x0076d1df, 0x1158db86, 0x1fe86ce6, 0x1c410284, 0x15f45f41, 0x1d45153b, 0x053d0185, 0x086cda63, 0xc73aaf}}, - {{0x05ad6605, 0x13cea8b7, 0x024dc834, 0x16af4a3b, 0x0dcfef75, 0x00df1dde, 0x05bbe738, 0x0d3d99f2, 0x9201ec}}}, - /* 13*16^30*G: */ - {{{0x05a745c7, 0x1b41fbcc, 0x1fab01f4, 0x144e7182, 0x152c1bc8, 0x0db57b0e, 0x1b49dc62, 0x11efab86, 0xe3ddee}}, - {{0x178efcc2, 0x1cf84a03, 0x1409ec68, 0x1185e2f7, 0x1d8f47c2, 0x0dc3553d, 0x0e1c6f94, 0x1a723265, 0x487199}}}, - /* 15*16^30*G: */ - {{{0x0d4170b3, 0x06399f42, 0x0e8d61fd, 0x0882adf8, 0x0a1d5401, 0x1508c360, 0x0796bda2, 0x1b8406be, 0x45c78d}}, - {{0x111faa1d, 0x177d1f6a, 0x08331cc5, 0x008cc0c8, 0x13a512cb, 0x14e8a27d, 0x1032c386, 0x08c471ab, 0x11af99}}} - }, - { - /* 1*16^31*G: */ - {{{0x1ab5c2cf, 0x0abccc97, 0x0c213788, 0x05e1843f, 0x090a4531, 0x03a0827d, 0x1d41c79d, 0x0863c443, 0xe4107e}}, - {{0x0b955c2b, 0x014201f9, 0x1ec90434, 0x1125b9fe, 0x02b323c7, 0x03343a0e, 0x1268e523, 0x1cd9ee03, 0x1e5f11}}}, - /* 3*16^31*G: */ - {{{0x09b67b7e, 0x1dadb1e0, 0x070f5216, 0x16f65722, 0x01142766, 0x0b80ef5e, 0x1df6ab3e, 0x0cbdc2f3, 0x85df9a}}, - {{0x030a36e3, 0x059c34f4, 0x0f1ba962, 0x15293a3f, 0x0297386f, 0x1eaf7f87, 0x0c22edad, 0x154735ec, 0xa6b4b7}}}, - /* 5*16^31*G: */ - {{{0x0b3510d3, 0x13e7ef30, 0x0875b904, 0x062cce09, 0x1292885d, 0x10c32e16, 0x1422a362, 0x1fcff3f9, 0x23c493}}, - {{0x1ae89a47, 0x1a317621, 0x0e76f5c4, 0x13c6bc4d, 0x1e6d8f79, 0x0ecf277f, 0x108c309d, 0x153c8682, 0xadf456}}}, - /* 7*16^31*G: */ - {{{0x0b49a71f, 0x06348c04, 0x089f0e6b, 0x0dd6d8c8, 0x035ddac3, 0x09f5d579, 0x03b77966, 0x016629ba, 0x94406d}}, - {{0x10b249a9, 0x18e0ca26, 0x18f3e511, 0x04e4c394, 0x1e897d0e, 0x1e484837, 0x05e73481, 0x0fd0c9be, 0x61aae7}}}, - /* 9*16^31*G: */ - {{{0x00e50c32, 0x0e6f19e3, 0x11a1cff9, 0x0212e5d1, 0x08d3877f, 0x1b6fadf7, 0x1050cdc4, 0x087bd10d, 0x3c2ebf}}, - {{0x0a93c124, 0x117c48be, 0x1a326627, 0x1a118735, 0x028e7be2, 0x1c9e9017, 0x06719496, 0x14d8bd07, 0xa703d9}}}, - /* 11*16^31*G: */ - {{{0x03a2355a, 0x041ecef6, 0x1812641c, 0x0a37e6b8, 0x15f12996, 0x12dc4f62, 0x1ae7ce47, 0x1ae4f281, 0x987536}}, - {{0x1bd05276, 0x063ae260, 0x0eb7337c, 0x1ed8f8d0, 0x17b48b85, 0x18e0a4f2, 0x05c90f82, 0x1ace9e22, 0x12f19a}}}, - /* 13*16^31*G: */ - {{{0x12f3ec22, 0x0782a3e2, 0x0a3e29ac, 0x10db9ee3, 0x0ddddd1f, 0x1c58bf79, 0x11a3c674, 0x08134988, 0xd515ac}}, - {{0x05a2863b, 0x10a31759, 0x06e01bb2, 0x0afbe006, 0x0bd464cd, 0x12420029, 0x0cb87a97, 0x02844893, 0x51c048}}}, - /* 15*16^31*G: */ - {{{0x1971c857, 0x0cd0f4d2, 0x19824fe9, 0x133cdb57, 0x1cf3ddec, 0x1e61c5b0, 0x02eb8914, 0x0930bcb6, 0x70b9d}}, - {{0x0ac8dfc1, 0x1ce5b1ee, 0x15186abf, 0x107b43d8, 0x13cfb33a, 0x0857231b, 0x09421ae0, 0x037fe96c, 0xed8046}}} - }, - { - /* 1*16^32*G: */ - {{{0x1789bd85, 0x1e427e4e, 0x05fab0d5, 0x0bfefb85, 0x0766efc3, 0x17eac463, 0x199fee60, 0x137ddb6b, 0x447d73}}, - {{0x12e25b32, 0x03f19e4b, 0x1eb94003, 0x09372b4f, 0x0aff73d3, 0x0eca9d25, 0x07bb84ba, 0x15706826, 0x2d4825}}}, - /* 3*16^32*G: */ - {{{0x1ea6db68, 0x1f0ccc76, 0x098cb09c, 0x15b0ac10, 0x0f4f6ddf, 0x06fcb2ef, 0x05fd62c5, 0x07c07940, 0xf8b653}}, - {{0x0e760da9, 0x0de92d85, 0x17283b5a, 0x1ae1bb38, 0x03ec66bb, 0x16ed2855, 0x1218bc11, 0x1ebd888a, 0xc30f4e}}}, - /* 5*16^32*G: */ - {{{0x1924e753, 0x16aea75f, 0x1e1c9f19, 0x02e60e59, 0x1fb755f0, 0x18c394f6, 0x11f1523b, 0x1a6ab050, 0xf35289}}, - {{0x0b13a20c, 0x122dae17, 0x0b43c12a, 0x05f8ae52, 0x01bd8c56, 0x0450da87, 0x0fee4f7c, 0x03d8bd82, 0x75c178}}}, - /* 7*16^32*G: */ - {{{0x1018017e, 0x1cae5b39, 0x17c8f5c8, 0x083fffad, 0x14e01af1, 0x0c2fdb39, 0x1c5920d0, 0x0e9b4882, 0xcfd06b}}, - {{0x0e0dff50, 0x0573df26, 0x1de5dde8, 0x0060f0d6, 0x07950003, 0x19cac3ed, 0x044e040e, 0x1536e575, 0xb647b7}}}, - /* 9*16^32*G: */ - {{{0x192db860, 0x0640c82b, 0x06891ec1, 0x11251065, 0x1dbe9810, 0x0b68478a, 0x1344544b, 0x09895abb, 0x755e7}}, - {{0x00d09849, 0x0b3006dd, 0x109dde9f, 0x06e8c99f, 0x15bc2b29, 0x196c11c0, 0x19374926, 0x14ea75b3, 0xfe16af}}}, - /* 11*16^32*G: */ - {{{0x0a26ba6b, 0x0f7b4aaf, 0x1684b0ea, 0x1945492d, 0x0bbdedd2, 0x0606bf58, 0x05a5a284, 0x09986e59, 0x88fba8}}, - {{0x044972f8, 0x020a3c0f, 0x033f73a0, 0x153c51c0, 0x0788d484, 0x0d22da8a, 0x183f499b, 0x0c93a737, 0x512aef}}}, - /* 13*16^32*G: */ - {{{0x14720cab, 0x0e245214, 0x0e6c5f8e, 0x1d4ba82e, 0x06f2c1a1, 0x1b73aaae, 0x0fae3943, 0x197d80ab, 0xe1a0ac}}, - {{0x14b185c9, 0x16a29fab, 0x000953a9, 0x1454e3a1, 0x0bcbf084, 0x1c183bf7, 0x015060db, 0x1f6fd319, 0xe07968}}}, - /* 15*16^32*G: */ - {{{0x0c26c42a, 0x14147eff, 0x0c46ed8a, 0x17f6fc8d, 0x1e6a0249, 0x1ac498f3, 0x11f9436c, 0x10dcd4e7, 0xae93b}}, - {{0x19f808ca, 0x08319a1d, 0x0ab6d924, 0x1473ddeb, 0x00b37278, 0x11f6e0f1, 0x184ea50b, 0x1ba28f0f, 0x7028f6}}} - }, - { - /* 1*16^33*G: */ - {{{0x137de736, 0x15ec5d60, 0x02338907, 0x11aac30d, 0x0c18ea2f, 0x0a15c66f, 0x1cfa24dd, 0x02929399, 0x9022e3}}, - {{0x04c42ecc, 0x077ae042, 0x1a9d95fd, 0x126cd889, 0x0ce087f4, 0x1d822913, 0x0e519b42, 0x09e52094, 0x2fae5e}}}, - /* 3*16^33*G: */ - {{{0x109b01f5, 0x0b8365c6, 0x1cf01464, 0x1e6a3064, 0x1857af73, 0x169f8d7b, 0x0517ec3c, 0x1c60edfd, 0x6e5872}}, - {{0x0bcd5fde, 0x183ac6cd, 0x06a169f0, 0x02f3a9c1, 0x0f0ebc13, 0x0ea579df, 0x05c60330, 0x05d91aee, 0x4213c0}}}, - /* 5*16^33*G: */ - {{{0x0fe45b1f, 0x09cdba3f, 0x185a30ad, 0x02abf65c, 0x1df827bd, 0x0f1a260b, 0x1d412bf4, 0x1634bb47, 0x292220}}, - {{0x134bb026, 0x1f205c5a, 0x17504117, 0x06886099, 0x18d7ff7c, 0x1caffd74, 0x16bb8df1, 0x0a657e2e, 0xe316a0}}}, - /* 7*16^33*G: */ - {{{0x03abd540, 0x14283315, 0x059b790f, 0x0080ca96, 0x13a340e8, 0x07c39084, 0x1c2b0c89, 0x0900f489, 0x3644e1}}, - {{0x155da631, 0x0e37f0e6, 0x1be85378, 0x0ef9db4c, 0x1e293001, 0x02984fd0, 0x05d8470a, 0x0a1b784f, 0xec5a91}}}, - /* 9*16^33*G: */ - {{{0x09142ccb, 0x117e90f4, 0x01fb78c6, 0x0414b58d, 0x1b4824c9, 0x1c7a4c9d, 0x00e3956c, 0x15c2ff9b, 0xedfe7d}}, - {{0x0c593d75, 0x1ca8e98d, 0x14450bae, 0x08856d3a, 0x1beb81da, 0x0e95a7aa, 0x1c858615, 0x0245c1b5, 0xc88692}}}, - /* 11*16^33*G: */ - {{{0x0a41b208, 0x1ed38e32, 0x189c003c, 0x0e8eb9bd, 0x00de8e7f, 0x00135e75, 0x1a5661c5, 0x11680b34, 0x531196}}, - {{0x048e4b69, 0x08f0fcf0, 0x0da1dfda, 0x183b8048, 0x1da113b3, 0x0c0f19d9, 0x05e29b19, 0x0567b845, 0xb78d73}}}, - /* 13*16^33*G: */ - {{{0x095daae7, 0x1e80dae2, 0x1c0cef2c, 0x0dcdd8f8, 0x0e1d5af4, 0x15fb7ab0, 0x1653de88, 0x1e45ff74, 0xf65dd1}}, - {{0x02556738, 0x0a72d4ce, 0x0b366ca3, 0x0c301f3d, 0x04cd0eb0, 0x1f31be7e, 0x1ba0839b, 0x0419c36c, 0x9d71b8}}}, - /* 15*16^33*G: */ - {{{0x1afcf1ee, 0x1f7e0af1, 0x01a6846a, 0x155008e4, 0x007c60af, 0x1a405554, 0x0922d06f, 0x0fdb995c, 0xa6670e}}, - {{0x00c67fc8, 0x1c43855f, 0x0f3ce28d, 0x07312392, 0x1ac131ef, 0x1b97a4ab, 0x0bce8e57, 0x19274a38, 0xa7fd14}}} - }, - { - /* 1*16^34*G: */ - {{{0x04cd3397, 0x008a2e0a, 0x1457ad5d, 0x09820c32, 0x16deddc3, 0x16795a8a, 0x1c8e24e1, 0x00833db4, 0x73baff}}, - {{0x1adcb8e4, 0x0f79509c, 0x0984d250, 0x1df259f0, 0x1825e779, 0x08f460c7, 0x117da803, 0x0c692ef5, 0x1e97de}}}, - /* 3*16^34*G: */ - {{{0x161fb913, 0x1587ca90, 0x14c4a5df, 0x0048c2ad, 0x0d04e3e1, 0x046f225f, 0x0860d10e, 0x01867cc1, 0x45f833}}, - {{0x0c15c3bc, 0x06a40be0, 0x1f0cdcdc, 0x1a10f3ed, 0x07760f06, 0x003b6c5d, 0x01bbe03a, 0x0db7fad2, 0xc212cf}}}, - /* 5*16^34*G: */ - {{{0x0e393654, 0x177cb48f, 0x1b75f1d6, 0x1a97c3e7, 0x15991965, 0x100e45e6, 0x16ad97d6, 0x09359af7, 0x5544dc}}, - {{0x0f53206f, 0x1b085dfc, 0x15639cc8, 0x1dfd2e07, 0x15192241, 0x02dadc49, 0x152b0130, 0x112a10ff, 0xed3e85}}}, - /* 7*16^34*G: */ - {{{0x08a15dfd, 0x1f9acca1, 0x1ea79544, 0x1b75804b, 0x0f695741, 0x176aad71, 0x1dcd2cf4, 0x120c33dc, 0x2757b7}}, - {{0x1042c341, 0x04742067, 0x09c55b7f, 0x112f1479, 0x1500d176, 0x1a909e6a, 0x04b97325, 0x1aaa9856, 0xdf46d2}}}, - /* 9*16^34*G: */ - {{{0x1f840a5b, 0x1f6fa135, 0x0b52613b, 0x195b4ba7, 0x03ccd148, 0x10e6608a, 0x0f236610, 0x0fbca1c5, 0xe87243}}, - {{0x1377e86a, 0x0272c2a8, 0x0e59192e, 0x1468d9f2, 0x08bf5ac3, 0x048fb312, 0x185964ef, 0x11224e52, 0xf984bc}}}, - /* 11*16^34*G: */ - {{{0x136afd6a, 0x0ab54dc5, 0x1e078f52, 0x03f8a142, 0x1334a926, 0x1b6af379, 0x1700e4bb, 0x15749aee, 0xc56cdc}}, - {{0x13f87c26, 0x10435f77, 0x0331c8f9, 0x090764ad, 0x14ef566f, 0x0f58bfac, 0x01c334bb, 0x1c5e70d6, 0xd16056}}}, - /* 13*16^34*G: */ - {{{0x0cfd2cd0, 0x0d6cc069, 0x070304c7, 0x08266883, 0x0abb1239, 0x142c1f24, 0x0a1a73f4, 0x0e71e7fe, 0xd3d6e6}}, - {{0x194ae2dc, 0x02bce3bb, 0x1dbe3c53, 0x1f4ad185, 0x1e59b001, 0x147fb9be, 0x0da14db6, 0x0bf9a2b3, 0xff448f}}}, - /* 15*16^34*G: */ - {{{0x1ab7a64c, 0x0ac442ae, 0x026ade82, 0x011ad474, 0x1d406565, 0x196b911b, 0x101ec0c4, 0x110adc8b, 0x88f977}}, - {{0x1575f103, 0x02f4c708, 0x1b499ce8, 0x06012442, 0x09e5836c, 0x0d792bcb, 0x11d0d14c, 0x1ba8d6ab, 0x4a745c}}} - }, - { - /* 1*16^35*G: */ - {{{0x1fa4e33c, 0x1172fd98, 0x02632cc3, 0x077d8f16, 0x0fb98268, 0x023614bb, 0x16ef25d1, 0x17234984, 0x9cf646}}, - {{0x0e0d4563, 0x0e22f030, 0x10580c86, 0x00b04fd7, 0x01f319e2, 0x0712c5c1, 0x0a247902, 0x09b83ecb, 0x37b062}}}, - /* 3*16^35*G: */ - {{{0x06bf1e67, 0x0c5b0c66, 0x172bd8fa, 0x0cce93fc, 0x04e0f4c5, 0x129c13bb, 0x126675e9, 0x1bc2a36c, 0x83cb43}}, - {{0x099acc97, 0x13f74598, 0x1445a7a8, 0x0884597b, 0x018f8287, 0x00373122, 0x1be3bec6, 0x1449731e, 0xbce28c}}}, - /* 5*16^35*G: */ - {{{0x1c0f057e, 0x1856ba46, 0x154f7608, 0x10c50e03, 0x1022484e, 0x07e0af12, 0x02300cd0, 0x1cac19d6, 0x3ff3b3}}, - {{0x0817965e, 0x0a0fbed5, 0x1c05d88b, 0x0046dd88, 0x07843a01, 0x08b82bc3, 0x1e3dbdff, 0x0de776ca, 0x7f17ad}}}, - /* 7*16^35*G: */ - {{{0x125e69f1, 0x088a5a01, 0x08af2d45, 0x0f51e5a8, 0x0af99636, 0x0ef0b9eb, 0x00ff7686, 0x05bb1ffb, 0x6e9edb}}, - {{0x002b7e9b, 0x1070bf1a, 0x07ca06dc, 0x04e8a8f3, 0x1bff61c7, 0x0b55b2f9, 0x153aacd5, 0x02d9dff2, 0xc08222}}}, - /* 9*16^35*G: */ - {{{0x0288f038, 0x19297b35, 0x17fe082f, 0x0ed129d6, 0x02d32f08, 0x00cef376, 0x112fbeaf, 0x1d009883, 0x5ee280}}, - {{0x16f1ee6e, 0x02d55c35, 0x19b1bd07, 0x0f067531, 0x1eec011d, 0x0c37f664, 0x0e4a1301, 0x1f28cefc, 0xbcd969}}}, - /* 11*16^35*G: */ - {{{0x00c708c8, 0x05f992b6, 0x1c2a1aa8, 0x08609e5e, 0x0288c2c3, 0x1b2ec8ff, 0x15cdb7f8, 0x0dc0b840, 0xe1f016}}, - {{0x1896ed38, 0x18c6b9d9, 0x0d6802b9, 0x0abe45df, 0x13016fb6, 0x1195f451, 0x0d481111, 0x07d22d87, 0xe64765}}}, - /* 13*16^35*G: */ - {{{0x076edefb, 0x10784e52, 0x039f575f, 0x117b0020, 0x1c7badd5, 0x0d5a14bc, 0x1171fc48, 0x10f57ec6, 0x280896}}, - {{0x1d1b0ae0, 0x17a2b914, 0x00e4848b, 0x06360f7c, 0x141c44dd, 0x0cf5ec82, 0x064699f8, 0x1e67a766, 0x5d071c}}}, - /* 15*16^35*G: */ - {{{0x1344897a, 0x096ccde7, 0x1309a774, 0x1da60eb4, 0x1edab7b9, 0x0f429212, 0x132dc161, 0x1bc50320, 0xeb15b0}}, - {{0x05bfe7ee, 0x0cef41e7, 0x1f42e0ab, 0x0d3165f2, 0x12f85814, 0x157c66b9, 0x01c42262, 0x02d384cc, 0x96cdd}}} - }, - { - /* 1*16^36*G: */ - {{{0x123b716d, 0x137d6e02, 0x13869ae0, 0x0712cdee, 0x0df9e0d2, 0x03c9c68c, 0x14d3a297, 0x1c717194, 0xf81f5b}}, - {{0x12632401, 0x120017a8, 0x01e0cc11, 0x0eb0a075, 0x00328660, 0x094e9c07, 0x1b7c755b, 0x065383e0, 0xdc7f49}}}, - /* 3*16^36*G: */ - {{{0x19d19d8a, 0x062b4281, 0x09548561, 0x024f12a3, 0x1490a3b4, 0x161ae0b6, 0x18e16bdb, 0x0a1f6250, 0xa60084}}, - {{0x190d2c4f, 0x0caedd9a, 0x0995cf7f, 0x1c011f4e, 0x1ca84f0f, 0x0a2f3df4, 0x11cb23db, 0x180cf46f, 0x7b4b79}}}, - /* 5*16^36*G: */ - {{{0x096cac9f, 0x14c3d9ca, 0x11379c89, 0x0719d4b8, 0x10e1c59e, 0x04bcc7f5, 0x0d531930, 0x1632cfbe, 0xde5382}}, - {{0x0fa473d3, 0x070417ed, 0x1610455f, 0x07c528e7, 0x19f2bc2e, 0x1804c8e7, 0x06158b0e, 0x0f16d392, 0x9c47b6}}}, - /* 7*16^36*G: */ - {{{0x06807a23, 0x0594b1bc, 0x097fcf9f, 0x0040a2d4, 0x14ec8400, 0x1ccea88c, 0x04214d12, 0x0100fe55, 0xa7227b}}, - {{0x14115894, 0x0238cc1e, 0x0a337247, 0x1f02af6e, 0x075abe7b, 0x023c9050, 0x07a1176a, 0x04ba8ba7, 0xf55075}}}, - /* 9*16^36*G: */ - {{{0x044d14c0, 0x13cbfe10, 0x0a3cd796, 0x1956bf43, 0x01d39005, 0x1f39d1b3, 0x1659196a, 0x11a84688, 0xcd2b2e}}, - {{0x02496315, 0x0b056791, 0x0b33b3a9, 0x16de1973, 0x0f671fdd, 0x0f76ed60, 0x02893541, 0x1bf3610e, 0xf13012}}}, - /* 11*16^36*G: */ - {{{0x199a1566, 0x17a75891, 0x08a3da59, 0x18cab5fe, 0x19dd8f25, 0x1e4dc1ef, 0x1fad33be, 0x0991e1be, 0x11b2e8}}, - {{0x1c56bb73, 0x0d259e02, 0x16025a16, 0x16ab0819, 0x005cc824, 0x0b4c3cba, 0x05d7410b, 0x13b79446, 0x610376}}}, - /* 13*16^36*G: */ - {{{0x1ba6f34a, 0x084f5946, 0x1171be1f, 0x10d41fc4, 0x11485312, 0x1051f7cc, 0x01c1d676, 0x052be574, 0xfb6d19}}, - {{0x0bcf0452, 0x1af9e411, 0x1a16c7e6, 0x19f45b92, 0x191b9ecf, 0x07ea6253, 0x1b7678e2, 0x053af7e8, 0xf29b2d}}}, - /* 15*16^36*G: */ - {{{0x0a435427, 0x06516950, 0x1936d41d, 0x0021304f, 0x06651a69, 0x13b286c8, 0x0f042abd, 0x19820782, 0xff429f}}, - {{0x19853824, 0x12822869, 0x0e8368a3, 0x17dca694, 0x06876205, 0x04e42b0c, 0x16c25350, 0x0cd149d9, 0x2addf9}}} - }, - { - /* 1*16^37*G: */ - {{{0x07354b7a, 0x16ce3f3c, 0x045725e3, 0x1d94ad87, 0x0de1d022, 0x1a31f29e, 0x01208e5f, 0x0e9aefc1, 0x856854}}, - {{0x116e04c6, 0x0aa2015e, 0x15bf6f62, 0x1123b83f, 0x1728706a, 0x089d9537, 0x1edbbaf2, 0x16a17eb0, 0x20b50e}}}, - /* 3*16^37*G: */ - {{{0x1b26165d, 0x1b663fd3, 0x1dc4dfab, 0x07442bec, 0x092dea71, 0x090497e6, 0x199527a7, 0x18ea5647, 0x617344}}, - {{0x06b2a286, 0x04dfee5c, 0x1c5c6931, 0x13582dc6, 0x020b2989, 0x0885e8c1, 0x0d6926df, 0x02f486f0, 0xe99016}}}, - /* 5*16^37*G: */ - {{{0x10442770, 0x10dfbb32, 0x006eb440, 0x11ef4dc2, 0x0fc2e901, 0x00ed4f20, 0x088b6813, 0x1dcabf6d, 0xf8ac03}}, - {{0x1b00c82d, 0x11432788, 0x0107408e, 0x0c057f2a, 0x06d17a97, 0x03a8d9a7, 0x1a3a90db, 0x0cab33a3, 0x5a358e}}}, - /* 7*16^37*G: */ - {{{0x165460de, 0x1f036c37, 0x0fddee3e, 0x07f1a155, 0x08ea60c7, 0x1e5f1866, 0x1719b1b2, 0x16a4b792, 0x8a731f}}, - {{0x1f8dc207, 0x0c2cd0ef, 0x1423b8fa, 0x0cfc78a4, 0x04dc4358, 0x0aa5ffd8, 0x0c663d4a, 0x18bc556c, 0x585855}}}, - /* 9*16^37*G: */ - {{{0x151634a5, 0x0155d6fd, 0x1b549399, 0x1214f06b, 0x1967f3cf, 0x15d0166b, 0x0892fc29, 0x0c26551e, 0xa20b60}}, - {{0x1226afad, 0x1609bcaf, 0x1517b7c9, 0x08eafc79, 0x1315bc67, 0x0ff41b9d, 0x0676b4f5, 0x13ed2fb7, 0x9c43ce}}}, - /* 11*16^37*G: */ - {{{0x13ba54a8, 0x02191225, 0x0eaa5c1c, 0x164c6ac0, 0x1cc0ab58, 0x0761a7e2, 0x1c26450e, 0x127e24ac, 0x16f1f}}, - {{0x064f2889, 0x0be30fd3, 0x08ed39bb, 0x15e4ea8d, 0x0e658d93, 0x188df24e, 0x055dbca6, 0x0822b12a, 0x8bcd3}}}, - /* 13*16^37*G: */ - {{{0x07c16d94, 0x177249e7, 0x0a117f77, 0x103a1540, 0x0c31fe25, 0x1eb3e667, 0x11e23023, 0x0ce17a06, 0xe61586}}, - {{0x114810ab, 0x1a768cd5, 0x0910eefe, 0x0b9a3c8f, 0x0f0ee4e6, 0x15c9fa5b, 0x12fa316c, 0x15d3ce24, 0x65ca03}}}, - /* 15*16^37*G: */ - {{{0x13ce728d, 0x0b6e5332, 0x1c7342f3, 0x1b20fc50, 0x05347f4c, 0x04510b64, 0x08995568, 0x01671aad, 0xdcd37f}}, - {{0x17cacbcb, 0x0be89b4c, 0x076afae3, 0x19a68da5, 0x0d6f3caa, 0x1db159e3, 0x1061cb0d, 0x1aef9b49, 0x6574db}}} - }, - { - /* 1*16^38*G: */ - {{{0x12378c16, 0x0e36e0d8, 0x05588ab7, 0x0c0eaa8c, 0x1597d9e3, 0x1296b5fc, 0x0c46cc67, 0x0b382567, 0x1136b7}}, - {{0x13488127, 0x0facde85, 0x147338de, 0x095451ce, 0x1ecb2961, 0x15e307f4, 0x1f7427c2, 0x19e8a2d1, 0x7dec0f}}}, - /* 3*16^38*G: */ - {{{0x1b299740, 0x18736c13, 0x0f6c4d25, 0x1e8cfae2, 0x0ad48f40, 0x089bc9fd, 0x0cdb312b, 0x16e39ba8, 0x53893e}}, - {{0x1fff7509, 0x0f5378d9, 0x1f7a3354, 0x0265de43, 0x1dc8dd8a, 0x0714753a, 0x0f60e107, 0x0fd290bf, 0x27728a}}}, - /* 5*16^38*G: */ - {{{0x138000fc, 0x059e4ab3, 0x1c0ef04c, 0x1c4ee0f7, 0x0e5604e1, 0x1b5d78fd, 0x089d5f8d, 0x1baea99b, 0xdd64e9}}, - {{0x1df4ac12, 0x1d50e286, 0x07923e2f, 0x0ba04572, 0x079ffbe4, 0x0f1f0ce5, 0x0b25b7e2, 0x1d618526, 0x432193}}}, - /* 7*16^38*G: */ - {{{0x13878c4a, 0x1c10a8b9, 0x1470157b, 0x07a0790a, 0x11b21d88, 0x0e307254, 0x145bcb1a, 0x150fdffa, 0xff9845}}, - {{0x1488df68, 0x1d3423eb, 0x173faf1a, 0x066e8d6c, 0x1e8ddf1f, 0x12476ffa, 0x0a3e3f62, 0x1e949c48, 0x835b9a}}}, - /* 9*16^38*G: */ - {{{0x00f51524, 0x054dc40a, 0x0be1bf23, 0x1d6d42b7, 0x093de5fa, 0x1d184229, 0x0273a1d9, 0x1d17722c, 0x954e13}}, - {{0x05e30a69, 0x0fed9eca, 0x079f2f7a, 0x1d486228, 0x0cbcfa9e, 0x1121a3cf, 0x010764f1, 0x07ff2548, 0xecc836}}}, - /* 11*16^38*G: */ - {{{0x04f77287, 0x1bcc6a9e, 0x0c3678c7, 0x12a786d2, 0x00497412, 0x0862e0ec, 0x0e4f7f35, 0x1edf483f, 0xb2217e}}, - {{0x04e7b5da, 0x1d0253b9, 0x17a4f2e5, 0x13b8e738, 0x0c843d70, 0x1e7c469c, 0x0e8ad77e, 0x1c19cf9e, 0xfb5153}}}, - /* 13*16^38*G: */ - {{{0x05c723e9, 0x17a9cb64, 0x09e05c62, 0x0e775535, 0x15c351f9, 0x175748ae, 0x16b448b1, 0x162f6f37, 0x3c3950}}, - {{0x16834763, 0x11c3bafb, 0x1241ab97, 0x1b596af8, 0x0a8b0b01, 0x1dbf50c3, 0x036ed252, 0x0c441222, 0xb99b73}}}, - /* 15*16^38*G: */ - {{{0x1c18abeb, 0x18beb711, 0x09758a8d, 0x1b9a320e, 0x0a944d59, 0x097c225d, 0x136b477b, 0x0599f2c3, 0x803c7a}}, - {{0x029bfa78, 0x121cdf87, 0x19c8735a, 0x18887854, 0x14409ed7, 0x078b4e25, 0x04a1ac6b, 0x0814f2dd, 0x83fb50}}} - }, - { - /* 1*16^39*G: */ - {{{0x143e832a, 0x03b1778c, 0x01b7dc27, 0x0a15602f, 0x1f18e07e, 0x19d412c4, 0x146a43d5, 0x1174f854, 0xd2bf2}}, - {{0x1b20d37c, 0x0131d78a, 0x15451192, 0x193b72c0, 0x0e7ed27e, 0x10854a5a, 0x02b1c21e, 0x086277a0, 0xcac3f}}}, - /* 3*16^39*G: */ - {{{0x112c9666, 0x0f5625e0, 0x1ff37cd8, 0x118d86cb, 0x1531b1cf, 0x061adbec, 0x00b3f66f, 0x0bd72cff, 0x34e5e8}}, - {{0x192a3666, 0x0d0c29e4, 0x18e949ad, 0x0fdac783, 0x046330f4, 0x12bae65e, 0x0dae0a11, 0x06264434, 0xc0ce68}}}, - /* 5*16^39*G: */ - {{{0x11209502, 0x0b295f1a, 0x16499970, 0x02e4004b, 0x1e154594, 0x09f7848c, 0x018e9b12, 0x198b3e9b, 0x727362}}, - {{0x17042b0d, 0x13be8e9e, 0x09d82ef1, 0x1a6ff376, 0x11d20a18, 0x05c61674, 0x0d627c40, 0x04537575, 0x17b0f4}}}, - /* 7*16^39*G: */ - {{{0x0447f959, 0x0af3c945, 0x1c74da11, 0x0fb57504, 0x1eee9f94, 0x0a625da4, 0x13b25ce8, 0x0c00a94d, 0xa0c3f2}}, - {{0x168b671f, 0x01ce9244, 0x149cb26c, 0x0aa804b1, 0x08208b1b, 0x060865b1, 0x113ce0be, 0x0121c965, 0x508c73}}}, - /* 9*16^39*G: */ - {{{0x1b37e1d3, 0x10fcf812, 0x193788c0, 0x0c37c279, 0x1fd04107, 0x019df20a, 0x09e9032d, 0x063ef2b9, 0x7e81b2}}, - {{0x0155681a, 0x1afe5132, 0x10b8380f, 0x1097e563, 0x07c4e4ec, 0x04b67736, 0x144c497a, 0x0361d37d, 0xf3855a}}}, - /* 11*16^39*G: */ - {{{0x16c051a0, 0x04bb8afa, 0x16cf9b71, 0x1e5248de, 0x12abddcd, 0x0c736d87, 0x1b6128db, 0x038fb004, 0x8035e2}}, - {{0x12d0dd12, 0x00f5d97d, 0x02d88d58, 0x0fb8c613, 0x1d318b3e, 0x1f341bde, 0x0fbcdd76, 0x14896f45, 0xb8810}}}, - /* 13*16^39*G: */ - {{{0x1f12c69e, 0x1006184f, 0x194658f3, 0x0b5deb12, 0x0fcecafe, 0x18008102, 0x14cc1aeb, 0x1bfac314, 0x196908}}, - {{0x1d114831, 0x0998c820, 0x1ee21ae3, 0x05c66e3f, 0x1054eb6b, 0x0ef56e90, 0x18102fb8, 0x0d65f22d, 0x3f65bf}}}, - /* 15*16^39*G: */ - {{{0x0b89c5ed, 0x04c700fe, 0x1e9e31f9, 0x0a619ef2, 0x10f3577b, 0x10e90856, 0x0abd1b9b, 0x1d712c34, 0xdb77fc}}, - {{0x0d25b46e, 0x0ff8e3f7, 0x0266be96, 0x0d8f56d1, 0x1ad411f1, 0x1cdf264c, 0x173bb3cc, 0x070e39dc, 0x7fd1dc}}} - }, - { - /* 1*16^40*G: */ - {{{0x17f82f2a, 0x174e3aef, 0x1f7d0eab, 0x186b0e95, 0x113269e4, 0x16fa1b9b, 0x185fd588, 0x0acdd8e6, 0x8a535f}}, - {{0x023094b7, 0x0fcd0561, 0x031d9a71, 0x0a670c99, 0x092bfcde, 0x140c842d, 0x0f5cdf80, 0x108d1611, 0x455c0}}}, - /* 3*16^40*G: */ - {{{0x0b348fa0, 0x18a790bd, 0x0550777e, 0x1c48b20a, 0x0b4bce0f, 0x1191b612, 0x00b70a88, 0x07bbbd71, 0x86eac9}}, - {{0x0da51cee, 0x171c04aa, 0x13fba293, 0x0db2c6a3, 0x146716c2, 0x17cf46b7, 0x1635690d, 0x0a797789, 0x948f38}}}, - /* 5*16^40*G: */ - {{{0x19222c03, 0x17a0ffe4, 0x197840de, 0x19cefd0f, 0x1f407948, 0x1ebc242c, 0x0ab8fd79, 0x175f3f67, 0x8bf09e}}, - {{0x0a72bb54, 0x0a2fba17, 0x08387528, 0x1d81c3bc, 0x1ba309c9, 0x18edf3f2, 0x09cced22, 0x15fc5c4f, 0x509cba}}}, - /* 7*16^40*G: */ - {{{0x11ae9300, 0x029d160e, 0x1120a02d, 0x188e08eb, 0x1735b5e1, 0x05d6d179, 0x1f18644c, 0x1976fce1, 0xe85e2d}}, - {{0x1546e25e, 0x1506fee8, 0x030c6edc, 0x0fc30bbf, 0x02707deb, 0x1dadc11e, 0x02ff1ee9, 0x14daa39c, 0x451aaf}}}, - /* 9*16^40*G: */ - {{{0x05260cb8, 0x092eaab0, 0x0c854bc9, 0x1e95019d, 0x1dbf6836, 0x13ed0dd3, 0x1e0a8fc0, 0x1e451925, 0x3f5fb0}}, - {{0x1852c964, 0x17da5a20, 0x17b0cc9c, 0x1d0ea3f8, 0x183f2fa3, 0x0f0a9b33, 0x061c38e3, 0x1b5b4933, 0xc55834}}}, - /* 11*16^40*G: */ - {{{0x1a1cd60f, 0x15222216, 0x0c24ba92, 0x0d315398, 0x0002b9f9, 0x083a5a6d, 0x06595ebb, 0x045631b3, 0x336856}}, - {{0x0fd57d67, 0x1fb9bb28, 0x142e2c92, 0x1eb49978, 0x1af175fe, 0x06006f53, 0x1366ea16, 0x13de248f, 0xd42f50}}}, - /* 13*16^40*G: */ - {{{0x17576342, 0x029db75d, 0x06488abc, 0x19110673, 0x179d95b2, 0x1cec4b04, 0x0203df43, 0x0b811e00, 0x4813eb}}, - {{0x17376316, 0x060aaf5c, 0x1aa413d9, 0x1b8cfaa0, 0x1524aca2, 0x0b424719, 0x0903d980, 0x1a846748, 0x043f}}}, - /* 15*16^40*G: */ - {{{0x1f69b2be, 0x1b38b8ef, 0x04447027, 0x03ee9db8, 0x06e56ba4, 0x16ddd71c, 0x05ebc4c8, 0x1f34b5d3, 0x80c3f1}}, - {{0x0102d2f5, 0x0825cbe2, 0x0dea2fe2, 0x16e966b9, 0x15a9bf14, 0x113b2d8e, 0x1a14a603, 0x0814013b, 0xa9321}}} - }, - { - /* 1*16^41*G: */ - {{{0x0476c81d, 0x041bfa7f, 0x194f57a7, 0x06061d33, 0x0a366b7a, 0x06939ed4, 0x08066bdf, 0x1bfb9683, 0xad6090}}, - {{0x0e6705dd, 0x0c55e427, 0x0c1237e0, 0x1fb86c9e, 0x1d8a8393, 0x1ae1662d, 0x047ab335, 0x1ba91e99, 0x77b5d1}}}, - /* 3*16^41*G: */ - {{{0x02725490, 0x11239be7, 0x12d66402, 0x06480c47, 0x1863c4ac, 0x15299d84, 0x05f28ab6, 0x176e35ad, 0xd90b45}}, - {{0x0feb1299, 0x07c27c25, 0x1119b32c, 0x180f7fe7, 0x0cbd80cd, 0x1ab439bc, 0x143c2762, 0x11d83766, 0x332692}}}, - /* 5*16^41*G: */ - {{{0x1e933668, 0x1b59d32b, 0x1aeafc29, 0x05b099f0, 0x106befb0, 0x1c9d7d0f, 0x1f1eb014, 0x02e62427, 0xac2592}}, - {{0x12776fb5, 0x173bfb39, 0x07879e85, 0x0c83138d, 0x0ed7c6f8, 0x009f75ec, 0x02ea143b, 0x070e1b75, 0x6c79a0}}}, - /* 7*16^41*G: */ - {{{0x128de7e3, 0x130be090, 0x1aa65b66, 0x08558757, 0x031bf868, 0x07ded3da, 0x0ea21cc8, 0x095d2f3e, 0x7bc337}}, - {{0x0e6e5e29, 0x1d059975, 0x10333c19, 0x05b67369, 0x1acbd55f, 0x1bd73725, 0x19778031, 0x048c10a9, 0xb431f0}}}, - /* 9*16^41*G: */ - {{{0x035e057d, 0x1659191c, 0x162696d2, 0x1e21b5ed, 0x1119329c, 0x0397e1a0, 0x0ac9a2f2, 0x128a75c0, 0xbf9c14}}, - {{0x0da20fea, 0x1ab25941, 0x1711e6db, 0x173c3038, 0x1a542440, 0x189f82e3, 0x0f83ce14, 0x13e4be47, 0x92acb8}}}, - /* 11*16^41*G: */ - {{{0x021d6584, 0x1566be10, 0x0cf67974, 0x00bac887, 0x1014ef27, 0x1ed1ad6f, 0x1e2a5ba5, 0x0736af09, 0x55bf08}}, - {{0x0ed744ea, 0x0ab27a14, 0x19696a03, 0x1c284055, 0x07dcf089, 0x1fb5d45e, 0x133c9eb2, 0x067c96c3, 0x3d9807}}}, - /* 13*16^41*G: */ - {{{0x01068c5e, 0x0b4efb72, 0x195cf437, 0x0bdcdc97, 0x1d4872a4, 0x10a73c0a, 0x15467cab, 0x02ca66f1, 0xfcf24e}}, - {{0x07c19d75, 0x10fa29f9, 0x052156dd, 0x0ed49650, 0x0e7aee91, 0x0f10dac9, 0x1f8d719d, 0x1ca66dff, 0x186bb0}}}, - /* 15*16^41*G: */ - {{{0x020da860, 0x1becaf83, 0x12744022, 0x08a35490, 0x11f2f843, 0x1b29a1ec, 0x1fb287f6, 0x1a05ea2c, 0x7bc280}}, - {{0x07aac76d, 0x11488208, 0x1058cbaf, 0x03345fb9, 0x0e6e0f2e, 0x15fd382f, 0x07978989, 0x0ef777a6, 0xb33f6d}}} - }, - { - /* 1*16^42*G: */ - {{{0x13c4a205, 0x097961b4, 0x042a1229, 0x15bf13ea, 0x129fcde1, 0x0ab83adc, 0x0f139199, 0x0a2c60b7, 0xb6e06c}}, - {{0x1db36d05, 0x1bfa6c32, 0x0aa7e1d4, 0x13283350, 0x0d73b63f, 0x189373cf, 0x0fd71787, 0x0f843664, 0xbdb427}}}, - /* 3*16^42*G: */ - {{{0x19e27907, 0x17c10fb7, 0x167935a4, 0x15a96711, 0x1bd68771, 0x0eaeb7ef, 0x1139ace5, 0x07f08483, 0xed9e4}}, - {{0x0e78c4fb, 0x09fa7a83, 0x0e86417c, 0x0a39fd71, 0x00e0ce91, 0x07ec7589, 0x0d1fd6f0, 0x095fed64, 0xb5af87}}}, - /* 5*16^42*G: */ - {{{0x15b38f54, 0x1682b929, 0x0bc1f38d, 0x150c1cb9, 0x0e1f92e2, 0x146da47f, 0x1df71549, 0x0200edb1, 0x57a7e4}}, - {{0x045f2809, 0x1d3c0f31, 0x01fae9d7, 0x0edc7352, 0x0dd21dfd, 0x0511de41, 0x14906532, 0x00791d95, 0xfd7f0f}}}, - /* 7*16^42*G: */ - {{{0x07a79593, 0x17f5dfcd, 0x17125e51, 0x14e493a4, 0x05cf3347, 0x0d92c665, 0x16fbd4b6, 0x1c5e7deb, 0x24799a}}, - {{0x10920d55, 0x11c46bae, 0x1ac4e635, 0x086c3f37, 0x1e300999, 0x08d4e9b1, 0x0deccb0f, 0x04c5d90f, 0x9436b5}}}, - /* 9*16^42*G: */ - {{{0x0bf5abdc, 0x010b75a8, 0x02d94198, 0x19a75f7a, 0x15d60456, 0x08e58406, 0x009bb4c4, 0x18d1098e, 0xf15017}}, - {{0x0def85b7, 0x1aef15c5, 0x0e15612d, 0x18ceb84f, 0x1e232cbd, 0x1a0f1fe2, 0x0aa3b360, 0x03858be0, 0xdea7ef}}}, - /* 11*16^42*G: */ - {{{0x1e78e9b1, 0x0f92958f, 0x10507a1a, 0x11cdb89d, 0x0dfcc897, 0x018fba89, 0x1aa9b83e, 0x01f13697, 0xe196e2}}, - {{0x0a77eed7, 0x00bab0fe, 0x1b4d7d11, 0x1a02257d, 0x0ed9a908, 0x045f3b59, 0x1698a990, 0x10bf0350, 0xa5d66d}}}, - /* 13*16^42*G: */ - {{{0x1b1cb64b, 0x17719f2b, 0x02b0f55d, 0x13ca4ac3, 0x1ed14d60, 0x1e6b8a9c, 0x0c0bce5f, 0x1bcd8360, 0x7779f5}}, - {{0x1aba3aab, 0x070c68e5, 0x0aa54cf6, 0x10528479, 0x0e3fae2a, 0x189a53d1, 0x1afab7ea, 0x07e8e987, 0x9a842}}}, - /* 15*16^42*G: */ - {{{0x11d1b492, 0x16c2c3b4, 0x0669bd4f, 0x00c31840, 0x08ce8dfa, 0x16cd5759, 0x0bc4797d, 0x097c8474, 0x8605b9}}, - {{0x18ac6598, 0x18ebbdfb, 0x07a49715, 0x06a4da90, 0x1d1a8ee2, 0x170610a1, 0x1d63cfbd, 0x050fbcea, 0xa8e561}}} - }, - { - /* 1*16^43*G: */ - {{{0x09aa52df, 0x06afa725, 0x0a989fcf, 0x08a56368, 0x1ece618c, 0x00c4ecc8, 0x0fddb6f1, 0x192fec11, 0x45a511}}, - {{0x125ec16c, 0x1a95e890, 0x0a55739e, 0x03364fa4, 0x05ad25a9, 0x19bfe5b1, 0x0db4ff8c, 0x18ee7d53, 0x73be0e}}}, - /* 3*16^43*G: */ - {{{0x0e75bc7f, 0x0b3a9f7d, 0x1dfec7d9, 0x0e2e1b6d, 0x14c7b95c, 0x07890be6, 0x0d0e3bd0, 0x09fce572, 0xb57ac}}, - {{0x0972e9a9, 0x078b96e8, 0x127f1881, 0x12d81c80, 0x094fab1f, 0x1a67d2bf, 0x0ed7ca30, 0x104ef53e, 0xceedbf}}}, - /* 5*16^43*G: */ - {{{0x033125ff, 0x1ba19e7c, 0x16a05084, 0x0aaf60b4, 0x0ae99354, 0x016ce50e, 0x05233d1a, 0x1f6dc97a, 0x1178b3}}, - {{0x18486abd, 0x007cb84e, 0x195346fa, 0x115b9a11, 0x10ed10dc, 0x131bf518, 0x056ce0b7, 0x1d53757b, 0xdfd697}}}, - /* 7*16^43*G: */ - {{{0x1662d955, 0x19b4ae67, 0x033913ce, 0x09ad0b69, 0x15693844, 0x1dbf4693, 0x041fe2a0, 0x104df29d, 0x8ac7a0}}, - {{0x046b3dae, 0x03516df3, 0x1b04e36f, 0x09038b7a, 0x19ad9e3f, 0x1291a65a, 0x0c73275f, 0x1241e664, 0xc80f40}}}, - /* 9*16^43*G: */ - {{{0x088803e5, 0x06c3cd6f, 0x0d1972df, 0x156f9c1a, 0x09e02cc1, 0x1d43802b, 0x08446adf, 0x050b3bbe, 0xc0f48b}}, - {{0x076619ee, 0x14991014, 0x00a3b6e9, 0x1c9c0e17, 0x0d7d3932, 0x12393f30, 0x08da7269, 0x16df1079, 0x3d4326}}}, - /* 11*16^43*G: */ - {{{0x1e1f515c, 0x0b6b384c, 0x194e6bea, 0x09146442, 0x1b8c0e2b, 0x047087fb, 0x19b68067, 0x01e06a2e, 0x2ce870}}, - {{0x052eed6e, 0x08c9b24c, 0x10f54b25, 0x13b9a7c5, 0x15d2ca7a, 0x17a17bf7, 0x129eeb2c, 0x09e76bd8, 0x73879f}}}, - /* 13*16^43*G: */ - {{{0x04dcf274, 0x0d51bbaf, 0x0b3a8911, 0x0400d059, 0x0ca1d807, 0x0dd87ebe, 0x04245178, 0x0c3b96f8, 0xfac442}}, - {{0x112f0472, 0x1b4c3007, 0x11652c58, 0x004f8c4e, 0x097bd732, 0x11eaea77, 0x02a1f31c, 0x18c2acd5, 0x9713fd}}}, - /* 15*16^43*G: */ - {{{0x1dc39d21, 0x1af25b55, 0x06b71a0a, 0x0e7d7a81, 0x12813683, 0x0f21d0cc, 0x18011964, 0x02cb6807, 0xf891e6}}, - {{0x05ca579f, 0x0c8470be, 0x11809535, 0x12fd89ea, 0x1c43b73a, 0x1c54716d, 0x13b462ba, 0x162577bf, 0x129f53}}} - }, - { - /* 1*16^44*G: */ - {{{0x1076f57b, 0x0678d5cc, 0x12b181ca, 0x170dc79d, 0x0a53f030, 0x11a80e79, 0x1e72e49f, 0x0ac91018, 0x20e118}}, - {{0x0da7afe6, 0x0c1df6dd, 0x186dc7a1, 0x01b93681, 0x1e9979fe, 0x01f1fdb6, 0x1bdcc6f7, 0x0a55886f, 0xff67b3}}}, - /* 3*16^44*G: */ - {{{0x0b661da4, 0x04f1d76a, 0x0a129721, 0x14a358c9, 0x01a21940, 0x1fec8183, 0x1b3a3df1, 0x0b770cc0, 0xffca2a}}, - {{0x0f9b8a5a, 0x0c241af5, 0x1a1fdbe2, 0x1f0f5bca, 0x06c8c478, 0x04d0a47a, 0x172294f3, 0x1c656e39, 0xce1cb0}}}, - /* 5*16^44*G: */ - {{{0x1c8cfd22, 0x0560f09c, 0x0daf960f, 0x160ce36c, 0x1354a4e2, 0x1a8a86d6, 0x1aad8d94, 0x1dbff822, 0xff209c}}, - {{0x13bc5535, 0x1c4d8a74, 0x1a12e508, 0x1e972592, 0x1f1c80d4, 0x00e92667, 0x1df881f5, 0x12e8d3a1, 0xad79f3}}}, - /* 7*16^44*G: */ - {{{0x0f4e983e, 0x003f2c2c, 0x05fddf3a, 0x1cdd90e0, 0x14b156d9, 0x0040699d, 0x0ea56181, 0x05a160f3, 0x33d5f0}}, - {{0x11ea37a9, 0x0b86dc3c, 0x18c3255b, 0x035524b2, 0x1a1eb7a7, 0x0b0b2add, 0x1e11cf0f, 0x0eda7388, 0x1eb5a}}}, - /* 9*16^44*G: */ - {{{0x01e52a73, 0x14ad3cfb, 0x01686f2d, 0x0622d92e, 0x15e65d0e, 0x1a6cd6dd, 0x1a42a7a5, 0x07b4c4c2, 0x8f1bd2}}, - {{0x0bea8f2e, 0x1ed0a28f, 0x06142a15, 0x0a05cc96, 0x1eade590, 0x0f2183a1, 0x02e2e1da, 0x0c7275b5, 0x4c7a8f}}}, - /* 11*16^44*G: */ - {{{0x1d2bc078, 0x0217f617, 0x1cfbd696, 0x00ec0a53, 0x080aec4a, 0x0c5bed6c, 0x1fcfb98a, 0x1fdef6c6, 0x963bc9}}, - {{0x1afb8657, 0x0ed918fa, 0x10e8f09f, 0x180851e8, 0x0a4f1c5e, 0x128494c0, 0x188a028f, 0x07f9a3ce, 0x7ed338}}}, - /* 13*16^44*G: */ - {{{0x0fb608ad, 0x038f3b75, 0x03f64e9a, 0x00aca9b1, 0x0bc49748, 0x16ee5be4, 0x011398c6, 0x15b0c3b5, 0x89fe6d}}, - {{0x064d4dad, 0x00a0373c, 0x1c2a1ed8, 0x13aa27c1, 0x1df39aea, 0x1effdb92, 0x03f21ed5, 0x10463e9d, 0x289827}}}, - /* 15*16^44*G: */ - {{{0x19ae4a29, 0x18c4c0d8, 0x08ca82c1, 0x1fb4dd44, 0x06984df2, 0x1d62708b, 0x1d654435, 0x0fe77af2, 0xc34804}}, - {{0x0eb09235, 0x04acd626, 0x126bafcd, 0x05d33e0a, 0x1ae9a842, 0x06e90706, 0x11584f6a, 0x1e40ad43, 0x584781}}} - }, - { - /* 1*16^45*G: */ - {{{0x19273a8f, 0x08a29744, 0x13f552b7, 0x1a8f1cd2, 0x1dac93fd, 0x163fefeb, 0x09ec0c63, 0x1f0e4740, 0x5c9cc4}}, - {{0x1ce80d6e, 0x0ed6534a, 0x06b2ad6b, 0x006ceb42, 0x0af964f0, 0x0c4e9b84, 0x0966a09d, 0x0f43bfda, 0x84efe0}}}, - /* 3*16^45*G: */ - {{{0x0883e382, 0x0464c2a2, 0x154dbce3, 0x009f9dea, 0x07431d06, 0x001ca900, 0x01716f89, 0x12577bfb, 0x5ac8e1}}, - {{0x1dfeaadc, 0x09b9ecde, 0x13674b94, 0x0dd9427a, 0x03976de7, 0x1ff9784b, 0x1200e723, 0x00098f51, 0xfcb7e5}}}, - /* 5*16^45*G: */ - {{{0x0f01a3e8, 0x052183bf, 0x120253af, 0x16ca865c, 0x07362c6e, 0x0ea2706b, 0x0460b545, 0x1316f224, 0x99dc06}}, - {{0x00f61114, 0x14322ff2, 0x1e3ca514, 0x0ce069af, 0x00044b7a, 0x0388b8ec, 0x0af1a5eb, 0x1ba47730, 0x67c69c}}}, - /* 7*16^45*G: */ - {{{0x0cd535ab, 0x01fbd802, 0x1d9370ce, 0x09b107d0, 0x1b9f3772, 0x01abe7e7, 0x18591009, 0x0c31c080, 0xabe2f3}}, - {{0x117b9c1a, 0x0388d9a2, 0x0b237664, 0x1cf43187, 0x1f7957fd, 0x1f959016, 0x0a4f7836, 0x0996eab6, 0x4f02d6}}}, - /* 9*16^45*G: */ - {{{0x0909970c, 0x1a5b359b, 0x19b93836, 0x11b74b33, 0x0099e451, 0x1d8fbbf3, 0x1c84df1b, 0x1af1873c, 0x227cd0}}, - {{0x1f809727, 0x02d25718, 0x0e67b10a, 0x01d87efd, 0x15defa21, 0x043a0e7f, 0x04761f5b, 0x0e390327, 0x2225e7}}}, - /* 11*16^45*G: */ - {{{0x09e65b59, 0x0cd6fe4c, 0x113fddf3, 0x02045efa, 0x1053b7a4, 0x14985466, 0x16da09fb, 0x10415db8, 0x363146}}, - {{0x09b4c2cf, 0x0050b213, 0x116dba72, 0x0792076b, 0x07fc1c14, 0x1c7c9011, 0x0a4a3a09, 0x0c42f12e, 0x1d87db}}}, - /* 13*16^45*G: */ - {{{0x0d4c2506, 0x0bd8ac5e, 0x07a7ebc0, 0x18bb8fe3, 0x11fec5b6, 0x14670c4e, 0x028f9d29, 0x16cd0d63, 0xf65ed6}}, - {{0x1913dfac, 0x0296e129, 0x15950af3, 0x11df8699, 0x0e7bd412, 0x0e17e9bb, 0x0ba14957, 0x0d065175, 0xd6d0bc}}}, - /* 15*16^45*G: */ - {{{0x1b47a80b, 0x0f27cba9, 0x0925d5e0, 0x0f8b4cc8, 0x1dba8ff9, 0x0e13b7d5, 0x0d5ca776, 0x1f423ec2, 0x66a0de}}, - {{0x1f795e8b, 0x1b8cafc7, 0x1bb74803, 0x014850a4, 0x0f474c23, 0x0f92b0d7, 0x09072b63, 0x0dbc6f59, 0x3e24aa}}} - }, - { - /* 1*16^46*G: */ - {{{0x08523eb3, 0x1eecd599, 0x1b1b12b9, 0x1474eaaa, 0x1cde3351, 0x0c22279e, 0x16ad1ab0, 0x1d7f1516, 0xbaffd4}}, - {{0x15acf387, 0x0bc18066, 0x122dbb86, 0x02399f7e, 0x0c09c245, 0x0759bcad, 0x1a0c00ea, 0x18bb792b, 0xfa93d}}}, - /* 3*16^46*G: */ - {{{0x14737641, 0x004cda77, 0x10b84eb4, 0x158182cd, 0x03fb71af, 0x1a891b2b, 0x07b5dde2, 0x04488391, 0x91b445}}, - {{0x0101bbe0, 0x1289594f, 0x01df40fc, 0x0fb8ccdc, 0x1b428c9d, 0x11ad5817, 0x05d8b04c, 0x17a6ffb5, 0x3f4463}}}, - /* 5*16^46*G: */ - {{{0x06dd01df, 0x1515d5c1, 0x03fbee71, 0x1dfeeca7, 0x09165743, 0x1363e434, 0x01ea8dfa, 0x0bbd05b6, 0x296d13}}, - {{0x0a3a0dc8, 0x0a869905, 0x199bd812, 0x15ec31cb, 0x177cadff, 0x094bbb63, 0x1790ae1c, 0x1bb25c28, 0x14054e}}}, - /* 7*16^46*G: */ - {{{0x1650f5dd, 0x13f37836, 0x046cb231, 0x1fc24843, 0x04a9654f, 0x0178f8cf, 0x08c63c4f, 0x1e7226cf, 0x4b0942}}, - {{0x1aeddc6d, 0x1752de63, 0x19ce98b0, 0x18cf05c9, 0x1c25f023, 0x0080ab09, 0x0f038157, 0x14c6cd95, 0x7432aa}}}, - /* 9*16^46*G: */ - {{{0x04e4a96f, 0x11baf478, 0x1956c51e, 0x10c5686a, 0x0e4af188, 0x0dc7e269, 0x046a0cfb, 0x14c5fd98, 0x36377}}, - {{0x11e08cdb, 0x1d7dbf02, 0x0244c9a2, 0x13184286, 0x06263840, 0x062abc2a, 0x1e0e364c, 0x03a6a9fb, 0xa1de19}}}, - /* 11*16^46*G: */ - {{{0x079b5a59, 0x00fe9f0b, 0x1a798a85, 0x03d13d64, 0x03251c62, 0x16ab84ec, 0x058af2ef, 0x1ee61ebc, 0xa1041a}}, - {{0x0c5514a3, 0x0695a011, 0x0b19b676, 0x00d21c3d, 0x02afbfb3, 0x086c39de, 0x0c650899, 0x0d551eb1, 0xad4217}}}, - /* 13*16^46*G: */ - {{{0x0ddd597b, 0x1746d836, 0x015d637a, 0x1262e199, 0x12007d88, 0x0d687cf4, 0x191c0cdc, 0x15a163ca, 0xda2167}}, - {{0x1de7fa04, 0x140b93f1, 0x13e7d189, 0x1c54a428, 0x0ebf6cdd, 0x180753cc, 0x14d87fd9, 0x16c4a8dd, 0xa21992}}}, - /* 15*16^46*G: */ - {{{0x13f7dbb0, 0x09a51115, 0x0fca7026, 0x1c47b84d, 0x0f29df4e, 0x1dc390b2, 0x19e2f218, 0x1846fed8, 0x1c3fdb}}, - {{0x12e16d32, 0x1265ee4b, 0x083e2b75, 0x0c8c7000, 0x118d41f0, 0x129ca525, 0x004fc2ba, 0x0206f253, 0x39260f}}} - }, - { - /* 1*16^47*G: */ - {{{0x1a111101, 0x1a9046c5, 0x16cf17e0, 0x1e1634ed, 0x04c96479, 0x11a692bf, 0x1d9bb48a, 0x0131f9da, 0x6ad2a}}, - {{0x1fb37ef4, 0x0d3dd4ea, 0x03a26bb0, 0x053b056e, 0x162a0de4, 0x000ddcf5, 0x18d56693, 0x038b1f0b, 0x2d5b8b}}}, - /* 3*16^47*G: */ - {{{0x102bef6f, 0x17e1c23b, 0x0c7b90dd, 0x1c9e308a, 0x0d475bba, 0x05eb35ec, 0x15c813de, 0x0aad8779, 0xf1a2ca}}, - {{0x0f6c1ca3, 0x0d968fec, 0x154ad004, 0x08fd503f, 0x00168b0b, 0x0a0bee01, 0x04fb7d15, 0x15e09106, 0xb39b59}}}, - /* 5*16^47*G: */ - {{{0x00455367, 0x030147e5, 0x1584f820, 0x09e59049, 0x11851e6c, 0x00b2c75f, 0x00c3b864, 0x093fc770, 0xe924ad}}, - {{0x08257e92, 0x1e8c67c1, 0x099d4ad7, 0x1463549f, 0x1bb6f52d, 0x029c9eb7, 0x1b55d482, 0x12f34287, 0xfcc97d}}}, - /* 7*16^47*G: */ - {{{0x02f8f1e7, 0x08f849ca, 0x147e78ba, 0x0954ba9b, 0x122f68dd, 0x09a882fd, 0x09be802e, 0x0fb8bee0, 0xf49d9c}}, - {{0x114d9972, 0x1114558c, 0x135ea0e4, 0x0002789c, 0x0f67901b, 0x09d9dcca, 0x12b9ab97, 0x08407c75, 0xf5585d}}}, - /* 9*16^47*G: */ - {{{0x1b219a79, 0x19d4b3bf, 0x0609e9de, 0x19a882fd, 0x189e65c4, 0x01aabaa8, 0x1522c38f, 0x007c8d53, 0x28b04d}}, - {{0x102dbe24, 0x05fbe6c8, 0x0012f97e, 0x0cd99f0c, 0x0206b861, 0x12b90c1f, 0x1c51673f, 0x13cb4299, 0xf658da}}}, - /* 11*16^47*G: */ - {{{0x17774af9, 0x14116ce2, 0x1182b62c, 0x0d74ca22, 0x0efb54c1, 0x01c94435, 0x1a4f5a14, 0x17cf983f, 0x3f4766}}, - {{0x1d4de990, 0x1e6bfb26, 0x0dcc9bfd, 0x1299fbf3, 0x05f511ca, 0x1c483737, 0x12e8eac5, 0x1ad4e663, 0xcc810d}}}, - /* 13*16^47*G: */ - {{{0x08e57705, 0x020cf8f2, 0x1356639e, 0x1d6ff590, 0x1361721c, 0x0d5a0eb7, 0x19e47cab, 0x00581f1d, 0xe3249e}}, - {{0x18caec0c, 0x0a58cf41, 0x1ce10882, 0x128bae2c, 0x06b5a501, 0x1c60f924, 0x141f72dd, 0x10e026d2, 0xa66665}}}, - /* 15*16^47*G: */ - {{{0x125b93ac, 0x1c5e757d, 0x01fe34b1, 0x0404a20b, 0x1b4e917e, 0x13b49efd, 0x1872f7e7, 0x017bf6f2, 0xa68958}}, - {{0x0f589aab, 0x11e8e26b, 0x113f4eba, 0x03f02fb3, 0x19ff2fcf, 0x1780af82, 0x00faa9fc, 0x12969e0f, 0x4657ca}}} - }, - { - /* 1*16^48*G: */ - {{{0x113728be, 0x07907fd9, 0x11ae529b, 0x072b2347, 0x15fc5964, 0x1fc1a218, 0x09d89cdb, 0x0ef4f092, 0xa6d396}}, - {{0x0357f5f4, 0x15d5c19e, 0x010166fc, 0x15241845, 0x1ecdf824, 0x1d5e9693, 0x00599ae2, 0x0e936171, 0x674f84}}}, - /* 3*16^48*G: */ - {{{0x0f15d864, 0x02a5ab4e, 0x13234f30, 0x0dfb8d43, 0x0cf35240, 0x1673df13, 0x0c36bf23, 0x1af8bbdb, 0x7ef66}}, - {{0x140907fe, 0x0312a13b, 0x1392a2d5, 0x0e1c7639, 0x1505e9f4, 0x062910fe, 0x1a941b50, 0x0bb713bc, 0x2db332}}}, - /* 5*16^48*G: */ - {{{0x17c7c05e, 0x14a8a1e0, 0x19505ab1, 0x07972a59, 0x08c0bb28, 0x1397baf9, 0x118053ce, 0x0bf80db8, 0x82df4e}}, - {{0x18fff26b, 0x0b09e816, 0x0b6cc02e, 0x0bb28d08, 0x1cbd1cf1, 0x0b10890b, 0x08289d48, 0x193192c8, 0xe4e188}}}, - /* 7*16^48*G: */ - {{{0x140368b0, 0x0570ea0a, 0x1ba52760, 0x09845f1f, 0x07a62132, 0x0dc69c72, 0x11a9f679, 0x13782561, 0x261efc}}, - {{0x1deb011f, 0x1d692acd, 0x06d74c04, 0x0817c3c7, 0x1ff797e3, 0x02966b27, 0x13a7f722, 0x1b7c70df, 0x8a9d2a}}}, - /* 9*16^48*G: */ - {{{0x15eb8034, 0x0819f4a8, 0x1a3292bc, 0x1666bead, 0x1692df30, 0x1f2cecf3, 0x1e4526a9, 0x1aef4584, 0x6d48df}}, - {{0x1ab0ce30, 0x039843d2, 0x0fa2587a, 0x0421d454, 0x14763080, 0x1cb24f02, 0x04bcf579, 0x08a2cbba, 0x3cb472}}}, - /* 11*16^48*G: */ - {{{0x140535c8, 0x08a1efe2, 0x036c4fad, 0x014ac619, 0x14e6f65f, 0x11fda7e2, 0x048e9244, 0x03cf7731, 0x93a5c0}}, - {{0x1d59b844, 0x04aba041, 0x16fb7ff1, 0x02c40926, 0x1a5c166a, 0x021ac70a, 0x0bd305aa, 0x12093018, 0x2d440e}}}, - /* 13*16^48*G: */ - {{{0x1e2047ba, 0x130d2b34, 0x0c3d94a8, 0x0e0932d7, 0x07031e54, 0x10700beb, 0x0aeecd76, 0x0522c24e, 0x3fb0b9}}, - {{0x1db24158, 0x1ff66a76, 0x1c0274d5, 0x0415cee2, 0x06dc86c4, 0x110e4cb3, 0x1e5329c9, 0x1cd042fb, 0x9d467a}}}, - /* 15*16^48*G: */ - {{{0x02df71c1, 0x05ededd7, 0x0edc8e80, 0x030d7d5f, 0x1e4381c3, 0x1dd4ef19, 0x0f5741d8, 0x073c11d0, 0xdab094}}, - {{0x04c3a1e3, 0x039a4209, 0x0d138eee, 0x0c661949, 0x00b3d6e9, 0x14379069, 0x13bce16b, 0x03ca89c3, 0x763cbc}}} - }, - { - /* 1*16^49*G: */ - {{{0x0514c6fd, 0x177b17fa, 0x0ac04f9b, 0x10769a1b, 0x15936fd6, 0x0dab887f, 0x1380cf53, 0x1001139e, 0xac25da}}, - {{0x05830541, 0x05a9cbb8, 0x0efcde98, 0x1307d048, 0x1338c810, 0x1498950d, 0x11ee20f6, 0x130b9689, 0xebc69d}}}, - /* 3*16^49*G: */ - {{{0x12fd0e3d, 0x0ca0e3f1, 0x09eb5820, 0x03c9b8a8, 0x05547e63, 0x04338f0b, 0x122ed35d, 0x0d893747, 0xeea7e6}}, - {{0x191d5868, 0x0208fb46, 0x0678f304, 0x175b4460, 0x17d985ac, 0x1f93df4d, 0x0984a210, 0x0b73f112, 0x83fed4}}}, - /* 5*16^49*G: */ - {{{0x0ccb63b3, 0x0cf8a793, 0x10239744, 0x1ffcd888, 0x1c67b7dd, 0x0a59aa01, 0x06f6eb46, 0x1a7d27c4, 0x9a3de2}}, - {{0x1df93fcf, 0x0e2994bb, 0x16089800, 0x003b3fde, 0x13c4c49c, 0x139b7740, 0x0b22027c, 0x120c2222, 0x2f809b}}}, - /* 7*16^49*G: */ - {{{0x163f1117, 0x07651a6e, 0x1fdb62f9, 0x12dee174, 0x1adaf348, 0x0698091f, 0x0b6c1440, 0x1e96a772, 0xa7c382}}, - {{0x1934cb9a, 0x091062f0, 0x1129a330, 0x1d6edda7, 0x1cfb5ae4, 0x1bbbb82e, 0x1e201167, 0x022cec37, 0x1b91e5}}}, - /* 9*16^49*G: */ - {{{0x06774a96, 0x05e40e2d, 0x0e38fa14, 0x063fb230, 0x0f497e82, 0x1dfe41d3, 0x084e1c50, 0x1319b0c2, 0x555aa9}}, - {{0x06db0e34, 0x0a04b96c, 0x189be9a7, 0x1aba9791, 0x0bbb89a0, 0x00f389a8, 0x11a66751, 0x0d1a40f7, 0xb05328}}}, - /* 11*16^49*G: */ - {{{0x0f7dac7d, 0x17bf779d, 0x0904848c, 0x0e572422, 0x1c369165, 0x0bc7c6bb, 0x0b5ed633, 0x0b66914f, 0x1a42f}}, - {{0x0195e46e, 0x1c4a518c, 0x13aa5ac9, 0x15e52651, 0x19216172, 0x1caa5c5f, 0x1e04d25f, 0x070aa40e, 0x957a9c}}}, - /* 13*16^49*G: */ - {{{0x002afc36, 0x015d0ea1, 0x0c1c74f8, 0x1bddaa28, 0x1d3a3134, 0x04f78da2, 0x18f4e96c, 0x06fd60b9, 0x4b47f5}}, - {{0x0f8133ff, 0x144fbb53, 0x17ef68d3, 0x1597d364, 0x1f573345, 0x037d0746, 0x1c30b72c, 0x0073390b, 0xf2fc45}}}, - /* 15*16^49*G: */ - {{{0x17d17f68, 0x15f971e3, 0x02eb61aa, 0x0b43bf97, 0x0418f791, 0x0b7a9b57, 0x033b5594, 0x1398a49d, 0x6b3dec}}, - {{0x09232402, 0x1d73d106, 0x1732da33, 0x0552d54d, 0x15d4747f, 0x00da0b66, 0x07bc1426, 0x06ffbdfb, 0xb86539}}} - }, - { - /* 1*16^50*G: */ - {{{0x0fd20bae, 0x0ea71bd1, 0x0d2a0455, 0x06ace5ab, 0x1343a260, 0x1d090bb6, 0x136409ee, 0x1db8779f, 0x285250}}, - {{0x14e0ab97, 0x0ef22ad2, 0x1bfcc8fe, 0x163459e3, 0x0c1716e9, 0x02568823, 0x1aa0fdca, 0x10de95af, 0x7866c0}}}, - /* 3*16^50*G: */ - {{{0x0636a50b, 0x0443b55d, 0x0dd465b3, 0x1dec2d57, 0x0baf65d2, 0x1d097e3c, 0x1d7160db, 0x0ca8bab4, 0xf1e3c5}}, - {{0x0b3128b6, 0x1bbc8a75, 0x0b5e9bb7, 0x1f4aeda4, 0x1f3136b7, 0x1533fb52, 0x139db1cd, 0x0f4dc3df, 0x12b884}}}, - /* 5*16^50*G: */ - {{{0x0e583340, 0x0bf8990a, 0x0d3cec94, 0x1836d6ba, 0x1228cf45, 0x06d5fd4d, 0x129db61f, 0x13903c26, 0x584d72}}, - {{0x14fb24b9, 0x17b72f4b, 0x05301c1c, 0x0ee14cc9, 0x0affa8f1, 0x1e2c9818, 0x02af34c1, 0x148ac1b0, 0x2fdd80}}}, - /* 7*16^50*G: */ - {{{0x0235809b, 0x1641f6f0, 0x1ae05ce4, 0x0e5be16b, 0x03c453c5, 0x0146e11c, 0x1df478b8, 0x001906fb, 0xbaaeae}}, - {{0x0154fd62, 0x0b0ec52e, 0x14b9f973, 0x18788543, 0x1f299835, 0x183de5a4, 0x0e02d288, 0x1067e649, 0x325788}}}, - /* 9*16^50*G: */ - {{{0x0d612268, 0x10021620, 0x17b405bd, 0x1eb3be14, 0x0b8b906c, 0x0f7d21ca, 0x0c69944e, 0x0c6c1842, 0x6c7e4}}, - {{0x060166a0, 0x05a5b009, 0x0b9c262f, 0x1b14b4f0, 0x053ca238, 0x03ae717a, 0x0335d1ff, 0x0bbee5bb, 0xcb6ad5}}}, - /* 11*16^50*G: */ - {{{0x012fbdc8, 0x0a1d1adc, 0x1038a8ef, 0x1c419545, 0x1a36db89, 0x1663db88, 0x10f96f0b, 0x1bd57acc, 0x64131}}, - {{0x09f99380, 0x09ff984d, 0x1ec08297, 0x15c4d163, 0x17598603, 0x006c9a4a, 0x00a3cace, 0x15865ace, 0x882c7f}}}, - /* 13*16^50*G: */ - {{{0x0bac9f32, 0x0f580032, 0x1a26c19d, 0x104398b2, 0x16400443, 0x00b7f0cd, 0x08de2859, 0x15984eb8, 0x366bb7}}, - {{0x1c85d47d, 0x17872e7d, 0x1c09a290, 0x19ca180f, 0x1cfc01fd, 0x01d5c6b0, 0x1c193c1e, 0x0e10f0b5, 0x7d107b}}}, - /* 15*16^50*G: */ - {{{0x06dd27eb, 0x1e5a9294, 0x0ed588c0, 0x18ab86f6, 0x032ecb98, 0x0871cddf, 0x1001ac9f, 0x04af98e7, 0xb767db}}, - {{0x0f65dac4, 0x0a90d23e, 0x1803b505, 0x0e016890, 0x1d85b64b, 0x05c0b5cc, 0x072d73ab, 0x1864c245, 0xdc1308}}} - }, - { - /* 1*16^51*G: */ - {{{0x1e07e4f1, 0x18f9d151, 0x17099f05, 0x0a28fba1, 0x0890e1fc, 0x15e03f7c, 0x19be0637, 0x00c5da06, 0xc472c1}}, - {{0x06b0daa9, 0x18e341ec, 0x0ad76295, 0x076f63c4, 0x067d885f, 0x15ad426b, 0x03a590ca, 0x00328bee, 0x41820e}}}, - /* 3*16^51*G: */ - {{{0x02f8acb1, 0x1b4a9a55, 0x0fc14fd0, 0x1acaa8c2, 0x0c31eb39, 0x1bd25c76, 0x0f5a1786, 0x0732a8bc, 0x86f88f}}, - {{0x05098a19, 0x05821688, 0x05949c45, 0x14c57dd2, 0x1b1214ee, 0x0bc7dac8, 0x035acdf2, 0x0d34d047, 0x2ccfdd}}}, - /* 5*16^51*G: */ - {{{0x1cadab66, 0x041bbdb7, 0x14d580b6, 0x0d46787b, 0x1f9a0e1c, 0x134b306c, 0x19d1f7dd, 0x03f93291, 0xa2a3be}}, - {{0x0c1aeb12, 0x1a509242, 0x002b497b, 0x1bd14a69, 0x15ee1fb2, 0x1f6ba691, 0x199c1941, 0x1cd6a497, 0xa93bd8}}}, - /* 7*16^51*G: */ - {{{0x007c25bf, 0x04e91099, 0x0b6025ee, 0x178eef7c, 0x080be82f, 0x09233be0, 0x16e4d9f8, 0x1daadcf1, 0x354e7c}}, - {{0x1a864211, 0x091d1cca, 0x082a8854, 0x0ead960b, 0x1e5585fd, 0x0c1252e7, 0x01c39baf, 0x1d377328, 0x629d62}}}, - /* 9*16^51*G: */ - {{{0x15e1dad8, 0x13ec1703, 0x112f4b92, 0x014f0b0d, 0x19f498b8, 0x07b90604, 0x016de6d7, 0x155ebbd0, 0xc751c9}}, - {{0x0e4afc8b, 0x09b59b09, 0x02a300b4, 0x112e1474, 0x1a4ffe6f, 0x00feb6e4, 0x178afad3, 0x06ff8f33, 0xcb9864}}}, - /* 11*16^51*G: */ - {{{0x099d9ba2, 0x1890d908, 0x0aa9d5f4, 0x0cafad2f, 0x19ca80cf, 0x09435a87, 0x0085e2ad, 0x08373a3a, 0xaee9cc}}, - {{0x1ed2ad58, 0x06b0241d, 0x05d2439e, 0x1cd1c4d4, 0x0b1f5312, 0x1dfd8063, 0x0dbdefa5, 0x005b6a54, 0xb72cf3}}}, - /* 13*16^51*G: */ - {{{0x094d208d, 0x1ff84124, 0x1ce46918, 0x1b51fe1f, 0x1f0cefb0, 0x1057958f, 0x1b15affd, 0x08d4f225, 0xd0d3f}}, - {{0x1d45d1b1, 0x117a9fbc, 0x0776aff5, 0x0781a34e, 0x09ff0b21, 0x06d32fc8, 0x05a6c11d, 0x1015c3ee, 0xdc371b}}}, - /* 15*16^51*G: */ - {{{0x19cfd890, 0x17411640, 0x01c45171, 0x0d86e6f8, 0x041e1cb3, 0x0c1c3f2a, 0x14d7d1f3, 0x0d68826e, 0x778d14}}, - {{0x16f057f2, 0x0d43fd3e, 0x1482b2b2, 0x148a911d, 0x0cd3796c, 0x18c6cbc3, 0x0bdca949, 0x02676023, 0x3c85ba}}} - }, - { - /* 1*16^52*G: */ - {{{0x039bb85f, 0x10784e1c, 0x14398b0c, 0x03f60d40, 0x13458010, 0x0164cd6a, 0x0cad55d6, 0x11a2ccc8, 0x55d539}}, - {{0x0fed936f, 0x1fb188c2, 0x0cf6787d, 0x1ad4fe30, 0x0a72ad90, 0x154f475d, 0x18b41671, 0x12093ff1, 0x576e22}}}, - /* 3*16^52*G: */ - {{{0x12ea285a, 0x03f15c90, 0x0e443470, 0x0383cdef, 0x0a7a39f0, 0x02c6eee0, 0x022de160, 0x011029f1, 0x1bb464}}, - {{0x107dc702, 0x006453cc, 0x0bea53b7, 0x05469732, 0x0a4fc1e5, 0x0cdc495a, 0x0496903f, 0x17f5f5c2, 0x8a3016}}}, - /* 5*16^52*G: */ - {{{0x1539785a, 0x08094806, 0x0a5fc051, 0x11a0ed70, 0x07a548c8, 0x1f133c31, 0x053b825c, 0x0e1c05f4, 0x4bc4e}}, - {{0x0a62e3bb, 0x1cc823ae, 0x0c9d70d4, 0x002ee4f4, 0x1fb4f877, 0x10e79f9e, 0x130e97d6, 0x04971969, 0x5729f2}}}, - /* 7*16^52*G: */ - {{{0x0114cfb5, 0x096a5d8f, 0x1569a8c4, 0x0c313547, 0x10876d13, 0x1cf0dbef, 0x05cd61dc, 0x1fb83f10, 0xd3e492}}, - {{0x097f7045, 0x075cd52b, 0x1d53bbef, 0x11e0dc8c, 0x1fea39c8, 0x133b5f8a, 0x122c7fb8, 0x014e7f18, 0x3604a7}}}, - /* 9*16^52*G: */ - {{{0x0c55ddff, 0x1133a5dd, 0x152de1c6, 0x1a367ec9, 0x1c1791da, 0x091887e6, 0x094e2939, 0x0ab0c508, 0xbebfc2}}, - {{0x1ea2f303, 0x0a6d8651, 0x02ea9c1b, 0x15045aca, 0x0576c3cc, 0x08e25bbb, 0x133a28e8, 0x0cb812c0, 0x850de2}}}, - /* 11*16^52*G: */ - {{{0x099ead51, 0x0d7a0f26, 0x164893a4, 0x06a87443, 0x184715da, 0x098e8b03, 0x1580beca, 0x1b8e7a41, 0x36e984}}, - {{0x0e0e5e3e, 0x17dcea13, 0x10a02bf1, 0x177d82e0, 0x12b203ba, 0x0fe4d671, 0x017d2fef, 0x08e437e9, 0x143771}}}, - /* 13*16^52*G: */ - {{{0x03093df0, 0x145bbdd9, 0x1d91935e, 0x1c4902ce, 0x193af785, 0x0a3caa6b, 0x12bbcc54, 0x00087ec0, 0x91b675}}, - {{0x12cc9f14, 0x06f461be, 0x12bd9fd7, 0x0d0bf1f3, 0x01c5d933, 0x1d6d4b71, 0x00351df1, 0x03cb0494, 0x7fedb}}}, - /* 15*16^52*G: */ - {{{0x1b7b8dc4, 0x11b3dc37, 0x06ce1228, 0x09260515, 0x02fac5b3, 0x1f0d01b0, 0x00f9f125, 0x18f43891, 0xc5d9c3}}, - {{0x1b2df0ea, 0x0b24bc68, 0x1f524dbb, 0x11ee5fa3, 0x10af3d0d, 0x01c302e2, 0x1e796023, 0x1c4b9fb6, 0x3e59b9}}} - }, - { - /* 1*16^53*G: */ - {{{0x12513926, 0x116de4e1, 0x1b217b31, 0x0d1281df, 0x03383cc2, 0x1d5925fe, 0x1c9a53fa, 0x08c79410, 0x884286}}, - {{0x0fb8a54d, 0x17214155, 0x16f05683, 0x1d6245d7, 0x01207ce0, 0x06d9b2ec, 0x1569a598, 0x1380385b, 0xf77842}}}, - /* 3*16^53*G: */ - {{{0x0520f1f1, 0x095d60cb, 0x1fdf693b, 0x03693cc8, 0x16f4ad7f, 0x1e8b4667, 0x00675697, 0x06deede1, 0x3bad9d}}, - {{0x1698df4d, 0x10cb8392, 0x0d9c961a, 0x1303449d, 0x00e80499, 0x1d3ecef6, 0x1966f367, 0x1c67c49c, 0x49f99d}}}, - /* 5*16^53*G: */ - {{{0x1185ae66, 0x18f22545, 0x09e0f7c2, 0x182db7df, 0x0118ba6e, 0x0fa9e892, 0x11b59e9f, 0x1635b618, 0xa6b3a4}}, - {{0x0bfbf21b, 0x1f5aa9fc, 0x0c92375e, 0x1e14bbeb, 0x1e8d8bcb, 0x148e6cb4, 0x188c4d86, 0x1aec7112, 0xcb6e8}}}, - /* 7*16^53*G: */ - {{{0x0c630e3a, 0x0b426727, 0x1cfc70aa, 0x00c182ff, 0x110c1f61, 0x11d3ec3f, 0x1293889f, 0x0b4d9222, 0x1f848f}}, - {{0x06ca610a, 0x176cffb4, 0x0d65c92b, 0x0724d7fc, 0x1a4264ba, 0x041e8d4e, 0x058e18a7, 0x0599a0df, 0xa02855}}}, - /* 9*16^53*G: */ - {{{0x041bcd10, 0x0ff79e6e, 0x0d138553, 0x12aaec8b, 0x02eb211b, 0x133be365, 0x0d622c68, 0x097d2938, 0xcfd1e5}}, - {{0x0a62e732, 0x000af5cd, 0x154e596b, 0x1322ba10, 0x12fafacd, 0x08dead82, 0x02ee715d, 0x0bec012b, 0xf4e31f}}}, - /* 11*16^53*G: */ - {{{0x028da5fb, 0x1d30ec91, 0x01c39c93, 0x097ebc55, 0x1291b90e, 0x0af1f3b8, 0x19544b1e, 0x01808d93, 0x4b3e73}}, - {{0x1c169e48, 0x1f8531c5, 0x08d1caef, 0x07423938, 0x0b48c65a, 0x1d03dc70, 0x03a7a032, 0x09b446cc, 0x8c4096}}}, - /* 13*16^53*G: */ - {{{0x1f894780, 0x0748d2c4, 0x06bb176e, 0x139946b1, 0x0d8f737e, 0x1c52a5a1, 0x1f9c7552, 0x1bee8871, 0xafe915}}, - {{0x1511d444, 0x1cb4aa9e, 0x102bd14b, 0x1d17ce75, 0x12e36909, 0x01a1199f, 0x1a1aa52d, 0x0811a408, 0x3caec3}}}, - /* 15*16^53*G: */ - {{{0x1182fa6e, 0x0d18fe5e, 0x00722bfd, 0x0c65bad3, 0x1c9e7c0a, 0x11d1b69a, 0x1215619a, 0x05021ff5, 0xc0548}}, - {{0x061cd145, 0x15e7f55b, 0x1db407db, 0x0cc8b096, 0x1602bf5e, 0x05c4a32e, 0x14cf46aa, 0x195a1e3e, 0x2f9cd2}}} - }, - { - /* 1*16^54*G: */ - {{{0x14441fbb, 0x105aead6, 0x1b44578f, 0x150a414b, 0x125559ea, 0x062af6dc, 0x0751fed8, 0x09c43bc4, 0xb4958c}}, - {{0x0add7118, 0x03f1e4fa, 0x118e1053, 0x13ec265a, 0x0b7e12db, 0x00fde1c6, 0x03bf9701, 0x17f32cc8, 0xfcd6e7}}}, - /* 3*16^54*G: */ - {{{0x171f07f1, 0x176b4261, 0x15c31296, 0x19db6e61, 0x0684b878, 0x105b2303, 0x11348cba, 0x0bcf6408, 0xb3a43f}}, - {{0x001dfda5, 0x1e6917a7, 0x12c3b314, 0x10ef419b, 0x15ee7ec7, 0x1139b4cb, 0x1ae1060c, 0x0b6491fa, 0x34c64f}}}, - /* 5*16^54*G: */ - {{{0x0b559d49, 0x1be4bd23, 0x1726d13f, 0x0368d21b, 0x008b148e, 0x17fec260, 0x052b0998, 0x0d348d7c, 0x452e98}}, - {{0x1a0d6ba7, 0x03e30236, 0x07b9095d, 0x050ad876, 0x1ee52598, 0x02bdb3b0, 0x0343d700, 0x11e32bf0, 0x8c5e8a}}}, - /* 7*16^54*G: */ - {{{0x13ce94da, 0x1bd33f5a, 0x00a79814, 0x049e84ad, 0x074ee8bb, 0x106e1d62, 0x0aed4737, 0x1a918dac, 0x19a7f5}}, - {{0x1025e7bb, 0x182238b1, 0x097ac0dc, 0x04a60d5c, 0x0a2e8fb6, 0x08ea2100, 0x170cbda9, 0x14f5260e, 0xa2504b}}}, - /* 9*16^54*G: */ - {{{0x147dc697, 0x0b4b6636, 0x1856e6ee, 0x1c315f6b, 0x06fa417e, 0x18595afe, 0x1370047a, 0x004149e6, 0xbdaf5b}}, - {{0x06a479e2, 0x088e5f3c, 0x0de91dee, 0x045cf10b, 0x1aa08551, 0x1af23dab, 0x0db233d5, 0x0d97bf50, 0x3cec0c}}}, - /* 11*16^54*G: */ - {{{0x0ce05e10, 0x1005c8db, 0x1841880a, 0x1ef93e87, 0x070db8ae, 0x1bff4267, 0x0576ae12, 0x1d2be73a, 0x265dba}}, - {{0x171324f4, 0x1bf80c58, 0x19319fff, 0x07e7267a, 0x15dcb066, 0x0745ab65, 0x1eb7cecf, 0x1a134606, 0x58df63}}}, - /* 13*16^54*G: */ - {{{0x13fe5938, 0x08ade2ad, 0x0db5f37b, 0x10607b6c, 0x068669ec, 0x04ea9d2b, 0x0e5a27dd, 0x07e2fccc, 0xc43e08}}, - {{0x183732f3, 0x13cbab76, 0x1101d1dd, 0x0460e2c4, 0x0402eab6, 0x0181d5e2, 0x1160d424, 0x12473f79, 0x54c602}}}, - /* 15*16^54*G: */ - {{{0x1dd52975, 0x18e387bc, 0x0d106030, 0x1e1fa60d, 0x066ba2bc, 0x086d3bd9, 0x121996b1, 0x0e0147f0, 0x7868f1}}, - {{0x1c626fac, 0x00b76a81, 0x05a4110c, 0x0df3d94d, 0x1276c68f, 0x10e36d88, 0x1b8444fe, 0x19e6242f, 0xe89097}}} - }, - { - /* 1*16^55*G: */ - {{{0x0e12e1df, 0x0127321b, 0x1d87412b, 0x0ffa16fa, 0x0027cd8a, 0x1f89d9a3, 0x0ad904d2, 0x12d11d26, 0xd0e091}}, - {{0x1fd28fbe, 0x132a26dc, 0x11ae37da, 0x19897b30, 0x1f867544, 0x105b48ed, 0x114ad3ad, 0x0b3fcfa2, 0x69c9a}}}, - /* 3*16^55*G: */ - {{{0x084aa098, 0x186c2880, 0x1b8f80ae, 0x02028152, 0x1fa8509c, 0x1ed65fe0, 0x03ace629, 0x0a942661, 0xb517a4}}, - {{0x0540efbf, 0x0025acfa, 0x0911ff58, 0x0916a8d2, 0x06fa3a4d, 0x1f17d879, 0x1e6983a8, 0x0fa183f0, 0xa3d87}}}, - /* 5*16^55*G: */ - {{{0x0744bfa1, 0x0cad6552, 0x04d90f5b, 0x0da4f9c1, 0x1e387cc2, 0x13896c79, 0x1bd9ef08, 0x07096a2c, 0xf8ec14}}, - {{0x12b65f6d, 0x14927319, 0x04001831, 0x06f58b87, 0x00f610a6, 0x07d934eb, 0x0698c8da, 0x164227f7, 0x761134}}}, - /* 7*16^55*G: */ - {{{0x1227a4bb, 0x1161df49, 0x03667cbd, 0x0d63e01f, 0x0f2e64be, 0x075690ea, 0x0b9e539d, 0x0f1b6f7f, 0x320cff}}, - {{0x10f3d2d4, 0x00e64835, 0x18be5c16, 0x0e46e813, 0x16299604, 0x0b512a7f, 0x1a4aadde, 0x1a80e550, 0xaf9fe8}}}, - /* 9*16^55*G: */ - {{{0x1c2ca683, 0x1adad2f2, 0x0569cdce, 0x19e6bc15, 0x1426a206, 0x0ee65aa1, 0x16145fb7, 0x0f8d4f5d, 0xc08de}}, - {{0x1db5f259, 0x12036dab, 0x1a9a31a4, 0x11af6fc1, 0x00e79c3c, 0x14ce6fe7, 0x1866df20, 0x10abd42d, 0xddb76d}}}, - /* 11*16^55*G: */ - {{{0x052ae5cd, 0x033d67c1, 0x1f75e187, 0x0ca5f5e9, 0x0390995b, 0x1bd22672, 0x10f4639b, 0x0d5a188f, 0xd1f8c7}}, - {{0x1e6d2dda, 0x15cbde1f, 0x027d3f1f, 0x15d02ad3, 0x1203239b, 0x0bd80fb0, 0x000ab1e6, 0x18cc241d, 0x74d45d}}}, - /* 13*16^55*G: */ - {{{0x0bdc603f, 0x1c803355, 0x17ff96ad, 0x1acb9acf, 0x020d8c96, 0x1f63133b, 0x03024f8c, 0x0d27e712, 0xa6cb83}}, - {{0x096befcc, 0x16701f06, 0x1985cd72, 0x1d82d498, 0x10b72fb1, 0x0ded2628, 0x0bf23cb6, 0x1c8c3e79, 0xd823c8}}}, - /* 15*16^55*G: */ - {{{0x02c374b0, 0x0f1d3097, 0x1c36d28a, 0x166b316a, 0x04ef0bf5, 0x04b8a921, 0x0c84dafb, 0x123d4d86, 0x8a6c9c}}, - {{0x178c08bd, 0x1fbe7c6d, 0x03d3560e, 0x0a69e868, 0x132a0461, 0x042ee480, 0x1ebde69e, 0x09ecb9bf, 0xe4bc7f}}} - }, - { - /* 1*16^56*G: */ - {{{0x0895df07, 0x1381f887, 0x01daf61a, 0x0be7f403, 0x08ffefd7, 0x03738670, 0x1fbbad6c, 0x0a84f07b, 0x68f6b8}}, - {{0x18712655, 0x063b7c53, 0x042fdfe4, 0x0527a5e6, 0x05028cf5, 0x0226fed2, 0x139bef20, 0x17525c81, 0xcbe1fe}}}, - /* 3*16^56*G: */ - {{{0x1aa89692, 0x00c7d119, 0x1308c239, 0x1611adf2, 0x0a776713, 0x1320920c, 0x1d37a65b, 0x0e302cd1, 0x3bdfca}}, - {{0x1a510308, 0x1ededa18, 0x18bbc4e4, 0x1818c68b, 0x05333c7d, 0x10a8d76d, 0x1ee12509, 0x0d0aca2c, 0xa721f}}}, - /* 5*16^56*G: */ - {{{0x037ea1d7, 0x1cd91a16, 0x06ab9341, 0x126cb1f1, 0x19e4fb23, 0x02a928c6, 0x0158d4ca, 0x12b6ea42, 0xaca8ac}}, - {{0x1ba973a2, 0x0f7d8824, 0x0e4d7a77, 0x1b7fb882, 0x12189e1e, 0x0625c943, 0x108250c1, 0x0cfbaeb9, 0x7f12c5}}}, - /* 7*16^56*G: */ - {{{0x13245b7f, 0x0d93c0e6, 0x165fefd7, 0x1acd2d20, 0x05311a37, 0x10d7cc6c, 0x103881b0, 0x009b6ccf, 0xfa9047}}, - {{0x0ddf6bef, 0x1c19ef8a, 0x13c751fb, 0x1dd9a4c4, 0x1c189cb9, 0x11f6a078, 0x1a90b5be, 0x06ce9506, 0x1a1c8c}}}, - /* 9*16^56*G: */ - {{{0x17f0953a, 0x10fbc7eb, 0x0550e6ad, 0x122907fe, 0x092a8184, 0x1c70e97d, 0x163359ca, 0x15723eaf, 0x1d431a}}, - {{0x1d68f260, 0x1e2cebb4, 0x1c1f9f05, 0x1c535210, 0x1fae0f48, 0x0641e70a, 0x087af14c, 0x1877e322, 0x8d667}}}, - /* 11*16^56*G: */ - {{{0x05227c70, 0x1188b89b, 0x08fa9c16, 0x17d56667, 0x1c60ff32, 0x19ad9718, 0x04df7692, 0x01ab47c2, 0x6deeea}}, - {{0x0369b9b7, 0x0b6d1c08, 0x0420f6eb, 0x15d83cad, 0x0cc84287, 0x05d7f7b5, 0x19000053, 0x01f8e887, 0xcbb93f}}}, - /* 13*16^56*G: */ - {{{0x0421b54d, 0x1afeaf44, 0x159293fe, 0x1657074a, 0x09dca579, 0x0e95d8fd, 0x036352b2, 0x020c6aaf, 0x28135c}}, - {{0x0ee5d868, 0x0e6784dc, 0x18c4362b, 0x09299923, 0x18c15ef0, 0x0eba083f, 0x18541bea, 0x17c70a37, 0x9ed84a}}}, - /* 15*16^56*G: */ - {{{0x08e4ac10, 0x0bf2ac8f, 0x1892a6a4, 0x0d502559, 0x1b568799, 0x062d04ff, 0x1def5b0f, 0x0ec41620, 0x339a05}}, - {{0x0d1312a0, 0x1b6d4322, 0x07319bbd, 0x13cf11e8, 0x1553e503, 0x06fbc567, 0x11fd0e15, 0x17dbad52, 0xca985f}}} - }, - { - /* 1*16^57*G: */ - {{{0x07ea1f34, 0x1a6d58bb, 0x1ce78374, 0x17a6a808, 0x153d6646, 0x0e8bc9d9, 0x0d9b0ed0, 0x1447a8e9, 0x7f9460}}, - {{0x0d9063ec, 0x05f61656, 0x18d3ff16, 0x1f02a249, 0x16661c6f, 0x195fc783, 0x061da7c7, 0x0ef1f60c, 0xd0d516}}}, - /* 3*16^57*G: */ - {{{0x1dfc0a9d, 0x02dba64d, 0x1ddea837, 0x0846b629, 0x0a2a2de4, 0x11b23570, 0x1808a983, 0x1f098653, 0xdb43e7}}, - {{0x1e9ad713, 0x058849d4, 0x14bc153c, 0x1208e6ad, 0x19fa4883, 0x1640a677, 0x1646e295, 0x1457c6d6, 0xb0168d}}}, - /* 5*16^57*G: */ - {{{0x0283808d, 0x05e58bba, 0x190ea24c, 0x1e0f9c6a, 0x078980f8, 0x0f4890fd, 0x06bae145, 0x103dc1a4, 0xe9af30}}, - {{0x19b39d33, 0x13617c71, 0x04db4665, 0x1724a22a, 0x14971976, 0x132a87f7, 0x098216bc, 0x091388e2, 0xa976c8}}}, - /* 7*16^57*G: */ - {{{0x0d8426df, 0x1497a165, 0x1be28289, 0x0272b49c, 0x083590f4, 0x049c1dfc, 0x0601c0eb, 0x0c65900d, 0x6eb733}}, - {{0x000b4267, 0x1fbacf71, 0x1f86e0cf, 0x05d2f907, 0x08e8d925, 0x05967660, 0x0a680c39, 0x1822e60f, 0x96c56e}}}, - /* 9*16^57*G: */ - {{{0x1a16453d, 0x1e8b6be0, 0x040cf6fd, 0x1d0f7cec, 0x0d0e68b7, 0x0dc8fc5a, 0x1d1785d0, 0x0bc0f3ab, 0xd3979d}}, - {{0x07b6d737, 0x1e5dc18b, 0x1a58693c, 0x1becc514, 0x0456917d, 0x092ba9b9, 0x16e69342, 0x06baf335, 0xc55f93}}}, - /* 11*16^57*G: */ - {{{0x0d326504, 0x150ccd2f, 0x0d480b33, 0x15368c4f, 0x0fc12f65, 0x1dc460d7, 0x08d0b0e0, 0x139cb718, 0x54c392}}, - {{0x1ec9e107, 0x1f808fd3, 0x0299c9a2, 0x0a7b3cc1, 0x0ab4447a, 0x1514248a, 0x1b431226, 0x04ac1d42, 0x469630}}}, - /* 13*16^57*G: */ - {{{0x1b5ecce7, 0x0227abb5, 0x03484d4c, 0x102b1618, 0x059c8732, 0x0741e0cb, 0x1b16e13a, 0x1650ccda, 0x3744e9}}, - {{0x0a247721, 0x00990a0b, 0x09be0e48, 0x116be0a5, 0x1ec3c28e, 0x14ab7594, 0x1ea83839, 0x1f1b3208, 0x9546e0}}}, - /* 15*16^57*G: */ - {{{0x05fef996, 0x064946f2, 0x1094d454, 0x0d1ec728, 0x1f2de140, 0x08520f79, 0x154cc933, 0x02fc6cad, 0x11343e}}, - {{0x1792aded, 0x00a8573a, 0x01bc6390, 0x1c9acb41, 0x13765d0c, 0x0fc98313, 0x1bbac137, 0x101bd751, 0x4a9e84}}} - }, - { - /* 1*16^58*G: */ - {{{0x0e6d8483, 0x164da6bc, 0x059fc373, 0x05af508d, 0x0e94582f, 0x1a4f8db7, 0x18f4a563, 0x0c1467aa, 0x9c39cb}}, - {{0x0b2c50fb, 0x0599ab7b, 0x0f90da25, 0x1bca5e7b, 0x16546bba, 0x0b5bde50, 0x14400f46, 0x03dc927c, 0xf097bf}}}, - /* 3*16^58*G: */ - {{{0x07a14dda, 0x0ae18fc2, 0x12d37cfd, 0x1a0852ce, 0x13083606, 0x147b9200, 0x0b5d10dd, 0x19921d5b, 0x18f37a}}, - {{0x05ac8364, 0x117e6e19, 0x014db2f8, 0x11ef8f15, 0x1cd0b77d, 0x1ff6770d, 0x109b5eef, 0x17554125, 0x2f944b}}}, - /* 5*16^58*G: */ - {{{0x0c7d59be, 0x13a74746, 0x024cbed5, 0x1430b11c, 0x0736b98e, 0x1ff723b9, 0x07693b17, 0x118503cf, 0x541335}}, - {{0x04f80590, 0x0bf50fb7, 0x002cd9cb, 0x04b62b92, 0x0515a53e, 0x07e900b2, 0x00939f12, 0x1bd2d396, 0x680fd4}}}, - /* 7*16^58*G: */ - {{{0x076051a8, 0x15c064fc, 0x115fa963, 0x0ee72c74, 0x05280bed, 0x00d1e0dc, 0x13c1773f, 0x04d23632, 0x4e2fd1}}, - {{0x09cc9005, 0x17f63a7f, 0x113f8b9a, 0x11754b25, 0x031bcebb, 0x1ad0a845, 0x0ec8dc6c, 0x1b7ebe9f, 0x122abb}}}, - /* 9*16^58*G: */ - {{{0x16a80e09, 0x13e547d2, 0x097d7f8d, 0x0be1eecc, 0x08fa0a27, 0x1ab409e0, 0x0d648013, 0x1a97dfe3, 0x2de758}}, - {{0x036a1cd3, 0x0b176faa, 0x16b5b267, 0x15b8cd2b, 0x064a07a1, 0x1958132f, 0x199f5f00, 0x062efb1d, 0xdd9acf}}}, - /* 11*16^58*G: */ - {{{0x1a3628ac, 0x1281ad97, 0x16d593b0, 0x177459be, 0x012a4568, 0x10b9e377, 0x095ca316, 0x159b83a9, 0xf597a0}}, - {{0x0cd3550f, 0x1501886b, 0x04841b9f, 0x1d9f23a8, 0x02cc0772, 0x1db944b1, 0x1155eec6, 0x11c6657a, 0xdb916}}}, - /* 13*16^58*G: */ - {{{0x0b75d6c3, 0x06b41ea5, 0x0e8a159e, 0x14a8afaa, 0x040f3c42, 0x038c10df, 0x1ee42284, 0x10dade89, 0xaa222a}}, - {{0x044cb028, 0x1932d273, 0x0a323d84, 0x03f9296b, 0x0df42607, 0x0512d771, 0x19db3912, 0x12600351, 0x563787}}}, - /* 15*16^58*G: */ - {{{0x035790c9, 0x1d42b9c9, 0x1cf140df, 0x03722ee7, 0x15e2e9e0, 0x0321c979, 0x16dd5bc3, 0x0d79b2e2, 0x8568b2}}, - {{0x06b1f5ac, 0x09faa9c1, 0x0151e7f7, 0x0bcbf1f7, 0x03ce8014, 0x0705721b, 0x0ab7a41e, 0x034b09ba, 0xd3f226}}} - }, - { - /* 1*16^59*G: */ - {{{0x1c38d4e4, 0x0ed4be2a, 0x0c5cdd1f, 0x1acbe363, 0x01e25e2f, 0x173a180c, 0x04e25d59, 0x04c22453, 0x3d9285}}, - {{0x00ccf0d3, 0x1d577806, 0x1eaf1fdb, 0x15627032, 0x069eacc0, 0x0b81ae14, 0x0ad4ffc1, 0x14ba8d1d, 0xd32ca0}}}, - /* 3*16^59*G: */ - {{{0x0dd7718a, 0x039192c0, 0x0f9393b4, 0x1036ca33, 0x0dff891c, 0x0cd12f3b, 0x1f0fdf05, 0x06e57205, 0x3b180e}}, - {{0x1afd82db, 0x1331314c, 0x0b45341c, 0x1222ace0, 0x1211e584, 0x1e4e9482, 0x02abea92, 0x02fe6d6b, 0xf2b6b1}}}, - /* 5*16^59*G: */ - {{{0x13d3653d, 0x140d288c, 0x1919f57e, 0x1d8964e9, 0x03eb2134, 0x07a04c54, 0x127d17bd, 0x00723ad3, 0xf352ff}}, - {{0x0a44f28e, 0x0f333c64, 0x1160b41e, 0x132bf2e5, 0x0d5055c1, 0x0b9efed3, 0x1af4082f, 0x0d996d8b, 0x447262}}}, - /* 7*16^59*G: */ - {{{0x0d4478da, 0x130ef8d7, 0x03805535, 0x19ed8448, 0x13ca1f15, 0x02e6dfbc, 0x108c2bed, 0x0e2069b1, 0x670ee4}}, - {{0x14f563f0, 0x147ff27f, 0x1c91dc18, 0x07702c1f, 0x150e81cc, 0x102d89c3, 0x0b289519, 0x084b7404, 0x5ca0c7}}}, - /* 9*16^59*G: */ - {{{0x03dbd581, 0x02714822, 0x15acd6eb, 0x1d671051, 0x06fa93cf, 0x1d185676, 0x0f6fbeef, 0x0a8693b3, 0x96f2e3}}, - {{0x08f59952, 0x0cd27ff7, 0x13d75153, 0x031e3aa0, 0x1baf435a, 0x0c71cb06, 0x1b3d3f97, 0x0110baa4, 0x9fcd8}}}, - /* 11*16^59*G: */ - {{{0x04e9c2fc, 0x159a1925, 0x0f5c1a87, 0x19e19e5f, 0x09a35e72, 0x01058a30, 0x06b20106, 0x0a2fd073, 0xc4946e}}, - {{0x06398ce8, 0x01a3b1bb, 0x1188c48d, 0x17e71da2, 0x18237e48, 0x07b47a3d, 0x0933a668, 0x041630b1, 0x748901}}}, - /* 13*16^59*G: */ - {{{0x04960b03, 0x05c8b9f4, 0x0023a3d7, 0x18756191, 0x14d8fb6a, 0x0462bc04, 0x0f6fe923, 0x09b537c6, 0x1653b6}}, - {{0x10f73c69, 0x00352a75, 0x0d5939fe, 0x11c1c943, 0x1a8948b3, 0x15ec1f4a, 0x15827d2c, 0x102d3cba, 0xa58370}}}, - /* 15*16^59*G: */ - {{{0x1b63640e, 0x0abbf18d, 0x11cb7cb1, 0x176fe521, 0x1cbf4979, 0x13ce5342, 0x14fd4031, 0x1afda5e2, 0x51076d}}, - {{0x02e4476c, 0x1b4b943c, 0x083bc087, 0x1d49d3ce, 0x0fda6c8b, 0x1280b970, 0x1ddabaf9, 0x0945adbb, 0xb39727}}} - }, - { - /* 1*16^60*G: */ - {{{0x044e8de3, 0x0318258d, 0x130781d8, 0x112cd45d, 0x117915c0, 0x1ee7845e, 0x02dce969, 0x16e8d102, 0xf50b99}}, - {{0x1b7f3588, 0x11f9dd36, 0x1c87a152, 0x0be31a42, 0x1cebbe97, 0x0b9d16f6, 0x1c321e26, 0x03cabe31, 0xe2b506}}}, - /* 3*16^60*G: */ - {{{0x02accf8b, 0x0ee35b5c, 0x005be9f7, 0x05332305, 0x1430481d, 0x1871289c, 0x1dc1917c, 0x0c34aa0a, 0x598d7f}}, - {{0x0f6cb808, 0x1c2339e0, 0x0d502e46, 0x11351e6a, 0x1ebcad22, 0x08b15939, 0x182551b1, 0x1ee9f1e4, 0x9f3121}}}, - /* 5*16^60*G: */ - {{{0x04aa7b3e, 0x1d9cd2a3, 0x1b273aa7, 0x09de360a, 0x0581013c, 0x1048aa0e, 0x113593f4, 0x025e93e9, 0x715a20}}, - {{0x1c7d081d, 0x0d19ca25, 0x02d1f436, 0x178b1151, 0x13b62421, 0x1447c548, 0x10287de4, 0x16354c0d, 0x5922b3}}}, - /* 7*16^60*G: */ - {{{0x020db220, 0x0dedb4bb, 0x01eb3934, 0x1996202d, 0x07876c71, 0x0744bfdc, 0x04971027, 0x0bcd5536, 0x49ec8c}}, - {{0x077338c2, 0x0dc56503, 0x0ee733a6, 0x1860e7ca, 0x15429842, 0x061a432f, 0x0a6cf6a7, 0x09fcbd4c, 0x99a97d}}}, - /* 9*16^60*G: */ - {{{0x1e771268, 0x0f5e518a, 0x02995a14, 0x1e294fb2, 0x07b7a2f4, 0x0c8702f0, 0x1120f9bc, 0x01a90a16, 0x1f8fb7}}, - {{0x0909c8dd, 0x17e98086, 0x04aceac6, 0x1a786239, 0x192a14e1, 0x16ba3930, 0x0afc4b0b, 0x1b68c374, 0xf53e7a}}}, - /* 11*16^60*G: */ - {{{0x08d9819e, 0x0f607959, 0x0b6ac695, 0x0cf25ee8, 0x0732cd60, 0x0a15d33c, 0x187f7574, 0x034c92fe, 0xb8d5c7}}, - {{0x09138ac4, 0x03c5f475, 0x15170f37, 0x093e26c3, 0x1dc79e2f, 0x121acdb1, 0x1e08edbb, 0x1e26426f, 0x1cd14e}}}, - /* 13*16^60*G: */ - {{{0x00bca3f3, 0x149edf33, 0x1801241a, 0x0686a28a, 0x0d8c4ecb, 0x15b0e440, 0x18f7758f, 0x158cb755, 0x2265b5}}, - {{0x117409ff, 0x0c14e362, 0x0e4f5689, 0x014c25a4, 0x164983c8, 0x09d1d884, 0x183d868c, 0x17eb959f, 0x97a198}}}, - /* 15*16^60*G: */ - {{{0x05bac36e, 0x19691ffa, 0x1d77340d, 0x11328b32, 0x1abcb599, 0x054fafe4, 0x049487f6, 0x1a206b09, 0xaf381c}}, - {{0x11b119a9, 0x19ec43d5, 0x1352a43f, 0x1705e3de, 0x02648589, 0x1f914a8d, 0x1ef72515, 0x0ff6fbe1, 0x681a08}}} - }, - { - /* 1*16^61*G: */ - {{{0x0037cfb4, 0x10cca02b, 0x136aa167, 0x01e48d87, 0x0b0e0740, 0x1a4406d0, 0x142c35df, 0x1047febd, 0x42531}}, - {{0x18775d23, 0x05b992f6, 0x1b177500, 0x07c9ea69, 0x01faceea, 0x12686433, 0x1df98a32, 0x199f5c01, 0xc90e8b}}}, - /* 3*16^61*G: */ - {{{0x06c34577, 0x027b39aa, 0x094abdde, 0x013d91cd, 0x0e4bde64, 0x11847460, 0x0922c7d7, 0x141c6179, 0x27557a}}, - {{0x0416193c, 0x0ff5cdc0, 0x110e02eb, 0x0594e6e9, 0x134f318a, 0x0bad24fe, 0x0ceddf23, 0x0b08c20b, 0x8399c7}}}, - /* 5*16^61*G: */ - {{{0x0401f4d3, 0x12c7edb5, 0x056cc07b, 0x185ca1d5, 0x1d7decf6, 0x1c1dfab0, 0x0d923941, 0x02fa4b0e, 0x8e6878}}, - {{0x0294d86b, 0x0140f4a2, 0x08644a24, 0x172de25d, 0x13cae900, 0x04d02836, 0x0fe98be0, 0x110dc593, 0x989cab}}}, - /* 7*16^61*G: */ - {{{0x0d2aa8e6, 0x0cb1ef13, 0x1bff8b71, 0x06b3f881, 0x1dbee205, 0x1401e533, 0x13db440d, 0x08c4a7cb, 0x98a417}}, - {{0x006cf75b, 0x0ba05f6b, 0x1fb4865a, 0x042ff556, 0x1cec9e30, 0x017ad17a, 0x0f5ac455, 0x128fc68c, 0x579ac6}}}, - /* 9*16^61*G: */ - {{{0x152c2d31, 0x0516647b, 0x187bb35f, 0x01576118, 0x1a946180, 0x17221f10, 0x03f64885, 0x084460a8, 0xe16854}}, - {{0x04a50fec, 0x05c41b41, 0x0660e507, 0x0c3257f1, 0x1c9343e7, 0x13216815, 0x0850becb, 0x0b9251ce, 0x70085}}}, - /* 11*16^61*G: */ - {{{0x13a7cdad, 0x119dd71b, 0x02f3ebd2, 0x1b07a5ca, 0x151a53ca, 0x117299c4, 0x0fa8728a, 0x09613aa2, 0xbfa631}}, - {{0x095cb953, 0x0fc44981, 0x1011e871, 0x0321f190, 0x0d1a7261, 0x02ddd8f7, 0x11e0a97e, 0x03299005, 0xb452e8}}}, - /* 13*16^61*G: */ - {{{0x0ade0fb7, 0x0de64280, 0x1946363b, 0x1b8e9bd4, 0x18e200c6, 0x0baf36ec, 0x134fb3c2, 0x05a152c3, 0xe68708}}, - {{0x1d4d6ece, 0x0bcf0798, 0x03089664, 0x03d0bdf1, 0x1a72117c, 0x072990e8, 0x1555797c, 0x090d0992, 0x2135a4}}}, - /* 15*16^61*G: */ - {{{0x0e432daf, 0x01e5af86, 0x009eb272, 0x1db41155, 0x14975f3b, 0x146bca83, 0x07a52ff0, 0x1f0c5535, 0x7ab16e}}, - {{0x071bdc48, 0x08bac455, 0x13f6c04c, 0x139c75ce, 0x1a7c5c7e, 0x0c1f146d, 0x02c68d64, 0x0152f865, 0x584e0e}}} - }, - { - /* 1*16^62*G: */ - {{{0x085fbd44, 0x03a3894c, 0x09899eb0, 0x0595473e, 0x1222c89c, 0x18e70b6a, 0x04178151, 0x1b5b356b, 0x9bbf06}}, - {{0x1a1d3e88, 0x1a23efd3, 0x00dbb4a8, 0x097bb82a, 0x147e8c0d, 0x0d798537, 0x028d9d57, 0x1509bc24, 0x1bcc7f}}}, - /* 3*16^62*G: */ - {{{0x14a7b506, 0x1bd73c95, 0x0182f822, 0x0b1775cf, 0x00ee9227, 0x1d9573a8, 0x0f4d0a73, 0x1ecb676b, 0x646ff7}}, - {{0x0b36f946, 0x0bce0929, 0x039a6572, 0x1bf89f81, 0x08fe8492, 0x1198c025, 0x02956987, 0x13a0c943, 0xbc112a}}}, - /* 5*16^62*G: */ - {{{0x0103b553, 0x0a5b8e4e, 0x0e16a005, 0x01d44f06, 0x0507ebe6, 0x0800593f, 0x0e6a7430, 0x0f54c2fa, 0x7cc054}}, - {{0x0d45a526, 0x1cce5d1e, 0x08a2df55, 0x10b41558, 0x094c4001, 0x14659cfe, 0x116af75c, 0x17a46500, 0x7b329e}}}, - /* 7*16^62*G: */ - {{{0x19e717a4, 0x0177a2a0, 0x1c06e06b, 0x1457b559, 0x0e15468d, 0x16a9b6d7, 0x0d38158f, 0x1321783b, 0x946851}}, - {{0x1526dea3, 0x1af87e8d, 0x02b36729, 0x132b21ee, 0x07dc0579, 0x08735933, 0x06a2cee3, 0x0841b8b2, 0xca5c78}}}, - /* 9*16^62*G: */ - {{{0x08362735, 0x1c161b78, 0x0acc9ef7, 0x1166fde2, 0x0d1f5dff, 0x07c75229, 0x03eac496, 0x037cfc1d, 0xe1b98f}}, - {{0x0ecb11b7, 0x0b09c64e, 0x1d0242be, 0x13dc67be, 0x12ba27e1, 0x1a4d41dd, 0x082df816, 0x15b352ed, 0xca83e9}}}, - /* 11*16^62*G: */ - {{{0x19046346, 0x1a05dec1, 0x0510020e, 0x0b1bce60, 0x1962d56a, 0x1035ecb3, 0x1fbdb422, 0x0b91fac0, 0x3536f2}}, - {{0x0c14fea6, 0x0008315e, 0x166cf8a5, 0x187a3d45, 0x1e1c39e6, 0x0b50d294, 0x0279cee8, 0x1c2fbc6a, 0x1e271f}}}, - /* 13*16^62*G: */ - {{{0x0847f6f8, 0x1ffc1f6b, 0x103bd4c3, 0x136a3f7a, 0x18c3c102, 0x08f9a5bf, 0x1379a405, 0x08d7c47a, 0xdf502}}, - {{0x1b1633a6, 0x06654535, 0x17cd126d, 0x1c2ccd13, 0x174ab4c6, 0x1627de10, 0x0728ac8c, 0x0fe53b5e, 0x210704}}}, - /* 15*16^62*G: */ - {{{0x1d4f1b81, 0x02e7576f, 0x1d2ec546, 0x134f4919, 0x09330ad6, 0x11d99b29, 0x0e63da77, 0x1a899ea6, 0xca9dd5}}, - {{0x02b7c8bd, 0x06316572, 0x0b6843e3, 0x12ec17f5, 0x07b3e66c, 0x18fda223, 0x11590091, 0x10ca37c5, 0x6e4b98}}} - }, - { - /* 1*16^63*G: */ - {{{0x044c0448, 0x1569919a, 0x00c678c7, 0x0aed0d82, 0x093e7963, 0x1e659dbc, 0x1e95250f, 0x1ea5287b, 0xb12fad}}, - {{0x1369de57, 0x12baf3b5, 0x1f137ca5, 0x0bce0665, 0x0a6a71d0, 0x07b33bd9, 0x12b3a5be, 0x12b3361e, 0x2e245}}}, - /* 3*16^63*G: */ - {{{0x04e9151f, 0x09b622a3, 0x0361063d, 0x14673390, 0x0fb3e67e, 0x0eed48ec, 0x15a10c95, 0x069e38a0, 0x48388e}}, - {{0x0d7a9434, 0x1f724f6e, 0x17a0c406, 0x028222a7, 0x02bcc99b, 0x19c98aa4, 0x1a79a894, 0x157e9c89, 0xb0ec63}}}, - /* 5*16^63*G: */ - {{{0x0e5b833d, 0x1efda52c, 0x02c90675, 0x1400e794, 0x109c9593, 0x0fc08280, 0x0f697997, 0x08f5c28a, 0xde8e64}}, - {{0x014c5cc7, 0x07e60cd5, 0x0071b27c, 0x1c062652, 0x1e265987, 0x14465eb6, 0x0f638cc8, 0x0782a122, 0x5e146}}}, - /* 7*16^63*G: */ - {{{0x01f7df48, 0x112c9f56, 0x1c4c5ecf, 0x0e8e7d70, 0x0f385b26, 0x0da7272c, 0x1a7a3955, 0x1a149d8f, 0xb18b12}}, - {{0x0846b546, 0x0d9eac87, 0x14795664, 0x07efc907, 0x0c33b3c8, 0x0135e2d6, 0x0d1173b1, 0x04546b3f, 0xff5319}}}, - /* 9*16^63*G: */ - {{{0x19c9fd24, 0x129d6e34, 0x0847837c, 0x1bb1308a, 0x0694402c, 0x05526394, 0x10cf1f07, 0x0e391b92, 0x5f51ad}}, - {{0x0d2ffd3a, 0x01dd7443, 0x092a65a7, 0x03ec488c, 0x04d03872, 0x02fde617, 0x0528334c, 0x023befce, 0x2bbbef}}}, - /* 11*16^63*G: */ - {{{0x17cec42e, 0x1e2031fe, 0x18ef6df8, 0x1b9267dd, 0x0d8556f3, 0x117721a3, 0x1aefe960, 0x1b26e603, 0xfdf340}}, - {{0x0db6908b, 0x07b1a5be, 0x178c320e, 0x1cd57ea1, 0x0d2c4456, 0x0ff39c65, 0x1b875c30, 0x0c72d198, 0xa3e59a}}}, - /* 13*16^63*G: */ - {{{0x01dc4fe8, 0x00c4a2a7, 0x01c9057f, 0x1142752c, 0x1c983f9e, 0x0948bdbe, 0x15e7b191, 0x19173d4b, 0xe3caeb}}, - {{0x1307b403, 0x15b05b90, 0x13e1a845, 0x0006cb42, 0x1f976513, 0x1ae5c580, 0x04a34880, 0x1a4f7e0b, 0x97f093}}}, - /* 15*16^63*G: */ - {{{0x1c119eff, 0x00a6b2e9, 0x07e6e119, 0x005c1815, 0x1a003399, 0x0d2e72c7, 0x16e26bd0, 0x1728550c, 0x3312d}}, - {{0x18b1b950, 0x14ad1d4f, 0x1455617f, 0x18b3b6be, 0x1e86f0ae, 0x1518bc04, 0x137c413b, 0x0c8d66e4, 0x6e3eda}}} - }, diff --git a/trezor-crypto/crypto/pbkdf2.c b/trezor-crypto/crypto/pbkdf2.c deleted file mode 100644 index 117d14e618b..00000000000 --- a/trezor-crypto/crypto/pbkdf2.c +++ /dev/null @@ -1,179 +0,0 @@ -/** - * Copyright (c) 2013-2014 Tomas Dzetkulic - * Copyright (c) 2013-2014 Pavol Rusnak - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include - -void pbkdf2_hmac_sha256_Init(PBKDF2_HMAC_SHA256_CTX *pctx, const uint8_t *pass, - int passlen, const uint8_t *salt, int saltlen, - uint32_t blocknr) { - SHA256_CTX ctx = {0}; -#if BYTE_ORDER == LITTLE_ENDIAN - REVERSE32(blocknr, blocknr); -#endif - - hmac_sha256_prepare(pass, passlen, pctx->odig, pctx->idig); - memzero(pctx->g, sizeof(pctx->g)); - pctx->g[8] = 0x80000000; - pctx->g[15] = (SHA256_BLOCK_LENGTH + SHA256_DIGEST_LENGTH) * 8; - - memcpy(ctx.state, pctx->idig, sizeof(pctx->idig)); - ctx.bitcount = SHA256_BLOCK_LENGTH * 8; - sha256_Update(&ctx, salt, saltlen); - sha256_Update(&ctx, (uint8_t *)&blocknr, sizeof(blocknr)); - sha256_Final(&ctx, (uint8_t *)pctx->g); -#if BYTE_ORDER == LITTLE_ENDIAN - for (uint32_t k = 0; k < SHA256_DIGEST_LENGTH / sizeof(uint32_t); k++) { - REVERSE32(pctx->g[k], pctx->g[k]); - } -#endif - sha256_Transform(pctx->odig, pctx->g, pctx->g); - memcpy(pctx->f, pctx->g, SHA256_DIGEST_LENGTH); - pctx->first = 1; -} - -void pbkdf2_hmac_sha256_Update(PBKDF2_HMAC_SHA256_CTX *pctx, - uint32_t iterations) { - for (uint32_t i = pctx->first; i < iterations; i++) { - sha256_Transform(pctx->idig, pctx->g, pctx->g); - sha256_Transform(pctx->odig, pctx->g, pctx->g); - for (uint32_t j = 0; j < SHA256_DIGEST_LENGTH / sizeof(uint32_t); j++) { - pctx->f[j] ^= pctx->g[j]; - } - } - pctx->first = 0; -} - -void pbkdf2_hmac_sha256_Final(PBKDF2_HMAC_SHA256_CTX *pctx, uint8_t *key) { -#if BYTE_ORDER == LITTLE_ENDIAN - for (uint32_t k = 0; k < SHA256_DIGEST_LENGTH / sizeof(uint32_t); k++) { - REVERSE32(pctx->f[k], pctx->f[k]); - } -#endif - memcpy(key, pctx->f, SHA256_DIGEST_LENGTH); - memzero(pctx, sizeof(PBKDF2_HMAC_SHA256_CTX)); -} - -void pbkdf2_hmac_sha256(const uint8_t *pass, int passlen, const uint8_t *salt, - int saltlen, uint32_t iterations, uint8_t *key, - int keylen) { - uint32_t last_block_size = keylen % SHA256_DIGEST_LENGTH; - uint32_t blocks_count = keylen / SHA256_DIGEST_LENGTH; - if (last_block_size) { - blocks_count++; - } else { - last_block_size = SHA256_DIGEST_LENGTH; - } - for (uint32_t blocknr = 1; blocknr <= blocks_count; blocknr++) { - PBKDF2_HMAC_SHA256_CTX pctx = {0}; - pbkdf2_hmac_sha256_Init(&pctx, pass, passlen, salt, saltlen, blocknr); - pbkdf2_hmac_sha256_Update(&pctx, iterations); - uint8_t digest[SHA256_DIGEST_LENGTH] = {0}; - pbkdf2_hmac_sha256_Final(&pctx, digest); - uint32_t key_offset = (blocknr - 1) * SHA256_DIGEST_LENGTH; - if (blocknr < blocks_count) { - memcpy(key + key_offset, digest, SHA256_DIGEST_LENGTH); - } else { - memcpy(key + key_offset, digest, last_block_size); - } - } -} - -void pbkdf2_hmac_sha512_Init(PBKDF2_HMAC_SHA512_CTX *pctx, const uint8_t *pass, - int passlen, const uint8_t *salt, int saltlen, - uint32_t blocknr) { - SHA512_CTX ctx = {0}; -#if BYTE_ORDER == LITTLE_ENDIAN - REVERSE32(blocknr, blocknr); -#endif - - hmac_sha512_prepare(pass, passlen, pctx->odig, pctx->idig); - memzero(pctx->g, sizeof(pctx->g)); - pctx->g[8] = 0x8000000000000000; - pctx->g[15] = (SHA512_BLOCK_LENGTH + SHA512_DIGEST_LENGTH) * 8; - - memcpy(ctx.state, pctx->idig, sizeof(pctx->idig)); - ctx.bitcount[0] = SHA512_BLOCK_LENGTH * 8; - ctx.bitcount[1] = 0; - sha512_Update(&ctx, salt, saltlen); - sha512_Update(&ctx, (uint8_t *)&blocknr, sizeof(blocknr)); - sha512_Final(&ctx, (uint8_t *)pctx->g); -#if BYTE_ORDER == LITTLE_ENDIAN - for (uint32_t k = 0; k < SHA512_DIGEST_LENGTH / sizeof(uint64_t); k++) { - REVERSE64(pctx->g[k], pctx->g[k]); - } -#endif - sha512_Transform(pctx->odig, pctx->g, pctx->g); - memcpy(pctx->f, pctx->g, SHA512_DIGEST_LENGTH); - pctx->first = 1; -} - -void pbkdf2_hmac_sha512_Update(PBKDF2_HMAC_SHA512_CTX *pctx, - uint32_t iterations) { - for (uint32_t i = pctx->first; i < iterations; i++) { - sha512_Transform(pctx->idig, pctx->g, pctx->g); - sha512_Transform(pctx->odig, pctx->g, pctx->g); - for (uint32_t j = 0; j < SHA512_DIGEST_LENGTH / sizeof(uint64_t); j++) { - pctx->f[j] ^= pctx->g[j]; - } - } - pctx->first = 0; -} - -void pbkdf2_hmac_sha512_Final(PBKDF2_HMAC_SHA512_CTX *pctx, uint8_t *key) { -#if BYTE_ORDER == LITTLE_ENDIAN - for (uint32_t k = 0; k < SHA512_DIGEST_LENGTH / sizeof(uint64_t); k++) { - REVERSE64(pctx->f[k], pctx->f[k]); - } -#endif - memcpy(key, pctx->f, SHA512_DIGEST_LENGTH); - memzero(pctx, sizeof(PBKDF2_HMAC_SHA512_CTX)); -} - -void pbkdf2_hmac_sha512(const uint8_t *pass, int passlen, const uint8_t *salt, - int saltlen, uint32_t iterations, uint8_t *key, - int keylen) { - uint32_t last_block_size = keylen % SHA512_DIGEST_LENGTH; - uint32_t blocks_count = keylen / SHA512_DIGEST_LENGTH; - if (last_block_size) { - blocks_count++; - } else { - last_block_size = SHA512_DIGEST_LENGTH; - } - for (uint32_t blocknr = 1; blocknr <= blocks_count; blocknr++) { - PBKDF2_HMAC_SHA512_CTX pctx = {0}; - pbkdf2_hmac_sha512_Init(&pctx, pass, passlen, salt, saltlen, blocknr); - pbkdf2_hmac_sha512_Update(&pctx, iterations); - uint8_t digest[SHA512_DIGEST_LENGTH] = {0}; - pbkdf2_hmac_sha512_Final(&pctx, digest); - uint32_t key_offset = (blocknr - 1) * SHA512_DIGEST_LENGTH; - if (blocknr < blocks_count) { - memcpy(key + key_offset, digest, SHA512_DIGEST_LENGTH); - } else { - memcpy(key + key_offset, digest, last_block_size); - } - } -} diff --git a/trezor-crypto/crypto/rc4.c b/trezor-crypto/crypto/rc4.c deleted file mode 100644 index e940d277061..00000000000 --- a/trezor-crypto/crypto/rc4.c +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2017 Saleem Rashid - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, E1PRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include - -static inline void rc4_swap(RC4_CTX *ctx, uint8_t i, uint8_t j) { - uint8_t temp = ctx->S[i]; - ctx->S[i] = ctx->S[j]; - ctx->S[j] = temp; -} - -void rc4_init(RC4_CTX *ctx, const uint8_t *key, size_t length) { - ctx->i = 0; - ctx->j = 0; - - for (size_t i = 0; i < 256; i++) { - ctx->S[i] = i; - } - - uint8_t j = 0; - for (size_t i = 0; i < 256; i++) { - j += ctx->S[i] + key[i % length]; - rc4_swap(ctx, i, j); - } -} - -void rc4_encrypt(RC4_CTX *ctx, uint8_t *buffer, size_t length) { - for (size_t idx = 0; idx < length; idx++) { - ctx->i++; - ctx->j += ctx->S[ctx->i]; - - rc4_swap(ctx, ctx->i, ctx->j); - - uint8_t K = ctx->S[(ctx->S[ctx->i] + ctx->S[ctx->j]) % 256]; - buffer[idx] ^= K; - } -} diff --git a/trezor-crypto/crypto/rfc6979.c b/trezor-crypto/crypto/rfc6979.c deleted file mode 100644 index c781e47b926..00000000000 --- a/trezor-crypto/crypto/rfc6979.c +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) 2013-2014 Tomas Dzetkulic - * Copyright (c) 2013-2014 Pavol Rusnak - * Copyright (c) 2015 Jochen Hoenicke - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ -#include - -#include -#include -#include - -void init_rfc6979(const uint8_t *priv_key, const uint8_t *hash, - const ecdsa_curve *curve, rfc6979_state *state) { - if (curve) { - bignum256 hash_bn = {0}; - bn_read_be(hash, &hash_bn); - - // Make sure hash is partly reduced modulo order - assert(bn_bitcount(&curve->order) >= 256); - bn_mod(&hash_bn, &curve->order); - - uint8_t hash_reduced[32] = {0}; - bn_write_be(&hash_bn, hash_reduced); - memzero(&hash_bn, sizeof(hash_bn)); - hmac_drbg_init(state, priv_key, 32, hash_reduced, 32); - memzero(hash_reduced, sizeof(hash_reduced)); - } else { - hmac_drbg_init(state, priv_key, 32, hash, 32); - } -} - -// generate next number from deterministic random number generator -void generate_rfc6979(uint8_t rnd[32], rfc6979_state *state) { - hmac_drbg_generate(state, rnd, 32); -} - -// generate K in a deterministic way, according to RFC6979 -// http://tools.ietf.org/html/rfc6979 -void generate_k_rfc6979(bignum256 *k, rfc6979_state *state) { - uint8_t buf[32] = {0}; - generate_rfc6979(buf, state); - bn_read_be(buf, k); - memzero(buf, sizeof(buf)); -} diff --git a/trezor-crypto/crypto/ripemd160.c b/trezor-crypto/crypto/ripemd160.c deleted file mode 100644 index f90e4340b9d..00000000000 --- a/trezor-crypto/crypto/ripemd160.c +++ /dev/null @@ -1,343 +0,0 @@ -/* - * RIPE MD-160 implementation - * - * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved - * SPDX-License-Identifier: Apache-2.0 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * This file is part of mbed TLS (https://tls.mbed.org) - */ - -/* - * The RIPEMD-160 algorithm was designed by RIPE in 1996 - * http://homes.esat.kuleuven.be/~bosselae/ripemd160.html - * http://ehash.iaik.tugraz.at/wiki/RIPEMD-160 - */ - -#include - -#include -#include - -/* - * 32-bit integer manipulation macros (little endian) - */ -#ifndef GET_UINT32_LE -#define GET_UINT32_LE(n,b,i) \ -{ \ - (n) = ( (uint32_t) (b)[(i) ] ) \ - | ( (uint32_t) (b)[(i) + 1] << 8 ) \ - | ( (uint32_t) (b)[(i) + 2] << 16 ) \ - | ( (uint32_t) (b)[(i) + 3] << 24 ); \ -} -#endif - -#ifndef PUT_UINT32_LE -#define PUT_UINT32_LE(n,b,i) \ -{ \ - (b)[(i) ] = (uint8_t) ( ( (n) ) & 0xFF ); \ - (b)[(i) + 1] = (uint8_t) ( ( (n) >> 8 ) & 0xFF ); \ - (b)[(i) + 2] = (uint8_t) ( ( (n) >> 16 ) & 0xFF ); \ - (b)[(i) + 3] = (uint8_t) ( ( (n) >> 24 ) & 0xFF ); \ -} -#endif - -/* - * RIPEMD-160 context setup - */ -void ripemd160_Init(RIPEMD160_CTX *ctx) -{ - memzero(ctx, sizeof(RIPEMD160_CTX)); - ctx->total[0] = 0; - ctx->total[1] = 0; - ctx->state[0] = 0x67452301; - ctx->state[1] = 0xEFCDAB89; - ctx->state[2] = 0x98BADCFE; - ctx->state[3] = 0x10325476; - ctx->state[4] = 0xC3D2E1F0; -} - -#if !defined(MBEDTLS_RIPEMD160_PROCESS_ALT) -/* - * Process one block - */ -void ripemd160_process( RIPEMD160_CTX *ctx, const uint8_t data[RIPEMD160_BLOCK_LENGTH] ) -{ - uint32_t A = 0, B = 0, C = 0, D = 0, E = 0, Ap = 0, Bp = 0, Cp = 0, Dp = 0, Ep = 0, X[16] = {0}; - - GET_UINT32_LE( X[ 0], data, 0 ); - GET_UINT32_LE( X[ 1], data, 4 ); - GET_UINT32_LE( X[ 2], data, 8 ); - GET_UINT32_LE( X[ 3], data, 12 ); - GET_UINT32_LE( X[ 4], data, 16 ); - GET_UINT32_LE( X[ 5], data, 20 ); - GET_UINT32_LE( X[ 6], data, 24 ); - GET_UINT32_LE( X[ 7], data, 28 ); - GET_UINT32_LE( X[ 8], data, 32 ); - GET_UINT32_LE( X[ 9], data, 36 ); - GET_UINT32_LE( X[10], data, 40 ); - GET_UINT32_LE( X[11], data, 44 ); - GET_UINT32_LE( X[12], data, 48 ); - GET_UINT32_LE( X[13], data, 52 ); - GET_UINT32_LE( X[14], data, 56 ); - GET_UINT32_LE( X[15], data, 60 ); - - A = Ap = ctx->state[0]; - B = Bp = ctx->state[1]; - C = Cp = ctx->state[2]; - D = Dp = ctx->state[3]; - E = Ep = ctx->state[4]; - -#define F1( x, y, z ) ( x ^ y ^ z ) -#define F2( x, y, z ) ( ( x & y ) | ( ~x & z ) ) -#define F3( x, y, z ) ( ( x | ~y ) ^ z ) -#define F4( x, y, z ) ( ( x & z ) | ( y & ~z ) ) -#define F5( x, y, z ) ( x ^ ( y | ~z ) ) - -#define S( x, n ) ( ( x << n ) | ( x >> (32 - n) ) ) - -#define P( a, b, c, d, e, r, s, f, k ) \ - a += f( b, c, d ) + X[r] + k; \ - a = S( a, s ) + e; \ - c = S( c, 10 ); - -#define P2( a, b, c, d, e, r, s, rp, sp ) \ - P( a, b, c, d, e, r, s, F, K ); \ - P( a ## p, b ## p, c ## p, d ## p, e ## p, rp, sp, Fp, Kp ); - -#define F F1 -#define K 0x00000000 -#define Fp F5 -#define Kp 0x50A28BE6 - P2( A, B, C, D, E, 0, 11, 5, 8 ); - P2( E, A, B, C, D, 1, 14, 14, 9 ); - P2( D, E, A, B, C, 2, 15, 7, 9 ); - P2( C, D, E, A, B, 3, 12, 0, 11 ); - P2( B, C, D, E, A, 4, 5, 9, 13 ); - P2( A, B, C, D, E, 5, 8, 2, 15 ); - P2( E, A, B, C, D, 6, 7, 11, 15 ); - P2( D, E, A, B, C, 7, 9, 4, 5 ); - P2( C, D, E, A, B, 8, 11, 13, 7 ); - P2( B, C, D, E, A, 9, 13, 6, 7 ); - P2( A, B, C, D, E, 10, 14, 15, 8 ); - P2( E, A, B, C, D, 11, 15, 8, 11 ); - P2( D, E, A, B, C, 12, 6, 1, 14 ); - P2( C, D, E, A, B, 13, 7, 10, 14 ); - P2( B, C, D, E, A, 14, 9, 3, 12 ); - P2( A, B, C, D, E, 15, 8, 12, 6 ); -#undef F -#undef K -#undef Fp -#undef Kp - -#define F F2 -#define K 0x5A827999 -#define Fp F4 -#define Kp 0x5C4DD124 - P2( E, A, B, C, D, 7, 7, 6, 9 ); - P2( D, E, A, B, C, 4, 6, 11, 13 ); - P2( C, D, E, A, B, 13, 8, 3, 15 ); - P2( B, C, D, E, A, 1, 13, 7, 7 ); - P2( A, B, C, D, E, 10, 11, 0, 12 ); - P2( E, A, B, C, D, 6, 9, 13, 8 ); - P2( D, E, A, B, C, 15, 7, 5, 9 ); - P2( C, D, E, A, B, 3, 15, 10, 11 ); - P2( B, C, D, E, A, 12, 7, 14, 7 ); - P2( A, B, C, D, E, 0, 12, 15, 7 ); - P2( E, A, B, C, D, 9, 15, 8, 12 ); - P2( D, E, A, B, C, 5, 9, 12, 7 ); - P2( C, D, E, A, B, 2, 11, 4, 6 ); - P2( B, C, D, E, A, 14, 7, 9, 15 ); - P2( A, B, C, D, E, 11, 13, 1, 13 ); - P2( E, A, B, C, D, 8, 12, 2, 11 ); -#undef F -#undef K -#undef Fp -#undef Kp - -#define F F3 -#define K 0x6ED9EBA1 -#define Fp F3 -#define Kp 0x6D703EF3 - P2( D, E, A, B, C, 3, 11, 15, 9 ); - P2( C, D, E, A, B, 10, 13, 5, 7 ); - P2( B, C, D, E, A, 14, 6, 1, 15 ); - P2( A, B, C, D, E, 4, 7, 3, 11 ); - P2( E, A, B, C, D, 9, 14, 7, 8 ); - P2( D, E, A, B, C, 15, 9, 14, 6 ); - P2( C, D, E, A, B, 8, 13, 6, 6 ); - P2( B, C, D, E, A, 1, 15, 9, 14 ); - P2( A, B, C, D, E, 2, 14, 11, 12 ); - P2( E, A, B, C, D, 7, 8, 8, 13 ); - P2( D, E, A, B, C, 0, 13, 12, 5 ); - P2( C, D, E, A, B, 6, 6, 2, 14 ); - P2( B, C, D, E, A, 13, 5, 10, 13 ); - P2( A, B, C, D, E, 11, 12, 0, 13 ); - P2( E, A, B, C, D, 5, 7, 4, 7 ); - P2( D, E, A, B, C, 12, 5, 13, 5 ); -#undef F -#undef K -#undef Fp -#undef Kp - -#define F F4 -#define K 0x8F1BBCDC -#define Fp F2 -#define Kp 0x7A6D76E9 - P2( C, D, E, A, B, 1, 11, 8, 15 ); - P2( B, C, D, E, A, 9, 12, 6, 5 ); - P2( A, B, C, D, E, 11, 14, 4, 8 ); - P2( E, A, B, C, D, 10, 15, 1, 11 ); - P2( D, E, A, B, C, 0, 14, 3, 14 ); - P2( C, D, E, A, B, 8, 15, 11, 14 ); - P2( B, C, D, E, A, 12, 9, 15, 6 ); - P2( A, B, C, D, E, 4, 8, 0, 14 ); - P2( E, A, B, C, D, 13, 9, 5, 6 ); - P2( D, E, A, B, C, 3, 14, 12, 9 ); - P2( C, D, E, A, B, 7, 5, 2, 12 ); - P2( B, C, D, E, A, 15, 6, 13, 9 ); - P2( A, B, C, D, E, 14, 8, 9, 12 ); - P2( E, A, B, C, D, 5, 6, 7, 5 ); - P2( D, E, A, B, C, 6, 5, 10, 15 ); - P2( C, D, E, A, B, 2, 12, 14, 8 ); -#undef F -#undef K -#undef Fp -#undef Kp - -#define F F5 -#define K 0xA953FD4E -#define Fp F1 -#define Kp 0x00000000 - P2( B, C, D, E, A, 4, 9, 12, 8 ); - P2( A, B, C, D, E, 0, 15, 15, 5 ); - P2( E, A, B, C, D, 5, 5, 10, 12 ); - P2( D, E, A, B, C, 9, 11, 4, 9 ); - P2( C, D, E, A, B, 7, 6, 1, 12 ); - P2( B, C, D, E, A, 12, 8, 5, 5 ); - P2( A, B, C, D, E, 2, 13, 8, 14 ); - P2( E, A, B, C, D, 10, 12, 7, 6 ); - P2( D, E, A, B, C, 14, 5, 6, 8 ); - P2( C, D, E, A, B, 1, 12, 2, 13 ); - P2( B, C, D, E, A, 3, 13, 13, 6 ); - P2( A, B, C, D, E, 8, 14, 14, 5 ); - P2( E, A, B, C, D, 11, 11, 0, 15 ); - P2( D, E, A, B, C, 6, 8, 3, 13 ); - P2( C, D, E, A, B, 15, 5, 9, 11 ); - P2( B, C, D, E, A, 13, 6, 11, 11 ); -#undef F -#undef K -#undef Fp -#undef Kp - - C = ctx->state[1] + C + Dp; - ctx->state[1] = ctx->state[2] + D + Ep; - ctx->state[2] = ctx->state[3] + E + Ap; - ctx->state[3] = ctx->state[4] + A + Bp; - ctx->state[4] = ctx->state[0] + B + Cp; - ctx->state[0] = C; -} -#endif /* !MBEDTLS_RIPEMD160_PROCESS_ALT */ - -/* - * RIPEMD-160 process buffer - */ -void ripemd160_Update( RIPEMD160_CTX *ctx, const uint8_t *input, uint32_t ilen ) -{ - uint32_t fill = 0; - uint32_t left = 0; - - if( ilen == 0 ) - return; - - left = ctx->total[0] & 0x3F; - fill = RIPEMD160_BLOCK_LENGTH - left; - - ctx->total[0] += (uint32_t) ilen; - ctx->total[0] &= 0xFFFFFFFF; - - if( ctx->total[0] < (uint32_t) ilen ) - ctx->total[1]++; - - if( left && ilen >= fill ) - { - memcpy( (void *) (ctx->buffer + left), input, fill ); - ripemd160_process( ctx, ctx->buffer ); - input += fill; - ilen -= fill; - left = 0; - } - - while( ilen >= RIPEMD160_BLOCK_LENGTH ) - { - ripemd160_process( ctx, input ); - input += RIPEMD160_BLOCK_LENGTH; - ilen -= RIPEMD160_BLOCK_LENGTH; - } - - if( ilen > 0 ) - { - memcpy( (void *) (ctx->buffer + left), input, ilen ); - } -} - -const uint8_t ripemd160_padding[RIPEMD160_BLOCK_LENGTH] = -{ - 0x80, 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, - 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, 0 -}; - -/* - * RIPEMD-160 final digest - */ -void ripemd160_Final( RIPEMD160_CTX *ctx, uint8_t output[RIPEMD160_DIGEST_LENGTH] ) -{ - uint32_t last = 0; uint32_t padn = 0; - uint32_t high = 0; uint32_t low = 0; - uint8_t msglen[8] = {0}; - - high = ( ctx->total[0] >> 29 ) - | ( ctx->total[1] << 3 ); - low = ( ctx->total[0] << 3 ); - - PUT_UINT32_LE( low, msglen, 0 ); - PUT_UINT32_LE( high, msglen, 4 ); - - last = ctx->total[0] & 0x3F; - padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last ); - - ripemd160_Update( ctx, ripemd160_padding, padn ); - ripemd160_Update( ctx, msglen, 8 ); - - PUT_UINT32_LE( ctx->state[0], output, 0 ); - PUT_UINT32_LE( ctx->state[1], output, 4 ); - PUT_UINT32_LE( ctx->state[2], output, 8 ); - PUT_UINT32_LE( ctx->state[3], output, 12 ); - PUT_UINT32_LE( ctx->state[4], output, 16 ); - - memzero(ctx, sizeof(RIPEMD160_CTX)); -} - -/* - * output = RIPEMD-160( input buffer ) - */ -void ripemd160(const uint8_t *msg, uint32_t msg_len, uint8_t hash[RIPEMD160_DIGEST_LENGTH]) -{ - RIPEMD160_CTX ctx = {0}; - ripemd160_Init( &ctx ); - ripemd160_Update( &ctx, msg, msg_len ); - ripemd160_Final( &ctx, hash ); -} diff --git a/trezor-crypto/crypto/script.c b/trezor-crypto/crypto/script.c deleted file mode 100644 index 4d181cea6b3..00000000000 --- a/trezor-crypto/crypto/script.c +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (c) 2016 Pavol Rusnak - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include - -int script_output_to_address(const uint8_t *script, int scriptlen, char *addr, - int addrsize) { - uint8_t raw[35] = {0}; - - // P2PKH - if (scriptlen == 25 && script[0] == 0x76 && script[1] == 0xA9 && - script[2] == 0x14 && script[23] == 0x88 && script[24] == 0xAC) { - raw[0] = 0x00; - memcpy(raw + 1, script + 3, 20); - return base58_encode_check(raw, 1 + 20, HASHER_SHA2D, addr, addrsize); - } - - // P2SH - if (scriptlen == 23 && script[0] == 0xA9 && script[1] == 0x14 && - script[22] == 0x87) { - raw[0] = 0x05; - memcpy(raw + 1, script + 2, 20); - return base58_encode_check(raw, 1 + 20, HASHER_SHA2D, addr, addrsize); - } - - // P2WPKH - if (scriptlen == 22 && script[0] == 0x00 && script[1] == 0x14) { - raw[0] = 0x06; - raw[1] = 0x00; - raw[2] = 0x00; - memcpy(raw + 3, script + 2, 20); - return base58_encode_check(raw, 3 + 20, HASHER_SHA2D, addr, addrsize); - } - - // P2WSH - if (scriptlen == 34 && script[0] == 0x00 && script[1] == 0x20) { - raw[0] = 0x0A; - raw[1] = 0x00; - raw[2] = 0x00; - memcpy(raw + 3, script + 2, 32); - return base58_encode_check(raw, 3 + 32, HASHER_SHA2D, addr, addrsize); - } - - return 0; -} diff --git a/trezor-crypto/crypto/scrypt.c b/trezor-crypto/crypto/scrypt.c deleted file mode 100644 index 4e94beab0e8..00000000000 --- a/trezor-crypto/crypto/scrypt.c +++ /dev/null @@ -1,340 +0,0 @@ -/*- - * Copyright 2009 Colin Percival - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * This file was originally written by Colin Percival as part of the Tarsnap - * online backup system. - */ - -#include - -#include -#include -#include - -#include -#ifndef _WIN32 -#include -#endif -#include -#include -#include -#include - -static void blkcpy(uint32_t *, const uint32_t *, size_t); -static void blkxor(void *, void *, size_t); -static void salsa20_8(uint32_t[16]); -static void blockmix_salsa8(uint32_t *, uint32_t *, uint32_t *, size_t); -static uint64_t integerify(void *, size_t); -static void smix(uint8_t *, size_t, uint64_t, uint32_t *, uint32_t *); - -static void -blkcpy(uint32_t * dest, const uint32_t * src, size_t len) -{ - size_t L = len / sizeof(uint32_t); - - for (size_t i = 0; i < L; i++) - dest[i] = src[i]; -} - -static void -blkxor(void * dest, void * src, size_t len) -{ - size_t * D = dest; - size_t * S = src; - size_t L = len / sizeof(size_t); - size_t i; - - for (i = 0; i < L; i++) - D[i] ^= S[i]; -} - -/** - * salsa20_8(B): - * Apply the salsa20/8 core to the provided block. - */ -static void -salsa20_8(uint32_t B[16]) -{ - uint32_t x[16]; - size_t i; - - blkcpy(x, B, 64); - for (i = 0; i < 8; i += 2) { -#define R(a,b) (((a) << (b)) | ((a) >> (32 - (b)))) - /* Operate on columns. */ - x[ 4] ^= R(x[ 0]+x[12], 7); x[ 8] ^= R(x[ 4]+x[ 0], 9); - x[12] ^= R(x[ 8]+x[ 4],13); x[ 0] ^= R(x[12]+x[ 8],18); - - x[ 9] ^= R(x[ 5]+x[ 1], 7); x[13] ^= R(x[ 9]+x[ 5], 9); - x[ 1] ^= R(x[13]+x[ 9],13); x[ 5] ^= R(x[ 1]+x[13],18); - - x[14] ^= R(x[10]+x[ 6], 7); x[ 2] ^= R(x[14]+x[10], 9); - x[ 6] ^= R(x[ 2]+x[14],13); x[10] ^= R(x[ 6]+x[ 2],18); - - x[ 3] ^= R(x[15]+x[11], 7); x[ 7] ^= R(x[ 3]+x[15], 9); - x[11] ^= R(x[ 7]+x[ 3],13); x[15] ^= R(x[11]+x[ 7],18); - - /* Operate on rows. */ - x[ 1] ^= R(x[ 0]+x[ 3], 7); x[ 2] ^= R(x[ 1]+x[ 0], 9); - x[ 3] ^= R(x[ 2]+x[ 1],13); x[ 0] ^= R(x[ 3]+x[ 2],18); - - x[ 6] ^= R(x[ 5]+x[ 4], 7); x[ 7] ^= R(x[ 6]+x[ 5], 9); - x[ 4] ^= R(x[ 7]+x[ 6],13); x[ 5] ^= R(x[ 4]+x[ 7],18); - - x[11] ^= R(x[10]+x[ 9], 7); x[ 8] ^= R(x[11]+x[10], 9); - x[ 9] ^= R(x[ 8]+x[11],13); x[10] ^= R(x[ 9]+x[ 8],18); - - x[12] ^= R(x[15]+x[14], 7); x[13] ^= R(x[12]+x[15], 9); - x[14] ^= R(x[13]+x[12],13); x[15] ^= R(x[14]+x[13],18); -#undef R - } - for (i = 0; i < 16; i++) - B[i] += x[i]; -} - -/** - * blockmix_salsa8(Bin, Bout, X, r): - * Compute Bout = BlockMix_{salsa20/8, r}(Bin). The input Bin must be 128r - * bytes in length; the output Bout must also be the same size. The - * temporary space X must be 64 bytes. - */ -static void -blockmix_salsa8(uint32_t * Bin, uint32_t * Bout, uint32_t * X, size_t r) -{ - size_t i; - - /* 1: X <-- B_{2r - 1} */ - blkcpy(X, &Bin[(2 * r - 1) * 16], 64); - - /* 2: for i = 0 to 2r - 1 do */ - for (i = 0; i < 2 * r; i += 2) { - /* 3: X <-- H(X \xor B_i) */ - blkxor(X, &Bin[i * 16], 64); - salsa20_8(X); - - /* 4: Y_i <-- X */ - /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ - blkcpy(&Bout[i * 8], X, 64); - - /* 3: X <-- H(X \xor B_i) */ - blkxor(X, &Bin[i * 16 + 16], 64); - salsa20_8(X); - - /* 4: Y_i <-- X */ - /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ - blkcpy(&Bout[i * 8 + r * 16], X, 64); - } -} - -/** - * integerify(B, r): - * Return the result of parsing B_{2r-1} as a little-endian integer. - */ -static uint64_t -integerify(void * B, size_t r) -{ - uint32_t * X = (void *)((uintptr_t)(B) + (2 * r - 1) * 64); - - return (((uint64_t)(X[1]) << 32) + X[0]); -} - -/** - * smix(B, r, N, V, XY): - * Compute B = SMix_r(B, N). The input B must be 128r bytes in length; - * the temporary storage V must be 128rN bytes in length; the temporary - * storage XY must be 256r + 64 bytes in length. The value N must be a - * power of 2 greater than 1. The arrays B, V, and XY must be aligned to a - * multiple of 64 bytes. - */ -static void -smix(uint8_t * B, size_t r, uint64_t N, uint32_t * V, uint32_t * XY) -{ - uint32_t * X = XY; - uint32_t * Y = &XY[32 * r]; - uint32_t * Z = &XY[64 * r]; - uint64_t i; - uint64_t j; - size_t k; - - /* 1: X <-- B */ - for (k = 0; k < 32 * r; k++) - X[k] = le32dec(&B[4 * k]); - - /* 2: for i = 0 to N - 1 do */ - for (i = 0; i < N; i += 2) { - /* 3: V_i <-- X */ - blkcpy(&V[i * (32 * r)], X, 128 * r); - - /* 4: X <-- H(X) */ - blockmix_salsa8(X, Y, Z, r); - - /* 3: V_i <-- X */ - blkcpy(&V[(i + 1) * (32 * r)], Y, 128 * r); - - /* 4: X <-- H(X) */ - blockmix_salsa8(Y, X, Z, r); - } - - /* 6: for i = 0 to N - 1 do */ - for (i = 0; i < N; i += 2) { - /* 7: j <-- Integerify(X) mod N */ - j = integerify(X, r) & (N - 1); - - /* 8: X <-- H(X \xor V_j) */ - blkxor(X, &V[j * (32 * r)], 128 * r); - blockmix_salsa8(X, Y, Z, r); - - /* 7: j <-- Integerify(X) mod N */ - j = integerify(Y, r) & (N - 1); - - /* 8: X <-- H(X \xor V_j) */ - blkxor(Y, &V[j * (32 * r)], 128 * r); - blockmix_salsa8(Y, X, Z, r); - } - - /* 10: B' <-- X */ - for (k = 0; k < 32 * r; k++) - le32enc(&B[4 * k], X[k]); -} - -/** - * crypto_scrypt(passwd, passwdlen, salt, saltlen, N, r, p, buf, buflen): - * Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r, - * p, buflen) and write the result into buf. The parameters r, p, and buflen - * must satisfy r * p < 2^30 and buflen <= (2^32 - 1) * 32. The parameter N - * must be a power of 2 greater than 1. - * - * Return 0 on success; or -1 on error - */ -int -scrypt(const uint8_t * passwd, size_t passwdlen, - const uint8_t * salt, size_t saltlen, uint64_t N, uint32_t r, uint32_t p, - uint8_t * buf, size_t buflen) -{ - void * B0, * V0, * XY0; - uint8_t * B; - uint32_t * V; - uint32_t * XY; - uint32_t i; - - /* Sanity-check parameters. */ -#if SIZE_MAX > UINT32_MAX - if (buflen > (((uint64_t)(1) << 32) - 1) * 32) { - errno = EFBIG; - goto err0; - } -#endif - if ((uint64_t)(r) * (uint64_t)(p) >= (1 << 30)) { - errno = EFBIG; - goto err0; - } - if (r == 0 || p == 0) { - errno = EINVAL; - goto err0; - } - if (((N & (N - 1)) != 0) || (N < 2)) { - errno = EINVAL; - goto err0; - } - if ((r > SIZE_MAX / 128 / p) || -#if SIZE_MAX / 256 <= UINT32_MAX - (r > SIZE_MAX / 256) || -#endif - (N > SIZE_MAX / 128 / r)) { - errno = ENOMEM; - goto err0; - } - - /* Allocate memory. */ -#ifdef HAVE_POSIX_MEMALIGN - if ((errno = posix_memalign(&B0, 64, 128 * r * p)) != 0) - goto err0; - B = (uint8_t *)(B0); - if ((errno = posix_memalign(&XY0, 64, 256 * r + 64)) != 0) - goto err1; - XY = (uint32_t *)(XY0); -#ifndef MAP_ANON - if ((errno = posix_memalign(&V0, 64, 128 * r * N)) != 0) - goto err2; - V = (uint32_t *)(V0); -#endif -#else - if ((B0 = malloc(128 * r * p + 63)) == NULL) - goto err0; - B = (uint8_t *)(((uintptr_t)(B0) + 63) & ~ (uintptr_t)(63)); - if ((XY0 = malloc(256 * r + 64 + 63)) == NULL) - goto err1; - XY = (uint32_t *)(((uintptr_t)(XY0) + 63) & ~ (uintptr_t)(63)); -#ifndef MAP_ANON - if ((V0 = malloc(128 * r * N + 63)) == NULL) - goto err2; - V = (uint32_t *)(((uintptr_t)(V0) + 63) & ~ (uintptr_t)(63)); -#endif -#endif -#ifdef MAP_ANON - if ((V0 = mmap(NULL, 128 * r * N, PROT_READ | PROT_WRITE, -#ifdef MAP_NOCORE - MAP_ANON | MAP_PRIVATE | MAP_NOCORE, -#else - MAP_ANON | MAP_PRIVATE, -#endif - -1, 0)) == MAP_FAILED) - goto err2; - V = (uint32_t *)(V0); -#endif - - /* 1: (B_0 ... B_{p-1}) <-- PBKDF2(P, S, 1, p * MFLen) */ - pbkdf2_hmac_sha256(passwd, passwdlen, salt, saltlen, 1, B, p * 128 * r); - - /* 2: for i = 0 to p - 1 do */ - for (i = 0; i < p; i++) { - /* 3: B_i <-- MF(B_i, N) */ - smix(&B[i * 128 * r], r, N, V, XY); - } - - /* 5: DK <-- PBKDF2(P, B, 1, dkLen) */ - pbkdf2_hmac_sha256(passwd, passwdlen, B, p * 128 * r, 1, buf, buflen); - - /* Free memory. */ -#ifdef MAP_ANON - if (munmap(V0, 128 * r * N)) - goto err2; -#else - free(V0); -#endif - free(XY0); - free(B0); - - /* Success! */ - return (0); - -err2: - free(XY0); -err1: - free(B0); -err0: - /* Failure! */ - return (-1); -} diff --git a/trezor-crypto/crypto/secp256k1.c b/trezor-crypto/crypto/secp256k1.c deleted file mode 100644 index 9b1f0d82e48..00000000000 --- a/trezor-crypto/crypto/secp256k1.c +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright (c) 2013-2014 Tomas Dzetkulic - * Copyright (c) 2013-2014 Pavol Rusnak - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include - -const ecdsa_curve secp256k1 = { - /* .prime */ {/*.val =*/{0x1ffffc2f, 0x1ffffff7, 0x1fffffff, 0x1fffffff, - 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, - 0xffffff}}, - - /* G */ - {/*.x =*/{/*.val =*/{0x16f81798, 0x0f940ad8, 0x138a3656, 0x17f9b65b, - 0x10b07029, 0x114ae743, 0x0eb15681, 0x0fdf3b97, - 0x79be66}}, - /*.y =*/{/*.val =*/{0x1b10d4b8, 0x023e847f, 0x01550667, 0x0f68914d, - 0x108a8fd1, 0x1dfe0708, 0x11957693, 0x0ee4d478, - 0x483ada}}}, - - /* order */ - {/*.val =*/{0x10364141, 0x1e92f466, 0x12280eef, 0x1db9cd5e, 0x1fffebaa, - 0x1fffffff, 0x1fffffff, 0x1fffffff, 0xffffff}}, - - /* order_half */ - {/*.val =*/{0x081b20a0, 0x1f497a33, 0x09140777, 0x0edce6af, 0x1ffff5d5, - 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x7fffff}}, - - /* a */ 0, - - /* b */ {/*.val =*/{7}} - -#if USE_PRECOMPUTED_CP - , - /* cp */ - { -#include "secp256k1.table" - } -#endif -}; - -const curve_info secp256k1_info = { - .bip32_name = "Bitcoin seed", - .params = &secp256k1, - .hasher_base58 = HASHER_SHA2D, - .hasher_sign = HASHER_SHA2D, - .hasher_pubkey = HASHER_SHA2_RIPEMD, - .hasher_script = HASHER_SHA2, -}; - -const curve_info secp256k1_decred_info = { - .bip32_name = "Bitcoin seed", - .params = &secp256k1, - .hasher_base58 = HASHER_BLAKED, - .hasher_sign = HASHER_BLAKE, - .hasher_pubkey = HASHER_BLAKE_RIPEMD, - .hasher_script = HASHER_BLAKE, -}; - -const curve_info secp256k1_groestl_info = { - .bip32_name = "Bitcoin seed", - .params = &secp256k1, - .hasher_base58 = HASHER_GROESTLD_TRUNC, - .hasher_sign = HASHER_SHA2, - .hasher_pubkey = HASHER_SHA2_RIPEMD, - .hasher_script = HASHER_SHA2, -}; - -const curve_info secp256k1_smart_info = { - .bip32_name = "Bitcoin seed", - .params = &secp256k1, - .hasher_base58 = HASHER_SHA3K, - .hasher_sign = HASHER_SHA2, - .hasher_pubkey = HASHER_SHA2_RIPEMD, - .hasher_script = HASHER_SHA2, -}; diff --git a/trezor-crypto/crypto/secp256k1.table b/trezor-crypto/crypto/secp256k1.table deleted file mode 100644 index 0fa87a8b16c..00000000000 --- a/trezor-crypto/crypto/secp256k1.table +++ /dev/null @@ -1,1664 +0,0 @@ - { - /* 1*16^0*G: */ - {{{0x16f81798, 0x0f940ad8, 0x138a3656, 0x17f9b65b, 0x10b07029, 0x114ae743, 0x0eb15681, 0x0fdf3b97, 0x79be66}}, - {{0x1b10d4b8, 0x023e847f, 0x01550667, 0x0f68914d, 0x108a8fd1, 0x1dfe0708, 0x11957693, 0x0ee4d478, 0x483ada}}}, - /* 3*16^0*G: */ - {{{0x1ce036f9, 0x100f889d, 0x1be66c21, 0x03908b06, 0x15229b53, 0x07c2fc4e, 0x0c4124d1, 0x00324b18, 0xf9308a}}, - {{0x04b8e672, 0x05cfebac, 0x1088c6db, 0x01533269, 0x1f356650, 0x1bf3151b, 0x00503f8c, 0x01ec65bd, 0x388f7b}}}, - /* 5*16^0*G: */ - {{{0x1240efe4, 0x1d46ab4d, 0x1866adf2, 0x17097bb8, 0x05128e88, 0x1392852e, 0x024d56d2, 0x09a340e4, 0x2f8bde}}, - {{0x06ac62d6, 0x0543e9d5, 0x035a1037, 0x104e3756, 0x1c426f78, 0x14eed364, 0x0f5b536e, 0x04c6dcbc, 0xd8ac22}}}, - /* 7*16^0*G: */ - {{{0x0ac4f9bc, 0x095eef6e, 0x0c38e73a, 0x0336fc06, 0x07a0e3d4, 0x19b2f975, 0x13aa8e63, 0x0c8dcbb6, 0x5cbdf0}}, - {{0x087264da, 0x08413140, 0x1f79ed69, 0x07a17027, 0x054dba81, 0x06b6c30d, 0x05828c5e, 0x081744ab, 0x6aebca}}}, - /* 9*16^0*G: */ - {{{0x1c27ccbe, 0x1af8886f, 0x15f9c530, 0x0f2d2e98, 0x19abde09, 0x0bc54faa, 0x194c26b4, 0x1c5e18fe, 0xacd484}}, - {{0x064f9c37, 0x0e613156, 0x17e383c1, 0x1111486e, 0x161e9add, 0x04b8bb1d, 0x07f590e0, 0x043614fb, 0xcc3389}}}, - /* 11*16^0*G: */ - {{{0x1da008cb, 0x1f60bc4a, 0x105e246e, 0x133017cb, 0x05aac564, 0x1235b863, 0x04797bd0, 0x1f0b1528, 0x774ae7}}, - {{0x0953c61b, 0x00eba64e, 0x1e75aa0c, 0x1b63c5bf, 0x1b365372, 0x0eab6bdb, 0x1864090f, 0x065d6d6b, 0xd984a0}}}, - /* 13*16^0*G: */ - {{{0x19405aa8, 0x176efc78, 0x03963377, 0x0bf78cc2, 0x08651b07, 0x0902e1ba, 0x022f1f47, 0x185b2ea5, 0xf28773}}, - {{0x1b03ed81, 0x0dae5a96, 0x07ea47ca, 0x140db4a4, 0x1af473a1, 0x0975b2e6, 0x0a25d608, 0x05d1b101, 0xab090}}}, - /* 15*16^0*G: */ - {{{0x027e080e, 0x056de7c7, 0x017de791, 0x0b28de79, 0x1f41131e, 0x0d7184af, 0x0a596919, 0x09efa87d, 0xd7924d}}, - {{0x16a26b58, 0x0826e4ff, 0x05b4e971, 0x015e57b1, 0x06defea4, 0x17611466, 0x0a9a0e10, 0x0e550d8e, 0x581e28}}} - }, - { - /* 1*16^1*G: */ - {{{0x0a6dec0a, 0x027744f1, 0x1e96ba71, 0x0626d370, 0x03e97b2a, 0x155e10e1, 0x1b14c046, 0x1276b3d3, 0xe60fce}}, - {{0x09616821, 0x0f996673, 0x148fc2f8, 0x0d123c89, 0x13710129, 0x0f9a7abc, 0x164a76e6, 0x0e733cb2, 0xf7e350}}}, - /* 3*16^1*G: */ - {{{0x1118e5c3, 0x1ec38550, 0x0afaf066, 0x0f364e8a, 0x05b4bfc5, 0x12b77a73, 0x01f6d105, 0x0bb2c8a6, 0x6eca33}}, - {{0x05a08668, 0x0c517bc0, 0x1e3b0d12, 0x12d47477, 0x075a03a4, 0x0bc83a5c, 0x1c4164bd, 0x16af4f40, 0xd50123}}}, - /* 5*16^1*G: */ - {{{0x0f87f62e, 0x16698f0a, 0x1c5849c3, 0x0dcc70c6, 0x059f010e, 0x1a2769a3, 0x03b035f1, 0x17de37f2, 0xe9623b}}, - {{0x044ee737, 0x1809f57d, 0x1a211394, 0x008793ba, 0x0a929fe6, 0x0a9d476d, 0x07a783fa, 0x07697853, 0x38a974}}}, - /* 7*16^1*G: */ - {{{0x0a8d733c, 0x18556fc1, 0x1f2a3e7a, 0x04e97ec5, 0x0d682ffc, 0x11b79040, 0x16e82212, 0x0e7ca2c3, 0xbc82dd}}, - {{0x147797f0, 0x13c30827, 0x0e25cc07, 0x074175ce, 0x102dfae9, 0x1a5fb8cf, 0x12b152a6, 0x07408963, 0xe5f28c}}}, - /* 9*16^1*G: */ - {{{0x1fbc7671, 0x1f7f118a, 0x01638cb5, 0x1e3790a5, 0x0f490743, 0x08e70bcc, 0x0847480a, 0x0918ecae, 0x8e3d12}}, - {{0x18717dec, 0x178ee320, 0x0f85129f, 0x0a57554c, 0x1e90eb93, 0x0070c9c9, 0x0e07d912, 0x1c21d9f9, 0x99a48}}}, - /* 11*16^1*G: */ - {{{0x1eb31db2, 0x1943a195, 0x1d41a83c, 0x15d04f11, 0x0a2b68fc, 0x0c9f6844, 0x126225a8, 0x15444694, 0x78a891}}, - {{0x19fa4343, 0x034eb11d, 0x002e0b4c, 0x0f379bb0, 0x02df6543, 0x192a9398, 0x172ff3d7, 0x0b7d6a06, 0x6912a3}}}, - /* 13*16^1*G: */ - {{{0x0db0e595, 0x09a47bbc, 0x0820aed9, 0x0c7973f7, 0x076eba71, 0x1bb2c0b0, 0x0c5f5f38, 0x030abb63, 0x7d8678}}, - {{0x1c733de8, 0x0ca8f1d5, 0x09754ca6, 0x0f089c1c, 0x18838293, 0x1715f6a7, 0x01dcb958, 0x1bfd90df, 0xe2b99a}}}, - /* 15*16^1*G: */ - {{{0x16060dfc, 0x047f7c28, 0x179a8a80, 0x08bf0840, 0x0b086765, 0x05cee20d, 0x0b212125, 0x01e00b05, 0xddc531}}, - {{0x07820ca8, 0x1afc55b7, 0x1411cc3e, 0x175f8d57, 0x10e9041d, 0x15b6e647, 0x1a480646, 0x075e41b2, 0xba0d2f}}} - }, - { - /* 1*16^2*G: */ - {{{0x15f51508, 0x123711fe, 0x0b072841, 0x073957ab, 0x1e238d8c, 0x171f0b96, 0x0767a8a9, 0x064258c1, 0x828226}}, - {{0x16e26caf, 0x18db757f, 0x1ec5efb4, 0x0c27585e, 0x00ace62d, 0x0b74185b, 0x1f917a09, 0x0130aafb, 0x11f8a8}}}, - /* 3*16^2*G: */ - {{{0x057e8dfa, 0x07e065cf, 0x11f8613f, 0x01232347, 0x18ca0098, 0x187c5654, 0x11303668, 0x05fe0f33, 0x8262cf}}, - {{0x1bac376a, 0x0e7fc6c7, 0x05311e0d, 0x0dda6656, 0x14f3457b, 0x111762d9, 0x19399bfb, 0x1c412213, 0x83fd95}}}, - /* 5*16^2*G: */ - {{{0x026bdb6f, 0x02972458, 0x1cd2e524, 0x0837a8f6, 0x19c877ca, 0x02d92674, 0x17545a04, 0x1163b41b, 0x19825c}}, - {{0x049cfc9b, 0x0efb8426, 0x1db4e9ad, 0x13dd9919, 0x19f6cebe, 0x10e64a7a, 0x1e3cc809, 0x01e1a990, 0x629431}}}, - /* 7*16^2*G: */ - {{{0x1d82824c, 0x07684a91, 0x054d3994, 0x0b1c68bc, 0x0999edfa, 0x1ab76361, 0x04510f17, 0x0d822c03, 0x6f12d8}}, - {{0x06eb34d0, 0x0bce1a40, 0x152f16e1, 0x19248210, 0x03769391, 0x0a79feb1, 0x1e821d66, 0x1e895677, 0x5c4ff7}}}, - /* 9*16^2*G: */ - {{{0x1b453629, 0x1b6ee016, 0x167980c1, 0x1fb9b81e, 0x0bef645c, 0x138b511d, 0x09745098, 0x0df34155, 0x203a8c}}, - {{0x1ff89f84, 0x0b8e3c29, 0x0a17b516, 0x1bd64b8a, 0x10612686, 0x1b68afa0, 0x06e4db31, 0x0a7bcbbb, 0x3b0f0b}}}, - /* 11*16^2*G: */ - {{{0x046c7ecb, 0x018986ef, 0x0ed33a5e, 0x15da7fcb, 0x0e1ec9d3, 0x1b99a433, 0x0607207b, 0x1d67a068, 0x6e2aca}}, - {{0x0ebc8720, 0x024900f7, 0x19d44b21, 0x0e0d7236, 0x1643afac, 0x026d787d, 0x18527603, 0x0cf2fdfd, 0x9e61a4}}}, - /* 13*16^2*G: */ - {{{0x10a4147e, 0x1b80c79f, 0x1d7c807a, 0x0fbb17ee, 0x10a58274, 0x18bf4524, 0x15aebd85, 0x125d3d22, 0xd5a704}}, - {{0x13fb65ff, 0x1259e5c1, 0x19fd5fe3, 0x09b308f2, 0x00532f4c, 0x04c83b2f, 0x071bf124, 0x1ebb7571, 0x9db526}}}, - /* 15*16^2*G: */ - {{{0x18edcec6, 0x16eda35e, 0x18d3d153, 0x0c3985a6, 0x0dac6a10, 0x17e31816, 0x0ea0148f, 0x13557c31, 0x38c511}}, - {{0x1933db08, 0x0b705fd8, 0x154c2991, 0x02d90456, 0x1282f28a, 0x196d13af, 0x0ca99a32, 0x0450bb2e, 0xe649dd}}} - }, - { - /* 1*16^3*G: */ - {{{0x11e5b739, 0x1fe72daa, 0x0888bb5c, 0x127067fa, 0x0846de0b, 0x0e63637e, 0x1969cbe6, 0x13ee5170, 0x175e15}}, - {{0x09fed695, 0x17d37ff7, 0x090d171b, 0x0b2ab5ba, 0x11f5eacb, 0x0bd28ffb, 0x07ae93be, 0x01b3c78f, 0xd3506e}}}, - /* 3*16^3*G: */ - {{{0x05041216, 0x0dbfc78e, 0x0ae0da99, 0x066bed08, 0x1ed523f7, 0x0cf7ee17, 0x13d04a2d, 0x0f643ef5, 0xda7531}}, - {{0x0e708572, 0x176994c3, 0x1eb3b6b6, 0x1580f5ce, 0x17fc6e9a, 0x110d9a16, 0x17c37c67, 0x08d7ee5a, 0x73f8a0}}}, - /* 5*16^3*G: */ - {{{0x0465a930, 0x00a1f38f, 0x04d4bf6c, 0x0fe382d6, 0x0eb1e258, 0x02c62541, 0x075c15cf, 0x1691d2e9, 0x1c71c5}}, - {{0x034638b5, 0x0c39fb66, 0x05d351c7, 0x08bc7f6e, 0x1b68c793, 0x18f94125, 0x08309c4f, 0x069d1ebf, 0x4a91c3}}}, - /* 7*16^3*G: */ - {{{0x1adb6ee7, 0x18c6d72d, 0x11281df8, 0x01ba864e, 0x14c9c785, 0x01bd484d, 0x159a4dba, 0x1f83e634, 0xd84e4a}}, - {{0x142ebed2, 0x16aab736, 0x08f99260, 0x11592e95, 0x1de4dfdd, 0x06ac7ab2, 0x07384a8e, 0x134f8f6f, 0xe52580}}}, - /* 9*16^3*G: */ - {{{0x049e6d10, 0x0a74f67d, 0x0b26748e, 0x15bfe75d, 0x16ed3f60, 0x15c942ff, 0x053506c8, 0x097bccd0, 0xf3d444}}, - {{0x1347da3f, 0x101c6602, 0x075b18c2, 0x15b4a19d, 0x04b5bfc1, 0x0ad60cc5, 0x18f52eae, 0x1bf4de02, 0xa4324}}}, - /* 11*16^3*G: */ - {{{0x09d33a07, 0x0659a037, 0x0e6f2ad2, 0x05dc1154, 0x1f4044e7, 0x0a9066de, 0x1627e421, 0x0593b383, 0xae3065}}, - {{0x00a0b2a6, 0x0438607b, 0x15fa571d, 0x186febbf, 0x0d5cf1c9, 0x13e99edc, 0x195fbf33, 0x1871ac7f, 0x6cb9d9}}}, - /* 13*16^3*G: */ - {{{0x0c28caca, 0x0cb2a5c8, 0x00a0769d, 0x138f7799, 0x08c9a186, 0x1f3ac19c, 0x07205785, 0x054b7abc, 0xd8dc1b}}, - {{0x03b3ec7a, 0x0db3b751, 0x004a3db3, 0x02ba59d9, 0x07d947d3, 0x06d21012, 0x1f5631b6, 0x1b24f9d8, 0x8cec0a}}}, - /* 15*16^3*G: */ - {{{0x1bc4416f, 0x090fdb31, 0x02c100a2, 0x1dfa47e6, 0x0f31da7a, 0x12d46819, 0x1b335650, 0x1258bf09, 0x2749e2}}, - {{0x1c6bbd8e, 0x14c5ef17, 0x1a58415f, 0x1d3f6cbd, 0x0db3ef59, 0x0a4c87e5, 0x0f1100f6, 0x09c6ece5, 0x50cc2d}}} - }, - { - /* 1*16^4*G: */ - {{{0x03ff4640, 0x135d6c7c, 0x154bff94, 0x0838fcaa, 0x02ee0534, 0x1602db13, 0x1272673a, 0x1a88f601, 0x363d90}}, - {{0x1bee9de9, 0x1001e3f9, 0x0667b2d8, 0x13512010, 0x1363145b, 0x0229cbf9, 0x088654ed, 0x15bf8e64, 0x4e273}}}, - /* 3*16^4*G: */ - {{{0x16e55dc8, 0x1c4890b7, 0x12810e52, 0x12b56dd5, 0x094426ff, 0x12206028, 0x1ecaea12, 0x08f218bf, 0x443140}}, - {{0x1be323b3, 0x0eca2576, 0x0a8b940c, 0x14536f3d, 0x0fed7a66, 0x01bfab21, 0x1be3fa66, 0x085cca6c, 0x96b0c1}}}, - /* 5*16^4*G: */ - {{{0x101b23a8, 0x1f4a42eb, 0x01fb82b7, 0x16fa8e15, 0x1089dab7, 0x01eadc90, 0x01f04989, 0x11b0cd95, 0x9e22fe}}, - {{0x0884edae, 0x1d209e28, 0x14473b3d, 0x0f9293f6, 0x01533c0f, 0x1f8104ce, 0x14405dfc, 0x1d394245, 0xfd2ff0}}}, - /* 7*16^4*G: */ - {{{0x071a70e4, 0x0ba045f8, 0x173d1d77, 0x1dca3ebe, 0x1306dcd5, 0x14f32382, 0x0a34bb75, 0x1aa079c5, 0x508df6}}, - {{0x09950984, 0x1972dfb9, 0x02ab7fb7, 0x006451dd, 0x049c54ec, 0x0255399f, 0x10b5ddcc, 0x13726778, 0x154c43}}}, - /* 9*16^4*G: */ - {{{0x0e1abe11, 0x157ed3b6, 0x12c883db, 0x124384b3, 0x125b2dab, 0x1ac0c980, 0x1d8cce37, 0x108aa212, 0xe3dbff}}, - {{0x1fa8de63, 0x1a4d6aa4, 0x1ad71052, 0x12fb2078, 0x08ef3d3c, 0x1d3aedc5, 0x108590e3, 0x01334682, 0x6f2f9}}}, - /* 11*16^4*G: */ - {{{0x03593449, 0x078d91b0, 0x0a91bff7, 0x16f825c8, 0x1014af61, 0x0e09d03e, 0x10361e36, 0x0c98fbd2, 0x19ace0}}, - {{0x0df83631, 0x120a5c9d, 0x0101a28e, 0x02173e81, 0x02c46ac7, 0x0b9ceca0, 0x0cceffaf, 0x006a4d14, 0xe37992}}}, - /* 13*16^4*G: */ - {{{0x0cba6b63, 0x11f4c496, 0x02ceed7b, 0x1fcce9fa, 0x08e310a0, 0x19914754, 0x16a84230, 0x1d841f0f, 0xd8740c}}, - {{0x0934c5f3, 0x1751b603, 0x005a52af, 0x009eb987, 0x0c37d401, 0x1774c81d, 0x0afcde29, 0x0678d726, 0x6472c1}}}, - /* 15*16^4*G: */ - {{{0x1b3ec038, 0x0ca7f960, 0x031ac9d8, 0x1aa2d5cc, 0x10a50d9f, 0x1f3b1794, 0x020d9220, 0x07236a0c, 0x58ac33}}, - {{0x10246279, 0x17551f88, 0x13eef285, 0x12087816, 0x1fe97021, 0x0924f4c8, 0x0b65e1de, 0x00daab92, 0x9163d7}}} - }, - { - /* 1*16^5*G: */ - {{{0x1ffdf80c, 0x0fbcd2ae, 0x16f346da, 0x094f0342, 0x1638843e, 0x025adba2, 0x0afa3189, 0x02cbbe78, 0x8b4b5f}}, - {{0x1fd4fd36, 0x1f7f8632, 0x18bb95ac, 0x066ca8c2, 0x0da04f9e, 0x0bc09d58, 0x02d2cfef, 0x0ded1a61, 0x4aad0a}}}, - /* 3*16^5*G: */ - {{{0x155812dd, 0x05152c17, 0x0b4c38a8, 0x08ce46aa, 0x0f78e3d4, 0x1f6b602c, 0x14bc2daa, 0x0f525fe6, 0x7029bd}}, - {{0x1a2d2927, 0x10e63358, 0x0cb1cf1c, 0x15d08487, 0x083ac47d, 0x0a257183, 0x0f49f759, 0x1b5fbd16, 0xb0eefa}}}, - /* 5*16^5*G: */ - {{{0x1d486ed1, 0x0e72b41d, 0x1596da92, 0x0b7d7492, 0x17560574, 0x0084ec67, 0x12640275, 0x195d5ccb, 0x9ccfed}}, - {{0x15e95d8d, 0x1b6acf6b, 0x164aa893, 0x02ceb2d2, 0x13411242, 0x12409005, 0x0b3ed848, 0x0e27ad46, 0x7c2f4d}}}, - /* 7*16^5*G: */ - {{{0x1bd0eaca, 0x10358d3a, 0x0b52ade8, 0x0e8aed74, 0x0df19d0c, 0x1ef19e52, 0x050cd6a3, 0x10ec6828, 0xcd9a4b}}, - {{0x0bff4acc, 0x137d7dad, 0x1bd8d3db, 0x0f671dda, 0x0a08b012, 0x0457499f, 0x08fa0552, 0x0f343d1e, 0xf04558}}}, - /* 9*16^5*G: */ - {{{0x07bc57c6, 0x104a9d1e, 0x0c9db2fc, 0x07af447d, 0x03094490, 0x169749eb, 0x0faa213c, 0x05f11db3, 0xad0988}}, - {{0x0e4a0ab8, 0x1196076d, 0x0438b4f2, 0x023a6e6b, 0x0be5f7b3, 0x036394ed, 0x14ae8a06, 0x11885f74, 0x7243c0}}}, - /* 11*16^5*G: */ - {{{0x0ba56302, 0x14186395, 0x03c618ba, 0x0d526f5e, 0x0d2e0f50, 0x116954fd, 0x107c7ab6, 0x015d6794, 0xd9d129}}, - {{0x08291c29, 0x156ed7d4, 0x09d3caba, 0x135d9b3f, 0x186e4173, 0x0ae33931, 0x1bb40a5c, 0x027d85a7, 0x7eb531}}}, - /* 13*16^5*G: */ - {{{0x14d1243a, 0x1669b827, 0x15297f6e, 0x024c047e, 0x1558402b, 0x10d6031f, 0x13be4734, 0x1bca73ad, 0xbc5079}}, - {{0x055db68a, 0x0e4a8b44, 0x1d1c5a7d, 0x1dc9eb63, 0x1c8d75f7, 0x195ca6ff, 0x12ee3bb1, 0x07674e0b, 0x65062a}}}, - /* 15*16^5*G: */ - {{{0x174a3f9f, 0x06922735, 0x02605a42, 0x0f4ccbb8, 0x0625ac28, 0x15573ed9, 0x1fa244f7, 0x0fca0bf8, 0x4d31a7}}, - {{0x101e0ba7, 0x1e581209, 0x18029d53, 0x0df6a36d, 0x02753cbf, 0x079c63c0, 0x1d6c1ac6, 0x192c130a, 0x22241e}}} - }, - { - /* 1*16^6*G: */ - {{{0x1232fcda, 0x1b08ac92, 0x1039def2, 0x01b7ff4d, 0x148c7b70, 0x18e005ea, 0x05b5afdd, 0x14dcbb73, 0x723cba}}, - {{0x1eb39f5f, 0x0ee034ec, 0x1e525200, 0x0140ca6e, 0x04d6e266, 0x09ba4441, 0x1262a484, 0x16ab2b98, 0x96e867}}}, - /* 3*16^6*G: */ - {{{0x00633cb1, 0x0b3f04f4, 0x140844c9, 0x144496d3, 0x01fcb575, 0x1399090c, 0x0b500318, 0x1e62f559, 0x6dde9c}}, - {{0x07ce6b34, 0x1eea4d53, 0x0167bcd5, 0x04ffb59f, 0x066a880b, 0x17c350dd, 0x10757267, 0x1cf4e0fc, 0x9188fb}}}, - /* 5*16^6*G: */ - {{{0x0933f3c5, 0x0cd28c69, 0x1c494890, 0x141ee22b, 0x1b850085, 0x1dbfc723, 0x17a04f12, 0x059ab6b9, 0x486fa7}}, - {{0x0afb0f53, 0x16a538d6, 0x03c8ede6, 0x136f079e, 0x0f19f62d, 0x045d7664, 0x150f9231, 0x033ead7b, 0x62e123}}}, - /* 7*16^6*G: */ - {{{0x1e99f728, 0x1eaca112, 0x1c48813a, 0x06ebf7cd, 0x05303677, 0x1f93dbb5, 0x1d3ed993, 0x0e951295, 0x247969}}, - {{0x0baaebff, 0x1d0028b7, 0x1d68b60d, 0x17e7812a, 0x1664a5ad, 0x143f3ec6, 0x0007b14b, 0x088d11e6, 0xe3d78d}}}, - /* 9*16^6*G: */ - {{{0x0fb0079a, 0x0f0636a1, 0x04981272, 0x1f3dee47, 0x18324916, 0x0cf73b59, 0x1fc18c69, 0x1b547aab, 0x2f39cb}}, - {{0x0c5690ba, 0x1114b981, 0x0a808c3f, 0x167f7910, 0x1a58b9bf, 0x1b6813c6, 0x060d36a4, 0x1bc270c7, 0xabeadb}}}, - /* 11*16^6*G: */ - {{{0x04f7ab73, 0x09980597, 0x1110e9de, 0x07a9d53a, 0x0aed262e, 0x0f43629a, 0x06e95a8e, 0x0d864fac, 0xe5a31d}}, - {{0x10561f42, 0x089d1fe3, 0x032e884e, 0x18889350, 0x0ce5dbf8, 0x054bbd27, 0x15e83046, 0x07b1a3d3, 0x37788c}}}, - /* 13*16^6*G: */ - {{{0x014dcd86, 0x07c94d1e, 0x0fdc6d62, 0x0ba8412d, 0x1dcf11fc, 0x0ecc1028, 0x111f7d43, 0x0941a2a6, 0xcc389d}}, - {{0x08f0a873, 0x075b6ece, 0x1f9e1d1a, 0x0afc31fc, 0x110ca05c, 0x05eddf54, 0x0fb66d5a, 0x1acc1ed7, 0x93ae4f}}}, - /* 15*16^6*G: */ - {{{0x18819311, 0x07ce375b, 0x01dc51c9, 0x1c3dc421, 0x1ed1f0b3, 0x10bf067a, 0x0408dd42, 0x1913ae3d, 0x7f9291}}, - {{0x0c2eb125, 0x14fcdabd, 0x01a85d2a, 0x15548139, 0x015b6120, 0x0f462292, 0x1bc3d743, 0x020c7d87, 0x9da00d}}} - }, - { - /* 1*16^7*G: */ - {{{0x0e7dd7fa, 0x1299f650, 0x0a4660e6, 0x0f2c246f, 0x0d3b5094, 0x17640961, 0x1e62e97f, 0x1a9277d7, 0xeebfa4}}, - {{0x01de8999, 0x0fea7ed7, 0x047dc4b7, 0x099b874e, 0x0089d9ae, 0x1f6d78bc, 0x03c9a7b9, 0x1472e1de, 0x5d9a8c}}}, - /* 3*16^7*G: */ - {{{0x1b7ceceb, 0x1fb3c7fd, 0x05febc3c, 0x0b3f2711, 0x0681473a, 0x1c0937b7, 0x1140dbfe, 0x04084eda, 0x437a86}}, - {{0x16c181e1, 0x1b1de61a, 0x03e5e09c, 0x041f9fb9, 0x097ff872, 0x1f5b4ce9, 0x0cbda6e3, 0x1427dd58, 0xb916b}}}, - /* 5*16^7*G: */ - {{{0x097f96f2, 0x0c6b94f0, 0x121cd735, 0x046a53a5, 0x0c273358, 0x1f1dcd1e, 0x06f20f2d, 0x027c5491, 0xa9ef9f}}, - {{0x16c04be4, 0x01eaad82, 0x06d1c0b0, 0x1f135eb5, 0x1613db74, 0x170b075d, 0x15f3655b, 0x1cb28ab3, 0xe814cc}}}, - /* 7*16^7*G: */ - {{{0x150cf77e, 0x0055ad09, 0x0a2ac36f, 0x17eae8a9, 0x0064207d, 0x13eb4fd5, 0x16f954e0, 0x083dc3a6, 0x66d805}}, - {{0x00eaa3a6, 0x12fcbd7d, 0x0c6ddb4a, 0x0968756f, 0x013f6944, 0x0a1029ab, 0x0d0b0fc7, 0x1ce65fff, 0x51cfdf}}}, - /* 9*16^7*G: */ - {{{0x07213a5a, 0x1aa43144, 0x17e98ae4, 0x064094f0, 0x0c3c80b7, 0x0450e326, 0x1e8afcd4, 0x1c26ca07, 0x62ac05}}, - {{0x146a9e45, 0x08690e07, 0x1e653bf0, 0x12120302, 0x12579e55, 0x13cf03d4, 0x1b934e57, 0x1e741a34, 0x236fbd}}}, - /* 11*16^7*G: */ - {{{0x07bad12b, 0x187ea4c3, 0x1d9b87b0, 0x0bd401d5, 0x12db385d, 0x09d7ae37, 0x1d6d6fd8, 0x092e4891, 0xca13c4}}, - {{0x1723b0f2, 0x0b4cfa34, 0x095d59a2, 0x0156e223, 0x1a3d6637, 0x1d327556, 0x1f22b057, 0x106c3850, 0x83aa09}}}, - /* 13*16^7*G: */ - {{{0x1c80414e, 0x0d772fca, 0x19b3dff7, 0x1f150c5a, 0x09e4914e, 0x0e27a230, 0x0ba16b04, 0x0034e0a5, 0x1cecb1}}, - {{0x12169a3b, 0x0574a496, 0x1c8c7437, 0x0aafca88, 0x1ad16907, 0x11941af6, 0x13ed396a, 0x0cd2c12f, 0xf34360}}}, - /* 15*16^7*G: */ - {{{0x0ed810a9, 0x07ade462, 0x1c005571, 0x113a7e78, 0x123924f8, 0x0d7af8ef, 0x1e732543, 0x0ea09af1, 0x2a6990}}, - {{0x1a9b4728, 0x1c359c47, 0x105171f3, 0x1a8fd2fd, 0x104fc667, 0x0c34aa7d, 0x1f6b0fb1, 0x136d87bf, 0x54f903}}} - }, - { - /* 1*16^8*G: */ - {{{0x19a48db0, 0x1ebc1ad9, 0x0f00effb, 0x042b4536, 0x1de459f1, 0x08504dbd, 0x059c9e47, 0x1b4d2dce, 0x100f44}}, - {{0x0bc65a09, 0x1deae6b1, 0x14656b03, 0x1e9431fe, 0x10666b7f, 0x19980604, 0x0ddcbb23, 0x06325401, 0xcdd9e1}}}, - /* 3*16^8*G: */ - {{{0x15bc15b4, 0x05cd09a4, 0x168bb9a7, 0x0a051c8c, 0x1ca8d927, 0x0774e76b, 0x1727b616, 0x05ca3dd5, 0x10e90e}}, - {{0x18aa258d, 0x075f304a, 0x0edaa20d, 0x0b12c605, 0x11f754ca, 0x14630b56, 0x0109355e, 0x00701abc, 0xc68a37}}}, - /* 5*16^8*G: */ - {{{0x1fe75269, 0x0e9fe181, 0x0f4cc60b, 0x0f47980a, 0x17dcda37, 0x1c85b8a5, 0x18e115d6, 0x085b4a82, 0xf7422f}}, - {{0x17e49bd5, 0x04c07438, 0x08e63806, 0x07446fe9, 0x035977fb, 0x13ee5cfb, 0x04ff4633, 0x03466261, 0x406c2f}}}, - /* 7*16^8*G: */ - {{{0x15a7175f, 0x09db34b7, 0x073d0a99, 0x11cee3a6, 0x1debbedb, 0x0d2ac16a, 0x13fdca1e, 0x0082fa87, 0x2d8cad}}, - {{0x1b9d592a, 0x19bddc8d, 0x0d797833, 0x08d7fb39, 0x09d377a8, 0x197d3096, 0x0529eec8, 0x10663195, 0xc73f3b}}}, - /* 9*16^8*G: */ - {{{0x14b51045, 0x1a64de1c, 0x07096cf8, 0x0d912de6, 0x0cf73bbc, 0x1ea758f4, 0x1a962b9c, 0x03b7314d, 0x1ecbfd}}, - {{0x02c70026, 0x1d338808, 0x0d908b54, 0x000c8d68, 0x09b38b19, 0x05d8c24d, 0x0e9911f4, 0x06117338, 0x1cf6e2}}}, - /* 11*16^8*G: */ - {{{0x09358533, 0x1d66bb37, 0x1ed2e77d, 0x1267f3a9, 0x12a8c10a, 0x0ad148e9, 0x14a20fa5, 0x18bfcaee, 0x9a0894}}, - {{0x0360ba08, 0x19f0e2ee, 0x00376b7e, 0x0dcb7b77, 0x1c32165a, 0x1eafcaa7, 0x1f0c7e45, 0x18840371, 0xa79883}}}, - /* 13*16^8*G: */ - {{{0x198ef7f6, 0x0a202eb0, 0x01e3e7da, 0x07e7eef4, 0x0aea6592, 0x042939dc, 0x0b8d6f67, 0x093b69fa, 0x664dd8}}, - {{0x1d1eac94, 0x1a4b7f9a, 0x039bb3b9, 0x1a756637, 0x058cffc3, 0x0672ee82, 0x04ca8512, 0x02e2fe4f, 0xad5120}}}, - /* 15*16^8*G: */ - {{{0x03c934b3, 0x060535be, 0x02b8b138, 0x03eb00b6, 0x1a7022b3, 0x0cb34c08, 0x018e08c7, 0x126efa17, 0x82113a}}, - {{0x042c6a0f, 0x1f2f3156, 0x111a00dd, 0x1ef84d34, 0x0c628aa1, 0x16795ad0, 0x19982119, 0x1b5935c6, 0x8da1b8}}} - }, - { - /* 1*16^9*G: */ - {{{0x0534fd2d, 0x04566f37, 0x1cece14b, 0x1f1a88c9, 0x0c017a77, 0x113d2502, 0x146c7724, 0x1c4c58fd, 0xe1031b}}, - {{0x1456a00d, 0x0278c794, 0x073b5e69, 0x05ba833c, 0x1535af29, 0x120bb2cb, 0x0179aeda, 0x12512808, 0x9d7061}}}, - /* 3*16^9*G: */ - {{{0x0f028d83, 0x1cb11d77, 0x1d0e5855, 0x0b24db74, 0x069db619, 0x1f2d0aef, 0x17b1a96a, 0x189c78f0, 0xa7ebf7}}, - {{0x19d0bed1, 0x1201c95c, 0x014e4665, 0x07124e96, 0x0804b47a, 0x039bb822, 0x0b573f67, 0x05f7fc6c, 0x620515}}}, - /* 5*16^9*G: */ - {{{0x07dd5cfa, 0x17072011, 0x02752d6e, 0x138a26fe, 0x146336a8, 0x1529a131, 0x1d307371, 0x11b96049, 0x5b5ca0}}, - {{0x1e48e98c, 0x132537cc, 0x0c9a74f9, 0x00cf995e, 0x09094bfd, 0x18675443, 0x0097a647, 0x1ee1542b, 0x3eccb6}}}, - /* 7*16^9*G: */ - {{{0x03531f82, 0x1ca94719, 0x030b27ca, 0x0264d762, 0x02c29ff5, 0x1f3a44e1, 0x0ed733bc, 0x15982229, 0x46f26}}, - {{0x0bceda07, 0x082fe458, 0x0d571fe9, 0x0b28b9b5, 0x12579d02, 0x185617e1, 0x0bab388d, 0x062c6b70, 0x6b804b}}}, - /* 9*16^9*G: */ - {{{0x10432711, 0x058aa1b8, 0x045ae418, 0x08165fbb, 0x1645acf1, 0x1787844c, 0x1ed4d7e5, 0x1b263c1d, 0xc11926}}, - {{0x0fe2610c, 0x04930f17, 0x1cd01579, 0x17245941, 0x0c6cf83a, 0x05f8c366, 0x1a246125, 0x198fa4b6, 0x8be1f8}}}, - /* 11*16^9*G: */ - {{{0x01257963, 0x0bcc3a7d, 0x12aa1870, 0x031aa586, 0x179c45b0, 0x1aa5a9c4, 0x1cb9b36e, 0x1d3d6d11, 0x690846}}, - {{0x066f9835, 0x12bb2cca, 0x124ac94e, 0x18795043, 0x1bb7f6cb, 0x18127c97, 0x0f16d005, 0x196fe7fd, 0xe2485f}}}, - /* 13*16^9*G: */ - {{{0x1ee23ace, 0x0889ca5e, 0x1374bf4b, 0x10f8cb6a, 0x12915dec, 0x0a96fa51, 0x02eaee9d, 0x1f719244, 0xfb3df7}}, - {{0x1722e8de, 0x04ef7a64, 0x149fd7b8, 0x04e285fd, 0x0b58a9e7, 0x08aa06dd, 0x11594a89, 0x178238e7, 0x510e29}}}, - /* 15*16^9*G: */ - {{{0x10272351, 0x103e7f7a, 0x07fc4261, 0x06967bf3, 0x1cd41707, 0x11f59d58, 0x062cfae2, 0x1849eb8a, 0x6dd85e}}, - {{0x1fc7664b, 0x183fd15b, 0x18205f26, 0x18dfef87, 0x041b7877, 0x03fcd7ae, 0x09f82750, 0x1e8243e8, 0x16ea67}}} - }, - { - /* 1*16^10*G: */ - {{{0x1094696d, 0x0af3446c, 0x075abd4b, 0x1164cd48, 0x1d7ec5cf, 0x01cf8a1d, 0x0d4c2b0a, 0x15c8daab, 0xfeea6c}}, - {{0x18090088, 0x0aaef5f8, 0x10510b4c, 0x1912af98, 0x0cd5c981, 0x07095f9f, 0x06eac1b9, 0x0d92fb9c, 0xe57c6b}}}, - /* 3*16^10*G: */ - {{{0x08dfd587, 0x1c9b0dda, 0x0c099581, 0x09747193, 0x1a12d5ec, 0x1d55167a, 0x022cd219, 0x03759e8a, 0x5084b4}}, - {{0x11470e89, 0x13cf4bfc, 0x047d581b, 0x0deac0d1, 0x127475db, 0x13642a94, 0x14c5866a, 0x0343b301, 0x34a963}}}, - /* 5*16^10*G: */ - {{{0x1ab34cc6, 0x0411930b, 0x1cc284b4, 0x1852ecf9, 0x17128c80, 0x1f8fe8c6, 0x17a94fec, 0x07c0c85a, 0x4f14c0}}, - {{0x187e681f, 0x0f61297c, 0x00774089, 0x1c799d1d, 0x02540b9d, 0x1387a1d3, 0x0253194e, 0x1519549d, 0x7b53d0}}}, - /* 7*16^10*G: */ - {{{0x1241d90d, 0x013b8808, 0x09113e0d, 0x19e283b6, 0x1d363e81, 0x1b04af6e, 0x1b475050, 0x0fc938f3, 0xa74db8}}, - {{0x1f7adad4, 0x1928c5c1, 0x0828c4fc, 0x1ca12689, 0x171c8a9e, 0x08452c40, 0x1bcc9ff7, 0x19b5e47d, 0xf78691}}}, - /* 9*16^10*G: */ - {{{0x11c1ae1f, 0x0f665111, 0x13502ca9, 0x1cb18d15, 0x15658456, 0x06a275d1, 0x0b3e6b37, 0x0ae89754, 0x6901fa}}, - {{0x122838b0, 0x1c19832e, 0x11dea4c3, 0x1e774bcb, 0x0900dd79, 0x09c0614e, 0x0849186d, 0x11044e78, 0x35de5c}}}, - /* 11*16^10*G: */ - {{{0x127a4bdb, 0x1818840d, 0x12532b12, 0x14da086a, 0x1a35d046, 0x1b21f8dd, 0x049e8912, 0x05a3a870, 0x8d3cd8}}, - {{0x069a8a2c, 0x1e9a63e7, 0x02b4a5b0, 0x0700fa6e, 0x0236ed4e, 0x1fce803b, 0x07d1c33e, 0x0c301dc8, 0x9bd425}}}, - /* 13*16^10*G: */ - {{{0x14ec1d2d, 0x00669e35, 0x1f63d151, 0x133eb8dc, 0x1691fd90, 0x0b394b3d, 0x1ce6bfa7, 0x0c7ac0c8, 0xeaf983}}, - {{0x0c838452, 0x07f3ab02, 0x1a12d4ff, 0x0a5aa8af, 0x199aaa9f, 0x14507358, 0x1489dd48, 0x074ffcf1, 0xe51818}}}, - /* 15*16^10*G: */ - {{{0x045ae767, 0x059adfb8, 0x025dc72f, 0x0753973d, 0x0e5d8c27, 0x07029603, 0x18d19bd0, 0x02c75db6, 0xfb95bd}}, - {{0x1bbf0e11, 0x029f5057, 0x167c4d06, 0x0de8e314, 0x0275bb81, 0x06ce9b41, 0x16f14801, 0x1b02351b, 0x664c14}}} - }, - { - /* 1*16^11*G: */ - {{{0x01ec6cb1, 0x1fd4bc5e, 0x0160f78c, 0x1acafb01, 0x1ca3cfee, 0x1f25f37f, 0x1372cd9e, 0x03b22093, 0xda67a9}}, - {{0x1a68be1d, 0x14f54713, 0x1dd0285f, 0x0f5b8a11, 0x180e5dec, 0x11fbf64b, 0x0af107d1, 0x06a902c8, 0x9bacaa}}}, - /* 3*16^11*G: */ - {{{0x15bc8a44, 0x17ee8328, 0x18546867, 0x0202ef97, 0x05fc7684, 0x12d25d2d, 0x168f4e15, 0x0b079f9d, 0x4d0180}}, - {{0x1adbc09e, 0x18fca648, 0x00b68d8b, 0x08408d0b, 0x03813969, 0x1d4003eb, 0x174d9fa6, 0x1831969e, 0x3a33c6}}}, - /* 5*16^11*G: */ - {{{0x05daeb00, 0x00d5a943, 0x1917ddaf, 0x07d6499c, 0x0e9d1592, 0x1b5139db, 0x055c20b2, 0x00fbeb9f, 0x2f6615}}, - {{0x033992c0, 0x113b3c4c, 0x174c2304, 0x1bdc4e32, 0x0add06ec, 0x09bd4100, 0x0cfdbbe5, 0x026dea56, 0xfd5c12}}}, - /* 7*16^11*G: */ - {{{0x116aa6d9, 0x01548504, 0x1c0b73c6, 0x05916c8e, 0x15a38366, 0x0ba6d0c1, 0x1f48953c, 0x0fa0bfc8, 0xf59411}}, - {{0x1f2e50cf, 0x1e834b75, 0x1ad6e1b1, 0x04ef107c, 0x0bceb7a9, 0x0ded584a, 0x0c4b3d97, 0x14add2e3, 0xcaa761}}}, - /* 9*16^11*G: */ - {{{0x1ac6f4c0, 0x15701cab, 0x12f71332, 0x1d1bd198, 0x0711dda4, 0x01c5f34b, 0x137b1387, 0x0fe3e9b3, 0xf2d4d}}, - {{0x12339b58, 0x1f3ddd6b, 0x090995b9, 0x14214f20, 0x1e71e8f6, 0x13fef9c0, 0x19345fec, 0x10afd27b, 0x3ec89f}}}, - /* 11*16^11*G: */ - {{{0x1f428cb2, 0x02a829fe, 0x088ad576, 0x1045facd, 0x0d0f1233, 0x10c0acac, 0x0ef47ade, 0x185c5429, 0x1d5dce}}, - {{0x095b189b, 0x12f68804, 0x18112947, 0x1d225f0e, 0x10b88bd3, 0x03c12437, 0x0314341e, 0x10762859, 0x6e5c40}}}, - /* 13*16^11*G: */ - {{{0x08381273, 0x1875447d, 0x04f6b97e, 0x06ed2c0d, 0x126ced84, 0x14d96620, 0x1d0e7f7a, 0x1fa6012c, 0x89d9a2}}, - {{0x1309773d, 0x01f59f76, 0x049cc928, 0x03ad015a, 0x162f58f6, 0x17903b67, 0x1d40ddea, 0x168a3acd, 0xdfca25}}}, - /* 15*16^11*G: */ - {{{0x15c71d91, 0x06195965, 0x0253f487, 0x08bc5c55, 0x0fece239, 0x0e0988ad, 0x0b8714a8, 0x10f074a1, 0x83191b}}, - {{0x02148a61, 0x05cf6047, 0x01117b9c, 0x023ea2ea, 0x0ae8a17a, 0x0f09e8be, 0x0dc2781b, 0x02ae7003, 0xe0ac7f}}} - }, - { - /* 1*16^12*G: */ - {{{0x1a37b7c0, 0x1aa2e660, 0x0441a7d5, 0x11a1ef76, 0x02151ec0, 0x0049af79, 0x13769b80, 0x15416669, 0x53904f}}, - {{0x022771c8, 0x0e584b58, 0x110d1a67, 0x133303c2, 0x13c1c139, 0x16656106, 0x01b62327, 0x1a179002, 0x5bc087}}}, - /* 3*16^12*G: */ - {{{0x08a2050e, 0x0d6217f2, 0x17e299dc, 0x1deaaec2, 0x19b89742, 0x14e63723, 0x0c625add, 0x1fa4978e, 0x673724}}, - {{0x061d3d70, 0x0864d248, 0x0d2730ae, 0x1759fa86, 0x06b6dbe6, 0x01604d44, 0x088080d2, 0x0af12d49, 0xe4cf82}}}, - /* 5*16^12*G: */ - {{{0x02de63bf, 0x1fb7241c, 0x098719b2, 0x15ea650e, 0x166a8e03, 0x05318fb0, 0x10c27966, 0x148e5be9, 0x4366ef}}, - {{0x017924cd, 0x16352047, 0x0a9b5ac0, 0x1618a4b5, 0x068eaf33, 0x09bf0981, 0x1f38bb89, 0x0137dc5a, 0x2e7dd9}}}, - /* 7*16^12*G: */ - {{{0x06f96190, 0x08293ff8, 0x125497bc, 0x005bd20f, 0x0a75fd1f, 0x0ab4b33d, 0x0c7e5ef9, 0x0c4f3235, 0x7bd753}}, - {{0x0bda00f6, 0x1e8b9025, 0x03a9a568, 0x1f98b83c, 0x027d6ce0, 0x123a4a1c, 0x0c2757b5, 0x167b774c, 0x8336f2}}}, - /* 9*16^12*G: */ - {{{0x16ad41ed, 0x1d8a98aa, 0x1c0d490c, 0x11586be9, 0x0dc92030, 0x00cf448c, 0x1706be8d, 0x0f7bb55a, 0x4f7e92}}, - {{0x1e642d57, 0x1a1abc33, 0x0bddd6f7, 0x0628e29d, 0x0f6a62e7, 0x006fa9d4, 0x0c4154a6, 0x0a2ad511, 0xdfe774}}}, - /* 11*16^12*G: */ - {{{0x07748690, 0x1d302893, 0x18c2c073, 0x0f209bb0, 0x13d007d5, 0x0958f6e9, 0x133252d1, 0x10cfa523, 0x2355cb}}, - {{0x0c89582b, 0x1e23a98a, 0x0724e451, 0x10d0b19a, 0x07c58582, 0x02f60ad5, 0x07e3d56e, 0x114b4e3c, 0x21c2f1}}}, - /* 13*16^12*G: */ - {{{0x0ade7f16, 0x0d7510b0, 0x0f80a31b, 0x1975d279, 0x15d24ae9, 0x0955b613, 0x15b004d6, 0x0b7367b4, 0xb6682}}, - {{0x08c56217, 0x0a221342, 0x19f34af6, 0x08781be7, 0x1a97fb72, 0x1b5d45ab, 0x0cffbce9, 0x17031c13, 0xa1fba0}}}, - /* 15*16^12*G: */ - {{{0x19060d5b, 0x08358114, 0x0e8e9f0a, 0x0c276238, 0x151cb904, 0x0d97ecfc, 0x08b2d842, 0x079e17a0, 0xf60204}}, - {{0x1af88f13, 0x17b7a858, 0x17468fdd, 0x14ada56f, 0x17bea37a, 0x07b25627, 0x1c66206a, 0x00d83e19, 0xf036b7}}} - }, - { - /* 1*16^13*G: */ - {{{0x1ad86047, 0x1fcacfa1, 0x06e2f2bb, 0x0a740875, 0x0906779b, 0x053bb265, 0x0e9dc673, 0x017a6b30, 0x8e7bcd}}, - {{0x0460372a, 0x108023f4, 0x1f5a2cfa, 0x111c5c8f, 0x1514579e, 0x08210654, 0x12ce500c, 0x016547b4, 0x10b777}}}, - /* 3*16^13*G: */ - {{{0x041ead4b, 0x1f443cd0, 0x06c0f07f, 0x0bdbf6d2, 0x076be3a7, 0x19a77d7f, 0x0dfb1c51, 0x019191e6, 0xbfc90c}}, - {{0x06fedaed, 0x02963784, 0x182b8f9d, 0x0d1dfe65, 0x02d36fb4, 0x18c6ea82, 0x1b4936e9, 0x163c139b, 0x7a9481}}}, - /* 5*16^13*G: */ - {{{0x15bb3b3e, 0x173de83b, 0x07dcf2c9, 0x0a6c2fdf, 0x13f5b507, 0x0c9f4616, 0x0a937296, 0x0397c7f5, 0x732df1}}, - {{0x17366693, 0x02bbf0f6, 0x11610db3, 0x1b5adac9, 0x13916e69, 0x12ac2012, 0x05df2df8, 0x07dbd1f3, 0x7f4190}}}, - /* 7*16^13*G: */ - {{{0x088dc3b9, 0x0fad9e0c, 0x1dd32671, 0x0cd249d2, 0x1ef6019a, 0x0420664b, 0x1ed0a36a, 0x172c0728, 0x4ce094}}, - {{0x05c0de52, 0x00a7bdb7, 0x1a81940b, 0x00b64193, 0x0aca2162, 0x06c0653b, 0x0cfb55ed, 0x1757e353, 0x5390f}}}, - /* 9*16^13*G: */ - {{{0x0aaafe5a, 0x01baf058, 0x026bb753, 0x08e1674a, 0x03fd0e25, 0x1354ad81, 0x1f7a5b62, 0x16edf8cc, 0x9a968e}}, - {{0x0ed975c0, 0x135fea12, 0x19c33033, 0x0b1014cb, 0x01d0ee7b, 0x13681ec3, 0x08a553f1, 0x00f4da77, 0xabf6fb}}}, - /* 11*16^13*G: */ - {{{0x134c6397, 0x1a8e5fcf, 0x1d33424d, 0x1a7b6a00, 0x0b22107d, 0x17c0129b, 0x06553c2b, 0x1da02e2c, 0xd3c6fb}}, - {{0x08cb3f9c, 0x0fe4682b, 0x1783802c, 0x18ee1120, 0x0b6468a0, 0x1ca6c975, 0x0bc65160, 0x18abcbc5, 0x4a0dd2}}}, - /* 13*16^13*G: */ - {{{0x0ac3137e, 0x0dc12c98, 0x0bd91186, 0x04b6c54b, 0x13953fe9, 0x0353b555, 0x0ee380bc, 0x13025248, 0x4cbde3}}, - {{0x0ec02fe6, 0x00478ae7, 0x03ab5830, 0x074f5cb1, 0x0c370d19, 0x1509a5ca, 0x05654024, 0x0c11e26c, 0x6ce554}}}, - /* 15*16^13*G: */ - {{{0x00fbf84b, 0x1d118367, 0x0fc2e291, 0x091f9778, 0x0cebb03c, 0x0e39860a, 0x091ee2f6, 0x0790562c, 0x4b9d33}}, - {{0x036c3c48, 0x1cf725a5, 0x1d6d7988, 0x0daa87c5, 0x1b7eb676, 0x1ecc133c, 0x054954c4, 0x1f7c4998, 0xfd7fc7}}} - }, - { - /* 1*16^14*G: */ - {{{0x19c43862, 0x1420f0ac, 0x05f99a42, 0x0fe9e307, 0x01bde71a, 0x00c344dc, 0x1c879b42, 0x069839bf, 0x385eed}}, - {{0x142e5453, 0x022c7f2a, 0x01b72330, 0x009dd841, 0x1f4576b3, 0x0f0cf4f5, 0x0fd59c07, 0x187d1d44, 0x283beb}}}, - /* 3*16^14*G: */ - {{{0x16e2d9b3, 0x05e98355, 0x0e358d45, 0x17250636, 0x1845641d, 0x15309ce7, 0x179d784b, 0x1e72f8e0, 0x19a314}}, - {{0x0baaaf33, 0x0a97712e, 0x012f95b5, 0x043a3a48, 0x128b3a50, 0x12fc43fa, 0x03748d25, 0x1ebb58e5, 0x6cacd8}}}, - /* 5*16^14*G: */ - {{{0x12f00480, 0x0c2c3f58, 0x00373b9f, 0x0b100942, 0x07219203, 0x0c31b27b, 0x0a8d5772, 0x0972b51b, 0x5840ed}}, - {{0x1e22cf9e, 0x0c96af15, 0x0b8e1c85, 0x0a44a8a5, 0x1d8daba7, 0x06f550ae, 0x05041e5a, 0x0d64417e, 0x670cda}}}, - /* 7*16^14*G: */ - {{{0x03f54c42, 0x0426f741, 0x068f7239, 0x1834784d, 0x0532545d, 0x060fa767, 0x03ec7716, 0x14a68d23, 0x9f5701}}, - {{0x0feb6a21, 0x1070e249, 0x067949e1, 0x1cf09564, 0x1bfdd89e, 0x0db5ab94, 0x195efee5, 0x171003b3, 0xce7b8f}}}, - /* 9*16^14*G: */ - {{{0x1522461a, 0x14a09994, 0x11c60c9f, 0x06144cb7, 0x0f9a0bcb, 0x0380833f, 0x13f006ee, 0x0d246b51, 0x27f611}}, - {{0x07301a2d, 0x152657ce, 0x018e511a, 0x103641b5, 0x08ee8e49, 0x02b093a1, 0x0b4ba923, 0x1532014d, 0xe512f1}}}, - /* 11*16^14*G: */ - {{{0x114f36b9, 0x07164d96, 0x0e7ce433, 0x119576c7, 0x0a981259, 0x197a1afa, 0x0504986f, 0x10ad9ddf, 0x640779}}, - {{0x04b4b50a, 0x0a58f74f, 0x073baca2, 0x1410e093, 0x07b6c8cd, 0x0bafec2d, 0x1bebbe43, 0x11e89cad, 0xda6192}}}, - /* 13*16^14*G: */ - {{{0x14d6b76f, 0x17b1efcc, 0x120576f3, 0x0b53f769, 0x1feae7fe, 0x1845e04e, 0x1a7d14bd, 0x1c6390ac, 0xa23750}}, - {{0x0dcca8da, 0x0ec96664, 0x0e123450, 0x00f823fb, 0x11113e77, 0x1d1f26a0, 0x19b6b0cb, 0x0294dcfb, 0xf7339b}}}, - /* 15*16^14*G: */ - {{{0x1ceee475, 0x1774e327, 0x1b5e0ac4, 0x15905081, 0x0ff41ab6, 0x0010f9e5, 0x1e357b4f, 0x00f47e46, 0xbbf1ac}}, - {{0x07fe5067, 0x03115004, 0x02d075a1, 0x173b1287, 0x15fe324e, 0x1e641b58, 0x03984220, 0x1bdd5a8c, 0xb4bfb8}}} - }, - { - /* 1*16^15*G: */ - {{{0x03fac3a7, 0x10376c36, 0x11fef271, 0x1bf094b2, 0x1fa180fd, 0x19d2209e, 0x06458df1, 0x17007d9e, 0x6f9d9}}, - {{0x1a842160, 0x03448301, 0x0a0400b6, 0x09ba5eb8, 0x1c4d47ea, 0x11518722, 0x06e9a6e3, 0x11cc060b, 0x7c80c6}}}, - /* 3*16^15*G: */ - {{{0x121ce204, 0x076baf46, 0x19d8f549, 0x0e4b1c84, 0x0f72fb2a, 0x06c2ce53, 0x193ee0dd, 0x1a2c5678, 0x43ca41}}, - {{0x134a8f6b, 0x09282274, 0x09291a39, 0x0d8f667d, 0x1a31f9ab, 0x07c90c6d, 0x0fe87194, 0x105c6e04, 0xdcea5a}}}, - /* 5*16^15*G: */ - {{{0x0be6efda, 0x07e74967, 0x1ca01659, 0x1a9fe7f0, 0x0506d922, 0x1b91bc2d, 0x0fd6d99b, 0x1de45125, 0x9c3e06}}, - {{0x07aefc7d, 0x18a07995, 0x0dbf7df7, 0x1790d0f6, 0x06fd5d43, 0x196a2671, 0x08f62bc2, 0x1cbcec52, 0xa7b709}}}, - /* 7*16^15*G: */ - {{{0x06c88be2, 0x0893d5ae, 0x1bb9789e, 0x18f041a0, 0x0775bea2, 0x13acec18, 0x1c0ceedc, 0x14627c41, 0x5d6f8a}}, - {{0x0d7ad75d, 0x0972a9f8, 0x0fe4b0a2, 0x16df1d4d, 0x0bc20eda, 0x1799d584, 0x13a31c6a, 0x11b1aada, 0xadc4b1}}}, - /* 9*16^15*G: */ - {{{0x12d1b844, 0x1449a8dc, 0x1cf9213c, 0x18070582, 0x08bc5c69, 0x0ae1e09c, 0x157f21ac, 0x186094c1, 0xf57d35}}, - {{0x01266837, 0x125d5deb, 0x04571a91, 0x0d2e4061, 0x0634c700, 0x09fad4f2, 0x1365e413, 0x13d531de, 0x707f3d}}}, - /* 11*16^15*G: */ - {{{0x1cc7cb09, 0x1a6803f9, 0x146d0d48, 0x0fd6d143, 0x071463bc, 0x10ff71ec, 0x1297d65b, 0x0f474cb2, 0x13e760}}, - {{0x08079160, 0x1f3ad450, 0x0d5d9046, 0x15c576cd, 0x0299d65e, 0x1eec2d9a, 0x02c78c97, 0x11bd1f77, 0x284dc8}}}, - /* 13*16^15*G: */ - {{{0x18cdee05, 0x03067092, 0x0bb0ee40, 0x0c3f642e, 0x0901da87, 0x12858d83, 0x0b989000, 0x044ad030, 0x29bea3}}, - {{0x062651c8, 0x12501acd, 0x11a638e7, 0x18636d91, 0x05ec7f9f, 0x0d9fdc38, 0x083aa402, 0x144d21d3, 0x7c40d9}}}, - /* 15*16^15*G: */ - {{{0x12f18ada, 0x0db63ab0, 0x16f6f304, 0x017b2777, 0x14a59d46, 0x17d7f99e, 0x039f670d, 0x0da47051, 0xf52178}}, - {{0x03953516, 0x0eb457e9, 0x16fc2607, 0x1de946d8, 0x1d1d6aa5, 0x10815e68, 0x0d5fb309, 0x17ec071b, 0xe0686f}}} - }, - { - /* 1*16^16*G: */ - {{{0x02d0e6bd, 0x1dbf073a, 0x03d794c4, 0x09a2c7b6, 0x16ecbf77, 0x0a3e0826, 0x18960a88, 0x00248789, 0x3322d4}}, - {{0x0c28b2a0, 0x079d174b, 0x01cebd89, 0x0bec7d45, 0x0f9b7280, 0x0cde26ed, 0x1bd6fec0, 0x12fd2cc9, 0x56e707}}}, - /* 3*16^16*G: */ - {{{0x059ab499, 0x1ece9f90, 0x1cf0cc2a, 0x065338dc, 0x101bc0b1, 0x0b59e33f, 0x16e97486, 0x1e602b80, 0x78baaf}}, - {{0x1ee097fd, 0x00e918c7, 0x0494665a, 0x065ddd1a, 0x0082e916, 0x027076c1, 0x02bebf2a, 0x1b7b60d8, 0xad4bdc}}}, - /* 5*16^16*G: */ - {{{0x1d06ace6, 0x049f0b67, 0x0e883291, 0x01366df0, 0x1ab1a237, 0x024c2494, 0x0f53082e, 0x0234295c, 0x6f70f2}}, - {{0x1602d5de, 0x045f69a5, 0x16b17b81, 0x052acd7c, 0x19f50753, 0x0c79a3dc, 0x0dcdbe57, 0x0612804f, 0x791e8a}}}, - /* 7*16^16*G: */ - {{{0x00ee1b40, 0x04771f73, 0x1a5891f7, 0x1a90b6e3, 0x1ccd48ce, 0x04f8c881, 0x1057e025, 0x1653ad54, 0xe1599d}}, - {{0x178f93a6, 0x0eb132f6, 0x0ca66778, 0x0c74e978, 0x1c7cfa63, 0x04a55517, 0x1283bebe, 0x0465503a, 0x793362}}}, - /* 9*16^16*G: */ - {{{0x09c00c3e, 0x00efd142, 0x048238be, 0x1d1e0792, 0x11859f00, 0x11699ea2, 0x05590d95, 0x12e0880d, 0xbb0b04}}, - {{0x11955a35, 0x0cd4c168, 0x1772429e, 0x0e089d20, 0x1b052fe6, 0x17d0bd58, 0x1d8d9574, 0x0b0a75f3, 0x4067e4}}}, - /* 11*16^16*G: */ - {{{0x05dd32e6, 0x073adce0, 0x0f97b9f8, 0x076aa36a, 0x05fbfc66, 0x0edf03ad, 0x027a6d92, 0x0aa832af, 0xdc5a41}}, - {{0x154a99b9, 0x073edbd3, 0x1926f3e0, 0x05dd20ed, 0x159442ff, 0x12f100df, 0x1e9db73a, 0x14c7f3ec, 0x4af3a8}}}, - /* 13*16^16*G: */ - {{{0x0544e7cb, 0x16a2dd62, 0x0a580d67, 0x0c844b6a, 0x14e99a10, 0x0a52b880, 0x0e76f8cd, 0x0e0730e7, 0x156e19}}, - {{0x0d250a37, 0x0a9c9605, 0x0d0e735b, 0x0dab1abd, 0x14be8949, 0x0b9531c1, 0x01f6a4e5, 0x13f1e632, 0x6bc08d}}}, - /* 15*16^16*G: */ - {{{0x059853ca, 0x1fde14e6, 0x066fd536, 0x12c4d73e, 0x1161348e, 0x1b511473, 0x0e09af29, 0x19d96d08, 0x4269bc}}, - {{0x15b8d367, 0x14ac77a9, 0x196a28f6, 0x12814bf3, 0x1a409fd3, 0x0654e218, 0x1adc8f21, 0x03505802, 0xed2b1c}}} - }, - { - /* 1*16^17*G: */ - {{{0x0134ab83, 0x10eba694, 0x190ce5dc, 0x167f35ee, 0x05868741, 0x1b86c4b3, 0x1f68af45, 0x0fa5bc16, 0x85672c}}, - {{0x190313a6, 0x07184a7b, 0x0a63d132, 0x1e2ff98a, 0x0c2e5e77, 0x024dfd31, 0x0bad8dd0, 0x136b6876, 0x7c481b}}}, - /* 3*16^17*G: */ - {{{0x03ba9000, 0x0f8391d4, 0x097a2dbf, 0x0e5f38d0, 0x0143dc48, 0x1b03ac20, 0x0305a121, 0x1f3ffe3b, 0xac3874}}, - {{0x0f10cf0a, 0x0d1e3ecb, 0x19ba7e93, 0x1c6a1afe, 0x17f93085, 0x0ef44a08, 0x01a6e18b, 0x04611438, 0xaa65e9}}}, - /* 5*16^17*G: */ - {{{0x0d06dbd4, 0x13037b37, 0x08834206, 0x1c7f0af1, 0x1e729ec0, 0x003af4d1, 0x004e6b45, 0x1cf54d0e, 0x570d5c}}, - {{0x1d1ed495, 0x132df675, 0x1182fb56, 0x0746db8c, 0x01bbbb68, 0x173388e8, 0x0bd816d9, 0x092841c0, 0xa6ae53}}}, - /* 7*16^17*G: */ - {{{0x092d230e, 0x1a60fc90, 0x04ce4a10, 0x1c6541a5, 0x06ef5dae, 0x114f701b, 0x0edbe1f0, 0x0e0504d1, 0x75b5f8}}, - {{0x151570b8, 0x1be5efde, 0x047e3ec0, 0x0f49600a, 0x1fa8e026, 0x03a2aa6e, 0x148d8f5e, 0x043c74f0, 0x527cce}}}, - /* 9*16^17*G: */ - {{{0x034fcc0e, 0x1db5fb56, 0x18e21311, 0x1e214857, 0x059c8927, 0x1c0713e9, 0x1b0ac296, 0x1f5c3dbb, 0x44fc8e}}, - {{0x119c420a, 0x07f818f3, 0x14122767, 0x1d294979, 0x1d3d7720, 0x13f3c419, 0x0d9f9249, 0x12975362, 0xd2c7de}}}, - /* 11*16^17*G: */ - {{{0x0bb69991, 0x0d240fe4, 0x0c1c5d75, 0x167f4586, 0x0f535fff, 0x0b310701, 0x0f1a19a8, 0x08f759cf, 0xdea2ba}}, - {{0x116fe2df, 0x033a154f, 0x07b66f1c, 0x1b9b66c4, 0x1b9e7229, 0x1ff6427b, 0x0392b172, 0x1adb2185, 0xae28bf}}}, - /* 13*16^17*G: */ - {{{0x114fc9cc, 0x01206812, 0x09185963, 0x00157853, 0x0a83626d, 0x07b6625c, 0x035bbf07, 0x1314dc2d, 0x3968fc}}, - {{0x1fad37dd, 0x0c9ca44a, 0x023ccd00, 0x08caedff, 0x132c91a1, 0x0c168d56, 0x04a048de, 0x1a1b696b, 0x789cbb}}}, - /* 15*16^17*G: */ - {{{0x0e0c85f1, 0x17610c8d, 0x0e8e8942, 0x0a5ca6c0, 0x145d5a6a, 0x0ef84abc, 0x08a49ac9, 0x00d31c0d, 0x689691}}, - {{0x0dc1de21, 0x03a41199, 0x1540e48b, 0x01833b41, 0x1d72d1f4, 0x02e0ec22, 0x1e495bcc, 0x16967192, 0xaefd3f}}} - }, - { - /* 1*16^18*G: */ - {{{0x00c82a0a, 0x1ecacd7b, 0x19a20cbf, 0x044d8c1e, 0x013b10f9, 0x04f8c8ca, 0x0291ac1b, 0x10136331, 0x948bf}}, - {{0x18c8e589, 0x065bfc46, 0x1f34afb5, 0x1bfe1192, 0x1418c6d4, 0x1a62e8e1, 0x191b71ad, 0x10adb96c, 0x53a562}}}, - /* 3*16^18*G: */ - {{{0x18c8ac7f, 0x1417f2fd, 0x18aa949c, 0x0485dccb, 0x0f849641, 0x1cb6902b, 0x0ef2d70c, 0x1f7c7045, 0x9945b2}}, - {{0x09aea3b0, 0x16ca1d0b, 0x16b37ea5, 0x1ef447dd, 0x0eff5282, 0x1a27fd94, 0x00b581f6, 0x104961e5, 0x3eefed}}}, - /* 5*16^18*G: */ - {{{0x169e353a, 0x08ebcf1c, 0x0ef87dbb, 0x008810a5, 0x1d5fe10a, 0x01113883, 0x03988d7e, 0x0d640b0e, 0x2a314c}}, - {{0x05746067, 0x12c9370f, 0x09962ff0, 0x14a955b6, 0x01ba0138, 0x1f23b5d5, 0x1eb06918, 0x017e6b44, 0x15a4ac}}}, - /* 7*16^18*G: */ - {{{0x09b84966, 0x0f17a4f7, 0x1fcffe62, 0x1e820dba, 0x16c911b4, 0x17d79d35, 0x10b5262d, 0x0016e07f, 0x5959a5}}, - {{0x07473a6a, 0x0533190c, 0x1f890990, 0x01b98119, 0x02a70910, 0x094106e4, 0x025fe50c, 0x0e83eb95, 0x370e6}}}, - /* 9*16^18*G: */ - {{{0x0bc6b173, 0x1864dc91, 0x1b4fface, 0x10c478fd, 0x15f9d7dd, 0x0623182d, 0x1fa38458, 0x0726e445, 0x9eeb31}}, - {{0x1723a71d, 0x09cb106c, 0x19c312e5, 0x0e357da1, 0x01ae30ea, 0x15fedddd, 0x163654aa, 0x1c0221da, 0xe121f1}}}, - /* 11*16^18*G: */ - {{{0x1ec805f3, 0x1b9221fb, 0x16656455, 0x081eea13, 0x097887de, 0x191c4037, 0x03d45bae, 0x1c98fee3, 0x39cc4f}}, - {{0x1a3c48d3, 0x0c8d0609, 0x1244b934, 0x12ae6e7f, 0x0de7dbbb, 0x1349e7a1, 0x03cc5479, 0x058b48df, 0xecb147}}}, - /* 13*16^18*G: */ - {{{0x0e22580e, 0x1eb17dae, 0x15be21e4, 0x06512da5, 0x1d4d2c71, 0x1fef326d, 0x11a5a24b, 0x0e8cdd9b, 0xf94c80}}, - {{0x1127db82, 0x0f291fb3, 0x0dc753a0, 0x0fd88a64, 0x081f6988, 0x1ffb55ad, 0x096a4652, 0x1b8cf0a4, 0x5e9c7f}}}, - /* 15*16^18*G: */ - {{{0x10c4f21f, 0x1f205d68, 0x00f69e9a, 0x0952b29a, 0x199d2502, 0x10346d8c, 0x058bf300, 0x0d8d5aba, 0x8cccb8}}, - {{0x129dfea0, 0x1f893753, 0x192f8a3d, 0x05b95904, 0x158a54b9, 0x17aa4e43, 0x110da66f, 0x0b0c4ea3, 0x57f896}}} - }, - { - /* 1*16^19*G: */ - {{{0x138fd8e8, 0x0766c0cf, 0x1a5d4ab3, 0x01c89bf8, 0x073a8f1b, 0x1e707814, 0x070d3c19, 0x0fe8c300, 0x6260ce}}, - {{0x12b4ae17, 0x0d4274ad, 0x14706630, 0x05244700, 0x01ef7ecd, 0x0824bbb5, 0x15c69fc2, 0x056df4b6, 0xbc2da8}}}, - /* 3*16^19*G: */ - {{{0x01136602, 0x185d232a, 0x078e96ee, 0x08de901b, 0x13b3d38c, 0x049bf919, 0x1f0cf416, 0x0500905b, 0x87d127}}, - {{0x18af6aac, 0x1b41e20e, 0x14efdf13, 0x1108e8df, 0x1745387a, 0x13b67fb3, 0x0f7a49a8, 0x10e14b40, 0x71ce24}}}, - /* 5*16^19*G: */ - {{{0x08c5a916, 0x1902eb9a, 0x15843c96, 0x18881aa6, 0x14aa13f5, 0x0631ed5a, 0x05d1ac2b, 0x07fc4c3d, 0xfd5d7d}}, - {{0x1adb8bda, 0x0a59bd83, 0x13dbeaac, 0x0e70297b, 0x1b52fe5d, 0x1e533ce3, 0x0c1f4ad0, 0x1a1dd6ab, 0xdd83e}}}, - /* 7*16^19*G: */ - {{{0x15f7529c, 0x0ecc84b1, 0x0f547751, 0x0cb7316b, 0x04381387, 0x09e1969a, 0x1848ae91, 0x02130384, 0xde0dd4}}, - {{0x04cd88fe, 0x1e106017, 0x06ddd018, 0x12498d11, 0x03570178, 0x04c116bd, 0x117e6c84, 0x13a21442, 0xd70a6e}}}, - /* 9*16^19*G: */ - {{{0x18f76d11, 0x0417a469, 0x014555cf, 0x02001f7f, 0x092a2151, 0x1f7e1ce1, 0x1139a716, 0x115b499e, 0xb26c20}}, - {{0x03b7356b, 0x00e0058f, 0x009892f7, 0x0060cb3b, 0x16433050, 0x143ec866, 0x0cf3c313, 0x1041293a, 0x1f1cf8}}}, - /* 11*16^19*G: */ - {{{0x069e22db, 0x0a71d833, 0x07224cfe, 0x127feb3f, 0x0294eac9, 0x1e9480c6, 0x148e9f8e, 0x171f8ffe, 0xfceb14}}, - {{0x1c2260a1, 0x1ab96d92, 0x13af932f, 0x0a1cf274, 0x10924ad5, 0x12d3a92f, 0x0347f362, 0x07481ad7, 0x64aa6b}}}, - /* 13*16^19*G: */ - {{{0x038a2755, 0x18420bcd, 0x1271541b, 0x0434e2ca, 0x07faa537, 0x1abba5a8, 0x12b15705, 0x0c4799ab, 0x4e909a}}, - {{0x03cca3de, 0x1bd24fb1, 0x0a60032b, 0x1aa729a4, 0x159dbb6c, 0x036ea83a, 0x16bb3bc9, 0x10f199c6, 0xae56da}}}, - /* 15*16^19*G: */ - {{{0x183ba64d, 0x012c4acc, 0x116568ea, 0x0c1eef11, 0x040e2bde, 0x10a2f5c7, 0x100efec2, 0x0b845c09, 0xc36745}}, - {{0x0a2181fd, 0x0a6647cb, 0x1de3c518, 0x04d55942, 0x164c7426, 0x0737426c, 0x00cc2038, 0x1a0d396c, 0x3a520a}}} - }, - { - /* 1*16^20*G: */ - {{{0x0037fa2d, 0x0a9e6469, 0x0ff710ca, 0x1d91eaeb, 0x14103043, 0x0420a5df, 0x0350f60d, 0x1c15f83b, 0xe5037d}}, - {{0x1d755bda, 0x072ee420, 0x1207c438, 0x1eb607d8, 0x10bddbd5, 0x0684fdcc, 0x0ed7e7e6, 0x0975529a, 0x457153}}}, - /* 3*16^20*G: */ - {{{0x177e7775, 0x04545370, 0x1b657d8e, 0x02ab2711, 0x091aeb5e, 0x01dd67a9, 0x0f3b9615, 0x075ff2c6, 0x9d896a}}, - {{0x1a056691, 0x1e7b69d5, 0x06494efb, 0x139afdc5, 0x0927de89, 0x1276b928, 0x1c2e53a5, 0x1c87e937, 0xdd91a9}}}, - /* 5*16^20*G: */ - {{{0x1c2a3293, 0x1ef026f1, 0x00d1db17, 0x1170ddd2, 0x0f4cd568, 0x052b9941, 0x1e4b43ac, 0x1dce22c6, 0x8327b8}}, - {{0x0e0df9bd, 0x1e42a70c, 0x0c9a905a, 0x1fb569dc, 0x1708496a, 0x1f53313c, 0x063862ec, 0x04cddc15, 0x4997e}}}, - /* 7*16^20*G: */ - {{{0x0562c042, 0x010d9362, 0x037ec689, 0x1a464697, 0x08ed6092, 0x130ec7cd, 0x05a25f59, 0x15454db6, 0x5ae42a}}, - {{0x0f79269c, 0x082e66fc, 0x1f3636fe, 0x01b72a20, 0x09d4a94e, 0x0eee301c, 0x147aad70, 0x0f80bfe0, 0x99d93a}}}, - /* 9*16^20*G: */ - {{{0x1e85af61, 0x1a440942, 0x12b9d9ac, 0x1dae45ba, 0x01b0f4e8, 0x1b47fb61, 0x03ad66ba, 0x1c84d439, 0x92c23a}}, - {{0x036a2b09, 0x1391b34e, 0x0a1bfb53, 0x075b056c, 0x0d5792d2, 0x0beae39c, 0x0ed027c8, 0x11e02aa3, 0x414cf8}}}, - /* 11*16^20*G: */ - {{{0x07b5eba8, 0x11578d96, 0x063a8db3, 0x17db8ff2, 0x0df422da, 0x1a0bb57c, 0x1c422343, 0x118ed5fb, 0xfee560}}, - {{0x0d0b9b5c, 0x1a8ae9b4, 0x04151e4f, 0x01fe857f, 0x1c14ee38, 0x017cc943, 0x02bec450, 0x12269fcb, 0x380759}}}, - /* 13*16^20*G: */ - {{{0x1c63caf4, 0x0f1dd259, 0x1d4f54a0, 0x1fe75651, 0x06afca28, 0x09da6315, 0x1f988284, 0x1d725ccc, 0x42e544}}, - {{0x169c29c8, 0x03d7604c, 0x1bf17c46, 0x0a1cf6d7, 0x15e7873a, 0x11060ba0, 0x19c7dc7c, 0x1c1f2398, 0x9ff854}}}, - /* 15*16^20*G: */ - {{{0x1e0f09a1, 0x0515ecc2, 0x100ca0e0, 0x0213e372, 0x00efef0a, 0x17695238, 0x138e0e65, 0x16ccaa65, 0x7aed83}}, - {{0x05857d73, 0x02ec66f4, 0x0fd29501, 0x165e601e, 0x12d8ed88, 0x1e855881, 0x1df1f76b, 0x0bf3463d, 0xf5b854}}} - }, - { - /* 1*16^21*G: */ - {{{0x04fce725, 0x0c335057, 0x09b16dc9, 0x11b7a38d, 0x171b4e7e, 0x082f478b, 0x1eb7d7aa, 0x161e9440, 0xe06372}}, - {{0x0eee31dd, 0x1381a7ca, 0x04121c2c, 0x1094ef0e, 0x0488cd74, 0x1dd956ad, 0x13f84a89, 0x0e979c31, 0x7a9089}}}, - /* 3*16^21*G: */ - {{{0x1a328d6a, 0x1d540c46, 0x0b7062f6, 0x1aee752b, 0x08fa7b24, 0x04c8c2d8, 0x18028d1a, 0x0b74c469, 0xc663c0}}, - {{0x1ec9b8c0, 0x1d85db55, 0x0afe7308, 0x0aa3d4a2, 0x11317dd8, 0x1ee793ab, 0x10e34e6b, 0x11abee43, 0x3331e9}}}, - /* 5*16^21*G: */ - {{{0x1996de2f, 0x048e4241, 0x0c94452f, 0x1dbe5dc1, 0x1e4e977c, 0x186f7507, 0x091673ac, 0x105bf70d, 0xd3fc26}}, - {{0x14526f8c, 0x0249120e, 0x1eafc5a3, 0x136931be, 0x181da4e5, 0x03aa6a7b, 0x0863da2d, 0x13348be1, 0xc4f0df}}}, - /* 7*16^21*G: */ - {{{0x03e697ea, 0x03624688, 0x17e0fa17, 0x0cf1f730, 0x1abd19ce, 0x0ff64d1f, 0x008df728, 0x087fd658, 0xc17a4b}}, - {{0x1edc6c87, 0x171dc3ee, 0x07c0aac3, 0x03436aff, 0x01fae96e, 0x1c7b8cb0, 0x05532b85, 0x05aab56b, 0x39355c}}}, - /* 9*16^21*G: */ - {{{0x1163da1b, 0x16961811, 0x04e8c460, 0x1dbdcc1f, 0x11fde9a0, 0x1a4ebfe0, 0x02d1a324, 0x0f944cf2, 0x8f618b}}, - {{0x03bdd76e, 0x1f989088, 0x126db9f1, 0x018cd464, 0x05a42645, 0x0d3a6bd6, 0x0dbad7ef, 0x04be117d, 0x78233f}}}, - /* 11*16^21*G: */ - {{{0x0ec8ae90, 0x142c87f0, 0x0ef177bb, 0x04d725d1, 0x1f1b8cfb, 0x0dc6d641, 0x19bae1b5, 0x1e2b6f43, 0x9798c0}}, - {{0x052844d3, 0x14d61757, 0x0c62389e, 0x092cb7a0, 0x073bee2e, 0x04a4a7ce, 0x1b4f74bb, 0x154eb485, 0xba40e2}}}, - /* 13*16^21*G: */ - {{{0x11d66b7f, 0x0d0cbc78, 0x01f72041, 0x0d24a0a3, 0x084757aa, 0x0dc85c49, 0x159d1f3c, 0x1c7f6b45, 0xfdfa6e}}, - {{0x18e5178b, 0x1547c033, 0x15e37a76, 0x0df3ba27, 0x018c4d84, 0x00e4d1ed, 0x036e4f03, 0x03c44933, 0x4d9cf3}}}, - /* 15*16^21*G: */ - {{{0x1265bcf0, 0x003abc24, 0x071f4c2e, 0x1c56f082, 0x1220e69c, 0x14d230e7, 0x190eb77a, 0x071bc453, 0xfd58ce}}, - {{0x0b996292, 0x19f3d4e7, 0x1c73477c, 0x0c37fc51, 0x1e4fb872, 0x155cd242, 0x056f54e0, 0x1ca6ec64, 0xffe0a5}}} - }, - { - /* 1*16^22*G: */ - {{{0x10559754, 0x056b4846, 0x08fd6150, 0x0217bbc5, 0x0e02204b, 0x1dfcee06, 0x114d6342, 0x0e2b9aba, 0x213c7a}}, - {{0x14b458f2, 0x1f9613a9, 0x1a9fbb77, 0x10a1ebe6, 0x1a190bb4, 0x1683122d, 0x0941c04e, 0x016b5c8c, 0x4b6dad}}}, - /* 3*16^22*G: */ - {{{0x1e05dccc, 0x196c008c, 0x066a4f94, 0x1f47da98, 0x1d172ae3, 0x104b5ca9, 0x00c2551b, 0x1c2ea7b4, 0xb8cef6}}, - {{0x0c6d5750, 0x00a5067e, 0x1ada04cc, 0x0aff86d4, 0x0bd99df7, 0x053a7269, 0x0efda935, 0x0c14d993, 0x302b8a}}}, - /* 5*16^22*G: */ - {{{0x173bb31a, 0x0deff709, 0x16e5ed21, 0x1ef6d6bf, 0x15f49701, 0x05ef175d, 0x0e1780a8, 0x1cef368e, 0x3fb33}}, - {{0x1d215c9e, 0x1a65f34b, 0x1d903538, 0x14f8ed88, 0x1572bc65, 0x0b0d55dd, 0x18a07830, 0x0a4a91df, 0xf36ad9}}}, - /* 7*16^22*G: */ - {{{0x1d9a4ab4, 0x0da8af5e, 0x16edf029, 0x186d830a, 0x17a36717, 0x17bda687, 0x184587c5, 0x1a213d87, 0x4b177c}}, - {{0x035ab6f7, 0x156eff23, 0x1d07d562, 0x0fc4abe2, 0x06b486e3, 0x1b3949b1, 0x0997f6a3, 0x1d34bc5f, 0x3ec966}}}, - /* 9*16^22*G: */ - {{{0x101c23a4, 0x1a37d94d, 0x09273d5b, 0x1482b08f, 0x1cd75c22, 0x1c14dcdc, 0x081b0a80, 0x0a40d44f, 0x5e8703}}, - {{0x10d986f9, 0x10bccb58, 0x1333f684, 0x0e3b0e94, 0x06e2da21, 0x0ae8d716, 0x08879c5d, 0x09df7392, 0x5b9664}}}, - /* 11*16^22*G: */ - {{{0x007b0c66, 0x19f9ae90, 0x1b21bec7, 0x0ffdc8ca, 0x0eb7434a, 0x1227b056, 0x002911c8, 0x003261ad, 0xe545c3}}, - {{0x11f2d470, 0x03fabe93, 0x1688e776, 0x0b051c36, 0x1bcbf97e, 0x17ed1cac, 0x1579f971, 0x01d18c52, 0xe06a34}}}, - /* 13*16^22*G: */ - {{{0x03648bba, 0x12c0c85c, 0x10f1d112, 0x01e160d3, 0x1b39882a, 0x17112f80, 0x160284cf, 0x02af4a9e, 0xb2a442}}, - {{0x00fb6452, 0x037d5a50, 0x0705eec9, 0x10aa2a39, 0x17e31c0d, 0x1e4bc7de, 0x19867a7e, 0x1856d26c, 0xfe4f5f}}}, - /* 15*16^22*G: */ - {{{0x0bab27d0, 0x1b6e9177, 0x11440ff3, 0x1683f458, 0x17007f70, 0x180c8c6c, 0x01946ea5, 0x01e7a8a7, 0x1b908e}}, - {{0x00d3d110, 0x0f433af2, 0x1f946d5c, 0x179526b0, 0x04b9ab5b, 0x1e48c0be, 0x0d79bcd5, 0x0bdd88cd, 0x9b6d62}}} - }, - { - /* 1*16^23*G: */ - {{{0x08fbd53c, 0x0661d1d8, 0x118b377c, 0x0718e15b, 0x19a87e28, 0x09a952a0, 0x0d3a36ee, 0x054f5e96, 0x4e7c27}}, - {{0x17dcaae6, 0x059ca0c0, 0x1df74cf8, 0x172c297f, 0x1681b530, 0x084fb6f7, 0x0c6385bf, 0x0ecd93a1, 0x17749c}}}, - /* 3*16^23*G: */ - {{{0x0521b3ff, 0x114c2327, 0x0a9d433b, 0x180e2fd3, 0x024a5233, 0x0695f4d7, 0x1571d791, 0x06021928, 0x2484e}}, - {{0x0269da7e, 0x00d725c8, 0x0eb22ef3, 0x000dbd24, 0x10eebad7, 0x159e1596, 0x0c341fb0, 0x1415447c, 0x9619d0}}}, - /* 5*16^23*G: */ - {{{0x004ba7b9, 0x0b5bcfd4, 0x1d05d47e, 0x0d48e060, 0x0954e20d, 0x1b86b396, 0x195fbde6, 0x04d9f6d9, 0x16c1c5}}, - {{0x1c5bd741, 0x1adf1d28, 0x126a7311, 0x0ab8037b, 0x094deec7, 0x13a2ce45, 0x10e41898, 0x0f868062, 0xdb157f}}}, - /* 7*16^23*G: */ - {{{0x036683fa, 0x01b243d5, 0x1b02097a, 0x0436a701, 0x07b66958, 0x0e73ba29, 0x06be1ea2, 0x1aea7f26, 0x3973c}}, - {{0x1f4a577f, 0x1afd95bb, 0x1a6077b7, 0x1109c31f, 0x1a26cd77, 0x095b195a, 0x0e8d90f8, 0x05986194, 0x38cf5a}}}, - /* 9*16^23*G: */ - {{{0x00bf6f06, 0x0059ccce, 0x010ed5c6, 0x1826644a, 0x05765713, 0x027a5810, 0x054470b0, 0x174e5d9e, 0xba6a9b}}, - {{0x181db551, 0x195f7b83, 0x1a5eabf8, 0x1a29ef58, 0x0bd8e9e5, 0x05f972ac, 0x06c0c808, 0x07166942, 0x13771e}}}, - /* 11*16^23*G: */ - {{{0x11231a43, 0x08f7cf83, 0x0b50ee7f, 0x0a29accb, 0x0442f44d, 0x0ca8326f, 0x174b62bb, 0x1984f989, 0x35c5bc}}, - {{0x1620c8f6, 0x0db97228, 0x0f3c2b9f, 0x0f49980c, 0x0589d4cf, 0x0c105b7d, 0x1b39cd39, 0x0e4772e8, 0xe3675}}}, - /* 13*16^23*G: */ - {{{0x1dd40609, 0x1a05e2b7, 0x00735daf, 0x0321301b, 0x0356ac74, 0x1897e2c4, 0x1af2848b, 0x048b8ab0, 0xfd479c}}, - {{0x0a64ca53, 0x0f1789b3, 0x07291ce7, 0x075dae4c, 0x041fd911, 0x0bd21e4c, 0x1fbfcb2b, 0x16a4d295, 0x3069cf}}}, - /* 15*16^23*G: */ - {{{0x0b799a7f, 0x0db817a9, 0x0e3a1093, 0x116d9aa7, 0x07d544f1, 0x075cd796, 0x0192f7b6, 0x0547599b, 0x6c4000}}, - {{0x13c81e32, 0x1ae64166, 0x0120fda2, 0x157a9904, 0x1dcbdc07, 0x01b5070e, 0x16f9a42e, 0x02a616c6, 0x95c2dc}}} - }, - { - /* 1*16^24*G: */ - {{{0x00fb27b6, 0x1213f142, 0x10c15d8c, 0x1c7b657c, 0x06aa5c76, 0x1c56b0b4, 0x0c6c43c8, 0x07b7cef1, 0xfea74e}}, - {{0x123cb96f, 0x00e9edbf, 0x0fdedddc, 0x16b2d72e, 0x0af93126, 0x1a6f665b, 0x0ca5f3d9, 0x1b736162, 0x6e0568}}}, - /* 3*16^24*G: */ - {{{0x1e889756, 0x0ec0d74d, 0x0012ec97, 0x16c932f6, 0x099f3f27, 0x0cbd938c, 0x1aa089b3, 0x1866423f, 0x762e8b}}, - {{0x1ca6b774, 0x0f12cf03, 0x013e9789, 0x05b66291, 0x0e347197, 0x0278a4c1, 0x05f0f1f3, 0x04c15e7d, 0xc02894}}}, - /* 5*16^24*G: */ - {{{0x0975d2ea, 0x17baf4b8, 0x053a3a89, 0x0559f420, 0x0f4a91e5, 0x1edd9184, 0x14d23866, 0x08fbec12, 0xdf077d}}, - {{0x11936f95, 0x11e16cf1, 0x0f749dea, 0x1d8b709f, 0x0527c8a1, 0x012e4c51, 0x1d109321, 0x11001def, 0xf8617a}}}, - /* 7*16^24*G: */ - {{{0x1c4c92d7, 0x1c248fdd, 0x10e46d16, 0x169addca, 0x1142935d, 0x0f5419a5, 0x080cb85f, 0x0eb17a7b, 0x9f3e7d}}, - {{0x114906dd, 0x05ddfe7d, 0x0538461b, 0x144607ad, 0x11502452, 0x1590e5d5, 0x19ad6218, 0x03d4efa8, 0xecd284}}}, - /* 9*16^24*G: */ - {{{0x12a8c483, 0x1706b995, 0x0102b0d6, 0x1619118a, 0x15281174, 0x01e9177c, 0x1e7b70e3, 0x0baf6b99, 0xa0cc79}}, - {{0x12cc6ba9, 0x04b3a2ac, 0x1a4d8154, 0x091e37be, 0x1df786b3, 0x07e4b918, 0x1cfb88dd, 0x045f1670, 0xabc301}}}, - /* 11*16^24*G: */ - {{{0x05dd3aee, 0x1878db5e, 0x05b4bc85, 0x0a75151f, 0x176ca131, 0x154d6354, 0x1f338388, 0x14a2aa78, 0x6d1c50}}, - {{0x1df597f7, 0x171aa727, 0x1b54eb7f, 0x1c621551, 0x1d474851, 0x19001143, 0x1f725dc9, 0x11c0d57b, 0xafff14}}}, - /* 13*16^24*G: */ - {{{0x04a6d0bb, 0x1c654dbf, 0x086bf719, 0x1a6245eb, 0x0418f659, 0x136c5453, 0x07cfcc46, 0x0c3172ff, 0x5e5f1d}}, - {{0x1033eaf9, 0x141c23c8, 0x1bd94e85, 0x0abe5ca0, 0x121da725, 0x15e68273, 0x1bdcd63d, 0x0560d4fc, 0xd7b150}}}, - /* 15*16^24*G: */ - {{{0x0f005e3f, 0x0d4daf22, 0x10e6f4b7, 0x0d1c637d, 0x1a1495af, 0x05cd6700, 0x09ffff4f, 0x0d6782c8, 0xf8138a}}, - {{0x0f357eb7, 0x16bf0101, 0x12f884d0, 0x18837aaa, 0x1cb51f4e, 0x0af2bd52, 0x0f67e740, 0x077df69d, 0xca758f}}} - }, - { - /* 1*16^25*G: */ - {{{0x17bdde39, 0x16015220, 0x1810ca54, 0x09c2f36e, 0x168d3154, 0x0b86accc, 0x1c384289, 0x027ecef9, 0x76e641}}, - {{0x1901ac01, 0x058ba968, 0x1b480cad, 0x1467a56a, 0x1f0d35e2, 0x136b8340, 0x173d5dc1, 0x11bdc9d2, 0xc90ddf}}}, - /* 3*16^25*G: */ - {{{0x0078ee8d, 0x182848e6, 0x1a46510b, 0x1e419ca0, 0x14ff64eb, 0x1931d54d, 0x06f897fd, 0x15b0b3b5, 0xd08e57}}, - {{0x0da63e86, 0x0cbfa6e1, 0x08bb677a, 0x1def9f28, 0x06df4123, 0x19773abf, 0x035cb585, 0x13095691, 0x852e97}}}, - /* 5*16^25*G: */ - {{{0x029129ec, 0x0c8a3382, 0x12095205, 0x1c759e3c, 0x11d080ca, 0x1f407669, 0x149d7d62, 0x10bc9a89, 0x7da6c0}}, - {{0x0cd9ff0e, 0x1a857715, 0x12961aba, 0x11810ca9, 0x027bf044, 0x0103a48b, 0x015d4474, 0x0d773e83, 0xf49814}}}, - /* 7*16^25*G: */ - {{{0x11654f22, 0x1c1ea4aa, 0x06abba53, 0x0fe72846, 0x1d94fb2f, 0x0800df34, 0x19b886fa, 0x19feb837, 0x90d090}}, - {{0x001a43e1, 0x1aef02bb, 0x08fe1d03, 0x0c6aca7b, 0x170336dd, 0x010f035f, 0x186a54fc, 0x03a5759e, 0xcd569a}}}, - /* 9*16^25*G: */ - {{{0x076b19fa, 0x1b77b28e, 0x020675c6, 0x0dc0da0d, 0x1292ed9d, 0x16188410, 0x07b31cc8, 0x0b0f9e3a, 0xda4798}}, - {{0x126f5af7, 0x15137759, 0x14ff081a, 0x17a27d2a, 0x0569ea67, 0x1483bf0b, 0x1c0745cd, 0x0f137995, 0xebb1d7}}}, - /* 11*16^25*G: */ - {{{0x19135dbd, 0x0c97db2d, 0x1618c7b3, 0x010f5e73, 0x1897cf0c, 0x157ac174, 0x19ab605e, 0x00951bbd, 0xe3e475}}, - {{0x0748045d, 0x083579f2, 0x12576a5a, 0x0405febd, 0x03ffea5a, 0x040ca95c, 0x1b102e63, 0x1f013978, 0x930a5b}}}, - /* 13*16^25*G: */ - {{{0x0dee455f, 0x1f85cf2e, 0x13901d72, 0x0fffcdd1, 0x1db4aff6, 0x099c7c05, 0x06c291d1, 0x0dfd0e15, 0x7e8c65}}, - {{0x171b9cba, 0x19ef4cc0, 0x1d1989c5, 0x05a2ce8d, 0x1a53b4aa, 0x1b07a401, 0x103ca8fd, 0x0659460e, 0xbdddc6}}}, - /* 15*16^25*G: */ - {{{0x0698b59e, 0x1bcb5cdb, 0x0d11e90d, 0x06b24b12, 0x1c7260a3, 0x01ad59f1, 0x1ac56fac, 0x1f12352b, 0x3df841}}, - {{0x0b92baf5, 0x07c733cb, 0x12527e2f, 0x190cf642, 0x0f3867bf, 0x1d74788e, 0x0307680a, 0x1bf31612, 0xb38fe6}}} - }, - { - /* 1*16^26*G: */ - {{{0x0bcbb891, 0x158a8121, 0x09b2fb8e, 0x198c87be, 0x18f9a8f7, 0x0dd53a1f, 0x0f87a0a0, 0x0d607655, 0xc738c5}}, - {{0x099a84c3, 0x1f39aecb, 0x0033fa45, 0x029ddef1, 0x1bbbb823, 0x0797565f, 0x094dfdc6, 0x0f12a35a, 0x893fb5}}}, - /* 3*16^26*G: */ - {{{0x0761d58d, 0x05d5799c, 0x15838bcd, 0x1937c811, 0x0df7aca4, 0x0051ab90, 0x05184289, 0x04f047ec, 0xb8c461}}, - {{0x1d1051a4, 0x1c7505d4, 0x041f16d8, 0x0a08f0bc, 0x1c5053f7, 0x0c7b4bf9, 0x0df45291, 0x0d8a2e1c, 0x8f9ed9}}}, - /* 5*16^26*G: */ - {{{0x050b0040, 0x0d859820, 0x04d2b70b, 0x08fb73e0, 0x03671f3f, 0x04c9ff4d, 0x07de8d43, 0x13ee204e, 0x8d56e}}, - {{0x1b3fd0a1, 0x0c7133d2, 0x05e0afad, 0x0f88e41c, 0x1b285f6d, 0x0a8546bc, 0x0a887ff4, 0x15d7a153, 0xa12185}}}, - /* 7*16^26*G: */ - {{{0x13573b7f, 0x0b2b1ef8, 0x02d89c3d, 0x1438bda6, 0x05a37889, 0x07cbbdf3, 0x198d0788, 0x065a85f9, 0xdc13f2}}, - {{0x0c1f2ba6, 0x15669142, 0x1012c710, 0x0aa8e02e, 0x10a76704, 0x086d4254, 0x1030f1d0, 0x100853c6, 0xc909ba}}}, - /* 9*16^26*G: */ - {{{0x07ae9ceb, 0x017ac85c, 0x1bcb452c, 0x1843d4e1, 0x119a8226, 0x0ab2ed90, 0x1c1cebc6, 0x1cc03bef, 0x25c02d}}, - {{0x0bc6e275, 0x1848689a, 0x1961c991, 0x0c83be14, 0x111d537c, 0x0706e7d6, 0x00f03221, 0x1a590247, 0x8a9fea}}}, - /* 11*16^26*G: */ - {{{0x0c5a3c34, 0x1c04c7b7, 0x16527f87, 0x1d058052, 0x03e58aec, 0x0fa653d7, 0x1273a2ae, 0x03659f1c, 0xfedd9d}}, - {{0x013d7714, 0x15aa5fa7, 0x0fe0a5d8, 0x1d6a8d33, 0x14b00ada, 0x02989647, 0x0382c2fa, 0x18630a77, 0xa52e24}}}, - /* 13*16^26*G: */ - {{{0x0ab6a396, 0x1a72a68d, 0x04d81da6, 0x04372342, 0x088b3730, 0x16bfaf42, 0x1230b7b8, 0x10d78dd4, 0x3e0e32}}, - {{0x1980e27e, 0x1598f246, 0x11a3da8b, 0x002083e6, 0x13fc66ab, 0x1f0ef5a2, 0x1e593cc7, 0x0e5f4766, 0xca4481}}}, - /* 15*16^26*G: */ - {{{0x141023ec, 0x179ac311, 0x1b3d5c2a, 0x0bb1eedf, 0x0b9af564, 0x101004c1, 0x14a1260b, 0x06101865, 0x344ab9}}, - {{0x1e1eeb87, 0x07c4c148, 0x0e6575c1, 0x1d2ed5f8, 0x14c5ffc4, 0x1968f528, 0x18a9cfe3, 0x00856488, 0x6e1c2b}}} - }, - { - /* 1*16^27*G: */ - {{{0x08f6c14b, 0x1974fb2c, 0x0494050d, 0x0e5cbe75, 0x12877d1d, 0x03b1be4b, 0x0e078993, 0x0ca916cb, 0xd89562}}, - {{0x1d7d991f, 0x09b1f6ba, 0x0c19f85e, 0x051ac657, 0x140eb034, 0x03040c61, 0x1ab9ca3b, 0x071e578f, 0xfebfaa}}}, - /* 3*16^27*G: */ - {{{0x0127b756, 0x05d43ffb, 0x0825c120, 0x0517c957, 0x0b416034, 0x116d2830, 0x0499cb4d, 0x05ee2dbe, 0x6d8c78}}, - {{0x1f172571, 0x0a8fba55, 0x1f373299, 0x154db45a, 0x14daf4e3, 0x14169b69, 0x04445166, 0x0112dfb7, 0x99aedf}}}, - /* 5*16^27*G: */ - {{{0x158cf17a, 0x0f70d39b, 0x0208d493, 0x10bb974b, 0x097f8f1f, 0x0d778da0, 0x0b2a3416, 0x1bb2b7ef, 0xebcabe}}, - {{0x1caa0ccd, 0x0366e2fa, 0x0b3a5711, 0x15a425a1, 0x12e6b10f, 0x050db3e1, 0x072c0b00, 0x01f1e457, 0x47d3ce}}}, - /* 7*16^27*G: */ - {{{0x0c855c5b, 0x077728ad, 0x1f22beef, 0x0ac43402, 0x1fc28118, 0x0d1b4f0b, 0x189114cc, 0x05c97a99, 0xe8df4d}}, - {{0x0e465650, 0x0eaf3961, 0x07935f56, 0x076abe3c, 0x132c5966, 0x0da7acf7, 0x0c991113, 0x0e188ff3, 0x6c57fd}}}, - /* 9*16^27*G: */ - {{{0x12e7e454, 0x047aded2, 0x03985434, 0x05dfde1e, 0x01662fe3, 0x03011d4c, 0x00ca4492, 0x1ae31d95, 0x4068d3}}, - {{0x18ef191e, 0x1cd66f2e, 0x10dccc9d, 0x1a43da27, 0x138d1988, 0x0a2cbece, 0x1eaae7b0, 0x16e4a948, 0x8cd853}}}, - /* 11*16^27*G: */ - {{{0x06c5d939, 0x02bd6fc2, 0x0a4cf782, 0x0b450ef7, 0x0027ea47, 0x19973065, 0x1782d56f, 0x19b63b04, 0x12550e}}, - {{0x19e757c9, 0x153a7e2a, 0x16350c64, 0x16e83fd9, 0x04a72838, 0x121e0bb9, 0x1e9d5123, 0x069f0e5a, 0x7b8f83}}}, - /* 13*16^27*G: */ - {{{0x16c8a56f, 0x06855632, 0x1cdd084e, 0x1278a869, 0x0d08f850, 0x1bda9d7d, 0x17531a6e, 0x035876b0, 0x944d67}}, - {{0x1a7be289, 0x0fa6e32e, 0x01945fae, 0x0982e9ba, 0x0c61967d, 0x1c9b099d, 0x1ffd3050, 0x12ef6a03, 0xa71065}}}, - /* 15*16^27*G: */ - {{{0x0e08f15a, 0x175c50c5, 0x04a402eb, 0x13cadb90, 0x1c305fd6, 0x01b2ad69, 0x0833f9ac, 0x1239a133, 0x86a54e}}, - {{0x01388308, 0x09268f3e, 0x0d49534d, 0x053a24b6, 0x16867771, 0x146836ba, 0x1180e9ca, 0x0906f4f0, 0xcfee61}}} - }, - { - /* 1*16^28*G: */ - {{{0x0f676e03, 0x08a852b2, 0x1a13b752, 0x1f8e6d27, 0x08761cef, 0x1219ab8f, 0x1463ac3d, 0x006552ae, 0xb8da94}}, - {{0x0efdf6e7, 0x0447273a, 0x1fced445, 0x18b09b2b, 0x008b092c, 0x0e64bb14, 0x07935f26, 0x148900b4, 0x2804df}}}, - /* 3*16^28*G: */ - {{{0x06e1346b, 0x10cc24ee, 0x16bc717a, 0x1cf62070, 0x152c05ab, 0x1b0f8a68, 0x042f9531, 0x1fe1305a, 0x69068}}, - {{0x17226c13, 0x1dac52a6, 0x11809b9e, 0x0d127329, 0x0442aa4f, 0x0d95e843, 0x189b6a17, 0x1c1217fb, 0xb863e3}}}, - /* 5*16^28*G: */ - {{{0x1ca1f6a1, 0x07348fe6, 0x033fc68c, 0x197a2869, 0x06ce1068, 0x0e2e58f4, 0x1d854a1b, 0x127964b2, 0x898c34}}, - {{0x164f647c, 0x056e1078, 0x0af5e729, 0x0f9f2f3e, 0x06e93b2a, 0x0a122956, 0x15527611, 0x10d56ad4, 0x75f759}}}, - /* 7*16^28*G: */ - {{{0x1d1e3998, 0x134f01e1, 0x0810ca2a, 0x0d91722f, 0x0274e5e5, 0x0ceb8115, 0x0fc0694a, 0x1fda5231, 0xb213e2}}, - {{0x125fb81e, 0x0165ee31, 0x17b45d7b, 0x082cb7d7, 0x03bc3d53, 0x0f5fc1d2, 0x104b0f58, 0x1841e5a7, 0x229f8e}}}, - /* 9*16^28*G: */ - {{{0x025be234, 0x05f0ff65, 0x17cd41c0, 0x07c7ce1b, 0x06e060e8, 0x05d348b0, 0x04f97474, 0x1b02d8ff, 0x4b3b3a}}, - {{0x120e8362, 0x11dbf01c, 0x0846e101, 0x0ffb259e, 0x0c04c41e, 0x14b6fec6, 0x1271e1d7, 0x0770bb57, 0x5eec02}}}, - /* 11*16^28*G: */ - {{{0x0289f55e, 0x0b31a53f, 0x11d9c1ee, 0x0d61e1c5, 0x165be297, 0x08d813c4, 0x1e580809, 0x16dbb609, 0x9f7b88}}, - {{0x1e1e4bde, 0x1e69db2f, 0x0428ac6c, 0x0a6dc1d6, 0x1c71fc0e, 0x035a14c7, 0x03def8c5, 0x1098e082, 0x32f9f7}}}, - /* 13*16^28*G: */ - {{{0x1dfe1d2b, 0x12a4e460, 0x1a1e3945, 0x07ea13ca, 0x173a4c49, 0x056fe9fd, 0x038f9db3, 0x1d396e89, 0xd58a43}}, - {{0x0cd50922, 0x0793cadc, 0x0f5befff, 0x137442b9, 0x0276b54d, 0x14899414, 0x0f3c429c, 0x0d740b10, 0xfc1786}}}, - /* 15*16^28*G: */ - {{{0x049c795e, 0x120a8df1, 0x13a01784, 0x080d5533, 0x1ea4eed4, 0x0b4e1e13, 0x0c4335b6, 0x072e2230, 0x21d271}}, - {{0x19208ece, 0x0009761c, 0x17edd86c, 0x03289495, 0x1b4c3d67, 0x0dc2a915, 0x13a85dcd, 0x16960eb5, 0x94c5f9}}} - }, - { - /* 1*16^29*G: */ - {{{0x03c0df5d, 0x0d08bbc7, 0x15a9e4bc, 0x13dff6a2, 0x17fab201, 0x0d5ca3ae, 0x0ce9f62b, 0x028883f6, 0xe80fea}}, - {{0x0ac9ec78, 0x05a148db, 0x0c8baa7f, 0x0abd015e, 0x094472d1, 0x1b4651e5, 0x01dc7a25, 0x0fec71c0, 0xeed1de}}}, - /* 3*16^29*G: */ - {{{0x17592d55, 0x001acf66, 0x18d4064b, 0x0b728e81, 0x0e3b106d, 0x17b4b19e, 0x149822bf, 0x1b789420, 0x5d2ec6}}, - {{0x0f5183a7, 0x1372e875, 0x04545d09, 0x14f79b5a, 0x0d6950e2, 0x087d1346, 0x17ad63dc, 0x1f138dc8, 0xa92cd}}}, - /* 5*16^29*G: */ - {{{0x1e8f9f5c, 0x08fa5a4f, 0x02029466, 0x03e3c2b3, 0x1404d736, 0x171a10af, 0x1d0bf8b2, 0x1876237e, 0xac371d}}, - {{0x125a503c, 0x1d41ff99, 0x1d478745, 0x0a68b1dc, 0x0e735229, 0x00f3992a, 0x11dffc84, 0x1830e134, 0xc51616}}}, - /* 7*16^29*G: */ - {{{0x19e33446, 0x050e46a8, 0x0bce177d, 0x127788a5, 0x0a17a408, 0x005e8111, 0x10324d23, 0x07429e30, 0x894200}}, - {{0x06387689, 0x069c5007, 0x19d3e610, 0x1aee6cf3, 0x1e4e06bf, 0x16b6877e, 0x1de9362c, 0x12b2b4a0, 0xa9fd03}}}, - /* 9*16^29*G: */ - {{{0x1913cb26, 0x0b9464ad, 0x0ef5b40f, 0x16833802, 0x05c9899e, 0x1227faa8, 0x0aa28b36, 0x0d661468, 0x277026}}, - {{0x1348a7a2, 0x1f38b99f, 0x0056faef, 0x01923799, 0x0b324e94, 0x092683f9, 0x0c69554b, 0x0bcf361b, 0xf649bc}}}, - /* 11*16^29*G: */ - {{{0x195e8247, 0x0555010a, 0x01b346bc, 0x1fb88aad, 0x0ba9097b, 0x13700e7c, 0x1485e397, 0x1a70797d, 0x75e4d0}}, - {{0x19982d22, 0x111fecea, 0x06b624f2, 0x156b6dd5, 0x126d47dd, 0x0b8763db, 0x0641d07e, 0x142ea821, 0x1fce42}}}, - /* 13*16^29*G: */ - {{{0x06333323, 0x03cfa26d, 0x1d2afd1d, 0x177838d1, 0x0da849cb, 0x06b02cc2, 0x0fc0fc08, 0x07066c37, 0x3ed1b6}}, - {{0x15d61ba3, 0x189fe245, 0x1e3dca52, 0x0e514216, 0x1929ea9b, 0x04c4b447, 0x1b9d765f, 0x14916b69, 0xd84f2d}}}, - /* 15*16^29*G: */ - {{{0x133980bf, 0x1282bea5, 0x17402ebc, 0x06e05ca1, 0x0dd4368a, 0x1ebb91a4, 0x0606e11b, 0x1e0d4eb0, 0x70fdd2}}, - {{0x00b75785, 0x17754675, 0x15d29584, 0x006b070b, 0x0596b0a1, 0x008688f7, 0x1a5a55e9, 0x181a1ab0, 0x5edfca}}} - }, - { - /* 1*16^30*G: */ - {{{0x04e16070, 0x0e03dde6, 0x1f5a4577, 0x0304063d, 0x07543f2a, 0x04728eab, 0x010c4ee9, 0x0f7bf9ae, 0xa30169}}, - {{0x1e177ea1, 0x0068d020, 0x084684c3, 0x0bb7ef81, 0x00f9b173, 0x04fd12ea, 0x13d42060, 0x039f6cfc, 0x7370f9}}}, - /* 3*16^30*G: */ - {{{0x138011fc, 0x18093800, 0x1ca15899, 0x12d4cf5a, 0x00a4d835, 0x09984110, 0x0c4455ac, 0x146102bd, 0x6e8313}}, - {{0x1f15ab7d, 0x165b4fd1, 0x1147e69a, 0x1f22b5d3, 0x0c30426a, 0x16d900ed, 0x08130684, 0x117b849e, 0xc14781}}}, - /* 5*16^30*G: */ - {{{0x100e6ba7, 0x1d3a4dc6, 0x045bdfd4, 0x0dd8b689, 0x1e1b43d3, 0x101c526c, 0x147caf47, 0x0132f090, 0xf952a9}}, - {{0x0175e4c1, 0x0dd77728, 0x18a8ae63, 0x0e2cf698, 0x1a0f6555, 0x1b51713f, 0x1afe184d, 0x0b611579, 0xd8a93a}}}, - /* 7*16^30*G: */ - {{{0x03aa0e93, 0x08032d14, 0x1ec7d89a, 0x1c72875d, 0x0893a8f2, 0x18d0cecf, 0x1b9d4100, 0x0bc63a7f, 0x94016d}}, - {{0x07addac2, 0x07769344, 0x15ec1e8e, 0x086e7754, 0x06fd7f48, 0x0e9aa777, 0x165900d5, 0x1dcb88a9, 0x675032}}}, - /* 9*16^30*G: */ - {{{0x0266b17b, 0x07a43170, 0x18aeccac, 0x0ad14404, 0x109c2023, 0x1c42354f, 0x0a246ee5, 0x0e9ab3f6, 0xef22d1}}, - {{0x19dac83e, 0x1537021b, 0x10d06dcc, 0x0e4edee3, 0x0a1073ee, 0x0661d71a, 0x11d5a3e7, 0x192f5649, 0xbc5784}}}, - /* 11*16^30*G: */ - {{{0x12d382a0, 0x18980ad4, 0x1b366b88, 0x1b9779c5, 0x1f927f28, 0x063c0596, 0x04b4e72b, 0x19c99d71, 0xb5f7ef}}, - {{0x05b4b532, 0x117855dd, 0x0b3e316e, 0x1612da53, 0x1ddd371f, 0x0be37065, 0x08d4f025, 0x0b6a387e, 0x684354}}}, - /* 13*16^30*G: */ - {{{0x012cffa5, 0x13492322, 0x0331711f, 0x1a8410cd, 0x0624389e, 0x0a6c7dea, 0x01d9021d, 0x1a565ce2, 0x1cddc3}}, - {{0x1521954e, 0x0f36c4e6, 0x0dad4a2b, 0x193084d6, 0x0b08ac41, 0x0935fca1, 0x0298ff6c, 0x01965e3f, 0x1e476a}}}, - /* 15*16^30*G: */ - {{{0x14a9f22f, 0x1aff21c9, 0x1ea38ab4, 0x10338a42, 0x035b0cc0, 0x05c5ca44, 0x04e7c87e, 0x0b3e4b9d, 0x2accb3}}, - {{0x175c4927, 0x1baee59d, 0x0e9542de, 0x17af7d8b, 0x0edf1154, 0x1d1bf6f8, 0x0b946484, 0x1d2b115a, 0xd518a4}}} - }, - { - /* 1*16^31*G: */ - {{{0x1fb04ed4, 0x1bd631f1, 0x0c1fffea, 0x18661622, 0x18de208c, 0x0e828933, 0x04d918fe, 0x16713ad7, 0x90ad85}}, - {{0x0b6ef150, 0x08ea6a46, 0x00a25366, 0x1df57c2b, 0x022b839a, 0x05eca139, 0x0986bff7, 0x06c41470, 0xe507a}}}, - /* 3*16^31*G: */ - {{{0x10b7b678, 0x13aed99d, 0x1d8e0598, 0x18862379, 0x16c76f13, 0x15e52135, 0x0c6e8661, 0x0e669c84, 0x186e49}}, - {{0x18d91fc1, 0x1d03b797, 0x054d7729, 0x0ee44a89, 0x1e67e110, 0x0412e05b, 0x1612a9ff, 0x1c9300f7, 0xc0d460}}}, - /* 5*16^31*G: */ - {{{0x13421fb8, 0x18372e5d, 0x16957433, 0x12e3e5de, 0x12412984, 0x159a61db, 0x1d8b9f81, 0x1069edb7, 0x61c8d}}, - {{0x0e3ccd80, 0x0af7b342, 0x1bf374a6, 0x0e269674, 0x1eb5c806, 0x092c8702, 0x12deea4e, 0x1b320076, 0x6dfc6a}}}, - /* 7*16^31*G: */ - {{{0x0f6b35a4, 0x0925f0c5, 0x09fed21c, 0x1e6f4d56, 0x068ad889, 0x1920399d, 0x144edcd8, 0x074411dc, 0xf6a6b6}}, - {{0x01f422a6, 0x175b7f64, 0x1b8618b2, 0x0aeadceb, 0x0186f19d, 0x1b827ab0, 0x0e2c72b4, 0x150005a2, 0x3df7c8}}}, - /* 9*16^31*G: */ - {{{0x06954c11, 0x0b411f45, 0x1834062e, 0x1148782a, 0x178ff7fa, 0x0d878a83, 0x0dd88834, 0x051850d8, 0x87a2fc}}, - {{0x072a8b45, 0x0b719971, 0x1e0492dd, 0x11267e54, 0x07532cc4, 0x0a46d069, 0x13be5ec6, 0x1168b55d, 0x33ad51}}}, - /* 11*16^31*G: */ - {{{0x02706ab6, 0x123a3957, 0x194f036b, 0x16683ba5, 0x04cfe3c0, 0x177e5e1c, 0x069a1155, 0x008dcf10, 0xe1472e}}, - {{0x1d58de05, 0x174350b4, 0x0f349d4d, 0x113aaa8a, 0x021f8aa5, 0x08cbc643, 0x1f1a0fda, 0x1548f8b1, 0x82cd92}}}, - /* 13*16^31*G: */ - {{{0x07a84fb6, 0x1fd72a10, 0x0854087a, 0x06d0ea1f, 0x0b9ebc42, 0x06b00f24, 0x1bd77a2d, 0x19009f15, 0x1caf92}}, - {{0x07149109, 0x158c0c81, 0x0b399d85, 0x1982d2d4, 0x01622ec1, 0x127c7f88, 0x14e92069, 0x0b592edc, 0xbc24b8}}}, - /* 15*16^31*G: */ - {{{0x0a955911, 0x1b467f0a, 0x17b54b6d, 0x1c2d44c1, 0x18397107, 0x17f4d9eb, 0x14349627, 0x0d35e12c, 0x705bfd}}, - {{0x1fd200e4, 0x1dbe349f, 0x10b9cb62, 0x1e76a454, 0x051fa297, 0x1ec0faa0, 0x06429f98, 0x02616e7f, 0xe14aa4}}} - }, - { - /* 1*16^32*G: */ - {{{0x1ec4c0da, 0x1bda2264, 0x0fa8cd46, 0x18acf0e4, 0x1162ee88, 0x00d6cc0f, 0x1cce48e7, 0x1a5ec76b, 0x8f68b9}}, - {{0x101fff82, 0x11e5fbca, 0x1442ff7c, 0x1459fd2a, 0x0215dbbe, 0x08615b5f, 0x061b7876, 0x05b740c7, 0x662a9f}}}, - /* 3*16^32*G: */ - {{{0x123809fa, 0x0715c76e, 0x16552f86, 0x08b966a3, 0x11f08fd8, 0x19b1f922, 0x1c8a2ea4, 0x17c5ca13, 0x38381d}}, - {{0x131fed52, 0x0b83a8c1, 0x163c936f, 0x03f99665, 0x0b1cc368, 0x02d2a907, 0x1f72c250, 0x0141f722, 0xe4a32d}}}, - /* 5*16^32*G: */ - {{{0x17c2a310, 0x15213244, 0x04898c0f, 0x0d5d4a80, 0x099a1f18, 0x0dc15523, 0x0b9bda48, 0x049c86e5, 0x492627}}, - {{0x1e27ded0, 0x020db40a, 0x17fe3383, 0x0c6c254e, 0x0303b6d1, 0x1d2b4b8a, 0x0fe568b3, 0x0e7794f5, 0x1337e7}}}, - /* 7*16^32*G: */ - {{{0x0ebd2d31, 0x1c2583ce, 0x01b6e344, 0x1834adfe, 0x1e2f84dc, 0x09d9f23b, 0x12435789, 0x11834481, 0xe30656}}, - {{0x12546e44, 0x095a041c, 0x0dce099a, 0x1900857c, 0x10db6ffb, 0x15883fbe, 0x0982223c, 0x1c6f1268, 0xeac6f}}}, - /* 9*16^32*G: */ - {{{0x163136b0, 0x09861cf1, 0x0d077671, 0x17f1b355, 0x1d63374e, 0x073b11fd, 0x1bf09c6c, 0x01c48519, 0x3b9e10}}, - {{0x0cdbbc8a, 0x09f60b7b, 0x14c7e065, 0x1c514675, 0x15b26a2a, 0x19f5c7a3, 0x0dc77c54, 0x02a5a2d7, 0xfafb98}}}, - /* 11*16^32*G: */ - {{{0x0f485d3f, 0x10478239, 0x01efbba5, 0x140ed102, 0x0def717c, 0x05407aef, 0x06a4addb, 0x092e2559, 0xbb0aad}}, - {{0x1ca2f975, 0x1c9c9281, 0x19c2fff9, 0x14b5f462, 0x1da34895, 0x100fb94b, 0x11e63b34, 0x0a78b06a, 0xea699c}}}, - /* 13*16^32*G: */ - {{{0x16718dc9, 0x177699d1, 0x0448f792, 0x0b169b60, 0x00113e1e, 0x158cbd7f, 0x130353a3, 0x191c9ddf, 0x79090a}}, - {{0x1cfae7c5, 0x11991588, 0x0a4022e5, 0x0d5f6e17, 0x0aa56dd3, 0x0b65e6cd, 0x0e3c4f60, 0x0572320b, 0xeaab72}}}, - /* 15*16^32*G: */ - {{{0x1f60c7d1, 0x134b4a63, 0x1dd6b4a8, 0x0e3bcf9a, 0x1ba668dd, 0x0dde72a4, 0x0d54700f, 0x15bd3f2f, 0xe77c81}}, - {{0x02d72449, 0x162c0f94, 0x0a61b4d3, 0x08e1ee38, 0x01543631, 0x1d991f54, 0x0c8717f0, 0x0f1ddf02, 0x3acf14}}} - }, - { - /* 1*16^33*G: */ - {{{0x13231e11, 0x1437ea82, 0x1a078f99, 0x11d0ca06, 0x036091f4, 0x0ffc8cc6, 0x17597fe6, 0x002ed5f0, 0xe4f3fb}}, - {{0x0feb73bc, 0x1161c2bb, 0x14747260, 0x0fce9d92, 0x0b7286cc, 0x13687501, 0x1c705986, 0x075a1de9, 0x1e6363}}}, - /* 3*16^33*G: */ - {{{0x0bf05bd6, 0x1ccf8273, 0x0aa65194, 0x0adc0642, 0x10deca2f, 0x1a8ff5a3, 0x1fa420cb, 0x0837dc89, 0x900c32}}, - {{0x100d358b, 0x129569df, 0x13bec577, 0x10a6b078, 0x12439d69, 0x19022b85, 0x03d7e571, 0x1d1d163e, 0x6c31f9}}}, - /* 5*16^33*G: */ - {{{0x1f105c50, 0x13e14664, 0x04e1495e, 0x01bddef6, 0x033cd82e, 0x061a01e1, 0x02ab58a3, 0x0c5560b2, 0x5a8d03}}, - {{0x18a4cde9, 0x04d900c1, 0x1404f0d7, 0x0bed14cd, 0x1ff74a60, 0x15b920a1, 0x14da4da9, 0x16227a9c, 0xc059ea}}}, - /* 7*16^33*G: */ - {{{0x12d64feb, 0x03f0c5cd, 0x048a4b19, 0x05f14b25, 0x1a4e8377, 0x1d2bbb65, 0x182923bc, 0x0062465e, 0xd93f4d}}, - {{0x14698359, 0x187deac9, 0x124368de, 0x008617dd, 0x08c4d93e, 0x188e2a6e, 0x1cce88dc, 0x0ba8b964, 0x792555}}}, - /* 9*16^33*G: */ - {{{0x039fbc84, 0x1110266a, 0x15e8059c, 0x00c522c0, 0x0c65b7e7, 0x115e3315, 0x01106c53, 0x18dc6de5, 0x2f0769}}, - {{0x1c201bec, 0x1dc816f0, 0x137575cf, 0x0f36d498, 0x02149cca, 0x1803cc87, 0x1777e977, 0x0e49ae77, 0xb434f3}}}, - /* 11*16^33*G: */ - {{{0x06a758ea, 0x09bf5664, 0x1fc67135, 0x11063124, 0x16e39911, 0x04ad0aa0, 0x0c26561a, 0x100ab3c0, 0xfe7e67}}, - {{0x1b7ab649, 0x07916cae, 0x0c483479, 0x002e0e88, 0x1251f3b8, 0x070b4c24, 0x12e62302, 0x0cf4503b, 0x38aa69}}}, - /* 13*16^33*G: */ - {{{0x0cfffefa, 0x138ab134, 0x1946beb7, 0x10089ee0, 0x1af85101, 0x17c8a861, 0x049f5b7d, 0x194ea706, 0x91baf5}}, - {{0x1f7f6faf, 0x1e9b79a6, 0x1f4c0f71, 0x1de621cd, 0x13d92f4b, 0x14f893ee, 0x13af9765, 0x023268f7, 0x4e5cf}}}, - /* 15*16^33*G: */ - {{{0x0d33c546, 0x1e048bf4, 0x17b6bccb, 0x0ebf6650, 0x1ddd7825, 0x09b9f07a, 0x029f2cb1, 0x043967ef, 0x445841}}, - {{0x000b4fd4, 0x05368c38, 0x0b29cd98, 0x1e1479f6, 0x0da20852, 0x0f571c8d, 0x14e9dc89, 0x0efe7f0e, 0x308d93}}} - }, - { - /* 1*16^34*G: */ - {{{0x00eae29e, 0x17db7571, 0x138741c5, 0x069e5e1a, 0x04266c70, 0x0a9bd22d, 0x0cc7ae58, 0x13631d7e, 0x8c00fa}}, - {{0x0702414b, 0x1e952633, 0x18db1539, 0x15b5f503, 0x0c974c2f, 0x1a1d1b9b, 0x0686a770, 0x0cffd4a4, 0xefa472}}}, - /* 3*16^34*G: */ - {{{0x0bfd913d, 0x1fcab01f, 0x15327ab0, 0x0d01cddc, 0x08d2050a, 0x1d042615, 0x17e1d341, 0x14fd20fb, 0x36362a}}, - {{0x052e243d, 0x027cd756, 0x0cbeabf1, 0x017621ad, 0x02a82d83, 0x1221b86d, 0x1f54d058, 0x0ced9715, 0x48f278}}}, - /* 5*16^34*G: */ - {{{0x0d132896, 0x055d5fcd, 0x0f1b259f, 0x03c7756f, 0x1200dfcb, 0x16cb16ec, 0x180bca56, 0x0dbe6543, 0x448797}}, - {{0x0f685248, 0x0600d895, 0x1daa9e92, 0x081ab4c4, 0x01a3306b, 0x1483ba2b, 0x1f87bf26, 0x0c1a22b5, 0x27bd58}}}, - /* 7*16^34*G: */ - {{{0x1fa2670c, 0x040ab7b5, 0x189cf9b8, 0x01f05740, 0x10324740, 0x0e55419a, 0x0de22daa, 0x18517970, 0x4a4d3a}}, - {{0x16c1764d, 0x045cffee, 0x0a6fbb60, 0x00b2997e, 0x02b9d493, 0x188eef78, 0x093c5f9d, 0x0380308b, 0x70abb9}}}, - /* 9*16^34*G: */ - {{{0x0cb24aa7, 0x0cb57f12, 0x0d1714c3, 0x02118c72, 0x0fe18903, 0x17ec4ffc, 0x000b0eb9, 0x03215d23, 0x5f7b2d}}, - {{0x0a693d7d, 0x0bc03a1a, 0x1207431b, 0x1bd6e127, 0x07c47ce8, 0x1c051e73, 0x0accc28f, 0x00189fbe, 0x77036}}}, - /* 11*16^34*G: */ - {{{0x0f7fb8bd, 0x0c8de2f9, 0x19024290, 0x08c942bf, 0x086d87bc, 0x12736ca4, 0x0340abb0, 0x0a2673b2, 0x513974}}, - {{0x03908c0f, 0x04a5616c, 0x1e6356a0, 0x1865a7c7, 0x15c47faf, 0x1f7ed740, 0x0fcd2e23, 0x07c8ec87, 0xfcd714}}}, - /* 13*16^34*G: */ - {{{0x0e36ca73, 0x15ba48af, 0x0efcfb78, 0x10c9a6d7, 0x14887eac, 0x08895f80, 0x1ee2a90a, 0x1ac57f7b, 0xcf8316}}, - {{0x0ec25534, 0x1a490ca1, 0x035c43d6, 0x0c2b31b1, 0x0ca681c9, 0x0284486e, 0x15cf8e11, 0x11bd6bb3, 0x9feb5}}}, - /* 15*16^34*G: */ - {{{0x1752f97d, 0x0449beb5, 0x0d1d984f, 0x1df78ebe, 0x1a6165a2, 0x09433467, 0x127794e7, 0x13498976, 0x8610de}}, - {{0x1f1b1af2, 0x02bee6a0, 0x1550f820, 0x169b3ea8, 0x1f99e57a, 0x0ccd9299, 0x0ef24df3, 0x14056c61, 0xd31997}}} - }, - { - /* 1*16^35*G: */ - {{{0x00cb3e41, 0x0bfeefe3, 0x02e4b026, 0x1a109e61, 0x18ed3143, 0x076054f4, 0x0a7cf843, 0x1cd3ba90, 0xe7a26c}}, - {{0x0f2cfd51, 0x1454a10e, 0x03a0f883, 0x0d658084, 0x1bb18d0a, 0x00350d57, 0x012d1c6c, 0x0601f4f3, 0x2a758e}}}, - /* 3*16^35*G: */ - {{{0x1aee42db, 0x07cfe95f, 0x0c1c529c, 0x1778b68e, 0x0bfc1d9b, 0x176dc8f6, 0x0543f1ed, 0x1cfb36b2, 0xcc3427}}, - {{0x15d87bdb, 0x1114e008, 0x1c908b71, 0x0b975b1c, 0x1520010e, 0x1fe9fd90, 0x1a862178, 0x0834a438, 0xea2498}}}, - /* 5*16^35*G: */ - {{{0x1ed4a086, 0x1a1b3633, 0x071043bf, 0x0eb82b1d, 0x15cf4b1d, 0x02c2fde5, 0x1177b20f, 0x1759b308, 0x948f05}}, - {{0x1e2bca4b, 0x150c007c, 0x0fe8b468, 0x06514e38, 0x139411c2, 0x08533008, 0x08ce0bd1, 0x13ff6b45, 0x864ca8}}}, - /* 7*16^35*G: */ - {{{0x17542c21, 0x065f5365, 0x09930570, 0x13e9a51d, 0x16d43ae1, 0x1e22a28e, 0x0b24195b, 0x0c525233, 0x258419}}, - {{0x072bfabf, 0x1f5f18cb, 0x10ab5ece, 0x07430dc9, 0x113d5f3e, 0x0d52663a, 0x11200797, 0x03e39b64, 0xfcb35b}}}, - /* 9*16^35*G: */ - {{{0x0c1ecbf8, 0x1e230fa0, 0x1bb5f290, 0x13e1bd35, 0x0421f648, 0x1aa660f4, 0x14948aa5, 0x18826e78, 0x7e12cd}}, - {{0x10bed615, 0x0a2dc66d, 0x18767d67, 0x13ec3b1f, 0x11259c96, 0x0a6d5f26, 0x00dc50fe, 0x111111b9, 0x71284f}}}, - /* 11*16^35*G: */ - {{{0x14557d86, 0x1f3328e0, 0x199ffd05, 0x1dd88f1c, 0x1a6cf1cf, 0x08e53d02, 0x0a99dcae, 0x1fe546e8, 0x4b8ec2}}, - {{0x15167eb9, 0x0ecd8c8d, 0x10fda4af, 0x0be5de1f, 0x1ac5f28d, 0x0396f293, 0x1eac5290, 0x1fe0982a, 0xfde6c3}}}, - /* 13*16^35*G: */ - {{{0x0780763c, 0x15c169da, 0x195a4754, 0x14dabd24, 0x0c07e5f8, 0x1b6e34bd, 0x09094c90, 0x00e672c7, 0xfcd5c1}}, - {{0x18e851cb, 0x0a73a101, 0x1918e92d, 0x13645ce2, 0x0e38cb11, 0x06d9afb9, 0x1118edc8, 0x1c5caa45, 0x18ddab}}}, - /* 15*16^35*G: */ - {{{0x1d8ef686, 0x071df182, 0x09cb99af, 0x1c91e804, 0x06e53f68, 0x12ed7c13, 0x0f9488e2, 0x1dcb0879, 0x900f2c}}, - {{0x0121a8cf, 0x19d24b3f, 0x0455b541, 0x19bfe879, 0x1d110596, 0x0a8d89a4, 0x096b5871, 0x0abd8c08, 0x732ac1}}} - }, - { - /* 1*16^36*G: */ - {{{0x1e6b80ef, 0x0794f59e, 0x1e5093cf, 0x17972cfa, 0x0bdc571c, 0x006111de, 0x1b2348d5, 0x01dc6cc5, 0xb6459e}}, - {{0x1a71ba45, 0x185f85b0, 0x18d6cbfc, 0x075cda91, 0x01db3c4b, 0x0f8b72b3, 0x01b7876b, 0x0da0de7c, 0x67c87}}}, - /* 3*16^36*G: */ - {{{0x119888e9, 0x1ce793c9, 0x1122a2d0, 0x0574d7e4, 0x081673d1, 0x069814b3, 0x1b8b7798, 0x0ee75874, 0x1f90ea}}, - {{0x0f113b79, 0x17efe4bf, 0x0548b995, 0x0ea3fdcb, 0x196a8213, 0x09e938f5, 0x043a5605, 0x0f82bb54, 0x89be36}}}, - /* 5*16^36*G: */ - {{{0x1562222c, 0x02f7db79, 0x19bcb182, 0x0688f323, 0x152bade0, 0x15d699a5, 0x02b5b9c0, 0x09bdbffc, 0x13a4e5}}, - {{0x08200145, 0x058b3465, 0x12413023, 0x138aef5b, 0x09a52d4f, 0x017c0eb0, 0x004ecb2b, 0x09cb02dd, 0xc9d67d}}}, - /* 7*16^36*G: */ - {{{0x143b46bb, 0x1bf26e07, 0x12494950, 0x1a74c7f5, 0x15dbd12e, 0x1e02ec22, 0x0b747501, 0x17e46795, 0x61991e}}, - {{0x0c20a848, 0x047ac80e, 0x0bb363bd, 0x10e5394a, 0x1adf11ca, 0x1c38b37d, 0x124a54bc, 0x011e7fbc, 0x1c5e3}}}, - /* 9*16^36*G: */ - {{{0x121add3b, 0x12df1eee, 0x1c9f63df, 0x15289e8a, 0x026118b9, 0x0e6d868b, 0x0e1d240e, 0x1496f0fa, 0xea27ae}}, - {{0x168ce7dd, 0x1af148ed, 0x1386f9c6, 0x0425ad1c, 0x02f6278b, 0x0759192e, 0x1f795c8f, 0x1cdc8542, 0xc70ff1}}}, - /* 11*16^36*G: */ - {{{0x011ff757, 0x1457c5db, 0x13089b9b, 0x19d2e838, 0x0b6da9b4, 0x087c5b71, 0x1552ea40, 0x06ad6fff, 0x594651}}, - {{0x094d031a, 0x05337654, 0x0fff8eca, 0x1778f6ff, 0x006f9961, 0x1c680ae2, 0x1d401080, 0x019cbbe4, 0x361136}}}, - /* 13*16^36*G: */ - {{{0x036160b5, 0x1b12c51b, 0x19faf019, 0x0a7e48e1, 0x11ec0ccc, 0x17bcb804, 0x0a43a10e, 0x0722bee6, 0x16b26e}}, - {{0x18a1dc0e, 0x0312fdfa, 0x0e8fbe05, 0x0c7558e1, 0x054d4e13, 0x01a9231b, 0x1e2ed8d9, 0x0b4c605d, 0x60f56}}}, - /* 15*16^36*G: */ - {{{0x17b32db8, 0x0b269449, 0x11de47ce, 0x1dae93ec, 0x0ea85c91, 0x0a1e4216, 0x1e4c6fa8, 0x12b88ab3, 0x24b52}}, - {{0x0a64f760, 0x0a2a7d55, 0x16e06a56, 0x16d02240, 0x05be862a, 0x1410e62f, 0x0271edb8, 0x11eb7fe6, 0x609fef}}} - }, - { - /* 1*16^37*G: */ - {{{0x096943e8, 0x16d07ada, 0x1cf16977, 0x1e3f8cfc, 0x106231d6, 0x1a5508c7, 0x0101e4c8, 0x19050177, 0xd68a80}}, - {{0x0b133120, 0x0a642133, 0x114a568a, 0x1cf71ef0, 0x0e28b5b0, 0x0fc8bbd8, 0x1b40312c, 0x1ffe96b0, 0xdb8ba9}}}, - /* 3*16^37*G: */ - {{{0x0ca1c4f9, 0x0da1550c, 0x0df8a08d, 0x1dfc6995, 0x116f44f4, 0x1c1ed30f, 0x0a313102, 0x11e457ae, 0x7815f7}}, - {{0x1778bc15, 0x158f51b5, 0x1df47866, 0x085bcc2a, 0x0c35d5cb, 0x1e798a2c, 0x1da9f764, 0x1d19a735, 0xc1c601}}}, - /* 5*16^37*G: */ - {{{0x0e8c8530, 0x09370e1f, 0x03d3639b, 0x1bed03df, 0x06c6d512, 0x1e3200b5, 0x005db8dd, 0x19b41d88, 0xc39273}}, - {{0x198446c7, 0x0018787b, 0x1bb5c571, 0x1bf97b45, 0x1199850e, 0x09ca20e1, 0x123a7407, 0x084ae867, 0x8c41be}}}, - /* 7*16^37*G: */ - {{{0x037a26c1, 0x0f9205d9, 0x16fda94c, 0x18dcb181, 0x03b25166, 0x1218e0e8, 0x06c09d48, 0x08feb082, 0xda3174}}, - {{0x0cf74d6f, 0x08c1b767, 0x0a05497d, 0x106d8baf, 0x1d8b7d36, 0x00b3e12c, 0x11a748e1, 0x170febb1, 0x753b97}}}, - /* 9*16^37*G: */ - {{{0x1739dc49, 0x15b14549, 0x1d5580f4, 0x0725b4cd, 0x1231a239, 0x162845ff, 0x19b04192, 0x196055e1, 0x6a4be6}}, - {{0x12edd5cf, 0x13515cef, 0x1292934f, 0x1c569962, 0x08058ab7, 0x1371b07d, 0x1d65f705, 0x15455120, 0xf15d8f}}}, - /* 11*16^37*G: */ - {{{0x06987fac, 0x05f45062, 0x0a1bd9e6, 0x121f0c81, 0x18a3c8bc, 0x01301f64, 0x1c4d13a6, 0x13e275cf, 0x1f7c6}}, - {{0x19174c68, 0x1c8f39c0, 0x1daf098f, 0x1ebbc433, 0x0b6f9cb7, 0x01b7194d, 0x08b796c4, 0x07e6dfb4, 0x9d4ecc}}}, - /* 13*16^37*G: */ - {{{0x16daba4d, 0x099d0deb, 0x0c658987, 0x0bf61c40, 0x02bcb9c6, 0x1176ec7d, 0x0e072dc1, 0x002ec3fa, 0x557e94}}, - {{0x17a52316, 0x09ba50dd, 0x10d64294, 0x1371ebf4, 0x0bc86c5e, 0x0e7d0ae6, 0x1f811b4d, 0x074c03f4, 0x7a7e8f}}}, - /* 15*16^37*G: */ - {{{0x09f5341a, 0x1c7a97f9, 0x058dae00, 0x0f4658cd, 0x101419cc, 0x0a0753d8, 0x16a4eca7, 0x10433310, 0x3adada}}, - {{0x0586c6cc, 0x08ac049b, 0x17a3ef2d, 0x0da5cb68, 0x11017e9d, 0x1adf9b55, 0x1a7d54b8, 0x04513326, 0xbfea1e}}} - }, - { - /* 1*16^38*G: */ - {{{0x028d3d5d, 0x04acc07e, 0x11273a90, 0x055d72e6, 0x030b0961, 0x0138483d, 0x01094b70, 0x0fbecb90, 0x324aed}}, - {{0x16ab7c84, 0x1391257c, 0x0cca10e5, 0x027618fc, 0x01f4f192, 0x0061ad76, 0x1cbfc4c3, 0x0aee96c3, 0x648a36}}}, - /* 3*16^38*G: */ - {{{0x0fd53ed3, 0x0e48bac1, 0x095b33bd, 0x1ee9f73b, 0x17c49163, 0x105c98ef, 0x0ab56e3d, 0x1ab32cee, 0x20840b}}, - {{0x1a7a7132, 0x18a1ff28, 0x19c22661, 0x0f88e729, 0x0fac2548, 0x0a3b535d, 0x090d21ef, 0x12f9d830, 0xf29934}}}, - /* 5*16^38*G: */ - {{{0x08a35b35, 0x1e965dac, 0x028487b6, 0x0bb114b8, 0x0ebfd1ab, 0x0814f2c4, 0x06eef44f, 0x1ec1d667, 0xe6b6bf}}, - {{0x1c1007bd, 0x0b949edc, 0x1a6671f1, 0x16d93a77, 0x161ddfe3, 0x01f1c1ac, 0x0bcc99bd, 0x17a6601a, 0x1a5ff2}}}, - /* 7*16^38*G: */ - {{{0x00360dd3, 0x0a77c696, 0x14388243, 0x11506db0, 0x0e3bb47a, 0x1c043706, 0x06ca22c2, 0x0e8b7c93, 0xe05317}}, - {{0x1c24f87b, 0x15766c89, 0x040f70ac, 0x130fbd30, 0x0a01461b, 0x0ac15adb, 0x1ce73602, 0x0e34bb25, 0xdc1c3b}}}, - /* 9*16^38*G: */ - {{{0x1098dfea, 0x00a33316, 0x099e1e5f, 0x1967925d, 0x05e57fad, 0x12e9541d, 0x11678063, 0x074ef10d, 0xa8153b}}, - {{0x0e6d892f, 0x124d4efb, 0x16bd0562, 0x0bc1ee85, 0x13e03b1b, 0x0dce2bc2, 0x03f14f63, 0x0c8c3a0c, 0x2a4739}}}, - /* 11*16^38*G: */ - {{{0x0bcecfa5, 0x13f3bd24, 0x05aec082, 0x1ac5b436, 0x0da3b2a0, 0x14ae31c7, 0x176b7ad1, 0x1661fd95, 0x4f05c3}}, - {{0x15d37b53, 0x16681254, 0x06d2334b, 0x1dc863e0, 0x134b9447, 0x191b0aca, 0x09beb758, 0x1d4c07a8, 0x53a499}}}, - /* 13*16^38*G: */ - {{{0x084b96aa, 0x10f3b2c8, 0x0aaf3391, 0x130c6aa4, 0x1980ed02, 0x02f0d51d, 0x046f8990, 0x1733ecf5, 0xd9309a}}, - {{0x02b28a86, 0x136279be, 0x13fa5e3c, 0x0d93f75f, 0x0cb5fd3b, 0x0783313a, 0x155f5f84, 0x055369d8, 0x6ef99b}}}, - /* 15*16^38*G: */ - {{{0x1f8fcf0e, 0x1bdc682c, 0x1129beb3, 0x16dffbcf, 0x03411d65, 0x1a236f55, 0x14d6ea70, 0x14270ac5, 0x7d587c}}, - {{0x18bc9459, 0x00e0d04e, 0x08de0294, 0x072015a6, 0x16ad0c46, 0x0005b67e, 0x11849c8d, 0x00710609, 0xa7295c}}} - }, - { - /* 1*16^39*G: */ - {{{0x1d054c96, 0x145e9b9f, 0x1472a223, 0x08287751, 0x0e5dceec, 0x0fedf2ff, 0x187db547, 0x092339bc, 0x4df9c1}}, - {{0x0ad10d5d, 0x175d6036, 0x02124064, 0x0a0d9b85, 0x185d4b5d, 0x1a611d0e, 0x1ca01425, 0x0a2125b0, 0x35ec}}}, - /* 3*16^39*G: */ - {{{0x1def001d, 0x07912ed2, 0x06e89fbd, 0x1377ad31, 0x1b64b21f, 0x1e8e04f1, 0x12bc8382, 0x05b64fc5, 0xa549a3}}, - {{0x10624783, 0x0aed8d3f, 0x125c16b7, 0x083c54c5, 0x19456eb1, 0x01876c52, 0x1b2f7d33, 0x0f20db2c, 0x799b7a}}}, - /* 5*16^39*G: */ - {{{0x052ed4cb, 0x0d778f2e, 0x027b1681, 0x09bdf678, 0x12e6ec95, 0x05ecc1a9, 0x13448fc2, 0x061b40fd, 0x7e798f}}, - {{0x14bb9462, 0x0b8b303c, 0x07cde849, 0x06ae37ff, 0x0f057b17, 0x0aa4ef79, 0x120c106a, 0x189449b5, 0xd23dcc}}}, - /* 7*16^39*G: */ - {{{0x13630834, 0x0f9d07de, 0x16b019ff, 0x07e250c7, 0x08108846, 0x0f4b5d46, 0x1e0eb56e, 0x00062a28, 0x224fa2}}, - {{0x047a2272, 0x09e239be, 0x0943dd73, 0x05249d81, 0x109f53d6, 0x187d1c8e, 0x0970f12d, 0x065767db, 0xbbe54e}}}, - /* 9*16^39*G: */ - {{{0x183c19d7, 0x13b24e8a, 0x0b3d5eed, 0x16bc8b58, 0x08ef2bbb, 0x12e67211, 0x07904a68, 0x198c0147, 0xc2d4a0}}, - {{0x0507928d, 0x17945c16, 0x0d1725dc, 0x0095062e, 0x1260d268, 0x1dafbfa0, 0x0a535060, 0x1f38100c, 0x65ada0}}}, - /* 11*16^39*G: */ - {{{0x1c940c9a, 0x064056a5, 0x1d08cc21, 0x1e79c275, 0x1dc2113c, 0x02f13a26, 0x0c643956, 0x0fd860be, 0x2ec22a}}, - {{0x051e7a4d, 0x08ca7ecc, 0x08d6f6b3, 0x00a307b3, 0x07feb124, 0x127a814e, 0x05a130b8, 0x0d1bc66f, 0x8b1da4}}}, - /* 13*16^39*G: */ - {{{0x1d683eeb, 0x138772fe, 0x034c4cea, 0x0dd67141, 0x0b8f33e3, 0x1b292842, 0x13b2ac6b, 0x0e71f351, 0xafc669}}, - {{0x10cd4509, 0x0f14e559, 0x1b77f724, 0x1756aa4b, 0x19c16570, 0x0e3fe511, 0x1d4af0d6, 0x12edba44, 0x2c21}}}, - /* 15*16^39*G: */ - {{{0x176f4293, 0x1100fc3d, 0x0f144e7a, 0x12f16aca, 0x1282e10e, 0x04679b85, 0x0c24486f, 0x0b53e686, 0xd2557b}}, - {{0x1282740a, 0x0d8c3d12, 0x101697c7, 0x16d071bc, 0x0d21fe34, 0x178375a5, 0x1fc049a0, 0x086abc84, 0xa787b3}}} - }, - { - /* 1*16^40*G: */ - {{{0x0c1f98cd, 0x1fe4ce45, 0x1fc0c232, 0x09120a9a, 0x06021523, 0x054e0e63, 0x01c3ebb6, 0x150948e9, 0x9c3919}}, - {{0x14fc599d, 0x13f2f01e, 0x193239af, 0x064deed8, 0x0e641905, 0x0225f930, 0x155d613c, 0x01e949bb, 0xddb84f}}}, - /* 3*16^40*G: */ - {{{0x0fb64db3, 0x1dcc6a9c, 0x1754e105, 0x1bc99473, 0x1b8d6a7e, 0x1c1fdf29, 0x12dd02ee, 0x124537b9, 0xc11423}}, - {{0x1c0259be, 0x118674ff, 0x1159f478, 0x0b01209a, 0x18bd1a87, 0x06f27f4b, 0x1f0a973b, 0x1b8b690d, 0x1237f6}}}, - /* 5*16^40*G: */ - {{{0x03081e46, 0x16f6c1a0, 0x11567a87, 0x044318aa, 0x034713a5, 0x0e160c93, 0x089020b6, 0x1f0634ee, 0x6c5b4b}}, - {{0x0bfbcd70, 0x08fce5c0, 0x108a98bb, 0x019f04d5, 0x1e47841d, 0x1c31e715, 0x10bec8d1, 0x0e2924da, 0xcb0513}}}, - /* 7*16^40*G: */ - {{{0x064dcd4b, 0x0572d762, 0x04704937, 0x018fab32, 0x10a450c3, 0x0332e558, 0x1792d59c, 0x0acce195, 0xe1e9a8}}, - {{0x1b041f2c, 0x085b12f5, 0x085aca4b, 0x09a33559, 0x177927f4, 0x01accd92, 0x14c6deb1, 0x12a88ab8, 0x562b0a}}}, - /* 9*16^40*G: */ - {{{0x0badd73c, 0x02c3b7f1, 0x0992df40, 0x139bb205, 0x014208fd, 0x1a72176e, 0x0265de29, 0x0af5a236, 0x51b21a}}, - {{0x0b36d8d1, 0x1bea570f, 0x11cd2e9b, 0x00261e51, 0x01cfa6c2, 0x03f80e96, 0x0f975528, 0x020003fa, 0x7930}}}, - /* 11*16^40*G: */ - {{{0x1b09f34b, 0x0bae85b9, 0x1319b39b, 0x10e7cc11, 0x19d61e58, 0x114b79f9, 0x1e6186ad, 0x14c76396, 0x9701f3}}, - {{0x00df5793, 0x06e42866, 0x1731e52b, 0x097872ff, 0x08337710, 0x18da98ab, 0x1b4575c0, 0x177195e1, 0x3dd44b}}}, - /* 13*16^40*G: */ - {{{0x1f1e2f46, 0x0e73111d, 0x09de0c05, 0x01ee3d0e, 0x03c57527, 0x0970206b, 0x1b311156, 0x03a593cc, 0xa036b4}}, - {{0x1effb349, 0x198f134a, 0x1c2c7d3d, 0x01c5059f, 0x0b08d068, 0x1b5523cf, 0x0cf5f7c7, 0x14007d2d, 0xc3bf91}}}, - /* 15*16^40*G: */ - {{{0x0c4cea08, 0x06c5c81c, 0x03a8876f, 0x16b1741c, 0x04652654, 0x108a9a00, 0x1141bd29, 0x1b7549d1, 0x6a85fa}}, - {{0x1862f4f3, 0x0cef672c, 0x15c86da8, 0x0e349687, 0x06230b42, 0x19e0a47f, 0x16754c64, 0x00975c8c, 0xb646}}} - }, - { - /* 1*16^41*G: */ - {{{0x00a959e5, 0x1109c109, 0x04753316, 0x02927517, 0x006bb91e, 0x0f940ec7, 0x1f7e3781, 0x0163ba25, 0x605717}}, - {{0x0385a2a8, 0x04cdf499, 0x1893197a, 0x02a5787d, 0x1f262465, 0x116d7b8e, 0x001eb766, 0x164d4d49, 0x9a1af0}}}, - /* 3*16^41*G: */ - {{{0x171c032b, 0x0a6d0b14, 0x0bf72603, 0x16cd142f, 0x166c5ff6, 0x0dafefe3, 0x0980f744, 0x1f9adc00, 0x71eba8}}, - {{0x1668359f, 0x1d5ad470, 0x12d1d579, 0x0635a2ee, 0x0bb7f719, 0x028b7aa6, 0x0e77bd98, 0x0c496c3a, 0xd2ff12}}}, - /* 5*16^41*G: */ - {{{0x0a03a61c, 0x03723a29, 0x01c15d34, 0x10d1e8d2, 0x09dd0507, 0x1a215d55, 0x148cb285, 0x00b66493, 0x855ec3}}, - {{0x065dfc07, 0x0fe37556, 0x1f912597, 0x05ee9d42, 0x0fb4ed33, 0x05ffcda1, 0x105fd50f, 0x05d8be03, 0xdd85d}}}, - /* 7*16^41*G: */ - {{{0x1f32d706, 0x00605240, 0x1a819e2c, 0x119948e8, 0x1bfa2061, 0x094d184a, 0x0fc7c543, 0x0d57567f, 0x3ce448}}, - {{0x1c7fd9e4, 0x05b9b1bf, 0x187a27d0, 0x02ac879a, 0x14906edd, 0x08235884, 0x014a23bf, 0x11b55c6f, 0xe77540}}}, - /* 9*16^41*G: */ - {{{0x191cd3fb, 0x0da065db, 0x0a6f9a1b, 0x1467fb2e, 0x044eb4a2, 0x0190c7c4, 0x1febc0b8, 0x0287e9c6, 0x11ccc5}}, - {{0x15160d86, 0x09b8b5d2, 0x174d1caa, 0x163dfa59, 0x0c239fa0, 0x112249c6, 0x077ad4a3, 0x05520562, 0x4aa56b}}}, - /* 11*16^41*G: */ - {{{0x018f7552, 0x03dc88cb, 0x0153eb0e, 0x02271730, 0x182ddbd4, 0x1bba7c11, 0x11bd0ee5, 0x02fca293, 0x250bb}}, - {{0x1510b14d, 0x18424b11, 0x0f5bc78f, 0x00de7866, 0x1d817da0, 0x1efbaff4, 0x0208d0b5, 0x1f9377d0, 0x731930}}}, - /* 13*16^41*G: */ - {{{0x1f725d12, 0x0e89f49c, 0x0d7d1d41, 0x0c8577b9, 0x02fbfd94, 0x1ce70501, 0x1f4ead28, 0x111668cf, 0x1a749c}}, - {{0x03ac56e4, 0x09b28a69, 0x0436a9c0, 0x0410d313, 0x13d8f607, 0x1f3ae157, 0x18b3d162, 0x12ae7d81, 0x7e91d1}}}, - /* 15*16^41*G: */ - {{{0x09fae458, 0x10824729, 0x1bb25ff5, 0x14b884ec, 0x17b328b0, 0x0ab52efd, 0x06304274, 0x0b7c1f04, 0xc75068}}, - {{0x1757b598, 0x00b420ca, 0x165468ac, 0x1b94a066, 0x0c7b40a5, 0x1a0a6339, 0x1817ed4b, 0x1f19f243, 0xead795}}} - }, - { - /* 1*16^42*G: */ - {{{0x0cb94266, 0x0d34b9f7, 0x1537c4ac, 0x1de1f74f, 0x1a31880c, 0x1cd228c6, 0x10450850, 0x11c47410, 0xa576df}}, - {{0x01b28ec8, 0x145f08d7, 0x05367cfb, 0x1c214fea, 0x0d82c432, 0x0bd7f2c6, 0x02cb24ae, 0x041cecc8, 0x40a6bf}}}, - /* 3*16^42*G: */ - {{{0x0d9ed6c1, 0x14575ac6, 0x1564f5ad, 0x1ce8b787, 0x0dd0ec24, 0x00c3b82f, 0x14fa02ff, 0x0db96e9e, 0x32833}}, - {{0x18fafeee, 0x16375f37, 0x12d252b7, 0x17e9be4b, 0x17c8c265, 0x0ca1d106, 0x1ca311b5, 0x07025fb3, 0x71a898}}}, - /* 5*16^42*G: */ - {{{0x1235983a, 0x0cd4d469, 0x0ef3aca4, 0x14206e02, 0x01531e38, 0x0936b87f, 0x1153718e, 0x15d17223, 0xce4f4e}}, - {{0x0d3cdecf, 0x07eb58c8, 0x0fdd02bb, 0x18ca451d, 0x07543526, 0x10124f38, 0x0eecfab7, 0x0e78721f, 0xf3c9f9}}}, - /* 7*16^42*G: */ - {{{0x15b0e6c9, 0x16d55b32, 0x1b932269, 0x1ff39ef0, 0x0bcbddb5, 0x07d9b6fc, 0x0889e38a, 0x14a9730c, 0x4dbebf}}, - {{0x1eb2cc25, 0x0a53c2aa, 0x1413beba, 0x06236578, 0x029f3589, 0x11373711, 0x0bb7d169, 0x16079227, 0x10fee7}}}, - /* 9*16^42*G: */ - {{{0x05857295, 0x0700d08d, 0x10cfc059, 0x11c8fe06, 0x0a12069c, 0x08c7e50e, 0x10862cb8, 0x017fde8b, 0xa42a24}}, - {{0x0a7eb9c1, 0x159bbff6, 0x1464e555, 0x038459a2, 0x1a4c427a, 0x1915926e, 0x15159e9a, 0x1e4c200b, 0x3aa0b3}}}, - /* 11*16^42*G: */ - {{{0x0fcdc098, 0x1107faab, 0x191a00c8, 0x15c01ed5, 0x099c1550, 0x0fc36062, 0x0899e9fc, 0x05f2df64, 0x34e12b}}, - {{0x0a7474e2, 0x0658d6f3, 0x0620fd99, 0x1ea261e3, 0x172db04d, 0x05e420bc, 0x0c8b65d3, 0x1bbaf6ba, 0xa64ac2}}}, - /* 13*16^42*G: */ - {{{0x0f173b92, 0x06b75af4, 0x07edd847, 0x1ce5e82d, 0x165683b7, 0x0d10c7a6, 0x07ca6f8c, 0x081b3772, 0x10f4d2}}, - {{0x033146c2, 0x0810036b, 0x01ab6df2, 0x16ed3a29, 0x108ba90b, 0x12d2d19c, 0x0eb4846c, 0x12a122ea, 0x850e2d}}}, - /* 15*16^42*G: */ - {{{0x08d84958, 0x137e8ecd, 0x0b3172bb, 0x03bd62d9, 0x0cc866a1, 0x0dcbb6a0, 0x1f9d27c6, 0x016d36ce, 0xe846e8}}, - {{0x1882cf9f, 0x062323db, 0x18306990, 0x03466ce3, 0x0b76fad5, 0x0c8823cc, 0x1895076f, 0x1f91298f, 0xa29cb8}}} - }, - { - /* 1*16^43*G: */ - {{{0x1e58ad71, 0x1bb1c44d, 0x068e8823, 0x01a3eb9f, 0x08c38bb3, 0x1f4b14ef, 0x0f8c2817, 0x11851bd8, 0x7778a7}}, - {{0x1d9f43ac, 0x1a89fe0f, 0x092b158e, 0x070823fe, 0x1580087b, 0x0709797f, 0x08bfdc26, 0x1356b4b6, 0x34626d}}}, - /* 3*16^43*G: */ - {{{0x1319c869, 0x1be37571, 0x07ac9c0b, 0x13f2baec, 0x0acdc18a, 0x1c8117e6, 0x1f234060, 0x0bb302e7, 0x301804}}, - {{0x12b856f0, 0x0063b64e, 0x0f669eff, 0x15099494, 0x02e3bca2, 0x121906b1, 0x1edbe198, 0x0f04a076, 0xac5fc5}}}, - /* 5*16^43*G: */ - {{{0x05ed29b5, 0x0334c37d, 0x0ab746d3, 0x0616c0e2, 0x1c885f58, 0x13edcd31, 0x1bcd0ead, 0x16c3dcaf, 0x322881}}, - {{0x1cd15ad2, 0x1a789373, 0x1b9d813b, 0x075d5729, 0x131e1ca8, 0x18cefa0a, 0x113ac442, 0x1082f406, 0x167702}}}, - /* 7*16^43*G: */ - {{{0x06c96100, 0x1fd4203c, 0x1a72398e, 0x0602354d, 0x1dbcca16, 0x0ecd96f9, 0x0e7fed0f, 0x07581f63, 0x3f3847}}, - {{0x12624707, 0x0f1560df, 0x1d7c6f3c, 0x0e38f816, 0x19ce5665, 0x02231783, 0x0e5494d3, 0x0abeba80, 0x70c69c}}}, - /* 9*16^43*G: */ - {{{0x1d22d2ac, 0x1a637637, 0x12ab3808, 0x1bfc24db, 0x1df2f10d, 0x00704bc2, 0x1db72d0f, 0x18bfa4f4, 0x288113}}, - {{0x0f42a268, 0x00f5aafc, 0x12323f42, 0x07a8942a, 0x16137ddc, 0x1b93064b, 0x1723c81d, 0x002b1f78, 0xa1a7eb}}}, - /* 11*16^43*G: */ - {{{0x045f8ea8, 0x05d406e9, 0x134a4035, 0x0491c72c, 0x19fe5d17, 0x06caeb88, 0x08a954fd, 0x001908c7, 0xf963a2}}, - {{0x19bc99eb, 0x1e2afd82, 0x02d82092, 0x11e46b3b, 0x027208bb, 0x11180ffa, 0x0f028edc, 0x04d18ff0, 0x9c8594}}}, - /* 13*16^43*G: */ - {{{0x0606a315, 0x10d44189, 0x1a58eb67, 0x04c0e5e4, 0x0097e407, 0x05952c87, 0x069fe636, 0x099fee1b, 0xa5d922}}, - {{0x1e3b68d1, 0x1ab099ac, 0x0469f274, 0x1a1a68fa, 0x00de9ed4, 0x0355ebcc, 0x096cd0cc, 0x0007641b, 0x87328b}}}, - /* 15*16^43*G: */ - {{{0x06231493, 0x06dbdaa0, 0x131351a7, 0x02350619, 0x1e6a4964, 0x120e8072, 0x0d813ad3, 0x05c36e78, 0xf1fe98}}, - {{0x158848c1, 0x0b54cd33, 0x17fc3406, 0x07f668dc, 0x199d3f17, 0x1e102fbe, 0x177085b4, 0x1d5db349, 0x2e2019}}} - }, - { - /* 1*16^44*G: */ - {{{0x06d903ac, 0x04f6d4e0, 0x0b5f972c, 0x12c4e9cb, 0x0fd2ed5f, 0x0fe9873d, 0x01118dca, 0x0bdcc6f5, 0x92895}}, - {{0x1bcd091f, 0x08c0749a, 0x0a360ff1, 0x1a4ddf51, 0x095eeac3, 0x0509849d, 0x0aa09ede, 0x0007a7e8, 0xc25621}}}, - /* 3*16^44*G: */ - {{{0x1874b839, 0x088943ab, 0x0f4ad060, 0x022b672a, 0x0b6aebe4, 0x186fd918, 0x16a014f7, 0x03f81c3c, 0x3e03b8}}, - {{0x1c0594ba, 0x060e72b3, 0x0ad6e368, 0x0b8be1fb, 0x18f667de, 0x1303ab8c, 0x1d0b113d, 0x0c7bfe0f, 0xd13ae1}}}, - /* 5*16^44*G: */ - {{{0x0357a513, 0x11fbc734, 0x0cc08fce, 0x037a268b, 0x122c5f15, 0x1141d514, 0x04b358be, 0x16f45e89, 0xe662c0}}, - {{0x0017d07e, 0x095100e5, 0x14e36246, 0x06b9ac4a, 0x1a419d80, 0x11045090, 0x148c176b, 0x079cc248, 0xab0b19}}}, - /* 7*16^44*G: */ - {{{0x1f37d242, 0x0cafbf7e, 0x07052c12, 0x1fd94c0f, 0x1587dc29, 0x1163e5f1, 0x1b2e10e1, 0x1639299e, 0x40bf80}}, - {{0x06405088, 0x08ec13cd, 0x0f4d560f, 0x043d7485, 0x0fe12743, 0x1f4d8d93, 0x0bc13d4f, 0x06bb0ad5, 0xb579dd}}}, - /* 9*16^44*G: */ - {{{0x195a3558, 0x17959b22, 0x0d29fcae, 0x0e3f0bc4, 0x159f6ac0, 0x0bc09c6d, 0x09c201be, 0x12ec03b9, 0x3d14fe}}, - {{0x0443df4c, 0x156d9d63, 0x1075c9f1, 0x0145c28f, 0x16e1482e, 0x1498edfa, 0x07be3ca6, 0x1add08d0, 0x16c6bd}}}, - /* 11*16^44*G: */ - {{{0x02b1fc24, 0x0f4fad6c, 0x0fdd5c3b, 0x11b038fc, 0x04865252, 0x16269649, 0x14947306, 0x081d05cc, 0xdd6fa5}}, - {{0x0e9b74ca, 0x1218e230, 0x1cc88c12, 0x01bcd7da, 0x17e77ec1, 0x18f5f8b2, 0x01bf8d9b, 0x0fd63a63, 0x67e62b}}}, - /* 13*16^44*G: */ - {{{0x1dd08e02, 0x0f548ac9, 0x0b7c0a20, 0x0a8f6ffb, 0x11e80108, 0x0a4cd51e, 0x15e03e1a, 0x1505bcab, 0x13fa2d}}, - {{0x1cb03410, 0x12aa0ee1, 0x090ae5f6, 0x095f7633, 0x032c7e64, 0x0b1035da, 0x09c8c4cd, 0x1608aabb, 0x136338}}}, - /* 15*16^44*G: */ - {{{0x144ee41a, 0x0119d5cc, 0x1f5a69ab, 0x16adba76, 0x08282879, 0x085b3963, 0x0910fdf0, 0x0a3a78e1, 0xd06c48}}, - {{0x0b295e6f, 0x18fc274c, 0x18bb894b, 0x170868c2, 0x030919b7, 0x166a7a7b, 0x02b6eec2, 0x0980b09a, 0x5815fd}}} - }, - { - /* 1*16^45*G: */ - {{{0x03d82751, 0x1d573a8b, 0x0b4d5149, 0x0b69520f, 0x1b285564, 0x1279d071, 0x0424e641, 0x1e7d8db6, 0x85d0fe}}, - {{0x0eb1f962, 0x1611bd12, 0x1dccc560, 0x0ea3d2d0, 0x0f5663e8, 0x04b72c16, 0x102f8a75, 0x10827471, 0x1f0364}}}, - /* 3*16^45*G: */ - {{{0x0cde4cf3, 0x0caa830f, 0x02819aae, 0x01ca6a8f, 0x19ae7934, 0x169368ae, 0x0b0ef9f0, 0x09582284, 0x384dab}}, - {{0x052d0566, 0x1e3cb591, 0x146e9ced, 0x0614672e, 0x0c6f01f4, 0x16b6d15a, 0x090efed3, 0x179a3739, 0xd6e3c5}}}, - /* 5*16^45*G: */ - {{{0x1e5238c2, 0x0579f490, 0x03b2e2e6, 0x0abeb870, 0x0ed48403, 0x1085a741, 0x16a906c5, 0x01d6fa82, 0x14f0ec}}, - {{0x12f07922, 0x14351a3c, 0x0124e75b, 0x1801b006, 0x0747fd25, 0x039f1c21, 0x1602487f, 0x07ba906b, 0xab12d5}}}, - /* 7*16^45*G: */ - {{{0x1543e94d, 0x1b1a977e, 0x1e638623, 0x06054ead, 0x00ddadd1, 0x1a33c52d, 0x01fb1070, 0x176f0585, 0xeb42f3}}, - {{0x05924d89, 0x02acef22, 0x035b5090, 0x108d1bcc, 0x1fb774cd, 0x0eab97e6, 0x04b72683, 0x00e9e4bb, 0x234a6d}}}, - /* 9*16^45*G: */ - {{{0x1e19aaed, 0x19272dab, 0x199cc9c0, 0x1759bd18, 0x0a920459, 0x0017b703, 0x0366a7bb, 0x194a2d04, 0x1cf138}}, - {{0x092f400e, 0x09b752eb, 0x11dffef0, 0x1ddf1fdf, 0x1de17479, 0x195335b6, 0x0e197d0d, 0x1e62e38c, 0xd6ffda}}}, - /* 11*16^45*G: */ - {{{0x16a8aa39, 0x1b6074fd, 0x1e3eb157, 0x0cc6f694, 0x190d937a, 0x104b424c, 0x104b21d6, 0x17cbe81a, 0xb58686}}, - {{0x0b493c1f, 0x1e3c9ae9, 0x16cd1ee3, 0x1b5f31cd, 0x0a91dabb, 0x1c6a2a60, 0x10b05251, 0x086498f1, 0x5632d5}}}, - /* 13*16^45*G: */ - {{{0x103b4cc5, 0x148f5f1d, 0x071df0bb, 0x106374b4, 0x1a802572, 0x1e27f3f9, 0x10ad9ed6, 0x160d7179, 0x5fc19d}}, - {{0x05b57c28, 0x1d9cfdc3, 0x021fb128, 0x0dea0798, 0x05ef4927, 0x09c7cd1d, 0x1f19bb88, 0x181d9318, 0xec8e84}}}, - /* 15*16^45*G: */ - {{{0x0cb38cb5, 0x1a5c2bea, 0x0e22522e, 0x16ffbe9a, 0x0ea1be10, 0x05207e9f, 0x0a277aea, 0x01a85dbc, 0xb88fb7}}, - {{0x1965f3d7, 0x1dfd3ab2, 0x0be31c65, 0x1e7c244f, 0x1a8e24d4, 0x1dcca59a, 0x0a0180d2, 0x15a8dd46, 0xd6c736}}} - }, - { - /* 1*16^46*G: */ - {{{0x0526087e, 0x1aa02412, 0x16880c23, 0x16db1105, 0x0b85dfdf, 0x1b020bcc, 0x1a5f0726, 0x19d2fdd9, 0xff2b0d}}, - {{0x10c29907, 0x04a8f00f, 0x038b3acb, 0x0fdadf72, 0x07936c7b, 0x026e2a68, 0x08622bd3, 0x1fdea497, 0x493d13}}}, - /* 3*16^46*G: */ - {{{0x19d681f9, 0x0c82a7f3, 0x03fae7f1, 0x1c1ddf59, 0x094b066c, 0x1f92f016, 0x0c2222df, 0x1e4eebe4, 0xc745fd}}, - {{0x1bbb1247, 0x018b9a1b, 0x1f5171d8, 0x17a66b8c, 0x018678cd, 0x1ca63874, 0x179e29c4, 0x1e5ed73c, 0x590222}}}, - /* 5*16^46*G: */ - {{{0x15cd0ea3, 0x10267769, 0x12b12057, 0x08f1d041, 0x0e7f2b34, 0x0f2f5b39, 0x142c9e96, 0x1e752ea0, 0xabb279}}, - {{0x1c307bce, 0x1849899b, 0x00bced91, 0x0ed20b3c, 0x18ed47c9, 0x1f060183, 0x1c367ed2, 0x0777e2f2, 0x5dee10}}}, - /* 7*16^46*G: */ - {{{0x1bc9ee3e, 0x017179f8, 0x19ce0b17, 0x1f4352c7, 0x1ed11ea9, 0x1553a133, 0x00a09feb, 0x016b3f8d, 0x3f8115}}, - {{0x199aae06, 0x0756d862, 0x16a0580f, 0x0765b9f9, 0x15662762, 0x1f59e23c, 0x00b519c6, 0x0d1fb7f5, 0x19c88a}}}, - /* 9*16^46*G: */ - {{{0x18e4a007, 0x0b8df7d7, 0x0ecd62d8, 0x19dd9e11, 0x0c7ec15e, 0x1d19d52b, 0x179a5652, 0x05ba0105, 0x5cf813}}, - {{0x1068b883, 0x131f8484, 0x071ffa33, 0x08df2d8f, 0x03df6c89, 0x00ac4246, 0x0837d2b5, 0x0b81ac3f, 0xb45aee}}}, - /* 11*16^46*G: */ - {{{0x06c2d4a7, 0x0ab7f3d9, 0x13ffec42, 0x06df2677, 0x04ed21bc, 0x19cb9e20, 0x125194f8, 0x09a1a974, 0xb6d5fe}}, - {{0x1ae86371, 0x0c6e73f1, 0x178f3204, 0x16fc9cde, 0x1fd8e745, 0x1c904eff, 0x1b0537f3, 0x1427577a, 0x47f373}}}, - /* 13*16^46*G: */ - {{{0x1c66dd33, 0x0499b117, 0x171db714, 0x1f791fe3, 0x1b022ea8, 0x0d8a8014, 0x021c1aec, 0x180cd9eb, 0x61c8bb}}, - {{0x16f10bfa, 0x1ddd4f9d, 0x00832328, 0x020dd585, 0x1d3fb6a5, 0x0cca5cc2, 0x1c0d119a, 0x0473ca9e, 0x93599e}}}, - /* 15*16^46*G: */ - {{{0x1d6b2ff8, 0x002dbe66, 0x01b23ea6, 0x066d82e5, 0x1bdf1876, 0x1a9b9f61, 0x01461f27, 0x14ae84cf, 0x94e32b}}, - {{0x0ce1af3e, 0x0ea42aa9, 0x1aff84eb, 0x15e084a4, 0x19e8cb33, 0x12443316, 0x13864bc7, 0x11687b40, 0xd1b44}}} - }, - { - /* 1*16^47*G: */ - {{{0x1856e241, 0x0072f167, 0x15b74a1e, 0x03dc2919, 0x1212b57f, 0x1973180d, 0x03aa7b4a, 0x1c963d10, 0x827fbb}}, - {{0x0ec293ec, 0x102db45d, 0x040c59b5, 0x0f4c630d, 0x112687ff, 0x19633e8e, 0x0c2dc6fb, 0x12478e4f, 0xc60f9c}}}, - /* 3*16^47*G: */ - {{{0x1bb80fa7, 0x1a242e59, 0x0104e218, 0x0fb4d76e, 0x0819f3aa, 0x1035e990, 0x0bef0346, 0x03ec6118, 0x857e3}}, - {{0x09366b2d, 0x0cc108f8, 0x15c05aaf, 0x1c6e0879, 0x17147172, 0x064e8ee5, 0x1c824b5f, 0x08475c02, 0xf64393}}}, - /* 5*16^47*G: */ - {{{0x09c70e63, 0x0e8161d0, 0x14f525bd, 0x1716f1ce, 0x0672e9cb, 0x032abb25, 0x0010d517, 0x1d4ad7ac, 0x28aacc}}, - {{0x1057da4e, 0x0f81c417, 0x13687a2e, 0x18b39c88, 0x0ebb7f5f, 0x0b33e3b4, 0x18559ea2, 0x05df0341, 0x2b6932}}}, - /* 7*16^47*G: */ - {{{0x13e674b5, 0x00ee297b, 0x0182ab18, 0x11ed39ce, 0x18a4f92d, 0x1964de75, 0x19851776, 0x04b40ab4, 0xa2f3b6}}, - {{0x0e937941, 0x049c6470, 0x0cfe94ec, 0x05f462f8, 0x07c4b922, 0x05487995, 0x02ba0011, 0x0b2c298d, 0x620ea1}}}, - /* 9*16^47*G: */ - {{{0x191eb056, 0x1b00b18e, 0x13f4e1b1, 0x05dfb71d, 0x115f5a00, 0x1ae351fa, 0x048c7662, 0x193d55cb, 0x3c4f83}}, - {{0x005cecab, 0x012c49ed, 0x13dae1dd, 0x056a8903, 0x07880198, 0x12b9e1d9, 0x0da8ceb5, 0x00ea2951, 0x944790}}}, - /* 11*16^47*G: */ - {{{0x1d86dfa9, 0x0830fedd, 0x0e64e9c6, 0x11694813, 0x03baadc3, 0x0f01f408, 0x1f538a70, 0x0511532c, 0xaff8e1}}, - {{0x01d12681, 0x1881e1b6, 0x067e71c1, 0x02db5288, 0x153f4f91, 0x15d50fe7, 0x10ff4f4f, 0x166426ef, 0x8d8b4b}}}, - /* 13*16^47*G: */ - {{{0x189ba9c1, 0x07939b5c, 0x074ce38a, 0x1ef94b41, 0x0e579e40, 0x01315767, 0x02cfa116, 0x08a51b80, 0xd3fb78}}, - {{0x0b51b267, 0x10dc46ff, 0x046b7801, 0x19dbab80, 0x10fe6341, 0x102bac5b, 0x139f29f2, 0x069df4d6, 0xf894d4}}}, - /* 15*16^47*G: */ - {{{0x0f2bb909, 0x0d4b60e8, 0x16636667, 0x0204f8a6, 0x07d7f639, 0x14c41c8c, 0x0a23fd1c, 0x01c15935, 0x4ec930}}, - {{0x04cf4071, 0x0451c1fd, 0x0b0e09ee, 0x1c2d041b, 0x049bad52, 0x0e228c26, 0x13717203, 0x00d7c360, 0x782ba1}}} - }, - { - /* 1*16^48*G: */ - {{{0x0120e2b3, 0x19dac7d1, 0x11fe6a9f, 0x11fb9cfe, 0x0e5217a5, 0x0571a673, 0x16eb9ef9, 0x1e43ea37, 0xeaa649}}, - {{0x1a5ad93d, 0x03d2982d, 0x0fdf9675, 0x0d72cbe2, 0x1aa5a01a, 0x007c4c3c, 0x00eb1a6a, 0x1dab7776, 0xbe3279}}}, - /* 3*16^48*G: */ - {{{0x1f2e070d, 0x0c1fe9d1, 0x0a9aa63d, 0x156e398a, 0x047e229a, 0x18e1dc28, 0x0affd21c, 0x1d2085e9, 0x4b72a5}}, - {{0x096dd780, 0x025d4177, 0x05230f79, 0x08cbbba5, 0x13c10b0b, 0x1dd9b687, 0x073d809d, 0x09c3ad5c, 0x599e1d}}}, - /* 5*16^48*G: */ - {{{0x0a02591c, 0x0e73fec2, 0x1449687a, 0x0a932cb0, 0x1fd613ef, 0x1fdf5af0, 0x038a169a, 0x1f8ca739, 0xa9fc93}}, - {{0x09bec2dc, 0x0856ef7b, 0x13dc94de, 0x111882bf, 0x165e5ca8, 0x00bd0d48, 0x1c5cfa13, 0x073b8a70, 0x9c2ce7}}}, - /* 7*16^48*G: */ - {{{0x0d968b59, 0x08037071, 0x12ef0b84, 0x05175c27, 0x1027709a, 0x1d60904d, 0x1c29a9f5, 0x0f834df3, 0xc94001}}, - {{0x0de572fb, 0x17ebb204, 0x0432723f, 0x08596c87, 0x1742ce28, 0x10dfd2da, 0x18804ee2, 0x0a019370, 0x39d922}}}, - /* 9*16^48*G: */ - {{{0x126b3332, 0x143999ab, 0x1b9779a8, 0x0711a0e7, 0x1f8e0310, 0x09d2fb85, 0x0093b19e, 0x13afdda0, 0x1f84bb}}, - {{0x14e8d52e, 0x0a214518, 0x1b70e895, 0x199c5a86, 0x1edf0c2b, 0x013fbadc, 0x1b30951f, 0x00e57953, 0xee726d}}}, - /* 11*16^48*G: */ - {{{0x0defa98e, 0x06d52a56, 0x0b09e657, 0x1088d023, 0x1e9c7724, 0x0abd9cc8, 0x1341b2a0, 0x112128bf, 0xf13e0}}, - {{0x1e286767, 0x0453bb4d, 0x13ab3370, 0x1ce0bc2d, 0x162db287, 0x1c5853d9, 0x1140d78f, 0x1e2ec9cf, 0xadd521}}}, - /* 13*16^48*G: */ - {{{0x09f59b6b, 0x0f0e01df, 0x02238be9, 0x0718c783, 0x026d3e9b, 0x050e96ac, 0x11f6cdca, 0x14aa3bbd, 0xdde191}}, - {{0x06cb1410, 0x156cb149, 0x0553fb3d, 0x0e7177ce, 0x0e14e8b5, 0x0beb0e29, 0x172f829d, 0x0f00504e, 0x5b2bfb}}}, - /* 15*16^48*G: */ - {{{0x09c6b699, 0x1462afee, 0x191a1c6d, 0x1eae6ad7, 0x01682a86, 0x0bdfcbda, 0x1de9685b, 0x05ddb06d, 0x5fab01}}, - {{0x01c6c3aa, 0x0b990a96, 0x020d466c, 0x1622ffd5, 0x02f7b90a, 0x1a08986b, 0x0513a7ae, 0x0e14787a, 0x2d9bfa}}} - }, - { - /* 1*16^49*G: */ - {{{0x1a34d24f, 0x111b196e, 0x084dd007, 0x0db1e193, 0x02ee541b, 0x0fb6f67a, 0x1a764e47, 0x0878b9e2, 0xe4a42d}}, - {{0x1eba9414, 0x13fb898e, 0x16393c4e, 0x0dddbf51, 0x0d34ce88, 0x0ce67dc5, 0x1cd49bf2, 0x1ce2da38, 0x4d9f92}}}, - /* 3*16^49*G: */ - {{{0x1bea0c68, 0x04208579, 0x1ece4ad7, 0x060246ce, 0x16faf094, 0x1e47469c, 0x0e892526, 0x069c2ad4, 0x3e4196}}, - {{0x1a45edb6, 0x05db7fb8, 0x0f3686af, 0x02328c60, 0x093062fa, 0x05ff1b83, 0x07dfcdcf, 0x13b24964, 0x123c5}}}, - /* 5*16^49*G: */ - {{{0x139824d7, 0x1bae91e4, 0x072625eb, 0x0f6c986a, 0x10b576eb, 0x11f317bf, 0x1423bb52, 0x1ea8abae, 0x8d9438}}, - {{0x1366489f, 0x10027a44, 0x1ac18f62, 0x13c57064, 0x0ef6f8fb, 0x05e98d5b, 0x10a8b298, 0x0e69fdcd, 0x3261e0}}}, - /* 7*16^49*G: */ - {{{0x18d713de, 0x124038d4, 0x0a398823, 0x0185f6e8, 0x14543936, 0x089517f2, 0x1108352a, 0x18ab1dca, 0xb72524}}, - {{0x1b8350e9, 0x17ff292c, 0x1297f2dd, 0x05a4dfc8, 0x09415048, 0x08c174eb, 0x1914410b, 0x13514507, 0x4c51b3}}}, - /* 9*16^49*G: */ - {{{0x1e3b2cb4, 0x0636149f, 0x1c84100b, 0x13e6b7e6, 0x1149e304, 0x1b71c090, 0x09466b71, 0x0b442da2, 0x3de45f}}, - {{0x107eb02f, 0x10f19d61, 0x01c1133d, 0x1c51ccb5, 0x09106823, 0x055254be, 0x17714382, 0x13080bd5, 0xba2a85}}}, - /* 11*16^49*G: */ - {{{0x0ce4e5bf, 0x11a3b37b, 0x04016c5c, 0x0f950d41, 0x106ae9b6, 0x1e71ba44, 0x1a1f078f, 0x18d12b37, 0x8511f1}}, - {{0x01789c08, 0x1e494a26, 0x14d9498b, 0x10f11378, 0x000232da, 0x0fbf6355, 0x121d3077, 0x19f2379a, 0xecdff5}}}, - /* 13*16^49*G: */ - {{{0x1d3258ab, 0x0dab3451, 0x0f05370c, 0x04850315, 0x0ab5957d, 0x0e39770a, 0x0088b3e8, 0x05d039ec, 0x8c5a05}}, - {{0x022d0f8f, 0x0bf04298, 0x16512b79, 0x15d1f381, 0x008c246d, 0x0063c826, 0x16841e6a, 0x09768877, 0x6811db}}}, - /* 15*16^49*G: */ - {{{0x1f91bcee, 0x0c615055, 0x03036105, 0x1e3b1e3c, 0x1f137f5c, 0x1e762ab5, 0x1582f718, 0x02dbd7a6, 0xcef7f8}}, - {{0x01966b33, 0x1da6d4fc, 0x1cbdab1a, 0x1c960542, 0x1245fa63, 0x199ce00e, 0x1c04918e, 0x106c6e90, 0x67e74c}}} - }, - { - /* 1*16^50*G: */ - {{{0x0300bf19, 0x18b9dcea, 0x03fa9251, 0x0a6aed51, 0x12b6b92b, 0x07d6d59a, 0x17655058, 0x1de6c197, 0x1ec80f}}, - {{0x0107cefd, 0x18e6e0e6, 0x05681ed9, 0x0dcefec5, 0x1bf5e014, 0x04ac53d5, 0x1034bce9, 0x06ead6a6, 0xaeefe9}}}, - /* 3*16^50*G: */ - {{{0x12fea1f9, 0x18be5de2, 0x0114ae52, 0x1e16a118, 0x06531c4f, 0x0ed5b388, 0x0ba0ef3f, 0x014aba3e, 0xa6dc88}}, - {{0x1bc345e9, 0x18e0a723, 0x1a3df98e, 0x1713b6fc, 0x0bc50057, 0x01d08b56, 0x1f0c0e1a, 0x0a8fb86c, 0x7ef1a8}}}, - /* 5*16^50*G: */ - {{{0x06d6c9b3, 0x06a061f8, 0x01958df2, 0x1899d0e9, 0x081ba8c6, 0x114e3c52, 0x1664af70, 0x07fd4848, 0xfe6ba9}}, - {{0x0948bdfb, 0x0163c47d, 0x10ba6c03, 0x01e37e0b, 0x13b56d98, 0x00d9a2a0, 0x01cadaed, 0x1ae80a73, 0x7ee918}}}, - /* 7*16^50*G: */ - {{{0x0cf95151, 0x11788398, 0x0b12d910, 0x0900dc88, 0x0ded1b96, 0x04616e04, 0x02fec083, 0x1e28df93, 0x15d5e2}}, - {{0x0ff8ecf2, 0x01503e61, 0x16303e52, 0x009f72fb, 0x023f9bb2, 0x1084bc48, 0x13b1fe43, 0x06322bfa, 0xa5b72e}}}, - /* 9*16^50*G: */ - {{{0x096a5658, 0x0bc085c9, 0x1bd9590f, 0x0964a483, 0x029be381, 0x100493d7, 0x11eb631f, 0x0e4ad108, 0x84c0e8}}, - {{0x181b80d1, 0x0cb394de, 0x13c7f48b, 0x0c35303e, 0x1725ed3a, 0x118c8329, 0x0b12821f, 0x10182c04, 0x265983}}}, - /* 11*16^50*G: */ - {{{0x14dc6a0f, 0x1addae44, 0x1f855d4d, 0x06832285, 0x077c2744, 0x1273d160, 0x0c755949, 0x18e3526e, 0xfed6b1}}, - {{0x176fc7e0, 0x05c6b96c, 0x0ff10273, 0x09ab2614, 0x1ae23137, 0x0c0d7269, 0x1c2a11e4, 0x1cd61fff, 0x8de2ab}}}, - /* 13*16^50*G: */ - {{{0x18e29355, 0x19c42f88, 0x1c8361b6, 0x191d2672, 0x1b9d82f1, 0x1c302011, 0x0f1c3f3b, 0x1b325a79, 0x2a6a4d}}, - {{0x15cc2872, 0x029f007d, 0x1131db00, 0x00b474c9, 0x0f90dfe9, 0x0e40f134, 0x1831d83f, 0x174f894f, 0x8677df}}}, - /* 15*16^50*G: */ - {{{0x0148dabf, 0x1cfd447f, 0x075e3ac7, 0x1ba57269, 0x0e735c1a, 0x07611afd, 0x151b65d9, 0x004d924e, 0xe42d93}}, - {{0x011e1361, 0x1b963ab4, 0x19dfae75, 0x04eae033, 0x18530327, 0x0675fef9, 0x12c362ce, 0x058dc5b0, 0x641386}}} - }, - { - /* 1*16^51*G: */ - {{{0x166642be, 0x0edac941, 0x162ea227, 0x0920e2fa, 0x1fa8bce3, 0x057a3406, 0x10be46c0, 0x11808ce1, 0x146a77}}, - {{0x1d83efd0, 0x0594ba41, 0x1f97b474, 0x152e3a5e, 0x0b2870aa, 0x0c13fcea, 0x0a2b759a, 0x1d866a80, 0xb318e0}}}, - /* 3*16^51*G: */ - {{{0x07315443, 0x0c9c39c1, 0x1a19ca67, 0x1377aa95, 0x142a13d7, 0x04cc1050, 0x0d7fd0b2, 0x0080cc12, 0xfc696c}}, - {{0x17d28960, 0x0486b05a, 0x06f46c5d, 0x1fe90c21, 0x077b5487, 0x10e6eb4b, 0x024aefc3, 0x1d7f076b, 0xe0ce27}}}, - /* 5*16^51*G: */ - {{{0x16fdb4eb, 0x0dd97ae0, 0x0b9a9e74, 0x07baf38c, 0x0b0928fa, 0x151ab15f, 0x0ab46b95, 0x043fe9fe, 0x974af2}}, - {{0x09f6f484, 0x004e1efd, 0x1ff08d21, 0x18ae5477, 0x090ed111, 0x121e8160, 0x0f299347, 0x0faa6a00, 0x555238}}}, - /* 7*16^51*G: */ - {{{0x1d5aeee3, 0x1c8256da, 0x163204dc, 0x109786cc, 0x070c5e82, 0x0d9d3349, 0x062c2448, 0x13693bc7, 0x5baab5}}, - {{0x10f69717, 0x1694d7db, 0x14c7bb60, 0x1bb93b57, 0x1daf7215, 0x004330ff, 0x1a15b968, 0x0c2f81ef, 0x8a577f}}}, - /* 9*16^51*G: */ - {{{0x15726890, 0x08d8f227, 0x00df4561, 0x004bfd59, 0x10a0ee59, 0x17e75fc8, 0x10f4040c, 0x14fd6938, 0xfb685f}}, - {{0x1835783a, 0x0375479d, 0x039e6c98, 0x0d9625d2, 0x0ba094fa, 0x10b6cc32, 0x18ba1a72, 0x045931cb, 0xd750df}}}, - /* 11*16^51*G: */ - {{{0x08bca48a, 0x0325f89d, 0x0f7d8bd5, 0x09abe9d0, 0x1f71d78b, 0x1dc8e143, 0x05682ac9, 0x1ecb3c53, 0x5de58f}}, - {{0x12fd41cd, 0x03ca7406, 0x03e2aa56, 0x0b7b389c, 0x13c843fe, 0x111e1296, 0x0d54c269, 0x07b006b3, 0x685a3b}}}, - /* 13*16^51*G: */ - {{{0x05ef63b6, 0x0f1c1a27, 0x06c60baf, 0x09a02ce6, 0x1c1c85ab, 0x1fed1da7, 0x02febc6d, 0x19bd5ac3, 0x6f1825}}, - {{0x05c655f3, 0x1642367a, 0x1fe51504, 0x12b4c804, 0x134553c8, 0x1026bc19, 0x046e63d0, 0x0fbab232, 0xff097e}}}, - /* 15*16^51*G: */ - {{{0x14a63f3b, 0x1a823f6f, 0x1e3e5c9e, 0x00760332, 0x0c765832, 0x08afaf3b, 0x0ddad61c, 0x12beec54, 0xc5ecb8}}, - {{0x05005024, 0x09f9ba34, 0x0c2fb96d, 0x16cbdcbc, 0x033ec8be, 0x002c7fc9, 0x07cdd3a9, 0x03032f10, 0x222525}}} - }, - { - /* 1*16^52*G: */ - {{{0x1180eef9, 0x0bb543c9, 0x0a2e5ddb, 0x00244134, 0x07b128d0, 0x075d8d50, 0x17c1f8eb, 0x1ec3a45c, 0xfa50c0}}, - {{0x1f4f2811, 0x066c6be9, 0x1e884ece, 0x1065274a, 0x1a68a5e6, 0x09439140, 0x0ea6dcb3, 0x124472fd, 0x6b84c6}}}, - /* 3*16^52*G: */ - {{{0x11da5e12, 0x0f70719c, 0x12b2ca5c, 0x0c14802b, 0x0a2b6a9c, 0x14fc9d0e, 0x0c6f368c, 0x07886f3c, 0xf7502e}}, - {{0x0385f4eb, 0x125ce2f4, 0x0973af1e, 0x0da65dee, 0x072047b8, 0x04a2e1eb, 0x0bf5665c, 0x1dbacf9f, 0x3c57f5}}}, - /* 5*16^52*G: */ - {{{0x10b7d105, 0x03a49988, 0x195f27c8, 0x14b89729, 0x055b3f4c, 0x1b31271a, 0x018a8e93, 0x1f3075cb, 0x12fe78}}, - {{0x1f794a60, 0x0c5637dc, 0x1ba42e11, 0x19c4cbad, 0x1cb771de, 0x0d50ccd3, 0x13dde1ad, 0x14671ad7, 0x2062f1}}}, - /* 7*16^52*G: */ - {{{0x1e0c5d05, 0x110ec19c, 0x15cb6bfd, 0x1cd8a154, 0x04b1a480, 0x1d881404, 0x1cf56312, 0x0268fbe8, 0x76aac3}}, - {{0x11ece63e, 0x0b30cdba, 0x179bb8d5, 0x044b9e02, 0x0625f4b1, 0x19641901, 0x03beafbc, 0x1de1ab8e, 0xef5576}}}, - /* 9*16^52*G: */ - {{{0x1c53c086, 0x1137a42f, 0x0686bb27, 0x0ab86939, 0x08104c6b, 0x0618a2f4, 0x13321f98, 0x0b77cb8b, 0xa663fe}}, - {{0x05016201, 0x14e28195, 0x0653f039, 0x1d994ae7, 0x03dc3991, 0x081644c1, 0x1efd746c, 0x0fed6423, 0xb54199}}}, - /* 11*16^52*G: */ - {{{0x0b758574, 0x16c00f6a, 0x13e71ba6, 0x04cd1286, 0x0bdf3d83, 0x10813d71, 0x16096df2, 0x0f4040d9, 0xde9552}}, - {{0x1b67232a, 0x1bef8fe7, 0x168b7ad2, 0x0d420a2a, 0x09ed7bae, 0x0e423f8b, 0x05393887, 0x0ad5927a, 0x4cd3e0}}}, - /* 13*16^52*G: */ - {{{0x1d85474f, 0x1bcdc55f, 0x18d19a35, 0x18945712, 0x05aea894, 0x065f223c, 0x0b76ffb7, 0x1c0cda65, 0x8da6bc}}, - {{0x1d7b4ef7, 0x1efce3e9, 0x16f75e97, 0x198b260b, 0x1fff10b1, 0x0fae838e, 0x13fe13f7, 0x0d5e63da, 0x13fc6c}}}, - /* 15*16^52*G: */ - {{{0x1dd042ea, 0x1aea12b3, 0x03abf41a, 0x144d4c8b, 0x093beebf, 0x1324f19e, 0x08e6c6f5, 0x18f9f677, 0x7329ac}}, - {{0x0f5c94a1, 0x0d467f61, 0x1ead3c2b, 0x112ee63b, 0x168ee184, 0x073ca7d5, 0x1c5224a1, 0x06a836ed, 0x927249}}} - }, - { - /* 1*16^53*G: */ - {{{0x1f067ec2, 0x129e995a, 0x0ee94883, 0x11156bab, 0x0e8421a2, 0x1fb5bec4, 0x0846c696, 0x1a194e43, 0xda1d61}}, - {{0x1ad836f1, 0x0afdd078, 0x1e6d2299, 0x0e7133a4, 0x11e2966a, 0x1b30b0e4, 0x01b1e701, 0x0b4f9326, 0x8157f5}}}, - /* 3*16^53*G: */ - {{{0x1a95a8db, 0x0ec3b997, 0x074dbd85, 0x1d81888f, 0x11723b83, 0x13234c8d, 0x141067a5, 0x148c607b, 0xe3e90d}}, - {{0x1b0d1cf9, 0x00b67bf8, 0x06134f44, 0x03df2f99, 0x0e76afbb, 0x15486381, 0x1e2ec03e, 0x1800ad82, 0xfbe53b}}}, - /* 5*16^53*G: */ - {{{0x112ee214, 0x1d57eb20, 0x04c55005, 0x149d2f2b, 0x0c01c782, 0x086feae0, 0x1dd6a2d9, 0x18e65c7a, 0x9f4ffe}}, - {{0x1085f37a, 0x17a21112, 0x12200a0f, 0x136d2617, 0x1c69d971, 0x13417eba, 0x1cb983a5, 0x1c2631c5, 0x639ce2}}}, - /* 7*16^53*G: */ - {{{0x1f61a0a5, 0x05b40a28, 0x10538fbe, 0x04a7c367, 0x00b2c4b1, 0x10520fa2, 0x0b06c5c6, 0x05a82269, 0x431f62}}, - {{0x18cef899, 0x15bdbff3, 0x1595dc91, 0x1554086a, 0x1aa7241b, 0x1328cb91, 0x0e3db5b7, 0x0ffcf548, 0xa29832}}}, - /* 9*16^53*G: */ - {{{0x07748503, 0x16133eab, 0x04ca39f2, 0x1c1c8ee6, 0x012ac0e6, 0x1779cfc5, 0x1ee1b2d8, 0x1bbd9cf1, 0x993dba}}, - {{0x1eb0cee2, 0x1d39b09b, 0x02b6a926, 0x10f7ed37, 0x1f51a17f, 0x1c23c3d0, 0x1bade17d, 0x1dd0ad3d, 0xa521a9}}}, - /* 11*16^53*G: */ - {{{0x06d23d80, 0x0f5113ad, 0x11d351e3, 0x17a23e4c, 0x162c0e10, 0x018e5c84, 0x1a968ce5, 0x1740d5ab, 0x75f17a}}, - {{0x080dd57e, 0x03542d81, 0x13a96426, 0x1ff7db76, 0x16292372, 0x1e85f8cd, 0x0a031ff1, 0x1fc2ac73, 0xa07a62}}}, - /* 13*16^53*G: */ - {{{0x01ad3413, 0x115cdb6b, 0x09f5a12b, 0x13800806, 0x07b7a8db, 0x0fa42e5c, 0x12829ba5, 0x0bc23b3e, 0x667855}}, - {{0x1ebca672, 0x12408103, 0x17199804, 0x1c5a2a75, 0x1df9ea6c, 0x136c93e5, 0x191a4949, 0x07bc4f1e, 0x510dda}}}, - /* 15*16^53*G: */ - {{{0x06cc8563, 0x00057ad8, 0x18407aab, 0x09beb7ff, 0x03688922, 0x015ec0cb, 0x1d22b6b2, 0x06c59b4e, 0xebdc4a}}, - {{0x0394ccfa, 0x0d8bde75, 0x0813e492, 0x080f2492, 0x14f07bec, 0x11af366e, 0x0d7e6c7b, 0x089d0ada, 0x659a31}}} - }, - { - /* 1*16^54*G: */ - {{{0x0d064e13, 0x139d8308, 0x1bc7818a, 0x023bc088, 0x14166153, 0x1fcc747e, 0x1a41c857, 0x1fe192e0, 0xa8e282}}, - {{0x11f4cc0c, 0x17be3988, 0x175af5b3, 0x0f347ca1, 0x115888b6, 0x1f9e2d92, 0x1026afed, 0x0b71b703, 0x7f9735}}}, - /* 3*16^54*G: */ - {{{0x1a3979b5, 0x150ccd85, 0x1fa0a788, 0x111f1bcc, 0x00e50ba2, 0x1f858f72, 0x098c9fcd, 0x18b9b5bc, 0xae2207}}, - {{0x0450fa6f, 0x079e6b34, 0x153e225a, 0x10f6fa6f, 0x17060fca, 0x092291d6, 0x1dc6b532, 0x0a2180f3, 0xea91fe}}}, - /* 5*16^54*G: */ - {{{0x0efca824, 0x0080c880, 0x08593eb9, 0x1c189fd4, 0x05461e0b, 0x0a08032c, 0x139673b1, 0x0195ae55, 0xcb8ded}}, - {{0x0f227361, 0x0a05e82c, 0x04c5d0bc, 0x1a3fbf8f, 0x0cbc496a, 0x16243d16, 0x03216cc5, 0x11ee81b1, 0x33a500}}}, - /* 7*16^54*G: */ - {{{0x1bcbd327, 0x008da6d1, 0x192abba5, 0x1c10a443, 0x16ed6b04, 0x04806bf3, 0x0d9c23a5, 0x05315e30, 0xb0c53b}}, - {{0x0d7be436, 0x10b5e251, 0x18da83c5, 0x144418e8, 0x0b2afd82, 0x15300db3, 0x1a858e3d, 0x0803f7af, 0xee2a97}}}, - /* 9*16^54*G: */ - {{{0x17b836a1, 0x1377db1c, 0x19e7bdf7, 0x0c80bfee, 0x1f526c80, 0x0317673b, 0x04835362, 0x07e653b7, 0x6f6ba7}}, - {{0x06832b84, 0x0def97d8, 0x187fe4d3, 0x1218a511, 0x0e9c0d89, 0x1c5202df, 0x0638f6c2, 0x02ffebf8, 0xdc778a}}}, - /* 11*16^54*G: */ - {{{0x1eb39ede, 0x1771a104, 0x0184b50a, 0x110065ae, 0x04360310, 0x1a33f081, 0x0bd2ef3f, 0x0fb8e845, 0x7d471a}}, - {{0x0bf6607e, 0x04de6ca5, 0x08aa3f43, 0x0b9efa38, 0x086fa779, 0x1118f7fe, 0x15941ee0, 0x033e74d0, 0x4a7b}}}, - /* 13*16^54*G: */ - {{{0x165eb1c1, 0x0b6a4f12, 0x0b9716a9, 0x1aeacf9b, 0x0f0967fc, 0x1e618cc1, 0x1eb1c1c3, 0x0c7f36e7, 0xf00251}}, - {{0x0dde2ae0, 0x0d3eb823, 0x1344795f, 0x0dfeb9ad, 0x0afed857, 0x1a52ed13, 0x037d319a, 0x1d1107a4, 0x54ea9}}}, - /* 15*16^54*G: */ - {{{0x1ac32c64, 0x079a849e, 0x0b92fe13, 0x0dfc07d9, 0x1715e0e3, 0x074a87a3, 0x0ea83dc1, 0x00003da4, 0xac1214}}, - {{0x1eb1a867, 0x00956dd6, 0x14fc2262, 0x14ba74b4, 0x099a3ed5, 0x1b4ab982, 0x0ebc61c2, 0x19758671, 0xce8ebc}}} - }, - { - /* 1*16^55*G: */ - {{{0x0319497c, 0x179c16f4, 0x09423008, 0x1363f4a2, 0x1cab15d5, 0x12b73489, 0x161cb4e7, 0x17393450, 0x174a53}}, - {{0x079afa73, 0x1ed09d60, 0x0e6150e0, 0x16743b19, 0x1f9e6646, 0x0aaf9623, 0x10595ed0, 0x06f57f93, 0xccc9dc}}}, - /* 3*16^55*G: */ - {{{0x154b8367, 0x0a4039eb, 0x1541affa, 0x0b6efacf, 0x16a5db77, 0x12ea2c21, 0x09b9032a, 0x095c88ca, 0x5e5a09}}, - {{0x11ce85ca, 0x0994d4ec, 0x197fe911, 0x1553de7b, 0x04b7a796, 0x00f8ab95, 0x18170b24, 0x19348f2b, 0xae8af8}}}, - /* 5*16^55*G: */ - {{{0x17b10d9d, 0x0cc2bc9c, 0x07e3f2bc, 0x0a5e313a, 0x0b82de6a, 0x05c19886, 0x1a1677b3, 0x15b72e05, 0xd4e0}}, - {{0x1140dced, 0x01080243, 0x18b648fa, 0x113192f1, 0x087f70b5, 0x1c232191, 0x10251f4b, 0x130306ec, 0x87b801}}}, - /* 7*16^55*G: */ - {{{0x1c9caee8, 0x0408d304, 0x089c2ec8, 0x1c408b63, 0x0a667632, 0x10cd7762, 0x1303dbde, 0x026d1dee, 0x36652}}, - {{0x1772b711, 0x14f6351d, 0x056e9fc2, 0x17531265, 0x0944501c, 0x1e340dd3, 0x1b666527, 0x0565527b, 0x1f18c3}}}, - /* 9*16^55*G: */ - {{{0x1446c85c, 0x1ffcba8c, 0x007018d4, 0x0fbc11cc, 0x0c6eade3, 0x1b6229fb, 0x1c7ea819, 0x00adfb71, 0xe5891}}, - {{0x0148972e, 0x1b63bf39, 0x1376b757, 0x00469d01, 0x01898f49, 0x00c5d7a4, 0x0f683b1d, 0x0be23f4f, 0xe39a48}}}, - /* 11*16^55*G: */ - {{{0x022e1259, 0x18c56f9e, 0x004d8abf, 0x0e73480d, 0x17771931, 0x1afd5003, 0x18fcadb3, 0x0da4de28, 0xa74012}}, - {{0x134a5f43, 0x1bb8b921, 0x075b6b57, 0x0cbcea76, 0x07ee5178, 0x18ba533e, 0x07fc6c17, 0x175e329e, 0x5a9ff}}}, - /* 13*16^55*G: */ - {{{0x0b08f1fe, 0x0450bd1e, 0x0821eff6, 0x1cfdce17, 0x0d177d7c, 0x02bb2ec1, 0x13929950, 0x042db28e, 0x87e4b8}}, - {{0x0ed5e2ec, 0x0fba564d, 0x1e1b675d, 0x1e47b7ac, 0x0c2cc6e2, 0x1fc7517c, 0x033b35f5, 0x180ecc69, 0xf74e3a}}}, - /* 15*16^55*G: */ - {{{0x01c4d15c, 0x14380735, 0x1039d2e6, 0x1d4f7e0f, 0x14a44105, 0x11606092, 0x0696a4b0, 0x08c7fd4f, 0x35ea1b}}, - {{0x1f3fe1ea, 0x049cdd7b, 0x194257b7, 0x06754060, 0x13b185d6, 0x1d2feba6, 0x0b35e223, 0x0ca373da, 0xad2191}}} - }, - { - /* 1*16^56*G: */ - {{{0x1475b7ba, 0x027eff84, 0x0462cf62, 0x13ce61c9, 0x18cdbe03, 0x0bf6fa80, 0x0170f4f9, 0x1303286f, 0x959396}}, - {{0x1524f2fd, 0x0dc55fc3, 0x1c24e17a, 0x0a7ec990, 0x0d6849c6, 0x1c3525ce, 0x07762e80, 0x05111866, 0x2e7e55}}}, - /* 3*16^56*G: */ - {{{0x0fd69985, 0x04e2eec8, 0x17dcaba8, 0x013996db, 0x0cf149f3, 0x1486fde6, 0x1df9e23d, 0x0eb9d6e5, 0xae976}}, - {{0x1409a003, 0x0e475a08, 0x1b86bfe2, 0x133a82f5, 0x054c5d0b, 0x0ff7028d, 0x1453a6e3, 0x0e7edc91, 0x912199}}}, - /* 5*16^56*G: */ - {{{0x19262b90, 0x0e0c9efe, 0x0f30a6a7, 0x078983fc, 0x05d1fb72, 0x0f8bbc01, 0x04bb26d9, 0x054b582c, 0x2b1586}}, - {{0x083d7557, 0x08ccb732, 0x05226926, 0x0692e1f3, 0x149066f5, 0x06a96d43, 0x0fea9e8c, 0x07b54146, 0x2eb005}}}, - /* 7*16^56*G: */ - {{{0x08e7be40, 0x1fcb8a65, 0x0103b964, 0x05912922, 0x0769af2d, 0x0e0b0b72, 0x199dfba5, 0x1da352dd, 0x6af9ea}}, - {{0x0e387e1c, 0x120b7013, 0x1d655a7e, 0x07eccd41, 0x1dc8145e, 0x152141a3, 0x19259c27, 0x022d200c, 0xb3812a}}}, - /* 9*16^56*G: */ - {{{0x1482801e, 0x135b7d07, 0x0f505574, 0x129f178b, 0x0d6f9407, 0x15a1265c, 0x113bacea, 0x1dc08882, 0x596668}}, - {{0x04870c37, 0x03b8a478, 0x14d4d6b5, 0x0d8396c7, 0x1304e8db, 0x1cb043b8, 0x1d7c7b23, 0x150b775d, 0x949aa0}}}, - /* 11*16^56*G: */ - {{{0x032c19fd, 0x064d973e, 0x000a30f9, 0x1571d20b, 0x10b5b4ac, 0x068bd5ab, 0x01d8bf7d, 0x11036a0a, 0xbe84d1}}, - {{0x12f1281f, 0x1b4a529b, 0x14370dd9, 0x0b4feabc, 0x03994795, 0x12fa4184, 0x02513479, 0x19665b8a, 0xeff960}}}, - /* 13*16^56*G: */ - {{{0x16c69482, 0x08dbafea, 0x0be859ef, 0x156a8026, 0x0bc88cbe, 0x193a6579, 0x1b9507d5, 0x062981af, 0x9867a0}}, - {{0x0f792cd7, 0x178308a3, 0x158a2a45, 0x048b9ea2, 0x099639e6, 0x16aad8dd, 0x0d3e71e4, 0x0b476210, 0xd02e61}}}, - /* 15*16^56*G: */ - {{{0x1d557aa1, 0x0511cec8, 0x007f0a5e, 0x1b25fd9a, 0x1d6abdf1, 0x1975004c, 0x0569649f, 0x08a81b10, 0xa866f2}}, - {{0x01430634, 0x0c0ddda6, 0x184692de, 0x16d38cf8, 0x0e13961e, 0x0c7d2ed8, 0x0d135e4f, 0x1ed50045, 0xb58739}}} - }, - { - /* 1*16^57*G: */ - {{{0x1d82b151, 0x1a89a064, 0x07ee8b6e, 0x01487aac, 0x09a8fcca, 0x108a9d88, 0x195b5916, 0x0a15c803, 0xd2a63a}}, - {{0x1cf89405, 0x00a10ba6, 0x013294b5, 0x1eea15e9, 0x08220a70, 0x172c594a, 0x12dd596b, 0x1f6c887f, 0xe82d86}}}, - /* 3*16^57*G: */ - {{{0x0e4b3ba0, 0x02cfb1af, 0x02fc7c5e, 0x157debe3, 0x1245f5c2, 0x0b8798df, 0x0dcefbf8, 0x00a443ff, 0x410811}}, - {{0x17525595, 0x034b0ee0, 0x08191552, 0x0c930acb, 0x18498133, 0x12d70eb5, 0x19a3cb29, 0x0d2edfea, 0xdc37f3}}}, - /* 5*16^57*G: */ - {{{0x13d98ded, 0x114e4dc4, 0x02808611, 0x1e450677, 0x1d65edbd, 0x114c8298, 0x0323233c, 0x02142d98, 0x63a2a2}}, - {{0x00d1cfc2, 0x0c8cbea7, 0x11e1ce94, 0x17ed4013, 0x194461fa, 0x01992a76, 0x1dbf4194, 0x1c5cffd8, 0x882b42}}}, - /* 7*16^57*G: */ - {{{0x18045445, 0x1430d285, 0x07a35fba, 0x1ee6e320, 0x1bef080f, 0x17172eab, 0x1f28f7c4, 0x0ba893ac, 0xc1581}}, - {{0x0054a206, 0x0a7c3eaa, 0x1633a8c8, 0x00a9c86c, 0x1cd28ba3, 0x1e1db331, 0x045742a4, 0x01475d28, 0x2f30d6}}}, - /* 9*16^57*G: */ - {{{0x03857faf, 0x14891ce6, 0x05865c07, 0x1f95bc3d, 0x0ef7f882, 0x1d47a414, 0x0a70355e, 0x0d7135d1, 0xc757eb}}, - {{0x00584ca4, 0x0eced865, 0x06253040, 0x1a621a20, 0x0c61b627, 0x14f9045f, 0x1cd895cd, 0x19f9a47f, 0xf03a59}}}, - /* 11*16^57*G: */ - {{{0x1459225d, 0x1268c27a, 0x02163443, 0x15eefc5b, 0x002a244f, 0x0a04c8ce, 0x0343e057, 0x15d5b5f4, 0xfa8063}}, - {{0x1ece1507, 0x115bf1db, 0x01f1670e, 0x16d86da0, 0x0425c0a2, 0x11104126, 0x01a45837, 0x120af818, 0xba71f}}}, - /* 13*16^57*G: */ - {{{0x16f0d044, 0x13cff4a0, 0x079869e5, 0x153fa921, 0x0e0a5aab, 0x14e7c317, 0x1ea278bd, 0x18b3a04a, 0x658ca3}}, - {{0x17cb872d, 0x0e8f9977, 0x0633a26b, 0x02cec788, 0x1d37655a, 0x0eb1389d, 0x15183c59, 0x06ef5d44, 0xae5cc1}}}, - /* 15*16^57*G: */ - {{{0x1696756d, 0x1ad64ab9, 0x158505ea, 0x15e2c0ac, 0x1305c676, 0x0cc1ae9f, 0x0aeb3930, 0x0955f23e, 0x31c94b}}, - {{0x1e08ae78, 0x1e22f46e, 0x0b441c2a, 0x1dbf95cd, 0x0160683a, 0x05acd57a, 0x0ea6fd2e, 0x096aadd0, 0xf80f88}}} - }, - { - /* 1*16^58*G: */ - {{{0x1617e073, 0x01b7cda2, 0x0e4c5ecd, 0x197b7a70, 0x1dc866ba, 0x1c4b6be7, 0x1ae243b9, 0x0466a8e3, 0x64587e}}, - {{0x1faf6589, 0x014cf2f4, 0x0ebaacd6, 0x12147226, 0x099a185b, 0x0eb223e1, 0x0b8aba5b, 0x1ab7ed20, 0xd99fcd}}}, - /* 3*16^58*G: */ - {{{0x0e103dd6, 0x0fb8a390, 0x0012166b, 0x0c0980f8, 0x0a17ac34, 0x09e7e2c9, 0x0fe0da88, 0x1aab4840, 0xbc477b}}, - {{0x16f7c343, 0x1c8416c6, 0x0ed12b18, 0x126ae58c, 0x0a6395d2, 0x02a9636f, 0x1549b2eb, 0x0485351b, 0xe31e1e}}}, - /* 5*16^58*G: */ - {{{0x144eab31, 0x086e1d86, 0x198d241f, 0x0b5e2809, 0x1f8e80ac, 0x1a11933d, 0x00e00c0e, 0x1fcb4d77, 0x589db4}}, - {{0x11361f6a, 0x00d75f3a, 0x123e36e5, 0x021caa42, 0x190f31f6, 0x0310125e, 0x0a93d81c, 0x0b821154, 0x625544}}}, - /* 7*16^58*G: */ - {{{0x1a0c2c41, 0x1fcb0b94, 0x00cdb19e, 0x063838ac, 0x0db7c428, 0x0c3056b7, 0x1e8baa28, 0x06fa2dc5, 0x1339b3}}, - {{0x09f1bc2b, 0x02f82a5d, 0x1a48e906, 0x044ff0fb, 0x1a3406b1, 0x1207e889, 0x196e9e8c, 0x0c6c58f5, 0x9f9b29}}}, - /* 9*16^58*G: */ - {{{0x18fc47af, 0x18258fc2, 0x10337b0d, 0x1bfd9065, 0x1b568a8d, 0x194da800, 0x1c5f3140, 0x14226c79, 0x7ff3bb}}, - {{0x19ba43a7, 0x1c30b267, 0x02eb0a5f, 0x0a7635a3, 0x1446b17a, 0x048dba39, 0x18a682f2, 0x15d00314, 0x1f6ba7}}}, - /* 11*16^58*G: */ - {{{0x15213775, 0x0c26b004, 0x150aa640, 0x08527647, 0x07f6100b, 0x149caff6, 0x02ed8507, 0x08c79d6c, 0x8ec670}}, - {{0x1841ffff, 0x100879f3, 0x13d47a43, 0x15314179, 0x1ee0e71d, 0x01a0ae76, 0x0f8c1b99, 0x0df41b4b, 0x8f58a6}}}, - /* 13*16^58*G: */ - {{{0x1452abbb, 0x059ffc69, 0x0e570b8f, 0x154e5fc5, 0x1d495ae6, 0x161ff6ca, 0x06ee276e, 0x16883ce0, 0x83de61}}, - {{0x054eb66e, 0x1028bb58, 0x1390a462, 0x18be6d77, 0x01563d79, 0x1b57c627, 0x027e9afe, 0x0694698c, 0x32f0e3}}}, - /* 15*16^58*G: */ - {{{0x159276f1, 0x0b446137, 0x085ec57a, 0x1c4b6525, 0x0d86833a, 0x1ae007be, 0x076c6a2e, 0x1131ea18, 0x3d7663}}, - {{0x059cbcb3, 0x0f2532dc, 0x0c65e180, 0x03304033, 0x0333ef32, 0x171a6d6c, 0x176d825d, 0x0e6f430f, 0xd37669}}} - }, - { - /* 1*16^59*G: */ - {{{0x1d45e458, 0x0c6b6436, 0x1439ff4d, 0x154d9d44, 0x1de042f0, 0x0369f2a4, 0x0216ce95, 0x1c1c9c9b, 0x8481bd}}, - {{0x1779057e, 0x0b258dac, 0x098b955b, 0x14f38856, 0x0b2ca900, 0x0df9ce76, 0x13761289, 0x11974a80, 0x38ee7b}}}, - /* 3*16^59*G: */ - {{{0x152da17d, 0x10507d20, 0x14191ac5, 0x1827b611, 0x00bc811d, 0x13582ff0, 0x117c2253, 0x03c1ea31, 0x3beaed}}, - {{0x0cc768d2, 0x13824c2f, 0x1fb105b3, 0x1bcef71b, 0x0e1b554c, 0x145f5f40, 0x0b37fbd2, 0x1eab5fef, 0xc3b0d7}}}, - /* 5*16^59*G: */ - {{{0x1a4edcc5, 0x1a7d0bed, 0x00c46dc8, 0x1c644284, 0x15997e74, 0x0fe01c93, 0x15861d4b, 0x14197b93, 0x6e73db}}, - {{0x159da0e4, 0x198fbe6e, 0x019e40de, 0x14efb4e0, 0x08693278, 0x0a844441, 0x1122fa91, 0x1f893dd9, 0xee0ac1}}}, - /* 7*16^59*G: */ - {{{0x0a80b979, 0x04e26212, 0x06aecc50, 0x01ebf465, 0x1c310049, 0x0cbf5523, 0x1649d89f, 0x1126fcb6, 0x7706dd}}, - {{0x0126cfde, 0x067a4082, 0x1fbf8c85, 0x141469fa, 0x09c7117f, 0x0ebcc8f5, 0x1c51dde3, 0x104fab76, 0x8a02a9}}}, - /* 9*16^59*G: */ - {{{0x126fe285, 0x06dce25b, 0x0fbc49f7, 0x17585282, 0x1e06aa45, 0x1e3d20fc, 0x03e034bc, 0x18b25378, 0x16d422}}, - {{0x0155b441, 0x1f07ff36, 0x0d93508c, 0x1e18226e, 0x131b1e93, 0x1f34d31a, 0x1906a2ad, 0x1f4a3c44, 0xdf888}}}, - /* 11*16^59*G: */ - {{{0x1acfa513, 0x11885e55, 0x1838ebab, 0x080f3f34, 0x16a9c4e2, 0x1a23a87a, 0x158ea968, 0x08fdd8ed, 0x1fcc0e}}, - {{0x107afc9c, 0x083add20, 0x060e461d, 0x1bdba2c9, 0x1f9b44be, 0x1c3aa19c, 0x11e2d238, 0x14083c6a, 0x165dc1}}}, - /* 13*16^59*G: */ - {{{0x1edc69b2, 0x0d1383e7, 0x1addc5c8, 0x14c11364, 0x0a386a50, 0x01821ae5, 0x0285cc19, 0x0e75ad97, 0xc12b90}}, - {{0x00dd41dd, 0x19574d81, 0x09eca800, 0x1c3b7c7a, 0x14976b8e, 0x16e44fc4, 0x17c765dc, 0x07fca699, 0x3173c4}}}, - /* 15*16^59*G: */ - {{{0x1961fe4d, 0x1b7ac2ef, 0x041c65ea, 0x0910df16, 0x0a73e8a1, 0x14989896, 0x0a3e8fcf, 0x10bb864f, 0xd059bf}}, - {{0x0ae823c2, 0x0c9ad833, 0x16ad932e, 0x111743e9, 0x155b4fd3, 0x1b2ee424, 0x0978ff81, 0x0c18116a, 0x45107a}}} - }, - { - /* 1*16^60*G: */ - {{{0x0caf666b, 0x06b181fb, 0x0c738c2f, 0x19fda789, 0x1f4637ff, 0x0bcd740b, 0x0aa98ada, 0x0af4f020, 0x13464a}}, - {{0x1f6ecc27, 0x1a4ad483, 0x0250b84f, 0x0601503a, 0x0b0ca48f, 0x019a29e6, 0x1603bdf9, 0x12008c28, 0x69be15}}}, - /* 3*16^60*G: */ - {{{0x0eca5f51, 0x10b5904c, 0x1f26bafc, 0x142e3729, 0x1b5cfdde, 0x0b595f82, 0x1a58b1bb, 0x029bb3dc, 0xdde9d5}}, - {{0x10c638f7, 0x16b4d39e, 0x0255c7e6, 0x1dd7d1bd, 0x18f0950f, 0x19a5853f, 0x04476247, 0x02679c50, 0xb84e69}}}, - /* 5*16^60*G: */ - {{{0x199c88e4, 0x1c83582c, 0x0b51bb0b, 0x1aa27c41, 0x04b179ae, 0x00375890, 0x0dcdba7d, 0x02046d32, 0xfd1a62}}, - {{0x195bc8df, 0x0e4648b2, 0x13003ca6, 0x16e3a92b, 0x17782dc6, 0x0034aa4b, 0x082fec4f, 0x0a973918, 0x1ac97b}}}, - /* 7*16^60*G: */ - {{{0x1f8018ce, 0x0a8adada, 0x024b5a2f, 0x1b4ae71b, 0x06dc7cf2, 0x1e727964, 0x1ae7c4ff, 0x063b1852, 0x4ee485}}, - {{0x1e48381f, 0x01ad30d8, 0x1a01804f, 0x0e92e369, 0x1c5e6710, 0x02046863, 0x02d7f1ed, 0x1a90217f, 0xb68f9e}}}, - /* 9*16^60*G: */ - {{{0x11473678, 0x1429748f, 0x10e4bdc0, 0x00af2a12, 0x0c070cba, 0x18a62adc, 0x036ffd78, 0x13869880, 0xfd76cc}}, - {{0x0d144f4f, 0x10b27754, 0x007210df, 0x051ddc28, 0x12cd6606, 0x10539e81, 0x0f6b83fb, 0x086f0e28, 0xf20465}}}, - /* 11*16^60*G: */ - {{{0x0d7a2193, 0x1805b8a4, 0x05d51bb7, 0x123c89e3, 0x0ea212c6, 0x07413a5a, 0x1008679b, 0x14662476, 0x85a2ab}}, - {{0x10cdcf3a, 0x18641ea5, 0x0ec4909f, 0x0db5bf38, 0x0029fe1c, 0x104168e6, 0x145b60b1, 0x0afd6560, 0x9c1298}}}, - /* 13*16^60*G: */ - {{{0x177568b0, 0x0d8182d9, 0x180ec14d, 0x1d1ba033, 0x1650f35a, 0x16b62bc1, 0x19d1102d, 0x1f8e79ff, 0xd25ddb}}, - {{0x1f39929c, 0x0509c936, 0x0f0fc018, 0x04e7103c, 0x1d92b832, 0x17ba66f3, 0x024e2fab, 0x0eb27f09, 0x7a3aff}}}, - /* 15*16^60*G: */ - {{{0x071d7c13, 0x0ff288c5, 0x03fe8e15, 0x156beff4, 0x0c805641, 0x1f4d4bd5, 0x09c957c1, 0x06287d29, 0x458135}}, - {{0x1aff63cf, 0x11c5e2a9, 0x0f65ae65, 0x000caa85, 0x169c9702, 0x0878bbee, 0x0b62d5fd, 0x1d8292f3, 0x9f5858}}} - }, - { - /* 1*16^61*G: */ - {{{0x0d83f366, 0x16d1d069, 0x1ca16232, 0x1399dbc5, 0x1c97a0cd, 0x0185e60e, 0x18ba6bbd, 0x1eb6e27f, 0xbc4a9d}}, - {{0x181f33c1, 0x1ac6b332, 0x151ec5b5, 0x1153f7f4, 0x198caa6e, 0x1bd6fa5b, 0x1018e0e4, 0x194dcf0b, 0xd3a81}}}, - /* 3*16^61*G: */ - {{{0x1712be3c, 0x03517197, 0x051a99ac, 0x0be31db4, 0x01534729, 0x12edd580, 0x0de34f1c, 0x13b26636, 0x39d734}}, - {{0x1c6ff65c, 0x0180cfa0, 0x09059133, 0x1def4bd9, 0x012e8ef5, 0x1aaa6334, 0x0fdfec49, 0x09eadde7, 0x8f929b}}}, - /* 5*16^61*G: */ - {{{0x1f77f22b, 0x15727de6, 0x1e0b80d6, 0x12e47329, 0x1af26c69, 0x06b614ca, 0x17427947, 0x02fefb83, 0xf0cba6}}, - {{0x1909a03c, 0x1e47163a, 0x08255987, 0x0318fd21, 0x0d04004f, 0x1361b28b, 0x1e627bcc, 0x08627f3b, 0x1a25ab}}}, - /* 7*16^61*G: */ - {{{0x06509c12, 0x0566e6a6, 0x147d1d96, 0x17fc46b7, 0x068ed8d6, 0x18746c39, 0x134c8745, 0x173b642a, 0x381d7a}}, - {{0x0eb46102, 0x03215471, 0x19babdd5, 0x1050b0d9, 0x181e7205, 0x122b9d32, 0x16a7ad74, 0x0b6ffb47, 0xa47aab}}}, - /* 9*16^61*G: */ - {{{0x184fe955, 0x0d9da6b1, 0x18ef3923, 0x12ad4b40, 0x08a53f69, 0x1b9f34a6, 0x1f991e4e, 0x0a8cf570, 0xa703f0}}, - {{0x161344bd, 0x135977de, 0x05c9dfe8, 0x1c2c538b, 0x1199b539, 0x1c96b58c, 0x1eb703a9, 0x06b45608, 0xd500f9}}}, - /* 11*16^61*G: */ - {{{0x06eace58, 0x04cb2b60, 0x1cc8474d, 0x1c8ac745, 0x1a03f1b7, 0x1686b829, 0x035d1b1a, 0x18b55aaa, 0x73c6b3}}, - {{0x0af8654e, 0x0b8548b9, 0x0e34a45c, 0x150f1b77, 0x191c6aa6, 0x04c89b36, 0x1dd06ea4, 0x148e6749, 0x3a2fb4}}}, - /* 13*16^61*G: */ - {{{0x00181d5e, 0x059c45f8, 0x16abc815, 0x03675372, 0x0ddb8de7, 0x069d0e07, 0x1ff68740, 0x1cea0da8, 0xc627f3}}, - {{0x169f886d, 0x13c80531, 0x12f8b0e4, 0x0d600a93, 0x1f7d68ef, 0x0b009c50, 0x098ecb5a, 0x1ae3c885, 0xd78f9d}}}, - /* 15*16^61*G: */ - {{{0x17828b16, 0x07146cfd, 0x09211fcd, 0x0f2b1e51, 0x0de53a04, 0x1a053783, 0x08cfe897, 0x17c1dbfa, 0xbb88fa}}, - {{0x0aea5df7, 0x08a39d10, 0x087a5a71, 0x1502e9c7, 0x19163ec4, 0x02cb008a, 0x0374d177, 0x16609ebd, 0xb73676}}} - }, - { - /* 1*16^62*G: */ - {{{0x05324caa, 0x0a55987f, 0x051ca8e5, 0x16cbc615, 0x0a32e694, 0x063a4a29, 0x0f0348f6, 0x0f7f0531, 0x8c28a9}}, - {{0x0bef9482, 0x138ee39e, 0x072e5167, 0x0f09e08a, 0x0c0eb7ae, 0x16f98fbe, 0x064cde3f, 0x0c74660a, 0x40a304}}}, - /* 3*16^62*G: */ - {{{0x0754dd40, 0x11f438aa, 0x0d19b3e1, 0x044c63f8, 0x0f6e9a24, 0x020fe6b9, 0x1f3d16d2, 0x0e06581b, 0x972924}}, - {{0x0aa36143, 0x025a4979, 0x0b2bd24e, 0x15d0a4ab, 0x0df3690d, 0x03aee5ea, 0x08773457, 0x0884cbfd, 0x91d1a2}}}, - /* 5*16^62*G: */ - {{{0x0c2ca7ff, 0x016c175c, 0x17c0868f, 0x06c8bb2b, 0x127af180, 0x08d6ad17, 0x05b8141e, 0x12eb014f, 0x89637f}}, - {{0x10493e68, 0x16a0af0b, 0x10baadef, 0x178d471c, 0x17489f87, 0x0e78aa1a, 0x109355ee, 0x04919110, 0x2d1fe1}}}, - /* 7*16^62*G: */ - {{{0x0ca8dd7f, 0x0c36b1d0, 0x084e0698, 0x0e5006ad, 0x157421bc, 0x0ed01ea9, 0x1824bf72, 0x1ce37c4b, 0x308138}}, - {{0x0a92c7f2, 0x00af923c, 0x12b64579, 0x0cac8c86, 0x08e18c81, 0x1622e8a0, 0x124978e7, 0x1a51051f, 0x28d1e2}}}, - /* 9*16^62*G: */ - {{{0x066a3fb1, 0x06e62b44, 0x04b881b0, 0x00125033, 0x0862f6ec, 0x1a8642db, 0x0d974795, 0x1d054dbd, 0x575fc4}}, - {{0x102655ad, 0x0dc74854, 0x08ebcbc2, 0x076ae78d, 0x087daed3, 0x0de14bc7, 0x128b59c7, 0x120854df, 0x6f6edb}}}, - /* 11*16^62*G: */ - {{{0x190117df, 0x0db54523, 0x08040a48, 0x0a77779c, 0x02a8fda2, 0x137c0f75, 0x0de889fc, 0x06d6c9d5, 0xa5ec90}}, - {{0x186462fe, 0x0094099f, 0x0528d8f6, 0x08c3e0ac, 0x1a0f71c8, 0x1cc1d68f, 0x01003165, 0x0c4bd828, 0xb79dc6}}}, - /* 13*16^62*G: */ - {{{0x172ad712, 0x187cbae0, 0x0411ca42, 0x131961bc, 0x149b95a3, 0x1cbb0a31, 0x0c252779, 0x1d226621, 0xa153df}}, - {{0x0d48fdd2, 0x052fb29c, 0x1c2cca72, 0x0a7e50b5, 0x1b89abd0, 0x0c6af8f8, 0x0cbf120c, 0x0827f60b, 0xfd94d8}}}, - /* 15*16^62*G: */ - {{{0x11cf5b3a, 0x1861b808, 0x0d69e040, 0x0675077b, 0x1c824c44, 0x1a684476, 0x18564d70, 0x18d5ef28, 0x9a541a}}, - {{0x16a44ae4, 0x1faabaf9, 0x07a94b58, 0x11fdc4a4, 0x1475f554, 0x0d79b447, 0x0adf2bf8, 0x1839620d, 0xb66148}}} - }, - { - /* 1*16^63*G: */ - {{{0x1faccae0, 0x0625d088, 0x12eccdd2, 0x166a18b4, 0x11fd23c8, 0x0a672783, 0x1ea30776, 0x0cc272a4, 0x8ea96}}, - {{0x0e62b945, 0x0d79a518, 0x1c3e3a55, 0x0f077d39, 0x1c5d735b, 0x1e067dca, 0x1e0b8939, 0x17791dc4, 0x620efa}}}, - /* 3*16^63*G: */ - {{{0x06a06f5e, 0x0d205acb, 0x0820dc08, 0x0324a2dd, 0x1b716a34, 0x06a10931, 0x14eb0dec, 0x1f7d4284, 0x383b24}}, - {{0x13c6e772, 0x04fa3c36, 0x030ac102, 0x0d5ce977, 0x05a19e8f, 0x0b36aa75, 0x0881133d, 0x0d589db7, 0x54cf70}}}, - /* 5*16^63*G: */ - {{{0x0638a136, 0x1cbae0ea, 0x00e06571, 0x1a39c66d, 0x1790c2b0, 0x0ce35b06, 0x15b5e279, 0x1a07c05d, 0xe68432}}, - {{0x0c6c2584, 0x17e8c084, 0x0d5ccdaa, 0x13c7d7b6, 0x1e6472e0, 0x13981d00, 0x0998934a, 0x02731c6b, 0xca5be4}}}, - /* 7*16^63*G: */ - {{{0x16e8c10c, 0x0743e220, 0x06f5a01f, 0x0530be72, 0x06e7fb47, 0x1ef67398, 0x10a83bbe, 0x0b3c5fcb, 0x395dd5}}, - {{0x05fe638e, 0x01eb6c98, 0x19a48b2f, 0x013809b8, 0x04e274c9, 0x1f43d7fd, 0x0b174104, 0x1a968b25, 0xfd62dc}}}, - /* 9*16^63*G: */ - {{{0x0d6c14ef, 0x0b326d90, 0x1e4f23d4, 0x11f8ea0d, 0x06480085, 0x16adf771, 0x172e7dbb, 0x1b86aa4b, 0x7a514a}}, - {{0x0b3fbd13, 0x169f0c38, 0x09b893de, 0x1d5dee55, 0x15c3729b, 0x185ca647, 0x1363a25f, 0x1fda2a5c, 0x56edd1}}}, - /* 11*16^63*G: */ - {{{0x0f6d65eb, 0x14a31e82, 0x10085492, 0x1220eea8, 0x08f235a9, 0x179dfa48, 0x04363aa3, 0x0b0864bb, 0x1ee1fd}}, - {{0x1d22941c, 0x101b5c92, 0x0d60ac47, 0x05e9d30c, 0x0fcfaca3, 0x1cfe27a1, 0x1cf073ea, 0x1237bad8, 0xbb6928}}}, - /* 13*16^63*G: */ - {{{0x0e36cb44, 0x18ca8778, 0x12880647, 0x1d16d91b, 0x1357d617, 0x07e86c41, 0x0aa2f016, 0x069a71f5, 0x155156}}, - {{0x1f495a68, 0x14d7a328, 0x0830794c, 0x06e2ebe0, 0x1205da8c, 0x11f85a31, 0x08578dc3, 0x18eaaaea, 0xab4fff}}}, - /* 15*16^63*G: */ - {{{0x1427bacc, 0x0fca3083, 0x0b58b854, 0x0662c9ba, 0x1c4aa9e7, 0x07584ac6, 0x0804d8d6, 0x0c88d7ea, 0x3bc6bc}}, - {{0x0ad6fda6, 0x0619feaf, 0x02fad1c5, 0x16eb4a45, 0x0c02bd71, 0x1771136b, 0x0c1736d8, 0x180e2ed8, 0x8e305c}}} - }, diff --git a/trezor-crypto/crypto/setup.py b/trezor-crypto/crypto/setup.py deleted file mode 100755 index aee3dd4a928..00000000000 --- a/trezor-crypto/crypto/setup.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python -from distutils.core import setup -from distutils.extension import Extension - -from Cython.Build import cythonize -from Cython.Distutils import build_ext - -srcs = [ - "nist256p1", - "base58", - "bignum", - "bip32", - "ecdsa", - "curve25519", - "hmac", - "rand", - "ripemd160", - "secp256k1", - "sha2", -] - -extensions = [ - Extension( - "TrezorCrypto", - sources=["TrezorCrypto.pyx", "c.pxd"] + [x + ".c" for x in srcs], - extra_compile_args=[], - ) -] - -setup( - name="TrezorCrypto", - version="0.0.0", - description="Cython wrapper around trezor-crypto library", - author="Pavol Rusnak", - author_email="stick@satoshilabs.com", - url="https://github.com/trezor/trezor-crypto", - cmdclass={"build_ext": build_ext}, - ext_modules=cythonize(extensions), -) diff --git a/trezor-crypto/crypto/sha2.c b/trezor-crypto/crypto/sha2.c deleted file mode 100644 index 47d7eebbcdb..00000000000 --- a/trezor-crypto/crypto/sha2.c +++ /dev/null @@ -1,1317 +0,0 @@ -/** - * Copyright (c) 2000-2001 Aaron D. Gifford - * Copyright (c) 2013-2014 Pavol Rusnak - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the copyright holder nor the names of contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTOR(S) BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#include -#include -#include -#include - -/* - * ASSERT NOTE: - * Some sanity checking code is included using assert(). On my FreeBSD - * system, this additional code can be removed by compiling with NDEBUG - * defined. Check your own systems manpage on assert() to see how to - * compile WITHOUT the sanity checking code on your system. - * - * UNROLLED TRANSFORM LOOP NOTE: - * You can define SHA2_UNROLL_TRANSFORM to use the unrolled transform - * loop version for the hash transform rounds (defined using macros - * later in this file). Either define on the command line, for example: - * - * cc -DSHA2_UNROLL_TRANSFORM -o sha2 sha2.c sha2prog.c - * - * or define below: - * - * #define SHA2_UNROLL_TRANSFORM - * - */ - - -/*** SHA-256/384/512 Machine Architecture Definitions *****************/ -/* - * BYTE_ORDER NOTE: - * - * Please make sure that your system defines BYTE_ORDER. If your - * architecture is little-endian, make sure it also defines - * LITTLE_ENDIAN and that the two (BYTE_ORDER and LITTLE_ENDIAN) are - * equivilent. - * - * If your system does not define the above, then you can do so by - * hand like this: - * - * #define LITTLE_ENDIAN 1234 - * #define BIG_ENDIAN 4321 - * - * And for little-endian machines, add: - * - * #define BYTE_ORDER LITTLE_ENDIAN - * - * Or for big-endian machines: - * - * #define BYTE_ORDER BIG_ENDIAN - * - * The FreeBSD machine this was written on defines BYTE_ORDER - * appropriately by including (which in turn includes - * where the appropriate definitions are actually - * made). - */ - -#if !defined(BYTE_ORDER) || (BYTE_ORDER != LITTLE_ENDIAN && BYTE_ORDER != BIG_ENDIAN) -#error Define BYTE_ORDER to be equal to either LITTLE_ENDIAN or BIG_ENDIAN -#endif - -typedef uint8_t sha2_byte; /* Exactly 1 byte */ -typedef uint32_t sha2_word32; /* Exactly 4 bytes */ -typedef uint64_t sha2_word64; /* Exactly 8 bytes */ - -/*** SHA-256/384/512 Various Length Definitions ***********************/ -/* NOTE: Most of these are in sha2.h */ -#define SHA1_SHORT_BLOCK_LENGTH (SHA1_BLOCK_LENGTH - 8) -#define SHA256_SHORT_BLOCK_LENGTH (SHA256_BLOCK_LENGTH - 8) -#define SHA512_SHORT_BLOCK_LENGTH (SHA512_BLOCK_LENGTH - 16) - -/* - * Macro for incrementally adding the unsigned 64-bit integer n to the - * unsigned 128-bit integer (represented using a two-element array of - * 64-bit words): - */ -#define ADDINC128(w,n) { \ - (w)[0] += (sha2_word64)(n); \ - if ((w)[0] < (n)) { \ - (w)[1]++; \ - } \ -} - -#define MEMCPY_BCOPY(d,s,l) memcpy((d), (s), (l)) - -/*** THE SIX LOGICAL FUNCTIONS ****************************************/ -/* - * Bit shifting and rotation (used by the six SHA-XYZ logical functions: - * - * NOTE: In the original SHA-256/384/512 document, the shift-right - * function was named R and the rotate-right function was called S. - * (See: http://csrc.nist.gov/cryptval/shs/sha256-384-512.pdf on the - * web.) - * - * The newer NIST FIPS 180-2 document uses a much clearer naming - * scheme, SHR for shift-right, ROTR for rotate-right, and ROTL for - * rotate-left. (See: - * http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf - * on the web.) - * - * WARNING: These macros must be used cautiously, since they reference - * supplied parameters sometimes more than once, and thus could have - * unexpected side-effects if used without taking this into account. - */ - -/* Shift-right (used in SHA-256, SHA-384, and SHA-512): */ -#define SHR(b,x) ((x) >> (b)) -/* 32-bit Rotate-right (used in SHA-256): */ -#define ROTR32(b,x) (((x) >> (b)) | ((x) << (32 - (b)))) -/* 64-bit Rotate-right (used in SHA-384 and SHA-512): */ -#define ROTR64(b,x) (((x) >> (b)) | ((x) << (64 - (b)))) -/* 32-bit Rotate-left (used in SHA-1): */ -#define ROTL32(b,x) (((x) << (b)) | ((x) >> (32 - (b)))) - -/* Two of six logical functions used in SHA-1, SHA-256, SHA-384, and SHA-512: */ -#define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z))) -#define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) - -/* Function used in SHA-1: */ -#define Parity(x,y,z) ((x) ^ (y) ^ (z)) - -/* Four of six logical functions used in SHA-256: */ -#define Sigma0_256(x) (ROTR32(2, (x)) ^ ROTR32(13, (x)) ^ ROTR32(22, (x))) -#define Sigma1_256(x) (ROTR32(6, (x)) ^ ROTR32(11, (x)) ^ ROTR32(25, (x))) -#define sigma0_256(x) (ROTR32(7, (x)) ^ ROTR32(18, (x)) ^ SHR(3 , (x))) -#define sigma1_256(x) (ROTR32(17, (x)) ^ ROTR32(19, (x)) ^ SHR(10, (x))) - -/* Four of six logical functions used in SHA-384 and SHA-512: */ -#define Sigma0_512(x) (ROTR64(28, (x)) ^ ROTR64(34, (x)) ^ ROTR64(39, (x))) -#define Sigma1_512(x) (ROTR64(14, (x)) ^ ROTR64(18, (x)) ^ ROTR64(41, (x))) -#define sigma0_512(x) (ROTR64( 1, (x)) ^ ROTR64( 8, (x)) ^ SHR( 7, (x))) -#define sigma1_512(x) (ROTR64(19, (x)) ^ ROTR64(61, (x)) ^ SHR( 6, (x))) - -/*** INTERNAL FUNCTION PROTOTYPES *************************************/ -/* NOTE: These should not be accessed directly from outside this - * library -- they are intended for private internal visibility/use - * only. - */ -static void sha512_Last(SHA512_CTX*); - - -/*** SHA-XYZ INITIAL HASH VALUES AND CONSTANTS ************************/ - -/* Hash constant words K for SHA-1: */ -#define K1_0_TO_19 0x5a827999UL -#define K1_20_TO_39 0x6ed9eba1UL -#define K1_40_TO_59 0x8f1bbcdcUL -#define K1_60_TO_79 0xca62c1d6UL - -/* Initial hash value H for SHA-1: */ -const sha2_word32 sha1_initial_hash_value[SHA1_DIGEST_LENGTH / sizeof(sha2_word32)] = { - 0x67452301UL, - 0xefcdab89UL, - 0x98badcfeUL, - 0x10325476UL, - 0xc3d2e1f0UL -}; - -/* Hash constant words K for SHA-256: */ -const sha2_word32 K256[64] = { - 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, - 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, - 0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL, - 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL, - 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, - 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, - 0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, - 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL, - 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL, - 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, - 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, - 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, - 0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, - 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL, - 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, - 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL -}; - -/* Initial hash value H for SHA-256: */ -const sha2_word32 sha256_initial_hash_value[8] = { - 0x6a09e667UL, - 0xbb67ae85UL, - 0x3c6ef372UL, - 0xa54ff53aUL, - 0x510e527fUL, - 0x9b05688cUL, - 0x1f83d9abUL, - 0x5be0cd19UL -}; - -/* Hash constant words K for SHA-384 and SHA-512: */ -const sha2_word64 K512[80] = { - 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, - 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, - 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, - 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, - 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, - 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, - 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, - 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, - 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, - 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, - 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, - 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, - 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, - 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, - 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, - 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, - 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, - 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, - 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, - 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, - 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, - 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, - 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, - 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, - 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, - 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, - 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, - 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, - 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, - 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, - 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, - 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, - 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, - 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, - 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, - 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, - 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, - 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, - 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, - 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL -}; - -/* Initial hash value H for SHA-512 */ -const sha2_word64 sha512_initial_hash_value[8] = { - 0x6a09e667f3bcc908ULL, - 0xbb67ae8584caa73bULL, - 0x3c6ef372fe94f82bULL, - 0xa54ff53a5f1d36f1ULL, - 0x510e527fade682d1ULL, - 0x9b05688c2b3e6c1fULL, - 0x1f83d9abfb41bd6bULL, - 0x5be0cd19137e2179ULL -}; - -// [wallet-core] -const sha2_word64 sha512_256_initial_hash_value[8] = { - 0x22312194fc2bf72cULL, - 0x9f555fa3c84c64c2ULL, - 0x2393b86b6f53b151ULL, - 0x963877195940eabdULL, - 0x96283ee2a88effe3ULL, - 0xbe5e1e2553863992ULL, - 0x2b0199fc2c85b8aaULL, - 0x0eb72ddc81c52ca2ULL, -}; - -/* - * Constant used by SHA256/384/512_End() functions for converting the - * digest to a readable hexadecimal character string: - */ -const char *sha2_hex_digits = "0123456789abcdef"; - - -/*** SHA-1: ***********************************************************/ -void sha1_Init(SHA1_CTX* context) { - MEMCPY_BCOPY(context->state, sha1_initial_hash_value, SHA1_DIGEST_LENGTH); - memzero(context->buffer, SHA1_BLOCK_LENGTH); - context->bitcount = 0; -} - -#ifdef SHA2_UNROLL_TRANSFORM - -/* Unrolled SHA-1 round macros: */ - -#define ROUND1_0_TO_15(a,b,c,d,e) \ - (e) = ROTL32(5, (a)) + Ch((b), (c), (d)) + (e) + \ - K1_0_TO_19 + ( W1[j] = *data++ ); \ - (b) = ROTL32(30, (b)); \ - j++; - -#define ROUND1_16_TO_19(a,b,c,d,e) \ - T1 = W1[(j+13)&0x0f] ^ W1[(j+8)&0x0f] ^ W1[(j+2)&0x0f] ^ W1[j&0x0f]; \ - (e) = ROTL32(5, a) + Ch(b,c,d) + e + K1_0_TO_19 + ( W1[j&0x0f] = ROTL32(1, T1) ); \ - (b) = ROTL32(30, b); \ - j++; - -#define ROUND1_20_TO_39(a,b,c,d,e) \ - T1 = W1[(j+13)&0x0f] ^ W1[(j+8)&0x0f] ^ W1[(j+2)&0x0f] ^ W1[j&0x0f]; \ - (e) = ROTL32(5, a) + Parity(b,c,d) + e + K1_20_TO_39 + ( W1[j&0x0f] = ROTL32(1, T1) ); \ - (b) = ROTL32(30, b); \ - j++; - -#define ROUND1_40_TO_59(a,b,c,d,e) \ - T1 = W1[(j+13)&0x0f] ^ W1[(j+8)&0x0f] ^ W1[(j+2)&0x0f] ^ W1[j&0x0f]; \ - (e) = ROTL32(5, a) + Maj(b,c,d) + e + K1_40_TO_59 + ( W1[j&0x0f] = ROTL32(1, T1) ); \ - (b) = ROTL32(30, b); \ - j++; - -#define ROUND1_60_TO_79(a,b,c,d,e) \ - T1 = W1[(j+13)&0x0f] ^ W1[(j+8)&0x0f] ^ W1[(j+2)&0x0f] ^ W1[j&0x0f]; \ - (e) = ROTL32(5, a) + Parity(b,c,d) + e + K1_60_TO_79 + ( W1[j&0x0f] = ROTL32(1, T1) ); \ - (b) = ROTL32(30, b); \ - j++; - -void sha1_Transform(const sha2_word32* state_in, const sha2_word32* data, sha2_word32* state_out) { - sha2_word32 a = 0, b = 0, c = 0, d = 0, e = 0; - sha2_word32 T1 = 0; - sha2_word32 W1[16] = {0}; - int j = 0; - - /* Initialize registers with the prev. intermediate value */ - a = state_in[0]; - b = state_in[1]; - c = state_in[2]; - d = state_in[3]; - e = state_in[4]; - - j = 0; - - /* Rounds 0 to 15 unrolled: */ - ROUND1_0_TO_15(a,b,c,d,e); - ROUND1_0_TO_15(e,a,b,c,d); - ROUND1_0_TO_15(d,e,a,b,c); - ROUND1_0_TO_15(c,d,e,a,b); - ROUND1_0_TO_15(b,c,d,e,a); - ROUND1_0_TO_15(a,b,c,d,e); - ROUND1_0_TO_15(e,a,b,c,d); - ROUND1_0_TO_15(d,e,a,b,c); - ROUND1_0_TO_15(c,d,e,a,b); - ROUND1_0_TO_15(b,c,d,e,a); - ROUND1_0_TO_15(a,b,c,d,e); - ROUND1_0_TO_15(e,a,b,c,d); - ROUND1_0_TO_15(d,e,a,b,c); - ROUND1_0_TO_15(c,d,e,a,b); - ROUND1_0_TO_15(b,c,d,e,a); - ROUND1_0_TO_15(a,b,c,d,e); - - /* Rounds 16 to 19 unrolled: */ - ROUND1_16_TO_19(e,a,b,c,d); - ROUND1_16_TO_19(d,e,a,b,c); - ROUND1_16_TO_19(c,d,e,a,b); - ROUND1_16_TO_19(b,c,d,e,a); - - /* Rounds 20 to 39 unrolled: */ - ROUND1_20_TO_39(a,b,c,d,e); - ROUND1_20_TO_39(e,a,b,c,d); - ROUND1_20_TO_39(d,e,a,b,c); - ROUND1_20_TO_39(c,d,e,a,b); - ROUND1_20_TO_39(b,c,d,e,a); - ROUND1_20_TO_39(a,b,c,d,e); - ROUND1_20_TO_39(e,a,b,c,d); - ROUND1_20_TO_39(d,e,a,b,c); - ROUND1_20_TO_39(c,d,e,a,b); - ROUND1_20_TO_39(b,c,d,e,a); - ROUND1_20_TO_39(a,b,c,d,e); - ROUND1_20_TO_39(e,a,b,c,d); - ROUND1_20_TO_39(d,e,a,b,c); - ROUND1_20_TO_39(c,d,e,a,b); - ROUND1_20_TO_39(b,c,d,e,a); - ROUND1_20_TO_39(a,b,c,d,e); - ROUND1_20_TO_39(e,a,b,c,d); - ROUND1_20_TO_39(d,e,a,b,c); - ROUND1_20_TO_39(c,d,e,a,b); - ROUND1_20_TO_39(b,c,d,e,a); - - /* Rounds 40 to 59 unrolled: */ - ROUND1_40_TO_59(a,b,c,d,e); - ROUND1_40_TO_59(e,a,b,c,d); - ROUND1_40_TO_59(d,e,a,b,c); - ROUND1_40_TO_59(c,d,e,a,b); - ROUND1_40_TO_59(b,c,d,e,a); - ROUND1_40_TO_59(a,b,c,d,e); - ROUND1_40_TO_59(e,a,b,c,d); - ROUND1_40_TO_59(d,e,a,b,c); - ROUND1_40_TO_59(c,d,e,a,b); - ROUND1_40_TO_59(b,c,d,e,a); - ROUND1_40_TO_59(a,b,c,d,e); - ROUND1_40_TO_59(e,a,b,c,d); - ROUND1_40_TO_59(d,e,a,b,c); - ROUND1_40_TO_59(c,d,e,a,b); - ROUND1_40_TO_59(b,c,d,e,a); - ROUND1_40_TO_59(a,b,c,d,e); - ROUND1_40_TO_59(e,a,b,c,d); - ROUND1_40_TO_59(d,e,a,b,c); - ROUND1_40_TO_59(c,d,e,a,b); - ROUND1_40_TO_59(b,c,d,e,a); - - /* Rounds 60 to 79 unrolled: */ - ROUND1_60_TO_79(a,b,c,d,e); - ROUND1_60_TO_79(e,a,b,c,d); - ROUND1_60_TO_79(d,e,a,b,c); - ROUND1_60_TO_79(c,d,e,a,b); - ROUND1_60_TO_79(b,c,d,e,a); - ROUND1_60_TO_79(a,b,c,d,e); - ROUND1_60_TO_79(e,a,b,c,d); - ROUND1_60_TO_79(d,e,a,b,c); - ROUND1_60_TO_79(c,d,e,a,b); - ROUND1_60_TO_79(b,c,d,e,a); - ROUND1_60_TO_79(a,b,c,d,e); - ROUND1_60_TO_79(e,a,b,c,d); - ROUND1_60_TO_79(d,e,a,b,c); - ROUND1_60_TO_79(c,d,e,a,b); - ROUND1_60_TO_79(b,c,d,e,a); - ROUND1_60_TO_79(a,b,c,d,e); - ROUND1_60_TO_79(e,a,b,c,d); - ROUND1_60_TO_79(d,e,a,b,c); - ROUND1_60_TO_79(c,d,e,a,b); - ROUND1_60_TO_79(b,c,d,e,a); - - /* Compute the current intermediate hash value */ - state_out[0] = state_in[0] + a; - state_out[1] = state_in[1] + b; - state_out[2] = state_in[2] + c; - state_out[3] = state_in[3] + d; - state_out[4] = state_in[4] + e; - - /* Clean up */ - a = b = c = d = e = T1 = 0; -} - -#else /* SHA2_UNROLL_TRANSFORM */ - -void sha1_Transform(const sha2_word32* state_in, const sha2_word32* data, sha2_word32* state_out) { - sha2_word32 a = 0, b = 0, c = 0, d = 0, e = 0; - sha2_word32 T1 = 0; - sha2_word32 W1[16] = {0}; - int j = 0; - - /* Initialize registers with the prev. intermediate value */ - a = state_in[0]; - b = state_in[1]; - c = state_in[2]; - d = state_in[3]; - e = state_in[4]; - j = 0; - do { - T1 = ROTL32(5, a) + Ch(b, c, d) + e + K1_0_TO_19 + (W1[j] = *data++); - e = d; - d = c; - c = ROTL32(30, b); - b = a; - a = T1; - j++; - } while (j < 16); - - do { - T1 = W1[(j+13)&0x0f] ^ W1[(j+8)&0x0f] ^ W1[(j+2)&0x0f] ^ W1[j&0x0f]; - T1 = ROTL32(5, a) + Ch(b,c,d) + e + K1_0_TO_19 + (W1[j&0x0f] = ROTL32(1, T1)); - e = d; - d = c; - c = ROTL32(30, b); - b = a; - a = T1; - j++; - } while (j < 20); - - do { - T1 = W1[(j+13)&0x0f] ^ W1[(j+8)&0x0f] ^ W1[(j+2)&0x0f] ^ W1[j&0x0f]; - T1 = ROTL32(5, a) + Parity(b,c,d) + e + K1_20_TO_39 + (W1[j&0x0f] = ROTL32(1, T1)); - e = d; - d = c; - c = ROTL32(30, b); - b = a; - a = T1; - j++; - } while (j < 40); - - do { - T1 = W1[(j+13)&0x0f] ^ W1[(j+8)&0x0f] ^ W1[(j+2)&0x0f] ^ W1[j&0x0f]; - T1 = ROTL32(5, a) + Maj(b,c,d) + e + K1_40_TO_59 + (W1[j&0x0f] = ROTL32(1, T1)); - e = d; - d = c; - c = ROTL32(30, b); - b = a; - a = T1; - j++; - } while (j < 60); - - do { - T1 = W1[(j+13)&0x0f] ^ W1[(j+8)&0x0f] ^ W1[(j+2)&0x0f] ^ W1[j&0x0f]; - T1 = ROTL32(5, a) + Parity(b,c,d) + e + K1_60_TO_79 + (W1[j&0x0f] = ROTL32(1, T1)); - e = d; - d = c; - c = ROTL32(30, b); - b = a; - a = T1; - j++; - } while (j < 80); - - - /* Compute the current intermediate hash value */ - state_out[0] = state_in[0] + a; - state_out[1] = state_in[1] + b; - state_out[2] = state_in[2] + c; - state_out[3] = state_in[3] + d; - state_out[4] = state_in[4] + e; - - /* Clean up */ - a = b = c = d = e = T1 = 0; -} - -#endif /* SHA2_UNROLL_TRANSFORM */ - -void sha1_Update(SHA1_CTX* context, const sha2_byte *data, size_t len) { - unsigned int freespace = 0, usedspace = 0; - - if (len == 0) { - /* Calling with no data is valid - we do nothing */ - return; - } - - usedspace = (context->bitcount >> 3) % SHA1_BLOCK_LENGTH; - if (usedspace > 0) { - /* Calculate how much free space is available in the buffer */ - freespace = SHA1_BLOCK_LENGTH - usedspace; - - if (len >= freespace) { - /* Fill the buffer completely and process it */ - MEMCPY_BCOPY(((uint8_t*)context->buffer) + usedspace, data, freespace); - context->bitcount += freespace << 3; - len -= freespace; - data += freespace; -#if BYTE_ORDER == LITTLE_ENDIAN - /* Convert TO host byte order */ - for (int j = 0; j < 16; j++) { - REVERSE32(context->buffer[j],context->buffer[j]); - } -#endif - sha1_Transform(context->state, context->buffer, context->state); - } else { - /* The buffer is not yet full */ - MEMCPY_BCOPY(((uint8_t*)context->buffer) + usedspace, data, len); - context->bitcount += len << 3; - /* Clean up: */ - usedspace = freespace = 0; - return; - } - } - while (len >= SHA1_BLOCK_LENGTH) { - /* Process as many complete blocks as we can */ - MEMCPY_BCOPY(context->buffer, data, SHA1_BLOCK_LENGTH); -#if BYTE_ORDER == LITTLE_ENDIAN - /* Convert TO host byte order */ - for (int j = 0; j < 16; j++) { - REVERSE32(context->buffer[j],context->buffer[j]); - } -#endif - sha1_Transform(context->state, context->buffer, context->state); - context->bitcount += SHA1_BLOCK_LENGTH << 3; - len -= SHA1_BLOCK_LENGTH; - data += SHA1_BLOCK_LENGTH; - } - if (len > 0) { - /* There's left-overs, so save 'em */ - MEMCPY_BCOPY(context->buffer, data, len); - context->bitcount += len << 3; - } - /* Clean up: */ - usedspace = freespace = 0; -} - -void sha1_Final(SHA1_CTX* context, sha2_byte digest[SHA1_DIGEST_LENGTH]) { - unsigned int usedspace = 0; - - /* If no digest buffer is passed, we don't bother doing this: */ - if (digest != (sha2_byte*)0) { - usedspace = (context->bitcount >> 3) % SHA1_BLOCK_LENGTH; - /* Begin padding with a 1 bit: */ - ((uint8_t*)context->buffer)[usedspace++] = 0x80; - - if (usedspace > SHA1_SHORT_BLOCK_LENGTH) { - memzero(((uint8_t*)context->buffer) + usedspace, SHA1_BLOCK_LENGTH - usedspace); - -#if BYTE_ORDER == LITTLE_ENDIAN - /* Convert TO host byte order */ - for (int j = 0; j < 16; j++) { - REVERSE32(context->buffer[j],context->buffer[j]); - } -#endif - /* Do second-to-last transform: */ - sha1_Transform(context->state, context->buffer, context->state); - - /* And prepare the last transform: */ - usedspace = 0; - } - /* Set-up for the last transform: */ - memzero(((uint8_t*)context->buffer) + usedspace, SHA1_SHORT_BLOCK_LENGTH - usedspace); - -#if BYTE_ORDER == LITTLE_ENDIAN - /* Convert TO host byte order */ - for (int j = 0; j < 14; j++) { - REVERSE32(context->buffer[j],context->buffer[j]); - } -#endif - /* Set the bit count: */ - context->buffer[14] = context->bitcount >> 32; - context->buffer[15] = context->bitcount & 0xffffffff; - - /* Final transform: */ - sha1_Transform(context->state, context->buffer, context->state); - -#if BYTE_ORDER == LITTLE_ENDIAN - /* Convert FROM host byte order */ - for (int j = 0; j < 5; j++) { - REVERSE32(context->state[j],context->state[j]); - } -#endif - MEMCPY_BCOPY(digest, context->state, SHA1_DIGEST_LENGTH); - } - - /* Clean up state data: */ - memzero(context, sizeof(SHA1_CTX)); - usedspace = 0; -} - -char *sha1_End(SHA1_CTX* context, char buffer[SHA1_DIGEST_STRING_LENGTH]) { - sha2_byte digest[SHA1_DIGEST_LENGTH] = {0}, *d = digest; - int i = 0; - - if (buffer != (char*)0) { - sha1_Final(context, digest); - - for (i = 0; i < SHA1_DIGEST_LENGTH; i++) { - *buffer++ = sha2_hex_digits[(*d & 0xf0) >> 4]; - *buffer++ = sha2_hex_digits[*d & 0x0f]; - d++; - } - *buffer = (char)0; - } else { - memzero(context, sizeof(SHA1_CTX)); - } - memzero(digest, SHA1_DIGEST_LENGTH); - return buffer; -} - -void sha1_Raw(const sha2_byte* data, size_t len, uint8_t digest[SHA1_DIGEST_LENGTH]) { - SHA1_CTX context = {0}; - sha1_Init(&context); - sha1_Update(&context, data, len); - sha1_Final(&context, digest); -} - -char* sha1_Data(const sha2_byte* data, size_t len, char digest[SHA1_DIGEST_STRING_LENGTH]) { - SHA1_CTX context = {0}; - - sha1_Init(&context); - sha1_Update(&context, data, len); - return sha1_End(&context, digest); -} - -/*** SHA-256: *********************************************************/ -void sha256_Init(SHA256_CTX* context) { - if (context == (SHA256_CTX*)0) { - return; - } - MEMCPY_BCOPY(context->state, sha256_initial_hash_value, SHA256_DIGEST_LENGTH); - memzero(context->buffer, SHA256_BLOCK_LENGTH); - context->bitcount = 0; -} - -#ifdef SHA2_UNROLL_TRANSFORM - -/* Unrolled SHA-256 round macros: */ - -#define ROUND256_0_TO_15(a,b,c,d,e,f,g,h) \ - T1 = (h) + Sigma1_256(e) + Ch((e), (f), (g)) + \ - K256[j] + (W256[j] = *data++); \ - (d) += T1; \ - (h) = T1 + Sigma0_256(a) + Maj((a), (b), (c)); \ - j++ - -#define ROUND256(a,b,c,d,e,f,g,h) \ - s0 = W256[(j+1)&0x0f]; \ - s0 = sigma0_256(s0); \ - s1 = W256[(j+14)&0x0f]; \ - s1 = sigma1_256(s1); \ - T1 = (h) + Sigma1_256(e) + Ch((e), (f), (g)) + K256[j] + \ - (W256[j&0x0f] += s1 + W256[(j+9)&0x0f] + s0); \ - (d) += T1; \ - (h) = T1 + Sigma0_256(a) + Maj((a), (b), (c)); \ - j++ - -void sha256_Transform(const sha2_word32* state_in, const sha2_word32* data, sha2_word32* state_out) { - sha2_word32 a = 0, b = 0, c = 0, d = 0, e = 0, f = 0, g = 0, h = 0, s0 = 0, s1 = 0; - sha2_word32 T1 = 0; - sha2_word32 W256[16] = {0}; - int j = 0; - - /* Initialize registers with the prev. intermediate value */ - a = state_in[0]; - b = state_in[1]; - c = state_in[2]; - d = state_in[3]; - e = state_in[4]; - f = state_in[5]; - g = state_in[6]; - h = state_in[7]; - - j = 0; - do { - /* Rounds 0 to 15 (unrolled): */ - ROUND256_0_TO_15(a,b,c,d,e,f,g,h); - ROUND256_0_TO_15(h,a,b,c,d,e,f,g); - ROUND256_0_TO_15(g,h,a,b,c,d,e,f); - ROUND256_0_TO_15(f,g,h,a,b,c,d,e); - ROUND256_0_TO_15(e,f,g,h,a,b,c,d); - ROUND256_0_TO_15(d,e,f,g,h,a,b,c); - ROUND256_0_TO_15(c,d,e,f,g,h,a,b); - ROUND256_0_TO_15(b,c,d,e,f,g,h,a); - } while (j < 16); - - /* Now for the remaining rounds to 64: */ - do { - ROUND256(a,b,c,d,e,f,g,h); - ROUND256(h,a,b,c,d,e,f,g); - ROUND256(g,h,a,b,c,d,e,f); - ROUND256(f,g,h,a,b,c,d,e); - ROUND256(e,f,g,h,a,b,c,d); - ROUND256(d,e,f,g,h,a,b,c); - ROUND256(c,d,e,f,g,h,a,b); - ROUND256(b,c,d,e,f,g,h,a); - } while (j < 64); - - /* Compute the current intermediate hash value */ - state_out[0] = state_in[0] + a; - state_out[1] = state_in[1] + b; - state_out[2] = state_in[2] + c; - state_out[3] = state_in[3] + d; - state_out[4] = state_in[4] + e; - state_out[5] = state_in[5] + f; - state_out[6] = state_in[6] + g; - state_out[7] = state_in[7] + h; - - /* Clean up */ - a = b = c = d = e = f = g = h = T1 = 0; -} - -#else /* SHA2_UNROLL_TRANSFORM */ - -void sha256_Transform(const sha2_word32* state_in, const sha2_word32* data, sha2_word32* state_out) { - sha2_word32 a = 0, b = 0, c = 0, d = 0, e = 0, f = 0, g = 0, h = 0, s0 = 0, s1 = 0; - sha2_word32 T1 = 0, T2 = 0 , W256[16] = {0}; - int j = 0; - - /* Initialize registers with the prev. intermediate value */ - a = state_in[0]; - b = state_in[1]; - c = state_in[2]; - d = state_in[3]; - e = state_in[4]; - f = state_in[5]; - g = state_in[6]; - h = state_in[7]; - - j = 0; - do { - /* Apply the SHA-256 compression function to update a..h with copy */ - T1 = h + Sigma1_256(e) + Ch(e, f, g) + K256[j] + (W256[j] = *data++); - T2 = Sigma0_256(a) + Maj(a, b, c); - h = g; - g = f; - f = e; - e = d + T1; - d = c; - c = b; - b = a; - a = T1 + T2; - - j++; - } while (j < 16); - - do { - /* Part of the message block expansion: */ - s0 = W256[(j+1)&0x0f]; - s0 = sigma0_256(s0); - s1 = W256[(j+14)&0x0f]; - s1 = sigma1_256(s1); - - /* Apply the SHA-256 compression function to update a..h */ - T1 = h + Sigma1_256(e) + Ch(e, f, g) + K256[j] + - (W256[j&0x0f] += s1 + W256[(j+9)&0x0f] + s0); - T2 = Sigma0_256(a) + Maj(a, b, c); - h = g; - g = f; - f = e; - e = d + T1; - d = c; - c = b; - b = a; - a = T1 + T2; - - j++; - } while (j < 64); - - /* Compute the current intermediate hash value */ - state_out[0] = state_in[0] + a; - state_out[1] = state_in[1] + b; - state_out[2] = state_in[2] + c; - state_out[3] = state_in[3] + d; - state_out[4] = state_in[4] + e; - state_out[5] = state_in[5] + f; - state_out[6] = state_in[6] + g; - state_out[7] = state_in[7] + h; - - /* Clean up */ - a = b = c = d = e = f = g = h = T1 = T2 = 0; -} - -#endif /* SHA2_UNROLL_TRANSFORM */ - -void sha256_Update(SHA256_CTX* context, const sha2_byte *data, size_t len) { - unsigned int freespace = 0, usedspace = 0; - - if (len == 0) { - /* Calling with no data is valid - we do nothing */ - return; - } - - usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH; - if (usedspace > 0) { - /* Calculate how much free space is available in the buffer */ - freespace = SHA256_BLOCK_LENGTH - usedspace; - - if (len >= freespace) { - /* Fill the buffer completely and process it */ - MEMCPY_BCOPY(((uint8_t*)context->buffer) + usedspace, data, freespace); - context->bitcount += freespace << 3; - len -= freespace; - data += freespace; -#if BYTE_ORDER == LITTLE_ENDIAN - /* Convert TO host byte order */ - for (int j = 0; j < 16; j++) { - REVERSE32(context->buffer[j],context->buffer[j]); - } -#endif - sha256_Transform(context->state, context->buffer, context->state); - } else { - /* The buffer is not yet full */ - MEMCPY_BCOPY(((uint8_t*)context->buffer) + usedspace, data, len); - context->bitcount += len << 3; - /* Clean up: */ - usedspace = freespace = 0; - return; - } - } - while (len >= SHA256_BLOCK_LENGTH) { - /* Process as many complete blocks as we can */ - MEMCPY_BCOPY(context->buffer, data, SHA256_BLOCK_LENGTH); -#if BYTE_ORDER == LITTLE_ENDIAN - /* Convert TO host byte order */ - for (int j = 0; j < 16; j++) { - REVERSE32(context->buffer[j],context->buffer[j]); - } -#endif - sha256_Transform(context->state, context->buffer, context->state); - context->bitcount += SHA256_BLOCK_LENGTH << 3; - len -= SHA256_BLOCK_LENGTH; - data += SHA256_BLOCK_LENGTH; - } - if (len > 0) { - /* There's left-overs, so save 'em */ - MEMCPY_BCOPY(context->buffer, data, len); - context->bitcount += len << 3; - } - /* Clean up: */ - usedspace = freespace = 0; -} - -void sha256_Final(SHA256_CTX* context, sha2_byte digest[SHA256_DIGEST_LENGTH]) { - unsigned int usedspace = 0; - - /* If no digest buffer is passed, we don't bother doing this: */ - if (digest != (sha2_byte*)0) { - usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH; - /* Begin padding with a 1 bit: */ - ((uint8_t*)context->buffer)[usedspace++] = 0x80; - - if (usedspace > SHA256_SHORT_BLOCK_LENGTH) { - memzero(((uint8_t*)context->buffer) + usedspace, SHA256_BLOCK_LENGTH - usedspace); - -#if BYTE_ORDER == LITTLE_ENDIAN - /* Convert TO host byte order */ - for (int j = 0; j < 16; j++) { - REVERSE32(context->buffer[j],context->buffer[j]); - } -#endif - /* Do second-to-last transform: */ - sha256_Transform(context->state, context->buffer, context->state); - - /* And prepare the last transform: */ - usedspace = 0; - } - /* Set-up for the last transform: */ - memzero(((uint8_t*)context->buffer) + usedspace, SHA256_SHORT_BLOCK_LENGTH - usedspace); - -#if BYTE_ORDER == LITTLE_ENDIAN - /* Convert TO host byte order */ - for (int j = 0; j < 14; j++) { - REVERSE32(context->buffer[j],context->buffer[j]); - } -#endif - /* Set the bit count: */ - context->buffer[14] = context->bitcount >> 32; - context->buffer[15] = context->bitcount & 0xffffffff; - - /* Final transform: */ - sha256_Transform(context->state, context->buffer, context->state); - -#if BYTE_ORDER == LITTLE_ENDIAN - /* Convert FROM host byte order */ - for (int j = 0; j < 8; j++) { - REVERSE32(context->state[j],context->state[j]); - } -#endif - MEMCPY_BCOPY(digest, context->state, SHA256_DIGEST_LENGTH); - } - - /* Clean up state data: */ - memzero(context, sizeof(SHA256_CTX)); - usedspace = 0; -} - -char *sha256_End(SHA256_CTX* context, char buffer[SHA256_DIGEST_STRING_LENGTH]) { - sha2_byte digest[SHA256_DIGEST_LENGTH] = {0}, *d = digest; - int i = 0; - - if (buffer != (char*)0) { - sha256_Final(context, digest); - - for (i = 0; i < SHA256_DIGEST_LENGTH; i++) { - *buffer++ = sha2_hex_digits[(*d & 0xf0) >> 4]; - *buffer++ = sha2_hex_digits[*d & 0x0f]; - d++; - } - *buffer = (char)0; - } else { - memzero(context, sizeof(SHA256_CTX)); - } - memzero(digest, SHA256_DIGEST_LENGTH); - return buffer; -} - -void sha256_Raw(const sha2_byte* data, size_t len, uint8_t digest[SHA256_DIGEST_LENGTH]) { - SHA256_CTX context = {0}; - sha256_Init(&context); - sha256_Update(&context, data, len); - sha256_Final(&context, digest); -} - -char* sha256_Data(const sha2_byte* data, size_t len, char digest[SHA256_DIGEST_STRING_LENGTH]) { - SHA256_CTX context = {0}; - - sha256_Init(&context); - sha256_Update(&context, data, len); - return sha256_End(&context, digest); -} - - -/*** SHA-512: *********************************************************/ -void sha512_Init(SHA512_CTX* context) { - if (context == (SHA512_CTX*)0) { - return; - } - MEMCPY_BCOPY(context->state, sha512_initial_hash_value, SHA512_DIGEST_LENGTH); - memzero(context->buffer, SHA512_BLOCK_LENGTH); - context->bitcount[0] = context->bitcount[1] = 0; -} - -// [wallet-core] -void sha512_256_Init(SHA512_CTX* context) { - if (context == (SHA512_CTX*)0) { - return; - } - MEMCPY_BCOPY(context->state, sha512_256_initial_hash_value, SHA512_DIGEST_LENGTH); - memzero(context->buffer, SHA512_BLOCK_LENGTH); - context->bitcount[0] = context->bitcount[1] = 0; -} - -#ifdef SHA2_UNROLL_TRANSFORM - -/* Unrolled SHA-512 round macros: */ -#define ROUND512_0_TO_15(a,b,c,d,e,f,g,h) \ - T1 = (h) + Sigma1_512(e) + Ch((e), (f), (g)) + \ - K512[j] + (W512[j] = *data++); \ - (d) += T1; \ - (h) = T1 + Sigma0_512(a) + Maj((a), (b), (c)); \ - j++ - -#define ROUND512(a,b,c,d,e,f,g,h) \ - s0 = W512[(j+1)&0x0f]; \ - s0 = sigma0_512(s0); \ - s1 = W512[(j+14)&0x0f]; \ - s1 = sigma1_512(s1); \ - T1 = (h) + Sigma1_512(e) + Ch((e), (f), (g)) + K512[j] + \ - (W512[j&0x0f] += s1 + W512[(j+9)&0x0f] + s0); \ - (d) += T1; \ - (h) = T1 + Sigma0_512(a) + Maj((a), (b), (c)); \ - j++ - -void sha512_Transform(const sha2_word64* state_in, const sha2_word64* data, sha2_word64* state_out) { - sha2_word64 a = 0, b = 0, c = 0, d = 0, e = 0, f = 0, g = 0, h = 0, s0 = 0, s1 = 0; - sha2_word64 T1 = 0, W512[16] = {0}; - int j = 0; - - /* Initialize registers with the prev. intermediate value */ - a = state_in[0]; - b = state_in[1]; - c = state_in[2]; - d = state_in[3]; - e = state_in[4]; - f = state_in[5]; - g = state_in[6]; - h = state_in[7]; - - j = 0; - do { - ROUND512_0_TO_15(a,b,c,d,e,f,g,h); - ROUND512_0_TO_15(h,a,b,c,d,e,f,g); - ROUND512_0_TO_15(g,h,a,b,c,d,e,f); - ROUND512_0_TO_15(f,g,h,a,b,c,d,e); - ROUND512_0_TO_15(e,f,g,h,a,b,c,d); - ROUND512_0_TO_15(d,e,f,g,h,a,b,c); - ROUND512_0_TO_15(c,d,e,f,g,h,a,b); - ROUND512_0_TO_15(b,c,d,e,f,g,h,a); - } while (j < 16); - - /* Now for the remaining rounds up to 79: */ - do { - ROUND512(a,b,c,d,e,f,g,h); - ROUND512(h,a,b,c,d,e,f,g); - ROUND512(g,h,a,b,c,d,e,f); - ROUND512(f,g,h,a,b,c,d,e); - ROUND512(e,f,g,h,a,b,c,d); - ROUND512(d,e,f,g,h,a,b,c); - ROUND512(c,d,e,f,g,h,a,b); - ROUND512(b,c,d,e,f,g,h,a); - } while (j < 80); - - /* Compute the current intermediate hash value */ - state_out[0] = state_in[0] + a; - state_out[1] = state_in[1] + b; - state_out[2] = state_in[2] + c; - state_out[3] = state_in[3] + d; - state_out[4] = state_in[4] + e; - state_out[5] = state_in[5] + f; - state_out[6] = state_in[6] + g; - state_out[7] = state_in[7] + h; - - /* Clean up */ - a = b = c = d = e = f = g = h = T1 = 0; -} - -#else /* SHA2_UNROLL_TRANSFORM */ - -void sha512_Transform(const sha2_word64* state_in, const sha2_word64* data, sha2_word64* state_out) { - sha2_word64 a = 0, b = 0, c = 0, d = 0, e = 0, f = 0, g = 0, h = 0, s0 = 0, s1 = 0; - sha2_word64 T1 = 0, T2 = 0, W512[16] = {0}; - int j = 0; - - /* Initialize registers with the prev. intermediate value */ - a = state_in[0]; - b = state_in[1]; - c = state_in[2]; - d = state_in[3]; - e = state_in[4]; - f = state_in[5]; - g = state_in[6]; - h = state_in[7]; - - j = 0; - do { - /* Apply the SHA-512 compression function to update a..h with copy */ - T1 = h + Sigma1_512(e) + Ch(e, f, g) + K512[j] + (W512[j] = *data++); - T2 = Sigma0_512(a) + Maj(a, b, c); - h = g; - g = f; - f = e; - e = d + T1; - d = c; - c = b; - b = a; - a = T1 + T2; - - j++; - } while (j < 16); - - do { - /* Part of the message block expansion: */ - s0 = W512[(j+1)&0x0f]; - s0 = sigma0_512(s0); - s1 = W512[(j+14)&0x0f]; - s1 = sigma1_512(s1); - - /* Apply the SHA-512 compression function to update a..h */ - T1 = h + Sigma1_512(e) + Ch(e, f, g) + K512[j] + - (W512[j&0x0f] += s1 + W512[(j+9)&0x0f] + s0); - T2 = Sigma0_512(a) + Maj(a, b, c); - h = g; - g = f; - f = e; - e = d + T1; - d = c; - c = b; - b = a; - a = T1 + T2; - - j++; - } while (j < 80); - - /* Compute the current intermediate hash value */ - state_out[0] = state_in[0] + a; - state_out[1] = state_in[1] + b; - state_out[2] = state_in[2] + c; - state_out[3] = state_in[3] + d; - state_out[4] = state_in[4] + e; - state_out[5] = state_in[5] + f; - state_out[6] = state_in[6] + g; - state_out[7] = state_in[7] + h; - - /* Clean up */ - a = b = c = d = e = f = g = h = T1 = T2 = 0; -} - -#endif /* SHA2_UNROLL_TRANSFORM */ - -void sha512_Update(SHA512_CTX* context, const sha2_byte *data, size_t len) { - unsigned int freespace = 0, usedspace = 0; - - if (len == 0) { - /* Calling with no data is valid - we do nothing */ - return; - } - - usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH; - if (usedspace > 0) { - /* Calculate how much free space is available in the buffer */ - freespace = SHA512_BLOCK_LENGTH - usedspace; - - if (len >= freespace) { - /* Fill the buffer completely and process it */ - MEMCPY_BCOPY(((uint8_t*)context->buffer) + usedspace, data, freespace); - ADDINC128(context->bitcount, freespace << 3); - len -= freespace; - data += freespace; -#if BYTE_ORDER == LITTLE_ENDIAN - /* Convert TO host byte order */ - for (int j = 0; j < 16; j++) { - REVERSE64(context->buffer[j],context->buffer[j]); - } -#endif - sha512_Transform(context->state, context->buffer, context->state); - } else { - /* The buffer is not yet full */ - MEMCPY_BCOPY(((uint8_t*)context->buffer) + usedspace, data, len); - ADDINC128(context->bitcount, len << 3); - /* Clean up: */ - usedspace = freespace = 0; - return; - } - } - while (len >= SHA512_BLOCK_LENGTH) { - /* Process as many complete blocks as we can */ - MEMCPY_BCOPY(context->buffer, data, SHA512_BLOCK_LENGTH); -#if BYTE_ORDER == LITTLE_ENDIAN - /* Convert TO host byte order */ - for (int j = 0; j < 16; j++) { - REVERSE64(context->buffer[j],context->buffer[j]); - } -#endif - sha512_Transform(context->state, context->buffer, context->state); - ADDINC128(context->bitcount, SHA512_BLOCK_LENGTH << 3); - len -= SHA512_BLOCK_LENGTH; - data += SHA512_BLOCK_LENGTH; - } - if (len > 0) { - /* There's left-overs, so save 'em */ - MEMCPY_BCOPY(context->buffer, data, len); - ADDINC128(context->bitcount, len << 3); - } - /* Clean up: */ - usedspace = freespace = 0; -} - -static void sha512_Last(SHA512_CTX* context) { - unsigned int usedspace = 0; - - usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH; - /* Begin padding with a 1 bit: */ - ((uint8_t*)context->buffer)[usedspace++] = 0x80; - - if (usedspace > SHA512_SHORT_BLOCK_LENGTH) { - memzero(((uint8_t*)context->buffer) + usedspace, SHA512_BLOCK_LENGTH - usedspace); - -#if BYTE_ORDER == LITTLE_ENDIAN - /* Convert TO host byte order */ - for (int j = 0; j < 16; j++) { - REVERSE64(context->buffer[j],context->buffer[j]); - } -#endif - /* Do second-to-last transform: */ - sha512_Transform(context->state, context->buffer, context->state); - - /* And prepare the last transform: */ - usedspace = 0; - } - /* Set-up for the last transform: */ - memzero(((uint8_t*)context->buffer) + usedspace, SHA512_SHORT_BLOCK_LENGTH - usedspace); - -#if BYTE_ORDER == LITTLE_ENDIAN - /* Convert TO host byte order */ - for (int j = 0; j < 14; j++) { - REVERSE64(context->buffer[j],context->buffer[j]); - } -#endif - /* Store the length of input data (in bits): */ - context->buffer[14] = context->bitcount[1]; - context->buffer[15] = context->bitcount[0]; - - /* Final transform: */ - sha512_Transform(context->state, context->buffer, context->state); -} - -void sha512_Final(SHA512_CTX* context, sha2_byte digest[SHA512_DIGEST_LENGTH]) { - /* If no digest buffer is passed, we don't bother doing this: */ - if (digest != (sha2_byte*)0) { - sha512_Last(context); - - /* Save the hash data for output: */ -#if BYTE_ORDER == LITTLE_ENDIAN - /* Convert FROM host byte order */ - for (int j = 0; j < 8; j++) { - REVERSE64(context->state[j],context->state[j]); - } -#endif - MEMCPY_BCOPY(digest, context->state, SHA512_DIGEST_LENGTH); - } - - /* Zero out state data */ - memzero(context, sizeof(SHA512_CTX)); -} - -char *sha512_End(SHA512_CTX* context, char buffer[SHA512_DIGEST_STRING_LENGTH]) { - sha2_byte digest[SHA512_DIGEST_LENGTH] = {0}, *d = digest; - int i = 0; - - if (buffer != (char*)0) { - sha512_Final(context, digest); - - for (i = 0; i < SHA512_DIGEST_LENGTH; i++) { - *buffer++ = sha2_hex_digits[(*d & 0xf0) >> 4]; - *buffer++ = sha2_hex_digits[*d & 0x0f]; - d++; - } - *buffer = (char)0; - } else { - memzero(context, sizeof(SHA512_CTX)); - } - memzero(digest, SHA512_DIGEST_LENGTH); - return buffer; -} - -void sha512_Raw(const sha2_byte* data, size_t len, uint8_t digest[SHA512_DIGEST_LENGTH]) { - SHA512_CTX context = {0}; - sha512_Init(&context); - sha512_Update(&context, data, len); - sha512_Final(&context, digest); -} - -// [wallet-core] -void sha512_256_Raw(const sha2_byte* data, size_t len, uint8_t digest[SHA256_DIGEST_LENGTH]) { - SHA512_CTX context = {0}; - uint8_t result[SHA512_DIGEST_LENGTH]; - sha512_256_Init(&context); - sha512_Update(&context, data, len); - sha512_Final(&context, result); - - memcpy(digest, result, SHA256_DIGEST_LENGTH); - memzero(result, SHA512_DIGEST_LENGTH); -} - -char* sha512_Data(const sha2_byte* data, size_t len, char digest[SHA512_DIGEST_STRING_LENGTH]) { - SHA512_CTX context = {0}; - - sha512_Init(&context); - sha512_Update(&context, data, len); - return sha512_End(&context, digest); -} diff --git a/trezor-crypto/crypto/sha3.c b/trezor-crypto/crypto/sha3.c deleted file mode 100644 index 0b01bfe3a14..00000000000 --- a/trezor-crypto/crypto/sha3.c +++ /dev/null @@ -1,397 +0,0 @@ -/* sha3.c - an implementation of Secure Hash Algorithm 3 (Keccak). - * based on the - * The Keccak SHA-3 submission. Submission to NIST (Round 3), 2011 - * by Guido Bertoni, Joan Daemen, Michaël Peeters and Gilles Van Assche - * - * Copyright: 2013 Aleksey Kravchenko - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. Use this program at your own risk! - */ - -#include -#include - -#include -#include - -#define I64(x) x##LL -#define ROTL64(qword, n) ((qword) << (n) ^ ((qword) >> (64 - (n)))) -#define le2me_64(x) (x) -#define IS_ALIGNED_64(p) (0 == (7 & ((long)(p)))) // [wallet-core] pointer/numerical type, for MacOS SDK 12.3 -# define me64_to_le_str(to, from, length) memcpy((to), (from), (length)) - -/* constants */ -#define NumberOfRounds 24 - -/* SHA3 (Keccak) constants for 24 rounds */ -uint64_t keccak_round_constants[NumberOfRounds] = { - I64(0x0000000000000001), I64(0x0000000000008082), I64(0x800000000000808A), I64(0x8000000080008000), - I64(0x000000000000808B), I64(0x0000000080000001), I64(0x8000000080008081), I64(0x8000000000008009), - I64(0x000000000000008A), I64(0x0000000000000088), I64(0x0000000080008009), I64(0x000000008000000A), - I64(0x000000008000808B), I64(0x800000000000008B), I64(0x8000000000008089), I64(0x8000000000008003), - I64(0x8000000000008002), I64(0x8000000000000080), I64(0x000000000000800A), I64(0x800000008000000A), - I64(0x8000000080008081), I64(0x8000000000008080), I64(0x0000000080000001), I64(0x8000000080008008) -}; - -/* Initializing a sha3 context for given number of output bits */ -static void keccak_Init(SHA3_CTX *ctx, unsigned bits) -{ - /* NB: The Keccak capacity parameter = bits * 2 */ - unsigned rate = 1600 - bits * 2; - - memzero(ctx, sizeof(SHA3_CTX)); - ctx->block_size = rate / 8; - assert(rate <= 1600 && (rate % 64) == 0); -} - -/** - * Initialize context before calculating hash. - * - * @param ctx context to initialize - */ -void sha3_224_Init(SHA3_CTX *ctx) -{ - keccak_Init(ctx, 224); -} - -/** - * Initialize context before calculating hash. - * - * @param ctx context to initialize - */ -void sha3_256_Init(SHA3_CTX *ctx) -{ - keccak_Init(ctx, 256); -} - -/** - * Initialize context before calculating hash. - * - * @param ctx context to initialize - */ -void sha3_384_Init(SHA3_CTX *ctx) -{ - keccak_Init(ctx, 384); -} - -/** - * Initialize context before calculating hash. - * - * @param ctx context to initialize - */ -void sha3_512_Init(SHA3_CTX *ctx) -{ - keccak_Init(ctx, 512); -} - -/* Keccak theta() transformation */ -static void keccak_theta(uint64_t *A) -{ - unsigned int x = 0; - uint64_t C[5] = {0}, D[5] = {0}; - - for (x = 0; x < 5; x++) { - C[x] = A[x] ^ A[x + 5] ^ A[x + 10] ^ A[x + 15] ^ A[x + 20]; - } - D[0] = ROTL64(C[1], 1) ^ C[4]; - D[1] = ROTL64(C[2], 1) ^ C[0]; - D[2] = ROTL64(C[3], 1) ^ C[1]; - D[3] = ROTL64(C[4], 1) ^ C[2]; - D[4] = ROTL64(C[0], 1) ^ C[3]; - - for (x = 0; x < 5; x++) { - A[x] ^= D[x]; - A[x + 5] ^= D[x]; - A[x + 10] ^= D[x]; - A[x + 15] ^= D[x]; - A[x + 20] ^= D[x]; - } -} - -/* Keccak pi() transformation */ -static void keccak_pi(uint64_t *A) -{ - uint64_t A1 = 0; - A1 = A[1]; - A[ 1] = A[ 6]; - A[ 6] = A[ 9]; - A[ 9] = A[22]; - A[22] = A[14]; - A[14] = A[20]; - A[20] = A[ 2]; - A[ 2] = A[12]; - A[12] = A[13]; - A[13] = A[19]; - A[19] = A[23]; - A[23] = A[15]; - A[15] = A[ 4]; - A[ 4] = A[24]; - A[24] = A[21]; - A[21] = A[ 8]; - A[ 8] = A[16]; - A[16] = A[ 5]; - A[ 5] = A[ 3]; - A[ 3] = A[18]; - A[18] = A[17]; - A[17] = A[11]; - A[11] = A[ 7]; - A[ 7] = A[10]; - A[10] = A1; - /* note: A[ 0] is left as is */ -} - -/* Keccak chi() transformation */ -static void keccak_chi(uint64_t *A) -{ - int i = 0; - for (i = 0; i < 25; i += 5) { - uint64_t A0 = A[0 + i], A1 = A[1 + i]; - A[0 + i] ^= ~A1 & A[2 + i]; - A[1 + i] ^= ~A[2 + i] & A[3 + i]; - A[2 + i] ^= ~A[3 + i] & A[4 + i]; - A[3 + i] ^= ~A[4 + i] & A0; - A[4 + i] ^= ~A0 & A1; - } -} - -static void sha3_permutation(uint64_t *state) -{ - int round = 0; - for (round = 0; round < NumberOfRounds; round++) - { - keccak_theta(state); - - /* apply Keccak rho() transformation */ - state[ 1] = ROTL64(state[ 1], 1); - state[ 2] = ROTL64(state[ 2], 62); - state[ 3] = ROTL64(state[ 3], 28); - state[ 4] = ROTL64(state[ 4], 27); - state[ 5] = ROTL64(state[ 5], 36); - state[ 6] = ROTL64(state[ 6], 44); - state[ 7] = ROTL64(state[ 7], 6); - state[ 8] = ROTL64(state[ 8], 55); - state[ 9] = ROTL64(state[ 9], 20); - state[10] = ROTL64(state[10], 3); - state[11] = ROTL64(state[11], 10); - state[12] = ROTL64(state[12], 43); - state[13] = ROTL64(state[13], 25); - state[14] = ROTL64(state[14], 39); - state[15] = ROTL64(state[15], 41); - state[16] = ROTL64(state[16], 45); - state[17] = ROTL64(state[17], 15); - state[18] = ROTL64(state[18], 21); - state[19] = ROTL64(state[19], 8); - state[20] = ROTL64(state[20], 18); - state[21] = ROTL64(state[21], 2); - state[22] = ROTL64(state[22], 61); - state[23] = ROTL64(state[23], 56); - state[24] = ROTL64(state[24], 14); - - keccak_pi(state); - keccak_chi(state); - - /* apply iota(state, round) */ - *state ^= keccak_round_constants[round]; - } -} - -/** - * The core transformation. Process the specified block of data. - * - * @param hash the algorithm state - * @param block the message block to process - * @param block_size the size of the processed block in bytes - */ -static void sha3_process_block(uint64_t hash[25], const uint64_t *block, size_t block_size) -{ - /* expanded loop */ - hash[ 0] ^= le2me_64(block[ 0]); - hash[ 1] ^= le2me_64(block[ 1]); - hash[ 2] ^= le2me_64(block[ 2]); - hash[ 3] ^= le2me_64(block[ 3]); - hash[ 4] ^= le2me_64(block[ 4]); - hash[ 5] ^= le2me_64(block[ 5]); - hash[ 6] ^= le2me_64(block[ 6]); - hash[ 7] ^= le2me_64(block[ 7]); - hash[ 8] ^= le2me_64(block[ 8]); - /* if not sha3-512 */ - if (block_size > 72) { - hash[ 9] ^= le2me_64(block[ 9]); - hash[10] ^= le2me_64(block[10]); - hash[11] ^= le2me_64(block[11]); - hash[12] ^= le2me_64(block[12]); - /* if not sha3-384 */ - if (block_size > 104) { - hash[13] ^= le2me_64(block[13]); - hash[14] ^= le2me_64(block[14]); - hash[15] ^= le2me_64(block[15]); - hash[16] ^= le2me_64(block[16]); - /* if not sha3-256 */ - if (block_size > 136) { - hash[17] ^= le2me_64(block[17]); -#ifdef FULL_SHA3_FAMILY_SUPPORT - /* if not sha3-224 */ - if (block_size > 144) { - hash[18] ^= le2me_64(block[18]); - hash[19] ^= le2me_64(block[19]); - hash[20] ^= le2me_64(block[20]); - hash[21] ^= le2me_64(block[21]); - hash[22] ^= le2me_64(block[22]); - hash[23] ^= le2me_64(block[23]); - hash[24] ^= le2me_64(block[24]); - } -#endif - } - } - } - /* make a permutation of the hash */ - sha3_permutation(hash); -} - -#define SHA3_FINALIZED 0x80000000 - -/** - * Calculate message hash. - * Can be called repeatedly with chunks of the message to be hashed. - * - * @param ctx the algorithm context containing current hashing state - * @param msg message chunk - * @param size length of the message chunk - */ -void sha3_Update(SHA3_CTX *ctx, const unsigned char *msg, size_t size) -{ - size_t idx = (size_t)ctx->rest; - size_t block_size = (size_t)ctx->block_size; - - if (ctx->rest & SHA3_FINALIZED) return; /* too late for additional input */ - ctx->rest = (unsigned)((ctx->rest + size) % block_size); - - /* fill partial block */ - if (idx) { - size_t left = block_size - idx; - memcpy((char*)ctx->message + idx, msg, (size < left ? size : left)); - if (size < left) return; - - /* process partial block */ - sha3_process_block(ctx->hash, ctx->message, block_size); - msg += left; - size -= left; - } - while (size >= block_size) { - uint64_t *aligned_message_block = NULL; - if (IS_ALIGNED_64(msg)) { - /* the most common case is processing of an already aligned message - without copying it */ - aligned_message_block = (uint64_t*)(void*)msg; - } else { - memcpy(ctx->message, msg, block_size); - aligned_message_block = ctx->message; - } - - sha3_process_block(ctx->hash, aligned_message_block, block_size); - msg += block_size; - size -= block_size; - } - if (size) { - memcpy(ctx->message, msg, size); /* save leftovers */ - } -} - -/** - * Store calculated hash into the given array. - * - * @param ctx the algorithm context containing current hashing state - * @param result calculated hash in binary form - */ -void sha3_Final(SHA3_CTX *ctx, unsigned char* result) -{ - size_t digest_length = 100 - ctx->block_size / 2; - const size_t block_size = ctx->block_size; - - if (!(ctx->rest & SHA3_FINALIZED)) - { - /* clear the rest of the data queue */ - memzero((char*)ctx->message + ctx->rest, block_size - ctx->rest); - ((char*)ctx->message)[ctx->rest] |= 0x06; - ((char*)ctx->message)[block_size - 1] |= 0x80; - - /* process final block */ - sha3_process_block(ctx->hash, ctx->message, block_size); - ctx->rest = SHA3_FINALIZED; /* mark context as finalized */ - } - - assert(block_size > digest_length); - if (result) me64_to_le_str(result, ctx->hash, digest_length); - memzero(ctx, sizeof(SHA3_CTX)); -} - -#if USE_KECCAK -/** -* Store calculated hash into the given array. -* -* @param ctx the algorithm context containing current hashing state -* @param result calculated hash in binary form -*/ -void keccak_Final(SHA3_CTX *ctx, unsigned char* result) -{ - size_t digest_length = 100 - ctx->block_size / 2; - const size_t block_size = ctx->block_size; - - if (!(ctx->rest & SHA3_FINALIZED)) - { - /* clear the rest of the data queue */ - memzero((char*)ctx->message + ctx->rest, block_size - ctx->rest); - ((char*)ctx->message)[ctx->rest] |= 0x01; - ((char*)ctx->message)[block_size - 1] |= 0x80; - - /* process final block */ - sha3_process_block(ctx->hash, ctx->message, block_size); - ctx->rest = SHA3_FINALIZED; /* mark context as finalized */ - } - - assert(block_size > digest_length); - if (result) me64_to_le_str(result, ctx->hash, digest_length); - memzero(ctx, sizeof(SHA3_CTX)); -} - -void keccak_256(const unsigned char* data, size_t len, unsigned char* digest) -{ - SHA3_CTX ctx = {0}; - keccak_256_Init(&ctx); - keccak_Update(&ctx, data, len); - keccak_Final(&ctx, digest); -} - -void keccak_512(const unsigned char* data, size_t len, unsigned char* digest) -{ - SHA3_CTX ctx = {0}; - keccak_512_Init(&ctx); - keccak_Update(&ctx, data, len); - keccak_Final(&ctx, digest); -} -#endif /* USE_KECCAK */ - -void sha3_256(const unsigned char* data, size_t len, unsigned char* digest) -{ - SHA3_CTX ctx = {0}; - sha3_256_Init(&ctx); - sha3_Update(&ctx, data, len); - sha3_Final(&ctx, digest); -} - -void sha3_512(const unsigned char* data, size_t len, unsigned char* digest) -{ - SHA3_CTX ctx = {0}; - sha3_512_Init(&ctx); - sha3_Update(&ctx, data, len); - sha3_Final(&ctx, digest); -} diff --git a/trezor-crypto/crypto/shamir.c b/trezor-crypto/crypto/shamir.c deleted file mode 100644 index 6438d30fdba..00000000000 --- a/trezor-crypto/crypto/shamir.c +++ /dev/null @@ -1,335 +0,0 @@ -/* - * Implementation of the hazardous parts of the SSS library - * - * Copyright (c) 2017 Daan Sprenkels - * Copyright (c) 2019 SatoshiLabs - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - * - * This code contains the actual Shamir secret sharing functionality. The - * implementation of this code is based on the idea that the user likes to - * generate/combine 32 shares (in GF(2^8)) at the same time, because a 256 bit - * key will be exactly 32 bytes. Therefore we bitslice all the input and - * unbitslice the output right before returning. - * - * This bitslice approach optimizes natively on all architectures that are 32 - * bit or more. Care is taken to use not too many registers, to ensure that no - * values have to be leaked to the stack. - * - * All functions in this module are implemented constant time and constant - * lookup operations, as all proper crypto code must be. - */ - -#include -#include -#include - -static void bitslice(uint32_t r[8], const uint8_t *x, size_t len) { - size_t bit_idx = 0, arr_idx = 0; - uint32_t cur = 0; - - memset(r, 0, sizeof(uint32_t[8])); - for (arr_idx = 0; arr_idx < len; arr_idx++) { - cur = (uint32_t)x[arr_idx]; - for (bit_idx = 0; bit_idx < 8; bit_idx++) { - r[bit_idx] |= ((cur >> bit_idx) & 1) << arr_idx; - } - } -} - -static void unbitslice(uint8_t *r, const uint32_t x[8], size_t len) { - size_t bit_idx = 0, arr_idx = 0; - uint32_t cur = 0; - - memset(r, 0, sizeof(uint8_t) * len); - for (bit_idx = 0; bit_idx < 8; bit_idx++) { - cur = (uint32_t)x[bit_idx]; - for (arr_idx = 0; arr_idx < len; arr_idx++) { - r[arr_idx] |= ((cur >> arr_idx) & 1) << bit_idx; - } - } -} - -static void bitslice_setall(uint32_t r[8], const uint8_t x) { - size_t idx = 0; - for (idx = 0; idx < 8; idx++) { - r[idx] = -((x >> idx) & 1); - } -} - -/* - * Add (XOR) `r` with `x` and store the result in `r`. - */ -static void gf256_add(uint32_t r[8], const uint32_t x[8]) { - size_t idx = 0; - for (idx = 0; idx < 8; idx++) r[idx] ^= x[idx]; -} - -/* - * Safely multiply two bitsliced polynomials in GF(2^8) reduced by - * x^8 + x^4 + x^3 + x + 1. `r` and `a` may overlap, but overlapping of `r` - * and `b` will produce an incorrect result! If you need to square a polynomial - * use `gf256_square` instead. - */ -static void gf256_mul(uint32_t r[8], const uint32_t a[8], const uint32_t b[8]) { - /* This function implements Russian Peasant multiplication on two - * bitsliced polynomials. - * - * I personally think that these kinds of long lists of operations - * are often a bit ugly. A double for loop would be nicer and would - * take up a lot less lines of code. - * However, some compilers seem to fail in optimizing these kinds of - * loops. So we will just have to do this by hand. - */ - uint32_t a2[8] = {0}; - memcpy(a2, a, sizeof(uint32_t[8])); - - r[0] = a2[0] & b[0]; /* add (assignment, because r is 0) */ - r[1] = a2[1] & b[0]; - r[2] = a2[2] & b[0]; - r[3] = a2[3] & b[0]; - r[4] = a2[4] & b[0]; - r[5] = a2[5] & b[0]; - r[6] = a2[6] & b[0]; - r[7] = a2[7] & b[0]; - a2[0] ^= a2[7]; /* reduce */ - a2[2] ^= a2[7]; - a2[3] ^= a2[7]; - - r[0] ^= a2[7] & b[1]; /* add */ - r[1] ^= a2[0] & b[1]; - r[2] ^= a2[1] & b[1]; - r[3] ^= a2[2] & b[1]; - r[4] ^= a2[3] & b[1]; - r[5] ^= a2[4] & b[1]; - r[6] ^= a2[5] & b[1]; - r[7] ^= a2[6] & b[1]; - a2[7] ^= a2[6]; /* reduce */ - a2[1] ^= a2[6]; - a2[2] ^= a2[6]; - - r[0] ^= a2[6] & b[2]; /* add */ - r[1] ^= a2[7] & b[2]; - r[2] ^= a2[0] & b[2]; - r[3] ^= a2[1] & b[2]; - r[4] ^= a2[2] & b[2]; - r[5] ^= a2[3] & b[2]; - r[6] ^= a2[4] & b[2]; - r[7] ^= a2[5] & b[2]; - a2[6] ^= a2[5]; /* reduce */ - a2[0] ^= a2[5]; - a2[1] ^= a2[5]; - - r[0] ^= a2[5] & b[3]; /* add */ - r[1] ^= a2[6] & b[3]; - r[2] ^= a2[7] & b[3]; - r[3] ^= a2[0] & b[3]; - r[4] ^= a2[1] & b[3]; - r[5] ^= a2[2] & b[3]; - r[6] ^= a2[3] & b[3]; - r[7] ^= a2[4] & b[3]; - a2[5] ^= a2[4]; /* reduce */ - a2[7] ^= a2[4]; - a2[0] ^= a2[4]; - - r[0] ^= a2[4] & b[4]; /* add */ - r[1] ^= a2[5] & b[4]; - r[2] ^= a2[6] & b[4]; - r[3] ^= a2[7] & b[4]; - r[4] ^= a2[0] & b[4]; - r[5] ^= a2[1] & b[4]; - r[6] ^= a2[2] & b[4]; - r[7] ^= a2[3] & b[4]; - a2[4] ^= a2[3]; /* reduce */ - a2[6] ^= a2[3]; - a2[7] ^= a2[3]; - - r[0] ^= a2[3] & b[5]; /* add */ - r[1] ^= a2[4] & b[5]; - r[2] ^= a2[5] & b[5]; - r[3] ^= a2[6] & b[5]; - r[4] ^= a2[7] & b[5]; - r[5] ^= a2[0] & b[5]; - r[6] ^= a2[1] & b[5]; - r[7] ^= a2[2] & b[5]; - a2[3] ^= a2[2]; /* reduce */ - a2[5] ^= a2[2]; - a2[6] ^= a2[2]; - - r[0] ^= a2[2] & b[6]; /* add */ - r[1] ^= a2[3] & b[6]; - r[2] ^= a2[4] & b[6]; - r[3] ^= a2[5] & b[6]; - r[4] ^= a2[6] & b[6]; - r[5] ^= a2[7] & b[6]; - r[6] ^= a2[0] & b[6]; - r[7] ^= a2[1] & b[6]; - a2[2] ^= a2[1]; /* reduce */ - a2[4] ^= a2[1]; - a2[5] ^= a2[1]; - - r[0] ^= a2[1] & b[7]; /* add */ - r[1] ^= a2[2] & b[7]; - r[2] ^= a2[3] & b[7]; - r[3] ^= a2[4] & b[7]; - r[4] ^= a2[5] & b[7]; - r[5] ^= a2[6] & b[7]; - r[6] ^= a2[7] & b[7]; - r[7] ^= a2[0] & b[7]; - - memzero(a2, sizeof(a2)); -} - -/* - * Square `x` in GF(2^8) and write the result to `r`. `r` and `x` may overlap. - */ -static void gf256_square(uint32_t r[8], const uint32_t x[8]) { - uint32_t r8 = 0, r10 = 0, r12 = 0, r14 = 0; - /* Use the Freshman's Dream rule to square the polynomial - * Assignments are done from 7 downto 0, because this allows the user - * to execute this function in-place (e.g. `gf256_square(r, r);`). - */ - r14 = x[7]; - r12 = x[6]; - r10 = x[5]; - r8 = x[4]; - r[6] = x[3]; - r[4] = x[2]; - r[2] = x[1]; - r[0] = x[0]; - - /* Reduce with x^8 + x^4 + x^3 + x + 1 until order is less than 8 */ - r[7] = r14; /* r[7] was 0 */ - r[6] ^= r14; - r10 ^= r14; - /* Skip, because r13 is always 0 */ - r[4] ^= r12; - r[5] = r12; /* r[5] was 0 */ - r[7] ^= r12; - r8 ^= r12; - /* Skip, because r11 is always 0 */ - r[2] ^= r10; - r[3] = r10; /* r[3] was 0 */ - r[5] ^= r10; - r[6] ^= r10; - r[1] = r14; /* r[1] was 0 */ - r[2] ^= r14; /* Substitute r9 by r14 because they will always be equal*/ - r[4] ^= r14; - r[5] ^= r14; - r[0] ^= r8; - r[1] ^= r8; - r[3] ^= r8; - r[4] ^= r8; -} - -/* - * Invert `x` in GF(2^8) and write the result to `r` - */ -static void gf256_inv(uint32_t r[8], uint32_t x[8]) { - uint32_t y[8] = {0}, z[8] = {0}; - - gf256_square(y, x); // y = x^2 - gf256_square(y, y); // y = x^4 - gf256_square(r, y); // r = x^8 - gf256_mul(z, r, x); // z = x^9 - gf256_square(r, r); // r = x^16 - gf256_mul(r, r, z); // r = x^25 - gf256_square(r, r); // r = x^50 - gf256_square(z, r); // z = x^100 - gf256_square(z, z); // z = x^200 - gf256_mul(r, r, z); // r = x^250 - gf256_mul(r, r, y); // r = x^254 - - memzero(y, sizeof(y)); - memzero(z, sizeof(z)); -} - -bool shamir_interpolate(uint8_t *result, uint8_t result_index, - const uint8_t *share_indices, - const uint8_t **share_values, uint8_t share_count, - size_t len) { - size_t i = 0, j = 0; - uint32_t x[8] = {0}; - uint32_t xs[share_count][8]; - memset(xs, 0, sizeof(xs)); - uint32_t ys[share_count][8]; - memset(ys, 0, sizeof(ys)); - uint32_t num[8] = {~0}; /* num is the numerator (=1) */ - uint32_t denom[8] = {0}; - uint32_t tmp[8] = {0}; - uint32_t secret[8] = {0}; - bool ret = true; - - if (len > SHAMIR_MAX_LEN) return false; - - /* Collect the x and y values */ - for (i = 0; i < share_count; i++) { - bitslice_setall(xs[i], share_indices[i]); - bitslice(ys[i], share_values[i], len); - } - bitslice_setall(x, result_index); - - for (i = 0; i < share_count; i++) { - memcpy(tmp, x, sizeof(uint32_t[8])); - gf256_add(tmp, xs[i]); - gf256_mul(num, num, tmp); - } - - /* Use Lagrange basis polynomials to calculate the secret coefficient */ - for (i = 0; i < share_count; i++) { - /* The code below assumes that none of the share_indices are equal to - * result_index. We need to treat that as a special case. */ - if (share_indices[i] != result_index) { - memcpy(denom, x, sizeof(denom)); - gf256_add(denom, xs[i]); - } else { - bitslice_setall(denom, 1); - gf256_add(secret, ys[i]); - } - for (j = 0; j < share_count; j++) { - if (i == j) continue; - memcpy(tmp, xs[i], sizeof(uint32_t[8])); - gf256_add(tmp, xs[j]); - gf256_mul(denom, denom, tmp); - } - if ((denom[0] | denom[1] | denom[2] | denom[3] | denom[4] | denom[5] | - denom[6] | denom[7]) == 0) { - /* The share_indices are not unique. */ - ret = false; - break; - } - gf256_inv(tmp, denom); /* inverted denominator */ - gf256_mul(tmp, tmp, num); /* basis polynomial */ - gf256_mul(tmp, tmp, ys[i]); /* scaled coefficient */ - gf256_add(secret, tmp); - } - - if (ret == true) { - unbitslice(result, secret, len); - } - - memzero(x, sizeof(x)); - memzero(xs, sizeof(xs)); - memzero(ys, sizeof(ys)); - memzero(num, sizeof(num)); - memzero(denom, sizeof(denom)); - memzero(tmp, sizeof(tmp)); - memzero(secret, sizeof(secret)); - return ret; -} diff --git a/trezor-crypto/crypto/slip39.c b/trezor-crypto/crypto/slip39.c deleted file mode 100644 index ec1adf20169..00000000000 --- a/trezor-crypto/crypto/slip39.c +++ /dev/null @@ -1,151 +0,0 @@ -/** - * This file is part of the TREZOR project, https://trezor.io/ - * - * Copyright (c) SatoshiLabs - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES - * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include - -/** - * Returns word at position `index`. - */ -const char* get_word(uint16_t index) { - if (index >= WORDS_COUNT) { - return NULL; - } - - return slip39_wordlist[index]; -} - -/** - * Finds the index of a given word. - * Returns true on success and stores result in `index`. - */ -bool word_index(uint16_t* index, const char* word, uint8_t word_length) { - uint16_t lo = 0; - uint16_t hi = WORDS_COUNT; - uint16_t mid = 0; - - while ((hi - lo) > 1) { - mid = (hi + lo) / 2; - if (strncmp(slip39_wordlist[mid], word, word_length) > 0) { - hi = mid; - } else { - lo = mid; - } - } - if (strncmp(slip39_wordlist[lo], word, word_length) != 0) { - return false; - } - *index = lo; - return true; -} - -/** - * Returns the index of the first sequence in words_button_seq[] which is not - * less than the given sequence. Returns WORDS_COUNT if there is no such - * sequence. - */ -static uint16_t find_sequence(uint16_t sequence) { - if (sequence <= words_button_seq[0].sequence) { - return 0; - } - - uint16_t lo = 0; - uint16_t hi = WORDS_COUNT; - - while (hi - lo > 1) { - uint16_t mid = (hi + lo) / 2; - if (words_button_seq[mid].sequence >= sequence) { - hi = mid; - } else { - lo = mid; - } - } - - return hi; -} - -/** - * Returns a word matching the button sequence prefix or NULL if no match is - * found. - */ -const char* button_sequence_to_word(uint16_t sequence) { - if (sequence == 0) { - return slip39_wordlist[words_button_seq[0].index]; - } - - uint16_t multiplier = 1; - while (sequence < 1000) { - sequence *= 10; - multiplier *= 10; - } - - uint16_t i = find_sequence(sequence); - if (i >= WORDS_COUNT || - words_button_seq[i].sequence - sequence >= multiplier) { - return NULL; - } - - return slip39_wordlist[words_button_seq[i].index]; -} - -/** - * Calculates which buttons on the T9 keyboard can still be pressed after the - * prefix was entered. Returns a 9-bit bitmask, where each bit specifies which - * buttons can be pressed (there are still words in this combination). The least - * significant bit corresponds to the first button. - * - * Example: 110000110 - second, third, eighth and ninth button still can be - * pressed. - */ -uint16_t slip39_word_completion_mask(uint16_t prefix) { - if (prefix >= 1000) { - // Four char prefix -> the mask is zero. - return 0; - } - - // Determine the range of sequences [min, max), which have the given prefix. - uint16_t min = prefix; - uint16_t max = prefix + 1; - uint16_t divider = 1; - while (max <= 1000) { - min *= 10; - max *= 10; - divider *= 10; - } - divider /= 10; - - // Determine the range we will be searching in words_button_seq[]. - min = find_sequence(min); - max = find_sequence(max); - - uint16_t bitmap = 0; - for (uint16_t i = min; i < max; ++i) { - uint8_t digit = (words_button_seq[i].sequence / divider) % 10; - bitmap |= 1 << (digit - 1); - } - - return bitmap; -} diff --git a/trezor-crypto/crypto/sodium/keypair.c b/trezor-crypto/crypto/sodium/keypair.c deleted file mode 100644 index 0375de0c59a..00000000000 --- a/trezor-crypto/crypto/sodium/keypair.c +++ /dev/null @@ -1,63 +0,0 @@ -#include - -#include - -/* Perform a fixed-base multiplication of the Edwards base point, - (which is efficient due to precalculated tables), then convert - to the Curve25519 montgomery-format public key. In particular, - convert Curve25519's "montgomery" x-coordinate into an Ed25519 - "edwards" y-coordinate: - - mont_x = (ed_y + 1) / (1 - ed_y) - - with projective coordinates: - - mont_x = (ed_y + ed_z) / (ed_z - ed_y) - - NOTE: ed_y=1 is converted to mont_x=0 since fe_invert is mod-exp -*/ -int ed25519_pk_to_curve25519(unsigned char *curve25519_pk, const unsigned char *ed25519_pk) { - ge25519_p3 A; - fe25519 x; - fe25519 one_minus_y; - - if (ge25519_has_small_order(ed25519_pk) != 0 || - ge25519_frombytes_negate_vartime(&A, ed25519_pk) != 0 || - ge25519_is_on_main_subgroup(&A) == 0) { - return -1; - } - fe25519_1(one_minus_y); - fe25519_sub(one_minus_y, one_minus_y, A.Y); - fe25519_invert(one_minus_y, one_minus_y); - fe25519_1(x); - fe25519_add(x, x, A.Y); - fe25519_mul(x, x, one_minus_y); - fe25519_tobytes(curve25519_pk, x); - - return 0; -} - -/* Convert the Curve25519 public key into an Ed25519 public key. In -particular, convert Curve25519's "montgomery" x-coordinate into an -Ed25519 "edwards" y-coordinate: - -ed_y = (mont_x - 1) / (mont_x + 1) - -NOTE: mont_x=-1 is converted to ed_y=0 since fe_invert is mod-exp -*/ -int curve25519_pk_to_ed25519(unsigned char *ed25519_pk, const unsigned char *curve25519_pk) { - fe25519 x; - fe25519 tmp; - fe25519 one_minus_y; - - fe25519_frombytes(x, curve25519_pk); - fe25519_1(one_minus_y); - fe25519_add(one_minus_y, x, one_minus_y); - fe25519_invert(one_minus_y, one_minus_y); - fe25519_1(tmp); - fe25519_sub(x, x, tmp); - fe25519_mul(x, x, one_minus_y); - fe25519_tobytes(ed25519_pk, x); - - return 0; -} \ No newline at end of file diff --git a/trezor-crypto/crypto/sodium/private/ed25519_ref10.c b/trezor-crypto/crypto/sodium/private/ed25519_ref10.c deleted file mode 100644 index 48256483900..00000000000 --- a/trezor-crypto/crypto/sodium/private/ed25519_ref10.c +++ /dev/null @@ -1,481 +0,0 @@ -#include -#include -#include -#include - -#include - -/* - * Field arithmetic: - * Use 5*51 bit limbs on 64-bit systems with support for 128 bit arithmetic, - * and 10*25.5 bit limbs elsewhere. - * - * Functions used elsewhere that are candidates for inlining are defined - * via "private/curve25519_ref10.h". - */ - -#include -#include - -void fe25519_invert(fe25519 out, const fe25519 z) { - fe25519 t0; - fe25519 t1; - fe25519 t2; - fe25519 t3; - int i; - - fe25519_sq(t0, z); - fe25519_sq(t1, t0); - fe25519_sq(t1, t1); - fe25519_mul(t1, z, t1); - fe25519_mul(t0, t0, t1); - fe25519_sq(t2, t0); - fe25519_mul(t1, t1, t2); - fe25519_sq(t2, t1); - for (i = 1; i < 5; ++i) { - fe25519_sq(t2, t2); - } - fe25519_mul(t1, t2, t1); - fe25519_sq(t2, t1); - for (i = 1; i < 10; ++i) { - fe25519_sq(t2, t2); - } - fe25519_mul(t2, t2, t1); - fe25519_sq(t3, t2); - for (i = 1; i < 20; ++i) { - fe25519_sq(t3, t3); - } - fe25519_mul(t2, t3, t2); - fe25519_sq(t2, t2); - for (i = 1; i < 10; ++i) { - fe25519_sq(t2, t2); - } - fe25519_mul(t1, t2, t1); - fe25519_sq(t2, t1); - for (i = 1; i < 50; ++i) { - fe25519_sq(t2, t2); - } - fe25519_mul(t2, t2, t1); - fe25519_sq(t3, t2); - for (i = 1; i < 100; ++i) { - fe25519_sq(t3, t3); - } - fe25519_mul(t2, t3, t2); - fe25519_sq(t2, t2); - for (i = 1; i < 50; ++i) { - fe25519_sq(t2, t2); - } - fe25519_mul(t1, t2, t1); - fe25519_sq(t1, t1); - for (i = 1; i < 5; ++i) { - fe25519_sq(t1, t1); - } - fe25519_mul(out, t1, t0); -} - -void fe25519_pow22523(fe25519 out, const fe25519 z) { - fe25519 t0; - fe25519 t1; - fe25519 t2; - int i; - - fe25519_sq(t0, z); - fe25519_sq(t1, t0); - fe25519_sq(t1, t1); - fe25519_mul(t1, z, t1); - fe25519_mul(t0, t0, t1); - fe25519_sq(t0, t0); - fe25519_mul(t0, t1, t0); - fe25519_sq(t1, t0); - for (i = 1; i < 5; ++i) { - fe25519_sq(t1, t1); - } - fe25519_mul(t0, t1, t0); - fe25519_sq(t1, t0); - for (i = 1; i < 10; ++i) { - fe25519_sq(t1, t1); - } - fe25519_mul(t1, t1, t0); - fe25519_sq(t2, t1); - for (i = 1; i < 20; ++i) { - fe25519_sq(t2, t2); - } - fe25519_mul(t1, t2, t1); - fe25519_sq(t1, t1); - for (i = 1; i < 10; ++i) { - fe25519_sq(t1, t1); - } - fe25519_mul(t0, t1, t0); - fe25519_sq(t1, t0); - for (i = 1; i < 50; ++i) { - fe25519_sq(t1, t1); - } - fe25519_mul(t1, t1, t0); - fe25519_sq(t2, t1); - for (i = 1; i < 100; ++i) { - fe25519_sq(t2, t2); - } - fe25519_mul(t1, t2, t1); - fe25519_sq(t1, t1); - for (i = 1; i < 50; ++i) { - fe25519_sq(t1, t1); - } - fe25519_mul(t0, t1, t0); - fe25519_sq(t0, t0); - fe25519_sq(t0, t0); - fe25519_mul(out, t0, z); -} - -/* - r = p + q - */ - -void ge25519_add2(ge25519_p1p1_1 *r, const ge25519_p3 *p, const ge25519_cached *q) { - fe25519 t0; - - fe25519_add(r->X, p->Y, p->X); - fe25519_sub(r->Y, p->Y, p->X); - fe25519_mul(r->Z, r->X, q->YplusX); - fe25519_mul(r->Y, r->Y, q->YminusX); - fe25519_mul(r->T, q->T2d, p->T); - fe25519_mul(r->X, p->Z, q->Z); - fe25519_add(t0, r->X, r->X); - fe25519_sub(r->X, r->Z, r->Y); - fe25519_add(r->Y, r->Z, r->Y); - fe25519_add(r->Z, t0, r->T); - fe25519_sub(r->T, t0, r->T); -} - -int ge25519_frombytes_negate_vartime(ge25519_p3 *h, const unsigned char *s) { - fe25519 u; - fe25519 v; - fe25519 v3; - fe25519 vxx; - fe25519 m_root_check, p_root_check; - - fe25519_frombytes(h->Y, s); - fe25519_1(h->Z); - fe25519_sq(u, h->Y); - fe25519_mul(v, u, d); - fe25519_sub(u, u, h->Z); /* u = y^2-1 */ - fe25519_add(v, v, h->Z); /* v = dy^2+1 */ - - fe25519_sq(v3, v); - fe25519_mul(v3, v3, v); /* v3 = v^3 */ - fe25519_sq(h->X, v3); - fe25519_mul(h->X, h->X, v); - fe25519_mul(h->X, h->X, u); /* x = uv^7 */ - - fe25519_pow22523(h->X, h->X); /* x = (uv^7)^((q-5)/8) */ - fe25519_mul(h->X, h->X, v3); - fe25519_mul(h->X, h->X, u); /* x = uv^3(uv^7)^((q-5)/8) */ - - fe25519_sq(vxx, h->X); - fe25519_mul(vxx, vxx, v); - fe25519_sub(m_root_check, vxx, u); /* vx^2-u */ - if (fe25519_iszero(m_root_check) == 0) { - fe25519_add(p_root_check, vxx, u); /* vx^2+u */ - if (fe25519_iszero(p_root_check) == 0) { - return -1; - } - fe25519_mul(h->X, h->X, sqrtm1); - } - - if (fe25519_isnegative(h->X) == (s[31] >> 7)) { - fe25519_neg(h->X, h->X); - } - fe25519_mul(h->T, h->X, h->Y); - - return 0; -} - -/* - r = p - */ - -void ge25519_p1p1_to_p3(ge25519_p3 *r, const ge25519_p1p1_1 *p) { - fe25519_mul(r->X, p->X, p->T); - fe25519_mul(r->Y, p->Y, p->Z); - fe25519_mul(r->Z, p->Z, p->T); - fe25519_mul(r->T, p->X, p->Y); -} - -/* - r = 2 * p - */ - -void ge25519_p2_dbl(ge25519_p1p1_1 *r, const ge25519_p2 *p) { - fe25519 t0; - - fe25519_sq(r->X, p->X); - fe25519_sq(r->Z, p->Y); - fe25519_sq2(r->T, p->Z); - fe25519_add(r->Y, p->X, p->Y); - fe25519_sq(t0, r->Y); - fe25519_add(r->Y, r->Z, r->X); - fe25519_sub(r->Z, r->Z, r->X); - fe25519_sub(r->X, t0, r->Y); - fe25519_sub(r->T, r->T, r->Z); -} - -void ge25519_p3_0(ge25519_p3 *h) { - fe25519_0(h->X); - fe25519_1(h->Y); - fe25519_1(h->Z); - fe25519_0(h->T); -} - -void ge25519_cached_0(ge25519_cached *h) { - fe25519_1(h->YplusX); - fe25519_1(h->YminusX); - fe25519_1(h->Z); - fe25519_0(h->T2d); -} - -/* - r = p - */ - -void ge25519_p3_to_cached(ge25519_cached *r, const ge25519_p3 *p) { - fe25519_add(r->YplusX, p->Y, p->X); - fe25519_sub(r->YminusX, p->Y, p->X); - fe25519_copy(r->Z, p->Z); - fe25519_mul(r->T2d, p->T, d2); -} - -/* - r = p - */ - -void ge25519_p3_to_p2(ge25519_p2 *r, const ge25519_p3 *p) { - fe25519_copy(r->X, p->X); - fe25519_copy(r->Y, p->Y); - fe25519_copy(r->Z, p->Z); -} - -void ge25519_p3_tobytes(unsigned char *s, const ge25519_p3 *h) { - fe25519 recip; - fe25519 x; - fe25519 y; - - fe25519_invert(recip, h->Z); - fe25519_mul(x, h->X, recip); - fe25519_mul(y, h->Y, recip); - fe25519_tobytes(s, y); - s[31] ^= fe25519_isnegative(x) << 7; -} - -/* - r = 2 * p - */ - -void ge25519_p3_dbl(ge25519_p1p1_1 *r, const ge25519_p3 *p) { - ge25519_p2 q; - ge25519_p3_to_p2(&q, p); - ge25519_p2_dbl(r, &q); -} - -void ge25519_precomp_0(ge25519_precomp *h) { - fe25519_1(h->yplusx); - fe25519_1(h->yminusx); - fe25519_0(h->xy2d); -} - -unsigned char equal(signed char b, signed char c) { - unsigned char ub = b; - unsigned char uc = c; - unsigned char x = ub ^ uc; /* 0: yes; 1..255: no */ - uint32_t y = x; /* 0: yes; 1..255: no */ - - y -= 1; /* 4294967295: yes; 0..254: no */ - y >>= 31; /* 1: yes; 0: no */ - - return y; -} - -unsigned char negative(signed char b) { - /* 18446744073709551361..18446744073709551615: yes; 0..255: no */ - uint64_t x = b; - - x >>= 63; /* 1: yes; 0: no */ - - return x; -} - -void ge25519_cmov(ge25519_precomp *t, const ge25519_precomp *u, unsigned char b) { - fe25519_cmov(t->yplusx, u->yplusx, b); - fe25519_cmov(t->yminusx, u->yminusx, b); - fe25519_cmov(t->xy2d, u->xy2d, b); -} - -void ge25519_cmov_cached(ge25519_cached *t, const ge25519_cached *u, unsigned char b) { - fe25519_cmov(t->YplusX, u->YplusX, b); - fe25519_cmov(t->YminusX, u->YminusX, b); - fe25519_cmov(t->Z, u->Z, b); - fe25519_cmov(t->T2d, u->T2d, b); -} - -/* - r = p - q - */ - -void ge25519_sub(ge25519_p1p1_1 *r, const ge25519_p3 *p, const ge25519_cached *q) { - fe25519 t0; - - fe25519_add(r->X, p->Y, p->X); - fe25519_sub(r->Y, p->Y, p->X); - fe25519_mul(r->Z, r->X, q->YminusX); - fe25519_mul(r->Y, r->Y, q->YplusX); - fe25519_mul(r->T, q->T2d, p->T); - fe25519_mul(r->X, p->Z, q->Z); - fe25519_add(t0, r->X, r->X); - fe25519_sub(r->X, r->Z, r->Y); - fe25519_add(r->Y, r->Z, r->Y); - fe25519_sub(r->Z, t0, r->T); - fe25519_add(r->T, t0, r->T); -} - -void ge25519_tobytes(unsigned char *s, const ge25519_p2 *h) { - fe25519 recip; - fe25519 x; - fe25519 y; - - fe25519_invert(recip, h->Z); - fe25519_mul(x, h->X, recip); - fe25519_mul(y, h->Y, recip); - fe25519_tobytes(s, y); - s[31] ^= fe25519_isnegative(x) << 7; -} - -/* multiply by the order of the main subgroup l = 2^252+27742317777372353535851937790883648493 */ -void ge25519_mul_l(ge25519_p3 *r, const ge25519_p3 *A) { - const signed char aslide[253] = { - 13, 0, 0, 0, 0, -1, 0, 0, 0, 0, -11, 0, 0, 0, 0, 0, 0, -5, 0, 0, 0, 0, 0, - 0, -3, 0, 0, 0, 0, -13, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, -13, - 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 11, 0, 0, 0, - 0, -13, 0, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 3, 0, 0, 0, - 0, -11, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, - 0, 0, 7, 0, 0, 0, 0, 5, 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, 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, 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, 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, 0, 0, 0, 0, 0, 1}; - ge25519_cached Ai[8]; - ge25519_p1p1_1 t; - ge25519_p3 u; - ge25519_p3 A2; - int i; - - ge25519_p3_to_cached(&Ai[0], A); - ge25519_p3_dbl(&t, A); - ge25519_p1p1_to_p3(&A2, &t); - ge25519_add2(&t, &A2, &Ai[0]); - ge25519_p1p1_to_p3(&u, &t); - ge25519_p3_to_cached(&Ai[1], &u); - ge25519_add2(&t, &A2, &Ai[1]); - ge25519_p1p1_to_p3(&u, &t); - ge25519_p3_to_cached(&Ai[2], &u); - ge25519_add2(&t, &A2, &Ai[2]); - ge25519_p1p1_to_p3(&u, &t); - ge25519_p3_to_cached(&Ai[3], &u); - ge25519_add2(&t, &A2, &Ai[3]); - ge25519_p1p1_to_p3(&u, &t); - ge25519_p3_to_cached(&Ai[4], &u); - ge25519_add2(&t, &A2, &Ai[4]); - ge25519_p1p1_to_p3(&u, &t); - ge25519_p3_to_cached(&Ai[5], &u); - ge25519_add2(&t, &A2, &Ai[5]); - ge25519_p1p1_to_p3(&u, &t); - ge25519_p3_to_cached(&Ai[6], &u); - ge25519_add2(&t, &A2, &Ai[6]); - ge25519_p1p1_to_p3(&u, &t); - ge25519_p3_to_cached(&Ai[7], &u); - - ge25519_p3_0(r); - - for (i = 252; i >= 0; --i) { - ge25519_p3_dbl(&t, r); - - if (aslide[i] > 0) { - ge25519_p1p1_to_p3(&u, &t); - ge25519_add2(&t, &u, &Ai[aslide[i] / 2]); - } else if (aslide[i] < 0) { - ge25519_p1p1_to_p3(&u, &t); - ge25519_sub(&t, &u, &Ai[(-aslide[i]) / 2]); - } - - ge25519_p1p1_to_p3(r, &t); - } -} - -int ge25519_is_on_main_subgroup(const ge25519_p3 *p) { - ge25519_p3 pl; - - ge25519_mul_l(&pl, p); - - return fe25519_iszero(pl.X); -} - -#ifndef CRYPTO_ALIGN -#if defined(__INTEL_COMPILER) || defined(_MSC_VER) -#define CRYPTO_ALIGN(x) __declspec(align(x)) -#else -#define CRYPTO_ALIGN(x) __attribute__((aligned(x))) -#endif -#endif - -#define COMPILER_ASSERT(X) (void)sizeof(char[(X) ? 1 : -1]) - -int ge25519_has_small_order(const unsigned char s[32]) { - CRYPTO_ALIGN(16) - const unsigned char blacklist[][32] = { - /* 0 (order 4) */ - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - /* 1 (order 1) */ - {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - /* 2707385501144840649318225287225658788936804267575313519463743609750303402022 - (order 8) */ - {0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, - 0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, - 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05}, - /* 55188659117513257062467267217118295137698188065244968500265048394206261417927 - (order 8) */ - {0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, - 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, - 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a}, - /* p-1 (order 2) */ - {0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, - /* p (=0, order 4) */ - {0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, - /* p+1 (=1, order 1) */ - {0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}}; - unsigned char c[7] = {0}; - unsigned int k; - size_t i, j; - - COMPILER_ASSERT(7 == sizeof blacklist / sizeof blacklist[0]); - for (j = 0; j < 31; j++) { - for (i = 0; i < sizeof blacklist / sizeof blacklist[0]; i++) { - c[i] |= s[j] ^ blacklist[i][j]; - } - } - for (i = 0; i < sizeof blacklist / sizeof blacklist[0]; i++) { - c[i] |= (s[j] & 0x7f) ^ blacklist[i][j]; - } - k = 0; - for (i = 0; i < sizeof blacklist / sizeof blacklist[0]; i++) { - k |= (c[i] - 1); - } - return (int)((k >> 8) & 1); -} \ No newline at end of file diff --git a/trezor-crypto/crypto/sodium/private/ed25519_ref10_fe_25_5.c b/trezor-crypto/crypto/sodium/private/ed25519_ref10_fe_25_5.c deleted file mode 100644 index f0b495e05c7..00000000000 --- a/trezor-crypto/crypto/sodium/private/ed25519_ref10_fe_25_5.c +++ /dev/null @@ -1,885 +0,0 @@ -#include - -/* - h = 0 - */ - -void fe25519_0(fe25519 h) { - memset(&h[0], 0, 10 * sizeof h[0]); -} - -/* - h = 1 - */ - -void fe25519_1(fe25519 h) { - h[0] = 1; - h[1] = 0; - memset(&h[2], 0, 8 * sizeof h[0]); -} - -/* - h = f + g - Can overlap h with f or g. - * - Preconditions: - |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. - |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. - * - Postconditions: - |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. - */ - -void fe25519_add(fe25519 h, const fe25519 f, const fe25519 g) { - int32_t h0 = f[0] + g[0]; - int32_t h1 = f[1] + g[1]; - int32_t h2 = f[2] + g[2]; - int32_t h3 = f[3] + g[3]; - int32_t h4 = f[4] + g[4]; - int32_t h5 = f[5] + g[5]; - int32_t h6 = f[6] + g[6]; - int32_t h7 = f[7] + g[7]; - int32_t h8 = f[8] + g[8]; - int32_t h9 = f[9] + g[9]; - - h[0] = h0; - h[1] = h1; - h[2] = h2; - h[3] = h3; - h[4] = h4; - h[5] = h5; - h[6] = h6; - h[7] = h7; - h[8] = h8; - h[9] = h9; -} - -/* - h = f - g - Can overlap h with f or g. - * - Preconditions: - |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. - |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. - * - Postconditions: - |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. - */ - -void fe25519_sub(fe25519 h, const fe25519 f, const fe25519 g) { - int32_t h0 = f[0] - g[0]; - int32_t h1 = f[1] - g[1]; - int32_t h2 = f[2] - g[2]; - int32_t h3 = f[3] - g[3]; - int32_t h4 = f[4] - g[4]; - int32_t h5 = f[5] - g[5]; - int32_t h6 = f[6] - g[6]; - int32_t h7 = f[7] - g[7]; - int32_t h8 = f[8] - g[8]; - int32_t h9 = f[9] - g[9]; - - h[0] = h0; - h[1] = h1; - h[2] = h2; - h[3] = h3; - h[4] = h4; - h[5] = h5; - h[6] = h6; - h[7] = h7; - h[8] = h8; - h[9] = h9; -} - -/* - h = -f - * - Preconditions: - |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. - * - Postconditions: - |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. - */ - -void fe25519_neg(fe25519 h, const fe25519 f) { - int32_t h0 = -f[0]; - int32_t h1 = -f[1]; - int32_t h2 = -f[2]; - int32_t h3 = -f[3]; - int32_t h4 = -f[4]; - int32_t h5 = -f[5]; - int32_t h6 = -f[6]; - int32_t h7 = -f[7]; - int32_t h8 = -f[8]; - int32_t h9 = -f[9]; - - h[0] = h0; - h[1] = h1; - h[2] = h2; - h[3] = h3; - h[4] = h4; - h[5] = h5; - h[6] = h6; - h[7] = h7; - h[8] = h8; - h[9] = h9; -} - -/* - Replace (f,g) with (g,g) if b == 1; - replace (f,g) with (f,g) if b == 0. - * - Preconditions: b in {0,1}. - */ - -void fe25519_cmov(fe25519 f, const fe25519 g, unsigned int b) { - const uint32_t mask = (uint32_t)(-(int32_t)b); - - int32_t f0 = f[0]; - int32_t f1 = f[1]; - int32_t f2 = f[2]; - int32_t f3 = f[3]; - int32_t f4 = f[4]; - int32_t f5 = f[5]; - int32_t f6 = f[6]; - int32_t f7 = f[7]; - int32_t f8 = f[8]; - int32_t f9 = f[9]; - - int32_t x0 = f0 ^ g[0]; - int32_t x1 = f1 ^ g[1]; - int32_t x2 = f2 ^ g[2]; - int32_t x3 = f3 ^ g[3]; - int32_t x4 = f4 ^ g[4]; - int32_t x5 = f5 ^ g[5]; - int32_t x6 = f6 ^ g[6]; - int32_t x7 = f7 ^ g[7]; - int32_t x8 = f8 ^ g[8]; - int32_t x9 = f9 ^ g[9]; - - x0 &= mask; - x1 &= mask; - x2 &= mask; - x3 &= mask; - x4 &= mask; - x5 &= mask; - x6 &= mask; - x7 &= mask; - x8 &= mask; - x9 &= mask; - - f[0] = f0 ^ x0; - f[1] = f1 ^ x1; - f[2] = f2 ^ x2; - f[3] = f3 ^ x3; - f[4] = f4 ^ x4; - f[5] = f5 ^ x5; - f[6] = f6 ^ x6; - f[7] = f7 ^ x7; - f[8] = f8 ^ x8; - f[9] = f9 ^ x9; -} - -/* - h = f - */ - -void fe25519_copy(fe25519 h, const fe25519 f) { - int32_t f0 = f[0]; - int32_t f1 = f[1]; - int32_t f2 = f[2]; - int32_t f3 = f[3]; - int32_t f4 = f[4]; - int32_t f5 = f[5]; - int32_t f6 = f[6]; - int32_t f7 = f[7]; - int32_t f8 = f[8]; - int32_t f9 = f[9]; - - h[0] = f0; - h[1] = f1; - h[2] = f2; - h[3] = f3; - h[4] = f4; - h[5] = f5; - h[6] = f6; - h[7] = f7; - h[8] = f8; - h[9] = f9; -} - -/* - return 1 if f is in {1,3,5,...,q-2} - return 0 if f is in {0,2,4,...,q-1} - - Preconditions: - |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. - */ - -int fe25519_isnegative(const fe25519 f) { - unsigned char s[32]; - - fe25519_tobytes(s, f); - - return s[0] & 1; -} - -int sodium_is_zero(const unsigned char *n, const size_t nlen) { - size_t i; - volatile unsigned char d = 0U; - - for (i = 0U; i < nlen; i++) { - d |= n[i]; - } - return 1 & ((d - 1) >> 8); -} - -/* - return 1 if f == 0 - return 0 if f != 0 - - Preconditions: - |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. - */ - -int fe25519_iszero(const fe25519 f) { - unsigned char s[32]; - - fe25519_tobytes(s, f); - - return sodium_is_zero(s, 32); -} - -/* - h = f * g - Can overlap h with f or g. - * - Preconditions: - |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. - |g| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. - * - Postconditions: - |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. - */ - -/* - Notes on implementation strategy: - * - Using schoolbook multiplication. - Karatsuba would save a little in some cost models. - * - Most multiplications by 2 and 19 are 32-bit precomputations; - cheaper than 64-bit postcomputations. - * - There is one remaining multiplication by 19 in the carry chain; - one *19 precomputation can be merged into this, - but the resulting data flow is considerably less clean. - * - There are 12 carries below. - 10 of them are 2-way parallelizable and vectorizable. - Can get away with 11 carries, but then data flow is much deeper. - * - With tighter constraints on inputs can squeeze carries into int32. - */ - -void fe25519_mul(fe25519 h, const fe25519 f, const fe25519 g) { - int32_t f0 = f[0]; - int32_t f1 = f[1]; - int32_t f2 = f[2]; - int32_t f3 = f[3]; - int32_t f4 = f[4]; - int32_t f5 = f[5]; - int32_t f6 = f[6]; - int32_t f7 = f[7]; - int32_t f8 = f[8]; - int32_t f9 = f[9]; - - int32_t g0 = g[0]; - int32_t g1 = g[1]; - int32_t g2 = g[2]; - int32_t g3 = g[3]; - int32_t g4 = g[4]; - int32_t g5 = g[5]; - int32_t g6 = g[6]; - int32_t g7 = g[7]; - int32_t g8 = g[8]; - int32_t g9 = g[9]; - - int32_t g1_19 = 19 * g1; /* 1.959375*2^29 */ - int32_t g2_19 = 19 * g2; /* 1.959375*2^30; still ok */ - int32_t g3_19 = 19 * g3; - int32_t g4_19 = 19 * g4; - int32_t g5_19 = 19 * g5; - int32_t g6_19 = 19 * g6; - int32_t g7_19 = 19 * g7; - int32_t g8_19 = 19 * g8; - int32_t g9_19 = 19 * g9; - int32_t f1_2 = 2 * f1; - int32_t f3_2 = 2 * f3; - int32_t f5_2 = 2 * f5; - int32_t f7_2 = 2 * f7; - int32_t f9_2 = 2 * f9; - - int64_t f0g0 = f0 * (int64_t)g0; - int64_t f0g1 = f0 * (int64_t)g1; - int64_t f0g2 = f0 * (int64_t)g2; - int64_t f0g3 = f0 * (int64_t)g3; - int64_t f0g4 = f0 * (int64_t)g4; - int64_t f0g5 = f0 * (int64_t)g5; - int64_t f0g6 = f0 * (int64_t)g6; - int64_t f0g7 = f0 * (int64_t)g7; - int64_t f0g8 = f0 * (int64_t)g8; - int64_t f0g9 = f0 * (int64_t)g9; - int64_t f1g0 = f1 * (int64_t)g0; - int64_t f1g1_2 = f1_2 * (int64_t)g1; - int64_t f1g2 = f1 * (int64_t)g2; - int64_t f1g3_2 = f1_2 * (int64_t)g3; - int64_t f1g4 = f1 * (int64_t)g4; - int64_t f1g5_2 = f1_2 * (int64_t)g5; - int64_t f1g6 = f1 * (int64_t)g6; - int64_t f1g7_2 = f1_2 * (int64_t)g7; - int64_t f1g8 = f1 * (int64_t)g8; - int64_t f1g9_38 = f1_2 * (int64_t)g9_19; - int64_t f2g0 = f2 * (int64_t)g0; - int64_t f2g1 = f2 * (int64_t)g1; - int64_t f2g2 = f2 * (int64_t)g2; - int64_t f2g3 = f2 * (int64_t)g3; - int64_t f2g4 = f2 * (int64_t)g4; - int64_t f2g5 = f2 * (int64_t)g5; - int64_t f2g6 = f2 * (int64_t)g6; - int64_t f2g7 = f2 * (int64_t)g7; - int64_t f2g8_19 = f2 * (int64_t)g8_19; - int64_t f2g9_19 = f2 * (int64_t)g9_19; - int64_t f3g0 = f3 * (int64_t)g0; - int64_t f3g1_2 = f3_2 * (int64_t)g1; - int64_t f3g2 = f3 * (int64_t)g2; - int64_t f3g3_2 = f3_2 * (int64_t)g3; - int64_t f3g4 = f3 * (int64_t)g4; - int64_t f3g5_2 = f3_2 * (int64_t)g5; - int64_t f3g6 = f3 * (int64_t)g6; - int64_t f3g7_38 = f3_2 * (int64_t)g7_19; - int64_t f3g8_19 = f3 * (int64_t)g8_19; - int64_t f3g9_38 = f3_2 * (int64_t)g9_19; - int64_t f4g0 = f4 * (int64_t)g0; - int64_t f4g1 = f4 * (int64_t)g1; - int64_t f4g2 = f4 * (int64_t)g2; - int64_t f4g3 = f4 * (int64_t)g3; - int64_t f4g4 = f4 * (int64_t)g4; - int64_t f4g5 = f4 * (int64_t)g5; - int64_t f4g6_19 = f4 * (int64_t)g6_19; - int64_t f4g7_19 = f4 * (int64_t)g7_19; - int64_t f4g8_19 = f4 * (int64_t)g8_19; - int64_t f4g9_19 = f4 * (int64_t)g9_19; - int64_t f5g0 = f5 * (int64_t)g0; - int64_t f5g1_2 = f5_2 * (int64_t)g1; - int64_t f5g2 = f5 * (int64_t)g2; - int64_t f5g3_2 = f5_2 * (int64_t)g3; - int64_t f5g4 = f5 * (int64_t)g4; - int64_t f5g5_38 = f5_2 * (int64_t)g5_19; - int64_t f5g6_19 = f5 * (int64_t)g6_19; - int64_t f5g7_38 = f5_2 * (int64_t)g7_19; - int64_t f5g8_19 = f5 * (int64_t)g8_19; - int64_t f5g9_38 = f5_2 * (int64_t)g9_19; - int64_t f6g0 = f6 * (int64_t)g0; - int64_t f6g1 = f6 * (int64_t)g1; - int64_t f6g2 = f6 * (int64_t)g2; - int64_t f6g3 = f6 * (int64_t)g3; - int64_t f6g4_19 = f6 * (int64_t)g4_19; - int64_t f6g5_19 = f6 * (int64_t)g5_19; - int64_t f6g6_19 = f6 * (int64_t)g6_19; - int64_t f6g7_19 = f6 * (int64_t)g7_19; - int64_t f6g8_19 = f6 * (int64_t)g8_19; - int64_t f6g9_19 = f6 * (int64_t)g9_19; - int64_t f7g0 = f7 * (int64_t)g0; - int64_t f7g1_2 = f7_2 * (int64_t)g1; - int64_t f7g2 = f7 * (int64_t)g2; - int64_t f7g3_38 = f7_2 * (int64_t)g3_19; - int64_t f7g4_19 = f7 * (int64_t)g4_19; - int64_t f7g5_38 = f7_2 * (int64_t)g5_19; - int64_t f7g6_19 = f7 * (int64_t)g6_19; - int64_t f7g7_38 = f7_2 * (int64_t)g7_19; - int64_t f7g8_19 = f7 * (int64_t)g8_19; - int64_t f7g9_38 = f7_2 * (int64_t)g9_19; - int64_t f8g0 = f8 * (int64_t)g0; - int64_t f8g1 = f8 * (int64_t)g1; - int64_t f8g2_19 = f8 * (int64_t)g2_19; - int64_t f8g3_19 = f8 * (int64_t)g3_19; - int64_t f8g4_19 = f8 * (int64_t)g4_19; - int64_t f8g5_19 = f8 * (int64_t)g5_19; - int64_t f8g6_19 = f8 * (int64_t)g6_19; - int64_t f8g7_19 = f8 * (int64_t)g7_19; - int64_t f8g8_19 = f8 * (int64_t)g8_19; - int64_t f8g9_19 = f8 * (int64_t)g9_19; - int64_t f9g0 = f9 * (int64_t)g0; - int64_t f9g1_38 = f9_2 * (int64_t)g1_19; - int64_t f9g2_19 = f9 * (int64_t)g2_19; - int64_t f9g3_38 = f9_2 * (int64_t)g3_19; - int64_t f9g4_19 = f9 * (int64_t)g4_19; - int64_t f9g5_38 = f9_2 * (int64_t)g5_19; - int64_t f9g6_19 = f9 * (int64_t)g6_19; - int64_t f9g7_38 = f9_2 * (int64_t)g7_19; - int64_t f9g8_19 = f9 * (int64_t)g8_19; - int64_t f9g9_38 = f9_2 * (int64_t)g9_19; - - int64_t h0 = f0g0 + f1g9_38 + f2g8_19 + f3g7_38 + f4g6_19 + f5g5_38 + f6g4_19 + f7g3_38 + - f8g2_19 + f9g1_38; - int64_t h1 = - f0g1 + f1g0 + f2g9_19 + f3g8_19 + f4g7_19 + f5g6_19 + f6g5_19 + f7g4_19 + f8g3_19 + f9g2_19; - int64_t h2 = - f0g2 + f1g1_2 + f2g0 + f3g9_38 + f4g8_19 + f5g7_38 + f6g6_19 + f7g5_38 + f8g4_19 + f9g3_38; - int64_t h3 = - f0g3 + f1g2 + f2g1 + f3g0 + f4g9_19 + f5g8_19 + f6g7_19 + f7g6_19 + f8g5_19 + f9g4_19; - int64_t h4 = - f0g4 + f1g3_2 + f2g2 + f3g1_2 + f4g0 + f5g9_38 + f6g8_19 + f7g7_38 + f8g6_19 + f9g5_38; - int64_t h5 = f0g5 + f1g4 + f2g3 + f3g2 + f4g1 + f5g0 + f6g9_19 + f7g8_19 + f8g7_19 + f9g6_19; - int64_t h6 = f0g6 + f1g5_2 + f2g4 + f3g3_2 + f4g2 + f5g1_2 + f6g0 + f7g9_38 + f8g8_19 + f9g7_38; - int64_t h7 = f0g7 + f1g6 + f2g5 + f3g4 + f4g3 + f5g2 + f6g1 + f7g0 + f8g9_19 + f9g8_19; - int64_t h8 = f0g8 + f1g7_2 + f2g6 + f3g5_2 + f4g4 + f5g3_2 + f6g2 + f7g1_2 + f8g0 + f9g9_38; - int64_t h9 = f0g9 + f1g8 + f2g7 + f3g6 + f4g5 + f5g4 + f6g3 + f7g2 + f8g1 + f9g0; - - int64_t carry0; - int64_t carry1; - int64_t carry2; - int64_t carry3; - int64_t carry4; - int64_t carry5; - int64_t carry6; - int64_t carry7; - int64_t carry8; - int64_t carry9; - - /* - |h0| <= (1.65*1.65*2^52*(1+19+19+19+19)+1.65*1.65*2^50*(38+38+38+38+38)) - i.e. |h0| <= 1.4*2^60; narrower ranges for h2, h4, h6, h8 - |h1| <= (1.65*1.65*2^51*(1+1+19+19+19+19+19+19+19+19)) - i.e. |h1| <= 1.7*2^59; narrower ranges for h3, h5, h7, h9 - */ - - carry0 = (h0 + (int64_t)(1L << 25)) >> 26; - h1 += carry0; - h0 -= carry0 * ((uint64_t)1L << 26); - carry4 = (h4 + (int64_t)(1L << 25)) >> 26; - h5 += carry4; - h4 -= carry4 * ((uint64_t)1L << 26); - /* |h0| <= 2^25 */ - /* |h4| <= 2^25 */ - /* |h1| <= 1.71*2^59 */ - /* |h5| <= 1.71*2^59 */ - - carry1 = (h1 + (int64_t)(1L << 24)) >> 25; - h2 += carry1; - h1 -= carry1 * ((uint64_t)1L << 25); - carry5 = (h5 + (int64_t)(1L << 24)) >> 25; - h6 += carry5; - h5 -= carry5 * ((uint64_t)1L << 25); - /* |h1| <= 2^24; from now on fits into int32 */ - /* |h5| <= 2^24; from now on fits into int32 */ - /* |h2| <= 1.41*2^60 */ - /* |h6| <= 1.41*2^60 */ - - carry2 = (h2 + (int64_t)(1L << 25)) >> 26; - h3 += carry2; - h2 -= carry2 * ((uint64_t)1L << 26); - carry6 = (h6 + (int64_t)(1L << 25)) >> 26; - h7 += carry6; - h6 -= carry6 * ((uint64_t)1L << 26); - /* |h2| <= 2^25; from now on fits into int32 unchanged */ - /* |h6| <= 2^25; from now on fits into int32 unchanged */ - /* |h3| <= 1.71*2^59 */ - /* |h7| <= 1.71*2^59 */ - - carry3 = (h3 + (int64_t)(1L << 24)) >> 25; - h4 += carry3; - h3 -= carry3 * ((uint64_t)1L << 25); - carry7 = (h7 + (int64_t)(1L << 24)) >> 25; - h8 += carry7; - h7 -= carry7 * ((uint64_t)1L << 25); - /* |h3| <= 2^24; from now on fits into int32 unchanged */ - /* |h7| <= 2^24; from now on fits into int32 unchanged */ - /* |h4| <= 1.72*2^34 */ - /* |h8| <= 1.41*2^60 */ - - carry4 = (h4 + (int64_t)(1L << 25)) >> 26; - h5 += carry4; - h4 -= carry4 * ((uint64_t)1L << 26); - carry8 = (h8 + (int64_t)(1L << 25)) >> 26; - h9 += carry8; - h8 -= carry8 * ((uint64_t)1L << 26); - /* |h4| <= 2^25; from now on fits into int32 unchanged */ - /* |h8| <= 2^25; from now on fits into int32 unchanged */ - /* |h5| <= 1.01*2^24 */ - /* |h9| <= 1.71*2^59 */ - - carry9 = (h9 + (int64_t)(1L << 24)) >> 25; - h0 += carry9 * 19; - h9 -= carry9 * ((uint64_t)1L << 25); - /* |h9| <= 2^24; from now on fits into int32 unchanged */ - /* |h0| <= 1.1*2^39 */ - - carry0 = (h0 + (int64_t)(1L << 25)) >> 26; - h1 += carry0; - h0 -= carry0 * ((uint64_t)1L << 26); - /* |h0| <= 2^25; from now on fits into int32 unchanged */ - /* |h1| <= 1.01*2^24 */ - - h[0] = (int32_t)h0; - h[1] = (int32_t)h1; - h[2] = (int32_t)h2; - h[3] = (int32_t)h3; - h[4] = (int32_t)h4; - h[5] = (int32_t)h5; - h[6] = (int32_t)h6; - h[7] = (int32_t)h7; - h[8] = (int32_t)h8; - h[9] = (int32_t)h9; -} - -/* - h = f * f - Can overlap h with f. - * - Preconditions: - |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. - * - Postconditions: - |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. - */ - -void fe25519_sq(fe25519 h, const fe25519 f) { - int32_t f0 = f[0]; - int32_t f1 = f[1]; - int32_t f2 = f[2]; - int32_t f3 = f[3]; - int32_t f4 = f[4]; - int32_t f5 = f[5]; - int32_t f6 = f[6]; - int32_t f7 = f[7]; - int32_t f8 = f[8]; - int32_t f9 = f[9]; - - int32_t f0_2 = 2 * f0; - int32_t f1_2 = 2 * f1; - int32_t f2_2 = 2 * f2; - int32_t f3_2 = 2 * f3; - int32_t f4_2 = 2 * f4; - int32_t f5_2 = 2 * f5; - int32_t f6_2 = 2 * f6; - int32_t f7_2 = 2 * f7; - int32_t f5_38 = 38 * f5; /* 1.959375*2^30 */ - int32_t f6_19 = 19 * f6; /* 1.959375*2^30 */ - int32_t f7_38 = 38 * f7; /* 1.959375*2^30 */ - int32_t f8_19 = 19 * f8; /* 1.959375*2^30 */ - int32_t f9_38 = 38 * f9; /* 1.959375*2^30 */ - - int64_t f0f0 = f0 * (int64_t)f0; - int64_t f0f1_2 = f0_2 * (int64_t)f1; - int64_t f0f2_2 = f0_2 * (int64_t)f2; - int64_t f0f3_2 = f0_2 * (int64_t)f3; - int64_t f0f4_2 = f0_2 * (int64_t)f4; - int64_t f0f5_2 = f0_2 * (int64_t)f5; - int64_t f0f6_2 = f0_2 * (int64_t)f6; - int64_t f0f7_2 = f0_2 * (int64_t)f7; - int64_t f0f8_2 = f0_2 * (int64_t)f8; - int64_t f0f9_2 = f0_2 * (int64_t)f9; - int64_t f1f1_2 = f1_2 * (int64_t)f1; - int64_t f1f2_2 = f1_2 * (int64_t)f2; - int64_t f1f3_4 = f1_2 * (int64_t)f3_2; - int64_t f1f4_2 = f1_2 * (int64_t)f4; - int64_t f1f5_4 = f1_2 * (int64_t)f5_2; - int64_t f1f6_2 = f1_2 * (int64_t)f6; - int64_t f1f7_4 = f1_2 * (int64_t)f7_2; - int64_t f1f8_2 = f1_2 * (int64_t)f8; - int64_t f1f9_76 = f1_2 * (int64_t)f9_38; - int64_t f2f2 = f2 * (int64_t)f2; - int64_t f2f3_2 = f2_2 * (int64_t)f3; - int64_t f2f4_2 = f2_2 * (int64_t)f4; - int64_t f2f5_2 = f2_2 * (int64_t)f5; - int64_t f2f6_2 = f2_2 * (int64_t)f6; - int64_t f2f7_2 = f2_2 * (int64_t)f7; - int64_t f2f8_38 = f2_2 * (int64_t)f8_19; - int64_t f2f9_38 = f2 * (int64_t)f9_38; - int64_t f3f3_2 = f3_2 * (int64_t)f3; - int64_t f3f4_2 = f3_2 * (int64_t)f4; - int64_t f3f5_4 = f3_2 * (int64_t)f5_2; - int64_t f3f6_2 = f3_2 * (int64_t)f6; - int64_t f3f7_76 = f3_2 * (int64_t)f7_38; - int64_t f3f8_38 = f3_2 * (int64_t)f8_19; - int64_t f3f9_76 = f3_2 * (int64_t)f9_38; - int64_t f4f4 = f4 * (int64_t)f4; - int64_t f4f5_2 = f4_2 * (int64_t)f5; - int64_t f4f6_38 = f4_2 * (int64_t)f6_19; - int64_t f4f7_38 = f4 * (int64_t)f7_38; - int64_t f4f8_38 = f4_2 * (int64_t)f8_19; - int64_t f4f9_38 = f4 * (int64_t)f9_38; - int64_t f5f5_38 = f5 * (int64_t)f5_38; - int64_t f5f6_38 = f5_2 * (int64_t)f6_19; - int64_t f5f7_76 = f5_2 * (int64_t)f7_38; - int64_t f5f8_38 = f5_2 * (int64_t)f8_19; - int64_t f5f9_76 = f5_2 * (int64_t)f9_38; - int64_t f6f6_19 = f6 * (int64_t)f6_19; - int64_t f6f7_38 = f6 * (int64_t)f7_38; - int64_t f6f8_38 = f6_2 * (int64_t)f8_19; - int64_t f6f9_38 = f6 * (int64_t)f9_38; - int64_t f7f7_38 = f7 * (int64_t)f7_38; - int64_t f7f8_38 = f7_2 * (int64_t)f8_19; - int64_t f7f9_76 = f7_2 * (int64_t)f9_38; - int64_t f8f8_19 = f8 * (int64_t)f8_19; - int64_t f8f9_38 = f8 * (int64_t)f9_38; - int64_t f9f9_38 = f9 * (int64_t)f9_38; - - int64_t h0 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38; - int64_t h1 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38; - int64_t h2 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19; - int64_t h3 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38; - int64_t h4 = f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38; - int64_t h5 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38; - int64_t h6 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19; - int64_t h7 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38; - int64_t h8 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38; - int64_t h9 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2; - - int64_t carry0; - int64_t carry1; - int64_t carry2; - int64_t carry3; - int64_t carry4; - int64_t carry5; - int64_t carry6; - int64_t carry7; - int64_t carry8; - int64_t carry9; - - carry0 = (h0 + (int64_t)(1L << 25)) >> 26; - h1 += carry0; - h0 -= carry0 * ((uint64_t)1L << 26); - carry4 = (h4 + (int64_t)(1L << 25)) >> 26; - h5 += carry4; - h4 -= carry4 * ((uint64_t)1L << 26); - - carry1 = (h1 + (int64_t)(1L << 24)) >> 25; - h2 += carry1; - h1 -= carry1 * ((uint64_t)1L << 25); - carry5 = (h5 + (int64_t)(1L << 24)) >> 25; - h6 += carry5; - h5 -= carry5 * ((uint64_t)1L << 25); - - carry2 = (h2 + (int64_t)(1L << 25)) >> 26; - h3 += carry2; - h2 -= carry2 * ((uint64_t)1L << 26); - carry6 = (h6 + (int64_t)(1L << 25)) >> 26; - h7 += carry6; - h6 -= carry6 * ((uint64_t)1L << 26); - - carry3 = (h3 + (int64_t)(1L << 24)) >> 25; - h4 += carry3; - h3 -= carry3 * ((uint64_t)1L << 25); - carry7 = (h7 + (int64_t)(1L << 24)) >> 25; - h8 += carry7; - h7 -= carry7 * ((uint64_t)1L << 25); - - carry4 = (h4 + (int64_t)(1L << 25)) >> 26; - h5 += carry4; - h4 -= carry4 * ((uint64_t)1L << 26); - carry8 = (h8 + (int64_t)(1L << 25)) >> 26; - h9 += carry8; - h8 -= carry8 * ((uint64_t)1L << 26); - - carry9 = (h9 + (int64_t)(1L << 24)) >> 25; - h0 += carry9 * 19; - h9 -= carry9 * ((uint64_t)1L << 25); - - carry0 = (h0 + (int64_t)(1L << 25)) >> 26; - h1 += carry0; - h0 -= carry0 * ((uint64_t)1L << 26); - - h[0] = (int32_t)h0; - h[1] = (int32_t)h1; - h[2] = (int32_t)h2; - h[3] = (int32_t)h3; - h[4] = (int32_t)h4; - h[5] = (int32_t)h5; - h[6] = (int32_t)h6; - h[7] = (int32_t)h7; - h[8] = (int32_t)h8; - h[9] = (int32_t)h9; -} - -/* - h = 2 * f * f - Can overlap h with f. - * - Preconditions: - |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. - * - Postconditions: - |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. - */ - -void fe25519_sq2(fe25519 h, const fe25519 f) { - int32_t f0 = f[0]; - int32_t f1 = f[1]; - int32_t f2 = f[2]; - int32_t f3 = f[3]; - int32_t f4 = f[4]; - int32_t f5 = f[5]; - int32_t f6 = f[6]; - int32_t f7 = f[7]; - int32_t f8 = f[8]; - int32_t f9 = f[9]; - - int32_t f0_2 = 2 * f0; - int32_t f1_2 = 2 * f1; - int32_t f2_2 = 2 * f2; - int32_t f3_2 = 2 * f3; - int32_t f4_2 = 2 * f4; - int32_t f5_2 = 2 * f5; - int32_t f6_2 = 2 * f6; - int32_t f7_2 = 2 * f7; - int32_t f5_38 = 38 * f5; /* 1.959375*2^30 */ - int32_t f6_19 = 19 * f6; /* 1.959375*2^30 */ - int32_t f7_38 = 38 * f7; /* 1.959375*2^30 */ - int32_t f8_19 = 19 * f8; /* 1.959375*2^30 */ - int32_t f9_38 = 38 * f9; /* 1.959375*2^30 */ - - int64_t f0f0 = f0 * (int64_t)f0; - int64_t f0f1_2 = f0_2 * (int64_t)f1; - int64_t f0f2_2 = f0_2 * (int64_t)f2; - int64_t f0f3_2 = f0_2 * (int64_t)f3; - int64_t f0f4_2 = f0_2 * (int64_t)f4; - int64_t f0f5_2 = f0_2 * (int64_t)f5; - int64_t f0f6_2 = f0_2 * (int64_t)f6; - int64_t f0f7_2 = f0_2 * (int64_t)f7; - int64_t f0f8_2 = f0_2 * (int64_t)f8; - int64_t f0f9_2 = f0_2 * (int64_t)f9; - int64_t f1f1_2 = f1_2 * (int64_t)f1; - int64_t f1f2_2 = f1_2 * (int64_t)f2; - int64_t f1f3_4 = f1_2 * (int64_t)f3_2; - int64_t f1f4_2 = f1_2 * (int64_t)f4; - int64_t f1f5_4 = f1_2 * (int64_t)f5_2; - int64_t f1f6_2 = f1_2 * (int64_t)f6; - int64_t f1f7_4 = f1_2 * (int64_t)f7_2; - int64_t f1f8_2 = f1_2 * (int64_t)f8; - int64_t f1f9_76 = f1_2 * (int64_t)f9_38; - int64_t f2f2 = f2 * (int64_t)f2; - int64_t f2f3_2 = f2_2 * (int64_t)f3; - int64_t f2f4_2 = f2_2 * (int64_t)f4; - int64_t f2f5_2 = f2_2 * (int64_t)f5; - int64_t f2f6_2 = f2_2 * (int64_t)f6; - int64_t f2f7_2 = f2_2 * (int64_t)f7; - int64_t f2f8_38 = f2_2 * (int64_t)f8_19; - int64_t f2f9_38 = f2 * (int64_t)f9_38; - int64_t f3f3_2 = f3_2 * (int64_t)f3; - int64_t f3f4_2 = f3_2 * (int64_t)f4; - int64_t f3f5_4 = f3_2 * (int64_t)f5_2; - int64_t f3f6_2 = f3_2 * (int64_t)f6; - int64_t f3f7_76 = f3_2 * (int64_t)f7_38; - int64_t f3f8_38 = f3_2 * (int64_t)f8_19; - int64_t f3f9_76 = f3_2 * (int64_t)f9_38; - int64_t f4f4 = f4 * (int64_t)f4; - int64_t f4f5_2 = f4_2 * (int64_t)f5; - int64_t f4f6_38 = f4_2 * (int64_t)f6_19; - int64_t f4f7_38 = f4 * (int64_t)f7_38; - int64_t f4f8_38 = f4_2 * (int64_t)f8_19; - int64_t f4f9_38 = f4 * (int64_t)f9_38; - int64_t f5f5_38 = f5 * (int64_t)f5_38; - int64_t f5f6_38 = f5_2 * (int64_t)f6_19; - int64_t f5f7_76 = f5_2 * (int64_t)f7_38; - int64_t f5f8_38 = f5_2 * (int64_t)f8_19; - int64_t f5f9_76 = f5_2 * (int64_t)f9_38; - int64_t f6f6_19 = f6 * (int64_t)f6_19; - int64_t f6f7_38 = f6 * (int64_t)f7_38; - int64_t f6f8_38 = f6_2 * (int64_t)f8_19; - int64_t f6f9_38 = f6 * (int64_t)f9_38; - int64_t f7f7_38 = f7 * (int64_t)f7_38; - int64_t f7f8_38 = f7_2 * (int64_t)f8_19; - int64_t f7f9_76 = f7_2 * (int64_t)f9_38; - int64_t f8f8_19 = f8 * (int64_t)f8_19; - int64_t f8f9_38 = f8 * (int64_t)f9_38; - int64_t f9f9_38 = f9 * (int64_t)f9_38; - - int64_t h0 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38; - int64_t h1 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38; - int64_t h2 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19; - int64_t h3 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38; - int64_t h4 = f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38; - int64_t h5 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38; - int64_t h6 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19; - int64_t h7 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38; - int64_t h8 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38; - int64_t h9 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2; - - int64_t carry0; - int64_t carry1; - int64_t carry2; - int64_t carry3; - int64_t carry4; - int64_t carry5; - int64_t carry6; - int64_t carry7; - int64_t carry8; - int64_t carry9; - - h0 += h0; - h1 += h1; - h2 += h2; - h3 += h3; - h4 += h4; - h5 += h5; - h6 += h6; - h7 += h7; - h8 += h8; - h9 += h9; - - carry0 = (h0 + (int64_t)(1L << 25)) >> 26; - h1 += carry0; - h0 -= carry0 * ((uint64_t)1L << 26); - carry4 = (h4 + (int64_t)(1L << 25)) >> 26; - h5 += carry4; - h4 -= carry4 * ((uint64_t)1L << 26); - - carry1 = (h1 + (int64_t)(1L << 24)) >> 25; - h2 += carry1; - h1 -= carry1 * ((uint64_t)1L << 25); - carry5 = (h5 + (int64_t)(1L << 24)) >> 25; - h6 += carry5; - h5 -= carry5 * ((uint64_t)1L << 25); - - carry2 = (h2 + (int64_t)(1L << 25)) >> 26; - h3 += carry2; - h2 -= carry2 * ((uint64_t)1L << 26); - carry6 = (h6 + (int64_t)(1L << 25)) >> 26; - h7 += carry6; - h6 -= carry6 * ((uint64_t)1L << 26); - - carry3 = (h3 + (int64_t)(1L << 24)) >> 25; - h4 += carry3; - h3 -= carry3 * ((uint64_t)1L << 25); - carry7 = (h7 + (int64_t)(1L << 24)) >> 25; - h8 += carry7; - h7 -= carry7 * ((uint64_t)1L << 25); - - carry4 = (h4 + (int64_t)(1L << 25)) >> 26; - h5 += carry4; - h4 -= carry4 * ((uint64_t)1L << 26); - carry8 = (h8 + (int64_t)(1L << 25)) >> 26; - h9 += carry8; - h8 -= carry8 * ((uint64_t)1L << 26); - - carry9 = (h9 + (int64_t)(1L << 24)) >> 25; - h0 += carry9 * 19; - h9 -= carry9 * ((uint64_t)1L << 25); - - carry0 = (h0 + (int64_t)(1L << 25)) >> 26; - h1 += carry0; - h0 -= carry0 * ((uint64_t)1L << 26); - - h[0] = (int32_t)h0; - h[1] = (int32_t)h1; - h[2] = (int32_t)h2; - h[3] = (int32_t)h3; - h[4] = (int32_t)h4; - h[5] = (int32_t)h5; - h[6] = (int32_t)h6; - h[7] = (int32_t)h7; - h[8] = (int32_t)h8; - h[9] = (int32_t)h9; -} \ No newline at end of file diff --git a/trezor-crypto/crypto/sodium/private/fe_25_5/fe.c b/trezor-crypto/crypto/sodium/private/fe_25_5/fe.c deleted file mode 100644 index bdc215c6095..00000000000 --- a/trezor-crypto/crypto/sodium/private/fe_25_5/fe.c +++ /dev/null @@ -1,237 +0,0 @@ -#include - -uint64_t load_3(const unsigned char *in) { - uint64_t result; - - result = (uint64_t)in[0]; - result |= ((uint64_t)in[1]) << 8; - result |= ((uint64_t)in[2]) << 16; - - return result; -} - -uint64_t load_4(const unsigned char *in) { - uint64_t result; - - result = (uint64_t)in[0]; - result |= ((uint64_t)in[1]) << 8; - result |= ((uint64_t)in[2]) << 16; - result |= ((uint64_t)in[3]) << 24; - - return result; -} - -/* - Ignores top bit of h. - */ - -void fe25519_frombytes(fe25519 h, const unsigned char *s) { - int64_t h0 = load_4(s); - int64_t h1 = load_3(s + 4) << 6; - int64_t h2 = load_3(s + 7) << 5; - int64_t h3 = load_3(s + 10) << 3; - int64_t h4 = load_3(s + 13) << 2; - int64_t h5 = load_4(s + 16); - int64_t h6 = load_3(s + 20) << 7; - int64_t h7 = load_3(s + 23) << 5; - int64_t h8 = load_3(s + 26) << 4; - int64_t h9 = (load_3(s + 29) & 8388607) << 2; - - int64_t carry0; - int64_t carry1; - int64_t carry2; - int64_t carry3; - int64_t carry4; - int64_t carry5; - int64_t carry6; - int64_t carry7; - int64_t carry8; - int64_t carry9; - - carry9 = (h9 + (int64_t)(1L << 24)) >> 25; - h0 += carry9 * 19; - h9 -= carry9 * ((uint64_t)1L << 25); - carry1 = (h1 + (int64_t)(1L << 24)) >> 25; - h2 += carry1; - h1 -= carry1 * ((uint64_t)1L << 25); - carry3 = (h3 + (int64_t)(1L << 24)) >> 25; - h4 += carry3; - h3 -= carry3 * ((uint64_t)1L << 25); - carry5 = (h5 + (int64_t)(1L << 24)) >> 25; - h6 += carry5; - h5 -= carry5 * ((uint64_t)1L << 25); - carry7 = (h7 + (int64_t)(1L << 24)) >> 25; - h8 += carry7; - h7 -= carry7 * ((uint64_t)1L << 25); - - carry0 = (h0 + (int64_t)(1L << 25)) >> 26; - h1 += carry0; - h0 -= carry0 * ((uint64_t)1L << 26); - carry2 = (h2 + (int64_t)(1L << 25)) >> 26; - h3 += carry2; - h2 -= carry2 * ((uint64_t)1L << 26); - carry4 = (h4 + (int64_t)(1L << 25)) >> 26; - h5 += carry4; - h4 -= carry4 * ((uint64_t)1L << 26); - carry6 = (h6 + (int64_t)(1L << 25)) >> 26; - h7 += carry6; - h6 -= carry6 * ((uint64_t)1L << 26); - carry8 = (h8 + (int64_t)(1L << 25)) >> 26; - h9 += carry8; - h8 -= carry8 * ((uint64_t)1L << 26); - - h[0] = (int32_t)h0; - h[1] = (int32_t)h1; - h[2] = (int32_t)h2; - h[3] = (int32_t)h3; - h[4] = (int32_t)h4; - h[5] = (int32_t)h5; - h[6] = (int32_t)h6; - h[7] = (int32_t)h7; - h[8] = (int32_t)h8; - h[9] = (int32_t)h9; -} - -/* - Preconditions: - |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. - - Write p=2^255-19; q=floor(h/p). - Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))). - - Proof: - Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4. - Also have |h-2^230 h9|<2^231 so |19 2^(-255)(h-2^230 h9)|<1/4. - - Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9). - Then 0> 25; - q = (h0 + q) >> 26; - q = (h1 + q) >> 25; - q = (h2 + q) >> 26; - q = (h3 + q) >> 25; - q = (h4 + q) >> 26; - q = (h5 + q) >> 25; - q = (h6 + q) >> 26; - q = (h7 + q) >> 25; - q = (h8 + q) >> 26; - q = (h9 + q) >> 25; - - /* Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20. */ - h0 += 19 * q; - /* Goal: Output h-2^255 q, which is between 0 and 2^255-20. */ - - carry0 = h0 >> 26; - h1 += carry0; - h0 -= carry0 * ((uint32_t)1L << 26); - carry1 = h1 >> 25; - h2 += carry1; - h1 -= carry1 * ((uint32_t)1L << 25); - carry2 = h2 >> 26; - h3 += carry2; - h2 -= carry2 * ((uint32_t)1L << 26); - carry3 = h3 >> 25; - h4 += carry3; - h3 -= carry3 * ((uint32_t)1L << 25); - carry4 = h4 >> 26; - h5 += carry4; - h4 -= carry4 * ((uint32_t)1L << 26); - carry5 = h5 >> 25; - h6 += carry5; - h5 -= carry5 * ((uint32_t)1L << 25); - carry6 = h6 >> 26; - h7 += carry6; - h6 -= carry6 * ((uint32_t)1L << 26); - carry7 = h7 >> 25; - h8 += carry7; - h7 -= carry7 * ((uint32_t)1L << 25); - carry8 = h8 >> 26; - h9 += carry8; - h8 -= carry8 * ((uint32_t)1L << 26); - carry9 = h9 >> 25; - h9 -= carry9 * ((uint32_t)1L << 25); - - h[0] = h0; - h[1] = h1; - h[2] = h2; - h[3] = h3; - h[4] = h4; - h[5] = h5; - h[6] = h6; - h[7] = h7; - h[8] = h8; - h[9] = h9; -} - -/* - Goal: Output h0+...+2^255 h10-2^255 q, which is between 0 and 2^255-20. - Have h0+...+2^230 h9 between 0 and 2^255-1; - evidently 2^255 h10-2^255 q = 0. - - Goal: Output h0+...+2^230 h9. - */ - -void fe25519_tobytes(unsigned char *s, const fe25519 h) { - fe25519 t; - - fe25519_reduce(t, h); - s[0] = t[0] >> 0; - s[1] = t[0] >> 8; - s[2] = t[0] >> 16; - s[3] = (t[0] >> 24) | (t[1] * ((uint32_t)1 << 2)); - s[4] = t[1] >> 6; - s[5] = t[1] >> 14; - s[6] = (t[1] >> 22) | (t[2] * ((uint32_t)1 << 3)); - s[7] = t[2] >> 5; - s[8] = t[2] >> 13; - s[9] = (t[2] >> 21) | (t[3] * ((uint32_t)1 << 5)); - s[10] = t[3] >> 3; - s[11] = t[3] >> 11; - s[12] = (t[3] >> 19) | (t[4] * ((uint32_t)1 << 6)); - s[13] = t[4] >> 2; - s[14] = t[4] >> 10; - s[15] = t[4] >> 18; - s[16] = t[5] >> 0; - s[17] = t[5] >> 8; - s[18] = t[5] >> 16; - s[19] = (t[5] >> 24) | (t[6] * ((uint32_t)1 << 1)); - s[20] = t[6] >> 7; - s[21] = t[6] >> 15; - s[22] = (t[6] >> 23) | (t[7] * ((uint32_t)1 << 3)); - s[23] = t[7] >> 5; - s[24] = t[7] >> 13; - s[25] = (t[7] >> 21) | (t[8] * ((uint32_t)1 << 4)); - s[26] = t[8] >> 4; - s[27] = t[8] >> 12; - s[28] = (t[8] >> 20) | (t[9] * ((uint32_t)1 << 6)); - s[29] = t[9] >> 2; - s[30] = t[9] >> 10; - s[31] = t[9] >> 18; -} \ No newline at end of file diff --git a/trezor-crypto/crypto/test.db b/trezor-crypto/crypto/test.db deleted file mode 100644 index e9c03593f91ac94836fa485dbe5d31953b4430f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20467712 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%0DQGp&?S?r}*FEBIpE@dAXQjs^f)@08eWS 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
EAM{pF*eaU!}`6CJq3^pR8 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

  • 3?F%Ge0rF5~rJ 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�rtEs@P)`WMh+)kCu}_*t2oI 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
    NU!%Qke=8YZ#86cm$5PY1AC~XDp!@c$&u>Nq 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

    HCGI!@sQAZgneA0qwJHfb>UTxR@6BBMga7B@xgG zjvl~l*?htv#KPROQkkS?>cqi=x&7jW}pP?;)?ZvdvQr~_;%#J%9=&^ zO1LbsjX)qh%z{^J@ll;VGgRg`yuN+=g4stk3w#~-EcR3kW~;&-BIL5zH_ka<0M*&$ zloWwvgcrv%LHLOYO<_7&c}4sth@^N%Gi5fWHUuq_fjznnY^na?;`-+u!p0XpKh)yx zE?YN+JyBKsA;`tTBM=F}6)_4RC6w`ExS>c!{NM%X z(rbNDO#zdPGo9^&)5j}U;;RmaWYtemph~O{>o}!6RfK8 zq|w!HeYQ0@G4Q3IqCWxpxel2WUV_>^D)+H2v3w+iAJZ9^Jt;}2ObUibkVX=Zm&MiQ zHM%uyQNie+9~V41JmlA`<4$#+>rGMEsw5g&LO&L>&FX=1=qIlm$ZMWE+~UTE{?`_?*H4>t^c zr{d+QuwlC}0O~fl1x7Mj3+W5NwTZ>jLB)l?^C7+H7~2;G^h6>=bhFLc=5}I`+}w zkuaYYF>O{G4N8I#8sh#MU4uCUSH7(MWi*8_Md!~a22|=+?RF}O8|K%*eVurzYOuA8IUQqCgeQw6ygZ3+Q4S_(H7&dU_ z8M?HtqcVwz(;KF$)Ik%C4T^EwEr1&AwbRUb=)p z-3kFM+#0+iq9KYb6?K#q`Is9Ti5sz1Rnue9{mzy+sGMzLHlRoEcTM8bd=)(z_?@GX z|LR@&M-WE>GtZdVA(bna=>-nktnH4kJQ(;A`noC{dZUg2E7A(nVqTCMlJi&Nb&hVt zOO?c4mL%AY?SW0r{BK@J9x)Kc7?91rRJoIbvvHu({R+3ND0Y8k4i6g^7wDFXcZJMu ze=arj{K@ryo;9B+Ts<;v#?vBqMK^ZjNQ5}_&2P%G>wuFPaXy3susGn>A$Eu?6wCx( zA=PWg7H3*v?DomTFryxw>K1%vep}yhr<2_Mf9!kJd-23TMOWt3Aks;pp)jJ005XhD z{`zQg!ByC&7%&IOpJSCPFaxkaBz;IE3Pk7_yg7_bU{vhzp{I|f-`Tlzb?egCsg*ua zhU1HUJr!NplHli+ly321r?ag3Np*GQ3$%4?ODJnzPqO(UHA~?(U%2 zAw&Tq=IhT20YI*omy@FEtI}1@-HXB|a>Nzujd5Dsk+h_c7d#WpXXef$588cN(K4;U zIrF{ulU?70?tzL;2gX^0vqc;=@{y4Df_;EED%?W0zPXGYE_c!F!o#)=zDNjee1E?o z%DyraCAkeGVY_#s^BUiRal`s|aGceA!;WseJ{?3I5aLMKk=^d)8kM@9%_5Vub9v8&cCdTW#J>bwj&` z&EiyV4H$X%VS%@zEjx_hh9GKz_b`4KHawY0iqYd=SUpzyP>ih$PX*jLa2ZHr9|sB) z1AW*vnU&??Mai$de=+QZvAXK&Z+}XvzR}+_fXt~ZdbtOPu=(17wcW%>;hb8m*jQUv>yTEk zcpB<@$20>p>#CY+oKR}45u(vhe~0>M^(Y}sy}rJV+8L!Nl`@r3m1fEpmE)9~>-_^k zPvJ#RRCHry8{i}(o+Tw9rN}3aB`jSmEXhNVsOynriFle;E=A2t%#*0Q*s?Pv0wj_W zXGGF>_K5aibHuND2xcQ#;LVHeGR=32L*gFHqDC;KNHTk}l2b^YYE&ylP{>8JJHOYQ|u)=*#J>jr70d|D-WR-VM8U&CUN2o^{r zF)1e?CD}2jyudTXAy{?<#W+~T=6y$sW62+jkr|l>E(Kv=lc^FT`+_h$P&~&rV%03* zxXSJ^ycb4<#H@$F^08A=G`yqS1ZjpYN^-YYwkW8GATr&VWsd-dA)_f$uvH4*;hqr+ z=M1l69yq5lF<*@44-SR5cL_!Eq?u(A846e85(n!korsk6;@5M0W#$UVabu9zC|3vx zR(1*;)96G98T(@3BPi96UfeucQlyd|+}ul~k)5Ok=4Qo^vL#aemDbyA)9NO74#;21NpF#VjaY zv_VEipadPr)sPXTB-$XpEcQLI17(AW-^Ne^JToUYnM5EQ*$R?-gaZ`DnQH@c+l6Wf zEa!>BW{4KSpJNsfR9r+D;PUt(%sq`+U0-jpkyKzwK3CYaN#gaR-VRC;v7OEw4Jnr^ zxK~kB zN-DL$E2fY2kSw2ctWtm(sYT2;#jt@Xh(whQX>KWz4R10Umoofp7;cmrLMz9v;G+e3 z3n!x}#Uu}smjaZe1Z1z$8x{g%EdJLq#hqe& zOC(KU99RaDHELGTuRIa;_8DH#ESmnrpz`e zygB}3%?A%6BUM;AT}=i{8W{l2}(7}C^cRJ zcZTV|J`}e%JPN@${jd)1i0w&dsvN790ywmgsD3TZnJ@$yBmE|X!Q*T8_q$V+Ued?s z6@?+f0Ms2E5PYq_pgaV)d|(<_U1X-*#IKtO34y8We@fbzWRuC%{4J@-+z}KIW%mWj zyUE@u0)2?ZFs`_ayzz);C66V3@gS3bnt84uf|btdS|ixXk_0@NibOosC6Vi~aX~2Z z_kb6JpGpQS5>B%)cd2xS(kOi(Y|k=MQsKO4c#u?g6?+_+C~rwL1y+LHf*8%7D{Of9 zbAMP0Tt)QwXP=UIIM+6k^nM~(N(vj15#%K|M>_S;E5n|U`~%mP0eT159@odxf~Rrf zjzkwny3AnhVD-T;z+5xqhULl%ok0JJ{Su;VlNgtjkqQgNMHi-uA)|zfFEDlH`0Bh{ z4)u``gGkB2l7!-{^bD2I8aVD4ycORURspUal7!?~2fMT69SKE^Sp3hM12STuW( z2{Ljw2vfv!S)QRxBp==^GgG`bRgjCTbG?4NKG2Zx7zyS%1y#9TN zO4?oU`>?7wc$uQvY!_nNdQ*x3eY4{ZZiElshu_JS65v*Q`iVpC{nN?enNB$t#*uSU zwL-x85NUxo!w?Br1c)ighX@y5V+lXePE|4{@+r>>G@-^XTa5CRm|%&#P!!Xx!_c^% z20P^=Ls?C9UTPE0J^~+Nk9jQrZy|&T`fK(3>fM9>pO$WpZf~7yIzBq0_ImB^TGzE= zwd!l`(j2PsOd~;~q54ks!D>&yw%1YJsA{Y72n>1?8x{rj5$qP089!y2g@iNp z=y~^b?w(~0FT7XI3vVB_W=QF|VV;UG79W;@E*vvV4xJZ{PAoNEDg>JpACDDYMw%5^ z8W;-tHgLx!M-tWs3Mz;SMp;;>y0~mUwfM@6CpIf@t{ir#T=Y_eviLAJ#*$+~nMlCh z=}^FJfnq~+KZCD@@6Q9Z9^Sx7%4JJrKpRuMXmK>d?^~8zTF6tM1G|0?>R+Sr^}L%R zgwjS{tPZsgt9UG(XVR)bUV;JLnYm%5v}CXZtTRKHa$K_%a6z~rcG2OqLB(%Z4cF;x zels#=^Xvm_UVjKu1OtU;2|z2QCt(&S0?*T2@-ZW&4e%>?x|LZCmXN@bq#r1Ifb#!x z_vhbQq4gUq>@<$k}@4t6~jLFo~{z3Z~n(!S9OL(EvU3a|~`Dj#11EW(WQ?S3L8BGAqt;4lTgeT3-L- z!LI3ZLL3V(dMr=4z9e8|{b3;rZ~=h+$KH;Azw3NQAnBu9fAT~ZiA!7Oyh z<0kc((Ybo95+3eSAE{t#?Wn%|7F$@TAqu zZZGUwzWr$nsRjNtP!ULF0_KErNs5V)pP12__%Uh;* zQU-?kwkjCiteKy}gDnP_4y+yE_!#xcpF%O*#h0$pyv`)AEQAOtD^e}H#eeV9#9i<} z3K*ZR+oVVWuUsLNpxnF;+^Nquv3O9DxP`xLPG{7Yj z3zwW0R=WvX3ON~6T0`Y9^v!W%-m%_n3&CDfWurJ&h6)3;jf!(M?Udf7kOV%eXhQz!iBwsmy(QxZZXSrVatH@2FDq%!lEX%%qSqUaZ6(X zJvcOs+7QVC4+y(^F?2r1Q|6Z3P|*#= z*Pqj^pj*oV*R<$xnPfyJT$7nE5X;k9gC&S4!X6w?j)>{kXUH z$j2Blef|nJ#T0gKa!d>Plv0QUcQw3&a+p#sl>#GDq#i$s6tAcb9zqVdD~6(mskc)6 zs}kL)xH+d=64LKup z8lY{M11f^a{32M9m{3G(-~_;};K6^&8%W%*lG*7`HdHyq=Ov^puRpFz+~|;b(MN&H zQyWW}LxD2rfiSm(2bUv$a<+sL(`*w+ARNZ4gnfWNCM75l5yI(%pvJdfbA7UM>iOs8 zZ#r)DZnNU$ri1m|6bNoNhME}`ktB%?*b`%?A=$^q4kN&U$&76Xdx`Z+_$?muX1|(6 zJ@H)#H89{oRhyY-b<1`Yg^d_J!}`;e3OhRw1ri+1EKz_H_D2G_v2zTm2F#rtM8)R` z#_nU+Uy5(YgCT|hteOv{a6%&hFL7K|ydt7}@7y}8d^h$os;tS4of{jdz?~2|O-}1% zF-%&k_=wD0awIL4!IR>s!LUvMt1z<#P`)?WNv>hWGCs_n$$7dy4OY4&_k6QJ*`rtQ zt%@0{XAT^j>7~H^u%)H!q6Vu;152E4VmuXr z;EdOg-z_^2df4?%n?vqZFU~mFow$0euCD??vUW(-vx0#5h)7QXb~GJ{BxaUDkYdnz zNW)2p2UK}Rzki){>`Aa6SoB8XHnJfgYWY!r(3^Y4trDIckDaWeccALvnHz6B6o@E5 znUBHoBy=oh4LcpT43>REAQr(pFjUw=20;x`bD4AI!O2M!EI?WgZ)NM_M$oZmyGmzF z3QKC&@Ml-ggQHhkxO-Ce$nr0Em8w2Rhmd~?+m9G3)---0k4-TlZomfER-$`6ynjYj z#iyl0f|O|g;jB8i-PL=oJ4~_aFlf$(QB!KIuK6o)%kE@}ExROX1Wg(u zPtSrE#DJ3GB;>7OHgHE`F+Iw#@Lav@aPoP@E!9652RXCVZPzbua7)*IcEct6AN~IA zG|_tPfk~4UNVwFsq{Ir3O-t5=Q{i}Ij28vPae~Rijv|MU-Uyoymx7C~f4*e<=253v zb``C>o2|*zFDP``mFA&9_@J33d0N=dSduIQMcV|cmr7GgfMfUrj4^`Lq(2rJ4xMOP z!q_KN5D=e=bYJIeoV|V6btSz%>FuBCxeeT?rkWMR>i;bgvsCo$#4P=C{oVSR`ZF}@ ztN&Dgq<&I;oBA^KDD_F|HtOc;nriRWt||{!+po4>ZI1FqHGj2HYTeZusR^ntRLfOV zRClSaRE<}4Q?*waui9SKP^Ct{jq+aQ*ZTT;ReHDej_7UHTc{VRH(q(Bp0!?6JvH4o zx|g9KkfpL!H%Zr5cerj>6>D8%onJanbWZE+&`H;srZZJ%fR2TZuJ#x0TiS=UH)$`> z4%T+m?xAg}5~eNcyGWXXnUbPldo4qiW18PIA1JTVEYr+WX`-2;?4cQ{IZ?B(%012I zDvR}>YpQF!)u_-Y)>x~NsNt(IT%(I}du0QquS$27jwj43*>mO9cu2foe>L;s45COzI)Qqw3C=!`V6pacg&*T{KTxu_Qf3 z9F@$ZIT9)ohVLmZ4el^+dGG!S%`QKUd03-nN)J`c*rDC;OX@YpN{#jR?+u&eS=v*y zOrnP_-^@36D886=%4uYx;lYl#E3YLrH>8$J&ns4UH9FPcLDNNl)D7;g{OlGp)1A7z z^z7z?*^0dZ-(Q&5(^+nKNL^!z@S8+H5-vq$8=O?QScFV@6SgQo*?!K_Oi}v_^>=aN z@YC0i%-p59&*o9n`T<`P_|YzE$mbRlP3_qI~~V$2L|*4hPQuxT+a*;?-@3#==VyKTIf3Jbu0L z!H_?#%QJT#yEl1KRo!%9RRTSA#xo0HxJX-7ObkC^^yLzWC92c8^RUAo`loATFuDiJ`Nj6P!x|v@2 z;$Y<}ld0m!*7VfkqxGKE_*E)jR_NA!7PX|ehU!dl_*-h)+r{5v+xw2~x9mH!fAia@ z2LQjQ#yX)Hi;~96aedS}n8}=F) zcf7f1zn_}sM&~;kH%Xjau*_Cn`Ol$GnUzPxK~(!FA@-c_mg8qr2k#elbloA&>e%3X zE76Xm^Ah%!1cx5-YW_s$xUttslbAX^GHS(vzo^5E_=X3@z8Kte+wiKx=RUhu%xd3D z?B9=?W(}!RGO=&xsY)C3nze28=*rgOWnyoVu}avwv1NAjj$L|c0eO?0{7f!+lpBk^ z74+2pj<=2awI81wbIWK)$8Zcp&eV%@MZ`*XXbx? zKCdFqL+$!VQ+=yZLBjeO)D&-@nWx;PlTXI62?bUQTznidX9{~C)0^T(6z$qkpsU$9 z-MxK4&dZEtQD1bF=%KiwjjFaK{Lx$gu$9Tj`hi6q9M=e2La8ZsU3|24<6$3%46G9# z)Ma(N;q}Hs^<3&Nw)?=MY1x_|r@r;rrZlGWloXBNP*OQen4a50?TgFEpl3BV_WaBo z=-9o|PI%Ol9*XYry1-#@WAl$2H;q1*=iem#rhBjJ)Do3ESE#jJtaH-H(ks)*q_N+g zbnRkliO7vh8Jg>{FJ!%rwZob&S5#M&PCH92;Xj5RcOJ1yJ1VOC{#eZ)I|FZBG7F+{ zhfQ(PB_^9Y->F_GoN(byjmqBStFJTIf=XH{p7lEJbek!p4yUHTxeG>>rNntHy|OMs zKy|X7S%qSYi_{eG-L>Jn4heN5Ry2&ui_m}fP+di=o5Y*m@9v+O<{Pm%`HI8IYaW}T z^a6#&pLo;xr*R+BPn0%&t-LzPp`~}_F9%`2DsMXGscyR=w`|1BGoK#(@Lgwnah7mz z5^svW;c)zQS>qK}b)wF^zt(E~tvSNO!@Ma{=|=RRu%FY?zSvlXbw54u-7(<~w^=8=gBGzv;D!B@ zaWBH34Rvy9)FIufc?u2Uc<0NB^A6oG$-Q~Tsc4(Q(I#oRqP9LYm3}Xp;yeFa|D7u* z9iMf2*v@H{$-<5U)O7fOQ(C@a=O=hPpu&P-}5xfAz2x@SR3o7o=+8aeFT zp*qG~IL3|Hz7&UvhZc1-nyhhcv*+}>;?=Wzg_Zp9DfX-PqS|7m@A-S*)>ItKT3&a@ z8&OPmS2%Y7?VORyARx$m!8zl*}m>b)12;4V-JqCY#T}~yB6;l z{m}0~+q^54mP(y!bUV2icrBn873>O3SaIB|cC}Vb1Di$fHW%3r7T%NYT|$A+NAopH zKK!}xZN1_B$;t~4e{0_?nI76cah>a1|4P+{S#2CP?H(87_q~Vko~jup~s}tF!tve3*JzS2*iQgUC8tUM;g;ni%DUF9 z6OE}1UBZ?3c_bIw^<(JW=( zbjrPbWPZ}!yWuAa+FsDSWIb}oZ|W}ln9HL<;_E$UC;7+sc)NJ`IkOtE0r!fsr#E-# z&}ij}y1oTN-)vHfJQ5s1 zHuB!1pIf%%74Etc@`(Cd{q@5B%(l~g%m!Bm3IS-l`_uSy;HB5 z>fOOLQ`6~p-BcST0E@466+dr$R};$5QV zj?+JXS~#psgUC?TSnzSyo3#eDISsF7p0_+vu+(fl^_LD3-iw-h6^EA{xqGvh*TL=E z422AS)~9c3>GIm5*I28#`pzG#))aL&T_PlIr2f*gnkD~w-7b5Dwdu@i{k$0u)B8G~ zqL#FgsmI5?s~Z}Za%@87%b^!`X_TsRbGqzuW#$6?jEDVJukEzt)u4(^Q)BJ&Xbj8B z_g#ALw(qCotl3?Hzelurbia-8yD4v)6@TARkkJ&anGzIiy{nc9oYoU#jB-u0Zd{`u_4n1|He67xoJ1~xt! z#%G$nXfmqDnm<`uLd7V0Xwk9HZw*Y=cSzpoRnT>%>8{fz4Fk9pTNG=qTQtT?_u|i; zOPAc((f;0`R-F&fGYe~9j+#2V{p7692ODfZ)MA9fZ`CaBA1pAjpFgv3a@4|aORZ80ERA8_szkSy>wVi@i)%U8yGm!FYqQg$`di-QQ08klGWdH<`Gu>c?Sp4r zI^`oQtD&ak=myHahU|Tz<6lv{EUuMmL7zLKi7$;Jxx00~?UVG@CzLw0Y~6L*>Z8Uc ztzS}0Qtj9u8%F;&cP;K+_ViGe_PiOJn-9{bCXdHU%BLr7T>dTo#^vQ*cc0&u_q)C+ zy-AU(KB06@WYNY8A42Cg*^_BJ^Rw8TN#aqfZzuhd^igWdk9BXhEcK*CZbGR!DCYhw2IC;Pu1h#yMNcx|az zr#F6*@`8!!9&?Q6M0e9!Ka>Ot6ZU)6`7>|k*X?B{F*}cznRIO2b*I?ukCvDr%n}C* z`porub<(6eduts%+}UqyA3^=Vh1wz7tqQ&Ve)%B`#RsEs)Z>ic)9H>&O@|5)cmrH8@ZB+D? ztCa63mn!EdFNWIx1m(WU8cOe!t}5+UTCX%$DL`qIlBJS~_(yyuo)veB8DblJEXZ1$x}e~I|Hs?&qVG1^lcpJaW>{_8?j(ntWF5I}q;Y<0*BPZ< z`u$jDvENH=vb8$()@!tIa^k4n4PO7LKH9nNlfAD)*CbH2@}!=bt>Szx7Jt_%Yg+7< z(o5y(+Hqp%!}O5Vtj#-j9XPa2#o}_t?PS+Q+Z1b^Dro6-@49hV_Zp`uw;N=w^_r*G zXY{a6!fUd^N!_*A+#P6T_VHZaIlbD-gj1u}&eWwaMv~>j=}yBQM_j$qY5$f}i>n=% z+}b6okhpk~W$>Mcv-1-^TRr;hayH!mk-@&HqMi{AqFZQw#@Uc6#g;Sslq@^#nUK)< z>#$GM(zS8L{P0;n-UmMFR~fl&@w%;tgWFSFDXGh=!CkVOb@1)8s@lP9N8YjO8#9I4 z3>tZt?bEW?f9ZFjZ2sXcs@vLmj~*~nsJcV_wQGHQ;5&zr+dDdCd)<3*^r)FyaJ^J| zsO{5>#}5XyyjSY9VQ{~ju9m4yf`r)=N=a&Sr=8~Tw!s5$ezMqWADCD_FaE8NPZ#&3 z)|;k{nY<_~TxYOfkp8M$8?0X}6O#B-7I|?c3vSeXEsh=g#QEw^^BKt%LX|F!!lF~! z($r8xo!M7XG6!iSo_pZl%8NhL;$1}Ug_#y@I+qXn^~rgQO~{*FqB<%0C$&ibTobjp zf4V5F7GmD=kAmk7;;r)_MaXZN9tVp0peU-n}(-xlvT zclmH3qU!EO)e&OHcl1!R4?Den4iD^a5LVsohkw0UeY;d^UZR<3`b4v;%W>U}k2=(P z?ETU>F)h2b@Q|d4lbXEV<>qeFWcq*!YlcSj?ibj@qqA_6yv!uC#-saePdht2FkJn~ z-DktLPB{8WG~#dD6tu%X)RHj-S_6g@)IlHtoGuE4>#Dwc=a8G6;V+NAsAF zOre*g2CtWltLv6CJfMgEhf@iD_p{oSwjgQtqy|PsJ)3LqNHLmNyQ6)_?tLz8doMid z$on%mmptn3#d@s*OqW_Od^|b3k*ToDfQDDUW&h*K_cyLmU!$aZLgQR<^OF1Cx9K6H z);b3ip-`p%RC2oWwyJ}JhE|JKW2mX#m;9YQZVv3-XTSUR zve2--uq0YzRrKl)(^qB=(siSU46lEm5WVzxr#1s7H*xOqs@bbawn82i$4D~FIl0-(LCK z4%~GzZ_Ng)%kCekw>n({FYIc(!`3M#ph?fcj^C|*JDAL&7X3pVH*E{HvtO8V!10>) zsW}USrrMFinxwz}L&ES?y-KVP4x01&c|}-O_W={U=o!7np@&}mtk{%xu%q{ko~f4U zjhlyTryr#I`%u(`Rhw#ZW^V|nIu)~iSYrnvV+wC_HflGaVV~-hpG(cnuX&%h^41f| zJ5Ya`8QzMApH6%qyDn+MPwTHn6-|emQ(f#NjgXyl^{$8LR{r^O;&M%cJza~M*P(J1 zN$N-HUCathQGVLz+o>VSA2NCzd7?ux+9Wl7>--s~SA^B;^FArfExf?$YNFWvCXGSa zX0~6Bq4|fV=5?)N-#eJ3XTKL-SMw&#>OnR?Cxr}Z_~CNY$WyQ0WZV-yQAi(`_Qtuedps>elE zi+-I?JJoy(QqTbR`^j(C-FK)6p^FtK*U*@qzs1De`z zsMp~7_O2bPMm!XIQp7Fs*Wur#7O~q;wG>Rt`v?u2tk5qSSWf-@3VZ5wsKxpIhq}9* z_waYKcaAU;dr@d5@t1kaFkFot=9g7cOc$fF7zjvFOaI4n6E6>O`s89*cJP)NQCx8$wOh7a#W<^-SU4+g;~~Xa8|K zep((E-cyt*vFgR^cdqNYHQcM{du(?*SK)K}sbW_;h!U$(oEL^!$6VRJ{d(7(Lzczd z$X+gd-$xyO35`AKS@+_ZFM+3z$6jin7u2Y;*m^5(5*xlPchae4(s$wP;N7^u*5{)&)DxN%Y9>Tot78%WZ;%Y9C?V zWa{wK;!T0~dIpCbwtPKkY@}YR@M8u-nl(+vhn*kxeHpTB{$+wn;(_)X1;%uZPpz-X$#+-_(Q5j z?M6>ns*&i~Xz~bQ-O#k;lis!Trplx0M+$%GXNs$e*Z05q-PNppjA&U(4^`StQJ$Mr zvS!J4GduNV`hnXQ4r=;}w{+{Ac*Mf*VBHkWwNKxv6sV6I7fuIdVr7fF_5&NQ?XaYt z+KYh~ighky1zoSL=5*kOOALD9l{$vvLBQjwL!7lp~sjTikkSTS$^ zGjspgwnkbrCQ?8;@kQ#(@hf!AN(*;{u9{q(;JP=uK$v}x2L8-{q)YW`N9Bi>eY>n2 zm|OhvdyH^?Dh==HuiJ(_?G!E#+#co}@m^hfV$o2)PV~@|S^D=5rCFscwV5*K^qQ&Z ze@eQFeGk$@Po^fD?h9V&7c|q~@#2UxXZzo|q_mqJdc4wDWn7TwjYR{t=-vpa`PO)h zfiU|UJ@n|s^9c)TYD|+xsnt8ByUTNd!rm;59(wrB_1cCeojrcptk?f>t2XY@l;y$_ z3j8EK$UENS_WEDdC!Rab+ZJwLZq~B9s56E7yZ?2f`EuWL?*`nowJLk-Y*9QXW+H_r z67SC{Enm=Q?eYuW7w#%0s+R-=ju9SA;19)*a%#0^+I;osl^+wf9#U<%U$_4_8pOTM zk1yYv+o{rKXL$4DUmrENxYJ80q;frpcPC{ZSW+~t+s?IBgWhlHZ<{&9!P$-;y4z!9 zYVnookR!Fem38#n{B*UPD7IWqO}Af8xVfjrYRg57eO<%XRmUHW`5;^=q^6q}9fw(H zZFumixb*S+39HZeH!l)4a-(!3x-|1!?~utyjLnM+|NOA3P<}X#3K1k;`*>~fV8iP< zUH@Et5!l+j{;C*Lp^)O3iC27jHFr4`qtxlegQU0DdZrH_>nTRC+D*I2sh65wA z^}~g)WbYHtTj@l`t(eew>#NMLhg#?E+8v7*o6V-)p^-p?&I9{j?uyu zE$Z)dbMI>tiYtCbZ!GA%b9d?1(qqvRxfwe(?vwq@)))07KTbHMxGyR*9se7}N)V7zSFC>C2ou&Gl^yyBlRr>u_nP z;XUoW#)osds5IED;+fZgA^V~j*$yBl227E~9Jz?AOv-^s3HT=p`z+8a9C%C7~-~pJH%>gT+3jk#hqi(o|WXFa3d^B~~9Xu&$mJnY7hFRpnyYm01ftee>!uv=`Hj1BM50@&t&_m(jN*$rP$p z`gdN#fXxMEjSDDpHlDkrd>jSXG=V;W|LWPOs-y1QDJmnEmgoi^3^Q9a{HBjeNn=`8yqW@6E+$!JaJ-;xB8ftS;S5}!q^}kl;Q?SI06|y}fbM$+y4B=rCGI|P z-avie+ms>6;ql|z2v$zF6^_$8zr;3k(*SvtomZ_M)cPS3w8^A`LH+ z6f~KBA|%l{xtyxIlIoCL0D!8ff=0nmxJ~2BE6ITiy(quo<74}`v7+gy>b{D zB9usEFIg`U$SkI2!mE4x0E5NAUcmLj0C=$fg-u{$Fkoyk;Ta|;oU6L`QoZbxeQyU$ z9$K-`Jv!mh?Ut^2dhF{H+#GZefY-3EY2GDO4XkC5a5xj0TLNOo_%ap&@QbV>3ekB9 zdc4TlX;spXfFpZyZp^j&ZhR*&d!MpTo-VU!T*E?Q1xQR`iNFciBBN(Xa591&QUwZP z>B&;nfU}V}7+8Av*#%D9wHs7Td2pfPM8>E>i*K&udxp5?>9C&?2y`NYSd+p9BnN<7 zW!P#^tk^dvTpsA*FsPiF%*)pT7Yy*4#3Z5VeiQ1wVA@juHyt_(gS$2<|57=rzm;E} zHv29zX&AC_I1B)!#6;so46RD^O-`-^4l^he##>Uw;7WwtD#OZgIXuo_=ok0pLQ1-3 z!8GrFuDgR;HugT1Gs-Vdi@l0V9n0(_P|~pBaEg%bI##qqS)@%UVJBfwn2DCu6NoH9 z`YKpEvt5Rxj1D(aP5K9zIhtV ztbq{;$s~6403ps;w15L+sw8;_pv9Rr;{+_O4)eEWfKcu^y9cN5=?lL^Coqbpys%7^@6?5p@gFGT@c0nh>lL;0}R7hD7pQ(rtqmrwxgL4lP z#VBT26vW+?l~`fvyg>_!%B?%B`P$fRZ?er_S3a`o_bd9l%2~HOHG)qEGJ6ez9Z;ay zekA|K4ja%yu@6agg4lkfnGESHb~s7$bf&hMJe19D2ca`*g-QFBm3)9QH+~XkmB}c?V*c5`d2$t_ zrnF=Ez5**T6g!qtQ5ZUu5uG=EF8Z}-_r<0zQ#Y=km=NEfO1G1VOP(?V&2r&CuF^K0 z1GJeSDV0>0skBU10Hw}3Wkxb5IC7Zo4t6t0+hJF0@3~o)JUH|5Rlp?YLjCZX$79O9 z@|4(&;bit=o&>@DxsD+iYkX~!LbyBd(_G@j+Xtru8B?lqK>G=28&(L2yUd&@gvgmH zKL_3MU7I?ge)?MX>`gbsYErV{=rzBt}4N6S0OnY&k~dF06@zhW(S>DqZK;%E^|%pnp`gpL<1Bla&dQ<9Sir#~9Q z<~VvEZ+O6rMFzqdd=8Sr1%HZJaHdqT_<-(gvoQ`ct*i5o&iZhFP}rwAI^K#{_8!nAoedLPa zsf7cc6?rM9vk#9chX=$Yw%{#s>IGMpGYaDC># $Dx5z2B!T;&-QSng3VMkA^D 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%0D!OauP`)*J}{9M~}U zu7v!;%nIVvy~{6~%TKkE0>*>NI=CeM=zm0u!ZW8jvYA2shvx#v1t*RNrtn^W6&C>k z>Bi}hU!RuhyU1yvW~KyKP$VwvP33=Rhfd7};3MH~Rsn!TbzIj4EP#fw_nJEVva6(1 ztS%%`wEj5Eo3vXN=L{@wzT4HXY@>105g{Kr0b_TjU+xXWV`vpYeiGaq>RyNi0PNh4 ztdKZxXtcnGiUAE<2OVmX5)G%O(wbkFT`CE|X@y}&w1?A3@V!K1>>!4KSWEVb)w0FK z6YjVY^yj84M&dq(d}P_B6D2@E;w1A`Ep;Lq+=QV+m>wR%7o%#UD&;QJvpzm5x=^QI z?6ED)LTd8-uDUu$>e3_xjWSc`cpN8UOJgRmjDEQF2&gh<_vV;HJsjt0G?E#>-6o|# z6Dbj(sMxby9-;Fv%r!@qpsl}_%H^2c1~h?MxjO2yEAVvGz%c;TrnDM(YpTlRQVS@D zQ)MkhX=o}4j8l+O?lWSH{QcMWCHy6fTTC)aDabWiUBJ>(wg>nDU3vP#Bp(dg{#2!@ z+S3`I3;nXf?5<&55hKG9q(aoF7;Y;Q5sVCoW}l;h0I!C`Knl= zzAR=?f$f-A%n4&QNQIGq%iZQeELbn(ziw7ttEsn^wGzVewa;kfg zLOWcET+*g}T3s22B}T~%SuMEST=F1`OvO%w!Ag})%k-Q*QOhf()(A?t2(35mVTI8! zk|aG5Bw>QV@+8o~3nTv&o>Qo?zkq&Gf3hcBYl_W@NKEDjHWD*}dlf}71hx25RugMV z_bYeVjkF-VM*PEVoU859SE`Jr8p5&bWb0wqhZ-m-WX(Qk#rTU`CEa{3N(0AsW;$^a&ySk(-O5gj{2;&BP0pA+63qtVR(`$=cBSb)_3 zFEpV0U(xHFR{-k&|MIMd*uSrbyZbWtYUuyh$ITrYeN|mfx|q=yV1jcor(I4R9N&O7 zUljHKo$O!PkFw8Zx6!Vd>6R(nKV zKtnP@6yWv-5;&maZyX15%h3=AX-=IS7yBzdsBZrngo8lEp5!I=yF2d8wRpTVo_XdVX+W+Z@ zZD4iFflO|L`wXd>(4+N+bWQWGzc-)^4nJye!p&F!Dg^r%T-CKs!@7=bGEG}pvq$bS<4(OfthZ zyyNhX4<~%DH#Q;chQGBc7ZKrB!R?8iO*vDbpR`_pMR7_Qi5P zDKH|W#F^F81Fe;)j1;>I;fm5!kY-^T*_KvRkyMd~4bKfvb+r^q9VC0G(Ws;BdfNk{;Of|k!1T8|n9hnNBq zV9b>K#aYF6B+s1wQNTWK*AjvdeCzStIes&xynb^#N4CdXx1U{Fy<#zowE~r{1t4{d zQ4zt2pog{td=*F!u_RZjSNCp~9oV89VfCM(*RsGj7|HuG>-H z#ZTvTxc)XvcWZg-DFwd-_ZDS3u+LQIBAgfcWpY;FumXK!EFU)mq4ky!4l39~h7*U) zX%5Sn+~Q!wisZ+M?x-aEZO`)Orx(<+Sj*9S09=fqPBFB5Vj|ee19JhyO%;W(LR7rQ z4vB_GB#pu}6Sb0|5r8xqzZuZ%pM+LNC-r%^e9GdKzk5#GKJds-Uu#)1b5eweO>9AC z5J^kQ#jt(|_B6LerRz@+X;p8BBtw8AivaPUqbHb^<@Yb{9qTOYG{j?glTTODXL)&W zS`R<#AEaeMm@9ekpp~E;4VN+*J=G8u7dKg);4nIp8uXSPmN2tKAj0zy7>CW^Zw|@5 z+qH0x5h5>P-H#n&ZNDFxtbZFooP6#0x zQZtO5M)8_fJTN2C8x*X=xD)A?_4b?U5xVPa!%ayiFN|syx43LCYZ)rWlV*6_0gan~ zlS>t6TPg}ciQ__hkvsk9kl3)yGJ4YfIit~#(wBjJC0aT5wF2z@vl4(tXbQ$mBT z-JH3tc+c>94@#UKQ~310zY5(9ww9zToC_Z{>fw)mo;X9Qxfo1O1fn|pHa0!hhYbM! zD>*9gkSNiG3?keZw4a1MSNw}5F9)`*?QpH|kOxKfxBk?+U*yqk#rjxF(EMrqN8lxG zuDIczN;ttF;Y0;8!YMS<=0%T zQFdncmxukWh3Pic^}>`u=LiSxG0YsW6p>{>z=qg`gB9h`ix|W&Fn%;PCL-ZL%c+Nf zCo0Zsv9jjyB2$}Y8kiK@s7n!lYaw<-0KKWb2{bhlyGrb%G)fgA6AhCo)`q~pkud~D z+Hx`II%xT$Ym`shwJ*D^{_~XiOU=Ux<7c=Jvseq#&A|P<`3e=6&6nERaaT!%#e(A? zhC>F2&`F3r5Ms@wLESM9`;NjGZaLX2-|_Ims5E@yq5uT84g&d*waTT4>g zdr69E08lke=733hF~dcp$OB0v`n`x?v?0U}!JP%A8)QasQxUT(bDo#}rPCjYDLFKt zZpZ1v<2TKnRxrq#pH7Y3?AVZSu6TpmJa=|p$a|!W34u%@ly+AZLbcTtLmq{j5x5L* zI`4^9=2vO4*t6-{wu4*#m9$~{nuixG)_gQ^d^D)?9U(QqHwj@uWnq)UiW}+$kPs%@ zjqr|P0-J~i4sRI!5CJ01D|KCcr@-0hme$o<`gbcg{ph-CIeJ+0l4M2@^2RtT#0LW+ zOZ~hF6_Hp_|@GMwAn!ijTsfG7HU(RA(Iuezk7_f86AkKUR3G-omBc|PNCT9{ArBVI z5@^9RA`3!nYJt8a6H1yVpoU~T_bS&}r(vHngtxa;O}+kCE5|RTeXTjkR?6v9Za$KR zbgKvzmeZ@@3;_TI$M#YxLxSXR*J_;(VB8>l)`W~tA9bvem?7wUeC+m{4=Xgu-rbsm zCX5Gxc_cLq9P~iB^I^piS;3Bm6MKX|iOkJ z^sJlFK6t0D-?Y-mls6-KHd+vv7S&6u_=uY2#&Hmi1grQz#(A9 zy2k53&BGQ#a!cDBoYFvenx=~VfDdK1!^o5oV=)Xx5yhg15~K%kfX59YIi5%`^TD

    TXlOwM4~0&gY>vBRyK6>C$%J&B2YNoqXmd=MPhX9ysytoCX+rXNHE zCnO*}K}e+FCZbRhjT45~B|lW7_(XSt*bXCnNc;tvihbcUgBne{HY$Qw_%*j&hY!a@ zmN;^JLt!UihwwR^0HLJQiW2P4kye9?3~TFA)$#&Wz~TYBp-Y)I0jQjGK@$rAH=9u{2e;q;shAm9y8rik4mSIGhbL1VFpP$Ryvf!aRC6t3Q&PTI6c&=6|7_Y z8a|Qaj6&SX{fQJtW<8?aM2&bYE3bUgkv8YZA(10rUpg700c&04N0Gb!o8(i;3NplJ(h`W_js+r?1 zp$x#f)9MH~47n2o4f=BR1)iC&C?oM|GBx@o-KK>|97q>CRCfscl-{MPDnn)dz|kNV zP9U&6aH!%pL@Q6fCMifu8^#$N3^4B#O9iCa30%|Eecd zHo_liDSQizN9Br2wu%uem|HxTvMqch+4I90GKM_lY_)AHO3o2DO=zlcJ-7q2IO&pl zLhM7vmzG)ksc=V#bOSWf5^22q1Q58qULE$FY%~!Z4cy~|w&DIGR9!k0V>1)xg67D? zT-BT!*d-W0{0{PF_;=XH@JURER8I>LLbj5YFlIK2P80l>Ah;8h7m6W+O8L}qUuH(A zNz@{Ua1cN}z^nQN8F!a}29RGrb7*)JnUQc<5O&e%ydZZ2$>Uu-+Ac=HBPiJgP$EbUZFQ5&<5JI@4BxF-qVLjV$riF~gWBPC7wP70w7~61cLB z(@hz%*K?riCU6ZfMIR_44z$8YVKC_K7iN)+$G3rmPwXpougsheHJZZD0j&{XMAsjI z%tZ(cfj%L)y~WW*iXPyFYNs9;jx-f!M$T=M`a1{KviH&70odWM@%&dW|XsuNyd+{^JgS3`gJ!#nU<;JoO)6#q|dTNFd1xO zqRfHNMJE&fx|ppn5VDzm7E!1oC*{?WDDak&lh;ANUJ+|_;& zc`s_GFqRn#BLK4`T!|>~Gw3nIoa5$3!itVN2DMcWz^w&B4muvG==mEX-f?PPJuFWr zToY`*Fo05+C^kM};ba0MP~igF7nxzkOC>YN_6@ek4|!A5IDM~)`$iam{0?4#>RAdAE_~kF@UR-#)hpR7K;Vj6s$H7RXF(x z_XWpJ4Jul>3M=(D$D0b~(_V*+2N^&P<^)^gje-~-Z?5x?Y}!$;5ym-9#ias50Rw=E zH6!Sa3^AoY!Hj22ql_x_28&P{RgOaoaH(JgdqTKrOtDepYDh;VL72T`EZ(74p9hB* zN|$9AZeDVwn74xa3B*0hkWwtBvxK&@jLCo*P)uvYuk4RT_Aw^~ zJ`N9{XI2|oC1w;$S5-sF=`mjALFf?rpN0?rGe6Y(gpOQP5Qh|kafwNYl_D^eyVHja zfn^wA7`WtgdV*rGH6^zW+Zz*QOfIQ+vy4jNI8!sJToz}IQr!3|tR69A7;sfX!7t-l z1KbR3FBm#D+112&x%C~buvv+9aqkE_mhnhUKE_Q#-UOv6q{PGm^>X0kaM4INdf??> zU7tuM%}5{sY8@poloYuD7s?$|zKQyEq6(f_1tKcU* zJIo{CQN-LbE}k?NdfITjNbs2{ji#1=G5OJUk|j+fShH_TJRX2ySwLc3<3yN^aE3+V znSE#_#|-}lUXrQtDMTd2^-Tz>7em=fmQj`F>02NM+$1>RB2rC~sj#(FHKFVQ-<-k* zHP!B(W~n%WBet{BsO8FPDI!xPNJ!9dsz_E9dyNt;tgx*JKc?y}-R$=mgH45;?U&n+ zwU4mxU^;4F%|5SJm}#o#OnWE0=XPgZ+uCjQd~7%06z68P8{)a%F3_&IU0J)VP{}Lg zW#`?~yPoTE*S)6N-bKB=y!(5-a~S1y)oYK}a!<2o1J4qk={-JyO|ai%rDuPS2_8`% zojhuI6fkx3aC3j{e!;7fLx}4b_Z{ww+=sh{xd*$qbT99o&F!bdQr8b|$K2MrrE@Fp zHq&i@Yh~Bmt`06wT~51fa+%|L&1H~_pGzaR1}-IC(mQ{0zTtepd8PAs=f2L>(68^l zb6#gBrx#A=oVGg6cN*#x?0Leeg;QClERH`MA2@e#Om$rAINdScv4>+_$0ClN4sRVU zJM4C7<51Bdhl9!fvHeN6hhDk7+IfwI*5NVKMa=wip9B<=hkUu8>(oX|`MdpB7dROE z#QXHB^QJO6`J>_YK2Mug??cOvXPuWO^zO36r^s7V#rOQt+@$S4Zns+0W>60w569sl zHyf_rWPBLK9}OxQdncoJP2bojbG|hUn%V#6(YwZ5R_;oUd)4lrK5s_79OzT)==T>5 ztph5YGd`9#kvK*2VdufWXWBIGPy62s{iwd~j{|uMCi{%z0b|QvZLs5Ro50RjJO)<1 zbTrbt=VIg5DgNki{n3M_Zf??lSB{D;N|#!*u}k#=#<_U@Xvn8iO(s_>`{&X|yUN_z zG_2{-^X-krd-$h@Z2RyePxTpFeqLT+o|5;Nqua0t&Hm(12Jd^lsO`lL-@mu2*YjhK z#^F7j`xN1JWyyml-+SOw()-)wl*30iSDcxxZ0T3o=kj;{yxMDu=jaFL(if=uwL?_r zIawx-tG-lb;>6WysYMDuS?t)L^X>gFm)2d>%b0zJKN|ROcS4~nbz)aE?)|Jr)ByAE zbF&(wS*IyE;hA@t%I!Gh_abUPlVu4M|`6X0}F;f{GjK7sy9(RRb@d_-S@|Y`-}+-tWk$-qVe- zZv0W~-7?lS>Cf(}Tyu1xb@#Hi_-9uaV>t^hCCB!(q(9%Xuj}3;?K}7NSse8>*|Gmn ze%+Y2ue|R}wUoV(Ysf4kl7Zu-`{JC|}^N!}%GrE4hkkjts0#j?2en}pmFLT54m#g2LwdPg*y%k1% zy)`hqsntCGR#LCwtNSmQTybOHM{UAaZ%DSQ=xu6gMtN|NQO&pUl;F)yr#l z$yBNge{`;M)1%g9&hg!!$L9|B%J8mRovFt3nf%e&ReyH8{jTPf*=Ic#&-jq7QlWbh z#`>51(V5FvEANV*o%!v$(Ba#+jxfz#H`bK@jeIoyfbYF3M~`gXo0=|{XX{g28XPtK zzM9|oOw)9kCSJU8b41;CeI6~Ef5+is!!XvOOg?>MW!(o0o^70Z=taoQD;G`GEI*7% ztP+@fx?_q{mG669ukK1d(Y(<2Z7*IO$lZX4I~ku8(%JLnvj@}e*Uc9FvRl(`C$h2b zQ1bDlLv81m^Ij3WalZ9N_rCAH)NwSHa4+cOV-Z)r9NDuu$;W&#e}M(zlb+ozYs`7g zPo8?aLk_1G6@mj&o)lhJ%WQkCZ2iK<99BzA zP8pDRy3gfv&r^DL>0fksiIqRhLVCKxyHHQT@*wyOwt34t1>*bTu z9zF85Z`|{JivhkhQ~jG3oLRmOE4U>e{+MTOr?c&{9z4-z@ra+fM%KC;W_;?;uXK2H zug4eKbvfC<{YjCU8MD{gcDKCox*3mmXuym@D{kg)wYi-2uj1iG`njVV9a-Ee`Luue zn;++m*zkSEKZBj_UA{cM##vKk78*@Hxc>8=)de>7>7Vu7o0pCDZyP@_k13OEiGAlB z{_Hp+L$Jq}=iZK8Cr3OR(ciekl2gfhhqo&;^ZxWNtIKbQ+cYCQvDM3|#>^4?6nm~N zZrOXeXPdbzzklp?q*}W%dsD(#BsF+I|S)zghM7Os+S{W0-#yK0B#FUlK#t?h}MYhP{iF{a5b+u>MY>&aug z_coo;Gh3zG8@}JzbJ)0_pTD&|ChX=vhxT6ITdTLwexamr(u@QDz0=Z9Q^7dR!hR$gFJ^O`^uZwKCux3X86P9T4@BS)1@4@dPrx$R|z zHD!I>C%F|3Hr39{A0>S&+W+X?U71#{i78RBRFwsjd+#%Ob>xrM?a7w^;O#Q0Z>o%6 z-00EHLA}j0OztdlmAwAh=G>!V2NjKS%-wnGABC5NtvFzExhB7L)?7OI?}MMuG%Jy< z#EUB_oAN}M>U#1=gSvciPR#SJ;LKKKD;GPMZ~TD+Uybk2WeT&l-!|1{%h)cb?&kgU zaz*uzxrf(%$)9YPS9f0Ss39{mzv#YdP~PgbCVWpH6Uv{g-8ArR`8~M``Daa9zT?@p z`X5tc+p^+V^6Ck%ds%;cyX3#>#k|S~_T7u#WA53@@XGa{^=?j1Dtf!?;WmXkthgK9 zxpwr7Jg1jbGNzp82Ty$XYUV!$8-2^;_U`J&LFv|xuk*JtTnvfzWYXd;_4BR1yzhD9 z!|D^W?JJdB%8ws!)x)X#v&R(A{_TDCUE``4zSA!L*--qPRRas}@Ob>}dug{`gB_dx z={mFHeq$+jhf7`&nE!jm-*;zvn$n`z-v{?TAAiZqnA(*eV)413<8D9LovrPgxK8_0 ze!mxza?BW&!aucm_1sQV3vM|1==qVLIoUUq>*QW9V?+MZyb=u;^(bihYs34ug)4cS zu-aFzYn+h-H!ttHE0=~wgk{aNJ>TO$b|2`yq?56Xo3-h7xfGk2~XMVnpNADyYbeTKq`@=57zC4GN%@wnNjVDSZE%bl(@ zT@}fiy2&#?j;%N-q(W!=Zh!suH2HI<2{o!1)4K4NX1u!n=}7dE5hd$pxKQojg1|iQ zOEeYVZN{~sod>qceYwxt2LHI^TbAwNy(T4&%Xb!@4&2scXS$$ng)e1T(KTJ=qWiM2 zAZGH6(%q74tXSY)>4azd_8Hqw7}2hlF+=R%G|%Vhp62hm%v^2pQGfrHM_orwF*XPB z6iuD9q2Z(fQ@U>75kKtNp>$h<{=Q{wxWhj+wZo%7o*tZ<`^SY7_a6*zde7}sV2@Yw zNvr!kY9?0Np4*|rq+%a;o&IveyKE5|uJZETrKcp6IyipT$1)91T+g~BtFiSwe`#|2 z7Hj+c{i45T-m(>Zesiqo{Gny|JO0v){eM@eT&2~qYQqbC z9V)IddS`UV{C3E+!#5iiFx6%CDjSdQdU>_`PY=%gd{_IA{#jww%M_QJJ(tDqD3-p3@#348 z*VzB`TXCH?olQ5h!q$o@O{dN(^Yg|0$D53kdA+@7{y%+7rm)2GRUH!QcK*0#YJ2Zi zy+ixu9_MMt&p!UnZw-6LSB+~Dx1f^Sqm*pvYo!=gR-Q~Af4gAC^dH(?+43P*)-U(A zE;NnE-E1^}Y3%5fX?@!~3(xv``?gYZ^VI9Szq65igFhNQuSLGMsckykOpe>z;lzy7 zsnuE=hoAFDBTGzOvEDA6t>z_Aq$7rtD0 zb>OoxpGBgRhc!y?ux)g%Wq%hfS0=FDgDMBNb*j|LFyuKJ4_?2n3nmhjEnC#fi(cNK( zLod@lQ=rM)*kVdAzs6WG0BW#Jsc?dMYsgHaJAvrBG27>0eKeuMw}pa0`Cz;LqyuT_;NAX-xWQ3_pEMK zg2xGt8<9DHQ>ywN(x;&31NaDRk{bTVkg66C03N5QS&Pb4sU%~8wiy5wi#QJIg^B;v z8%@gyJ_XQ-QBi)O@>oiRMUpPSLHT=1_`z5b&RF2T!xEic=YgPRgn3{EArYrgdQO0m z(H#zoXkaLYp)3M9Qk*ShcZh6d$}ohJpn6DVNvRq)VT(w27_L$gwI5_V`_L*$-Vloi z3I(D5fN|9ZslD;noyASE6ivpK1IFSJ4;yce#RGE`U?~XFk|5GOjhH+i5Da=55&TJJ z%ve^b6^Rn6JX;!4)Sq*?yk?_hi3>TTI`gQCF(rWTZV8W0d4>>Hb9q_jJ zHLiUJV-nBA#exdD7bP6#5($BVT3}J`izI;tjc3?f4accPOg(UTR@`Eql4R$Cg>c2p ztO!(M(g0ju3gHB1t0>tXS=1xa!(c}c<}3#fn+a{+Kyjh^7^E#xxrN$NrPn8A zEL^tCwAhdeYJ;;zh|c~ZRY+MDolx_qG8Wbr4KU*kVW2?a(j!uxsc4)EE7rKx=+_A( zIZ-8B#^PeEF$iWJw}QfhL9_*FEB(Sc1E!=_k=TyteCO8*6fT2QZi0I+Oeh=-;;^FN z1gSDQ6;#}zlND04iVAZob*I)o#__l6hU9lfH4L(v%)jIe3$s8r$b{7F{h0%%Lxi5dIW`NI0t&&IVxWh}@wQ6(R_#ACsysMGH(7Sd=gK z>&mcbDeKqa(#AQ~7dB3$za+ZwOJt5filYM3b zl2x1y_a~DM+BcbOBuavj(A8xF*sWxRvAvP1R>h{sF=4|g{icRve?8Fw@EtG;(oski zrEs$q3EyuJ*He-|MEE#5%0V@&^>^=8y6fm4TVC{58|)G_-+YXE78nvpRh zU-g~TDMCHDQYxyKQ{~vwjZSl~A(;dNBy1SWC^=VhNvc9+r86SgIus7l78Zsa9wip$ zl2c5^1wSj60V2AcH}I!_**# zGwb>ulDh{5M9i7d5@Cw(r79Bjpw#S)Z+O&1B2O)Ehiq(|uHQ2bcygNBHaJyv;u=6= zk_v$}!wRQ^=*Rv<&*34-wkQQkTtK3c!Ff9}gr;)2)m{>nYqzF($_S7Q@CKYP)fGQ-JXrZdX#HSyV#nb&CPh7+ z7nZ(c%>OXuO;v|X`QOYARqYXpR7sT^up&4GqMgc*g@h?gHC`Rc8DX^da6O>hn~Ge8 zNm#sP8vh1tIv70KzUB~c7NG9L)Cz5|ETPi0R0unI*Z`6v!Yxgec~bLcEHE=wN3Rpk z4s|%=D_px|!uEmK0C_vSk8nal$rv=Y%@lr8tdd?* zybAC=SaC@TEc~(iIkeGws8SL_|@5fnj0fZe0YH0*FT4Nl9_Y%SPqIF*X14K5ybFVjENDw6-H>&UQJ z=zt2ArRJ32C}ZAOd$r%@G<(2SC!^?YepI}})j&kZ!x&07kV+)hOB#d&&Y}V;+)?`pG%=F!V={tqrEi*D3xg$<&8$xdA0Ev^sZ5Er5)dLKV1AVl zyRE85-6@(At#sYnxk~;24#Rt@cPXzFukN1TJm+|p@i^uY!NkcFwPyhdF0)TIbZr@s{I2$4m}e9GcnRw@j2-{u@#ufc2tBO< z)LoigN%miatbyspjRmtgec~4Jn)~yg=OtM zJ_NQs3zi4&5{^(EPucfx@?qlax9bXYsr@-~y9N7qRPSo_hcrt$mIh-#Pn<*4xL`OY z57YOlm=>X8KZH!!MZEo3(1?0F(oQ6k^V{X_Bg?xV>9uC(D&x_X?<4xZ_eCpQKdOV$ z;f!X&Q+$h(0CL2n+=hZ7Eq0~#gCVNG?UluMMf-EUiY)Q7CKd45GI@5}na`GwKQynV z*YniKSbwW8*Td;ph((u$B}0B2#1mkNG5HDf=!p8lsPD5clcq&%S)`EQXTVP+-eicS zTF0LSnw?&iv(=00xx1F=Q{na27b|?NW-9RE$Vm$JRL@&=pOOxQ_Lfu!b)^nKFUWNr za3An+fjDH;h1bKs!}p^fPIO=V_T`_o{o{u0%-*nMPirsQT|~0O#Rnk+k~id%I$YDL z;7T26G+pMUjp1$&93KnYQK*PG!(lbUa@1{qHoHr0>%f#}BQJ*ya(7%7VC_jIJ|N{H zFw`m*+Q;SSTa{fw1OPWl3~&(gq8Vx7S~8`uC(<^EIS|@eX4Z)sHLcA0Po=(|nCLmB z*=rZKdHVvaJ?Mut9Zzw?u{CMSs=@+93xu_B{G<8F+`5<}GAYYer2{aKcnZ(qnZJ#& zE?<1L%Cb`~wbn)7J-g^ypcO)`=p~K_8RvqoUHmC?iWQqbjJyj)@K=?%%oB!Q1KDqp zB%?o|dr5P^KfVs`X;c~BqFuiHen)ejXue>^(!lOk_=qURkFyXb9!DG=Ezv}A$)UD| z%deQLEo^eCyAmf)ig3&!a+e3i!&UaTe4ae4*X>WfiG|+$aXV*?Q}Z(&?CNjD6@fmr zh*B^|TD}Rh&tmu3x-cm?i#YKD3UJ>DGt4*@BLQ>a+GeRk~IjBC3B8*oI5or4v2LB9P2PKy;fq ze$P5LyZN}z^W9_LC;yTE`~c7BKr6g-v<;W?H>D3_34@(Ln9IaK>>7HH5YgI2jRY2g zNwruF3Ojgxw2~%n6;dK-)#bvlH7l+;q#v33Pv7H-w|utc-D|PpiYi==$>s_{6t2BO zFG$lG!UY1z3WK>2?i7dJLdIu_Fif}@HF;ZyH^(~?mV|B?v8WnB#RYJ zLE&<6`)Ju&j)f`z@GMF7Km?;uf=MJn8%>;q$&@e}z#ddY5xgw=dQ_m2^TMc`)o(S5 zuR6N#(yGbv3I0Q# z89*)~_c$)Am+kH$=N0z#Mu*oMl<(1j<28b;@EJ;$V{KV%F@Tg*O2u-YnuIT>2ung} zfox@A3?iT)N^m3>iD>yF5p3vw=1A@8D{C})v}~v)>ol($KRy=o_Ol|e$X^a*TNH(= z!-M}o>`!gVxqw$ome^cj_NSEJjuK*!OqHX>k=gtD>+bYha;f>@8(9`R_1^OG?rYaE zJ+1Iuv!jbRol2J54vvJ$B&P(ghL8!tt#Z;aZMcQtvtYGw?(q;>07n|OfF*l+t+{J( zOFe2#s-byjTNxJ5=ppcNPd_BGs592s1NRH356aaL24iZ;b9)|Y{(K;dE`p*0=L zr~ragGIPJ}e{DkI%nL&oyzZYex`5~N7W>Qfw!*LXDJOEJHg8H92ChW12^A_m6-dRv zxuf@t1YW6W{ZfJ?r6Fh+0egF-O}gu@>z&KLx?FO$`;6~yx7u6^wjz)$T~32eN>db^ za}8<9d+k_~z37~$-;Jk- z4pwvQVFf&ywVbV-i+V(}?idaWHSSRODgCB`1SE6=ric9^?G_}q0*HO-_0#op?;be* zTUFDIQ!Q3a-xZbI%ijvP4Mq4dq>qLg7HGu9U>Cq#Nue!HA`owhv$XuJ_H1!?P!Pfu z9}vsHmg8E#l&mu*=X-fPwen1#h^xKU4a$Cc;EP^XR08EhOK}D&Ah$xs1?*unO3?6M zrGQ^tLl%$=4nw3(Ko!6w!U#C@6geLN7EvqvzK;Bm)OpN-YQF_JPpTC@&f|EkAS-~) zlI8v_2~d3bV1it6Yf6b28)Z=)ggD*5_ya&Ef_z2G7>`Mnj;vL?p-#4YP2TK0c`@^} z--Z_X>(Iy~SHIp?l==9SQ!2|S%bkw>I@jB%wZT>h0+ucpM+=0YojN~|OfkSn8k3Ya z==Es*L;@~07YWv?76R5e}wzJaM5 zPfR<0+sn_|h-@2U6FSOPXuRU9OU%vsNy@K_HACu(_6WT%+fV}TP-y{Q$6_&--75cb zg5V$h8Y0oI1(OhIE1dZc7@;12_Z!h|EcLR55xCULj`6|4~7%i4hVwbU3V+_K^=w zUdvYYon*Pt?fB2(kNvH6$=K+#ri>+4iAvQZfx)mh;ZrIDWlLNsM9Z4$B3mpPv(Sa_-WQSpr4 zJiIWX>Z^Pn&Y`0g9;IG5L0V}+RF1Mtzk4Xxl&;2~fh*kqNch;HUC@jNl>aYoI%s%r z@a_uz|Mgzoy_`MQdv^7-_gL#eEd4F+J>A_+2i?}Yb%PGzX4g)xb}k!SIzahB*;Eup03`9bjIcZQ?OC=#!VrsfnvU&Xw=TMP2nG!C!l6QT9jDxLRhL z4+_`g0yc?2wHNGZq{ZY@Mo?GVfYQ(q2@04mk;IieI4;3(5|as!fnFPa2$DpY{t(D} z(=<>^Dgf2ok;F(oDj2zKOjeA?ngG4TY%#w<1XLAT&Q5~pc*(Fz*n<`g^GW_=RJ1AA zHNhJ7u(%ha^JvddBODr<;>@VJo1}`H660d#arcMe<;b%LGv-F79hE&CTSilYMzWFk zCqmCePt>eB=iu((E=)ji@t9m1#osV_3>SD@J*&uZYfTH4)z$x#i*aKJE0Rl8#GW8I zM;;zSIB20{jLyM@b$Uwy62&X9%g1aF4mB7<#TFyfT3PBql1yfONC=)GbwIZyX$BZy zOR;LJ9xqH(q{*w|guS$Af#NSSKhV7czynrT(k5nRzR<{y;DX5Vu8=3n>VMB?MiV%GW@6Sk%rolxRgLIV8ADR9+n$ z%xTv^5}0NgV|cdkAB3NS-3JK_9mFE@22Dl32uWtCrI%^Jafg#}+B5|4?=p!_?<2)h1w6Nb>ZObW>7( zY?zF7mw=v?x9vz3NlMfwntZ?miT48k5<(qN%*172leU&&6)vT50yyW0ZIB(#_e5AV zYBzB<9I*lR4`l7A#{nxH05Su_huun2J9MD{cfcA2!{r2YifO>U2q$huSc$MS=8nt@ zYc&z-f$&a5#&v#K7i}sb1wACrF+hJ%Q?TE$KP>2*GFWoh>M6NPiUz`@8%Z32P;dtk zUSp>TZvdAzr6;t>#xa7i0S865WD$CYDcw{>0J&a2BZ5ElmPg3QV%kI;zHjz*W!p zzA`6ROI%qh3d7ll<;q6g8bGL0ql%7<)S@ES0GENP_*Jp9;umpyNj0JZ6>;YQp@RPz zWQ;GC=G>D@4r|M(eE_7XA^A&Wqu@}^Ye;`$VsBxI6zB%4gt2VxS;(v#KdK4sKs_b= zS}Zxbq0$k+i2nZyHVbsCAV8ax8#VORt(qiLraBoJS104*FKnAt&hDF`@qbF7`g04@kOIKP`3R(Vo2ED0)*MQ04u~5L`?kycJ9W zQ7#2SMu|NeA94TVFQoBN6z;_Sm%tqY)9K#L!1pNNROCxFE+^X=(quxL@%)kSgw&&w zyqpH_@je77=*$o(GLmlN-do_D5h{ggML9Wc6H#}jkTP%(FuH=a2@Qm|hn`VP(UJ}u zIQW#*4};kULxV+rRZVuSICPxD1#je_A^y( zmgba-_@Zi^BHfJS4%FD4jE%^W=E7?k&c#*urS2f#n`x{4=lob=!AfU zLgE9k25~^8ab}qTX;ur+1@(iVgd5O*9Cc2rVXCn?p_$2Oxzc0_kO47UXrzVE1@4ie zvn`#N=1FaUEUr?vXar-6Vz)4JaeyiPt2;VMD}`T~rmP0(ekRS?7YK!?j+;^7eo26@ z!dI5wLfX%kW2}!_5Oh?cAb5^bG4NiRv#&)s*;!KUFO>9`&it@Sq&{Vovw|6AqYxDz zi6BEnb8{79oi}Z!Mc;}&cf##(6vZSm-bzmcWdf)yU@)Lh?6c~=b<{M;m|RKn|HBOL zIo=h#P9p#R)pL?(5s&R2t=ylu4|C7zw$`nY>s{9bS1*@jmpaZ@ox`0yo&IvF>3H3- zucNEOVuwHMkK6m(eYcx#SIo4-)WUdf4EyhlXDx{yl+w{EV60gxnr+}dGs%RZRoUz8 z+-PYhaWFA6yjMwzidfWSe=`p%@nBxfQfDgFANX{}i1b^d9-LX!E76@0HQ^D1*MaPP zAE11S>B#^MJO#4%U~ofwS;TZ;U5Q#PKD~6*0dfQnFD0(a`zxC&uUNO_^ZL}_ll8s7 z9qAUF=thVtZAH2RDNw^HV+Nq6J}I4L-Qi6)(nEHZ2t$E{M@JA{jdKWymbtgj@bfRK z3_5#k+uv)?X6h6>t^2gy&6|fNx}ry2InZgbIn<3zDK^p_R>ChDR5epmNSIn%t6kN{ zAjX$4MGX>3muJMs!C{RTPj%^I8Lf zR%Bj4qBCQx+@eGdz9!X+B$2|0@wQkk=t2=o4UrBvEw;hcl-tF5pkE|C_Ab%KkEW~C zcFyu!wNnbca>y`tRkZ>BiB5zXB7q-F%IZ3C2NZ&k3^?-4NVIt=?8XuQErcmH0!-To zq!Od=UU_{|O!XX5_u{?gyO)g|75-#O3;#q%`WcmhgtjOF6SxqFfiIq#iohAX^KZJv8ZC~>wq|GrV({>M@8Cw7*%Au`F!K6 zCG$0u)h||Ku`=Dq=cqYn0RgN2`0yQ8SBIoWqT(L4KU#fRjkH>G1 z*&7M>%FpYRy>qi0mP8X-A?8rwq9=h*)$>8~zV!XBn%$@q07fyUkOOj+v}GusqnN6F zd#ov@`=Y5YR*gvTs#SDSV4}gPOS!OAp9ju5=;8p)1<@C}#*wfc{lH%$Jtb63 ziR>QrWF)ztpoE+YEA^OvvS~HfpLqvN9$#hW?owv!p9EIx4ys%SAOSk*&x*-~FtEDCvSK z7-YFHp33Km^8u1EHc1@dJWA+O@s{)=W~M4Jg8V_3u!U-Bx}zI;+}`jk&(w!C^L;LL zdGzYM6;gLwtOIFn6bPdA_2D{De>15ZP^X>A_LRip#TL>GWi8;cD1sDTkU_L9rHVA} z|6+6NYwdl{$G0nV$D?McKYLmSP?8EBinvY85QmS0?WMH4lz%HKM%(~HV1BN^z*PaZ zCwuRt3%Yxo{e%JyN_{RktM;dbn+N}O;YG1df!6-C`k)q~&_{6=bSHNmZ8ACq|CgJZ z34KyU!)U(&M;=rVcsa<%l3&F4*uss61DcFWJ$Az~>S)8?_fp)RHI~ze1GZ;n1DQuZ;Txr#=S80N0-gu~Kj!9=FzSL0qX_WA@Z})!r^^y@BT* ztj>JN-x@Opf!p%o3w1P8Ag$YR#{tE6l5N-Ub@#L zb{8NCoz9ltTGV}uiPzwU_yy&BHsxB64*_W$s(NXQ?A@f!0X+CBfxmgMmM( z{0Db*CDe-PS^^^AfML~e93?jZzX5R~#Wj=%Q2`lKVcIRfHRisn_r!xuRy|I+{<(L$ z%IBhet$m5l$h{Q;!Vz4ul|fKZe6Dj4wOHcw)N8m!_RLYdnhLTg!&GU81(P^>W z>?(DufB)FK?!e&>-Dg^?5oAJ@L064yRJv3^2Wd8=zye^y0ML-R?nBUzt?-d^%c17p zxX^KEukE;#dMGZ*vq{{lxBZ^Ywd?lOzj}Z*oXn2w9>s`*i4Ve2M@xDQDNq9x@{oyf z0T_<6K`Qpc-OC177@_jKUfZ3fR1b?UKl|JMmHql0NT?gS)+fXo29U9w5JPY>2rE*D zQ864fNdhN~yijQ-qc;h_H2@+AmzV>HXJMxdAY@pA#a#Nr?8}d~WG}Ng|FT!jcNROg zyH+EAYbfn+WM&9R&_1W|v-9|=MybTbz-s_d1H*|zcd7bQn!I#;glK>T_mDYcWU0b6{dls+g#cC{avWFH-#ul5mXBq z3iTGI5l%N&zy|R7g6%^26Nna5Mva(=p~&=v!i7^xuR4sU-kEN1_Yb(^^G92+fs;M# z2aHa6cFrw*fHjylpW+VK7%&Juz>1Lrs`U17p~9HqbzqaI0)o_rkIM9Rqz@wQjwuHx&8>Rd#qhTV(uPBB1b3b8nL-9sDH#HDqOxIJezrSR znRjj)hUtY0E$lZSTvaq5y=3l~X{+Mf+}N?9OKgyH#dZZ-_bX2M{}QH}hN-6aGVhjN zU%VE2HTHbtInA@2OBs(l&ci$=o5pz5bbsYO)4h({Bh>y^biU?#!*!%HkhF;QzEg2dU{ddwp7&r-j>7 z+6SO`9!D03l+&P8=^cd78i@|++d_UfgN-EdXlf;`jrB-EPE+NX*sCPODToD<1#~+y zoZrV5+8}7h$)Qx9>J35pB~!Dd4C!lqa%m8*GCZ7r5F#_>0svJoA)X6X#A?A>;X>1P zk|H8gJs(CN^?-O^I=_;4u^nSoGK9{0pcD$s7(!~Bf)sOEgzaV~im`F(H#Lhfa7?KT z45ci9(8$+74vPveMXD5_DY~`VFI*E^_gvKcfZ%o|WqF7;hHpwyz(@P_I+%%V68|g65kfCNwD{W(3 zpxh2ufK6lP&a|Vl9Yf)^Y^aMZN=ukdMw46dqu^~9S>o@APVepV@7 zRdFJDMA&--57Ax6MqcgF4u97A@VX#WJsN$*hwm&M`%agSkc0$J#RDhf@md6CR2 zYp2JRVnd}WD`|h5hpX!YZGpCEY75zfMCE})Q>q)lNCZ%|ifx(9@bAcUuoPcY^24fK z0Ne;-$RSFi7nZmPJPYK~0G;Vcm#PUEGaPs02hf?2Z6~zOdAhU@pmVQcsfa1gP(Sv!TOgfwgf|OyH0$X#QK#!(P zDM<++Ur1+_=%lKU4?r63-T*khG_`6U_?mr@^`tbCE^mqSQ+rlCKBTQ6=S>Mfa7SZc z=7?|r*OpV|oXbKQAKggs7QQiz>;WTIFeQ3Q=RueQdmL}7Lg}Y}4VP+|MHTcokNiDf zkdX10a99!U!U0n8nTiQGu@2LYBP>i9is?)~*V8;Kw@=ZPV#rU>M&vZ0ib*8#9uGhf z=S4yzA{E0qh7TDIaxcJew9+vZ{>Ygk+q{Z&hZ38XvN19wlE+rcEs8?KwWu1wRvZr? zm=VT9t0bwvbcm2Kf!*xXx~5_aX~QdRj#+ox0&Zk)@FwJL7216+x&9AKRdL^KF5 z6|Q$EEpL-sni3QIl0yh%V|ghbR*yb z;FD18mu^YMTR_QG3nyGNQ1+&R-cb492Ubif?y;tet8{@49!0eN5hG#cgJ9P&V_c4m zFhhm=LUcf61Y=ayoaVma;1MJ;>}`dwY9No$Lq+wIR_rn;{1_4#K{7>ITVr)p8UsU) z2@b39Ol(9eWYm~qgjf+>vzZ=)fQ=#>Td9av*~fa4qeZ94n3pq+Nuazrvxi~bfS>9_ z6=Gka`r%?h>yLjCX`W$3lmx!RSXfx@Bg#_L9rsOX+w^LiDwTqOx)8PgOIU|#;vO3| zv%onmb$ue980 zf&uf|9uz4JY(zPwD%nA!a%&@SQCPnKu8VCx>6V3igKG=dCD*z-^kiC>%?-v_08)rL zjYMu7n4O$Y<4$WT$P+q`GKE4reysy3$|<`=K{r5^w+L7^XE#!C6e zWZy)&GBP2eavw!@TcQA*R)nl7i=xu2I4*b*P&aV@5|vO=q0$jcrCyC?6PfIz7fwnr zg%-i&V4L;f+$xft5ZoY>W2-;*4-VILMnK=;ubV?m85c7tPYM1w~3`i3C zs+prI+pj%yo4=rmdC^2HMv0DJkjEKia%CP62yg=dT@$D1Y`9nvaS-VcwgHcUL?W&< zSRy<&2xl-&m|46Lq8n!M4!W0Sy9xaex3&Bk?hstJl(fKCxv*4s5vNd{hg6(Q$(1tN zZ2bM6sbscEV3j9iD%qeY@|7R1cWNo978~2dkD7cbi5nGgsvLri07#_QTMH%rA8mLi zga7a3waulZSD;rG=dqrt&KEsnJ#%@S^cd(--2JBeDEA6(m)!=t6?eVhI>NPt%SD&L zQ2l%CG|j1o<8#Nkjx`)!J4|z^X8*)~ifOcc1-sjJW9=%M?){$@(EtDSyi99@GlXjs zOnqYQ+JvlN1d+lm9|UHJ0xM=!h-o=egxcaLWS6! zxZ;)eiAhd}#o=IRiC>4?J}ipp;HT!FgfPQZv6xFP$Jc6=inIfB8)3<@oSAs~?L$%P0Kq7F z673>|(>@>%7-Ip~V|r;$k|hN3C=f;2lwO+ABX988LTiu4Nhho+8#YJ?q#Erh)b^CtV0r$B&mlR@QH z{~9J32GKTSu{1m}h5rE9shA_G4{bwCTO4vWG=tbgU|dtfKGnAt19~+U950KzgMw~7F7_>GuOxs-X&ol(8?PeMSS8MzgMPzG zoj7j9y=H2tGD1%0Sz1`AWA*xj*YyfD$SQ$(s?gyN9 z{R?0av+}3_>oTe>&|r=((cO$2Intb=5Oi}SR|Oz44mWB9>AbO05gwV2yEMcL3ik!z zrO!Ls))JM5>dYuU4z{?pNc*f7R@CC`qUII{2NJiK^8r8sP(iG3W9=0PcZwwwiNFRA z$Vfec5UslH4$(v^X^w?i!{!94qd|FKsx&bY&?u-8PYgSa3s=x5m6}xGTE|Ts_UfIW z64#m$Oxs8ik3hQ>kxC7+V_293V9(ev8oP#VsNaLZx_<{0tVBPz{!bz;;OIhoPeF{T z8>I~tCPYL!Izq|S*ct%?mEATAL1chgpo9tPv^{C3gCIosB*O|6SYh8vK^lMqVlNPX zl9D!MT^LW(&nN{)a(=kx0}dGOMJe}$D+*mWcy0pGig1Lj{^q~1_vYa`*I(Q4b?tdH zj~O>1L#8$oLW3bwB_;D7u07Zj_THutibAN288VaW+C?;H$j?+FB~y}!DAAw+N#65Z zYkjWt9`}75_wl^X^T&G}&;7@*J8j?n{eFfuoaD1r=%cIGCL zeMV1%)5O|sZXpaD-xDt<_ur+uuz@%pI4r|`#ripyNfWAL+~m$AC*?p0HdsplvjFDU zSXWv(vzpZ~J_~jbzY++5=a17j8&XNUFscu$fkkrBE`m7iG=jwdLWgsNhodg1GvB;o%Xra)D;9MeZ6j5Vp!*~v~a3%>$Y0?7)@C8`4 zd_vsXO6x>%HIkP16mtyY3gDC=H)>Pt+G;iGrk&9fEDi&qt$;$xIW-X0Fb%bk@;umK zQN0ot7NwItuTFEmm+za<6Q-GV28+}Y@}?o)_>RuB z@oC}_Y-XyXsK~d&YVyCq`QzF+$l3l-AFR@BN~f(`^X6|R!UCWGZVV|KidR;@O`%W` zjHT#9z#veA%but+%yWjo28pzWXJIo0Ms;|d@s`w_b_WfYNu|exu93}ND95D-ZvZFA zHP5k8N*rkp;t8pymfJ`Ee;rS*r})C+&59i?R#>b={8}jfe;YSCu3T&YHUE<_<6_E3 zzZKm(>X)b~Q3;V-BOi;n7%?@XM)>CNhr&*UJrPzi)F0YDsykVx-hZ*$LKJnp~w zKcD{&vXF-0%;75QeELjWUDW89!DTxVx5+tMFbr%^1Rk}5tLmhpk|!=JFDGqV^Nky0 ze)yqst;p0B+fL7@bGg62DfPh^UTaOux|L;d@VkY&WxelYH!9ap%c_~Eq*(=2yjR@p zy~jd!EnfI{=bUj-318fJbN-&xRDTnaGZ-nt=sC+z@E38l1nJKIAg{uHpB-62j zN`~5_5!-Tk-N`8r2ljSZ*LO#s(yx6ubNZ?@|Lvp#1+6lC6o=&sbZi7to(?I+WoM3N zD8t1=gd!dGrsNU4T3TzLzO~GQQ6)NMkNc>?AMKikKh|^IwBBj{2K+_32tt7twOI2x zPG#!ohs&xO-MueBt)IvV@|@>3jaHi`-V+(K!gH^S@72RehnFhO{Y*R~CaiSeyEEZGeAnPmlTSAlY?+qhzl|I;N8X3l zw2Cv~l?CGnVhb8QgKCAiMK~O&JPR+`dQ^=UOH7CCQi>>q^jJ8dk+1bPJx;VJ-+%Iv zrd2mR{(Ppt4k^SYmR@}@**>m@YUz(j0gG@w2tuq#TM~!30jd>@kGSfNUM7W~!B6*l z@bU+3U+S7%apky6lO7+nV$xgCEF^Q(1Z;vKJ~+#Of^-}JxEOp>(1gKWCIvt!FZNqi zE*{==NN-mF-IT{I$epTh8vlbvNxEHNnh$MAKsA z5M}^%aNTgcVEK8eNeH5)omL$qu5j{#ve$Q3o;P#FSI_l*?CSP6wl0W|dv1{bR&wfO z{c)gJqYSEy+SBb^Vt=cz~bCN|dL}E?tOoyq)`+&`b>=+R#>^E{PoJ0ok zq}MO&Qf^^~{VV%c-?D$!tzWM{_`v(o8+`s6Tr^mC5onXi7gkQe-^X9nyaInjA_dBl1bZa% z(q*CMNl_CV0b!0?T#Ef;9zfO3Wea!iXkYpCnOE}W{L-mcLP4X1)nk1A>iiPe0o-V! zD&(~)W}rO*1XAg05T!takt;*@W6D>g^9Li`LV}XWFnn9j9R0BHYFfFOtyg9bdUkDo z!bg3!`25xQd(?^;``ESdC{0sQIhave8+?RRYy@M`F%SR1tsJdQ=6cE1?=PJa{d??h z{@)s(Zh110cjD1Wss5@oqpASvl3=68$SJijOk1si4G28WtBKfAH4)ot5 z$Cp?Djtu^n@Bp0*%?2JpD_{gaxHVKZq@c+$;=JaC$v|?lAYn`h2cDpsXZDp@onwz) zs9bSOrT;vOiUt( zDks%jHSXY#mFxF!m$l%P%w@!x^&m`t!odg`^b)0jyzacxn#)lPhu8+T1IxH?B zDal`stz%BpLPi-mSy&LeXqW|fb@l+DFLs;##uzyclOy&Hu_@nzjkc2yTzT)FSa{!@;_EZ>`}s?UO(>hF zv`b^Q=n`nvpW?P6gKwSbk|ro2lSfh;jg%A>|AN%J}Kyt7e(lCS&P%zty+ zobb-A)1N87;N0K+{3WCV2;KnUd*ytaPMG{w8%aU$YoO@sy^d1>P=)IGlVs}sLm?>*~4zjKQ#)PMi`nkoKbj9gv$!6@JOaS0IElPO4&MJJks z3#m-wI%(PBY3CrGWFI|R-nhvdmYsOab1||@L;qXf&-?OJg=WeAcsaEulnw&}E3cBX zs`;(2Z!Kri=&$Al7o#-) z&u0Js9mPH^Hn^BKenEWGxR2wq;yke{Vq3&~5;G|J@90I*iBTU#`JzH2Uyi&x;z&eJ zL}>V`@HSyzgyms!;F{1j$p5oKLcB}8tvp9P+5dI=pXo>46!Nx7qeQF}kpxB3h;O^% zA#8`K%{!G$kr%)BLyNc)AhBFS2TKE0h5eptW_es0m zgZ!wdquC$1BbEY@3bQ*F?HGRI-=UKloHQutFN_t;k4c;82r(3M@NOblHFVh4#;J!= zKYr!$ww^K5S4=NGDaV)WM?oL~jnGKu5N_j%#%l+XftN$LLInWUe~{O)`&Du%7%><~ z!<+9~fjMD3P;V!M}+l8&Faq_(1VwRRSvg4lBp`k4^mR{`mrEmNEa;102%xIM62L@j^5gZz+ zcW370aO{l|8{v&;l!I}&AcQ%X@`EZlrfubow3QUy6+;Q&cG3!>D*w29O`~!xFgNSRS~{$XLEax8@+p?`zZC<2~F9s_L>Oc^7+=m;YQ z0x_pQ`7(E9iw;GxC9hp;{$0kt+7-Gb`+?~pHB#QSsDVjlp*%90DiXArprW*p&5tC3 z1|zW){8qhZ1ic(WnM-=&+)Jk$^l5Z3<>0o?iS-YzePCbt;v3WbfaK~!j2spfCjh2Q zR|^3Qf(34ul*DYzm`gq3zNtB@iar`J0B6)eixBq9IPlH;7k2IXv;X)$^)B7+pHa_O zV{YAKKOo}-$dR)yjp!Ir9D_Tc@Q71j3@Ll>LkN%jM9rDYJd`2WKc1cx`pX@kzW>Do-*u{*_tL}t`6t19)$`^xf96$I)b9chxMbR9$0AVHWurv*?pK+=2oV3sTe{^~6 zhKVH`XI0%%zpl>@m>jv0+FPl6DPH278tTMYMj|{2iwNeUJckh0682zM*AULcv%wfG ze#t+v^UA23^p*ig+bseq&Z|U{ z0z^<6rLi&0#HIRGc1i6&-t6*cx3AkY{d?N>l`Gc9KAV{C?<%LbaP(+cR>O*HVg^nO ztM)=RbIuX=SJG$F-ZX%MG7eH`<6~f3Npu_VM}vXSx8Gbp@b0v~R{mbG&ysg$^-1?b z-MW6FBIv?1u~BVINw5u!k!f*JV#piW$D5taQ8E+~!Ge~=PxH_i=$XGcnkI-?f=q_wM2lC)#VhQnsKq|Za zIlp>~U+Z6OKXBIUq(!Nh{Bue#?(awIQh9VeL#9nZ1$>oO(jnhPDN`&weu1Q_*c3EG zu}{DV4KiPh(plop-@S14xAlcvYI!Fudn@9JQlGuuGjwFKADY$)Xm^(Ng%^a`GG40G zw%WNI{$k5#Ri{s z9~<>x+j$v&h*>i`N~aB0loXB`kR_fXd^*@-uwk@(04JnnJJGMkk!2JDkTI7_Wo@Cj zWGNYI`plkjWaNdX58iTM#nuy-LqaZ9pY8KQ*1Aq25Fls>W}?JzdQ}Eb0*r+N&_QNK zwXhCYm>F`Ttn!X>O<@j^Id%@+)?mYjlMbEU)M3l+{!5y7T7KvDMmq=k@8!neS`$?v z2SNrnnZh~IGR0iW#Q;OOPZX>X_qzp z%RdJCp=XWgVgvv*iE|J!h&l%fzEZwrBReukwshePApEdi6RF5@Du(?>L9cow6|H{c z!>_`2esbaYv-5XO{vm($nJmAkS<9R*E9qONWtpPrle(&jgF=8e)I|Z&8}R9b*}XQ7RSC6~j=NkZPmy4~(EL0-H7j zM_Gz8wA0dXA#M!}ZxPp~?tty%Ypg%oBw^i&qpjyYo9S=MP}Tsdpi*Wqeg#+|E(^;A zY+DHi+G(8*7=w%kAdRdR>!B2K6wUW9r4CG<{8q)54WD{@W6KR+h3DO1UQ@_?*`PAdflTTEc9Tj&;WN|83H#mqu=>d!pIWJ?AdDugI zwwQA5m58iyq-3+NJgrXT_YVE8Y4+~elsn%VU_P)@i8&!)nAQB@i)`#AIpNjotg>btBS(# zDG)ryH6T02h`Tz@Ku;|)Ch)Xf|6>YHJZdrkXnR+#d}Z8Mi8Yuk!_Zy z`V-|e!;A~z7aohHjXdZ+P-sN?bSiBj=0Dh_0Z=>#&$I-%~dy!tR;RG@Ml@EL6KS9-d@{2*9rrn>=;)p`Q zl6+971`G17xD%yNVStdg!IE<^24^4zC>vZwo#p8wBGAT|v|x}mA#U;-bKxRI%TgXE zqT>=%vn|tee6-N!+>W@~nIs3ni%AVpHNq)*qp0|TGlRg~TKj|=8msE`~(H}4)8`LI- zbD;Ww(>wvIlx^mCQ<2n9Lihmf&S163G#z`F`uE!=4a3DjR1SmzbrgZJVF&Ql(hG*u z4nP3!4{h-wu;)m2XpW?S%f`D(mQ(sakT;nMFjw`4C*uCs4<0E6TwPy1o{7Jp zF2f{>z`df1HqMw4%JhW=+|noqMm;~F1Yq4J|2$RJ5Ke@56UA9XeNY8RN612eHJARNK08l0=%p^)6a}3gUD2I>8;wxs_)t4*;z|I3^BE&K-`Gxc#3;3;F`oMnX(5 z`vFx+zp3@n+uEy!CSF-O-2ig2>f@k62nnY4s}6jpbnvkcG*7_94&K1L8zt6c#-)^sE)b+0SXpKs zEef!rchC%wZS5LlU8jk{s?N|jfZs7!b=C|cZ%ZXG2-}COQ6)*e)vmc4E00#p55^O* zxn<`9H@fTT94d=*8my^6gSv1xQ0-a|=dI+SNQCg!67P!*Lh8sKD&_eCOc%q7f@S_OtEM5`Q2Z>Kk1u*b!eehNlR44fdsfslmq zOl**2j0yt;o_9u)5CM}?hf9x|_5qo2hpH>2zTk|MK2#Agf##(n$IP%DOvgo#E-lqn z1)+2FXqYd_QDNJ?w?@N3*Wn$KCu#?f_HqHvk~*s(A;>b>!Kgae(lLdzDRc?!CCmOs z*kft+;a2~8%gj{W7#pBrqX=aNk@`aL`4H()sfoKH5IQtu1cyP<0VttoN4J~zUHh$! zB!F539Er*^62o^F$-SO|KnB8VeNuFGz0C(Eg-vMh_8m|eavB_W7$esrW}rV_2~kdV z9zDs^29Wx}_~K3yAcTct$mAe)5i*y)MFXAe zy{&oyg99W}ieR_Ty!n=Dg;p3dE=_Pfe(b#hCWH%WDxRuHDiP*r)=xNq$+KkH&9~Hs zFp7ICpht)0fXPfzCcHLzBxNTf@k&DfKI`n zk`_;?MP4i{E&&sr+@hK-v^ z1AKwDB!$#!S1`~)qhcm2wL}nE#ce*aS`IpqCLGY1IB<+Fq&jAt+h|R@$Oib;a^aM> zWqLE7>0Ml)%eNHDH=H74Qi=Idb4DjKTk;ThB6^b9V(b{QCqg8eu1P7M*7@tX(O>KT zgFVF;6;CX-zgTv$nD|xkZR5U-8xdDA_KnysF=t{%#uSfU7TqT5Xw=B4Vv!pnJ4SpF zksc8q{!)1Tu)WCnqe7R3-Wl?FNIv-gYrL&JpL+)XSNVQg00P?}N>fDy>iSGi{@D)^ z{-PDJS#_>y&no8*O+(=sY<)2C$j%`fxnN6)1{03|@brn#Z~WZhl^<7j8;}r43wZd8 z401tM$YsIdfl0DJQxRZbvv`Wa&{#w5iW6N(X)}f$CVvSzE+gqD@_RR5n;SE1`{Cq} z(chi=ZpERY`5Ar)Y-4b<7#G}Vc7CC&hVV=f2vz{tvDbrx>Q$gYZjCmLyC3bZg=&Ahnk2gBh%k)EGyAenL2*&M;5eU$eIH@&M zHE=FfO(ZykkJT7<bFnxkCxM{rvv^DeX=;0B&|tq=~SXr3`lc1R|s_O986jC zZ3wQfh030Xy3alI!i5^EHtt{jXV<%~6`WdHDb+uUIAA)2;}|gD7o}j#Ao3KrN3A1Sqebno)A zjFvEL2)J+hewrSEnjs8dIvMeBI9<5RhOmk54$d8t$S8@I3=3!kb{B$tWU%(sb zMk`40CC$D%P#fz2oWoe;+IoY}#&yJDR)R(z^cV4af-!>yJnPo?FI%WROajTnBu&YN>H{6pvpEV^?k zmT9Qm#D*0F+ci0v%#w{)mwrrq?Qon|_-|kzT1HRibFYzSqb8-0f$$F*R%8$|SHyzhubZ?Cj=;3652IqgT6e7B#WInn zI&SOP_t)-!rTcS9&_L}=ib+5@vp~!S5GnAKmND`6xIwOn!CoRsbkr(xv(lp*N_}@Nb@e01`ulU}1hjt*{TeJ(fZxXe4V@)9y(D!U86@twzA~U8#Nk+_pa3Hf@N! zH>c-)1rO&odo0bLNw)?G6OavHVaTNr{sqV@{Dcw6l_6s$F~4q)>qLZyg>_T8abmVe zxSa|_T;haA+sgNvI_0!CZFk|I=Z4o@{b!~h3fhelHE#nrExEqbLaX}_*zpsvBd|Bw znMiFFTT#@;;WG;NNf4chbw{vHgy+@4k1ShyXjGN16|evCN0Xs5`?uRNDAg}g+V~v7 z@?7&123DZktPp?E=-%k~=iSF8=120$!h=x?9Z*88P8XskQSf=aeJ|Xbx4GSi{TDyd zsKzh9{xqTSA8GzHt{swV7Ao16&ElqDA0hTEcR`RSyLF0AoQUw6EGk~EPA-ZA=<_ExO<9_j+Fu=R`SBqm6CU1CS>%p0xzY0%% zajJb$gCu`4e}}5?3)RapRE18$p-{|`a}l{ImWhogRAR0SX;>Oqif9)AbU@?m`|(Ov zjWxB(FC5$L*jLGOKRNmA{^}`yWcW1`1$t7DYB0#4@Q@smXTXwcx}bMPz$PQIg!$l# z8Gy9V1C7X+XXV*z6*@n2U-;fNMcqGX+AKP!;>#OnC;JC+!}a`|55?PK)p4W=g61;# zR(%kp`?#N~2co(M$P;ie7**kaz!F3MLdCKfKkpb5laN@nrQ4y9W_3oCxuwiN|3C%- z1n}TYu#eJ|Zf6IVja(m9Qo$u>g+r$+=V;%(G}dZa9*m~^{LM8Ud*S@lw}x+zU6Vh3 zO|^tatKVof$d6K36-+EcfD(Lb4eX^=6mc&RaIH?_lV}%=8l;MQ94~q}XG8BKKlG(= zfR*=w_hKyxWn0)4w-dq~RYe6L@rYzmNWevfw?IoFToQFo(vVsXD@0u@uweN!g@uL7 zhL>vJaNURZBt5!+|6JDp8+yihioa6)k>cKBuM~SI{_prT@ejrQ<~bO*GOly%@3Bi` zJH>n-^HNOb7*Di6x@XjtsKrt3BfpEBg-(DA5%VMN3I92Kad=1Q|IHQszZs#eLcZ~g z3z-~}=so3~>b?EHLId>A1QyLpmRh{wP&%+0b@_A_)eyO`T2Y2Eg z;R=dR2N3~fO+psLJ6r&VhB49TvadpopK@Xt4l?Ek!Cu`Xr5-230ERH3Y{Je2GW7vZ zu@zK=Pwa83$boMmg%~PV082bSUdK6a<`xzUreM7Wep+A}0zNaj8V)57W}>+$vg7M{ z7F^1$E(_Kc+yl??HB!xUZ|iywv_v6c8C;g3^}+>3Rf=gEu8{}}VPOT-l-ShPLcoc^ za|G3N4`!;Qfr9KdWCY-uwE8TyYLh0yt0MPde4r9R@ITl*>;+OnZ@qXF-|ujnl`;%R z17=-12@DB`HRB~$P+JE6x}*ww)Tfq+PWv%DJHe5spT(>>(ZRsYF zh`}L2*6Zc!a3W{u+4GQ41%l6{#Wde<$g<8Sa^Ha}IzufW-q?A(uIej|NJ5Zo=#*9d zy{%Jj-ZR)z7OkZT!xfF;sId&eyv*v@@Du4Uet0(47r+fkF4#Y1cQX>Y|9dHHx>r`n zSu$%2Uj$fE+}ZRjf_6inYOy1lt#}`-CDrrxH35)=&&|P((uaU;fd}I*Dm7ljZ2r}) z8(apY9GM(Nqdl8CfZ~o>H=i(&XjpJ0BI6FaMu><|7}4ojQ;wWlT$@tN;<8{~o>i-E zzPs8bU^>|JypAuV^D=-O2Bq?G5(uLsCgNA)`f@Zq9HqDX+yDA>DPy9;3U&^Iz}05p zTxccAiC~EG4xUJa4*MiW+U8W?N4RzgZT@v$tuzMv&LUngFOaq!!Moir1D^z7pGG7W zyTcN(p}^;v)#G2^j%d5AUb+kO6v516BdMg7A(9#T%X^#TU2j za(J|j$D$?)3oXVKu* zB3Y`mT{2lP(U*`ixo7-U^NHfRtSdr^t{N(cM57z81PB(N$I~s3RJ|<(8T>>071#rs zEYGj8H=Ax4K!W-xYKU;Yu(vGOLG4=PZ}9gRv%}e&cr6Fs^1%v!j6mFEJTA{C1Esia z%L^NXLMGxfsZ%MDQ@p=vli=i_0E8FB+e^!ad$2Nj0j{GR8rU8pI}C~$Doljnqe;lc z=K^uU$;G}P17t_8s#7`-E}|e#9#)Scpho;n0~|r80y>(z0*<_XaQ{$|Lsio#LQoDF z&Ere1wBzsM79gEcrX5Zim|?)+jO@g4LN~*6^39v?nU?Xi4B$#ncIMQtXbIE_oMO{t z6Rn*NF%`>#$Zq;|Lw`w0=W mIIm@cb~uxR}4oGra`NK+DIrFAX;^(JqAH_o|Dd( zrPyT)sz2ta91fl!g;#ew=1R#iYgoE(p+Qp`KZ;P{efyJI+l7KTsIwiaa<8hJpBMLHE=RZRrOn*gfSfDyPDW<> zX#4D_Lry0c#0D4FuoE3kYUF}azuVE$M;mKW8_$f6r(Jc--t|m~Zmo;r4P~ZPr)Zex z@TV5W+w(GEtPmxjgvIh8Ib%7LnRZC2!|MSt;maxhgT`}{IzgPO;-9P#5?k`@P)e#h z2#75Z>A?*IHNq6)k!d8r8W|#3Aa9sn&?%6)gbrsijJ(aCGn-E5>_E6hmN@aPEOW{6 zasxz+Bj&xe`M=)OQO7JYvp7!+_1PH9Xp%KbTf_>moz%c!H%Ekcinrv+f8}(JIi`Yb zs#T!XBLUiwfk>Qylg77|r{d_7`UAVCIi7|_o+Y1Hexyqv^nLs&G9P9{Bo0}roVXMN zZ1g@uNwN^-b&1T8`>C9tglE!_Un$LE7keaAAj0S1v9SOj++efUuu~*b$`CT1BA67! zocyPAbb3=&(wg%5tFOPf8my~CMR)a@UUPItU@P3KT zdu~i+{@=j6&r|&M;wi;rqwX#CUa>T20KOle6kjrKXIy?<<=78nhsWL;b1>$~n7YvZ zA0OR3>g%Z4k(VQvL_QF4DPmbfH}Agizr$CC_YC_hY*kp#u#nKVLz6?xg=`MV45{?r z=l=iqzx;1s0gz%Uf^b5QL!%D2AQ+v-`6?Q1Tp4Y4S9$$pwDGe(j?%FVCzBk++vUp5;I&Mw;knv3Sfe>NQI;b|FD^EGJ?nQKgc31`Nm`5a4;eHoBWj+fD)1$iioCE5n_&|pck+y^HMdD}W0fl4>!&l3=0dxzWb+(>alb zY$Tv?W$NrZ4T(AK9c-5;_2oeDlQYlwcmZX{v9xLL)NQ7fP?2^)wEUc5J%Jffeep%stgBl}k zkfJc}UR+bOTyR%tja1KHpXtadh6A#*ao8x&7bkBra7Km}(lr8xBnYfv{w0RtAvQ(0 zfZ`I6EHoZqRe`4|@}@$9LJ8|N5<654wg?HDbSGOAixQt~?-5c7p#T?8>_GtyrdGnV z)lX7FaYx<`m2NU_h9t7~Cu(naDum&*ER;`T7fFEjYSwV z=)ymAbRssE2!OaufqfC}!)4^jU{?4!p^jBI-2!jRMd%7Bsh=#0IEqng7q*O%MzNSN zA5sp%8&DCGYbvKmBxkkQ z(g-66UFBZ3+OP)@&s0${d>2U|lu3nL7b}K7b_UaK$XRjJQS2~Ld8CpI9ki{NrwxCl z@m?v_*h5ng9m8BIi{6#%EgZ1h6P!Oh#8Z?mog| zq)zbohAP<8aN03ySJmfLy~p$Xvv6oNbv+IDWyuEhT_XzkP`1owj^}6$z-0(SV|Gt6 z+K^Gy#d{%9P~rzlt8vwDej?QL$a}Fmm53BMNg|w2I00G;lP~1+z|Uf*wR|hh3Mnq* znp${;J-104#+eZ2o7LhqL4@!l+`}R!W}!uh9D{dkcL?Ra3k6e<_RbaE88Py!^tuT5uoMS=08@>syWsvZ^}Ye=)@vY=K-b(zz4uo=EC|% zl9J#y$&H7#^<|fN;bymk`=FEyDZb&5vI)=C`L&H45Gc#9_CTR47#g33fXU~Xyh0#6 z*e)<%Z+n0@4PLA!Cb5V)y~!o=|kX=SYnSY&C09)W-lUnX;> zbzG3B^dP^WO`^5MyOX+yG>K@Tj!;3xpEc=%ZWglfY19)^M3MTh`B|Bc_w6Q&1&fUW zl3FaUEt@d6nDcOUpz{BRc#6+0UboopV#)D;#m|esGwzeP^tkZYWwCd~d=`@x1K9Y>!f3$466TtA4w1ifG;zyQxeD zW1BkVNNIi*Sue%%aXa!eF0^e}{e0<*_s)sybywY?d*1oDa_!9i0f=xzXo{U;%8S;n zByFA{`5)E)l;d>cr02vZEfj+nh)Eq&ewU#2}llXQqCY}3iPHy`A zq&M!F`_sFNIvuUN=Unag1_f%8`(wRk!TCbk>a1h1F#iE0CPBynN5H5M9aSoT5osl% z6^#4&cVB<`wa6a*-pQP^=E2@WN`BC;Wz6r=>IlVj$_0E--g_6@nV65JN)qmzLe_)yppp--W02?7EXw8x&&a+~Prh;C;Z z{(Sc6xdmt2r=ROKt5^56EsrJ#puY_@O9H*f_eE;PeVnU=>CiQtri~^UPAr)yIH6=ryR=J*U@+){$ReCWwl8!c(nVY{gpvX-n1)J3 zc0b&A_no7w-MH;m@8f^`w&Z;5fIYq6=@+QP)`k&0YG_MDtdEKMpH=1G~-6Wh?RRm#97u;o@Q{~E{MqmJu>!11qSR)KX-ZbflVVHpBeJ~U3WYl zs7UHMZZOQg@^_&EzdQ=XPb$NwTONwX%d^3z53#;z}S*`<{O<;->W=&uSE6DXSp z|11>-o4Li=gF4A8DPdr2;+svb51nvjL?HjEm)pdQ`pg$7$KQhxfph2)1rlRZB^9tU zgAj;bcSMcYD82{+2JE4u#(;k!kdM(I`$c_l?9lN(i@U@uo!W7G?WrARmrEQRC`(yZ z?YnZuQ;G|PP`RLlu%VT~<7-W-j(_0NN7@BoqzGA(rRn1SQti&XGyTxO{ET1I4^OPN zamSMfh9?Ee&;ST71c^VOjZTP9gGyUk~m*VB{;aQv#)FFOg#7gd#4I1EgPq6N<3cWLa?;IZ_+jh+qm` z(x|(moC4E>+saa(=<5k%v(6v&_B44StX1C!S40&Sj2Rd6_MaCA21=1G%gh?Gl?>eg zS;LGgzsYW*D2p4$;7Tw!@FbWq(b2{sUexnff86uI-$$lL<#@Qr624ABN=aq-(q^KVtA0rD%;i3uyLwL1Xh?Gzmu<} zQR|qL$#>o2E8OtFM|Hw>wrX?alNqn~O$ij|_qf7px~kML;z8a(_`)&!wS`*6`>eN2 zG;<|!vzS~u+@3A-@~!aQB=W^4O6qR}LQ2msrhHfqu)#I7UZA%Z^b*@I)im8Ek+Dxsa zI8Z{y>cC&oj`H^#%R>f8pzS;x7>LSUz%{eN3nQwbtfVXhueTn(z3tMvd*@_V{^ss) ze3?zhUr7qYiOU2yM7{z{B10PlatJB2c={~p5N-)@AgpGA5KLsq1WpRO+h9-0y5X&E z`ueqw$&IhC%bHky^V(1P&Rd+)zskTstej=67JrQ6RLoOKf1k;+N;{E$AiGu3sFQ5T zi6v_QrYN`9+Pfut(nGiZ^-kM$HFqp~;MlH!H~6NQ~T6$ zf>i9o+!QPi+&4SLc$6jH1c-(J+ajuG=6rUrm+wZ&D#_KC&MHja+xW2sucrs1C37qMJb^+8BPy`O=^uP`lf!+ZtbJxo^h4Sk~{ z)yv+URO0nb|9Np~|BA_hNJe966y-)Di`6o5;$V6rXcBG>P!8+Gw95aGJOXV1S`k%9 zZAesLFUKZK9JcV*2hwl3eQw7sN##$JI&&>05Wx!x)LjS#_$Td!3FIpU31^s15Yo&q zb3Je_7{KsY*{Cm7p@$&6BkQKxf=?4&vEs=KcP0>3_ z-g_SA^GSIoKdiDPdbed6rkKq%d$byhog7H>pW00L#ebf{s9&ybQ1t~xU>95uFq zX+N{m-H?gQSPUfinFVXkHrXGMaJItKsuRzwzwd<#y}x>LT6!Ra=h&KQaR9`Y!BZhN zGCrHB^Wr>kLy}TSA}|yNMjE$RvuDG-V7ftDin$~8wRznR#?&iS;?nn{s`MKF#60Hz zwLNcmiq9#2Te0oM`o#Yc|04ANKa5L>y%PIkY=f8`G0D*XpB-I0YHQSh$V-t^BdbQd z5z#&TLijV`_0a#HiT?jZp)EpohV=Gc@J{kp!^8d$|6fJ`Ajs{Ba5ux`vHI3{xm>3bUvXI*j17f6H$#w8sRCDcU zNcxGaj~}n}?hpRA-@onQ{h4bH_sR?aC~buPXn~RhD}c5(b#37EldWoQfsmQ&m-M8IF%c7bwC&2F19~|ib8Ia@xnl=CYMz5cz4V#oQ zul=GicfL9P{)zSP8JQdaaS|{VGj|(Ssoq!`!iQvnQGj4l`^SWRU}uN;ExRMw@{aBm zV5fvTaWxY=&u=og`7412b24+++}1p&XSq-R$_zl5yAkBlVV7*(9Xz4ZAY5B)1_mT$ z!PIE`Hqb3HfM9q;_{0imGA{`kvkc>Fzi4Ks#D^Ajy03TPwDu#4;;O8?GiE>l65SOc zn9eSCx+oq4I1tN@%@1IUNDu((13E2&98jdtV#)~#5R2tBCRkf|#KBXa+4oo1we z@bxF}DY+t}4~dNtLmlr5*|pEzGggypd}u zHEa|&kR}sd#3|1@y5d5Y?t%B##g z`;D}Lp6@=pb$`o#FqiFUE;D$z6|i+ln`U7e7)vCi-HuFKi1@P=7gvMv2ht&MTwDWA z(hs+;?oel9V#mX2_kGgz^qJ5{*S1X$KzzGCQhn^IOAukCv{a_y2(StS7FvO|6u=Du zGIlI^v2G%V^q1AVci?W{^S{5hyWsTK``5H6c&ZE99S4OnIfYS zih|TXQ+6HsSaN7wN6E~bLJCqR9J21GU%M1@m%rbFr5qM+;VBoeHMG1lV zXpPl(Zh5Fq%Y$!J%iVNqR;3@mxob;m;4T5K1haJt(vAVCMPXOHF(Rn>1(_dglPt1m zFl0I&VaQ9qE}XC9kVBP-C7&~h+*b&f;zA>c!?i15~`n<6r! zhB~`~m^9Smyo!LK_3em)O6-?06Sg z4%1XM7BK6uP>6zrOM-)e4m2jOxE0pX;wIe7o%neC+5=19=#jDb>0#YE{`{kN*YVcl zk^(K5TuSeS!qC=0hSpSqvKfeC*<=Yv%_vDF+{6he``sjU1s$Maz4ZEh>rP&2d)K#X zmbWd}$@A{y_>*rA3^b?Jal+NI@S@8nq-7jyOs+L@oytBWCnZA|i!JylSc8!7!VkFZ zeBbvkF5gn}`N6CA_Gr0fT#3@7E2a+n*%xTWo1@ZJj89-iaQt!5@E zKd-O^3&KSMNuP}V%v{(kK%J=M`Nn+u*Y(wf(W3Di$xV3L|kc2Bkr$0X;{fuwow3WgNfrbsS2$Y8)atf~?IS&X6 zzG1!Zd~ra`+_@FAo;#iJ;2;`#>hfurm%))klxh(Z=@GId*8KMVUL{uZBkms$y+Y`oE~T_nZHV_ zSxc(}Sxo;z@i6-rI3Yk)tn8z)i$D?V8xB8UCmi z)@9$*`FQk$A2fb>>h@aQCx4ch6u4brSX$atP*@?oTHWzNRJT*FqKFyT<Pb?V}u|1;u^m?kIJ|2!lu(11W3?l26JJ6mpCfh9RsZ&Vx< zM+>#yn`%$e^rMA18G4Rz_w?_la{gS@?5RCBA~s(=^I-Cy^=srM2kP^Du_(-9r1ZoA zb14LBF7;CFb2IL#%>h^KAFG)XY(VIP;0wG1PkUVVS zi%Hc^51sztnuN~#HyrygBT$!@vk=50Bz&kea2Qfhbj_H#B2ofCO}gF`L(yVs8cmTq z6y*J5T_lwLasQ$~R{b$&k3Lj9;f*E__~im#|A=i;FKW-lN!+V#`B+D%RDrJN|dikocwX4@WhQ4~g@~4Ty_~-4Od| zY;?@)VVy!3#Uw?{@b-x*8@)3+FS=&bp{U7`CnIM>wu-pmAOYbNStzL!PhmQFlkhnR z1IXS%Zp5+-ZYN?oD_vPMY|3mHL!s(Fsv+%dIst^Lgi!ZbnGojG7dR>tzEQ^tLWv0; zlS~a)gycK1W+E=RrpAH_NtRq0U-1Ctjn=v8AQbsnDjX6fk4X+ zjFmzyo78*--uOc|pLQ1=16D?if*6d@4}c2f@of5YqX`w$w@H}F+Re;G83TbdCE2~v zyKjCd5Fx=+HNlp-)7d`UW;aOD&0Ju)+kh+Ub5;Rl4OTqYp1gSpf>a=L3y!QI#1P|L z291aEv#|!y`iDJ20UtC<6ubbpret`&uM2Rke2A^BCQ>;ZFrXx2-$)Fg#;^q#7hHIj zajoq^I$#keCIgoyXi4Hmc=*i+DFuu`&M&gEE69!c0MLLwz*!=5NBFFxrhGmyVEJ|| z)CikWr#_D;fAhVu-ilm_+?YWF`G71jP!Gi0f_ep;3ncC`hG{TY=7h@BZ8w{77&WjT z(JkzQ*=EdW|Bk2ywnGnrRKKOfYB`s-iqoXP@_{Y_o98+FImCNfI-_A))mo!L#h$>u ziA@QR%0e2-M6i_>$~#+$DwYOt4jdIs%G)p>X8f1`B~$+Y*S~bL0ReJALTNI>M^1xe z%Akx~VhYS%@gGLu;=vP{fzwD_@G?`Y@pht8a$UDWh-jEDInJIoU{Da$#X1DXwLRJO zI3s3+MYgct0DSL#N5l*>p@FeNmClB7sN~1sLjDI4Rh9NrqHa{Qg1bSMsQfm^M+Wm{ zh-cXXxjphY9UlM_ggT&bOc2}W88|2n4)AZ#P~z~-0w35WAW%s06XZzPEaDXHPsqKN zrRwl=aOEMWN|(4{V+U4@Qo*= z(!eNAz}V;mwax>Kh}fW>d(X`Kz*(*qB2QIWWog&LH6$>Ani1ED#VFQ5;oi`IuDP%3 zv#21m{Fs8wF~@^njE|D2*cx8prBFvx^0nr=u9iTKphoc`yDB0}1d+%5h(l z+GIq}q=7dFYr^r8vC^XHV4WMTTL6JBP7~a%@ag1z4NSDsqrvx{%UIM#&UIRi}O+r%? z2NZmmCd$GR76QBH+e$aTBV3^@o3yFPVKe85Gk~*->jeTLP8h1=#3%Gx8B%66LWHBd z-h(#Yd_c8+i-#b0G$jUE0H=0Jo|huHI2L-P@ba7yO%fhb4bOp{H@j|SO8`^^u!u#G znGm8cj~zkAZG!HXt*Ak!((J~DY5BtHoD z5!8SpPLiCyuxud&WZ8wHJk{155qJAPPZbm@6=|W2iw-7CCZ8${RdflYv)R{4w76l2 zfRk9lfMXl(tNS`UZY}p%M->1o7aq&7R5qZX>_uZeTv!(ZQGjBJ!bjdPQI&EgK+@y# zeQ%D5jMe}|26h~XtOF6Mn~1IT76>sONCOiK?H2@Jfxpr@W;?u|DLKmj&!zrB1(rBLG$lu z*s!o-p{qjg3ON>15K_{+!rRXCg(vsF3z#wjg#HD{y z-!gx~?&fd2S36|$5-)V5weQl?9+gW+p$Ls$XU*}j#UDk04rBHap2Op$0JgeS2;nX}qV-Q@y zoU@0D!JN|-dN@6&O{L3Y%M_GK8$SKBWfN*__-1liAV;~)BAag23dBoPH-J0R`GgS9 zMO{F8r?vSEKpKNRNRET&Y8bzCY^bk%c3#*!`|CXQQ|b4=f9yit=ia@vrA$#$Ae*>w zejy?#e9rJpqy*B{r#)q&E(hPu*GEMP!O$?Dm@Hvtg8)G+OUD?g&g`MBpLP8+>hNvH z-`srd_r=YY?u^d(F(r`2-B>y#QYOHxfUT%8rh>45BWJ!yFoV@bA*O`i%BBFpCPC^&8Ua;=op;~;S@r4X|8AMTFLd#Z z{*QO-^hZ=`AVZ3s6d{7kNfX=QfNSjlaOGTui-4r!*pK8Xb|8d35o~7T9F&? z4r7W4Us4%ZI`;0=Dc$$;|Aa>UHSp(Oj_qi+{I_Q7Di%))r18jMMX(0aR-z>a2XnCQ zqpjNrOF(fKM{6u&mq=P1iEJx9uXFVtbst%iG-Jg>KWwj1J^W0&S4SlI0uaou16A{p znP>zRA<-az9`FrEoTGH0NV$t3EtZyg3M%AC0bHA}7TBBK82kFP39+X>z3afYi`w0J zvE4%rdJPUhGrLKm7=1wvT?5fSe;N0JrUI@{TCG{#L4~AHOp{HJ#I!q5cM$)f#T18! zs_rShI}ZBo^~#TI@%;4Iz1KR%Cd|y4I53dHx~j1-jUEby6S7ALTIXurqR3T_-xN=nv0&1;)y8Gz4N1v^K^<>3o^P{Hx zIx+R{m-`0>(Ff>>GUZ+7AWVsfCR)~@c#^1iaFqZWL%enf$}UKidDG;5mfZH^yp-GK zUddY7Y}o5lW9m0wQ1hLn05r2j#vCV88|L9yQA|TW24(@PZ!n+_6$?&zLqbXOKm{2T zJQ2&AVvTg6+?gL=Z$rVr-v`wQ%ik2zbm#S3a(5*KAYDfpb4em(K#3@{GV%d$-O?GE z^WegG19r6U+Q@szKJtWq^i$W(k5wGs^Y(8N+qRuhxu_|0noA50Aj)i#XtV)bS>YK< z6y%8Tnr%QKlw)WdF-Y1qf;W{ZwuDV1aHxSBXZnUs?tggr_oYUz+cWLaQd`33wx71M zb7a2&1lY==TrY7DwlG7Pk<|s%H5?bSPJvL#L<8JDR8x)6k3trfkiZhBlRR}Jvgg(A zxkoZDZ7mw`eXXzV8NMEbd1T*cF#({3adMa%i#n-tanVVE_oI!r&R^|wP_h|fn)Eyb z)S^>?djjbk^*VotGpLsIyDYg)){u7T0|MwBuLv!3 zSEv?sbPE0(hei6OG^osGsH#Sv6;Q+?N!&tnGl)w!-23CxPk&l->X_p%OE-*Yrh?_d-civzg)1bP(3<1mCrQNP zcU}SGbsTir8O~O z4D$qSoKK$GdwvPAAc#A`1OsscM-Q9i1cpYYPv(Be|KmKs% z-aGbxFm&`+m1`_&FzZxO0Lef4ib;pDHXLCoV6_n)YZcgCL~~v^kjy`XpJI(;TB;z? z{^uEQJo;CKYV&3dzIS%zXD6pky5o&k`UL4)F)9yg2fRqLTO%8GNBC<|2hF zb5PY|3+s^^<9p)48DSgFo^P>lb=8U`m$nY8Qm@6z%m5N%juiv^s&hqQR#nLx+nEXY zp7FR8?uQ|)13rXrg*T`6B?528V;V>X3;WtVb<4=SxqBKsTxW2@lC2uGYwh`?aplVy z0hBq=jj8IyPG-_5k+_Jgv|7&;*t5Yt2Ul{E{uxI`HC-^ovZ-?#yph}Wndz*3~H_=u#z_yoIv8m78yiw);gbi5gfun3XZ54P>{ z);SL~pZ)iwnIC>L>r%sspYOO_BPl>&H&H4?PBF$eLWbf)QEi?vbTW#FU=Xc#aGcP;9B~fWVb zLyBfgI&fFyJxq-b>{%e2aGD^ZqdC5Bh0;4q-*{$WrPmJ?`|^kJ!87U*H09O}$pCd{%k4z%Q&5|zymu%*KfHv64mIN`WiA&};z8LX*%G(dl zn$vjfH?1F@R&QKpQ5l*_E7Li$g%)Rw6QL#_&I#3DSKY^sgB#@90+c`lb4=Hx-DUgV z%X@Qpsf(ULXTO~C{-lMauTJ}{K~7O=OtL_lZ~dIsyl)gKr`N3JUwWH^CDmNSco_3& zqeyJxgF)%=Z@7Nf`;PiEqDN+fkWa7f+HoLh%EoUS4e%9};RU)<} zBQPa-(KWUx@kp{dJ?Ds};Sk8XH_3bw8>4IgUhwMJJrU3Ra_*5q`%aCXch~prlZr}6 zaG8v?!;wI@KlW8ri zzqo!PFp=5uXp}c?WMhDYsw3oWwAOMyF29Rs$seT)&VPCLu-08W6nl2-Ey+c3Tr-?} z-aot^9)fxgWq>f5Q)ms^L?sv>pczCKiUCWC0Yl9mYRv%z1m5;VrIwGiJ3Z;oXD%OT z{`Zn2*Xn(7V^C2nq0u~5a#U-p%Pb~Mo2N`?UWO-FkC=kkNDl$2idY2t4iI72UDn`A zy=pK2`bD+1(Kj0G4ZAQRwp?;i48KD$8z)x4;Er9OAWm&f;dYPWa4=j-`X7nLmj@a0L-8Aa%ly&av)TJsKCDr;Var(`qXKQOC| z5-yZcLq`1<5;s5v6kIj5C4)S_DCyali|xi%*u11>PLF~vzue!yPI?jAWa}ft7bPe{ z$2|I~4QQrQo26VsrIRj7A<ZI)=(-|m=p)B%{z(Sc^%_q6p#WGgC78D&#fFk#!)9HBCv2iwiS_YLA~p`b4EYkq!|7 zF0r=XjbHlfys7mjteW%nu_rypjvTrW*=k@B`e!R8;uf%Mgv4g_=p-qOE6?01JxH=d zRWEHAa4HnStE7CQQ2>U0wk`DI``efN>5YWcYrky>n^h@($)F-M&sG7W8EKZ8F%Yt!+hLFD5$}w}g_uT(yvs->$*I>`*FMK)o z<#)1*LWwCN@8VH*wPV!&iA`#89oQ(Mau?_7U`Syp;r1eabG`4uz}D`9V+o`P4SU8TNViPBEBauN}==U82O+5-Zcb?E7_EkylyHlE!1LcvC4|6Rho9CThdO^@NBv z6aWx1zSzO%IxBD?Ii%``itA$&-ydFif83xiQr{^3^s0Re=VumqB$Y}U4pf6O9~uWR z{bb||Z$}|n4N}0Q9+t@hxr?T>Q%nvb+EhiY!e!s1j;E}il={lq?oD=lQh({8tPAyb z-jW)4lFotC{EQffRGGy@wmXWAMfFDpJ_B(@+J-|T!nt?}6l|*FP@@{1okU&-6%W`r z{?oyZwYsEUsnT!c;0ZOmZaCb#Rkb671LKG~1v_^~5f#N!vwuOyu;G9d@`CB?5eVPV ziyOCF%SQ%CIbwy*gPrnA$gX?e>GSU8d1cPMz9{q9i=$qeGAJ-s4!Pz`rv0Fn8DGQ@ zN{|jjYs61wwl@3*d|e7v=YT=y7i0j~lerT(6FFsCo%!JN4liCCI{byN_g%kyc1sl? zvSWBt(WlEpE)_Nwb%l^1+&8WHkzFDXu~fUki2-*Am04}R08bOYk=l2;&Bxw9Zg!bx zriAWFT%FYTRGSg2J{S;qf{+l}eApHN(KpQ+kbt6EQQd5x>G;Y&aI{JG64xQ*OPSq- z;RY~(uWjj-`)BMkAtQYoHe^((b3A-q==?!}(Oe~juvE~sOh;I+A}!;hbiF>t##UAj zD$*RDsAVC+DG;tEzqaSSuIW1tc5k_J^r9wJep}tT@Sc>wD1s-_o-(+QD7dLNt9R!~ zmV%gt^DLMnHX-zk`%H5|%_G+dC7jHD7r*XYc|qf@!-sDE`JO$~E`2+HIJSEvKSX^p zEQpfyrFl`BGqC4ym=L4I$|pe>oXsC3P>Qs^jD`fZr(*yBpk}{$^|QDQrBmA#KX7ou zuq{pAE_*)z;emk>+-}ogFeq7AO;&q#ff+x+kicXjt0-(Dh?cqJxTN^i%pNPHW$&v| z=KkT=kCYtVw`J!F^VYr9e^6k!oLQv1tl)^TRCIwIYdE~%eo7saTBAZ(LOLWe?hVBX-5n3&vDdsOi}foO5&s4z0Yt~W8P^ATfd1HCF*jmfjd>V~0Iy&Y;NMYeq8^V5i`*E} zAaWo|2X93TiU<$i5S|<|F1%#ej<9_11aA}1Y0vckapoU-_J91N|Mv&~Co3QfEJ-!U zgCeI+r+3TzbvcYOj}%BKvQ;D$qJji!1qayTf`~M&+&*vjDZtkfLiJe~#Y4gkQJ1Xh zB%Pxy2h;#}mL1H}l8hJ@;e_(utX08y>(&k7ec%b8gEj4m3iT_Xu5H4`%|qlu#+U

    y<1-rRTeZ9A!ro7VX*5TpOcyif)}D>eggarDHuMP zF{zYDH!F&zb(ZJy3OsLr+?51DdoWNT8Rn5HS`09~>eeN#u+h5Uw$N)x8AB9ium)-H zU3jEtRUrtkRYLS(>~KpaPu28WM6CiQ0x1%R+>Ge3Z!BC`XCquMys*9~n?;hc6XH=& zX<_f?@Gv+|I0t~yND3$uEDChm935o6n;N4g*N&d$wCmuE;R8Igsu6^=cXb)OkojSd zu%Xxt&1W@4kW2`litJRsUER60Bci-=M+&gh1U?Tw7ax~oD7@=nNR#p?)g=ul6@6mt zBiUOvNE@UwW(S&7M0CK;RMZD*PL{@!65as^+O!}jTVOuTnvP5&R)iWv9Z%VLJ&XQ% zI|cXz-u_W6Uln)8CGll&#!L{0 z6BsaF5$QeI7IX^Yo0Ne`wR;LB8Y-ib1>VH+H{BV{$$}u8gSf=<8x#sBk#0pP+!<^Z zfjJ`6Qki8o>{+)C#jHE*6>X#3VF+TL8p-JdbILXZi`G_cu-U|xYnz~kH(?tJC3Wr5 zM(xcTISNkChPz{z*yP1f2Ba`JHR}OS%GGI6PKg5-mF5VC8LbwJAHVsj_;a9m#BQt< z9uya~PJh5sU={(5VYNmob@>R8k!pYo#voaL%v(I`rj->y$Z?pgFT$#bA`5MD$lA`~ zYCxLD$b>{oK4vm7ODtv^!!99^$jh;-Sy0~(Sm}a91Dm*drRpFSbF>lduoG5$lGf zf_awus1+dHLygg?!q}uSd(Lka8mv83rg%dsgN!xpq7kkT>;6e#0JGE) z_Pzp0s%*Lx=ad}HIYISDy*c6VOVfP zm6#(jUxc=0IFj|39nTubWHNMvpGr6sIg~nOaHOm=)a9Cp0J%dQ2MNExrpZ{n7`A5# z{rnXD>H#FGEgW7AWmqJdG(-fb8pWJsvf)!Pn^vPa*c7bG$O?`Ps4}jN3Bmgyks(q5 z+=kGCf^JlGnI?cQQQc2>#%nM^T^iSvaa~+BXU3Odp2Hz^F!4_L z<`Fi^C_POeR03TnrlYE+PzZrZ2JHOBs-lobH>ny_^pH@t1B!{N5_bsDVBg`M5mAAH zjR2Q>(nOcZ@al9oYJm`Yh0+sY=USf^y9OnE9RWnZAYe25qaMc&iKzNYn3F{jG+#za z&G~-(;#9=N;}G!l4B}KVONf1!s>9)shM`|M65bh&SV|~F%hhpMVVIN|$G}V9Dn;E& z+f-H_-5FST>Dz^6LY)eM5{vWWjWhD2J0=<9py1l&k6(@fqHZ`$62nlUCW135KDHJM zke*6;AYu38kP{J*)uvhbD%|wTam7^;DMD6~*mk;JBoc$jj1e|bNrA+GV6jzimk|9h zy(a|sLN?fNGx?W=S2ZymMRAu7OHwL%Bkd9pUb1VTuu_AtFw}U$WHc8WYghio2VmdR z;8zJ*HEBfmma$s`1C>s{(qI#D8sQJI$443g*K6wT`HQ($mKS>;#s$_2JBuq#3QE_E z7i+LVH!5sXC6b^TZVe|2V=om`3y))nA&;sYgR&CKJ5yty8knXW9xj0xo%cO&c>%c) zHpIdXCr3BgF8u#AgXe6|${uGuVln&A?B2}nsoP+;Y_4lvTev)N>F<)md5v>RrzcJW zoU%Kvb?o5q!eO*SG5bCC9qeA(4YkW-yBX>M4{Qe6WHqiZHZ|Nfr2S_yQ-3oguRw1B zIHH|EfNWtF7$c6kCanSz60F%28302g+B4iA0-Kx49luG5@$=n#xg41Jt;m$>$z6{O z%TxQGySYqeC|*IU8Fm`TDv6Hc+$%v`sj(1pBH}u#!wOe~ZALuGX{U^<*F}A13Wdz; zyX(UHUKOS!70ll$_3x#L-K$&rnClZRC(7K+9aE@~!p6u%iP2>lO!b^x$kjGk*s{dq z@(zt3WV+Mh?22i>SG%2e$GHnb1I~q+A$bMSCN9&f37Lx1B2XO+7Hixn>w-lmkzx2; z{5jq)%Y@=3!gvcQQI!*XHuPVx-qHLj^v?VeJ=~uxG=yw@7HWp*Rat0}fqkXKq`01` z+ExT$(mViQV+dfSSfCYXbOA0vl4TnEV@ ziul~ahAoY-%gQ)L>_Pg_slap7VuD(;@Wh-qP_+p$RbRnU}i<%l5+VBNDFLlBT%?ocn z#x%_D$%Yf#KBT74zdm43;?dd37w63iGS_0@T=Y7T50GCL(Kg*LM1%?gGowi;51y3% z3DOzz$pm7Daf|?KN0Q$xdr)xaNsm^;n-w1zG`?`x*vZ#CTYoYKm}}B&Q(fbV-)q$e z0o#y9{=$sJLMNEwNs$7S-{SO|^A3OxT0y9CznRt7A9^`1^3;{RM_=98^Wa7L${;f| zuZn@p45>TZql#OA4p84Ca#bo`Fx(G*1?sc9=TZ};9z}Tta6{F1$=I<&DC@@?)#;vu zPyRD}>Rj*GuUA^zxBIJ9x;(x3wZOUl=4xEMXR%ZYul4L*v;Z;ek`O_q8^#0F0U^pX z`$HZd1*DqJhH?H!JWOiPG@=5v4}20v8*ns+sQ_>V~f9{=j|9 z)C^;fvRmr+HTw|K&A~qx0j*dXqEp0|R4m0lrq~V%E`C8B8XQ+-+3bJBa%i?EN%u# z1eKrhNCCi3a55=yLR3XCg~nBo7n`!*9WwN4zA{U0?O)LTN2=R_z;YY>dzmYe2&Doh z+0*j$9X2M|&d_wF2_v+7QD4Prf>4wSCSmJ{Kg4bU#-`$Vr-WSIb!MgHbUJSA)Uh zYGk#ogUoS9V#*e8(q++))F5+t+E&&S5`1s~r3eJ66sl@+i1xu0Rb8j7MIk?kAA++( z;z$G0lzJE#3N6zb&6&~sxOYnJtJn9Aw26OGAii&?xf~g0Rl-w{im3Ywt&O#%uq+7w zLscH43w=6l@n}svMhecqSx^vsA`wa%z9=1t zp|CC6`V7f5vGM3u1!4k+WghOBdBL+~0cNOOL4KStGge3v!wBES9t~#(^9rfYR%LO* z*do4(bp)0ca1<;3_;s_~1D;&FvC8tc>6DC$Z@l+8-w!iG<*K}o%nu?!jw%XN;L{-y zWvjTNJculU6>yg(M=d3zUK#2a$Rh_D=M;LByV#8ihOp2xwccIXw`f}VzUGo-!KkMi zP*xPRh;#Fm^n1cap$CWxAUYjY(lOG~C=Q$}CV0Q7H~8BoB`V_ZC;HpQrq; z=Bri*noDq5Q(z5g;imcuoCokp)lCN6g`Fw?R$>g{-zu1+8!x0Bx}r!AjH*9=e2;D$ z8$>?8*`OVWdxvlB3NRO^g;CAS%;eNqAuun3cEVA4N7J8b^x-})8GuH~fpo#K+JHiM zo$I_i;Q3GB(X4HI?A@2E?cp{xLvn~gp1rnroup<36LQo`FCJ_zO7sb!AIz2H z+tr=*>P+a8Ca)ba1Q3mpP^}e#R_QuYGhnIc_>OFKtoN7=3m&(+lj&Jp<^n-GuFtyK zytmnli0<5+#3J)5C<45I09h6Z#~s^^#dLTpE0`2nI6VIp9UZp(dStFOkugw15-&~k1Oac%H=q3{&fxphk3P`M638qp7 zK^HRPO=mPj5JLzTLp>H$uR+R%JP6c1`~5QtI3)%=e(o{6@uA#0{<96$g|xwAlf34~K} z0`FMwV3*>zT?(GAbF59_Y-LwZF5kL|zqtUtBB%lrZ$yo7mb(FhLz!YUtMyTW2d~J? zHdK?Koa#^e3g8oOW*U}4cV==s7kD#sx}`+%3$tgIZ5OhCUr2?%f#&>l(sf`+@0;6y zx%3a(G0zg0u3L$;XeP1R3s~MjMBP?$5E_FKY4}AAsr7c;Fo$HXJ6Fe;uIz4_X>x}k zb3XFHOzQ$Egs442L+5M<8x~s?VK0T|ItNx15?!Z2ECXT?^97{LGAQ8Q_>(Ea7Ek@u z|MI!1mDZnH^~XQL|DS8{oaR~FW3NXK_jm52-AlV2cI)Z-)^)6F5tm&qU7g=K4|mS* zw9~1L<3q=Oj-C!H9O~O&w@KS5A24NUw`QMHpc&n+G-rwFqCgy} zny&hXxV48M30OQmhn01aS&KsoMMCjofDgG4Rg$X>3JH5wufppW-KMV`^EgxS(zQ;u z4KxSRuOKNTVR|~ZP`gLX9EOIP6+*$w^0JX0*Ce7cy$}xjF*o};+vsikg2slL{m6HN z7D&xgQq*FhWmH9(Q%g;2+QTAB$908B_e-lXitRM4LPius!TL{}Mv*EPzN1oCn%nyakP z?838>Av5(|G_%p50&k}zZ|W0P^~E~JvZG4agqYF(>g|KY=98dlpwj%V7ha4yy9OQb zs!@l)&|%8e3{|ojQpo%CbVK=_j_*$Fy1HTEfRbO+=6EEI2sit3<}RcQnMbDkFeGQx z#vMl=kVd~SnCTdIJxrbgw(FPM{KtZG@}m|TpSaxIjVd|6INBL>)es43t$ zQ*T)U3ewi|)hA_qrKxjvFS}Bxv)8v}7fWAhIKE+!8Fijw=uW0U00}JOL5PszJE+wN zQ=QwSRBV7$kLppxfMmQyFyu$P{^Zs01Gs28=Q)Lj8nR{eiVr$b=6mmKWiekv-!3;y!OtN(tKSWvy zcp5?$!*OyD39~dBVi)SyUevDqno8wgRj(PaZ)QNq-I`arSh@w9Q9g$fGmMCIYa{{? zL{Nn=6X$^_Er7Z$N5T>sI}7leeFze1DU< z>(`JQ=MN3K^!Kp{GwL3dePEt(qBWymaV|nIs{juJN ziz0w5-X7R@>UB$w_A9=xy*anb&~bGN|I^ou(hapQqxS$Wi7aJ&C?p7?XslE$;RVEt z=1mBQi{?SYVOZ(oY`kS$3HGCZ)XVxEpYMpfcsZffqy8HQ&CBM{XjD+383iXVA3h@t ztmxz(`I{QkO|)p(bFeI6m60AGh*YJ#Sc)N8ULGe35hh-Lf~oy@x3eJ+NBrLE*p%6K zXEvJtBH~JgC^PCh)zGypJXC;rWL0!mJ;F>LT&=WyC25QXY(RyOXc9xz@UH4B)&eGo z%>yI;Jlym6vacm~E)H3p(7UzeM8L9MwfxK|%;g77nGnXL0AiR~%k5xvQ5hEYo7rPR zaNt2)*d1`WJko|0+=v7KZ)6H;y{>hS3wOTv=-XyN*){82gT^J*DilaGDvV8%dXDgt zSP4u9Jun{+881wRl6oN1WW5Qcsi*)z>_3p3N8gVcdN*Xs{^X0XJ=4}*Je8$di@AYj zaH|j?B3UD7gV}uW;MjYr2VO>gEB7NKbL47-kz#eUPp8rtyY#ZXBNLX)iSBXb!TkkA zFTT6s(P`q1AT!8%#n8hHxf@X}MHPt$+UxcLrnjiBAfZ}I)moJ`fKJog8ifMUaF&>P zt8W}neH2x=YMz+=nQDB`$d~3GYzAYd6bQ@8+{+=-O*nWra4~{@B3Y*Vm!*myF-f7S8<1WCmZZ1Zc|uK5-x!%hCKm-I$_^ zh~kWpYEbS)h-jb~UJR4wc6JJY0mPZ&*Y$q!EjV@FY$wOCw*8wpJ{f(tT~eqS+|#m1 zlem#n;jD-_a1zxER1-M}9K)p%!I9%p{4^Lq!o@^e4nLoQ42T6L*W@f)-q&H{iG?F0 z)^6-oeB8_h6J7_HTT)~tI1TmtiE^lFPh`wnQIvG#qk?Z$GT;B}My2Ttl~Zb9>Zn5roup&H6T@f;{uBr%FawI6&1bFx{#h`QDy=@W4h zKo~+yy=wj0H(%*(ckZX}Zyle~bj5+}1^0)Un-dlWkO60cwObGwV42DIBe2!AXxJa@ zZjD4Lwk-koh&oi(Y&w+)emROZS12LtiQ`&2)xn8PZI1%(s?B`1}^VJ=CZR)Vq7p_%^*^u}m z*TCvtf##<4D@yZ$eK0>gJOIIBESI1vu{bA$9OB^x$mB?0%(~zX;)X3lZaz-EGV$Yw z&|O#a&**dW`sC<UI^1Cq)Wtok;y0Dr1!LON1j;E1t+wCq1#s zraB$Va|jq03?Vht-yPj z%1<43q(^k*rBAyCb^Dou)}GEgrNpEx&ol0x3^O;P%^*lRzZx0uB#c}gTccnM-=nER zTKr8jU_gk0mb8-7hV>xY-k?&Y4tJPmnAxhIriR8L!3-bEuAW$+W*Dzj^iQ6RgS+qCfSs*?+;dghD~p~n|256R@hB|_Fs3q zx^_kFoNZs*Ub5X`yU2E=ZMf$&;|OD0&opD0XJ608w(UGidwSaXc)a(x4t!ym$5@XT z$DTIH9-TdEcog)ocYp4F&V94{9QVQQCij-^72LDCeQ~?%cGzYLrVIS;*3Ye%TYb+* z_Dww3x)rlI=H}x1#`A>hCD)yti#luGjlb2itV`_%TQQ14S;??=tGbrCPQ4Ta)gl8{rXStk}<Sq>I|QLb#>kfLmszt=5Gxf{;{OV`mak_gj&$I&BKaQyQ&hzH`oUe?fT==8N8}41suPWPa zZu>puI<|~I^89KUW6AvdQN44!$9nWVTXX&U4x4SyuWgdzbk^wk8-LVq{Ktn!+bo;(o8S+R@`RH_nklLwYASyw}pqJ`VX9I>T$GQvA2ifjCpSHM_qR;n=m-^@zZ=6 z6PMn7_T}=pk@pO5Qstv9Z+6|EVE3VQru)l})bFw;$JiW(18#g5U1u(85k7BTKtS2w zj``M_e(kgG=C0M`OHoa|8+BT_=VR!G?p+g04cJ@oykU&2R+mEmJo{eoclUJ%bI;to zDd+G{rwcdU!I$gwtd3p$(Za_XRI{|M)3W-7q{1!BRN>2Yy58XBtkg-h%Z|!Z{6W!k zndTPUZJ6I$zBD+8TZI=z@?9=^@rCR1iy8Ur7tOU%J}H{T;9Yq~l~b?2W?kc0FYfG7 z@BY2`la32#Jl%b$Px z2k@8Lx-Kg5utAN3|7;)ru-2!n6N2B(ZS{h`)aK8vA9K&Mbc!CC9`bh2jsAbwlrm;& z#b0W(ZOQR-0T-7f51jP+b|G>{cHF>I8?XCWqZ4h-wYYw_)D!q57wF0vOzcB>i^U_uq|-tp_3nqm*7tt zp8TzFwe{OPvYwxkwxmLnh`21h8$0sl;!C@3FLiOX!`3~Grk^~Md}G(!aAUKc{L$F$ z2l}Pmy8r&{nsXCpO)aow?V-lT#uxc|EpOdvXNzH>D0yD_i~hUdh6@8cSv*_`Kd-b+7uXE%jZb0GX)b0-#C`_+|`AKQDgXe zjk51NljXtZg%1u7E>@;-x%cHi4l#Od=Ib@QyD+`8Lz@>_ca;vwUBOUc-;(VVn@Qdhn0a^~CnOrv`&oE>Icrl4U%w0u-} zVa(s|y9Ny_`rE{jiAlF-Ep#{R_{Ja2&R4k3f*%_)KCMq#5xo7~=nh`13=g;PM>D5n zd)O)3CU#_rM{Tzlw~yGq^q%3xAilxrbzhg-dvE57g~w`C%pcck@{|eX2CwH&rcG~{ z>*LjTy+TX-w0=6YV1aKJd*_(Mmz%o(Wz~!AYh3$WwqcP6acv9LKlakFF@ryvGU#H# zYR?Kh+-!fY_4Kaqzb~i~VOXAruQ&OMxkBdxJ;Lnoe>;04EjV@YIKR5$Ehmqf&>(l) ziA7_FSYmvAiUki@Uc=~?gReJvWbo0uhI9=*!O% z`>uU3#li47l0O>zsNT?ZZk;adc^7}Rapi+KOE!66bU4Q!joGlKf^*T*b*sNxdF#}f z=cP*(du_Cz#g8>6X8WPwvYb)Bg-g9)94j$qfs3 z-FwWi;3)spuz&s;`{r}DkgGeE44hOV#k0hS{cel+leBct_XC`F+B}@}$~Pl^Th`Z0 zClo#_pUmrcFjtll@11I8o_g-^tnlNvwic+vpQP45u`_(#sjhK#PoMcVIp2kWKMse3 zk&7@iwO;qc?5*=3t^T9ol^0VSN>%8%$#AkCe<>xgSgn^?hjuC2^+CxSwLe$s{%KI} zCH$p+zYpkt^UkcpzjF&Fm+8oxeZi)!^2wHBWk(DMS<(9H!Cm`SM(UtgzvccYyg>+H48z27#l z=)^s>1D$IOHvH(#*9&cx^<|mHMf(;WapZA_kyTo^I^=2C)s#O9iH+I*SG!5~oYr^T zQGR0SFYcE*817BukAhpfJ;~dtU#}+5u8gU5WPsP_A^i-y<<$oJd>WSTMCFuu%e`i8 ztnPx(ycbB(yz z_O@Y?1dRV1;%tuEe7(N!P7b&>c0$7Z=*ab9-@|LZ==jj+wn#n-4xjkt?#NO%X4lAT zTYN%}=<|&Y2WIj|ef;eXjS4hJHJ);5TG5z1Z;I{cV>lVZx9&T4;f%CVx4av-&y}Oz zfnx8kWKC+=n!n^b@!pA>H;SAM8EQ^_wzgMNoAL>UQ<8l2ewFU{w%YI$r`CVHH)f@G zo!isr8ipO=>-An%W=+${i(B@seI@9Xs)h<43dftT% zYOjlu$i7>ala|jfZF02BbEELKcd4oU_hvSXk$f;Qu>Z*>p26+vcJbJE^@QKIho|Zo zv&&DVOl}mvX;OXn!>RSg=4rob@5j-*jjiVK4aUXwEwgmU+XmGpj855I__Al^YNL!< z-bnp_8|nX7^|<5_;o;=I#J#%PIkynke~@EWb2-oYe{-CxI9+rKbFz1w=UCCtg#7S${#BO*ZX}&yB;41r6K&Zv<>X7S^v)03HB02H}L1$PJZ%u>iXX z8pNy@DAS<)jj9Q%v_z9pRb&OzLvtk8!cMV%y|(?nqw$&t!`IBs-0?&2rT!LostYT9 zGM-?p;M{)bn+JTv^Mv&1Os&YB0;CwM1*$gz!o-Ml>J*}Z%L&zECj=CIKQnXh;fKCX z`1_yIeizC;Gg;gSkVS;<_gn#(XGDaE3AsvjNgPk0LE zlcQQdM4x`d=?gJk_AZ&!^vRKsOSNkIZ;Pt>ea^RDZ38TupFvJfV~yyc}4ex^`;g)QA|^cknSSH6Dju)h9>O+S3Me|~QmA8KL!D#d#T)J;T3 zK(9c=PyE)C6wfa_| zblC2j`ELbUpkIXw5b#kn?I5be$fM8_}zDyOunRaQRV)Cx#aE;s#c# zG^4|wIkRG2?^Mh<6W4oLzRWdE7HC(Yx)}%@9w;{tFxKT{={{4SENXnzD<%C}9^n**KK9%_H`Y^}jHZhSvnys63JK)-|XC&pZX6UIFP#7d*x zrVHkb%qljY8Wg1($fUMGis6dQL>*KcmgUIWx9EjNwI}2%{ie{)9I2mLpANCubIo2g zbIQOV7VKs!v=+An(K#ZFn9yJjqsYvyx2jBy$`MobqzW$@Imwy(mTm5yVm{k!X|_GT zZyui|?0e@B3#6+^Ukv!M8gU>UeN>b~jh+r24j;7CsSzUCgx)d~3srl%*7V^bieee_ zLT%X4ByPd=6@L}2pR3sir?kk8zDHh~EVlISL^7I6BBH$jDjH0RqC0>9LPN=@bx+eR zK-U0kL)Suu?giZgm>aj(41c!&9Ku(Pf(x0hV zET|20cN7i?Y>pmuiGvSY3od2=mXJQjzl5=eR&JuL{HSzx?9jsfJ-^r9w`@YKxzj&o zSp;${M5~}JrutUFedM9puw!%*BZKi8ssBb7v87OpA}&kOD%+&Suz|7-`AKYCP*k=S z8MQrf+(kd%yaoCi^8EGSa!|$A!RCQnw`WtOjl7UQB0;c%L9up6ar-c6DA7c8LUkI{ z4g8bpd#q-tiJI6{J=QLadUAcqiMtnDr*{eS$lTq@eUra=0C@&cNYh0F5XaD}$z*tJ zZxpcMnZiKoS1piSD^W6Cnv@44gaHlyA&u8PPI+SKysk)aNHz0}QV-HE+GPthL%OOg zs`cm`N99;4JVIhj>at4r0sDiyqfkJFM`HCEDVeZJWCGlLDD@VgKCbNJr|BiHj+q^^ zuH#?x7A1PMdDLU!Qgq{`(GgNDTS@>qmhK%@Kq8)sGoy*$0qB)irBsoNMN@hb(O0l5 zXskh4LR`~&_qL6AvEbdkGjqyjDo}GdM*jquAz@XRSj$w1#o{vKM=Xw_)k$lV=;`2N z93;hqkg*zIz@hX?tS64T-A;!@h|pHGdwJ?PT-=-B^Gz9;L_#nc>gE6E`^vA>3Nv&1k1=g zM{cpu`$ro(5Ebzrtqm7a|K=t?<>{oaLF(M~1HY9%e(LXA6L$Z|TyU`St=E$t#Pl#H z*bVjc5sr`0T3IOu0U-HoFvPI`a9MQyaN{ZOC%iX=L?N^!B6*My;c%{QwM5^F5C6Eo z;8^r~ceAEKk+_fek3sRAqRT7`b2*0%G$zdihDxhkP? zmuuJ;F$S8WX+tX(3nD=1mO|SWlvxBetePclQcAf8oM#^- z%uc}Dgy*Ms%E=ZNq!HXDMMbAw4e)iW-=OKTYMVCvI3Kn5Kyth7A?8SWWjYAc?p=-c z#erv)b+*&0B@q(QIFVt|;Yrw%cmm?@i~VF-Q|1o&d>Ay_d+ETLS3+$E4-9@ct?1hR zzJ1IQzCpsNBFlG8fXruHBlpqk$@0Sg8~}qN2c~DWh%wwC>Dfux2xxa;S5IqG)`|o z?j8+`^l9T?`nr4Bux!I#luEU^xziV?MV`wY%~1Vwc3AHaOa$Yp@iYDwp&^G|D7D^UqI~O4haT~@Cr9e3=Y(ryr0%#mNHYHld zDg8#2tKPRdJT#b&&ZM-Ja9=kL4H{&L&QC8A-7o&}Ztp<_t;iQtiKEtR;cgUIIz+45 ziCMd(^n6)38Qi^AHp0{z+I<+949nTGCwPk`@DiX$6;HVrwd}B?<>4dB59ym^O9rM9 z4>rK|qF_LT>%{AqFM~gBmvyX_5Vi`_evl)w5|INT@HAcxEk*nen}fbJks)j)0J3C2nhg#|XOgftnatKKQaCrLqu zzoS?dMwBin8mPVCb&Ce2;Z9Bv8gpZxHDKJcm%O8z$IW`nJ26Eyc+0^{0EN@ z^fW5gup{EQmQw_qAh}e3U;uFRH^9_U3UOrU^~^oUVT-b zRHwR&sd><_GW*X*M>=kTsE15|04r1mpeWftnpgmThP41|9ljp1H3C9~oJ#C%%u@n8 zv!Fb>b*d{9ABitcLt&-6i?z-Y5>v4X!U&dyNyPkAnRBOft-LHhjgf>!I@T7^Xg+$t zgwU0Z#7TOB$V4c`e;MKcGr+!q^Jrs%DxQ5nmd-2hQA5528Z66ioav}@>V1PzQ;MG&ch{k%(MU*rR+JWLhdc~h3qJ~(v(rOU? zInF9;VI$^FOf?J-1Km2ezA`uPC`?89vJfKJ^U=qFW6TN&p;i8YTUWW;oD>WVANv1t zHGkGz0p8240Cd=u9EOBq0A*kuApVY>B-&~$sR3XFK9dGf8y*zH)$+gxjNLLndNRELN`CIp#C~#MbPsk`>r6{FAXoAfe zA6;VSPp1e6NRD5G>VO?n41hwLQXc?H$*?4&yP_b4UL(pHWd%7IHq1W#a|bEtT$xQJ zY$Dy$7-)k`7S;%JbW@=ZgyRTFD!hE|Q3}A^5?Ch6ZjG*Ye%dm8n%mb&Bq+O+{hi!$eC*ET*by*b1NGHQTh|H#H zZ{cbO0txI@AY8OTln9BsWE72ng)?KVEkG>&Oi6`*dIah{)xB#0=mFF^hg{%fC<8eG znG9GjdfHIrEHL(f0g#b1ifVXL`RAKN?GwtWqA`Kg8fyJm-Bc(9QIyJ60P?6TMa9!q z5tZN|p-q64KB7#BJka9UK1oITFc5$WsE~vmU_#=-xG?+sWZL00LJ=nz)g20XuyV+) zXuVa8rDoa#|3gd|89;L3Dkc>O09VG{2F525CJ~7pCaIU&bUayuyB1Zh3A2WmE9bIq zL3qK+vMQ<(2VoovIJiaRNYrtn##V8MuL9_~=h+{lA)sG@s8gm|W7{J$p+mq+l(`$_ z|K$v;44!K}dwLo@)_DYYxH&l)R{#YFchBdx-!0TFGuZZhT%BCDx%6>yaNg}4;+)NC zkCUI{HpeK(tPa~90v$ZeUX7q7K z`*s~$>z!g#^Hp_ni_|oYLJXy3C2BAlHDxA*PVBJ3s6#d}8K#wVr-;aqYFOce{^KhD zx<1MYkQ2rUVJ#XU3&`T4SV@j2qq5RaAZZurD{It?vKj&_aJJEtnrh756pfiBY<}K5 zMS+ysKpf{3;9j z*}My7;0?(rTf4mx0s&$TGfcb9q46(Q6#$Z;Qmvb!3hX+t6}*M@32?VOOGhaLia05J zJs!;H@P>WMFIPt2EE!AO9op|rBq9maNk%JhfT3Uvq%DAcRkNNJcV#dL&ETd?{r-J| zv1F)atEpC;hpIpk2^S4+hKr&0Qgu0*B#yO2ZpN+khGAp={aUTJ#N~X}I1;O`+#Ixd z;G0BS6j3c6Nq{OzPpI01S8jMehqEl}!_#yIw)~(L0jp3?$`hUhWWbdmhK##}v^?@V z&H-5~5bKL}FyEX1UMAJk4Y$UHNQ&R#?{pgR1b|f~@Ulq|D7F%gRk5E(mbWI4tia2Prx9vQ zpOCR-0+_|dBvYKLE=tTQDx9)A^% zQep6MIJ(j%kG(?T>;P+G5?hV>_iJSTuJi$gp;d#L1q$utCV0{)r}oyXiv<>J2tOQ{YAnC1C6obw zdAo`WA#%1mWMb6oOrE%-@-@A1snM81i_i$r4va-n~@&u>zEh5;cO!A1uxe zc#SY==qvElKx`#4UEIEGEW8zx8nu4fX+^S-R2~hg2&_r1kQ6HA=A`r#6VQ)qbGqn< zzX_L13?I=+#~Y$6)u{0AtpySrwWed=;U9uSAv{84h!+ayhTxhg4S0N3r%`~o;x;jw ziqJVBq4;&qC9E5k*);0dN-a%UX9gEFl?V5qg8wka&iTBeU(gg&!>kwoHdPTcz6n_o z>1fG`#(Sq7g)39oAv6B)Vwutf@o`;$Q|}$;8PKoGck~Q<{@hRD{%L7)^pzwFJuP2E zRC4J23IRrt7D9wbS<&!m<-cPgHEf*3xD-l*w?~VLycN;7+9U{6*(G2xLy?wgkU1fO z;t)oAY|7y%RsOm9YVHqWbBM)Kb(U-n_8FnmpzILi1B->!lxBbf?Ir*mICc`e4#Eps za|(D#oj0-mzxO{%g!Gxn^&vMy+9H{i$m}3C#G6*JH(!ywizi8J0^IyUWanCu5><5sAeU_fG+wIxTp%C<*n1?B%1|y%mnVCb zfZd9sDSLb^{va@kTv?aG@ooTgsKnJUYR12BS=AynGy(I8lci+!aFB8EQ5eBFQ>e0R z1WEni$PnkjRm7+IKjS)$b@MZ~P$I|>s-}r}k)+;w0ae{XtgD{28EhhnmSD^+BJkwT zZ~u<1fLEY~pfz$uza9w<)vbW3Kv@^B9?jV_lQ)*s9jKZ?8;~86>WpX1yxlYu6s!E) zVpRB}5{BT{YEmGUA3GhvEtV8TDZE78u`T!f(;}c|gIc4|BkR;<6wdl8C}!o*gkoJ) z)|H~V^p_)-B;1>!M7Sf8-%f-&heAP1KV^(dA08?!!|=hk;eg`3iXxUyC8WO|)|SpM zSu`v?UJYk1a)jIcrHzyrZ2DMfc2JI^a3my=Agl`O6_1MejW4ITR!R&f3EDZav8jgr zU$v3qOClX7Q-r()@S`dg2^a}4NG=Lh4}7joG#(=jIt3)3j6{M@BdJpw=C@;HR6n=S zSX5k!3_J$JhyW|ugXWY5_(Qs!vKG1?j4C2PA#4NKH+uSW_HVr!&r_mrG4%!7AH-dO zuOk(IfoMn|1!s&YjKEw077%U^!zwSK&QvaQ=?iL3i&!>I1%kC=ZisM-^jJU@k>N(k zwy^3IP4pB#lwoZ;!-duhWB-!H;iwOI2B!9k!U{MdiL8al`h#M?R1AaFL*bu+AVgd= z(%8GszaAf$8-XK;I;A`z14AMq`b{yIg4VJB=w%Y{g=mO4a3UCw{5je`*AwCY?=X1I z@~q@>(xbQg2lq+tCEbp@`MZ8`o$p#5{QpoFJLj3s<(v*Xb#;8{INY(I!ybn&_OI-x z*n8XUv+HjA*7kR_n;*3CHNG>BG3Gby_-{$~|Bn~(v-l8WMbCFgpxO*PkI)J6#PFRu zz(VN23R_qog4;yag!#wZUdG_6WbRavm1k;zDhpXt+ zR7Q_;2=^zjm7<<2y%KEU=vNu}Z$D0LRq%@W-nOIRev32a6lxb}sX#OtNu3b3^MntW zXGysf9}8N{g`}*mM1~cMHI$-HomOe0g?HSPUgzV69B#1Nes}Qk`zK20s&0(7l(!pN z0}^B;Q;6ji{eHw%xP4fpDE1(ljRk8+ToZu9#Ptn=2iEj|`ZEN#8oX4Dkhs2?oA@rN z_-Rx0qg`iw8}bDC*HV^5tq|qo#j8OFB<{-oJc>(8Fmp6BP0JKY z35Lo?NOpL%lp;MKdfB`D+n^@j-TzD*^F33Ow;$I>Hz?9G&{BpLh58r}zu{{tgVlHm z6R<>=mPcTy$v;|G4@UtMcV%M)CYzHr&Ch9TzPtffCfHO7S{%FMV%ZHQi#PG`z_cRR z3Ah}BTSZSo^nl_X94*tZaC!JYD-z7)0)SgYGi6L&pb12SVUg+C-7aQSUYxhnnm4^$ z9l94)ZA0F`P)lh#Hv-Pm5&0Qpf|ib~0m&~&CYp{f0?0a9ff^N@D^3ALnn<__{1rUe zwrO14kTp(mErv91F)MRY)EkG$szDa$RbwhLR~)T{CpG|rP^)688poy8c>0AgH2@tA zh>F1(vi4LY1)55)8St!EMxT@bf;=pY4~I^jw_rtm$gkc}YFj66@$Br1^M3kx79O#Lz$;kp*uFaG<&h414_kMYH4W%g$Ll z>H9~+$9HcVwJPwUZXXMjs&o6O3I?sFYL1{6CnCte!cZq7gdCT&s=rYH!!d*6GElS0 z-Do?zLzl~R2ro3T_OzX8v$xK_`^nz|eQHQ96F~;F8e*g=`>I4|RaT;wo^BT5BL$iO zLO|D%s7uYf!^nuveTN*F@Yk>|X?^RA`tWT1-fq)&ObD@fkv-ODO7on#`$BRV$=`I7 z8`U=;(1@8KlrjdRqCjyuaTeA2=N&q)v*(w!EdC!twN<#O8={Ndi#t=1pSXCJ?Euh#8rBZDnauLg}-1%wFpIV~Z87QrHd zZDJWI33La0_&-D?(2k z2V3b?p{M~Ym52iz#9CPu)N-bOM5Rk`d|V>cdo&Tp3^zyfE>6|dUOFMheqjC%Q6)=2fAS7t|mB@eZL zaJf5JD68~J7)_PYHMp<|@fyro7;R;kAPhnZBc#(G=L$RA%0gxTQ~B=}H#ggPJ!?@a zeVa$GTW@nuU4Oi%B{xaVaaN@&k4%5$NkPn8#ipSC5iBkOA9dVVi4K!(FbNZ`3PE8^ z?Ce2H-LK#8=k0uW?_ar{O00@{Rjf#mB^SLY8Uu!xKoB0Z=di{&{~}+nhCpH{B55om zGNmAgjwcP26Ve!LA_`{M3DN?ydQic&TYMgM-E8CCeKm4#w$Iu`0wxq8Rn4}ZPyI6U3bZSlat zjcjMNeSfudkR?04E!ODg`k8g=RSbq-QIx?M8o1m-QDJR43MlCHFqKLVfD*?C@GWz; zN*l|K+k5M1`B9;zdVPBRxOsa1Kub0X&e$Wf=LP8 zc@hhdE{JWArK3u&@d5`g^9nZczQ}_P_c`-r3pYTkC&8vbi?gdc&9^s>fM~O{drnUMvotx zOddg&%=A=jwxC{!oG6B4joMNfFMg?Zmb%Wm%^PJ1c(PCl0#XsVM6?`*97SgbxkfAgb~sV1p^MZ#KvQ< zpb$#OLeLjFE%5MUV4{i3qc{kLA0xQbE%#|9po2UOPle;mom7H;>%rWd0-zX*aSU?O zHKz)(1}BCTiTBUPoF}=gnrw_4f+3`Y26w?0hN>rsF@g|03ISa%NFxrUi4-{|;MD}f z%W`<~5;p2`#2=@aPuGl@y{!cGMR%Ax8&&M5Gb=c5K*YEM3UA=&oLv}?R@V$8Tgd}{ zkoZ&I7qS-KE_6Go)L48R3Vsi8;o`KC8#;uokQUSb^KuSpZCGVFM_ zT>1+}(M(?w2!Rm^;P&mAb(j>j;?&v|3Zuhq8EXAqCimaa~mF zWJr#MBn-!fp;%Nh;5L5l1MPQbXK-Wg<& zp_B%R{suZ5MHnKA$e_fpt$65{Z7rLOX^=$N*QHq#MkxlP{{%yg1ZS3uNk4#eFW|Rm z5QJgrWxU72YF!i`MU6<5YAlde+yhRl9=Z$VToHIzqXlRHpxihE_64JVd0r_`h;jsm zF5%EIMg*h=2LNRvsxHB35I75v5j&k~EQmy;+lL#s-E--MCa1+SepF`xBkxQxkzq_) zAVY8~P$DG=4T35>Q<)_{GCXL(#SknsdqGw?K+c5ohTV%&Fxmn%s;AL73FX)g3Q3>4 zzv=K9&b|Bf_Wnygl_$I#8`=Ns_9*IS8DXvr;?BSsOr|r!)tx|UFqQUc#tQn;U{Gv& zDwEO>F2kPr%>U94tB5{$%LMJIbW&m{l+0MS0M%t|Nw^AS%lW!EpV*k7V}f0P6e<`? zmuRf8R$D}VkyO?YGcCD_LY0zD0R-wIWmI`F+ya~tpr2?H<^?13WHyjG^);(72SVMi z5RRof8<~p3L8M)fAE+9K_W>x0Nh>O1v|bonD5em07|$2vRMTK?#tl#Ux>))slMI`XpRPcTPU%}5UEBgg;OqID&`b! zmz%$m{gL77shp~zVfNquzAnqw;4LBm)>KM?Gm>ICUsC!Q8g0PaW!KH~alt^N`=-jf z(T%!xyvc&lHlY4L`1Z)86z0I0P+^vC0u(EF%Z7p%pr+4T0|aF{1woN}KOnGoRLH z-5_YyuwD;CPLLMk$q_8zZWOm429gC%f@uzwwGOIxxRn7V6iqf{(miKb*F}fyysfBm zq(oWN@v#n_(hc%q&=sOo0sBdj>V%|;N+?bV>O00FAy}{Sdh;XYMo*oZ+GhpBQ2a$q ztqQ^j2kBYh^e(swItp=@ z@?r@9CX|f&QLt@O4F^{L>n;IVA`yWr0^dRYyCV7|)4o-_S4G;ezE8oC6}tv!gz|;D zjeb3-x|LN^s8p#=5j6PdCli|zXOf*Aoo|Ao{u$O1l3F&}@T!;vMWMWT3@o1x)=DJIu2DvUlDjxanl%7r^9 zb+@z<4M<>(pe%DrOq+GyJYmV6aK|54z5P-j4@>s7z(D1MY#1h305POr0rX2jBH$uy z7DjbMcnB;RsjeW8LFj?4lLV`z!d0Rfj9EDK;HFi>p4_UL|8?6H>+i2BH=(DW1s*!T zkK_%oJ3^OaEC{xc3_XLZs+S6O1>|qUqiT>#BvD_KavA3r*KCJp&75oL>+twIJX4|LK{8+xF9i7(tRhYWEtPIL5kDxBh%lBw zje!8rk&6tvvdCj)i%pY)UF$!YJiF;%TU}@V=wq_LUP1$p^E0M00^?#9s5BsHfE5ib z_{mZ0ay|knDLltP2fK`U@)R2pE7kGBKNUL_%DL(G@rRSM%nkh8 z{>RQE0Tvj(!kF)+q9%c{;J~O&S8`2!n;KPM5-G=5Bn&I+8iJG-lm${mizS=bVd|=+ ztg%bC4{Oo>vY?0Mykl#Ab>1#r|;g_Woyr z?b8#dIyLtyoPER2zXC1rpqu5mE zAJ(XnMM}9%6W0%-A_(L^VeH_Fus-m(GJ{c3(x~Xzra*Jo*E#L_zn$=~;FIywBcn@S z{uXFKPa6;QqCu2s%q1}m%5>pDiDSW=**`i31axe2!kH8WO|dbVb_W~{kt7BR!=Xnz z{2iCAct%8#-q$`>i9S~)|J}$~b6*RDOfab%hmD7ufrtweuiDI&L@CL)pvgei94kRu zH&o6C35(Pi#m6yM-S*0?Eq$I}xb@G4QtSTQeb}|~^Rrz;Er6EFVxSjF=09y3)QqT| z3N#IRIw^4KMtr~`IFHJ_sva}u#xSx4e*~e3#EU&TFWS6!jNfM8UYEc1**LOYi@5tC z7R-zynJ@%PGz1)5%7kWZhFYlqm%uOfJ~&ONz=7KfJ4*C@N?H+r;;MT44DR88;h*z$(;%vp#T%`2wYNN=mJxfzdY<7*kn2U zp!S`^MaMpmUHd%5(v)5uZjSZ%=y;Qd!M;ISCyG#Tc06z|n5?J{e^~+*tV-I|$T3J> zga}{AP^e{~R>`CiT^4`txhA%I(D4V0=4LN6tXr4`Go;ExGK|Ah7V}5~V~H%Y2|xy3 z71Af9C4x3;o;!Le*vKjnje6%OTj#Anl}7)>J(HF-c(*6y@#JHT94gdWX+LJ_umTYl z9wenJ-&(mrRiUuDR>u#t2T?aR0}MFJ+EO5AC7BC;4)g%L_&~%;fu_m5>b#ou)qDP- zsoQeatKBW3wcC-55KAL^hpO;KstYPb;rZ68^V(myUhzpBBdVSZg)ge{b4ornP>Nc8 zOFysvJnzNwo&Fq^W9PEgzkNP>q;8OaE3_GJiyz~itx%d(b_zK7b$d6 z1U+%SnoEvkXld0MlRJ-`c<7$Xo3EwD*tD9{q>v@d(twrh?DJgszrA8|)f?VkGjK2MFQNHV0 z@1E>Z;OeeVyH_|>zvE}AM}cAppce2bu-Q}#3CAO9&mzabYr%Hrwrk~_uyZIMg0({` zgIz95%-LfmhQkOP?QvJ~Rhvq}#G?Ez?)i$LSr#1=8 z9VCNk=_y(%3YKE%i0knni&&XJdEv~|vZ%w(xe z=0`V(3a$(=2s%7W7{#WBAq^qK13wPXft)KrXQ9XweoDV;+7pw1evkE7ZwfP!Gt9ps$vdKXpQ#JqK7&=42-kXup3$r<#^Ra z1j7Nz+jvOTsV+ohI27K>wi3IhWLgzxQK3^-eJN!SC{l$y*NC%$PG8giNFQ(Gzc#M? z?GY~%JFX9~RHs7()Q}Ep6haFuHm^9+fG`|K&A1l{EFcWjWvW{N;UlO83ICu@3cwtR zF&kzi8cLKec7Ir(BkLah7PmdAZ1d|umTF{t$U$HSDXdo$iS)VW4m#ol5T0eC9SWa} zV3zwuSeH^oc8K~TO-&aZ8nM`r((%#594UEE-6+4Xy#eH}s^ns+xC3{|J;pfdFbB+^ zHo;{89fsSE0V4-CMvjc)yFhdU0Kbf+1$zwMOa;LnXRkVMvYcA7B5RvMFFFjpbm&8w zY+;rvWaTtStx4ZVw6FmfVP{={={=b>{|2`f^J^FzEKheFI+YYx*u79}>cP-?y+ohfJZRY{b z9!}{_bscXxra5MIusF1_e`!C`z8LubU2WgljW5R$F} zelc(W2BEF_AL_QeJCzF&UaX=GBHJ@j?GY~v8(Et$onON-P>4Kf-@sQ`2%Jwf^lzF)$a?=-2~A6pE5os!_ow3dFD_u+h+!Qj}xK7FkV5{wo7K zstzuVsnxD#^z_|ZyL|r!D?B<@Jsy!3-uqQtKo%5q0AYe)>_&}Z0;t8V0QD)f#gdNQ z2lihvVOFPQUDmglvbl7zLg4|HezXPjG>-tnQ&BUc#!@*$R=W%aJ#t1)R~7F>H4<_{ zj6DFT0a1IW)`gzfXT6+0>G!m8dCs@-sJU|VDPKzx!G(gdu^SdPrc%5hYb@nJCf>tG zb0)4h4m9PJ3hP8t0I4}T>!DZSMcWmAn>(gzp@ZXFt!k5frpb~>D=&Z}4k_?E{XoJ_ z)@YZyaaBZB-i$a93@r=IOrc;;|LA^T$>IF(q8nD&^84I8$4K1q)qp1v2%@Pc7Po_lhG4ajtp|TgFE-R1 z?p5;P<{X)qeeKz!o2$Pij!dNr!9*xiG#+#pnC|#YiiBxn31s@kpA{xl5#X%}@}ORf zj6S&=btrT`YNSJ4|MefX7YQGmJTur5OUE5pGZ_yhR3Pnrtg@xLmJsK`87uu}MOM?2 zVN%PLYNQk;7c|{kv%>Ui;mdDyncU6yL4lKtmsa(&#LzKR8q*?1f}t0vjSIXejmK2~ zgT+Sps-yy0z?B3&A&*amW1SL?I6Q4w*%jLdZ+q+-x66O^!HRBP zB!$Xx6_dM2P_7_?GIDsW5QJsN>qn5r1K_bJWP!+*S6Npy;_dhN{bSB#ulBs^?U-#3 zHyVR1QRIjPO;UM}pq5y777-KMrRbxqO`sa*fE;3*V?%S@QnU+Deu}DjFUN$sy{m;E zZ?LXZ^YPgp9f^BhHrNtLYl0LJWjs`eSSVVFLm~16os*lEpd-xCqa?&we?(zcRS-Ey z$f5X0!G_GY??}%#?~``~uDNmj^|Ocm$v2`NMpz&mUD?O#g5ajmeXl!&aCJCFG~<%3 z;C40zfvZ|NJSLD8OmD&JfChj=OGgZD6glUjVJ{Z8bO^8hV%)H}J~PjbYF}|_N?%Jj zL0!_5gzW+v4hNr9N~J1{2-D4KI{W=oMYn31W?qRcf{9h{2^@{2l1>C79xJA$GECLNWF;a&%?3{j zk_^TDT(5yQg8`3ZHn2bIDR4`t{R?9w!{pK-S&mn;e;Jj#Rl}K^9sDdIbcDeF!rj5d zQh1D+GE{^klSnLeDm;qpC8!asOd)?;Y$e`p2uDvacrnql=F}S2_3NU6m9G!1GHtV6 zw+mlWI{I0H=_k0b0{JkI;Sm~1xfjNYxWy!)fc2m(mQg2AdHCjGMR9WQqsZU+O<;wg z1#u&&+}BN|R{7P-J@LY*H617RI1p$tQJ4v99*u)1i%x}U{11_IV1lF8z)%b%rB{)J z<13C?QA+q(%ST>l*YRq6yP;u!OmVs6cO<>c?7AVAAacnh&L}NIptf-$qZt^glSz%b z!k*_gCe62Fl04!w+!PKjfH=8BpWgkCd`>#JYWd&6bz{w!+HD%yvty7Ykc@)$9O&Ur za?32;i-!azCx1Y6HJN3jlX^b5qFaK}F8Q5eA#U;SnmBKm{%57EgR-Cf=wa7+N2n!$ zU|fYUBYc)A5?BvTGO;4WnNuN02vygC4Eu*r0S~(aE$!&6NeZADHcEU$^o>7_yxzfX z*)50g0?jU69R4xi!pL4>!4`iG0Y!HkM-w+Ms*lurrFlWXq{9$S15j0Vhy)cVa3Rb? z=u6lW)^^&4K}F;4%x%4Mm2Kw{-R`%%op97IGRWdbua5F@SPm`@LtYtM7%z;*SP_9h zV{QznSpp%)@ibHvCSz!+YK?STy{>&SOi0DlX}13ARA` ztCSBIq}2Z@g=-4KV0~EEQoTc1Gpc(DZlN0t7|)4_4nyTsW3xbSa0W*GIV}3vBYV$U zf1Eiyu~>2Y_Fe_523z{j56RI~G+)Yvgg`5}unuIc1U7OIczR?&r1CB-*`HoFI5>6c z+%vJa=LTMRmA}f0Z2ybBuZ*r@+q$fZdw>AJ34{g?Kk*_>;x-dkPQA6Or?8a#%QWtgdg zH;<2lg2)7auedlXVL}^aD*{nw1ol^1gMAkrV?`d z=S+ukH4hc;RLXUB@PJ}-YT5OFJTr7P*mK?()y1JK$*!UhLLmpuEW%DOO7zt84FfG> zsT4MfJwb&d+T?`OP)i#N9%nDmH|O)tGf>GMxED9Zq8Yq4ml+`6?8o7IM}h2!%c@#4y92C7;az5?y)`}T!4DEKWvxTwzYM!*@(`YGCmDeCHemzkQy&zOP{3&ag!@}-)lhf)dz2w+CIsANDN31mJ|CRUIeN}McO zfwKwwA!G0KDn|jVd*e~D;}uI;njNL77xGZ-JcR|oH9(XTsD6eB9lsqNOiEi$jtd7{ zdS+L(W9X)a<|Td(VVJ7y!`Pz~kAql5L;Ta9oRgb!1H+MqaO0h>Z~e0iIWq^W&D=Yj zNab%}L1q0yAv+7OOd415pomaCL}o*XB3Nx|Vbop+w}CTKuO5CMk4K`eN}j=hc@P+e zh9VS4J&M8|Wb!nWO2mi3_#|}+R0$X@3kO;e6RO>G2KB;6?yH77nYDt{aA$Z(ln}O%&liMngCW%~`C?YU;qq_1`*-L>?cs8;^s#Y(x zFoxmvB1wR4-7R=uN#ujS837vBpKu)!64FOv{~@F$Q8dI+9K)rhc_u{O1K> zC*siK1z@K$4u<>$2MR%X1lc`xLxZ7)$}ROOD=rU3xe9F3v*iZpn?*ezB?gG2NX3{U zM*|d>1wy(`LVhd>%tsRtIW{@BG=!1HS)C?8K%Ld%p;p5cSqT6+r~s#&8Dfb4z+&KA zD6kwMHubQ{YzJ+Nh}p=Jaq}y<;G``n)C|cRltj21l4DHtV&bhT1aYW7I14s+hSX<)AEK9i&@h(7nB3Vvz0-#z& z4b@r-p;dVeKNo~N)LJp42(U0-IR_~D7XC4fN>CG7DRreGy(Nhw6(g&zZ4|Q!#%9)> zvFG5(;BRFFCxAR{Qu?1g47xs^Q8g?ROw39P*an2#a!{c1d6j6u@(}_=#BdOw@j=oV z)~yc%t#^|>A|Qy;ZZNVUf;V}%YzU$CbinZCn6iqHmi`Ew$SEQ3Q8#S%j^21KciV~-Qb zv$F)zmluGz0l%0O%oLkNd*AKPiDkh`wuNdc7xp0KpA<18{HE9s#$S>&>`|V6ATw&E zK|9|5FE=I7o>a)Bh%C+w6$lQgD7~ZMWoQ`z<$~av^}S2q=58WnUIG%h`t&!3ZxXimn82mzag zHI)ZKyo%Hedw?3TRoew=l28aw))XZqvR4V(wEduoD8djVZYh#w`tfw8V0vYQ zfNV{L3aLH}B?0ZV4#Y2ac}5{G86+4mOGp=m*}0082ap#vAL$MtPAk*0RQ-=m_QS$| z&LRsN{GlLnA0Kp}iy@I* z*P(Eh+)Q9NPMkrw<9f3aSR#TDemQM*fErp_EJ(x&ood4G>WP$ zENpU;JM+QWaEK?t_LqqmaYUI%2m*?bY!I~*DDgD5vw|wPRTg!T(5`#&rn9TZqtA;P zH%Z`1-zN?-F}hSM3HR>PP7DmjzQ7SwTDsCGpj*SJ`N+)>h+>UZtp>-CP%SJs6=+n6 zS!MXD3undU)b60~{#J!C;I@~>m0F#O^#!w!abtd1R5CH>#-6eet zq<@Ol;>L?AS^hZK|C7v=xvmIJ;cN8*v;J5#V}Y0jifvC;Px^LLUWkZ<-2q%Kip=C? zWU7%dtC&I|JyRXS&0;YbUz7qhiGNY)ne8TU3nt)Tgi1J9E18-kJrxQS2P}>1JVeBQ zzoGo!S8t@bE^}>-34p87|8M8KO>bmZ**VJD&FO$svQvJ?V~%l-c^r;7bl2xNc-ddH zA7Ed~?xEcn+h?{DZL8bdML$3_=mE~Q_P6?AHPgyZe{K21a*CzD#Y>B67XJU?6#W0; z*YWGdsU$rHz#p(Z$PK_rqu`G%J4(`cO9H78fk(}QQPZ~2-vejCldxF7S#MmEU$?rW zBIrpL#FcEJM1zY!=_U`367g#lwQ`3p6SNV`5_GKlpJk7zZb)n_D(a&O3O*J+nK4vG zLEN~)?it)3sENnGilBXkML5W=!Z9II3Dy1fb7-$_${iYpmlvFXVpupG?$AJp!D$z4 zJOf$q+^nJ@+R;dhsqJ64-oW9I@thR18kGpu04$6nm^Tu?QM+0eh`T||X4;r|0NxOM^O$keUSp2Qy@+ergg6$6Sn+u~c4dad@p)8B6iuEw0q z82(vWputHC?1_+y4>#Nv$(oTv3Mb1*xkNI82^eqft}S~=<2hM$(M-AG&UG;jC|g6@ zgTpPNcB&^dERBdYDtFj%E|(|Fw7z7B_Ld1VDNi<1I2r$MOLwLw@6zQfQ z3~*`K+)YgEA8khmY^;37;^MoAJBOe_rEDF<=i;UJ&Xg+>FXuleWV8?~{+P11e+YA~@W- zssJFZBBOz=$R^PU4*8~}dXK08IXFVm?xA`v_kX<{B3vRjBZQZuVy^q)4a41qfWAux zR@xA{$M@p5!zNVW8ibL0&F20_<0nIILRBA(3THHGm?$K&QV^XAyf0QI$IVI3UWB9+ zEfGIMq&E~$rA=G&uLZzoQN^S*TQM^OdvW?C1VK4IFkBeTz_o5;M}Tw*bBJUc`LJ%k zm(wfr1IwgZtT$p)coLS=L(ed_BVG;5YQl7p?N+1+tSZ@7l9yIF7OKA}m$=pa+e%@v zv5)X}bnV?K_QRJG619Ml?1 zid{pYmTJObgjE${MLJmWa+C$xm5GWg*O-~ur@j66&BFN&3keKf8;6s;6}94%MMbq3 z=|33{zL}Ck7X~cC&m@^TX@0UlP%^g%8Z%#Dkm&V?MTWv68GH|8{iFd^#^i;-iiBwo zv;6A}!B!)iWH1GZlfeiTOMUh)#Q(_OGFb!%hj&Mw` zSQG)YJ&}LgOHPsms3z40CF_$w%gaq}l%Yow^NB!>cyp5gboj8_h`y$m>cff{>UCoy z_a>gMxI9%bz>k*J9Tk@tSqzn&Fj)?j03-;YdoW%>eSb;|Of$LtZ8r?O2GIfl7DN6_ z6-W$?dLCN@ODcR%pyxbtOv;Ot)FG)%>{Uho`&~)NNgzdG;}|?27CB`3F@!l}&LWcn zjnocCp1JpcjZj%Hyz=i=SeCep$3p(9SsXEI$(euYtJF(~al{m^q9s z%Wx#p-Z)}oi@rM@7OIP>R&8=QOng*vI5=AX4dS###S9;A?!Q*=0Q3~hlIN1EiqWya zqKH9c2}3u?*f{1|f;Qob;TqW;fE0qAOt0Dq4yFFh+`Ar;A-F5d2AvTDWf6-#7OnIbdYYa`+wz}C!Glc&i8KX>j}#n)xU8sbiou8-O3;PqrpUw+ zL&2Hq+#)_sfhp1;8Z&=Bs8XpVS?*n3QocY@ZE#83g|B;fg_xi(jm{EST^wk_XJsZS zql}2)k$a-fNMFPH!LSc-95DBWM1jg7p=w6ng&WRJTz`D;pZguoAJqaIKmD?_<-~Ds zoI*@cnf3xbU+x8b8x$pM2V@!erG~@*#&52*l9i?m-40ay; zIfdA*Y(B)!UNu481yvsy>D0no90W(hnt`@?NdgOe=su< z3sd(CQJ0Z$Aa2Ghz?=R4=((4k{?yXvz z>q*JtO`3KJD#J37(Ix}MT~MP%M1P;0s;6r z9?>IH4FwZthL(WCd4H|+b+g)#IjZ=S3(2tqzK-eNF;kcc($Yk5W;snM>w+;wgal5> z1PDNefM2M(*%S*&b@nomUP0MLCGqLxkIiE%xmXOZA4@K1oLNSn;PW`Azg?B@DSX|LiQ zJ9IKZSQ^}X0b+reMo0Dl-(Gsb7{Nq>LkVPXd@lk9n9DA5uiO!j(||5CICC^d_i@P` zwPNJXe5(@X`~Q0Hu)N%xn*p6oIf+9}f($_^y|8ED7Zq+T@{IUnU`iBAAPHPp`p(Q`TU%oeclqgZ?i&7Iq2|XOCb(W%n+C)n(7L7 zPr)Te1UTK;LJX|qHB%;F?%E)NjnX(0A-ew8FMPVWsiL=C1AX9c! z6Cm-_llQCs#FJT>I^>p)XBV#cYqVG&kl>af{IsiaiNZYjgaW1S>I0p5nLcq$%{EJppj zGdlRxFFrEQ)G{#}_LS(nzR}2)p(b}ahvqy8MQ=Q-usTId4^b)wIOdbB)cI7bMv|aH z-DRwsI=EtX#XMf_wIV6Cb9OalA$qZ@^%GCa zyi&qIfb0p4zhls%(*HEfV^YWBL7_&gvnsfDK@wSCyLo2TLVETbvHIX@Z~_8!A?K(EYA9D zXTVJRMTK4t4KlgWf(2oZ0A$AIr41{L7gUBwGtW?yQ`<D#dP2OF-K zQ!)F1{WE8KPH=i>-6^&AwhzIkOk`BR2tY?2=`B;I7yKcrDylVBDQ+>x2sNpYNE9a2 za5U^0;?<)rOU+uU5_k6Pi0nRt)1Qv8eDb=P?G&e&Q<-;!nKIIvnVYeprB3t%96xec zQAiXh>QHrdW_&1wnHfAFy0XTP(GQ|x=H9h(tvXn(f3cnv0FxEP1L(oo-Pz>13BQ9+7IccR$l)U-tbWT4(HZ++ngQs zv-kZFlPi4*`ke|iIg^nw`ajgE5XmUw`WfUTqgp33>!7@X zS4ZoK79i9WiN*oOGfJ~1Eh^p_*12c&wGtNX;=0s+6?MJrsW6ihZ2(R@6*Cgq4W$-{ zu}DONrGJn#3dn*|1+{}pvY?ngs`O4&Bm{BD2m?>etTX@9A+J@H%b$!}v^1)Jk!`zr zM4KGBkmkxik?6f6;J4ivSp z4v*VeDIn-oF z4uBwX$OypCs{Ll}DN*w1Mt2_9H_`J2!<>>t-Drp_7{FpuG*tu=Ll17ho;E$3#jtz* zS9agM`FqVOqn`ARGT9QRTov$C_<$F{lx|AeF^B`J)+00ob|oBvvItNx?rs*JlbQ#m zjri#*fLhlD_|VaYYXN8-@QIAtz=8U&6WB!sWptQ4UQ_8YYU)z&55 zf%gmTulIcVwgC^m+B~s&vEM%VQqulNlLY{&GU!!A&^B8h>J z7)>NrEs07tK-{oFx~3UFXY}3UNBr~5t88qnTb0!Q!zV?KY7%bJDXUSW5T=mAX`u-Z za4jep_?4<^s_02(yi-u3Aksb+=Pd<&SSL`UoRjQE31RbZGQ)vK$Fai24gC@8W5GVrWtCiJ=BZM#(Pg9r#)-@D z)U&tS%WpH&KCHj;tz5PNJ9-|jc{R+$8dOTI8>l?x7~m#w8i()^4n&z|BP=jd7}l&% zEKxwmppA<%C#+!GzE{$p_XxgQf8X>c4c4bVEYrB%;UE(j*vMrF9yBJqNlAoqFEPu) zmonJmQW}HI03L`i4p?y{w+K)qiWr+K+t`W;cTWVY8l7SA)o=a$T*HTind*>5HjgUD zE`d!}!2qhdAn7Tatbzi`p+T%h(Hhw%H6bzP0T2t$LXzi^??Mb>)0?P`MX6uON$_%l?NkT_veV@cQ@DqEZ+6b&iz0L!uO=e zpMneaf^2xP(mWxa%GfAbq41(=4GMx%l|C?6q*G5j)9}Q;=F304JyNaL>1(gjJx5pb z2{A!p3h6j=NMXu*;Wbh{T4_wGx#9}i5{{^_2qEzT!68()6DjKDDG6vKqo^*&wqAjy z?_QahxH(Uk$*uDDdcAVum?)DU1sB*AWLK2wP$MfTzr->tT1yQ1OZX9KoH3NOiSLi~ zL&XcH%wR0jBiQ+`4ytnMu^#(&Rmr>$hadO-yeA>T1eGaYq~+4ufQmRWQ#ErFFf#n1 z(lJqdUs0=(z*S+R${Wk1J(xiPxk~S>zV2-8^oZ(KtC~z`=N7lersJ}%Ca6v2Ms`l~ zbO0^@BH;3ba(^f{B4?JhMR-ef8JX@P9*P1)xMGG@7-h=0DtSv_jcOb1hF?p$`0YaJ z-%T8T2brqUc_BziF}AURn7yJPWj1pL1curbx8n-K2MbMuQaNhpRv3b6s~>)~Hpbhj zYKz(925%ewMQ`-H;jSPPbf)r^k%|tn6tJDBcBRC#&2pB4h{?!N#i$8G@e~yjl0uC^ z!i4uk`}@KE(ambESvKz3^62crt-9pMF#FY@SW{&T2#5L?NjMlP#?l=^cYqnePGaCd z*-az(z((i(EaCkN-eJz-q}0}N%d_`23s=f}C-2Klr*eI~ay@6t(hyT6-rHQq0vgB= zf)IsNB}RqPs%L?iq%fDL*%0Fv%myqt4yLs3;5l)Qnyi}{YgO`Tmw`_Q*K3gL%=zcT zRz-%IAT$Lb6EU+!04b;;5>^;7Zbae!HM}dPz5rdN-hCeOB{3}GCv>721;$Brvv)nQ z@YaV>+4qM9EiW9gyiD_mP*Vl^9^;27v&rX>(k(LuG$ou0^#YQ zckvskY>3nY2BORAb+acO3=J*!X4Cq8(^}mfaH{so5K}pNH9eGPf}|hq2o$l9aZ|J` zOgB|nGqi9iyfmObPCE>7IIvm+WBS%U;pfxm)ntp4mbF@KpLjdnzH{p!Q(4~D!bc># zCbDzf{~(5x>Y?ExXQ+@)JUn`ORJ%B07$(Rl$j&h_$DUtXeV=7GleuuzE&W&cH~&+e zO=T#Ykq&M`zNrXK)B;MA2s%T!566S%5^j+f!Ce--5elENpxicGbMpCTxt71(^0VBr zQXAS$bXwVI)c!D2Y0l1(#$g4-yGQ}a+}M*C%98(FY$uRRC^tqUk1BntC`+uKO{atv z33e5hhnR8}a$EGQkK?5ZMMF%b=&f+JN-YTK8-<&SbLt{K|Qmfj%jc=M1w(zc^dpZkp>Soz&p{8>)4lUl0Kf&Z{HK=MCLt7ozjC#yS6@h0~!Yosxnd0tB zT8TIs2tL^@imUzr#ERT4F=H^{E^*1HulR?!2Dk2THkZ%6?4L5O?@{;XrXW*MI-R06 z!%Q*m-{tNrBL+7_D7YmYHFIjt6Zu}Vx`&u;!iBtQf86-7#rB0QJ;L$@p4sfCz3#Cq z+~h+q$SC??(NGytkv-E=kqN`K@^l*`_P}4pAE`bT6_=oBpB$M0%gT(A1qEL8dpIsG zyI+e1uQy&<_V(+pyJLb)MJVf#dY^#(+>0l3z6BS8|KyHR)iEq%oWxhFd3iuwV+jJH zaVS;mzw0pXPWrILTSmIvtX;dtkU@!#p{ByLv5g+v2pN{3LUVXtymjntBHso~$WqQ)H z{Qb7?HY{G$*}>|AHcBsTsW;It>f0?2TKsU_Yn#!olAV>~630=F-5lFGR&~tpXzTFO z;hb$hhb<0s9ELbVI5c)BWtZ2%-TtTjJ^Lf}YwV}k_qMri-^JeFW~6-)dsn-UcGv9o z*e$VGVKLrjk41__dyDE8UKWn}T)RzURAtwXIF=ohVB^=(+9 zzyFuRvW#WhuXoywFg34ter~Z!g^O>`yKc_!vwD_zJ-cNYi?*B6`%Wy^+I`Tgz30n4 zhh$mfQciV;@TZ;+PT88bQ?HIyF0YzvGq8WX+?(z63}yJz+rAIl&3Eck=wZDz?e4bUnyJRLgrJ(YTQ|~OS6T3PZRs}VtlLBF_Q5Ghqem2r zDRH{shn9=@Q=4`=l$;#Z^Rxd~-=E$Ei>;mWV~BR^DqU)qq3VN|rqcU&E^yj4=55~` zpHtti$A=;-j{?N1RC9OM~JVU<}aH-Vj>(r~IN))bM zmY?Ej2J0-bQ>Hb&+Vp@;PVav6r(a0bPWIwUPd3!fFxR&GUc2xc_Uk4MeLl5(9&J`j zxwO8-i>tRMTLfM&^mWyl%!8XeNYdWV;;(u%=t1c*pI$F_J!x67=(@z~i5{if_m?Zp z$N9CapJUv_lo9hM&u_ftL78{`hRKzz^XB$^9(-YZmf0PL-L$_FQYde?B>tiYubt}F z>L1Xcrp@-BHGDE`uAU`H+x3Yr-RscVe&wEf%T4=Nz3}$(dod^3LYw5um+s!UyT`|` z%bHI0C$6AAL`Yd*nTQsx!o%R~5|DAuQFKm}h zAAX{6$wG;n1F~#=*2`%^8>@wS$4 zqac$Di&xO|5>)z%0ewm)!>9uZ; z-oWqc#g9+>)_e7KVXlG~`9qm*`t-PbwPBpTyO#_F|B@TYbb^J_Hg(4vHMheA8|7(cK2N>iHd7r>vgbF0|6NWiftJ{8yQ ztPupZd>*v77ZO6Zz8JYEOq(UvhWmfE)VU z(Vu45suIy&ufVUQ?V)KW^LB4`+Iz4|=_)VXDtAw+T0*Z-oImxnOY7;^avyGSu&l@1 zERkvXZMF8gPa%wt zmfF45t8#kbN_^>_Xa0UsISQm4+4F5k@i)Ws>^S~O_uR;rc0Kvg`FI&8+iTg5_Z;G$ zr&P>^!Mf*kzI3d@(DbL_$B!&H=09s-=&06DZ8qw8XY-|gJ%17g9FAY!_Q~;hg0+)AsA`E9Fwh+LeZtcheTRXBbnV+LBh8 zn>W_o2k{NB{eC#A`Gz{{jtm^v`qY*&3BA+T=~)}mhH87hd;Hh$+w(u2_Oqr%rVBZW z*Us{#{1Lu#ZpKyb#^3k$JWyzDi~ZqO%4eC}IPe?a?(AEwjC=VHWCARH?RSRzZp0!K+B8LA|#+9AlJ}R8N;8K}R-lgWW@QE0!y=={2 zb@IK}vO;c-KAZ1Oy;`SE%Jm)h)9aScW?4pWd-8dkwDRqaFPxU8&cOP+iY>6N)Y6kb za^hI){^yS5dYB<+RqHLY&pFoJoXdiZPumloE&LB$&ESx!c~b4(7tYwnH-4av$jBc$ z_UXaJ4tpC)jjP$YmQp_p#KxqgUnWv?kh&S*MFxJrLqFoP zhUY7Gt%=9uw==gE9N5(RbEZpj(9p;REuDT| z@$6rD^5yjhUo9GaP#b(oK2%`Ws45oUGfv;;HQ>U?OMM2da&99L#39HntvcWkvh}y) z`@ZfLQya%wYr~H5;~iKtD(3XgteM_rEm{6WqnV9!c^%hwp65&Z`!v$^YnYyyw8}-gS~E-)S~$r_DyRVm^8GU+uJJbBZ_yn zdD3%}Wy5KFw>|Uy>&@xf$b^N)U{U-hz@6n?|_2TCS{w{k?t~4%QwQB*-tsBEetXX%&J#30w zo3Jc=#dSg8zNg*KwS0A<{PuHmd)%=-Ixh1Sdb)P5m9nmO+%fm@j9zn&r5Bp8=Jh5w z?a4s7RQBDf)7ks43ft?mC8AF3z;VUaYqNMCwR82E8k%kAz}?Pk-#Se`zd5?h{E^!G z!~9h)ts3og?cVEr*BjNJRPot&veCpU+4u1MoU&%AF>0Irt9Cc9j;K;Pf3QVmA8jF< zs&-CJ;Z{lZtU}? z4%;5YPC9b=Ob(xc`-7*n={o*oKkefJ{*e8&vM*w`f7rb>V13P|S#RB~TYh?A3;vM( z@C#8^3mUH&IHqCog$Ih73MEX?KCYDyV!I33Qnz3pnyk2*sJ0k6t%NFIqvW^PV3@qMuc=r8!m$ZMQEhx$#vMe^?_rAGa z4yGH2T72G~)@k*N(>*rv746!F+5u1P{ae0VSn+t#P6O7IT<5IGm606>wqJEH-L`&Y zOp$$G2Yh%^sI&3_WxO6YeII_uvPXQuf>s}LEgtt>ThoXhyzQ@7g{$P+6qZu2f3Wqj z@rQU1eob9jS_`6NR9xGMSE$AdSVN-r3@fXYa z)=uE3`|zXkiP8t$^3D9R?eyN`dzQCJN!0!+A(vjAvNlb&SW$o8oqMxlZobXaL(?|T z;ct4^`O2+6e*2GxoYyZsoKVQAdf~+#3-E_tU$Xa{qnABe!#g0{H*>q-7vH;US7-8v zUYFeX>D0cHJG9fK-d=wd+0p9k;fBxn$_u-i6V5*9Y*lS?$uXG*=k5M=+|ezJWLy90I|Z9m&ivh}e!WYfm_t@Rk| z!d8c^x>|m>oNnoBvDc!F{!t&Hd;Ev+{}59mm1dPviIft}r8g;JN`pH=go?F@QY`3B zqA12qJPNr**^o&5PH{U-XD@zT_@tw0T<3j*O5SN)`aqbe8x>*{L=L(ale$H2&rnI> z`45qCJQg;vHwvbTN2hK}&+yXUUMaMKsw1MEVanVds)>Q>Hm^y2uoX_=5(a(Be=TmUnu4}$m zYlimfGHSx0aj6mcyO`ohco>8!l>UOi(GpRh;6RY6)T}Ah;f+TTasYu^Oh*>+BB|U9 znszGAFD7~Ttpl~1?EBDY(e;ffpPr2kH^otr5xP<&?<*qED47$SM0Grw#SLtSrX#3v zuWB%rAp$Ej1w%`w?b6mYgpDB3gfhf|)hXYF>aT(ORyN4zWA2cBM@EvDehusKzGOEy} z&ufD%B1|!aqpE70yi0JCWKttS5 z13IhvfW@BV*P|AYaId+C!J6W*&%e$Yq7aq53__wVgrbs%b3Qz)) zNH2-HRm^)_6=#`W#_Vr%yC--CqyP&VISt7sS!$MR^K7wU?fb7;eijJ$J$Fb@_~SM6 z&lT)qir^iM8a;NDnxiR$wF%Ua!Yoh70fGq|zztoBWiPKrX{Yk&wNvkJjy&D5S=rAa zCkr=N+Plu^?4b(+P2q&mpkaXkbD1m5b43K?QN>u2985yxm3sA+YALz^V16)Cj?_z0 ztObfn?I5>{RGe!+|MKjaOIn7$+WKKt{!3M6fB8it^}<1=3Ck zBe!|N3V>#Zw@>r2Wi~o-L(r88)g7J+!SWpj8VQ3W2qh_dTpKlWX_fE^!)ul3bU6J+ zrsF65{qDw?pf+8$jBwBteMu1tv2R$lLp3x(E-w;xgv6k`!*sIp2_8iBgjDCaoKXm2 zkVsDE%!vm+Rqm&CyIU*x;b6z9yDFc28)OO=w7!S*u%K=ZEe+yh06KtuQ=Lu5EH4(A z#m~dMz&LJ^q`?e8wXrdjSvXWQYo4~3w%s{+^y{>^=mxh>&!3e!)D%QO1E8kvu!Y1| zh5aR zOZ73i0!>hx#y}VX8VP3;r7P}JVcwbaGbx@KBk78!C8ML%Ph$88?=UdB(4sKkoB5w= zv!|7H$+M!PV~3TumIax*(t@$*r(zAW+?>!ERVx!9FOi^Qx~5Q0m0BcsH5trTlJy^U zJz~h%c15b^x)b3(pwGSz&Sy%l4K+b#8u&bDOTvbut}DQZv;l}p2a`p-DgY-QL1cE*s#{)Uz>H7?eQgP7wYZY7y;|KJ`>%Fs9=|!) z+hMi6vrn=MF+pFNRKS@-l@y67Fkl6uKN-)*ZLtJvN{^d*YQiQ78D>ZlHbz>{nD-!h z7p1NJN;REYV(^=E&nr>&wiW-B`(ghO6WYq(s2gZ%F8(5^|zjJH8yOz@XYoS8! zhx%Xf%Xg$^we-i4CK7sq>^Ea-EH-M857FretE+mO)KqpQfoL3fgNLNrCh_C&klcAy zBk#dW!*Aa0)BRWD&)dq+sTyc;uvD0-4TYV?{S8h&1f%etS$B!#00~%3q8Wg!1!j=! z6}u11jH6&EX3`FMx@5}afPDAL_DhJ*xwh@={AscCK7^Ri`Aq}2p%^Yr%7)s8z@*sL zkt}7XNIC2^s38RuWFqpb?lg8E>S;^rInEU=R;S~uay_h@UW@M4)+X?LR}*@-b7SB( zJ2g1=|Fb6jMKABwxII0``tLWRoO4bnP+Hp?b-Akv;#Qcs&2op#DM1HWGKI8Ce}LPZ z3?dFI7xH03tjrpcJX{2A3e+o)KEGS5e|h)GHfzqT@YB~DZY=Hbyz&M6AQM#L2%l#- z*mDlo@?rdsC3>{$O_{+wK2Q_X`wgRtD0?|Ir1Gfo;$3}$n zGFtTzh$1ftxST(xVV@E=GJhMC>Fmsa#g!d~whS^grImu^;Cv||l;x9!Nk&FR!gC|^ z#QKv*Bt|W45TXF^Kk+BRJ`w5T^rYbz-Uq&({B^{%y}jJhZ*{ET*(T1^gr=Jav%sJ* z!tb%Vg6eNIv*JVr86>nwY+l4~GK(AWIUePYXefHL;iQ8RIs}LJOAaXQb#`6mc2&3e zoE-UfShQc12~ravZDNtJ76vl}zk;Z!O6ExVrG?2UUAcG??xZ#(II$@qJYu9fXhuni z9ql^V*VX&@fUe*7?CMZ&=?E1 zsitb9)d4J79-S)_l0cWd>2>1a(|q;cPIn9)-q7RGkj0C8S&;tU0E-iv>qFP$uIqrZ z_j3()t>^0Nn#tv}%T1U4z}LsRB)PN&ww@o#fG?fTJ8yBG?L62y+_{l+NoRMbpHBCj zjyRc|COP$V>f}_*sj!oa;|IrUj=LQfIgWsCU`xkJj(Hue9iBOyK`+5S4g(xQ92z*3 zaL9tH!X5jA_ABkj+o#yKxA(IzXzyhA*6uR;4CdPnwTrfEW>?-Wmz{3=$o9DHTHC3% zeQdkh*0wET>uU4S=9y6ektovIBTb!`2Z(YpV&FYKQ zO{@JDkFAzjjkQX$YG+l=%G1i;^0nnf%k7r)Er(jhST?t;V42&}!eTv?3j10FTGX-d zvB;o*(r@Vd^kw=OJyCC?SN$L0gE=fUO+WZQxcFax{GVR|DvhP(8a98F-Zkj(`!X8_ zwkXxV$RoFodZTc@+ph{72DoQZLin&jrpE$xMfrO*6T^jK2@HVEg_^Mm!Z>&j-LOZd!yrvgKoTU_1JON zKJNho>oHc9=F@yd-8}iWwW>L|clEQDL&mM#-&`B=fxoCo7rjV>Hgit+e;IrFr=!p7 zclnA3rO1^`Pt!_gu37nWX3Kza!@D2Mm9)CKFJCE`*)`MMrEfPCsdlVjowf~bo^G)@ z`6^%W>Y6(7Xr1O=P7L~fsA|!$>Zc3d?zx|@cs}Y{FGKe%XO}iz>rwupeV2NAu{KY6 z6+Aaa4ZPuAuW7SpiMBm#PP>I>yQ`fHluOPIMe^5KnN)7nt>iu#mz)l<+N@W-$M?&B zYvtA%YhL{r>RYDbgg)+3eZTrNl^>h`YNyalo4hSfWx4ZK+u>Nf#Ev%F(lh*_d_$9) zIaMxKa!oTTs?4^IuRv9)`Zq)m@AX{L63DfjGS5wCU~ z>r|+2)Wk8t)1RJ84Ajdp+MJfVa+b)k$xHof_G~dbD&@+))pcuWo1FMlxxT$C;n^p5 zr>8Hb4)O@AFllLxLE4W+{1mw^-pL!&zK2cues|pO47^&Y@qu3!bLB&s+B}YUG<;O_ z=fH2lU&4J~6{)6ee8QJ@?Fl!%^W+VZYlm{>#xQ$wO^m)Qp;QqT}s*hwvSU*H?Rs$7z)&sy5`QX3gQVjJN6l>N^TR?v zSR$l%`E&iWv3>bc_HAeTw((luTFW`ASB4G|ftwq6>p8i?pO!7yDyGUm&Y7dao>kgZ z^!}Bf$#n{D=UdRN$SF~!}V)ggT_kyg~H$2{{ zRBE4IWi9xN-2Lhnx>s*e-L0oA?dCmwv3FXI&e}|-jHJ0&$=uQPQ}Cef)nBZu-}UpY znj6n(@0jV7mNl&P=)p_cz6og2v~J%_SH}$=Hb}P)^H}F zWY-QdeJ0I0dS&IEiye=3`)A{oRXIx_R3m^Qu1&$iI<^IeJ6WeUreBDu7o{hiCsz9dNhw9C#ap!u{=*$JzRNnq2STA*l zFIj#aZ#DVs#<&;7oIdM$C%vwhZ;N)2!Lc-pZFke=*azK>&Gu2BzGmQN|K-oLV|(}k zEjIUE6EU^+JInr4vaNhSzsaSRXKioF70){xL#GCRZT-|VJI9rj#S>37t(=j+N$)>z z(wo5M8BXr`w9I$=llP8==V@Pl$tB13QSQ5D?W(fcw%_B;D_3s|sjiK82)qc>XyA_tCH?+}R*e0a@-nU@DkhXy{yL$|DDfr^t+lMbF=)ObfDN=vT4($IZ zYI)yp3vO+`KQ`Z!*!2#!ujqcMKXNtyr)<%h^-_I~1lYff9`C;^On2POANn4a*tuo7 z%iDH(EI*U9G-%=o=WVse@Re`YKffI*XPQ&_War8a?9S93^3AFwgGH%dLoITBJ94c2 z)H!4NwOG`@`usuf+VA2o`sy7}d_l(j?aoAAt5(1LrR@7Wo@!Sfs!R1=FI_t~#oICA zd!HPa_igc9q>W*3l={h|V&u)Q2gZ1R%k(N+wl`l}AIztFSLcWLdehQ zCZt)D;Pur@JGk@xK1Oee@vXe@cJZ%1QzFs|lnHq>M0-(@FMW9WVc@hV@6XdqPAp=o zTcc!7|C_oWpWxK@bGm$V%<4b!{o|l+AgrRKG2sd{=~O@Ay!qzD}C=zDY*wYhwaxR3GXepF8^! zckM0n)ly&WFMIIL3(u)-FAQ(r_4wzlHT?GKR_T1hSM6rbJv6ZFkY5p33V*s?_`vs< z#kK1ueu|fa#uqMq?)m;BU+hnM_}ADnV%CYaZ}~$no~~`(ro)Tp*P^;cHVY5_XWh(N z+LN*Tq36G<_Bc{4!@Hsbd*zHP?Va4SM8g(r%Tk{YpE~aQ(v;6Vsx^zb)yY3k-u6@V zO7r|H*7^3|D)r175H@6OiBXdx(spZSIJ8Q=w_|A8 z^xmVdFE8-mY&QKv(7D?7+I@D-srMGF9kE>hu){9jvsK<9cP83Z%%B%2!%u#%cfSd- zwc7g+>DJJ8We%5$gFEl363kcbWIKDwsf4M*$p+KkXP^ARdG4Z^hJ|>EZr>@hYsJw< z%P(aJ=ujwU#T>aN{G&}N#UHx4q(FN7tft!4tuC(1C;u+$IJa+$y>g}NhP%_-E?@e) z*o`CZqsx`t_4KxWl5cm@JNoOfMlZTKFTU1hUFkPJ94vEbYnVEgdSlg$4FxQEzZh{~ z-NiRO1KfY4{m}Mv43K)mwY|?xkAorQKGn$lqg}0r_NOQ4l{iRAz5c7BR&apnjeCce zT}sUK%jnYYuy$!Mf7kV&b(gzGJs99{|9AS{?h7}6&vi^YYb}>*Ec}vt)+6uXX-g_k zYk7aquh3H3TR8&P{4SNs`QYhD(|{52ZwB`NdO0|v413GeE7R`zEL?SKT(em-kO%-c2Y>A+r>-_wUj5}?<5sNldS=mjL$hn&%O>Rx zACyB|EsuV=;ZKiNOAB`%eB68Vj2chx_Iaik zIb0+kKgES*HNA2T?l8DcsiOx*S6}_EPsmzrqz6Cx`4^S?pFOta&6=}W+jgJSY0}Qd zjk7q&l{3d?nPv=}_waYr>RBxUV#A~7W!TBLJKyNbzP)uh&+JDI?meW<$d>am2e|#(Hc?w*&rfms-n^*8(`pRJ&|`1V*A*Z8eY||cOWxG! zK7H;^33&Z1rR<0)>(6a&cce-4di~@x!FJylFJC;`B5+N&;~-pHQFhcb}kOi z=@v(vgPdKR_FE)6Md(MI938hi2I_eoGg^0d*x?Z9;AFquKG;5k-A=n$J9pdNwsE$( ztWJO@(8I>V`q+Oi6GYJ|uO$FE?gBP2Ak-?MphRPtRPQ@lE757Ja4iy8rs^H4x~S=J z&ovE(z!0N2y>!DQD`>?TA~q%AT1WDQdMIrHxtGaN{Sq)=+RFi!2#W+9*cMy?}!CuMA%r3OXE_e z>fXbM06?H3xv5lbsLl$t4yDJXN?p1`CDt!B?i!5}K45khqD6&I=Ak4dhUjUg(X7I+ z9uu`w9-R_L5`}7ZYZ8E7stX&neHQTth83HD7Xu|oCIH|!Gd+V#$4b+sNP)jM)*pRE zfH8{EfYU%lWB}68hKH^bi`^T|a%eoFrb*$(;K4#dwOQqWqLeh&lm?3faS`%`h!b!D z6`GJV%NZ02v>)PaQZpoeKQ+#o>z`o+7?YX{hCHC0>wXH=;M}xh@ka4Wc+(jW0CN}w zMn^rHMZieMk-FLdM^a^*5e$e_&@v@qUb^o0m?vc#drB$Pa)Y|Us)1$zdkKpSr`>Nr zHMj*22EgK*RbdoiBr%j50rgTJ{w`LaZuPLj%>Y;e zP+g5lP9P?enpF=B8g2I?euU?Q$b2B|J1Y1@B$1B9j{6Op!f&1@n2Iu#n$M+d8;(5- zD6`TbYhbB&4+au7#hTSg!?9DfQx}b`Pj|&2Cfa)}L_~eE)Pt*VMA2szKt{+uJW>e4 zG7y2sY(oggfi@POsO=a)posCaLD)cVF#Qzp2e5$xv3@iq0fz(zS-wGGF9v18*mtyb z@$cak+zlg*MXLx7? zZl^XhEp=cFaF8VYdSWw)?{OjGK|9VF>1)Pk9$hj+S7w{uijEx|CAMh3S4BBsl!C;}cgyT-? zZZro@sRG07VSA?a`)f0agg@G5nFB#f3SD&?0dC#^YQCfbx@B5DF{5A@vB0#vdX~Rk z6OYjm=?(>JhsPuCNsdRqG1>cYm|kdkgD=C4@FJls!Vt__fZ#q%v-bWHTvKTyj6nQ> z_kwLsV=UC<4#VWhxNS&-AjAMk&Jey}+{@sM1cuP20t5*`9qsPGzn@nP&jG!J0Y{i1 zQhbNd9}WdndLf+;AQLA+X@v1^5_Xb7d82tm+Kvl`6A>rRzR=LvVfT+H3`gH+%oRi~ z5Qm>eDbZ+(Xhz*Bpn`kVP8Wd_DNP%S?q4rsLuyrXniX}Odtu@U*f^^$Wo+!T$85AUT-F9dxqck?XooPeA(+lb)f4g%_QKNj9{_5^-`s5P zc?h~Wf)$>xmUiy%jSt_JLhApR7meG3xb$!AQ7F^y2}oxEemB-HAhE#IM#5< z{G6-`1na|?KMP;*m;yTbl*+)ws%hQ`pz*bG+KE|B@g!s_0t*^0nQw=V`%t`Vs02n6 zPEL6c&DtQRMR5{@$4Fs`s9Q!N2KP?C2Y(e&Ar1iP25ILj7=DTGDx+0J2MZi)I+f(? z@ZLcBPz-9MYveqFWg1%|MngOdv@Jul-Q|@@Y z1x?csG1P!F#5^+2rc(V7kp$zX30DF>f@T0DW3~e&Xt*oCbz<>J_F(9UO3p1Id zR{O1@Ecdv!)P}m+S?qDy<`U_6)Fr#~Ugz%4-gZ$|87*BbqAYS+M(IcN6uq!^!!9en z=6~`(9N{dMqS(fYq)FIdBGoE-)y(L?QROjlti!KnnzN(FyH8Ns2)aWlOBcvF(k;M>^uqCj9wkf&La|u)Lww@QH$?D5XVg&Sr7QvgA{bOy zZZ)cx2V+1}2zH%DGxN+291~8o;FW@-wGme!VCartErd`ZKw>Hd5xZH>m>IV`ZcC>7 zusnrQe2N5Lm8YkQXr}i48J`T}X5tQ%=9Zf5ZJawrjG=P@;u8jd7%=DQkBf!X00R0F1_N|>pu0f(1oGsQq%k+csf<|Td*N+Oh3VhBr*!FVxw%|BUe zaCjhVn1qndr{VlKp9sYO0<5xREO3vATCwM0A3+QPjSvxz_H;5hGTDutBL|HoQ&6O# z;06^3oPL%3lHV5f4%j}LDh$CWSVeY2B(b6wyM=vfN86P1GvjBoSUMITN?c+EMCMm9 z-%!-yY7$Qq!HOM zvYu8R9jH`6cNcW;?e<-@7p)gNFASjQKlNJYs`egFFEKn1trrdy56&qC01!uD`9+;Ov z4X@@%&CI`Ltt2M2!G#B5W)HRRkf0xh;v#HsbPTAHg4-7Lw&bS#>Q!S*49TF0cji%F z%Bjk$HWt^Yz43rGcsrWE8LFinQszGn~? zj)D>nr?35%b7Ku!@(icbL zQ4n@9#fxzMq=J}VkN7zZD79cB&53__5Ds@2=1(BjP!s2My zckD!X3sp`us%+{%X@p~O3K_R3gBr)2z`;xp+{4%QRZLI zi7GsWC1?`~0zaCPQ@SM@#FWG?9+lgl*gtXp33uNd~ zL!>2R>u@_NbR|JNkEZG+c&Prfn)MqX-%%lpnV_ze%xMIJ>gpkRPXyLTL@9K|-4HQS z%?{N*5=lnn8Jc@GR~O~I8$~IIH^LclR)lgg!ZE0XiZkHg&>U(iIXS=-NCU9bI6C5V zS{t`UMgHWgm7kK1TuI23DzFhlBW0k_8fz%UtWfGsl+XoHBdF_+GJaO=PiiYbFDV7i zEGVtwNCTCSsf`9I#AN|AQDT)t0u$(a5Y=m^^ZzUIr}mp93btu%T5t;7vmrRlZAVxG zdPAxMF}4>{2^G$v%Gy0t+i~LW3^Wq;M+8WyZlfkcl?m>2ft!ZEj}0XV6#t?UP=*mi zuvmf^ZMw%_N6~2hOajz~2QbzPg*d!bY2gZ|PC}l%%qgn!`Dj(*S3(0kwH4iAYdA6a zSN?=50Y4of1_%bJN{%0jIdiCahf|FT!!}Y7k>jVx$ych(Je~kyO}Jj_1>^sEBh9Li z$;QmLf6RitbowpjGTWD+lw~gh0_d(0k z|J@(`fAR+~5;-lYY6CS;gubYu5*7;^h%g&*cQEPeOQ$^`6zo&^b7@dZ1}YCA1VI>@ z7-1{)T1_C?pAom?Jd(0&L-m)DN~PZ~Q*WqlsIvSJ08ZGvm=?#SUF4xq0fm>{q4Qj@lURTH>p^E1r)_&B(^kh{8cT(i5T; z7-)x4HA5PNY!)3h01K6^HF6m>7(g%joOd`qE0zzY5XMRnCGts9GZekw-sl`e43152 z08|RwQ3MPuYj}K|HtZpvY`Gzf zGGmx~8AVxMh3nGkg9YJ^B~@@%9nxxvwTlfoCE`aEhz^tD;QP#D&Sie3fe}!t79JFt zAzqhQ2fQP=TjW|??hceJ)v_Psz;avZZZ{xGY-PgLWC#r&k>_oRHBv+P@WebeieXo2 zzo&{!OsKZ+BeG0yEGYI630g$sS!i$q6&T1W!gWx^0}eNwrY@nPL^hGwDxz9z9*8W$ zzbLnb$A?;Q*rM)9omM=S8^X+7mk~Jxcn4yZm@5QQ*M^1X>MnPK^K=gY6(kIzUCxZd z=%yZ2?P6e+nE8ie&Ziq@mc%9E$={2_HU1NwnyfV|g(`bCilJrCJGo*~O#5p&WkQayi3nSzRYk0T zf(eEKV@D$nK@M-Gji`Idq%{N2qz`EGGW~f@)rGA<8y*>{I;ImN5J$v9$^`9$@p1Hj zSW3Jvb5V__x`_qIxq`z|{^g3?BoIR&-Y67AXhV~BwQz7ib4Y+KLeU6TfotC+zK^*H z1P{P0sk3L_^3RWF@fWG_tC52MNjb1b=nKEYkWgqG4=ja+qhc#;w3rAi6uw6(zG%18 znD@YsisBTX2oh)jB1;{mK(GPWa66}gfF*PI%brKZp5XaNmRf<&+R5U7-I1^eV?aB~ z5`-RwWBwQ+LM&+wTY;hwX*0l4AfPkwf1dP(Diw_o*Om=Ic$!-|M^>>B4Rkb*GJF_G z1I;L&g*BH)qVhBDsKlnZicAXTcv(2adX6a^P0DY^)Mu2BX>JJ%;s_H#pC>kGHePgt3SO07uF1;C`J}dwbAAibsg}`MxC51rozSsogG#Y!gKu(S+ z`T0nSZGBQ~8qGI4MmKCc^16e3Lqsp|1xM)*-|7;%7p~g8T z2ug+a<{=#1FRNfG*idW&xh?Tw$Q+~V6wX895#6r3^4}B{2-ia9j0?)iC>>BKuDjyA z2=K?{LDFQWL{H6VRPyB7kjsDGj08Kl2gSId33x#wyDsBOFeMB2k$^%}`Q(;Ze2*x6 zM-pBYO*qg#KKXV&^=`8(1H)cXZe0caApV~ zVI8zxk8x1Ec=aU61G|=87rk8s7g+xP#@<^8NqK%-qi;`-D>lI34g(B2xVyVMJ<~nH zDC0xWAR)Lr!5yXt4Nh|;?ydT&&N)^0yZ8J3@w3jn{q`eU z)?RyU`g59ngWt{MBVFpi3L{`t$+gnMw^~wT`<3Aon|>{);8^uYs`zn$hDC+ZR#gf) zjavRkhoykIV-v~D>C24&0`Fuhu*|WFNu00}&`bmHkTl4EVE`6{S5vngD~R^i_Nu08p5eY+Yz>cA1GO;EtY9=@=&>0C}H3YWRQii zeyX}D^hDtW0{WqBMo1)-KBAN_vvrV15WqsKf^$tiOR4&+A$dqxm5GkP))Xatx|Oi`Ky zp`>2KN}`0xR)%!==^XKY0L?-vkU|71oFU%CNCaD~$-B+JJA{2v)A)n{uEfk_?l_DQ zVJJW#;2b54X0<2C*-+LGgB6=R8JJIY&JKB+2trj@d#;YKs)??pfoYAUBm+iS5^gBG zJ?RF?-K5aYc({b&BM!zQe-f5F+>p~i#F&;g@t<)B9iugKdn zt-e1e!k&w?-eAKcaRb%rh9NkiqM?{u=nB!IvM7_1bHK@^n}C>{03H=B@c=9sF47_L z4=T0l$?xqC%<2EUs9PE7w?j%2ZX%DMD8)s23N7r69kRx73n+ zAA9EL`t?r!Aw7@&bE12lY-{`3vyre|Dp5OhU5^qAZgx;wES%g>?iY`c4i;7qf0bNH z^sI2*P;yIo^7Y4ulQR;}pIe(ZdzGz+tCUFY6Kc;&Iv=DSDa;E%g~^1UYF<89u4R@k z-&~~}g>V@pC0bb?9|n#Hyd?j|>w|*d|Nc*%`rdV_bR3uC&eWb+qVrqrSxDzcfE*%K zQM4oBmp-L*1VCaUBvqvzh~o(k!NeO_MIPiN`Y$4z`Mh4U9II+wK5rjVtNp4QqejH| zwK)@N_oE*Zl8k%^^i^C?DjF~?RRx(EB4DstS}rKN&=n9=uBnHHoQT-=1t~=vkB_Le z&trjWvywaR^xU}Opm(_4m(?YcY)H3c2$zbd0Blj02lo&t2u3%#06|8~qdgO`ObNG8 zRd}+U9Zx@s%N_fse!iuJEpJYat~n*|@_!=iK4b-SM$YC|B7Vg5xIO@yMTSKvLjaZO z`s*vnq+GX#K87p%1ZW&+9gF~E)Uc6o_w)L=o^Euh$?320lQUxm z)#>@(C|9Vz-HVixWJ5#YBG4*9BLI`Q6Q=XZIV&J>FTMQfilG8v^$9w|meJ(Lzkj5|9Ffq^)qi z6wISaL!ki96B$VS4}{6Y0Fr(XuFNnXyDm$oD{bn(x4zr9!>`Yvhd+&baJS|FJBCeH z3=XwLqE9Tm&>*VB;E$!zLFr-YsiyGR6b|g74Re1UG7FI*4vA5*SToWiZqzlEBQdrY&w0 zMh}eToi`19=;1fj(kj(^O}F01BkZnpvbp9!BnWJVo`~xR_!Pk;jAATY zDpex{kQdM_ zUB+vbES(@&7$J_pjD^Eq^v~sz!}d#s)AzAxsv+& zgNjAbuMqPE{1Zq!#zyr6NO>{o$`VSlZRDtvY&b{IwFO;kL;2{DLu=mrHEW}MW2R>_ zw=1-7Q3(g*Kg}%}!)X6`a+x6KYOojRW87Ya-$042 zkO)=jmj~=7aY+mpH-rEUYQLa2YqW4_o0`t4|8!jt*zZo`Mmwy%_Z{q;F_Z!h2n|xN z8wc@B%8yS*jR5P9h*I|oQKuweHJru}o>7gdk;ty4&r}+h!V7%dnsa=!ngfEad|r03 z<##XV4x1O6F@!89;tg9YxOdXEg;n6ho~MT4AVb%WHw*|8l{}@NfLri1&aG??W#Xf} z<}6%dd$j4#H={PJ@;mDMb^ZV>9j0_ET{Cg!57YK;8FVrCvX%P3F@f21@_B(fw}jI$8hr>sBkz?(k26 zGd&_wH{OXE=}XewOxUr?tg8Ki;$;BqNEu04LrdmKZ>Oq=b!n2p&o?09Ak_=hT5i zD)t?}hJklQf0ZkQs01IZ=~{jFyxQe{IW=Tp$l;1bR@7_e)GH(-k-kK-F+?&5^+gL- zhEl`fB1+(XGo2DUIzEZI4rGKptCWY!Ykb?(CMJ(HPc~p4$tZ4 zP|t=Q&phUPG;x3BzQDbW+gG=>Zhg$#+`Nr6*KMx7UCl1*TzZ=FxwtvybKZe3`A`26 zyecXgi$yGXAWYN&p9~<9`uBLd#eTxh;=1bYTE+GerDK^?jv7U3tf^hPdLp-eOJYMZ z+d{-vL)@wQP2$2VmkMkgXdwK7lrNYD4hRf~81XQeL@kyIro``v;s^T<9QkN?bVVM) ziKRLR^DHgG-(dxFcx$enB4msL3JjxJK3QuF&23uvFBjiOde)N0El2BA9%q2_h}a$qQ%MoXm8`D$-Avq)iTqqX`1Pn1(ROX;#AI zSZhjV&(LIRW=&zY(7nZH6Bdv<-Y{C->c~96cQ93JjcdQ^ zKVAL*H~vqxM|{*tUV=K@q_-2QrkHRn3p>dkNz)E^ z;Dq_YXu#`mHHJHUutDgSM@14<92LIf5@XkOs30&+{^wHS7n2E1X1Gz+2_^?y7a4Un zC~fKDM3n#mNEB>LRWm!ME?#rFF+;s*q*aLX2moQZh4ByHFeWBp!Zl*p#E(pP}O=}Xp{D7+6${m!;i(+SfY*V;mVN6txBih zhS5W)1P7MxQ(kEslREB0Y8(PWm6CugrXoE$>}d5NgRL3V0HRtpRsFklJcn9s>1sL?V%-5p@|@( z09{jp1*z0sk2Vc>7bzMWD{VNAWzw^2i*kAQi;51R4p-i!oxyH+GK6yi8tE3~1s% z;`H#&O9erGs$NWFVMqdqTS9>tOBMO8YVJJ3Px*6+aiyD0$u-~~!tW*`VQK;{3!2A~ z9UyFgxk5dZsXjae$Jk#M*DsfT5Vg3YevT9eYbl%>J-&hhc4^<@n1HVvP8uWFs(Z~? zF#MaSMMq0G*vg(T%mh%75RN0;CIV@|0qT z0>+^APcf^4#q}5sbqh%<1p99su8I9Fs$VoRlo;f|yeK_MU||^zAi`T=sku6)#Ql-~ zQj)=`IYKd57G`7EhO%CR`sucWFhYRAjo|-O(W9zQ>5&0Ko`N5z*)e!wel8$-#>SE$ z7{XFrIDp3GDLC#0)$3xS3A<3m@T2RZ)I~@$gT`t38Fm&%M#RFb`2Vr`3Z_xn4-PxUFH{easQ|{Nd&>OKjtRpWwiR{@ zX(LXjg8KpZA-Uvj*1(C{t}qUpWtJ7TU{HFeQpzRL17jdN#NLbQ#<)U+M$l~zfj3l` zxkM)*R#9R=5Tyb$5SCz(%MHfU0@Uy3rWo#QW{ojehlm&pIZ9^Rh>NCNSh5wY57V_^ zpCP<~im1g{Jn5T5L}dY)tHxQTN_tRFAxIs+9;d<4H7m0_7!|esgm(49`~RAWwF;I%*?@TVUZ(|Py^u-byGoR zrI(sXVyfSotC|F%qH3QA8U(x!l`3uxUg#13?1F#aj7tL!#l6`VpLg3_o4qa}SrV!v z1&XJL3N2KqD!`;t7X-pT!e^C?5BW31eQ;%TS7A|=&w}j*b4hwDhCZHjB0vx7uqnh{Y~fvab{iUJFSbiBP}%49lVy{K`l6qViS$RVA zgh>i(j_*_GYzQ3|M9cIe5M=PW;ootggr5S&B=7{=pBMKRU6=kb;9jld6G2mJX8RbM zeBWZPNjoo5F29&KI#t>fNLHd6DU?eVCFEEN*btw|oI=^2+s!&mY3y_FWr4WXty|@p zvAwc?yG2%e4O%bed1JeY@ubz-DA#5@7T1Z3Rf+>`BQRduUXlFc`BxgYuovmpYy9=n z4@w+cvE;zw@~29ug*11-UEQ0RER;YgF_36jhl9kwB>J>G46A zZ*SS3@V)(E_^#YneYD)}LpCahhf+Uy+Y8q!fqiS0*+ynfqla_Y6BrKbh;+OhwSTg$BWN_31> zRoaGzPa+EQw#sxY$@3vqM-&I8Gr~c-Skfry0uZnu@`kL_R;Tj!D`z+FdUpDgCC}|u zuhujsOuZFiuSnM(x50)YAAyosaslm-j^Of0rh;-n48UXJ$B`Bb4WZ$4MvAg&hrDk-psL>O41;>jTTfl~|w0!xlx$Wm4C znUSdV@J;~J#^FZKDpcx-z&4m_wDP%Z3il z|6}pD)v6qCly`L3?ng_cgxZ7XY%vzV1bF1E6rKSB!osUzV|tDfQ#Mi}IU-fve;|nA zm|$^;0s_`FIBv5qmp`$-%&Aoon6Lh9WW77*ACwNamt}GZtQNQs$rNTF^NK~Xt zVXUzxX<>WlSc}#lGHBM~(1bYx5eQpJu;rg^zH+Gff^oOakGIvX`p4$y?R>(+>}81h zB{B^N1ZkDAI25)~Ylx_@GBiv^P+B^9teNugLaP3kt+9uZdS6CLRh`5PN}BL1;iA3ym1% z^e^LRwr!6Bf9P9heP-Lgv)&l`bZwW7HhUmhG2AHxm*fzM8cG_#u$aW8#tEjCQqhW5 z1{C&+qTK@70}KymgW->uHWzL4JKnkY?$QrE%DuQfXY7|#=W^9K{FBvQitfH{)K+?G zF!}K4cwuxI_pVQHmenH-G0C0&kp&hhK5)5~<8_T}J#vbGiLKinx{in13c1A|lm@~gD{SRV)c zPM`+@Tz1_4(YV~HNsqb*4(~GDUgFO0`+huQu@|AA0&4_w#YKE36(CT-(oj6ZUHts(q~NUh zlwt4|h~K$tfz9OeNNfzBs5_Gvrfs>L7Jgys=S}Yp9a?vBkkww8PcykYF)({PBU%$s5y8gVT5VbXW=-CPWtt718{Yo%tbcNp zopz+3y%0s>2&3Urh>6QBAn@|I=`6#jpfr8URUjoXr{E$;lbE532>XbIhm4gU(OqYm)E$Pek00WfDbcYL>MLpL8%?a(Z8)>9v}mVWMGG+ zG^Xi`oVp@sNdQ5Viq`~QGjF$cwwwJMTnyS6yWp>G=7%fB`nezZE6kpsEVLsv8h~I+ z!Gv1pMh!ES%A3rGRbmE;qXqmD+Xn(WgOIqq9mj;H^s@%F*yMjT;ppO#Z#;sVEDE#d zqc7sFB@Out(@)jsMe>$6+krA63qb^hk_uO^`J7`+BY7hLr;)aVs{UW!^}I@yRfIUiu=EgMw#bu_~646H1b*N1)RO#aSXfBhAMev6odkwZh)Pab>g; zy9}GuI#1G%U4~@6dNRVp^sHmA#CBgs^$WM>AvX#`Lvf>{f(%L{jst*CWX z_dlFgOF^lc@CpdUyF%RCBhdl9%5})d6K9Iw)G+u%q|F{cjz{HvV%Sm2r^BjfU*cp) zut%&Pw>sLQLtxf5C>I)s-ws=g@RszT7Vaoj$W(6JAIILE3EgO}Cu|0e&+(ubiWM@1Gv zKI4ROxK>s;RWu|)HVWQLgW4QAa`6`~o`M6Wl7Z$+JO(i8`gJl4=gN3!K{Iz>##^X$ zNpYW4WDF-|NyCo9=EI#j$|fQMrS;Jex&XUosVZYBh7S6yLS;V4NwAS*K2&%oyZ|gA z`Dl(zq!C0@(ZgUPDL@r=Z36OYLf3GBsqtG`k(9B9;dYiGCUzysu_C3V0&mBiq=>`R2L~fq^;&99ktPK>ia|h9Xh1!8S{N%Q z;tOVRl}i^w03QRk)ENYPy&OIfl2NU&0&Nqv58ZMUSg?((AFbjAhze3aOmD;RSS_0= zR5v6P0FV^Lkz_4tGKKU$(gaktv_c_7;D*rFfgVa%GdD~`y*G@kt5HQNiPHx^2x|%_ z4x7ZwDy@hZxy}to!gOVzg?SDCvH)d=6cdug6g`s+!>=M2yx4H-URA6kb`E4q3g{Fm z9(E2N13zI672%&C!T<&XD-MWCpd+%2#>DJ~;nIdHH<~1`N_rglGWbumI-GFK>lLaT zkXCBqR#$_;SIHWrt^8>rbQP;8K&PDip^7#sEZ) zM5-fbA3e<#3*&U1=8B)zD34DbGzhU6=Rce$JJf6dl(VEAyc)Ng=nS1Hc)Ae5%|gbU zc`$3U)N}dPbNYsoKTHWrN&m|f=TJq%MhPBkoH~y)@Cz57U`>ea zrktnt-+JS*s`yZ;5yb;RLio=;U@#gyP#ee<&IfuXnJbMov!?fWXBeLQ)pa1g0J<{L zVWUCTC5MT02!|LrDP>!#IX*NJ@*ptm7OBSQCUvz13>kDUkEbqG=ZkL?3R>8H7AVI{ zL2Vr=B7cvfCWwMLIMj$|_-MyU^p(hM_2^(Wj-|QRHroBS`pj%!m@fg(#)uMmD}t=( z-|2WoD>i|efPps?s+BGy=mMVZJ&@fo?vKJtcKmOBQF>S!u2ymKZ4?Bn-WhJQQ@#CI ze+NK>TZc=bDlV}6L`#rnaE)kD!&LPfFomPVOK{SUSRO}3_wr`dwK1~Kilj}&U=7D>P%s2aj5vP8 zgU6HcqliAEI&6jikl_l=r_N##5V?jTyvMOuxRqkH2o@zG?u4$0bd0`Oa;im61=qz? zVFlb}3kgVs)kNKb#1+*Y$U7Mz zxGJ1W*NC7lQ~;HBJcVDHat()1NMg1Rf^+(rSTk0?=f$V~Bk@omq{Nak0t#ASQ4$Lr z5s_}MW}QC?AQA={hF1=ltHL-`M3-j1GMNf}Bz60$l+O~=nSlq23cQYT$pBtJI1qIc z)_5|x#bK*L)bD4M7>NueJ#?&?$F3^Hy0T3bV)}^qX#jI*!Ku2yl?^WY3hBm_>yu%4 z&y^St?b)$Fb`_iunLtJs@EVs$!s)IG(u)KSagGG9N~=tu5{HnsP-RT!>tq#N#;L_% zuCM2NhoU&js0-ZSg zRt;h(Z_&2kbZZlWIDs(?RCW;=vqeLm3C*3tdp1 zSTmQwA$O=+;#7Se92S@-9l1+xz*GuT)*viJ+m_Tw4jY7Ej;b0;j3pWQ2}#s)OSVJH>C^F*372c~T_9Chwq)=lu+@gyY+XBdGP)7s;9$Ofq z1A^zlmkYT=B+M?ld!A4LJ4RDS#aAtSm zLFgk+m^bQS!F#pqY@WDn!EZI{zgspae#_o|b{pYpOr1gr)_6&6jUa~2_RHG5Tysyz0^@D^E|ofjLYZq2qF?rRUF z&N7&73L2^U#sYPWZYo9rOfh{(geg_%)e0P7-r=I-CjhFTd?MjmiKkuf{Pp3|Xz%h{ zPxpD`ldyV8ggu0IL=!+DS7{;RtXk|X>}KK7knWLqU?e{fFQYkY-2h@&bku#_GldV&9=0US!fjS)UAa6maM z;p#NaM4UIO8)P?;iGjoK{<(LDDzmoS8u@B|%M<6u?7rE2#nn)IfBF`LWyGUGc?H1* z7^}+lau@;|;TTyYIHrQeVS*ULh9y9>ixw(U8%#*tHTOo7zyHQ?($LB-FY7-`UATH$ zxV;}?ep<6iH~Zo|vcQT6>cxRHu9_{xBP+5r^#dSu!#^wuA$S|Ij8T;@Ep(}RWBkR5 zZPNUQb#9;DwM+x6y)RgJ|14hkiEgIyN%2c=cC%Elw* zuul1Y+4FHFXPoiy%Cm6Th@(Dj=X_osZtp`s2+kARqu>;&=fM`ohSODKq{YEpfop_u zhExz3aM9^85X^C8wzR|2JN9?&ftwB&s`>Trsn7oC`RqoX67~(-``Ce=SpU zJmN;^n^M}qvBvlld>zs3=q!>TN1~*3x)jOP1BA(o$HX_k)-$H+lsEq2GY9`V>%`pG z3&ZRX)Q8N3%pHIZ0yu3%mEr^*^9QJhOxo>4+Cn!)tNI0eGc}`8q|U5f$uQPM&ksn? zR{Yr3x+Aic$pv8tfA(=OOw-#ESEiG%R_o?L@xod#2U3?7Z_eTl?~lSJmxpM{?~S3^77H zM<4?N3;6md> zQ31gaVW_J$$*puIO|TqXa+=dk!j|dZ9;Yq&))lX zfiLZ+eRy1P>!0SVVRi&nfx)`#1dbTO$e;^SybfNkQshL+Xd_dD4qJ}Bq!&lvKsXSN z%89LxzoIt&ogw4mo_1Q|SLo-98@AgQ&9@Z3l4pP&`Etcz7(n7ap!AAKsR;UE@Q6dE zDMBbYkxGRcn2MtiN6pNtJIDcEVl&|Q6%H6sA*t1wKIedgIuEQ-%xXsr zT0B^h&s0N_0`|fQBES*m?tWNX>5d|>Sh3x;E$6F+xCoUKgiD9^+{*}k{m1khftQ3n_8XZ(uo7Pc*t{X+yoMR)E#O;hoM;;2>B0RSf zVb_ig8aV!PwKB2SzK=*6Qs>#8HDPvSZK(dM0Tf(8)~KrLg0V3?DK-UbV*|=faU`YW zTvr#h7@F3AZ-R%7j4s*!f{}7}aLtgL*CNLJTCG!kuhL(_>qQxB~uUE5b zvE)qCk=9q-8*Y`5AWmuJVG-fa^nfp0V$`L+b+?r$RwlJ>P?;PlSqnYODA}W9s2yQv z(O{}y3V~9M4^cX=z^D*}>BA!V_gGCqyz z-gMFc(%Xj2gi=L7jfjp7G9mzS_*3AMxH$+7SY6doxnt6y16!8J4uJ@&>fqgAJ+b@) zJm;P%ICFKYgnh-=wy!b0z?5;hDh{xNHI4HvqFd;5{_h&WdicnZ5&c89_Iq9b>y|twZcc`M`8x$WFM0EPbyg&nhVuO@&|y4Fua7&@;l{{a zSt9pFj^Ep1{uHlgw;MvS1roPKsioP0^+awe8E{(aBAU9e%bqq_q<7d`b)!}3b|PXIj$fkpBZ z4uqTP0j3g5n5R0578))w1$Xh@S4%e9Rd@L9&MvXfo-Xo#R;*;&WvjgzeT@VaOa%~e zeoH!&?KP4qj5(EAP;kmIFNN1ysGVh=*uiH+T+5$3Fm>SOO7AONseCM|@J)|>A@-)U zFONiGskp`7n3EfU zhq>aQ$T=AC3}q&4I;tj2lhB-WsA&Rf08B- z-D9n@e;QzKM0b+oCGo|Ic?GjT$sQsy1U9&@*i}HfKsLAlC8{0jTlK(18nr{emv`4x zHh$Y~=Q$0Yj=0jTs_FTIwy}Ai_q8{q6@zKP(PY}Sqh*bX+UwI1 zM*2h{4!|Sb*{7Nk)&DQWKDag94tyaMJ`@-T$H$FYY~Ps{y>RgIY@6cteE&Z0r#`o= zhw@|(3tLJ0|HGZO8h-cvPWWx~o8|W%bO4(AmG#T&`_}iG?|$Evz7u>0`gZfJ?OWK_ z&F87lS)ZSM7WjPc6YkU6r=m|z?@!)$ypMWs@Sf(K>fOh?k$0fCuh&bjD_;A69{lJP z>($AthF3u^7tcREfAiexIp1@*XP9Se&x)S8JU)5c@i^+S-eamqibrpch90FneBA$W zzwEx->5Tgl_c89#?j78#x#xFx0_t$uZHwDnx1nxUx0Y@dFpKbm>rK}~u4`N;xhA^y zaINQB%+@zO+MSGq1z$2v z*~SN$gPn4WJAZKe&x2ijE%!b;ANsZJDWCSw>#X8G8n-{QLWuznnhvfvd|=-$T@HE2 z<~2sXmOonjCE(XrS-u-KyIHP^!(E#fc>8O+((*}1=Vx_F?{k@$J-++f*3az6?(8V? zh95U}Tgykix{keasm{5U`vdwOn3J&9R62qGXl$qP*J}O#(7Vk7x0_c#m;2z`eP8Xd z{6{~uxDXI{pwr<^Q7I`~S~;JXI((9;fCvB47_a5CpYF-HxXFKfzR4}SPCU`dY+J^k zjGlX}M#*|Fn>_6HFtosx6+ty@Q-+S=$Bp`}O^qQFdvAQT=R>(^Zd>P^J!&yd_v1er zweqPYJwCzn!f!45&p2}^zKH)G-^KjN_XBo2ZRt{e&Cn4&FW+dox$w$eI~tthPewGZ zeeZ6MhyJUE56s(m(wm6E*ZT(k&0jP;>)sQ`-mE*{;Bd?OHE%GG0iO$9FVM}t?6 z3OZKg`@L;ed#}H@}*j0^6xn!hZ^r5$VZm_DU<%lns##OjX z`|r-{S!3h0tVgO%I+(?3Y?{v>rMRBldcO3RSIy#+N-yf{e|1j%y~fz`yco$x3U^=A zYWlPOKh0ZS`|du=l$YLSZ~i2yfzR%9|LmOi`^1Z(e!J%vuR0{fUw&{>WzQ00BT82+ z+%fI#(Rnv>jGb?oiaGN)B^A7U`B=-m**^XA_F|pn#Gl_RSZ2I@$sZ*g_|2tr?O0>i zhr7Eno_~BDlX%{^{6#+MbUxsp#l;fxH{Iysml1Hkm3u?uj1#{_LhI)Fs{(of`P6=g56|^2GnL(x;`HF~1{!6!T29G2Yif= zyZNK=k5dwVFE{U8EsuVowJh`9_q;rB%36YdR``*5gII6_os`>t+K^BT`gL~b=R@k$4}Rv=F@8IfoBzE(QP^Y z^nJQ1OT$Z@((86w{72%@lKo7LXYf0O-pT)9)}U=GcUj9dv{euKe%sNR#_QGmO`*}n zdM&APJlzPfzz| zIjx=pKhA74+b`SI1B2u$TB^>-eddkJTK`wee;Q~QubN#NV+;@AKkEN`=-VIHC3nx$ z@laCGl<6}IJZV3mCx6l}YHiZXYl|1IcyqMQp)A4QhbO|9EX~f7^zySE^>?zv)g~n)v!Swb4jl`!*9{~ zNa%@<{4*;W8Jqc!I<3jy z@YLJ5oyq-9AKlox_oMpLCggs_f7CH&%MF79hctY#@w+33zqa4_`iE1-#l!qZ9n3M0 z-XFd_W8#cKPK~PhE#04%U^~s9w7??W z&zhEN>45v$P8KQO(XT!~uFb-VZLB|(sj_B!_OR!tjuh{`V1aRb8-LX5^5fwd-~Sap z-fQfoBjYD`@yYwQDPSc(ujRn4Q)g{!`}*?l!!{Q^QoYs}-?0%d_>&gb)0RIg>0kWO zX7iQP+X|T$C)P_oC7&#g-&A~YYMtSKjv4+(&Lg`{v@6s10Dn=7j?J7}{W)w24i94ffnzG9kXm0zoxuQpiXR+AA-UT*>KL}|Q z`sA0utS(vklP16X^);r;oL#T_FWFvm;`y6ZpI;9Xqtp1)z?8wg=3H*~K0W7>6(x(W z?=;X@n9SeQ_W}e`;1Eu{!nNq?E6aQMXm6uyg5+Dvi8fHE(v~nrvbH z7rp8pztQw@R2J{W3eBJqR^tNFjU$gWIa&oO)qy@pL+CYR0OR^-#O=_`XXjonw|qiW%U zV~#gnH+WrYH*2ek@&C9c7=PrKk1A$Ae{gX7xZ_D5S64bvd+x)Xe;FsO{1$aibq+XL zYvPBC1r~P8Keleqyw%$jFTj6PC)4-+>YT-!x=mQLv(1GOKJI^|8V}STO>a@B#iYoz zt*O=b_H_g>)YyG_6T`g22x2b_d6SW(B8UI6@uhpl{ zE}K|kzsM%w4z==zBYL=|E zy{e06{vRjLjxyE$K|b;u_vEifaliTHianW`_f7uePoj;B$^5(;r>`s=H^scJ;oUx) zw^lK`)*KSoqsn+=Q$<0wl?u8plE#Sum+ZI2+I681ogYQyiue|fI_N?NCO-0(u zN3DMJNSWbw`{>#s&tuN#%^PNYZoK%wA62}6=T-e9mn$~zR)6I2wZ11t?yYDlwuZl| z;`Bmo9>=|!`D3j`|1>N5bFqRkrwSRcN73HVx$rd>EJ5#NXRR1qwY%%;6_yzm^>e~nX|6_d$dhhUV?R681e>pri zfc1aVBh@3P`)2o+Za3WqxdpgxNB{rdE)!geIq!1rWqxWN4gLR}PVG#OP2ZdH;$i>8 z|FUL!6Eq^dt7yNAq)`rvS1-c8C>eun4|Gt}tU#v+2ZR}CN){)H)h2|BL9KH|^DiA+ zYeIwFaoL8Ci`-u`_-I6)+15iaTpP7J?wOO_eyMW6#mWhk_75brwtk8BH|Ud6O6w|{T0sh?sdjo5TGr9@~Z zw6cq$Pnw(0LF6LwNNyH$G$wNs1J~#T$;1bsH#1}w7td{aj#>jz6Gja^c;)J*0S$6> zY}zuVit(g-=%g|cnUKp43RW08s^6?v0LmMLAz zSz0c(W`ETN^B^N>Jc`o z)TIvZzrR;n%w^n-sMGFQ)OGIX;=3ajENnS^XXWSt=H(t)`ed59`VYncjw*N=xI)5F z$0- z(}_xeO6gb?P?=Q3)9Hw#2TmYpMa)w}0S(wFU~{ej#8c5xkv?@}0#8c(dEKqHmxE5n zxUI||7MO8l%zEpSrM65H^<6KL@cL zWDU}tU@;%67786w{Xf;ZGJrjMVUMhReC1FYR`O~cSVRY1ZVU@|jLJ>pR z!J(Joji4&nKwIF(4oz<28okKes8#}+dzn`9(B9n1t=@nTp4DkWsB^JOIlA<?E!nPLgf`A{78;J)*FKwhpbN&2rGh!$OaM!_G+kDsEN3g*CJM zd@I+=J5A32@jCwj|IQEl+6U7YC}DD3cdR_ob$QY!jxHA|=oBk{hj5l~+NcIE_1bte zSHU=;?_t}GMU|>pS`~|_6x!+9#=6P7CRd&pV*idTiz0G4n8-2^iDnf-RgdOA=}uy1 zg%_e)1?zU;H&e-!$3}ua4w1g-TB{4p8Qy7V!E0H@RKJ!G+_|%V`3O4%v4hYiO#^^& z9+P2xwIneZO|Tibi&9#~Z;9oSzw{TuU=#U3)m|J8PWa^zpNYK?)#IEs@=4nvmDlfR zQzgJ7;KPS7dpei?sm~QoQVBx}>lM!;BA9ZO!c7c8Gd%xutR|?0eZV)sTpJ8Pz&}sY zOk-B({Zy}9|DE7KMMex*MiGq>+-mh&Evv=q%yyxUh?QAZk7`%R)_pps8n} zN{p7Jg~C9>5~(Qo(a@+e{im!AuG+3exrM$1>s+aHJKycsh5Oo5Y4eqcAvm#QzQ9qJ zyRBp@c|4o6*}?wOhukN~LdL{I)@saA1@~WF@0q{%pz7u&D`O+?9$cPtmd&0*CIz%t z@)@|RFj6p2qE{siKwQEF7XyWE)LW@=jw{Y;AbP4AvAs0k9JIpGZSRb2UU~fM$7_a8 z4Vw07S)Qn^t1b3qGAT^7RlTkx;{(q{EmmZLX%F<2XjSGz4H5NdR#1@kDi%cMiuk~O zXMBbn@;f{`mo0v2?VpwwjGCV-+@3^yGA7lcdQNIqu&$u(f#0u4ecYHWlKeQ+#JbRf z9Z(Y|QmST_NXGz;eps07_V?`Vb$2aEtu=13*S+GCHjMe*YELBNB}Y?}u+^9srk1L{ zLhJ@{zo@M(nwotfiqAp`Qe4yxYFKOxn(z+mJLK%Z0v`sHpOwBQDRbSAC)E=XH}Fage!IY!DcFe}J>wZh|l$9J}VeJgqCU*Wg+ zH>$DxR;Ya-g)gkE2BQb70j4+h1 zTeR3VXgqkZ;t?S_<&H*N7*u#whZM88HOox5Z3&rERJF6G|588Txp`HV5ck6`2QN9$ zZQ;JXq4ro>Ki#BC0T8v1Acz+8!GnbU?f>B5hBfp=jYU)QQ;C5g%~{P;l6!Tr=09?8 z^0a&<@B9@$vs+?vm_3G!nn*s#q#vzLgX$`&If)S9z23M&{3>nqP1A>}$9^itZ~nUQ&33 zqbH5Ns&NGJp^0GOiA_~V#+~{ahPQ^5P>We0QcPjRC zFCyKs#H1YmH&|l0J(7M_baEOM!9o5eY7O!}u+F(PQPulZSg0+N*b=Z1*k19)mOg;U zSQ?>{etcJp*7H{Wx$=uwK=(CiqmLw4obthDk09f%XR@H-6h4O1Gl#-6%eCmfCmPhJ zubC!R3Tq#sE^ZZxyToi=`Xz79qrV=j*t~m(to5hIO&QVM&teZJ}>U9cOK#=D(&vBkL-IjXX^O)-1*rT@V zTlYuq(_ELhHg|dBvfQP!iy5`{4(5;M#pV`HZ=9AowJ^Qp8nYv|a+=`o?@I;b-Qkg!<=fD%gu<%S&kh%zk zP;=rO((kGd4a6&IzCnh7#weKqo_h|s#pTWVp=uzimibMNw-RAT`aBSR6H`ngcj;Jz zIgs1Uyk=2V6k@tDZY_5jJ06UXm&vX|u-3^dL?01tI2zz#ZXAy!4i80#P&GoTOrfdq zX*c(ns%Jvx9icrKG`L1c;P9Se6CF)moNH4LpQB-fAt3~_L?sl9?b;CB?)8-z!amYm3;aWfvz7<7O-oXw?m2JgJGURLf+#WTnb>62h0V%>|B32hS&67YkYoDQY6A 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

    6o<6F=dTIX1Fv2nCjkf-ECm1H8YxQQS` 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 z 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

    O5g7qKJt?LV!;oRoh|i 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%0A&Eyz%r_CP$aXH=mE%#ij16X=*z?jMv7M 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

    &bXl-z%;Ylr`rY9#wXON#$jQqd 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 |q%o3_Y8++4E)A?mD~u$EB2ceYCdU`<(rL z1|3x;g*>)^*D#(F9hZ4gQ?^oQlcEaGqFe$5Ci0)$(igQlZonu(pKCKS+W2g=_%&T7r_Ew?T6xmu$J4Ka`U z3pEfF1OZGMa;FjqB-`Y!3t0Nk{$6Hw6N<&0>elStrU2K66MPKjRAFI|gB(Z$RwJ9O zCD&2#C8RL1cckPFP%l7rqH2Y@0Z1T}G^f%>%k5uxUG3-;RknQLljWDWwdiVfK04H3 zMoV23)!`!D&}w-qEd&hbv#wG zMu%m83a*@yvwLBSjqNc~Wp#afC69bOb-h{g5x;I@RjZe`y0^c|!w$ap;6}Bk;(m6WxUdjbeX?2uuNd~D4I2+egP_n)YTC?L^Q5rf|UmGW}vQ7)dW>C z6tICMXDtfZc%Hl0mCUm{*5S%{rwZoBFVC6!xWW3p$LoY;kEF6577f=NfJ>wD8VZC| zQV_^zagJgo@ZZoVRq})qTudqThgm_-RxB2hTAxyQ(Aq9*3RTQVb@R7c(LVQ{zW@H9 zZ0K1-V?PSpgm_s3zaV7R=x{@uF)CdUpA1I?9pA9=_$QxD{zMqUQOul-a8Tk1M2n(j zkcC(1mhH#)xo0$*`Ov~joDlI2OVA}Nlq=`p3NO|sZ z<&<$)SPrmJVvjGpaMNkP?Mgex-2LWXE_3FyP9@u<`)5PQy1WZmB_Y&%l_}<;Z?oXN zNnD~IN2TF3U`@CQifHL2L{%{3wAcaqlaLWW&asiChOSmR{Qze#MN+q+UWh&&IL`5OtZ0F=aU`Jb$z%S z+~$o4*wXLgrWwpT#3Y3P6f4k=ICoe}2W+zPcemuSDRb_FZ<`fIH( z4V~#~dm|`2jux0uMl`N02&kY8iBh+)2jMJy$}K)nkB@=TjPheRi9FurW`zd<{1H)T zZ?qcfGo(N{GaJLQ?CIMVwaA{(qHB0|EHzVrL=wDq48abFq=Ug&!B2n$5`ZMcv&7QC z-vX;w=v6ZC&M4r->^l@?p;;@RQ-AfS-HVv*|IA$;nK7q!i4sx!-rM3rez7C$4 zvf1s}M*nr**@LJg&dP;?#>W^TRM25Vjg(ejI*IT-$m9bxiCYV1n^<2&?7}R^L)Ud% z?P9~nsQ#9tB3Ab=T4BVGnB3k0+0nFJZ?U2(jIm9yiP{+@uw~lVy%{M)E+y<}m zZ-TQU$R(9~WH2H#APV6 zJL|Sfz?s0L#$76Z?a^fSL)jO7c262vA*^`u#%}|%!`K5M6aqdF5DA({f-uNf*Z2yE zQxHC4Fm4d4Z8C-*aT6d-M_y=qWaOqiK2zPi#qroR0dqb(ZrJ>4UPr3`S65RMhb;~R z98~o#l>hbiwsw2%LhXv!9<>d%b+$Q+K7bX$1DIyr)atF(QmY<1do@M(Tenp=(DI;V zuw?;@BNh=Bj^_K!!_1w`j+!N!l`_pT9c@~{sixVf%>}3dp}O#nDi56YNw;J7aLE+N~UuP|03e!~Mo7^+7eAUsu~0siF%VZkOmVQpEAwY+3rM^& zQ%uC+rvda??gqyrJUDW~5fd^@kVh2FqnIHKV;HAeuVNi}?ZrVN#W%%3picNQ#S?%} z_&xaCY-)kvWbGdMEglh_8`*VaBtHx<7~#^^DifP90p2*U=!>QCpdstPffl9{4g&}R z%qS;(B@_69h#3HCH%YX|6{-wSY(?X@!M7&VAj7gnAgFb#6sHy;sxs?XOFgl0J_0kaG{vIq-Uu-FdjAebx-Yb) zkja8b6_LiGSfmOZHSCyRJvU2`{29SPmI{#^(J-qHJD5cu^(laWR~J?Y!yq9<^i(b0 z6wMI?>l67o;<#u*5AFp~X#|FF@nM^Qx}&5Ee=cMN)CPy~smjZ7!wH5dvJY71pfqUs zP7P$rSZu=P!P8(}GyvKN7KlGmDhL2$1QTjOl6)^E#}rK9t$|Seal=Ml%;hG(+W#fd+U)Op0C+M7SbkHK`V1 zK-vgc^@P1f=8~UM&WtjB6IN73@tE9C28I$2GzAD6|A3Wbq~1k*BKfJTStW)=;*|Io zqSd@qJyaE;B+rj3Xh8`9A*Ah(AcsY}gmorVrtFHQamEgrE0!5S0!=JLcqQytcuv~)7p+hELkrdutLCUpE&}H5OluV5iP)0&4Z=I3<@t$z zX{@NI<+F$wC%>?!2mzajoDdjDKza}F19Wn6IU+$?HIu_#eZ_JT$xoqK1KWxHh@6t- z$!HpILaR4a`FBAgm5V8txpytNju7N|NUp)N7X)F>`(XaySsDSC0=f##t`I-vb^+$j z@;eI!f1ouXUcsqQ(cB!qI!;^8AR?g0(S=Bbq60tLVpy7_FiCuTWq07ei<_e;Nnsky zoI=^{hs=)H5_o<12C!`;v57PQM`;3dDp;v%rZ==~{Y4rmq<^4t0Z)-R2eeS~CyC^OS}S#aW8Mmj?aNaC zQP9T42%@bamv%s4MhMP{2CYXEcoVR32yt9E3_XpeT_DpWfl=UA zXaxd6Mm7rk@PJx65brw}@M7?0p@E1jrx5WSJRPI9BjEp32Z9ndQ0F3* z4u%Cqq_k>S^+JdQ>9Le)4GB4t^?*~`h<6Vm&K|4_QM6&_1?Pz3c+rE7)s<){s+A#o zEg2o;WGJ)t{d=guhL8=`iclKBpL>^TO=Pr$o78vbLDA;M7vG$*?BE&DAgbAU%w|>b z-r8G9(Nk<`%sq$EB4)y2#!Bu~FofBcSz}hiqFxx3U6IdX--8L^yo-^=z^ThVCQK0` zM0^GMm?G(p1Dm>sYD6zNrXYz2Vs>LGMg!x2w7=$=QJOmevP9&mXpJ4&trfe3{5VWK zj!+mKcto%WQea+czEY+``7ju+eR=WPY`o!&2o+jR^<#CCC^}vWUF5WFr|F_&E-6z% zVjpx)!;eLx0&)&NNMokGMxb0>XI>DIyM;2`9b5K|X${&7bZ<$h_H z!@}}8Ni>BON=d8>)&d@!(Myyw0n0-Bkemad;3iY}IHLXg@A=nUYc(Jl$V(=#^lEX+ zvphdL`mso2U@h~IIC${r!7S#(&!Aj5%lb+Dp=B*fO8|)7!-{akmZA=#8bew8D;O2; zf%BF%!7+<)y+}QWG?$e9aeqZ$dSoDB;>p_(eOQT-W#s4!VwyNb+5AJIgN_0zyQIlD zsMROJW2BK7j)z9(~oYRC)BMOo{X})?`6MQZTfNAN5hpXi-$U$)c zYHBE2Z!PUa6s7Y8iG?Qn!x8$=TIS!xygk^)(mDscOXn#zwI9so!P z^Yt0%pmk@JG#Mem2UiLw9gBgo^aZyD5Oi#{#5Q(w2yPpQ8>zzEjZX~n#kes)!Y*Cy88%8Dx?+*7KA_XFCtu-N!0Ids}n0|<*SdbVeg}(jYv&v$Kylq+HNsA5s z%_~zNu93sFQH6gJsi$cZbZ;fwlg~}JFlp`c! zSV~*)A_K&b3iu%;O`!?fNlqj(K-dyehJ(O1+$kNK9MpU&rw>umH~&4GlnTRR!NCv$ z@-Tv=1+0aK5I#dFgGDbbl0@(z?!+{}!`WLpG{Lo3^hpiL(kb?)O1(GC18Qpr8|x4puPN9c_bE@Zjk zc4=q=SVY>B)Cg2-n~u*(kq`Tn#*Vo-$U^+g7hoEyYIaZxJ|Jm9mJ>%499?<*m znFVeRRa&z2NVOtngCj)#ln}fpIzMbDPGKr)3JjT$@&+ZG(pOl z09hB|bI|{B*Ji1M0Hpw=Bmf&kEDIz7<{o2nq;9O(OOIJOkzcw%Y#iiv59{!o}8AwFouE%rm&ZTBlIoP{@xTaXN!EHNNfwOW}zX@*>;QzEE{r?`1Ydq3W$TNWD@Hl>=<_C0wYXu`&@)la7vX-hf!Gwd2 z<m8WYIWp2}MZwUO0Y3 zXoz@LEYq|Z1u{$1zGEZ-t%s5)WmDvL`FL)G^q_FKP(Qpynp} zQQlB2Z^|@K;!jDr{@08KheVKPDD&FfQqTw+A(6Tv1V+M7DGRF&D*8iWCvr}SL7l4l zf-?Z(?Slj0YvcIhDuK{L8cH!3OQt|114D@;nO!nupJ8Ko<%znNP4r)12Yx*uNy5iR z+5n*m?JsP464#1ngG<6n$mg-Dy1+wI-U|ssggF!mQXX-{SPs+WYUeJa=&%st7YmdE zxw2B_1Vv1$Y0zZhwT9w5mCrd8#wR5zJS*7W9fnIf&G z)^()%|8$7ev-Fsc8Gl_8}AiHnReNmu(x{O15@J_J5L1xawl#Zd2c;gpF$b z)cTwnYQ5EZuJtfx|5rDeZC%jntJPhrV^(QaDOPb-{j6G=c$qx5s$gZSd#~o`uIUcw zR+yygChEdXTI+h~8tO{wOw{*aBV4fDVYvXD1V76Tmenl_T6{GPv$$h%%p%Pq#pI7g zyhT5Y78d19-7Kt364mbNa`QLl+2(spir~KO*{5DlP72{tmJ#@iq)V(eRmej01`}^g3R1tX&-x$R0ci(KJj(0UHZ%Cr#&@YVAFbZ%r}Y0(Cx4YH)7+O2R@)TfO9!{K zsZrZ(<>bI}4~s-u`dC-1b$N?@iuufjYB%P;=BymKs@nGf z?-zG|_M+*T-S-!Fs}ZRdTh71Am|VKpxlNUA96HqRednX;E4yL_wb(?y6jI^L?os_b zX5O!{YoXtoeGN-J|E3mULUK;p7~il{txP*4PRxzz_H+7|rAzJA!s2_K5B&M-X`c6y z@X^oHZY=W{ywt0Vn(qn^cs>7=P$&0mvFgQY9`#da{%rpAmRhF=UwXAEF|p4-l~3E+ zJgr%*`0uitwq&Zczwo83GVeF_`7uUUs`H}gVqGGCHwY}R*6zudPF+4dE^=w=8>joL)?&QZj@DuJCLc#k44Y*= z@s?s^wS1Yf`7HnHnboY<$*~c0tV(XU%n0O`joI{~H zgNMG48KZn-PHoPUWS6F^hD|DW&u+nknC=gLu1W2stS-*KdXg{gN>b%(Aq(D}_1+g) z*7DG?2qon;Kl{V);|2vzJKuQ76N~7bKBG2{-(AD3wYYMv>BzjCvC~T>m%4DI=g2bQUr}c}?FKnuc zAL+i`{CF!W?%a)ltD`=xTei~omZ~yAHs|)O(7andhog#>d==j#J@<&~njgx^Zv3lT zhfWP&UVhiekCi8Q_bvC^{o|p0%2IQ_l(RSOd*DRNB_O`_p4(g-LBSKcb2yYj-WsIA@Y9qdI)*Su7 z&*>KVLuc3d8SV4^!lyw+lxIwk&AH^*i_c23IM`8?po&ZDNb z%2%a|x6b@VsD)DYauG#2RF5xRSXr~~h>!Ws_kP+hzh}SJPFoiwD$~U`z7U^xE}`MC z0RuxklW=@me+-u`v zX`(MPr1F!k75cR2D`yV$j5YOXRsYMeCGLslu4zYl&B$-fk8pb0;2x`MC05+HG4M%t z)~BT-2IMJsnSY;is%L}tI`{iySL&m)eot)uYrlF(`Bsm|JKig}bES~@=0EdXBf8wz zO)fI@Q~rBA-m&pfo~6t>o_G)&SoFx|fSg5dLjO(yf`+99RQ4Wc1wcl*; z*;oEkN@2Dho;9u=C9pTt=?%eaQy1RYrSpCeV{#DGC=SBEdyGvz0 zc5c_McC~3$w*KreZp4^U=|eL4%C2+ePOSI8S$1snlYIIc4y{~Xd%61Xm7VAAc6Zr3 zEvZjlh3!AD*qqt0vVj>h!E<)5NKCI)X2p*l1uL(}9KH6~w<;0JghYOZ&0Sk%wCFcA zul}Y&y$w#MK6(7HP@alCu<3MC?#4q&4s{aro+})O*8J+-LYdK(Z?&mMa6<0GBl>M4 z1vm199N`H+L!0{)0~@4 zSC+-|kJkA|T{yDCPrbk0{rGQ-H|AkGa!Ow0E34n+?JD59%W6V_7G3A{JyUwf)^Syt z5TCPp#6z3$ud83Z@oLbLHG3T6eNS&zZhsa(8kkaToV~-rj=82W`>OS+v!>Qbzo+~R ztL;v$YaI2bY?*G`2Zs2q%KBU@OSwFff3#|XTg&i!4I01gkod&0XZk0<1dl)GuO>J&S$d_bdt}$s?YbE%NAZ>A^MXbM zxH$hxh>5vz;^nZ_8TlIbe!*9kU8?(WY<~BUv(XvDb=@tRnAdndOgKHu_Me~S6w~bK z;nPJoGNpRND+7)JHm_aLoSP34WS*-J+@DiQ4Xy|$C- zTH9jHPFSrEY_3dZ)@;s_BOmMJyS?yL%#`Qhm6X|2=Weo7CJ1xBB-LHXu^!>laK*QK zk(Cb?v-qc{vX7bQIg8))YdK`cgfn+5Rj(bkB>v=7ljcS8^Xx6!a^7XT-@r9di+UD% zdoiSukHJkD$4uay`D?aCOy0VB>`m(mT~0lVn!PxqvvQqj;5qZX&-f)&&n>WT!}))# zBW>I#mC04|#8H&`c+Q7)kL&J%oYDAp(V zw6g0DUz)YgyT^|iJAMUSN$_88dc|IsI!^g~oo_WW((hdL7ZD{p_08z%y6ji0?)S>} zVUwhSZx4O>5&FJ0W%%Q|tC4=K?Rn9oxw?N?;x0E}j z_)^N(Zj%hteY~p#O{(|Le7E?*`(l(SmHAFn!a5J#>AiRF@Ji>W9K85?l534Q-5>Lh zl7o&knZ0bveXDW}7aZ*0r$T4{Ey@pDzBKuElXKCRM!MEZO06`oAB~@JQqCPRj23d}-p3`%BuU74C3k))@bt54w#xb0APT{g7`p;a#uR zE3bAqzrA}vm#72j7S1)&28%5}VO%L?_tM4XbcN2%nNzOep4O+%_gcxn8Gmt9{WmX; z+N~S<(sNJ2Sf7 z-6^q6$A(9Lw7cjfZ0zU*Yct-tPW;xa)!X=xDVw)Fe%3`<#KiWTQ7$jvedt!Oj*0un zn3E+I=lf*ltz7$uUul$Gjl>0Ye5x$Y&dgruSE9tF$6u8>VrLJZP$RWWezn<-#>pd# zOlo#{;pFHtU-?Ig;e+4yPSr2n?E5|@xRjszYifBV(~h5S@aFB|FI}Q;?)Xt5#1Mb{ z_Pc=-+gtFJ_=b+J8tyAOqL%5i`bkNHPoCTFH;5$za$-~U!H3S2zFBXC?ZXRYQaw#< z{FKS#_)akmt3_{a^l<&zdRz4m^1nXnIw(zDwsSy{PZ(CJLQ>HW!m%0x6V2EpX{6K@&hpx7s?)1BptMXa| zACbZ7W!fE_+#xbnEzn@wn1Sod9#OtF;b#v&@_1|JaL?$mRg34{VH)vzO=W`F0M$pa@4ruT9CA6dU@I|P*;neEXp1>FrY&6MTDyLM+*uY%&U(HSboL*|$2ZO% zGP{zuG2VaC^g)wPC=Z1B3mO&uvcs!}gNt3>z3B0X^8Mpay;Loh2>t&M;{VsPzic09 zZ)&&Pu0DGIMInCQVAI_Cx^<|v4rqX8h{D66!8hNszQt9Ga0|tJfq50Pb7rB?|4%io zW^%$rk6wR^)H=}r5BXmsX8hU9(0~Z)RB1%^97qs|MiT^Cv?2me3&|DwL%6LEMv}fwppWVj<1RClSwS|F4s6tY%4%roI;_+|Q zH5$M$B(4}4r%_V`_>)yqfe}HDN9K-LUDme4ZQDBFsJhV zj;9;WAM$AYg{i&+4KAp9y8y(+?XH!P@r_S^!p-{0MUjju& zQbrJrHrWP%x)e&mQOb=~-fU=e*8M|=XRCVbYI-s7z^U*lAM}PgEMh4^*FwZWHe;mH zB&EfWnWqMXq!f!>F`gOs!lx%78CRP*Un{$lZ!6lUdO*`cH8wh5x>_uG%i|4Q)8qUN zwTYxDQ~g9J6P!4i7?bM_vksMZDd?=F3S}oo0?$AeLBfQQlT7IoT)X;+6Ju%yRprk^{Paw16 zsoa9PQiAv5dwE4_Rr6W8xR8Bd%cbWh^&E3M&`^^WnP40$`^s;-*}`eAg;&U{HE$kf8?g0wqjZKp&ZZj?y5L zilic|>?kHHkj8#I625@~2bLlrGU%2?4eW|cx32GzZ@`n8(H@QS<{y5Te|61?0}a(_ zIZI($qYN_TVUaxLd@P||U>@{Hr6Rjbpkeef!3rUei!>L6o5CM|7HH(x@my%qk+IR! zw|prWc4k3lqXC9$EKwml389>YfYOO^*+AW_7`+UDXFsy2eKLBguM zKx1aPmF`^qQ@asAqTC9kWF6X9xQ>6gp(^njvFiy0BcWeFbOFB3RtjZfJh7-YY5m`k z>tGRVyH~=)!ZLUBd-Msw3^6~3wd&_0E6lSwx`&{;5iUs4mAoGGs^f=H4clB3y=#2 zJ@YE|rC1A#sC}jP*Qm98+kx<(frd)FsAUUDsrsz7dV-aM0Yc*xZa^Xyq=ZwF8nQr7 zXxK}73dr-)#0WGeJq60bcdqW}t<3RnLSU>ng z^syOc%^jfe#R(ip_&wMh%ogk)Ro$>Ev3gjEz@^6YQDc;<``UW-4qd*sSLYF@=X-h^ zDi9--P9)%s@FVDo3r@QzPRg}~no(dNktE5o$s{~*F1RD%Lj-^i5a5_~U&l6_HoTbr zNSP%OgO5DEy5Bj**MRocq+K2X!-#b((LS&_m?Tjz!RPV=3|(MvxmkmRL9&VfeukHa zISqs{L``@3q_~NdGqZ~1G}<(#+>@Vci=3IVA>2@om$axB$tsp;bO+RH1d3-^)Lf9# z6rdS-t${B!wHBd$t`!P}e#MqKesy+DtFXPq6koT+qdZpRR#|#C*x*d|Q?6G*U)Ac* z;UUaW(b}dE)FLQ^&K|ALoT#g!BM4*EHSry=6as=jod_G|eXd{4-=%^dH}9GdJ|lII zp8=h&ogrgR*e!Sizy!k12w4~bN)faIsijYSC@{k;OaRM+)kaWlI*!m|PIN0;J)!CD zkkZfpWOQ|y73I-s|CbYuj_M6%=xifl4ekvoDB-f#sAT+e6-;2dQ0T#z@W=Cz=71z@ z!_E@Xo)^RToC6cqRUSC@T*aviTID_%_Cl?4%HL3$?0_KdNus{M*^Ba+RBA_Qli;8N zMTBO9D9g(;l{sG8%klMpe$N|nw(;hsn{Rj@*^+%B>gAfUb-WE|ZViqzNq9q<0qXk_ zk1l|sWG%TVCH4n76@-akgC}l?LL2Y^ba$L>fNr&0mP$uB~fq%xWO0hAJeooK8ygth(`2+0TM{e4!jG4 z)^Ia&BL$_(5Im+pj}jJtI=oxoG0)ahT^k0&Fy{&uG$H!2di0*+* zLSxP%*ooL5fFr?P#R%mfQPB+pVUDaEd?*6bXu3B*@*7$!l;gwKNr z03G$D^uN{t8>b6wp;$S@c;C@Nm-wi}g-1tEt^57*?KQ`3lkX<`8j6tN6rCMNg$fTF zga3=9(z66E$*wC>&xKkPh~5htD@-1+@N9ueT=}E+_Y2zw^a-3ZzSzLwZRbSxI}s6N zC`?|H#H3+DaXjI9C?LQ|$s_~WB?ZSPtV1|?>gdXg^Fl6XL9+z{6!sk0OHk_r5_}?; z4DM9?g-_q5!y68*8oF-S@3GlF1}FOJpqBdMti}8=w2jNVLKTgXJrV%~n?}2y&mzGI zVpN#KK$9L?hUq!yiOXJP?)&q&YWeAQyT8`5JMUv~q+7uJ3#}=s@*+JC@I1poh5@%6 zcxPhu2^wBh7+iP8>q??B+tVPd^Xf%n(<{7Bwp$%FZ)AX3jVX2u*Dee;6e7!yI0<$M z-<^mn8bV!b8Y00)u)+B7k|vJ^FWB>vWFT9yLlS}JF&4g0U)_z0)`3r!l(4KZCF5K1 z_V*^+n(7S&$tF|h2+7M8G-k<2hB<(nCJsUM5_T6Gw3bq6!2?SjuQ4N@8we z#~A|=t(79z`QeT+&531?hP8Ekt3H0TEhsC!cxL2+DQC@orcnLAiuzM=NO$PvpxSS; z_fmh_o1q8b06QDoJ+}R9&284%^tDll7XTf=m6l(udg^}amg~A%uD0xM@y%j`MQ`&z z=4;Hmn0+-{V%E|0o9RN+cF+S{W76k;lLh#{|LOmr832T}wyCDNA>w*~*0HZOVv45z z6H8t)#dR=b=rRE-r?t10%6JlpB1oQUm)%U{gTxCTbZoSdm*L+r@j&$dgMc7L0{9iq z09&yH<}wD8P7e-8Bq0#!0y15<9|P9^9gh2rvCM#kl4>1INxTrd9{sTp6p;`j4JaZB zrd1L3R#_9y+%DsAz=gtf*Sd5Nh&mMAvk}n5_yNr)Par&HFouIaD!fO8kfdeATvN61 zNWhZHV`pF*buw2|dk3s_cJ8}WUg#_MoC_{mnz9ZF|Bu*60Fab`ngO$;T(O<5~ zzhe>@KMLnl2zI@ex&mq_lEepvL&XzJxdpBHlN?*%LD4Z6_#hBB;GOBil@C_{#w%rF zd}JV!NoAAR?c|)Xju=b~>*a8}W&{JlfM;=RE_4uaUH}axV}UYe|D6KP-w=CSYG>lS z$I&jCQZTkMy25FY0F45CXhe^AblG$e4TSx|l-W+q%`!eCX$!#qNfwU;ioxw5zCFG& zGfgEWABUhIvS4f}og--B$OvJERr5-C+=~CcK5I*mAg91t4<B@*`p=tl zsG+GFNg$JuvrP$w~dsa128Q${9j4qQj$zS~Y)J1DXrzSMGY4 zfGmzs1%{zjj<>>u)-xXZOe@o#hi$vmEYX5g*cO^T<7~>HfOA<`UeWp?4E3e{c53~e z|5;TO3uui{1p!S47MiFj)FVme7;6&;z9?g`UZA!t1uaI|_VPcEEEr}QJzT4+Z~}*u zdosvH(uR~LqkIu{6F(cPghZ220-)t4`7x^VFOWg18>d6#5C~Ed!cz!N;{YL8w*YH% zRKbpc)=rpsF?1+E^Mp=ykq=k%uankFECS+N2@@lw2aq#{h0!w~UoJ60R^v~m>DK`L zOs9KHWc6`q1l!2?m=f(Q86-#`P`*LS2D9Kb&IHPhB2kMyjAKnct(Mo&=;g%fiB^`) zl~zkWEa%LilmU@G_+dm17oLyoY0ao)nahx888=+llu1Ry)#BCv`^ZdJ(L56^{f(>? zL@ThJG#(ftXsm3cmyV+j|9M8399zB zKmPNjjE;gvcogytEYKuVK*;w5vjm04`PGE*1VfNEckpcKw}=^~n#@H%8)MJVzR z{Zs4jEwc&8qL6rmJo;!{F0e4ah?Ibo)7}3QS4k!q9v2BM>GWv=1v0A>3}ObvSOZu} zTRQ|L(Ga@n?1{p-sEzeFU!9FhRFb5amI(LWn1~RWf2P1dLjkiI8BbY{7^J_+mlFgp z*d83`ZJts5UrkL?95$&*4xSE{_B-so5dUwp^S0C3ZnO17{J-AD!^XmTr?s!OoqE@5 zr_}%}3*Am#pstYR5zE1rB`nTb47Dg{e$G6>yqsCKS&~_G(_5w!OlzCmFd1)B_y3@9 z_y6Qa;geGj=LKhtOyvvDT~Y#Jdk`$Jc|@WYt5U{LsEm`Itys63GCh*r#NAOlG{cUm zXex(=^%MELL-MrP4M!aQkrv6$26-U$Wk%dNh}=Ne-arRPmJT4#CZ)xEf#();Iz=3ZWL# zl1Qn;u%Hl3A*?XDqVC0Nd8oVaPNmp^SvJavrF^iLBKL{Sc_tr)-rkn5(31lPbZsL)&BRGUA>so~t8w>sx zYi1z14X@5PF!nm2GFW}cF345`)-IfY60<4=Cg?Ltw38+fjlc^Qir+I3MI4MM+RbB~ z<1EAZNrI!~;)cBAvr&%c3j`T_XQU3O?B>ULa~Xicpaj4jm!kY4wGj{mp&UGXFiWD8 z7LK*YLb2S1QD&MhtRc1pk^-^}Bg7-Il2Ipwr-@VniAjshe%uh$DP@TZP7zk8R&G$M zpMx@4LCr2pPn#vNw0#&DK=w@Hw}7v}n^3EpxP&N#pNW*BAzK)LZi}VF;uH^wcEi*} z9$NQOXaMFkAc%_wfZfoHaHJ`tS(!fMN{$wn=>65=@xpM3AtiuC#PWiU%0VN70irch z#(){hQi(#wS2&O0Bf^Ej@erYmr+jd6?R~LpNPP|U3M@m-HDTE{k)oriM5O~}m1Wb} z`PNh)s4A~~J|lLxd_XF5Qh+5j*@XUyQHm0IB``o^C;=;~(=y!Rq$W&45HhAQdQJw( zbCP8vXqI7$$zK9*H~?BG$OYgOq8hEX2~q9EQlh|)GBS~{7;&_zr0$FII96;(@t7Ki z7tcbOP#n<&frJ&L90m9K(1!yJ$AWYi6bp$qtA-%cI%uHjb99`KjX2xnz*50496Lc% zRMV1HLM=f%!=)a%92)FoJglgXv)ip)Dfu6FLECE4$RJW^EAf94)&Lh9NGd=ALr6l2 z)I+%VjfU6(T*O88o%9Qo%u|0aP}awT8!C$3*fls?afTw}0>gp*hm#U(TjFD2WGOK; zgbpUIAqaO_t$hREzm&F|{Nu4w7_BI!iX?IcUN9xYS=Cb_(=Z^mcw`g_{&6n@@e5`1 zp#KaA)o@Bdvd{yvyx2Kf8iThM3-kp6OeBUA=}TdcWEiWK8u#}K^@kiQxaO2s7rq-_ zoA!-%9IqIJcfc%p(!BEM=^os;2m`3rp)W4QncThZSDpC3m_DD_PV3xU823Xv#f;L~6| zkw6IdZDLkAxkOF8u|{i}Xk&6!iFKP0p+QpxZV_nfSot`+@R0;|L0i4Do}r1YX-))J z^ebEa?J0};5EcjA7wk_lk1ykk5XpsOFR*KLX9EGFD4PFhY zM{INK0>U^5g>NCdfZ`47R4L!D{cWeD$1cGsGO;ZV8)TD6TMY37XMN$aAbo*-&kIWw zyWpa6=TXoC>|bWnxTPMS%ag5 z?<7YJLCy}LGXOr6q-FRid|DV40b`T;fKriI0M)ed7_vn&`O5P#!g-Km22;F%RZle- zc7X#DgOR#M?GGth*e-%xvhtFOg-w7Tj7NG|A7OJJ1N+%03 zGU~yf?xBq-@*)@ztA*S&C8z4&+ms8ELPUu$#OPWlJ|{UeumU2qKqiJO1LD0U)S2y< zTvhJJ2>)L}*`hcsacE$F#XiE`+-`+k1KSI>;kK&H5}StBSFHWP&7WdbRd-A`!1Al* z49m(EM=koBe>IF-Twj%NGqaiA8Efz3_46H z?HMV|#?CO#16qYF(iMi!s7JL(E0Bq*Gw0D|8s=`y6ZfYV&X#YRw{53WO7!M)m-BxL zFu>qebb;?pB`p+fDQZA%lqF`2!oSkiLgyFD4<#n@4zZQtgF)h#7YcGnSd*fV*Jjkm ztp)bw?(CYsSA#)?{L4gbil`A}KytgPi|pKh{|lj7{=mPWh(#iWpimYoy_Wu%YAi;1KZTI5oC|2PhP?&a6 z`iJ|(p`Wy=T3Bq%)S`}$08c>j@F-YyegRqMI{z9_uj}QxOHaNy8nMQr&+gZaeGTxQ z%eZLjiF|%z5EjQqsT7G?02dJxh$?6-*#)7F90Vy~QsN>9Sm1?3H8A^8zpRAMRjOS- zY4xS&_=n7C%wJOW1#XIzbdq}f(yP-TO%jd?MpNjcp=nK2V!3k)RE0|u+VX61)?Pu;vG#+%`^7=?KhcI z?7KC~m$_@~;)ypeU3}*Pt@&14zdiF-egavQ(WDg6qtGnbIimFrj%v6IQf^qtz)|oWiyDsXzY4p^ z#}W$rLUya}TDvLdX1Nb}`r~2!&i$^1pTFNq@i!pE$6}_8Ha;{~rsy9;rGE~SQR+a# z6YGtu^>AW{t3^Jn=JR0BCLolhj~0wVRCvI!7q<&cvZ=D6zvr#k=hyRkF041Xh`#~R zB>>W#eZXWfoJX*_u;}3O;(KG|5EXzkM>2DdLp7e$HJD9mFa@6ke?ydK%=b-Q{QSDI z>FXqiYO`(~+cjl%-^cz2Xpok7kzImt4p6{D@tH@b3JP8YTz{)-knz4B*d&4+F=6okz4ULn!YxFK6W|qsSy6h={e=CT z=B~fK%pBgZ!O$mr+(wK(QPSUlJV$xp{i%*Av%mCEu=Z4hmFrtzA&`zHxQeL4%YdDI z9IgB(@^s^N|2nu~ZL0%4ltmZNJX?ZqeBEMlQfk4q$(BysQ}!O z!M_YC)=V4vF%hB_lqIB>0;u&9(Ks-X!mGd7VCLcahIg+!SDw14NejPGKc?Tl;AKER znZhm-NXjq(D)?yNSbSH)XyN#z^cZ3K@nKN+h0#PZ30h>YxnLBk$H zgv4P89YPV@Oh}NtB(89IyR=^IH_dtzGS=j?(_AH}PuUM!d<|{Lzkw%&EsmT8NtBAk zi;*axgU64RE@bt!)(*_F!V!!74*~2ox(F*Su(SOA<_({`?edk8AWq#C)60zy*WO;}a_huV9I!NPMlJgDY$C)jh`f=YI9 zY&0D`vYPaX!_ndgkBpOA3O(a+Le5M&Bjn29uCmZ2ba?GLf9&1v;=s^Ok9w4Fty}cl zs2!_@>J80k4R9Yw#M@{C1$!;}Uor1OI)Sol1)WQb#()WmCz6~DV+npG5|xGCt=;UF z&tux&te|Grb>C+mcTbxbU}#DfR69I{VF3>h2O<<4Q367WQ&Yah8zBvib5%13(VWf| zH5N=Py2fx9n-!n>4Q%rAR?wqycLUZ{=$}z=Rkbm`h9C9O$t zoX$9no-67n@E@3SDTt1o@e_xy>76wpE~T7(ItKUh$eyr$O2acPR{0tl(@pTU@cfLY zyacRB-$OQPhEo$w?In>W$LFB-VpK2yRtVS;xQJ*CMsv{fYn2O8=dU^4?P*uV&wav~ z)#o?(_!%0Jk%1qK9D&G%Vh|2lF!OYB0`CE%gN7Lx7H1ngCz)f{f;*(05x_AO9w5sA zx1|-2M9$uIyjw>1?D^v=r*tZN@|TaHAvtLpJ(k-Pat2?^hjS5?c7_JT5@>-4Qdt-6 z)0iMdfNr>Wd+sbecs6)d#EjrmB^j?k6sT{ePxhUSGVmx?cTV1fRssy?VD^-Fo#^ zeMvZ+efHjKttn%UIVMazrtA^dj5`)N4TKq{Xg56}$=2*5Yud|=Qu`rqAH!E;$OpKV z2t#VnDJ)x=DkQVo$@Wl<(t4eO7$_^<8UBQ3)x-J#p-#Xedu&mX=7%L#c>xK6O!abUQrtAkK*3=JELNB1DtPP0|+kEJlHK)07d z?;bt482QOHc(EAxe`z5{)*%%`ZifNv6M>kF3%^8)v<}o|gqByqH1cRg7`I7~J@tf} z4I+Kdk`W2$gR(3vJ$Jtg``K2gh#!NAvJ|SpF3_|vFHl^?`Y1rh+Va#^1t^pWdx9>I z=Y?U>zQz2d7);kIawa1YCDsmj4Hg@D5*MNfq=Lmkz=k-0p(hjDYXhIcM-mRC-vE44 zNjHspOB@Os_L>M#gdUzKn(3H#7z{^3*&8AnDOs88;>l3y6Rgx^rq;dZ<`80qGz#J9 zTq8{BSkm$!Eg`D8*g8B~L0bT{m;=9sU@8$-*19;4acX%Bs-RABsuQ(P#2-ZgY;!ti z#Pf3YO5nAioMbkX=siei<8_+LKjy;Pr;TbJ)mDJx#S?FY1vh{*rxJS4qh5gpP_a)+ zH5>pWQx8Il-8#~eiErkD(vQd5$*2-gKF1mzrXtABH7S*1JAL*P99`Xn)L$7no=fF= zIj*>2)o}#*u*q(OL%_3&d;@j>H!@?1=xkt%u|uL6)I8^_ODzVH<$8z`sFIa{z5zhT zoR=ZPql3xe3pT40>`s81G9~id&yK`^>ME!cAV^DyYkb`pbF4Vx2uwTBB!rkmhMug9 zJVnyNFv$=bR@e#34VH=}*zI(6YtvJpqqSW0PO+!}Cuy_88z7D+;KC4G4S->Urw#BO z#kLo?KGq5;GNJ}7Du)qO#%4OE+LF$IlS!g9VdbQliL!XVAR%G>X?>`6qg^J3H&wVs zrYkCXVubjy;)pS2jE2nZ5$htR4#Q^oz%t4yz%07?v@r)+E6#!GaVTg)Psc-K48`g)B*@HW;Oc^) zqDZ_6qT+T2xE1Bh;Ey;^W|4(DeHQBSjtwHES!^4&^psrkR(B^GAXg%#7Ai^avQF z8mX7z`^F0;;|Pq3z&VyJkXm)l&rFm=wwCcS98B0nw3Neq64(LjLTMRZTrkBZ6uwb$ zhvOp>JrE|nH8OL}A|Azp{Sl8NCu5ID%0z_Tg_pzezVrD4HAFe{iCP-@PMSb{V`w90L9=WA~6(uCG#~5>q+8k7*n)< z0nFlwKH{XUb+YdLer`Hj$ zeqMz<54gm6Mq4SK`8*DLBzoj>-|MnB@Kg6NcVD+X|2`b-%yEMZlPT>D!5IzfKrBUk zcC0#CohT{^;H6?Gk|6D)XFC%0I zvq)g{6gxB8McPXhK@LF6EU?oAJ=iKWwMA1VoZm$+%fpLhFq#4WP{ZJqRT!>Fh7y7H z$Q(%K5mGfv`g2ijDEkfXU%i@0wZQCCpP|(6BEjy%<7p>|vdM^Fi=dM#aAqBG1VI(y zL>2@WTY-nnuhi=b7%LGPq0#`SiJrvZkwipN@@3|_GQkE$Mv-a@tLE97uE>0w@Sv#j zlM$e>i^Cx}3YHjg1`ApsASZ(NiI84#2MiG=$vr6**cyFj)<_m!MmiHA8Pp`!LBm?3 zh{B~9qSg>*t6Od5;j~}^G+t7iun;qGbJY4N12=#m^R1)q z!&)gwlSNZ;4M4 zl+H{}7BHGYEd$}A1Z~}Yiwa{-I-k7!$-&_W#ORo_mKz64@dU(5=>p|s>fF}?9!q8t zgM|bYV(lTX8D21#Qau5t5J^1*)A0}=auE^RP}=seg{6=%Acmt!=SVPxTZq`}S|6EF z9p^oABOMMfqB=y4p*WFLLO>ThI5h+V3A7JT4`vWC9%vrUoHi8{4>(H3%jZZ<;Nrg>MMJWV)R6E*Et#5O&t2H+aBf%94t@O*R90d9>n0%;Msz`LS z^hI!$LmQKX+c$U*VwZyCP{df#5PInzXDl)ykvWh|Hp1*7phPgUlmwuL3`rpFOW;|j zfVRjESP#$q>zxw8B878ll~z*R4co#Hp}>>{K!WwdMM;m%q_<|7$YOy=Zyhg*D_3*w zFOoWg{{rj{KZbY9gfP?fq3TBl6HP`(Bn^QUB9snmmls2CJSLh*FCX3k#>Ue2qp}ds zofWQwL?~K!=dzx;AZ6;v!K9giac1kvAhQ9;u<|T*)=j>%d@Efl-)-6%#<@j3?ecC!)fQWa@JXTJp#~8n#jjsp-iUVa&&R)tILh>urkship{E!x_quE-9@rksZS=QZ|6~ zlkN@_IYI0xnQ9IZElW6hD#+QJ38-+S03L}jiLMm{e&n1ON#~1c2MZPBaHQb>lG3c1 zBmaFp^3^C#!Zt7iSEUe1V%l)h&^m-Fpnia-p=^dW0~<-3&n)=5QYO3;Uls}^0g%5s z2}6mHR$%-Rg-KeopomPWHWajiAx9%_6LSby}ZD?w+Kxd*0VM1=@?!_wtKiivoQh?BJ6i%5YK zSZR)AJNmCDCv|G@`I%-CLVQ5nsvW_MvOuw@ILFGimsSU`@*_aG1~5fe<;W!Y(!W;6 z4y2pjw8&S}TLpAX6<0xVl4EIvFsnGp6?Jeq(;Z2c)w*?`%O1KFHANC733GrE$wJ*C z6`7K=EDftixEJkk=b1tzjh0En@eCfGIl}qSy+)`CtT=WLkQW$GfgcipWeZqFlx4H)@vyM50P7T5SGR~b8gG)5Vf3S<*by7x>>!BboCzOE z`3m6)fv7}SPC>=8-i6Gk6n{w2tFtjd#-@vs1|tll2Swl@vUozo5j-O%;cW)12y=~n zm=JHR`HM9#un8m?G0BXGJ0UrQEyr;nKB7iTroY0(kt%#-s1?kMG+HGkiR0=F_66RA zwjh!+4U9O_Rh4wIr$lT+DNt`$l&!#$Op*kPCUDw{yp#U_g{1%2-ScjsZ@?zo!hpU3 z0sb2>-7mM_F3bTa>U-8V-8abRtj}PdiryE!M|;=ty6ZL7tC8n4+bhpcJf1Qm;Enro z_fBrl-R8Nqaed>u!nLc*KQ7B$+Wb57|NsB>Uw;E^0XZxkpNL}7bo}hNWT=E3W7=`JKQ0wTdEkZnvev?N+7_LBhkDTts3Ka?$SmJ`s$g#!RIlXb9z)A0LEE1)6z_r zWnogyJYiPIwHe9)sN1kkay2l<5qK#3m6f-__(vfevpcCH)>_hq!_iC^#&hH0DHy}I zVhMd$U|mBBX_#feLoZ9UTb9_Reh*vjNDcs;R}?K&A-8KcAOi^r=yfJvE*)U3-Gu8= zlEw&=q7@XmJoxu5Vst6J8snbuIJ2Iz=GgZ0k)=CoGpB~MK;U` zNs0TU(-E+qD5X=VA`zsd-wIB_0ic-_86a|AW^dWtv$0qGa7huc5Vn{WK&ZJJC28vQ zi)EBn=N90iVx=+eLrZ(ws2$%4(u(yz?4}T!J;CS)`F;+#;6h;UDHp>X!)p>_1o^nL zflEaU40|>VOOL-Kx-kJW^=$EAZJ>(eU2!&X9+jmF2nMeNUJ6xmfwxdRq|M5iJx9N$ zw^HB$>#H9aI&!`UL@5-k5!q5gZ_PeLv{E2N9hl%u%vdQ9+ZKKl&avk|j2Vgr&V!-eXHb%J}R zumqfIyjjXcWJUs_Y9df^Hyh}Gbk)KaGMOoWqfi4(LP5e=SEdA)F(bplM`%E2)^t;H zWm5_gM8KwY0-OIgnI7EqPRZb6(`kyx8mX6fSPjaB0KljRkNaiXAaDe0bbj6SWS_Z@ zns!j4IX9}o>Fl~@3Zc@OJSIlL7i+Pmm;<^;Y_E)i@K?(y-BjnWaoKrJaEc z9aDN_BsKT~E{a#g3`&i=6{GEckV7=NrlvU9^$c3E&>W034?c)TkcjSQU^rMMMcc)2 z0R=^*^Py-@1R>i;tusA-IMOR*zo9^iVcw8xa+A9=sxmBSrmb+tergJ1%bMS7Y4r~2 zJe6|Q#}^NVcZCh9IT^T7a8@Afrh+x0gE2S}kTgXHo@m>yoC53IP~;|sapq~6c<4hS zvA#Km2kU^mBye+VKSL-H>n4x`2qRqh;Z`Kk+3b*)a~-`NpEbV}(1t2&FRIZ;(!V1`gS(Nge3L@KJIn(ReUZ?AzKL2isn zh3-|RqoWZls2xBl;N>Byp)dnC`6I}u9gcg3JwyMy*8Q}wL%@$M=nu8-v?ef(g#D=| zh3P^>7*BK;VQZ{Ai_a)OUWhW67 zqK;6MZ}ApjSVeAAN{;}Fag#)_MctC8AYof_eGSNgE$gJrSEEfQAc17AW-BX5%heg7 z%PHa_y%4x83YEbr*1cQUKm~PE!rlR!W*C7pOpslP5v|P*@Ncj-aBDaUyn~!G0J#4Z zcY&E!xih~*=uW^c0x3$s?Sqsntj(cmTPeO<0o=%V1!IR1kxrZ~q4 zF*s>*2*ps7AbJnS0*s7t98JtOuf3cU%cX#qF;EJ?fc1*N_NF+5hQ63;HOp!N2%}r2>zIC4qYT=U$j5<7ayJZK9?t{==1Rv1au}-3q%COr+*;={E0~PG896wU zEf3`)KD-Jd5h-|?A}o{RfRIqYr5Z^TBCyc~&`)83Dp&$(Fc9SnI1^hyBNLe8QcrQ*8Hqr*f|8#x)N z5P)WpjZbUL&g;WSRbmv%W>g%fAeWkK@n43TOTnQ`4Womh3M8f*Ga6xWnziYM^#5U*^GqjHzZ1zi|2DiG}uKk0N3Gt1xR}6L7sGS5hgJ|F8Akla)goF%0!sJjF zmL3Bjb~La8mIjMYvh5mjaKV9%Zj~;!hg-wnoqxULp#R4Uj%+`<;8@uZhee$+Dk%uq zR6H&PYw=KqaS~8W&V$k%g_?x%Q4TM(a;+dHCxXW_eqrAI`{#}gE_kWzt=vrxS1oeY zYgKHK9DQ4+rbUlAI^RANzUN&Q5l1&SyHlu#HjT2VAK_G z-sy0%jEy#7sr>;rnN|lFCXrPPR;`3Xp%lqZs&e16d+dmZAKL=1Po6($VWasyDs8uC z45D5v!C{f|!1f74O3L?!_lSL|JS{%@Se^t_C|SyuvqI&Mi-iA=)i33JRQJ(8{XfaF zy8hT{JANPTw=F8;3wkC)kThx}67gzDEM5z<^3$A6DXOFOZfCR)KtZJ*@^2keztG29 zg3jf+^Ws45>qQ@wxYX00kxtJ)j`2ly6nXlRAmxhg=>rGRJ}L8EI}q1FIA9S!S$IBQqQs@hFJ~fGx`@ zCe&N7*`aq0X%5QE@mFwoITzuq{A8cJQ(HcGkiO=XZii z{90swNX7vA5h_4Qiv|unJ9z0pL> zmILqD#99=%mHF&hmkag&{HRUEr+tnd*jm5fnl`IG&q$?-j3BO)X#`;p*$O3=M4(}8 z(R5z3KxAYp7DcMhCKG3lGbDdv>{~W1I@EH_M`hDb$9*?zWT~LOc@CV6$Vj0FVKXU2 zH)eb|InXk73D+i&oWfqC`>Zg5fl&=$F96J~Q86ilw=S;y4Y<3G0bhgaed!6%WC>9a^Yfoq+edZ za`1itjIMJu*lMr!SHM2chN&@!k5B#k>k`ePGU8~bNhz2-7WS8jiwfgZEJz}tLVTc! zFEUk`Z>ZyNG?QWx3X>ylFJAt$Y2~~^{;d2>-QK@{QRL^Ei`R!{#8T7|MphCBoR%|G zXD>@>AFd1Anrq(5W5IJ$8OGr0s|Uv- zGGgeI!d(JzkwQ2FFhbu%434D-7mlQia1)gD=>S(H%A-Ij5FX9~ED@jxsw+21T=r(` z)RF7Uy;(K5UwDax2fJF;3CV~i>**XXfh>(mrfdeJJC8OteopvKOao#BldI6lT#_}a ztI1LJ2>ZezE>{;doOph~V|QQAiO<7V$8dGVF;Xu8RbvKE~C3lG=ldAzd zzg8K+pap@bnOit?!Ha`C^qommGvPRk@P4s>dq{RG!dCQ zGzZa8#z<&FtEZ?qL)M1LZY<7;ivGl|)dRNm{^xA}j0wLDZ~U@a=*x3sLNg-hQ6$3v zuZDCE`LKy{g3z;(P*R7d6h)9(vmCiLyW&9z5lD?7;WEN+#!b1J=>Hf9Vy_Yxo*eHL z6I}6lWN=0}of^cLtZ1Q#D6AFlBx>VAGOcSZwOW?FQZ)nc29TsPOod|%W^c+z@$W~z z+p=_i@|%2FKkjyEMs&3qSE4fPbZS6XAlm^i2<`$XGQ=&z8RFh<>{MzVQ4JNHV?jn# z9^a^3DrH9F)^`gBdA5D?>mLIfys6Z<#lVdf4*q^MG$V}O8lnfX_voxf)kLu#Kp7MUy`1Og)w~Q#9)Oq#fUK2~K+V+(_Ba}iWr!Nal z08ArvP0<#T_F2@D32Nd;6SUsxlnHo%QvgFT8GnOtv_T^~Rd_ikx%IwNLzgaWK4tZy zZQs-m&j>-HS{<=HUWE}E%9>}QbLwTd7$dqCDSOu;Zvc+iJVRTp|fuMI~Y zIc%@5hyT*xwfpmo*1a~qXcv+Z%sV<26(ClnQIOfRn_xR5eid~{W<7C#rHB}a?SQlo zxmY+?CO^C6Uv|yj{Yt~aeFINL*ejhsa<%>OUKxE!q6MCrR9_;HjghtD>Ixu5@Z}}{ zhtVOeCnkhr#b|CS3Chv5$Y}7&u&fx_8`-qfhULj2gU*i*ICim2?dN0aE^Ho|(TBt` zbQFL}?J0>7+Zn|OvdT>Cr3OLg>LsQE&Y@RH&YZ|th&h2?iwsimfSMKh_VP>`cJbhE zyMymt>7O!oM71el8K_c~sIHkFfF9<4#=`K6Kq?kji!dycD{u|#+?d%Y%t_Yc3-=kH zvaxFFix)q(yz)AB!p%ODOTDXADE-Gw`G3`b;{hQ7Hvc*PANig23-fdFUEo{SC&MS+ z$L9ULcLlHG$p2qK|F49{UXRb+U%F31{=d&H#PyZy6xZ_5|7&NvY5Uri@4qAe4|bFx zn?k~89CfjfHwzyE+oFVtIBte=!!oMij-Y!>nOg|*u~1-O0u-V@vp4Tl%Ky_x)-`VCa@}b_b?aBWY13A?1i5!Q_k-suxtzmWrN15vgjXah{Zl1m8nz18ggS zPSf@-`m@KnEFaanzw6qL*p~0I=1wfw$5D#oJyP+;x3NyJa&a77ao{XwnDuJWnF}8p z7lm{MPsutT_-yRIp8ohrqyy8cAuAS2CLUUd zz!%{uLdfG9aRjx$lHG+zLCD;B&Jf;mTQlog0*IpAg=%(ThdL%4-P^mMe{{~9iw_+6 zWc=(u+MbDVU~F|Yq~k1rWH3nK4pX&{N>#$;ck;c_Z;rC1JOc=yv?M4?QTn0Lv>1ir zSp&Dt&Qrp5-Rqsvf1O-j<>QE-hQ&H~ezi*SaW&lJvA6;vFW7ceb`8vlHej9&g@^(& zT*$GUn6eTBL}3RD92(Pf-?KyadQJbsb93vUWo2&GJ#_HPU&9Rl0gio=Va|mK@EJ6*j$sB^9ii9UrNL$;2VJX1Gm#hTfO=OdPm)5KqY(Kqa_N;oB zo2=|nJFUM*gaf0i%ONF~aWpb`4onIXrzxELOa*Hrw1`(3!(QcZ2S zE^R|=$LA{DIPm)|0bO@38=P45X)i|+vdIkYGvSv?;kY2nh;u`F5gGmq@C)d2FsZLoVf^7v>z7Lu5_|?1h*IslSe=p2Ym{vvQzwtAO0N|%cfSU4evBuaU z+`J3ZjFY46ZKaqC&r7L}Oegcb(0$C3JMZf69X&i-wMWlxUAVhutKCtE%mGtaut%BP zN&OC@1goalXbS^2{LnXTW@ZYf-)P6Gja{&&!j<-=5+W*whB^vT zc!B65G7c~kfxmbJ#CFo(DQf?0v`joHQwLOe9U&-2MAF|zu@PhJvCV(=KHOrbcb8Mo z-Ta#c+<8RLuxZe=v8DDR8XL ze@Z0lV>1p-xs&hF&eHb_M3fG#J8NA2+zUb+`RPYYGOyW+_F<7cKK3!(7$O$x(Bvh4)CfO2E81kN5PMOShwD-~02^2t1L*+wnH-}sjfh#O2?wqRB1dviv#cKQ zq;#h<9pXOC_VdQ+4bIiw{brBdk%zXK46_5>k!GF??7&Z`QL%(;W@D+~rO+0P$}^E{ z5Qbs{r9ALDk~V(F`Ndz1KUB>BRh>zzbA34|+>x8&J{|QUah+CMDNCV;(*HE^7C;Du zNQg%$Y7r<@sLJ>Q{NneN`ro@R*nDKo>2(o~Tx4j>6l5iA2u>od z&2&$~`19@J+=hX6g%hN_8Y416@H%`J^3=FX#pVwyU#9x$kLvw&udMI*S6?-|x4w@f zCppq&RBE_u9*8@WgQ(kq?%7EHamt638NfbuECJr2mTx=-oHvcAknQusu;FiP8F`L` z^BcZV1*Ms2z>y#-R$s z?kDCdq2IvSF86x5IOKUk)h8WiMjme2ZTqC2hlhtaveWs&x6x&Uf)z)PGWLcmrkY7G zmH@|08rVA0ob)6NCarxS5O@o_G$6~8`s?EBI--{?etL0HT;$F6%cC9H2n-TcVW~eD z^i}HsrX1yD+i>84rHa521rtMLR>5sGsMeTxnJ@xi3%2JL?B z9_q+S#tkomNqg}7IF1a}iV6sVAJI1D$VX!h;io|02_k(QtSc`CcMui7WpikOn6{&j z4xCqKW#YmCRsKovZxZInLLPy1xShlUeqm;m7d0wKZl7`!1pf;5BT%D&M(`RT9t3Jqqie~Me~G>uF4y#%pnp#EN}`0 zi;x(Sg+cO9&>;3!DDq)Gvs|`C;P$@OO!q_Q9(dPY*JjVjIl<*`i zeWF&Z91+gkI4sk~UHpSKX9C)ss9p{a$>OC1cA}&r8MQJM1yYZ1ySHoV<%C*0vlo<{rd^%Ya}gX9C;C=dbJYfBz(2o0=g0i>3jon)Jvu44|P`kZaQ z`$Ug939S}YnLEApg|2nCtmqx)@St^f_PaU-`I8BRH65#X^&)GE?SXO))yY>)=lV%8$ zD8*4ZQyiH=i6cLFwHBa${`p(FK~?%%!1)nkQn*XdC=eb;TzGjHk+3Uj#8&nT`! zMN8}=hPRl-f?bToLe?SLFfdbLbnKmD8BGheYP@g}>}9vDSNi1mXF-c4)yw-QG^}1L z``X`#|6j>Ux4a5_9shVv_PUu34b zN?#B~2s&JG6h>1xNpTE(0cmCHh-J-AZOxXoK~7|Dm0TOkjlm>xGY~ltw8pU>8ked$ z`=_!dQ7p|+AXR^|eN_50SP646ZYD%~A+jLI213Gv(i^%OD|`umT^u;|L>Y5Syf!3E z08GK;ubGc|Rc384Ka0|Km>leLvI9a>X40cHCH$4D)&Q z!U!y>txRa&1`lQ+&Nxc2AwX~tdoR@|))fI^3SmQ1WExT=ThM{bt1gPIVylGnWfZuW z3W$m?@$;mKOx4y9O<=854_cXcww!~Q4WxG}ED)YT2D=)C39y2KP(!?+08;W%dOgu8 zKoF+XjjNuvtnuJ%Rl^GD9)im!)}6LoNU@F0tKOe7qA}&-gI2A9-kt5gjL6%3XJlS0 z&3u%!F9-nI7_b4jQQAo~%oI_JLZq$}ZW@XKREV{?(iHbnW><-YL|#C!j47;1G-kjW z-gu&rh`Ja_vr$250EH3ALwHoZ^*#@Jm8+PGDqL<#OK9~BMem)qmV}}M9%+REJf)f@ zQDQjc{E?qV&?I#9BZtCiSdUKO71wal1v75MS)z4F^L`m( z0saiHEd+bRtT1j1u7jHiB~g{Zb1*Z(676YGEH;u^-sUAB5}`&jCdMFZ6W~vIps1u0 z>6xjnI$Q!IGK9tCE1gibvZt@6gHv1Bd9$nx$dRrY6j5fjALCb(V5he9ZYEvbbFRmPPt6kuXQP1d!Y3UL@u4B+*UvKYfz90;31 zG{pb1ZvXXf<02IdW&IjU%25a24jE3tK2fNmt`}8d)7N7f4?!uw|E!E@Od@yMEqP(i z3R5DZp4uQqV?x2gM_|k9x42nqk>p6)B#_vH5%$kSvqaaPb>WaY!4(-WUmJZvb7!~1 zc{z&2k|j#gDKND({lIZp=X&dUakL=mrx6k~8Gz)P^6cCphWE{sdirgIRy;NWhg*2v z2}q``176IB)mt^jfr)a4biuV{Q5L{W5s86qiPH1|^TLFMAl(PFPkX!J{LJ=G$XRfJ zTw$j`j?i)L-oTA;Eh!-!024*pNVO5AF!^7^oH0}Y-Q$K5VXa$_h$62!qI^o=|48lx zln8toWmg)^&DsK#7w}XHToGXcS0Yt2U1<3Eo@KTp$c&g>!u48~pGKU59FUMUge{b1 zQ{Ie!7{Nfc|5!N6?j=D?q?(vD-Xrr17p=trnOqReM$o5%CL;Q@tVsDrn-g9`w_|`up(?d z@Ic7VAO&RKig;dxB~3Sl@TO2@z+Xub)iwQR`j46Y5?lb`ayWn3K%@WxFoX6Yh!1q= z)gA~KDc6jqlCqIt(Yls(ZyzfVnj5CH8+#9zfp7zp!OeX!iNUFuF;*>-0fK-A0#U=+ z!>d$qM9bmAg@@-QJ!2y;qI5db^p9*UJiLor=3pFz2|S6MIK*09$3n`(-_{nYX3P z+IF@#5X_@ii&Gwn%*8_z z(Z~QxsHamUh%O4JkB}9wigHpw-Ehv01rK2Y48Yhd zcm3&_^VB5h4T#JZela+<@fvO%(Ot=|00A@!Afe1fj6wm3*g3K^4S~7*J|*)gqQie- z=kYh=3cIMwM$N;wGx3JLqo8KK6*e2@Y!?`Vo${8nWSZIOP^BhW-(tOW|3Oh9((pc;_{ zN+Cs4!g*o18%logZJcL;ZHDaf@ej8uSR*X|yncuM()^0}p72dYum2fqgwIz#6}+!; zxBqRgnO-eDUwY2*Y-9cE@!VsHM^_KaeWiO}_iS#5-QwJGy6$sLaLpUI-sO->x=T^p z3ELO8ih+Fs0|T}O1ph1P|4;sd6IdbPF*mWe5lkI`{q2!aRKVk2E*XCz zePd#q;b%#5-_~*|Z*RFE-Xy7_leso+GI6l>pwX%XBQgyl`K$HL9{%zsgnH(jm}_CBL5HY zkQ8{hNn0Rnt%tzgVtntn?wnrgMQNUqN-(N{G24y(s_+zHnP`}YUejb^&a_qLHP~Eo z!_VY&l?zL5z@Zb{Fu9PP%g}@roMc0CB2^JXz@&3NF-1KiHI(i zXf-O6#=L50V<1P%rge_2pmjVa=aYJ63J8$Fv!9{vR8|p1D>6+v1faq^Z5D1bc?oiP z*kv)`%~o?jsh47f8Y=3l8xgomTVGWN2+xnumoJFufKzT51x{40t-CYb7-6_5EifmM zTir!dM_Xg){*<1?EqlhY^W9@YMD?zjjyHGTz$=q|&^Q*RiVAdU^r_m%vW6-v@6oHkNqcRyvrw$QYBJ5OcNy8B5tzLiWhNBwXp&OI1=izre?=W z%&$9$nY1WHQ2|t^ee(PSnF7i@c2=vxt0Tt*|BJt=bYu={i`f``kR_4EL1*xR(1!v~ zW{XhQEmB-WR>83$q{NxT0ViorF?0}zNTjOTHQE{Gp z!6cNfM;ddsYM*2}FZkOaP6{Q%aJO)Tc!Hrb4KvJXX2Azzk&vOKVl#qzDBXrKdCyvN z5dmdUHNHHvTSxM7zo{NtgX<;4ra_YxWDKzmxMN(GA52XvGD};LdkE0-JFkejFZlbh z!2^Lm@tcyN>zaxQoimoCN0Ew>zf=hhPTjB>ti6>pZ6E_%$cUK#jUI@yNtygZ2sF~n zoYn{`V<`hU*&e}p7|oL5aLu_gpwknQsdMFFwnHF!H4J;Qpgu1+8nAy7i0Xt;4p21p z!j7S{Ep$^{l_;q+mJ|F`gfs|%?Mb$P=?o3Y9@o}$EE=j(_#+C}%uo|-5S$}Kg#Vx5X~;pPFa~9qeMaO*Z>;AV}+@c-THNErm#v(Iz|i-h=NoDXdjX_(5g%Y zUWaEAAfgLnyctsjXpDyYAx^$zWdv5BA`Co+eIQK@gjLjplh`XM_6Rawyt8?s4XUSg zPnv@~;Cv?g1Hv@Qdaz(j3P%2gbms#Rn<_|Qgea5l8~;5G_PCu%HWs`9z7%iW{gYE; zy-@~Nw5SosObs)NnIkN~$y_*;2tfi!oCHKuaD+lVqasnq@|nUvZO~>Yr-%+#rx-H= z&qe{4sY{v7WH2&Q$ztV5DUDeN8)VvR={(s%`hBb`YO<>IDDFO_yv5JhXWHaDl( zYJHY@jY8;sqU;6JD~UVa9MgSO(GTDz#rEp;g>j176&#zyon)8oUo$3vml>}Da>!sw zZ^Xna9P&$84c$jzOGJ(^vy#vxgBXNb7f6!GR{Rapp11!=>hnMSk9DsjDAkS+jpL+M z3aCG~mvE0CEE+5XEgrGeO{WhEBG3(l=Vbhj_olURU8c)I=!-H!naz$f2p7&_3KB93 z_W3j#AO=A%3iL$u<0M+qdu(4)zOa=P?GQOaqDu>5KPnFPM_&|fR9u-WERnk=lX-C*JW|6m2q4y+w;0{#Ck{l9X6UtL?6B82bNz_`kEIV;pd~)iC3my7`;|AW!Z=tqp;&Q`^vJ97uqQg+^=zc^XWr z1rVqpy795JzFGVA{5z^fw%$9IczrTsQ|nXH?I)&2IS|lPLMt&ufwX(r%oXCYEX%1W zVZm5dr#zRS5zxP4rFJQSE71C)&6~M*Mt5Gaz5Y)tHr^?{WA^=l!66Q;DO!txfwI)R z=nLQu5zYf7rZGEU+uC7gDRx99Oytbbc}d)Hn_@*=$&3Y>=o?}r1zz&b2Sb1Vj0bcU>$Z+IeetP;w0k3TgAfu@|zU_ z8DJg6I+sp8Y3EO$`pdM=JKn~OuJZG);<1+|pC7U}SC|6{7AX1f!`J~(ydY1{aTSgf zA%{5r5tLrwyy8DlFaubK%o<)!`Bw*hoknWmCs;~Z*u>&#NY9GP66ijpa)SuuP$ReKX2HMrx6f#Cec|fW zw|lSmUC}1bw)<7X50^g}?!Z*(G9cn}%PtmyLb@=X6y{_y2@i`qrkSTHt&@g(im43@ zlW7D4oIEkX^T~y#3k(0%^>p4#TedXHxqO)aLAwL9sY-!|Pp^P=?UjH5Ym0D9SXhb? z>X2a-azoBYI4y!Q(Wyp;rE)}molEsA6Em^m^bI+07M?$(@SkOO?GAM`V~(eUCB#TV zfW+;ps!ECYKp0q=V`1(O=J^u{Mu|wp^Bu&J|4^-0zfQY%%xxHPvCX+DUwrQ!^y$Za z98HO}NJ@qnq#Hq{N8}Nr9>B^%_<80Wh{(8h2BM!5EI+t>O4+3soqy-V+|b5{`=`{c z((9*<=O+#7Fzey^PLYl#MEW+pJp$KANzIg{g@ong{0bdfsL`16$|E&6bd`-Sb-M_U z3$Ee+MBMtVm}g4#s+S(sPpxPh>}X6pKgl4Zy-&z!h&RC<(itNr4$?rFV-o@Ku>FCJ z(lJGE5SsQ1yzX3jWb^r9!5J&dE#0)PVYx2{_=GwzOFF1JJ33PhKq^b!&{DSfkVVn} zUKPWasu^l{L~aK)L{$$MublI#YlVmr?K=;vw=Ar{caFKA);TsR#DQtjpe%EQ4`3Cy zM9`QvS17GeP6Yd9lT;e{2UV$Iy>-?l4JwotJgE4i)E-~7sdMLP>$dOi-1#%g@iEzE z9^-~54vUwvFNs*g&NB(0i?sMbuf52X_kJVaeyPjvL?2pGHO$d~e#SU&<_J=3%`zEWrGj-~LI7|)viK@1 z56UW>kfG|!EHBOZv*pZ{!~2_J*O&gdp!EBxhyBhypED%Vfnm}#>{|@7oH4^#m2y7z zH0hP;$XPA-3i_k*GwOSUD9|egHcx|%qH?=ti9h;!*_ZEHo+v$JXsxY(j;s;ts7L!P z8OayC7b5&*1kycr#XE0|N~;BA9Av2f@{uuR!S~&%#6g!0p*= zdQ33@AXtBU!HFR!T7KTAL=D@l4SUA*zP9Yr>(^n9k7$LoCBYOT)Ca3lf*e`|Z>V43 zHxQ=?bAv0bvR%cui`07i${XC5kDnTQw`Ex5qu{u+xqTyZhC1rdYcuCTpg`Pm6y6Uu z1gtPeyGj(4&O%lIGSgJSLcqHufdzmLBvYBTxw~ttHOW0YcKmyI(EhR@=Ro#*ZE0)^7jwPEU`v3Mp-O)S!1Kkhtk6 zfFVGphuRt;9CAr6px~8Z?~{Ku&4-W%LN^mNAQSwjul;-PEvfytZ~i2-Scz-*mfriZ zRAj=Da7T4|cli6U1d&On9AZM!tYM}I;v0A|&0Vy@WhQ4Rn?N)pG&;ijjp%Y<*H;S~ zz3wxpVZJl3ejHzKWBRv<)vD10iB%RA1Z2!ue|D7sL z*OtX2eYf=O?c+C{`V6mmkhbkE~2iUIduJC8r)D+$lUkP(LDPmPCil{F%#x17e z;lasgH*M-$_%FX72EDu3cl)wFj&igC7!2o4t3U<-2J+iN^%Pzx6(Cq>Rsm4ac7aXA z9RWcFyCuHVh8vB%o;oTQ-#)zTlS-zVL`OJmpdy(x(@u7P2@Tb< z;W&{WMGytdkZdTIbs3l#MqmTMK4Yc6A5Y6q?i{5;SWu;M>J1DIVVVyD;!h+UBh zkXu90VOh52^bM}1$X@|aLa$U6gOU>f@F0*N;u;A<(JSQKRZtsP792E|&mc+5m-&Yxx*EyvK)uCL3M^`uUev!fx$$~f#tzI0UARlFe(Jr8ma+t!L}C98LgM9 zJu~+QBw$l3SYRr5^bMzsk%6{^<158U!@W|q>yL+Vy+`g5qy@M}<20O8GMv4%qGwM< zU3xd{7MOQs&a71QMWo60(dtaEQ>uo{Fh~qe_7QFQ`@ACZ%`PSA0tX4d`0QXl=di{JNMfW#)b8o;QQeLVxk5_A%~~-|0tLi znpDQXI?qQmC&#ohVC4b+AYFj}W>LS-`!XP|S7Z>_X%mT{M%EpI5p>TWrlAta9jMM- zfYoQZlY$Qdt&ktGP9|l(Rcvt)dU8^4QMRDzH1TNSgF+THj+(82ho{gilDVc5Sh1&W z<--MIxmD06K%S;*1xa|bqAIePII41(1_=`Zs%#VjU6SX;0fR;W7p?(6Svw10Clz(k zi;CM9(N$%SG2I;*pXs2~@n*8^5r7jmDoWdkjNoo+ZREnCxgbg(3H;0OT11+qfp!Q7 zXi6h&8{Q@rUT7!bf&>=*(tEM=W-hi1k3QU`$ct#+ydu2MDIDa^l8r_&! zQZX@(xEKkNf`ysjJ1`B22BF71%?)8pinTto0TD(i-W%o@DBK174Dg|9!U<(oIZ&_) z5L#iyb2cfAb6M+a$vGmyCU8qpg(P-HJaR$hIVoUc#%*HCAMXe5={3@xg*1GvOyhHW*rzW#)+@WP!$Xasg&>BCM>lkurP-`$U703z-a6*a#j|DCC@ZyA4VUh*kr@|lRqDeFWUcd?} z(OB>2;M^B|PS zWZiGhlr4RHDfgyI25&SkSwKujym1Qh;RELDX_ZY&#>yb;GMejba$WIAN_~;^7Yk2K zKiCUYD8T-~Yt~T~9F3Sd8*B)Rp(BV#@3VE}JF%nu4vg(W@tHsYLHrd5G+qSffyKb3 zABo};s_X$7A}T(4M*J&*MUmGsyF&TEbsg~(RrB4&jh_q`U zwk<-;4{F}hTw>b4^m3X$8f6P9{#cuvGN)2ji!mo88E6qYxIY1IMT$BA6gMdUc3rS5+2kI)I3Ry%cR~jr6Ln^9V$rSNx zW4zW>S@!^@kBjTZLIKI6*ijc1w~dR!2eW_*dO{EqBqYQ{*{wwn5i}Kbl?OJ;s%CVi zWEMy(XyCh)0;xea)=q$=nMj2QjJi)~<>@hOxonvpn^|Gp(904FY9_gzLLXO~)+wVG zY9<~;Mc`1BC_FNg*EHO9?y*m0^%2t*Ak1E^#i3}0+e@JK0HlU^)5MdZvL&W{PMVcz zfngaT`9%^3a2R+@>NN&m2q^(o2od)XIi9(Gd^uv%6OmQXHk<28WLazegXHZ0(La(M zlt6-$(prP)Ob{H9xrwSLtzGtTAu7T@rP2fwPRtP4u_goXMi}vA?wi7X>TkEXr!co( z0xxH$7>)(^N8kO=+1S^ky$_W1Sieds#|D8LS%j^0nbM|cN$?Lz(k zh37cW($N3!4E_JH?xo!hx^;HF=Q`ZAkjoC2_O?H4qingYE&mNWsFx#^Ma2-xpg>Ku zUY3hAv4bG6JhTi@PcSM#WHVskRIWy;W-$4ns-Lgl-S^HG$MC?IUY$bfb}W=-M7!DX zjuZ^-uCA?A9|U%VMrjI5g1{QcTm>^xK0#<``a=_8RmfFw%BE^FkxWwAX1+EjVPQmMVu@0LG`U4qYQ5UlqzF3V1(xV0{hwHSPOrJS z&b9IC98vz#FWX=KJ*IHBEKedFNmM7{2+fIyYjXhNBIePPokZ|SkZfcckuG6?$+noT zHHs&Q!peeK%wqZ3b4uAiO8s6pS9<7z<;&Lh$h*4ZZxN1u^h;{Z0IboN6l`aF5n}$Z zPmI8ti8qF1sw6h%Ou@lmRk2$c)&cB*9+0jxCXD#2<&zhkf9%q?$fcz%zn`DFyssmX z5Jk>WMJU%mK|0l2U|3eb<-7?2Av$*@dos|131s8b_Q&Nr{n!3}RT_18b$?Bj8Y8Yx z&08$YkwDle5)Cets`{4cNtgN8KsJD2DegyOZioj!EDZ$EBr3zlP6Re4K~ zb|d$my1Qarzs=PHT0YHtAj}a@KcmBsc*jc8O^#XSF5;R|Bz_f(K#FuAh_woES*iHg zg0S{X8qnbp+5U;Uv1i`Y?YV3A{_Ryt!{jjqcLs$!;s{bkI>egsT+mfQl)xs0!NxFw zn$iQsz>Ul+P&OKJ2P4B~*D!Fd@5?biZ0D}@u{XvH7@qJbd#heO-o`m%-Gt?kI?;IY)SV(hS3nr8Yx&7H%y!GHDF`^4f6U4x4kP zTdY0eoiy(KpBLZ6Z9di^e(r)>)Q}s9u$y7P;Diu1d|>S2 zt`Qy>%}Job6mueIBg!}We)E1i^5?rQ$FlVJ)W^HVqQ>n;HSf8zj|1A$`5`NYw?lOu zv_Sy+1fiG+^de}gyg2b~-2bWaPy(!R1rS&s3jFb-M|`%Vd97AcuYH;O(n^oZy|doT zyEe=LVd)Z({HC&yRwf8faZ(IK_)UR&D3_5s8e@Z6s^XARR9}!xQcp|$V^dqbc~Nai zV@IXzMRV>tW$)BGD#{T~ct18K3zJ~k!GQop0Yy!?XnHI|f9I{Pst&07(o+wBsvyBA z+V-U-YEJxI?}WY1b+|z3gQ2#eS@P$d=~c7%=fMs;Q8Y{@FE9>i{A!#pqNaul%Y!Wl zIH4T~%O}n2+>wGJ0Ia$FbgtJ)e!~kc+a1w&S+k&1Ys+1_I4r^eWoby|@$f@x-KKug*T>YlWy3w`c?3;x?c_7 z{N(t}kH70!s^HUBr9vDKmM)6QJoTt>g_>(O#`6lrUfRYLyEJVL+_%Y|M>Op;P3c%> zPN~r~9zMTtqUWB(_qVr|uJ8FK#KEG{Oaw;q2tTIH0@#_vG#8Sy>a5_;a8wM(QVZNH z!!9qC8b1z~=-lLH-kzw!V(O^s-n{jmiW-CkV_@_JGu7acxp7 zaWbU}mn-3T=42BRxNN7IQt!$$qhZ_%Sn&cl>Zq|T#Plj%v7VUtpbQP$5vlbvV zJQ5^<=@*a#A#Gm~F;yCoHs+Z49~lf`q?&g)6GavBf0up6pSvO_T-mwu&`P(iZmV8; z&*|lWvUF|~4`Y3P&?G|AxnKqPTXuBftz!8E{r5PxQ57?7adICLllttjzf@61h ztc^T-=>6Op!@-w3a;BxUxEZ#5&x#un?GvhbHjn;!LuiEKbB;7v6d8~JjVq&K63|gn zdY4`qQ$caQAOdZf^dwk1XyQOujYo$}T;;t*|JW6@FxQR3KjmJ$rg7y%cdvdO;y@Qa z_HqJ<@^~=_@rfd#HB6ZVgndVG6WC(-bDkzHm?!Z9!KH%VMh_Csfwb)G>|Su4`{%bt ztd0MoaE`@YQbM-$?H=MlDuPKyBGEyBiX;sHIEa-HZwI)hn!r*M#KmY8h%Es_Eq8jae^(k(XVox^p(sny}ZpPepV`fQ0v2Wm_esnO^r{;=v zATg{_U1M)ViUiF$WlYfAijd|5{)j;c_(Hl>lzi zmM3Kj5D&03IWy{s`eAXPeW_JL61kD64oqH4UPZ+P%5bog? zP>2&lY&R5YWP1P!1-}45G1(_N&Bh+V*OO{_?PvKP?Ao@!ZEdfeCrAC=f17Pm#hS4W z^uwzIOP88l1i3^MAE!-s;)EnMe-qtw^~Q1$ ztooXt?Y3_)N8Qg-&uBBZC=VgN?mfp&_9 z!ih5@n(yKH)ooo9(i)dseC>LtvTsYAsx-M%g+30T)+o_)Ovot`P!y`Fs{Au`dI@9B zkU((MQ13GhN(_tP6en|`(25HkHuhGLdudm$oc?{-s5W8UFOKZv0RDtAbi~ZVHY0Wi z8ZYtxFqU@Oh@xc6N-oL*L82!0e+`RdP&dfi_N3$Mj^!9K^4g#udp3$rewpiBj*qik z={0PSslUqDTEy{32Xd{B%y92?u4ey@wm*Dx9&FbBgss^f{%C8TKDE!d z`2N$a+RedneRCB&*6pyZnS5*h% zXGec7u;udIQ6s*XaJYx{*D(HQ+)m$34YK!mbK9koclG|u%AUM))4H@cuWNM|$GDc; zPYgetd(hm`QQyv--}l~=ofG-Ewu<*i?1{D{ws_eUo zevr)?V@;jJzcgZOmyV}T<=S?`Bj@h3Q!3T{`D)YpYxzsV_YYjq@|yzf$~WAc`l!*P zCeyCmx1RX%N7svfk?Xe}J}D_f`ZVz!9a`dg$b9QU4!+)R<;(P&U3~Q9P78{u62mK-{SXGzs>b~IsVee;1(5Is= z9x8X(R^t!;=*rnjg+kL;9a>tmSYq#7*M`rn8EH*=!#{O-$CN4^JZij{)TvmX!mVnyz0|U9Gw+t&9@aa3;79A5Dtx_5J-dFLrG5*yX0dB(F%lMbh z6kqtqqT<$_g7bQJnc8nk!FFZ0S|dE=qx^N|c$C?^b7P78^{ZX~Y5Urrhgkc9_)JtmrvKQc?zHQ!NBB#p20zZeYxbz8-DVFjJlv~Ww?27tT8HQImrk{N zy{T*OUR!s$2DT1vd$3siwni=N{H0%p?_3{o$>;r+38C4&zMimQ<1e$V%M1BSC-W_y zKcj=ywQC_{oDO@qjPO1wE42yvxig1v|L%NHh*#=``wB2 z2R^yA=wTA>ND@!Ka;4d95l=WE6AKO(PxihSB{v7V^UmX0|R`oo8ba+Sik0*a! zEbqQb_Q-y(7F3DcnZ;I64Bz2T`VPFk^};uk@(iukYgwy|&K)w`4_^ax)=9a^szqI>wn>SwPN?+de)7$5B zYRo_A9r+;VJ^s?}l3QQr@9wi@`rKD-hP|Em{q6?uYy~@K*N3c$br*L!RcPBF>--D($h&Tbue#O_ef+U^|00Wi zNne<4ko78vKl*9H;zud(noX;*>&%o}v(m3W4{Bq}HIRSmr^v^DpKYEsv`xnTr0g%( zMIH=%mrz)~)M{wd?eB8-f4u(EH`#OjHN}7J1zU|v@};UTzZ*R>Xz$_iJ9-8_ujiS& z<+|t&{H2|LKU~!G`1S`yn+*JV?ln*M&)PS%Ux+PZy9+D2V_)MNCs8{HiJR>_x2mz!Pd^^d;|e;TpxNcj#8hc|m=ZGOui zZMn3#-=3MS2b$#Yy!CVauL7TCB-*O@@%1)rncwMdQ1bAYs2}Q_xKOR@!FxxFcIHpk zHyY@eR`PIgnSR&u{;{Oty2kt5250am>pIt5YMmG#eu=IgCIRc&kN+kjqccdWa5bNCtWw=I@*-^O41VNLr( zhsvGzIsA3C;>&U!OV}5>+g3$BT2V22%7cZr^qX zx>eBg69?;rmdW<%{%L!e^vfJwu7q}+x_J2zBwBdu9@|VK>pH_zT0n4FP_n3 z>$PVaO6_?*zE$xp)~^@jOO0Cxe3ke5{(s)zC}z9S^%3PU8G7RmQii_0u<3bIYVZ zidEuYn)j+>y&pVZt=s(fwLxRwoIFvyQe|7Eru?OO^;`Y1A+_V2z%MS{E0twL_Vf0Z z!4e0}EB=emn$YJRdtI?_e4K5`n}ta?Ql|6e=2ROx^l_z~rE4@k7_i{@qL3^qtnMQv6FZ#%#|0QOC>m$4%e0dF!KJhg~0$weKVO zq~?p1v~Z8d{*OAijO*mt^g!u9%I%g<3N#pcVanGl&s?aSus-Epp24+)ay93BnD*<% zXy1MZW}djPYul_RxdVod9%`#7Uz)b5*?>IXw&{KM(5_2y&;OY?DDbPW()^{VXB#hb z>ow?UhdQ5aOl+~qclW@Sw)`dem!=-y<(+W3`nK-LT{av$(ja?i?Jpym@|UK}c(HV4 z{KiXd6K1wq8Qwni%Kq-w6p1FLlrMW?dH2cg z&z_I1dAwfE?;dpidc)*ai}-qzXAa)mZsoBNso@cAA0C)Brc4Qw^Vt0F_>+n4JIwmDM^5hndAp1lQLNR1dsi-0Z^M_H*r46(HEUi! zI@W%)m3+S3H{+VDv@S*RmnJ+ZFr@AN5?;gB{u11-!{9mYYfBa$!e1JHtDomIpGNlA zbLUsCuzShmeK%sQBY#T$f0q?FGq8NXv4EHWSO1y*W&HN~b@hGVJIc49&nlm`$h`-7 z=l9y+)!y@t=Wx{j_j?4pzrp;!5^j6ldbqxEorPTIm`fz`=4rMH)~^4)95(hGCQQ|+ zu3ax$S^?%PgX^Uk7U(vCZ!*9%Iv89fw5<^)&kf3;g9tOs2@2{=>J=yJZ(ch7`570N z8|@$b_G5M5$P;_)=kgII2Pj80!GI;IK$}un4f?L*z@Y3%=oTh%0m1_91PBe&6ck?t z-kD(UwWoH{JF4XjVACk?GyIl*+bjqSl;(iT%Qwve)e@@T-3QNRA@G>wG6YP zLINS+B#5CC%?u?5YC2TOLe!YBzxm}D@gt!=oBFU4N>Vmg8P{d?rM|BhPwTyZn=SI; zk#mK@&jr$nL$~Pjn~-@z7Fr%*IuO0Ie$9%s3Cz<0#QDi1CLTI zUY?3(YlRtxiXn7nVy2M4b56zRu&!wAU~5kL{pW-@3nj>O5J3|xN^gtS;9RL zR`;~FuZQ+d_$_;Cn`ho7roWz<)ABgp_nZ%x$7fL8BAxPjLMUVi& zJ;8%(Ah<)Y+5`v?2o?ea2^Q@4A#+#d+}8ekuibmw`~S6fTDv*treM}ubIm!Vk3RaC zwP0_T)G7Vu4|#VqU$Gh$-){}GW0VxSi81IC{_w7|3rt^DsP(1+pZ@URr-+$F`wp;Uo)p+6XnYVIgrpR3 zjOhLn9~Ea4tPGlbiq|97Ej9{vT{Kj1E1<2e<#o{>S-K_ro1fpzd|WcX<4&0!qmvso z+*mc#j)7981Gux3PYsdFhN3P4i$U@)st}d{>N8p&q;Cd~Lt6MKlE4_^F_@yQ1TC7- zwyNv59oKH?zur47@5*2A)%|MY!(ndy?QUd&*-T)!nfn$M9|Tg#5VR08tB2Kdl~gas z3dKw~M>NoK#SbhvY%Kg(a*-*2*OZz`+wDO*R8wX+_=@g)%=2U zqwE+XRRzU(=Elf)7LcAHe1M2V6&Aq%Ai_EYD@NWRk`6jbORA{>CJ1?K=>Qk8#&%UI z-|3Y)W9Rsv4^|qSGHq|Y0Sj98wL24u5p%t0QU^YTN-xk?LD35KB$<1eQb3!LslTR* ztX_iq!P#Z|)$w29`q_s#kGdVR9l2ZOk?W2LtK;lWM9S1+w}_*|DvFELx$>BDgaR^x z7#+=`BEs66o)X8(>!Ng_fgbD-vB54?8d|U8Z-sxnyeXgisLG4p#jUvfB*JdvVkP8F zX=|{mX=HG8Osb>_kEe!YYC;k2OMw-ta_qZ>1gr$!pE1>1UjQElzlBhx`Gbu6P75%5UC(*=| zQg*RUiIKx1YC+OqT)D~3k;7NTcg`EXFEDOhw$;A`{<0`M{c{S&Ao+mDq)N2#io!^W zUUe2kf6s*9rU;L=P8KsVvsQ7iiO3~Q5nB=#wOm}FaNYB9!{06{dhE@U>U(yDrhmq4 z6CvQBppICdCE!6&q+zVAW}=)0Ew>X%2=pKdt#u}?8Mv(@LS4KsrFf14o%IhO~w__+>^~*2tRRE1(k3N;ImmV{Ws#d!~(*Ty|5rHD2~^=ID~kzW8=k-@djc;2jR59dyuw4Mhw5AMBus?7_^_ zwrd$s#HNoMi9jx(;GKvuP_z&xRMl47WBQE0>{hhU=r_0aPn=Y%V6~}z(}$9EAYBq8 zEM+dMC|MHwf`~%urx92YMJbugE%Os)P$cnbYrcLy_`;3HlWw)mcd}lCsrx*y-WxR_ zeF!<8BrcANbwOR9>@PP*DBnv6F3iS^f{RlXNE17-G9DaDVDk7G9nwGPRy^nKtaS_I zAD?&ht^%{#MW&}x7>K(}`J53Z5d8;}O^EIn{s?uBCo^|W8)2}&WJd5X(klsu7v?4q zwew)75*;r$$m!d2et7uU^W7RZJ-;q6G<`51YZ_lc^J!Rk1=|egEs>p$84i>|NkY5O z{#tGCl(Es`Y`IaTZ}--p1@3j)(W^~r;qj|qjIVdE_jAermsqIkm-0K}*W34*Z<=oj zpS?cayq};dU(#z2`2UZ=|1a&a$D^zJpYET#`@5}nYwUW(HObZ8WtmG8bo~!;&gJyI zQw!TwTb#|)TJm3-|A*S4D21Gx3H02{1t)>6Rnar)N=JN1yfl!#;AFIQSm}u(-fOaE zN!OP?8&NhaV0WRCCERL!UuR*RW9#m?2iY4@6bVUm9CMBC_0XM2K829ph^4iXZHu--gH zg{4<&U0YpdRs989?Zw`^e>fSqp-)A(s4zQpr6_4(ni1Gd=pn^JG7k>jl9-Sn`V|fX zOrzTn?@)I(qCU!1VaISCnSQ9>c1Y^qxet!(J@~|xE|caz>NGgrvp}m5J5;5JSqwHj z5>>Q>qXkDfOPFO%!dNU;plJz_iFHA@IsbKN<=+H+f`W?cMRN?;6!QS@uPA z-t|kPhYhepRtiZ=JU$Nz@(G_-Z?za;CF@J`i7I=j+h%cRVSj-l88ftAK;Eap!F^WW ziD*`Pf9-19y*_R|8)&b?d}5IcG1G9MOiMaTG)ESJ1ymT!^eRq(xPL;M+0i+QVR;EW z$%rIZMz{NZTDRXO4oECrY)aATTSgCRf8=Gby*9-Za5)I?K#hzus$L?EB2yP6y->L& zM0vPKNGnpp>C>RA0`k82CUBGt$z0D{Ixz0*EE}^o3(r}r!kHDj@9+Gwzr7ZP$)W|t zW2V?Eux6Z&l~+qH+L6+rB@=_$iHOL8s1f{eC*2lZw)x@o^Dmz?&fYNW+qspN2ij}W zRsjX0tAeN(--JC=3e%8rka;{d)30C~alG;DIJB5ifPW(Lhu||>-Kq79C$Nm%yY$q& zJx&{{JJ;`8Xyn_KXQsGykF?hy)2?V<68L?Hdq{kEa3Gcx&i5qV+@Wp=xtcgtZ%(~6h7-|JnKF?njZN2dJzD8yc!&aj*kn!$wYFGOsU$e5N3 zL5jo-;S@SZn(=sXV2`kgxCOL*S)TOBmXdbpW}n^f!XMUJdGyy;o*hE$0rYE3I-$Zl zKom(wB0+}HD^(Eoh}y)_qKr3Arj}9lB&ng?;7OtbM6KO4H0U-DwQ*1%@s%)TBh!q{KTficMVFDvk3nTP&i#Je*pCsjKR6|J*!ttd7<(0p^631)IfJfB7(puMj(~Hdxx?U=Vuav- zQ)yg%95fV=;Lw;Qc=`;}0J|j>akZWO3$d`&-8feWh!%R1qwsiFnFyqvb&X6N^fJw{=j#iGJ zv)BBgV_}yC%YN|=+t%ns&hhal2Zh-y(UDLVJFed_`eob$;5h2sR;yDqlL7%c$xK$G zbEVxis_8;1T%Akb+A!<0mCbtiKfJ%BPm#p(374OT+o35{9_fq>YcM@hLMMmShhOFy z>-cW)*C6ZzYLFCIwdU}Mq!Ok{qDFxWXZ=;|SbCEUDc8NGovl)LU#-TaYWB5PpxBjB z5CvOw_8DnH;)s)^R}ddf4b!>Xfsu3^L;QB5ZJQ9gxA)S^UB29L;bqdvRd&}a?+eGS z54D%)Xb&wrae%9k6Cl78k0`La_LP%(Gt^u>2Dr09uvC;F;lv>LCgTH(z(X;dC+AJy}97ihlJb9(pOQVuON$xA*;3<05lv3YTwtVbBn9!hb+ar7Juh031+RJd}qUkWTff6BBSt zPUr1w&)>^^=8((bagE=Vtq^1{Nhbg)IaA>!mJwYvzA6OU#l}-p92(>tktuRe*jz*f z^ekk?wcQ21oi~5mm%ifY*`f97<~dkk^~L8qckS#HW-mboAQ-m`eTwMvlXiCiRpMoM ze{iH(iiM(D4cA1jKRyVx5i~cl1dgOo2*_j8|M-5Bn{DH~mN9>BaPE<(P_vE?ZU)(RFIHvS z?DyyDCVd|LxMA9Ur-Wu<_G0upfar*FoutAV7#-+eyPO>>@ha!P&KFW-{v&J>- zG~P-vyJSuxM1#ZJpaoA}FIty$p=C(3&NHqQ{x0sqikg3f+KbSo(P0k&31o^BHFins zGO28doH8WU@WM12lYE+Puueu3>OMjS`j|OA3Jm#XZu;Gtyp| zLMl|z;Fu__g72m4VNOoWcOmhN5MrV)3yiF~d|0lO* z-HU!!W#yS+$2#2XXD>vxEE#>vHAeZhT&fb2#k2bynvx9Wi2w)-U$C4h;7ueuuH6eA zXt(Oh&GmarK1=alv+@3wvqfU<1*xIcQT8-phT~u0ra<@Cq&G$t6t|4#3OIV1r7e)M zO4b*Vc7%^Lj@Ge_`fX-Z_F7*zYrCWD#bd9&3bz*^6N*0oL!d3D+jgNv73n459+*H7yCAV&LzMK8Ym&d>Gzczfz?upIP>UXGAjq?A}R!_@+x_?!_ z<9<=TAAMK&Hu1Rx{eKtl1>Tjs4tw?C{J*fr4v$Xm58NlXmvYu^l}TjSCU zT>>MV3p#B^|Nmq3|CdJnzo%sd+tFlNoLYc6V2_Dpc}-DF7e6WH*?d|gvZQk%MFCXs zs6G(KZJQi_pfT&~TJy6!?h`g@_0CC^M!xZH_PS}WFT(6-G%bsyofB!w^{@l94CGbS zB%981nr(tlq{_2_iR36mMg;L4wh^I0QUv-iVnQ#qod0vHr)5e+wM+3|Yx%a{)Bc+w z{q1NqMUsQsvrdST#E<5+P_&=G-8R(4hFT9-2dHb3n!gNM4;lbwMB}YuLwC5G9(-WQ zf?7i#9q{T_VaAy8FFU>uwxhok^4uhoBvW|I^_K=iKu(w@h$lv%33)2M9}MhC!&EZr zs({ths`HfBPHW4}{Vw8G2IXypmX8U{B@mA@(hxTlh*55vm4wbg2B8a3CG8J6O`^ofl zmih!hLKzvKaa;uaE?f_pt1Q#7@HL|&e)kCQoc#C7d}$4fuCAV?l-uR))kExPE5&pK zjudg9kjf*ZK&pn#m8qj*gn4iXJONKEGFbW0*dX+oqq2;HMVOSxa{D;fJioDa#Xb$c z-c`9w!1UQ3Q@1?~v7@21BocHij{7Vlp`}Wu9rG!obgCyzvD@qB1Q zMFv-Na3P-@PiA$`b^5lKTVnoRLp-WI4zZ)J6bjtbvxw%{&>$*cg#aN&!cP%4uc3iG zvRnkhqScPCN99e`50tV4ijsr`ZT`K%@ma5C_q|eo&ivdR23+|p-`SR7cC?iudl8R| z&BA8G=V5cOYuwSxwih8fb>_ifaT%H08@b@@D4uZNrt1EUPM`n&(E2)uYfn$=oYK(w z-G+Ln5GMRad5Fb7~kO5W6`2Cs>ejE#5Dcwj=v#3_eDnm}a~m>M@N=$5WI z=wrVhp9dZ2Q@(Hg!2$l`Ruvu*VMljq#Q^17sLTg43=(>XM4vc?7A(yzI1cayl!j3r zq1iE-=qjudIt0lB<(i~(GRvjEOT+s;o((QSU>&;{~?v9dvu*s$>(OQ z9lfPBk*3Q)Y)7DAaxk9S!za-2FAR!m3^r=g+~62SQu|`%)T#e*tXAu+t=nF%+Une% zN~;COm{IiU`{rkFdv!`~xud@wouzpLWI76oz^O2h89~e3C2G8ePmz&eL6nBb zv=r9u3Zbk6#}e+}$~pXe(|VtD^*VOD`{~(7e>!@>BRbfQhSK5z%iOmS!_-z5F~RBBHSjfp#66JV{NBXgU@u1+;`wkIp3Jb%CG+qQI(Rxmz*n(^0z~NI7kw&xxwWpG0a0@>>(P?vT zrwG6EbJ{&veR)Zie*5A>?C2vciN0Tgm>hE%VeUx+0t6j0*aT*h5VRUL9z_7pLLsf_ zG+_ub;snw>qmh@lCd~X{bB>U&&wkvxdQG7QAv=ptEEHl#4=Ds-v>O&55$h_K38E8@ zlgp?)%VlbmYdUXrv;^hk2oA!q2_f6_H1{n$x60%d<#c)e%LT7p3-lCWk!%ha}#LNp{b&av=Q^)uAlh+>dhHX z+N>>q&#U0Da)(0~P5wFD4uQ+^0p`XfL=GDkh$Iqbn#h+zX#^#YP`d~!^f4ZQFNnMi z84$^06Qcvs1C1;M`$R}+p*y;~b532-yU&?-KBXUYznbPc@m-i5Qd?yMlvWAPLdRC2 zz68=o=0h)42{Q;wg=cv3kK{eUcnX5RG?@OdXwHO#Cx2dA;kZwYZBuUMeDqh#4>5Mg zNLNS0FK@pwWR4;%DL$(+Qe;W1zpARJMOca!iEgRWdZE0CD6(0JRnFgp4_NNre$}qE zHx7(x^6R-aA@+83`n2wVF;nD`xuz~u#?ZKMdOHD_5T!)o<1m8>#wdma-(X3J1j+j$ z&a&RMV!jK!kJqdj6*J}9pIM*84e0(c#12_G9CD7E1SL~OE$0(NZi)l8f`yQDh(>N= z1f~O0G|O=X|p*YWIV(E}gqMKRMJiI?~>T62fGBUn7Qz zG+akp2(%(_r-Q?xLL>1UC90sP>QqVzpTn0)Pa>!N`pcwEQTZe8{j|fm=;ga*{)(_; zU{l2arN0@lMk)fTQ)zm%u|+s7TKNaC40IVEgiRCviBwI>3^0C)LnjYw@~HoedCjgB zYdwC}lm^N3p3M&)Xm3Tnfvz@e3Ah{JvtkoOIvjO-fKP;PI^biqO88EM)W{pl-DV8k zv%&gS(V?YUMwY$4bL{l})dn~AeBZ;~(q)u4rfFf&BEA7;P>~dj0CHv?BAIInL?Ng_ zyeGuHPYk@JqASbu{ zv-E=P!AGv==y0^{`1-Az)IT(@pS>wd&w&w!UVcD6*kMNP2s6X?MBE9{2`WIb$_Ds= z;0E$jDG@*hMDYN?mK|nX?X=`+j}Mn_CVf_Qu{Gp;@`Px66B^WpJ4-iMg(75pnR!MP z%uziO08y|3h}42X(?o!RghV-=nl$1xlUTygrRmFB?)mu3x)DQv?0xt5z~*ySME0Wo z|4KG@%YT)B4}TYr!tO)dOZ$CqTkF@?&);{aZ>X=o&sLv)K3?A2Z0_Czy?wm)dIfv= zdT#d&^UUjU$Rox5q}yq?5pHE&FJd0xCoY$4{aq%x)O3F2Jkz{Z@bH1`>DB7!5%HEs@3Na$lCFoH{#5z#v87O*D%T?8c;nmNONQ#n9pir8AW zgSW`5%OX%gmPkPW^Ggt;B*L3!451LgPT=6I6-Bd9B{Ks5hBG2t^9FFI1+f~A9XT3i z>8hj%mpUAiupALOOajO}RB$7Dgi-&-ZwB~bYZwmal3SmS)M4W()>l$4^VtL##j8>I z7z9f)?ou~b#$XES0#q9vYJGWx!ZF8UQYS^!K;aDd`wqxLh3`ebM6@zwMdVRbd{mP~ z@r(6J=H$v(fwfFVnh)RLfoUSLWOF~qQH3}kxSnZ1v--dw0Tv+UgmYuM(HeG8I}%4V_1@K8bpa0qoopMadO3yde3 znzS-BVO2OcX0+JB5`nlVOp>iXd}4O9E zIE3gmzrHymRh*XyuS^wXtx+Y?Fcp%dl5*TCc;|xrfGuJkiz&th` z0_bqcZ$@7z_UedGV%gIIp@Sn9xo=z5`^bZen?(^O69l7)I!7fW9f`|Gd8I>^Rw~NS zKfiDrVK~qy;4-((qmQ@Jjth z{e|G4_(bLcppuC)GYopXFv<<2!BePh`F3ZzlMLu-9)yUEfLyK`NhX;L0E6amaM45F z8?ADIz-LihrgTq1RhwTuKn7*yy;aCo?XW58V@v=xfu?i-FXPM(AsaIhNK!cj6bkOI zV3SH(SHRG09-%nz1`zW#Q~uJtxPA>L4uCvhpT)g z`@s|h_-M)_fDzhIj5CmyW+TA%$CFEU6|Efjh8Ux_5 zx>+?am0gLZvI?sp{pPcxxQfx+cA#xE(iEW+@wtt_6(PDIsh?+xuT&UUi3G~{0Ptpx z6Okk&qQYsQoS9Yj1w;)2r_B0&DlSlIeNdPNI5k`a;Z<3 zOh38N)I$~(S|-zn(wU(MVBm=yXgux<)t+Qppd1SfDJ1(2YBv?g17ng%w3xs?@lDpH zI+>nS{gwD>O}`w02CFMl!zBJqED`7UCU8n({Ivd+PtRvE@Omvseb@kbIs#ZnbP8Op zv!-}WxYBTZd$Gi<^lTlvohgnnZ)2qS3>0D@C)FWV&_;?M99fGNyV^4DB5E`ulbz>D z4hVEa%GONT0hEMnT&G1i&_M$_@w)ho0_ej8C*caAG;M8~lIe44?8YS%lXz*b8W*>; z1%Nq{3RG|==9p8WQJ%H9R;JHoE}bCj;E2Esz|qppRbN5G1Y9qY7u1ojh{=FBX$zI` zjVqPPbXUPDK;#Ry4ppG3#Z}43z@=nr2zLvNS}ui)s9r-;otiKZ6NpMR?5ti^_QcBnLA;9hQ8Jdb zauwj*;xHw6PAOBGCLS45V4!`bJaZVUoFREi@?o&5T0P_;nI#%)hRQ4AUh*tr9B4`- zWKbeLqKux<_(UTUwXNpkn?_F9&){ThRu{_uE85yw{u}*+{oVa``$hQW^4;$n=l+v# z4)*#Ez{Dt{t?0yry39})4YDu9A#rW63{LGXgA zP!h-kv=)8IJh~e-bA)GY177IcaK~orvq$|

    `{U6UH18E7L~2kx}UFW`)l(kIA$AqG;Gfyg?F5t$qgIOlMqk}^LcA*dRdn>?an$S@ODMKUqM4TT0`zcF&k0?a%} zISjEC5@YVmHIXhp1Kd@pe;~elMJKZ;SA`K!GFl=WQG>q`3`h|htP5U}+aT)(=jlS*{$BERG7^fII|z9#q8fs7Nz09F_XKO<9*^Bdjx*Q82Hq8(`?> z;DF;xqC$cUTSv|y7od|LhPDE#`GSPNQ`sF-uVzrUR#ybwY%>T&^6JjAyV!AH$_8QIrC*nw3|PvjP!tRRzS&YB~gSr{uuA`Ne0gPN0MvkZDaIHjwOb9IlHo&nb3WqiOQl>AW1Y#UuPKN= z@0frO8t(JEn^)%P(J;SMuFf9Mo>-nyR5Vfvuf;K^moxJd6_(^-L8cO9x{Ih8hq-q$ zQ^}SWC|XuJka%A)x11gD7+50xaH}Q}n8I`IyENCp*rlEh?d zaVt&_&8jHg!>I}WETibnfLhi^fDZ*_3%{3Zvc?4u#*rYhTuMR+C+jrqH2<%eju(%3 zk@0v0MTszo;6@qBPl1WxyJ%J<$Hss?f_O;hyOs7V)9pge#yAVV6$bs`E1G!#qB!AD zHlU$wvMTO1XBV*L@F#TWY?ccuyZ+=c=*IFn45Hz0T-+g8joUJ`Dni^NPHYi=AuR!3 z8j6FArq|d|&xtrrCCxf46@aJ+RtVj?t1 z;qy&AM~@~A)!c@NnmqtQ92IS;unUxOqZ+MVNEA zFRUzNRv@@6^p8yfii&Cvqn%84!0w?8PX%rzT#6ZrZ1InPn;5vv!js|@)?tVWmV`Z= zp`EkSW^3y(vl@OO6~Cgy??WG~!Lo||SJcgp;!BEw-604~5FgQ11u1$LQOuS)J!mbOILI7^5( zg_9;+X@N+a&CUGgjD{Nk$fOdgNg_fgAgUtD5sDp)S|KTl31NI1-d!Au6OP2YPl@3(p@^+syM%NUhLf5>8aBk zr$)BlZE61-a&@;zb}qQ|a7__JGhxs#47)?$*5OSI2hzApLm1)^Q-ysPomVqeVuWR7 zPr=$1oh7J{z=<&$oC5k{4z4g4FietVLUrxz2kI(4PR!xtf|&JnYs+%Fq|Z&zkqIhH zP6Zc*;2Z%Ae2vWMLv}~$m=R4ukW8=339$Lg(zWwRIm^oWnnMm`#OTM@epTKT)3+q- zL)6m1{y{wmbqA~r;7&_FQoY4}K)n~F;K-yv>v%=W@}JAOg(AOP+|>jNUX03q$og<$ zP{F43BpMTxh>xEFM+4-F$8(q3PJ}53v_J;crVjNd_;NE&L`4JxS{{ zaeUYq#KIKOlcvHj@c)+1c6>6zEexTDgI2fVIOyj%xx%{0MS%pupe0hFfZ;kRmYF-cOg^=yY_`MR)y!j_tAj^ za0w-e7!{zg8bk@w*62^ma<9hD6Ni`(2!SOfC(?E@r4p%`jD!Fbk~c(-m~g^5C(=rw z8OWqSsK%)nxl&1GJVSuC;2bW!diOK?9(m!J*M1 z(JRHno`n4>Zw(g?>mwSzhqvJqQ@)t2Qm3mI}@7$5F00a=@1DJ>8Xc?Fj( z9-5E{zk%&j=1qPYHB%;07+?rTAqBpHhU!>5hT=iL;*dw$NJAipg@=$@S#mrWJXjOn zgi|xz1>)tQ3e43LmK;@*4C*CZ*NR2v1lRzzhrk${<$=}1I7VS~1_c`WQwU7)$!J~| zoH223vAg&-1cT~oNjR@iMxeTZ3Qw7#t6a>mCd2aB%FCrg;7DDSD0i?7kra9*>0-7a z30tWQC(t!+c@HDKQ;}%U{2WOaUOUgH*y7?0=mpDIX4DU97ah^GhM;(0CKD3p70Cmg z6P!V6Ut!T#})ZBi966jhmc8I2vQ+Gb%3;fj1TaHBsP9rjfAnsu4S&N-z~d zDbOXkO0;o-oG|V@OjvH%dhlzl7=PhD3nP17t?GB^-e` z<#X}LmB-_X%$JXcG9#(XlG`9#4&J=bq_i1UGgmgGC|kmosdzBD4{I1s@R1;UGLRql=P45yu`fmrU)^RKT#sO-HggouXe`R>oG;q{mc8K?f7o#;I{cCNK}sK@o*bcKn7XpiqL2(Y2OWgQp0T2q%KB zw}}+h-kZXU=?GRhm|wy08c?U9C=GTLF+Z(A^X8ji_PWzc&O0Z-32hB)C?z}ShpC$9K z*%ZFuYLVCD>GGnVEHplke7LsP^-}P>OTaa7PyboDy>jm@%R|Z?cvP|-_`t-y6*iI? zwxVp1AO#`( zP53|-Cj{aElrV4%4&3K@Iv_AaVI|j3aPs&M6Bb2UtM~`{3p8F$2_j91aH>ubD*zya z5gdelN75c1S_a{D_Lkx|5iDRSya+rM{)s(x%qKM(zXU|9ss;5@YbYKQO7XnJRf6+--q4wO((K@fvqB9T`S)2vE>8q&ug90ZQeR014m6}d;E>oter^tml@ zi4pNp`-%i398L(|i+XDDzY#H$wM60r?;+|MtU1N^)zkrn^_#yjuAYzNZ$Lkw7aJ^) zHn4nZi-13%+)wfh>C%$=AmBbKn-J}T?uju;u;$Q{uudKEvNf^BqiNr-q@R~At&tm-vqJjrQ&2f5*4FZ$7^+zLw7&pA$ac`^@wi<}=WzxlcKtEZ#5uvj82~ z=l!ksB=14qJ-q9A7xi}YdgyiDYlqhYuhCu+UhTcAI_37t?fKsGmgh0gb)M5bhj{k) zZ0cFY)7Q4fUWV`8{;N0E0o^w%Wcc+I==lzyD?LfD} zXs2+e_D)s7hM4Ua=Rd;lh2IswAD`W|Xo5mom&Bbp{wn?KZixm73%ZA0uKs)YU|aEJ zehsZWxKsb(Km1qvuPOTm7Oye)rLE-;{I_a6_{E zjr+pAPv>v8HT;o(l>Pf(W8Vhe-gCpb(~LYzy;{xfUDVbvf`8Qimj>m3zBj1l#yb<& zX7`)aF!@PSTSJ~%mDc`ky-Gb}%T689q-J=VBJqtjygF{H-I)JYg<)4m)vmMq^{gCU z6?k9gO@RTJT~u=`|0rsEqhE4-S?1#D8QYGP?RNFsQWu8Uih9xninORY9}*uwo^<3- zw?DSz{W0;^A6+}w=aD375v7XcS-X4kUcYI-Tp2pBSKU9d#ab7i@p|EZ4j(u6*Dzbj z?#-Ib+%+rV;`>9^%!%@&JEfXLhHafwB_XlsgYkJQcWLWH-3ol)Ir%R+O~Y($MawPKFR-T|LB9M0}JwUq3b+8?)&-Erdxe_ zei3k`!K3f)9I*Kh=f4y>cyW5j#S3flgzWz!eP++;l^%C5UY-9^@cOU-`){^)L!BQy z=y_%M>?6Y$T6?_tF9rV9`G@kYP7Wzw>|z$@s5f6ZuW_z;PJS|a^rFz)_j_LJFyNO} z3xb<;>sY7FV_t6H>7ykV1bkim?!j|4BM!{;n!Dnabt@nLsDGD6bAK$?v&i)oeuL5r zocwL}oAK6-4*a9uS5xx6Y;`kmd+^qsEslKuCMv0xb&e--rS)#GbjK0*8$bWvXtebp zKF^Yfhli}Mi}F+S_MDZPt#tdCyUPw;UvuO_{x1`&l;tUKX}x;h>9#D7$ITx;1dcs@ zdgZ77=l}e)8vmW1Z^C=si29~gYRf|dHuVW`8j$D9peXstpNXRb%M6+Mn~#T2%#OXQ z%NMU&`;`3T^o^gdwZDFB%KMNvW&Cn4I@e@O=Q#Pvk~6)QE${Jg>icDrzX~riyXoqw z?d5>=Y`^~M+hz4DAG!bZ-QY{#zjIzU&6>`0q|$m;YuB~y?+YugoEWgP!WX@NFLmyq zb?um}mu<_fqK(fKYLNC#*VkXq3s{^pxWaF|Lp{!J3mxEeGtcy8KNp|8yK(c6d)`@B zc#=jvjmKjeRXVSBU6)_1Z? z9X>wv?dex@!_#HKO+Ib5IDUBcr8Z98qkhWo^b*?@y z*dbfcRi`J17Crc4YNKtVYH$0~Ufo*Dvyjr-zc2K%QB1w}{eN3J%i~q`(d9psvtIIS zs#`^`~@+Pw%1jh?gH+P#b4 zs@-^xh$5f&ZT;zQJGU=yo_pBIf#0Rd^R+E7KG&J7_wPOZ(4_kPvk99wf1bydg=ZS3 zwdruZ{q^xbPU{hKy+PaAF@-xW{MuGr{#L8E_OPygohwF<>GmS|z=5O5^*39$`|(?~ zyx(=q!G5)K78_sn+xl%LObA&u*XG2-snS}uI5seB+qK2JEA0E`&fx*RF9Wt%&valM;tEr8(D}P-#Q^&Wrdr zwWxFcNqlIz0m&m>w!O8#`25c1G1id>{JzZ}EnjmnF6s5R?(LjPjefh}=fMx#&*i_= z{9n#ZbJic5T5pffuvy)P{e3<7SL@i9@}pjRzyCO->{lrj`ek{%`N+l6JDOUHV|l%1 zNBV4csyu7sy2EPC3Micg+@OrLF7PSUyM7Im0L9 z41b??qVcuEr^+rHx~{@8Ywh>^n;Jhqa%BGQ-0`1oy7=j~fY1Zy$_%w-AICpxbo<`) zl|SE#X*yz;$AZ33vaTD{z~*yJepI7L)jhAinYJ@o+Ub zwz)0gAJu*`Wk%^6 zC$_d~n&)cSx%Lk?`!ulS=edDtwIkvh2X&t{WPaqRkC&T%d8KckpGq(0hp7F@(a&$} zTpQ4@&B8;A{Q4!QG#Z<2AOECQu^t`A4)WjGuls>V746^6y|e85LI?R5)wtT_=ZcTd z+b=acedtfm-@^C3sB76*@QUQiCe$RDPN;X`Qz2S`xY2iyX=5PAK&diGIH=0Yws)mO_d)GFEDv?;exfN zXWQ^YmXR^tjt{gRivz3NqHs*vl1raXY5Dea-aM`zLa z^-sG^y1HeHwd5{8M5WyxzdyYok4#f!&`^-8HeM z-;wq;R}E-nU5n!%m2bKA*x57FqIV1}b?w7qt4*0lZq|`Q@}mkR&c7c%x^>5qU#_^j z`^xU#!)(^gHTN%P;hOnEUTX^{=1n7$@6TcH62oPio#8?EXu~ISt(H z{XQ+T&YCJtqRf^tEm9(y-2eQOrcwER@cs7r*cfZS8?RUD%;bs_eX5l&9XjgFy1oNG zTUlm7_m%RK*^Rr_eLmsrw!*bNNBwI{?JRo-54piVDfz11A065c&EmRbX1}5@e;Re$ zr+ner{NN@3TF|)ZM`&JK5TI%C`GxoyiT&?f- zv5xXQu(T32YIaKNzh!3a^eB&87dMaDem;*aUv>Uf#V6lglDY07k z79LHRR&0Nlg#~)~|8csuTlcS?&-)><&Ng?6;fjqf6IZ$5B%T`k?vpz^R$sAh ziH9h9VpL%9<1J5|x;DH2xKc+Bo(l7~X7ez=v?8~fmg^OL&-GuemUn$ruX*vN&2J=* z9ZQJOA|bd03MvPppFy z!WGHh@u%wD+fMY}(HNCn#XG4^Mx(D>|P<;FW1JD`pK!shT#PUE^ign+GSwL5PpaP z^Gp7bIQG;B_miz+ykZ)B=(3`@&9A-us724m^=2)8ainR?rQ4&w`l?*DQ0s-Af0V!S zWheWB;*0XNUA=Do@b2}Usz=*$x$yJl&z5xe*HeS%zHA(`?tR}wP3PvQ+M)vgrMzp3 zm9$-$?Uubpt?Tz!{`^@Nul3d$aVmLxbzC{GQu-RJ$)CHYEH4>;Cq2QIQ@%&;>YtA~ zP%n3$Z~k>$ygjSiEk|>#1?4UCY|Cw3Z)L%{6}R zsglp{eVXfBzxzG2`pxZVD=tB1u1`u$7*cKEnMV`1ogCfvNwaAchxK#iN6+OxqFVL8 zKU{iLWb(=Ih2I`IyMF)Y|KjCxrrpeXwbFnrhvshWQY-h9am^#TTBjsL&l&Wh=$*QU z=3f8w`;{Ng?%or)_@3Ww{!7{3wOaD1OF)em*4w{48!Q{+yXm&|bvgb`*~V9zG~#;L z@FFjJ#D}J2*%|(5SUH>Dk5-O<&wO~Hd{=kWP7OQIrFDrfa*fP?)BWe58nyt*ZrTrR zJAYqP%GO?gWx0Fsx>wAdcE@a;KjFXCyS`h|n@@{&0k>mwg-_UO8)_)7oUIc$|B%BV9lVCsm&KUZB9`eUssi><(H zr^eVSwdKFnd0?9}FJp4&e~`6#RK1I7ds{UfV5^j!e^fuc!L1rIYIJQGRJPYO&rK&P z9zJC&TLupr`9FTx|AIfk&yc&;&V$iTap@mpwjFERIor{Sqo&zPH1=|9hWh_*L8JvS>eCde}I2Z=l6bx{Nnuz_#X5f;oQNuoX;;lX+CwG7CW!<{sZcO z?OZx~J@K03)xh(2&l#Q#JZ^eS0OS9aZIP{&^_NFg_dD+YYqfv(5C7q1d*I)26^)yy z0rC(J1%-Lw`Djg|A`ABr%Xk3oq7bMZ*bNsdQU5~80@xdXMd0AJS`Qhc3L&aDb5^zA zFqM%s;G+hyR?QnAXTVojSQnURJoUR7&{X8*QC=$i6<1R;tqGDmipRueOo#`14=@vW zE^~)bDshz5!%}!O3_<1&$SDcMEC4y6fm_p-Fv6H=S(J*Qi33PbiyT_%GlJfV>Z_ET zBI^VU>5vM62yM=r8PLvHS2RP=KSI;YP?APP4FvNfS5$FGp3NfUR_iGtm1VIeA+hL) z;CYK+eFigqn)x$W1tLWml|*Tplhz+SA)qRT6IO`nWyBM*1zgu{#j2=#N%5PCgpig? zVek~lZ3+r{ai~&4jbC`Drbx}aD-|UMgh#R)*l(({{wzgvb5@s(i9U&TF!aU)oQ)V%ts7eCGxEMNV1P=wM zlILbLd~m3(WdIWkcul=IEOJ3z9RiE9Oql7J*J`Qg=jm82w0~j;h%ru0 zE4D0EGd>qjq!hM%IMtI)y$b$tCAw=eWl;P)Q`1e!mwe(sYa4tj##M7CJ;&-Sgv#l zP@1A@Ii22&2ZQQ5Kwx4p62XWI39_lyJ%F%UCYK@w!8HR+5iQFWl3a0Ubs6)+}bZ%JP5CcA__@SEjwzf@}`{gj8j{hOU z1XMLIJd#Wq*Aj#>f^rX)ar9e(t0A8<>fbz_Qc$#%sy+T>5n$mCq_Q=l+OsW}=ReRm$$vWrnhq zZ*A^Pa%?3UT!h>SO&{k6reNr=|HArPV@f_|7m5u7_Lgci&3pJU7m!UHT7YGln> z?Vq^rd3P{VKwgt452>ECYIm?YjN>F=1CZcQlu5@IC!H9yI38T=R#9a9|8Pszq!V0j zmW7GpWQb|3nW91sO8VooB!o}1C?a?YlF;fBM-sUt2W!3mMe`1PHq%~bD$?K^QZRxm zMMqp{R#M0ofkqk4hx$8d>~Wi#iZWF2%eZ&aW5lEsNFu{c4#h)J-~*2*JV>SUsS31d zDsre-fVpNZEW-R%esS`Td~uQHppS~YrnD%53qq<{BG`c|gVaR&-9kd4M$fE@q-ggb z)>Dv`;&7u#<%P08Iws0Y&}57C)HSh{<%C-!0uDr97@Dm46*f;~8dc&Pfd+|12o2b5 zzp*;n*eOzLLI_3MPE0(rk*q()c_B{w#F}bxzVGd~%P+()3;O?KeRHGpKh`Io_d)Lz z@4{Z^ygu`)?0MaDs%K-5ryh$u+PnYlzS_O7y9Z_g^mX%h-R>IV>g}@ICE6vo^C`>& zEaQ~!^tn@c+YQ@9TP^Dme!>6bf8efbIveT-nG;0J(p~6K7B>_pTzb}*69N>tU?h-_ zZcU;vNphsUb0S(|E9=GlKsdJq5}OBg2tDMu>eOb%9aW*>P@@#k0BhhBg1&-3rON#> zWlE+BRGrGkkC#$s%R~>Q=@QLH0Pl>cj-&dtrwC_k~`dHK3iKZI8aVpW2KwAfS zmrkFkyK!HjNPH2w8XOtwb~PQa)Sn$mL>o*?wYl_XQj0twHDnp~5?(`CPCTs>lq}jg zri7`=n&~fBxk?d`2G=Hf4W&*clBr#C@0P;M^eQ z2oorOwVruoJgJc`C#EH0X8hAM?g|p2&iJo1=i8w+*OIgLXp?*LU@eG5No$v#wRg?W`+Sy?Tfg{M0mm@ zfLE%mAaDfu0geu}LSWcHk%*ySzu}L9Db}YA{3tX7>75NZT4nWl_$(fQzNh&NI%7%^ zCg5hGVy4|;(t9QMwZ=mBqq;CoIfQj+J7r1~hxd|HtEMsMR6G7sVczc%90)$2hfSFVyiB^eL zqW{Btz*39AJ&0^6`%hRM0$rjcYV$M4bw+A|XHrYs03j$@pD`N74+$p+lt`yR8d(aX z_W+~=ZWg_33b+b;5Ag%(7^v_q1TT;@C&1P~%^;m9#6{(#BL+ll&*7g&@sf4uggYbL zB)mFVl3OkfqmLDhY%OnO+((Bp7t`=K4pikLv_8=Y7r_f<$T^dWjjkZ9D^7@xud-F2 zz~Q^RzwA9MDYp|LB0=X5pJT^dlwe$HO`_2v&d?GOJe3e^9Zb!*KEZUb6@gQ^3;N7} zn@DRCBx#~2%6Gyp5(bR}ML~&}q;RwuS{sXIoJ!>o;ErXW?q-mNg(JhAp#`r4WQpF*D|a|P>M7%5fc>~ zYDBXL2Mz^66v95L&eqeC4jPTB*uwvRIGgElsEr!vwTpdk(5J$tH8WSFOXo|*a4P#=Fosz zX%G^_ZQ4pVhJ&dl9$N)m5(v^W20)4J(o+IwN8%mEm|;z!Udyu~Sdhs(qG{4CiR&2h z-35ZgmJ_7K_?Y?@k*QO{zuZm7vM4l?-7y+dWDcyOUuHNNGqzRbfz;WF_$vfrQ5l1e zrRt_Mv52*e#+X>NkVqEC9g4QvvtfUrrAn7d+oeP}!3ZcYR-u!z0hMBlxt)zak&Jp^ zQ>6e&(}H>>agiaPLw7?AAD1{x%uQDcMggDSQMAO1v$n6)Nik6~1wgIj0-qe!&7?Ky z%nasnz0}&JaWi8cqKJ*8S5zQGa}4hdmy3D>G%|tGQ12$O?9rdJ^fG7tJRXw|1}-c< zFAyOzJOF?Qub{XJ)|_TqvNJc!!Ci2R8O0YvaLrJkwB`6izYaYnR-uRGBv5s9f+6cc zUxg>Man(&_z_BPb`lF3dw6SDy`V%&%nHo=FO*m0#lHma2kBU?yi?+knu_9 zPFdew*4qC`c;SEhA7hWqXH~N#=wQH5AWgyX<@k`qXpy6{?7OD*z?`u$h@!~4qwkU= zTm*n>xP)svvLNeDK(`0YdMpN^MHC8La774v!mKM9@*QD>gv2+n9H;d$FTPE2Cs~Zz z9jOtE!bcU4jYsv$1n&qfd5WU_8OkqW~ zWl%Vw1!(Nx7!kuN)o{?PIz(uywWuElak8q3-cVfOmoy+IizDip*J_7Dg-Q@b^omo< zePmL;7aYXNdO@6w!gC@rh$ayv+%-gp+_0tK1`H$dBv6jv6a`r#ql_qdk(sp8+}ViI z3fOo?EMde*<64GbYHJ}OplHmNf5xf|whoUGX)m~XQa&RViN_+wBy~;puUMF19N1`* z#N)0(I5YYY!XY-XvREME8eAnbkD~uGapzJt74DJTXwZpn5ed2P>+C)1)OqG^dj;AP6{^jE;YBk|N2P?0}SnR3#tsc->viDaYqRs(}S40OUD50M?P& za1=_KU8)Fb>Qv&44U(o~CZvJSI+mNmC3#4iPe#*lNGe*UInY;zKjBGt2LGTD;M^*# z{dAeDBG%UZnO<6g1g(CsZxn>sI5DZ%sM>@)B{@G4#N|I)rB|l!0{?@=Lh%`$2jpPZ zXkVk0FSya)d=8mf%F_<$s#(jTjRB&PPJ9qGPG-bV)Wl0=tx%L$wT#O8{frjzYpZrD zO-Rx`ZTdYUC9;V|X4qk2I4PA;<5H}V`kGGJGqNk@S}^}XBaFh%#_r-tfz7Bs6~ZO5 zmnbIyU_w|yrApA1xD667%{VEKL(vLPWH3&cH7%#{3bYujx*4Sg^?Xq&gi67Fk!Zbi ztn-{%vyo=X1dci-1wQLC9Y}zb(wb@{3j}|X`JYT~14u+b9@9!xgB@TW30i{sg6BuD z4~)~=Ta{A+*-Jzh1PY4G10EW33UcY#$%F$1C52^vNFZGl_Hl+|RG(n;G5pc?NwZ9c z$i$HR2Vp$$>sho@Np1Kycs(h&U`av@xNM83kZwlkUqKqdnlp|QB7IDSc1_o|oH#IN z&|3{j6MQCUiHamCM;f{?UIpJy>+`HPVDtVa(-()m2Q~vAh#WGN@Dc(Hq^}egpOBG2 zjIJh(4^A734xGTTh$KR^kU(n<6~an8>ocl;g`^PF<iN_zE4SV-#A9WNVX~Xg#)LYh)xB*BUdC;BnRb^R2s)eF)*87xZzkT zT*!{F+)d6xnV%EzAAAkPo7_<9@X$c-@DV6-r)N?%b6u1LO#)M+p^F0fEfQq0#(%;< zwLT~BH1I*u@}Y?bz=J6|*Q}hmO*~pwwH)w-6mC<=)Oyn-)3;VU2PqykU9)T$nq2X; zT75NAxdJhvaX6T$BBGKY>5PhdS$7{Y&LL08dVI+0al%bz#$DH3jWt|6)gi=7Rb=%yg2#>BC(@MUAu! zA@k~}#zt9$SyaPSmhQvZ#H1$c;w*#&)%1ufcoI5<#7c3LRjmh+6T~$rvJk!jzPXGj zrg|zbk5UjEixfwbNSGJ1RBZvDW;*ElT=K3#B({jlhA)SgG=UZ@G3AbQ&_xywPzWns zNWq4t3)cTR(=H&eAnjOlyBzgKf$5Eg73V36rW@t~LrdBeP8PuW{sC9#noR*?bF zOz^g3?n!$JMqg&0F!|L`P|T`Q$$%LE9HbmaThVuZpIR;c^XW{cY>Z_Vaat0nM!?fx z3?Ue9M)({e62QeII9z-UTR3ay#c12hG1ZuGL-6xT@sL~#)2aQ*Vi*W3*^e1d&bjN zSH^vl>b7g6Teq-FzTlt-C=7&3AH@v#1f0j3QZ-O-Bpga!F_kzB0)_X%6`^cEFlJLN z-gmuY>xd_luO6Mg_I2_8zy6ifxlVZaB_A;VFh(7Y5`tfpQ_B3FgutOpYJdad4=kY< zYK#=MF-u4=ECmB-EXt{=)g@BRB!DdhNtjKGKkk5c5y}#LD07J6=n{h|m^74ZL@Pw@=K0;45VPh5>^Ld3#$G%- z{e0V=<-Th5SlXq@uH(<2%p4+o;Z%{Ew?Nx>hjCfBKpMmpnij4+tkZmZ$(rVNC{Q z!l8x^zzrZ1B3G1E;6@Y$R&4# zasfcF5ecD2n}CO>s1Bhp^mcUQYZSL?Aon*a=D-#i|2LCz$?@Y52`Bbdu4Bi?>@8fP zRQ@lU-28G$)yb7=heus+aT_N5LRu_Y|E6$maQUmU7YA-)ruK5h=h3g)N^ zR|^M`lwYoIEYW__jlV``sk5)4`|Wk%KW&R$8GOkV=u&{tu%t+9D&gYgO@R^akG=%g zsx)#2FM>2D$&rh?7=VJnm?bR0t0y(exP@PGB5R+>6)NOO_!GJBtrfzk2{&V0Ziwutd zTT+uw0<{)@9c3MGvXHQt%vZ}*YT1ZeDg&8>%pj_huTS6DdA{k8GjFG!{n-EE)bl0w z7abB}A4yk7wSIJS@Ib(xn1jGKh&El-rC^<5nM`DFM$2)~4fuh$J;~mKU)QbrxBFth z`bRELx@|ScI=$|NK>O!(1Q5X@pp|KGxYAs(WoRojj_DB6N5d-87)+1+N#x98DGEqJ zs*DsjFqS^dt|3>$$30k;d)LDXfh)grt9P|&rAvYK&**oUEH^NOwTXb4A`=M3P1HmV zHB6#^qZE!cIhz%%P1(uJ@nWO2KJPrgMP1%^JYH+zlkT;AcP`$z^I3#_1buwr?dpO+ zZX`#-1!t`(Qbt5H4P|OD*oABk!J#7YF+*c%5{WUa#IV+j+x55pB~CB-B(3A4iCcc$ ze>~szmCqtV?8E8Hn3@>gnsjKH;^k<767C0Ze?|+LmdcGZVj3oZ#w_4u zHjZD`=fyW|+Nb}x;57He5bbgJMn16pxQ$jM7?^bHw9l z3>;5SWds=sIh|g6eBmD3e$RSsla*!O{6@1wyUpDaZXZf_Q{)z9l$E9-Mssx-%A{m} zhEgi3wL?>C;_(NY$ENH}balY-+)2hoeUvUF)wagWZrqsjZ_*0xDmsl879Mn4voSs6j-0%Z^TAKY`q@)xM7N+~xXSP+p@}K5pu(c!U@@jb3w=wRyf?S)fL$jd?MW`9DhGf?3Lg&+ z4yu@-`=HQknpcE)L+y}U@xx`pRpA~!3;;!6BkVy+F#lTFVs+gOlXmqSRd!R+fTVyg zihpM9F6z~zO0YeVJ`8R=%oqqYbd`Z)Gi<{Y7|N2F&8VdtZE?^?&^gp9D$1LEcxCBn zx#rAUlpc}RIDOXc3P0BUqIal$5OA*m=?S6;9TQ<>M3I0tcublYE!M|y3{btMh)QZ& z_$MArN?->u~%d#($%C(zq~J5jHS12|*#kK{HsIp+=%|8oHlQdB!#0{jAH@ z_CKwz->Bb*;d2g_=#p}#uRRv3sR7bm!81b17c&7 zIpjw@Z|{m5{INxc4)7#vB}qTUcVekNnx$6SgPEh&Z;L_K{!+E`PAE$RtYn-~; zKKPcjZL|g1ysaHpxc_GV2>*P3$NUES74!Yccer$aTU_8kDUfIl9FA&NID!E1&5^cn zo3&_oGl(Yjws5ZIabuHdXyZTJup^fubU}9V(t~L_!{21?A z;SE>acv-EPp!PviaHxw*emBuKP|+7Y5x7uDR!}wMmMnS@ZRupJE#ER!A@VxOa->U& z7?S3~CV}q@9y4DYF0nD;mFfO?x<~*W%v4oTcdd1LwC6IB!*e@;PUfgPnlGq7)G?Zu zLO>p*L^M&QBC6E0^dwrR$}j|$v8r%dRM*>#B|!fq&(FY~ki0f#TL4(#9zShfkg6Ih zZ30IbYHLsjXQ;4q#`fmu)lGb^^86~QN*^DClS!%8O!q!X`BrnO2eFa zh$Iu`7sv!V3q!vR;bGEnR^d1vgkc%f#QIJK-#WHHJ~v7`kpu}k`eX1fh|S8`IhaO4 z6g$A2%DScoiin#rC4L86-YHy%%UBuhxza+zBkCMA2}H8g6ptD@GD>cmQ*1!&yU0?@ zgez-isefBjXFpTp0uB+V834MZw5;$6sSit?OIKy`8oMH?rrX?Waq%PLIjI8(Rt`?o zwBBGy0ZrFLqy#4nt1Lc8-(D$!8~_r$lw~j1*fXsM*vkad;{It4LB?oak`1TMH61*b z3axx3C&b65nMc-W8lamyHgOcn_Ta{>h;LcnW^7!(~?w5R{7aKzl~`skx2vGE+cTbItU(Bsc9BIIbmy?WZ!k1Uo1?1|J%YKV2%xMzzcQiMv0fNlH{mwmO)!eD ztw(|&>O?5y06Z>Lrh!OT8yB(wz4O#sM#*ar;bLUQ%4ofVVdXn1?MJ{c$dd{)41^eh z^FcHv+7mt!;y7vh#2FRpml-N@FYPJpc-4PGmlaqn?4C@Er95`-TPuv(|L1o{}EOHZm36-ArHniKF(YoZi# zg}s3~5uINK$V%ZEQzqb@7z;Bi12DklCFT@&2r#s3^QPf$6*Fyu_9z>n2MJLaQfp*iEFvl>;3VS10Dh6J5F(3E9kC(AA6BV*!E5w-2ESD!jLn3hia~h9d6Wbz z0xByxP9Z%0E|TT0ZSE`h)~Q_d#JexcNSp{3rEo$ z7VF@4jSDE3HleCa=3+7t0(%4%2+}!nmzoXnLc;^GFb`3d@r~5jXf5)l`hRKg|NR&F z*YZ2#7w6~dJKMK{&mo^M9~bWx-c7uI@(TCz_FU}Q$m6!h=N>ts%iqrJuG?6*ysqE7 zHg@?13j0~W`)}>^tJ83&EVl1#Ev)NU?*HXKk(a6yOe2IH^dTbxHu80b*2eLR)O8;PE!9>pAYo8P+E*nNTK zGSsS7xq^ZzSi3?w2_6XV6NYLF>U zYr0oxb1x#qT$&ynD=QR*BY?0HL_>y#6_G*5783*BQa30eFQWxWV(3dL1IQd(_fG5I z)$q^XL$j=J+H!`aU=bg44|6H%IjCoYaE4LXAl#GSpXiKKC#v~N+7^yWe56QFRZ!k0 ztvl}Yj2P`6I_KX?SDISaY1FTAu3P`+`4|ywF3CLy0?Y)n6ch{14K{BfuvRRVIGyMA z7|jSyr(;PaGkTJmGSPe9#dhP9xTVA2_+EQytQgsSbCVMFP397`V}Okz=M{iM28n3O zA4h1`?vktu`3fm`G6InrIHuqtB@1a9)b+M`LES5r`%McwJt@n*hi=`<2AGR;Zxf0I z&QHK`gCH}`#FQDFCG4moF#@|41D1h0ArRmN(6V41u;xIq*frvHBkR4k>Gj7yo`1RW z;F0+UpW1gb&|HibnC|JKZ3uCMXg1<$HjFa+qQWpi9~(ReS@}??HUq1*0GGtZyi1Bb z6hEVP-}8C)zUWXgc-*vS_lifFixL?Luq$PpD&})rOZwXx7!sBQDpbsb1hx%!0~*0x z+K`_WDjfPBJj_ta#zAyWfb=zP^G}aTTMOSGm49WI^XHmoAKL4lW2m_ZbsmKv!^lX+ znFJh-kyBZ3YLRKiBYMGjY*@tLnCbqWyj>z8jNF;!daVTu+J5eQt656S-k$@u)C~wT z7p6dfK7|QvZX~73V+vWY&_Q(GK%FLHFp@W@E+mB5fnNqYP3^ISLKiIM1Bow}%^7fF zTeifg{GYE*nc(^C+l!v&LexiLCN=*SB|KKeD9SW42SC-L(Ov3L$S5*3#a#(`S#-Yc zL4Mo2PfHhgUG0>&i?KrgE}N}71eps`KL`>yfPxJ3?gJD6&y3P&I~4 zsTB5zrTtb1ohrS;|hF`0~c)Z{|kvwK`R; z)O}5T=VBq|d=xZgmr3slTq$Vplur_FjYgXS_F_>if+yW?EOk402qac8PjgqgA<@`2 zp3>^aT(6Z!y>lGOT5NHhO3TXJei~%Q4bTNBl$ht4K99};^6q!w}2sF&2 zVnRRacHqWTV9BU1U}Aipqm@UHQ)`=t{Vlb(jT|>EwP1&>h4IPg_5yVhR_{dP7<_Gss@S|k%Xc`OD2|`S-Z~8cAN9(F5%4v4YE%lCd}Pzrf~%g|h4j34^;ScZIX7($Ag44cjIwEhLh-M#7=lcMopD$T zj~aopYS1{7=(nMlPVG62+$KHl!CLJUeB`(_V4r zTs8x1_$pO5zIKdvB;F#J6I28#%{P`WYi^ZbLx673ISC)c3lXJZh8hC?w|G~0lX!fW z>pU+}&tdn~o-G3UowY1`T+}biGk>!u^=Jv5Rj!O`%q0j$M3@pk7IgW6r4qM{lLF*y zWZ#EVkLoeWg0W>`l{4u`=9$&TO_^K1O{046?T4aT|Ns%{ll^&hOD@Sz-g;G;j~v0&L9L*gnt0NbO?k;S?8T@cYf5Ox9bnw^XR^y zcI7@jFNbwA=YThnZyXSAawWui(JUY)nQ;+IXfs7EvBx772%)Fc<`gs*%L3o8RH3OQ zrd}-dEMj>Xo4hsC9`2enJG;r8ofa3H=;0=!@eZks)Nl$D`|$EbTAN3iwMRVy@Jb2q(wQq3BK;v=J~IK3tfQ}={b|(HCk#6K&t=al{lg!>cy+Sb((*mSUUv>PXQuQh!@!J0AM9(YXoS*8 z(ZNNxFW_ehW~Pw!u|P?Af)If~Knh=EANiVn^>iD2KTE26@&m8Bx7$~F^}b@TITNMh zsP|}3vn82SmAd-ZFo|%fVz??26^}S@DQ|6n>(~u}$R1;!giLPN>$|I0V4pTuhkpDx zHL|T!te@GPj(TBU3ob-`5AE0pUnX=^sODoe5>_U;it8RLtjoaAP;O>;l&N`d_&hE( zqQ!*34(HOlbS&kTp1qXR|050Vb5Q@EcQd(Jxz2E{;Bw9-!uhB3H0M%Ihf)9AIL?Fb z-*txs2Rr*E_7&~^vFnA|duwc)+B~sIx5q5g|J(oRW(G{1 z)t5S3VyjO_0~onLD+sux3AwmY08MzXh@6V&FRpvkqC@$P0s=uKangjh@T?or z*tPeGKErdwSMu~X!}!(9SJ4QuJW)_Uc+b05Sud(Xf$c_LsKY(fB?GEU@Ipiu2jxrHPO86KW4$29z}m2bKYy-n7Ip1*0Ac9BYVxA zE2GVT_AB|Om}H5f^T-kaUj^PF1#Csor^SxEWH+(!Q#C zQ8W}(VmRVOiX}sw0oc$Xs>JY7a3-e4g%*)2UoVB@;J3$_I@ERE&}E~|>4XY?a`D*k>W~N$NrwHO3FTb; zIJEfAS(hpw@)*?mev{4nmd?L(IMNKTy1cI@A_!oC^o3G%-uZGI2xh4z3K>VV(r|-8 ziC!I!M&5Mlm>ts9|7O1-wGK5%XuH~KPwnRGuU9_T(+q?-m#?COM6tc~^;^3-f|!{cr6{J8qX zK0oSaMlUEUihtyC+9-z!9uoWHFyM0qB@+@LT1rAO4iu|1CMr<==~2cvxqY+p-L5U} z)G6Tapf0^yy}S}^M%%HVFAjf_SxQ`jo*STV=8OY({V1-J24@i;M|Nwh>7pNv$1L4!-G*%CcNx?%=yON=S3YW`I&){ zd-$rIN4W}yvQwv+3QQtjgn&m*fH>rJI93Eb7fJ(h%gH-PAV3oOp0s+OkDzPl|~-mz_;-t*d0W=p%K zOE(9Z8xgpmmXu(LOp8;;rQn*M09(;Mu z-fHFcm+g9HaqRF0UCj+C=mYs8?hV-4aw3W6gKABa|D7x-z`ZCm5bLmt$>#@W8@3nU z{fyC0zpmXVVcqHPqDkW&N4#+!9Qz~4+<9aLgq5 zi8Fa=!1IBmwC%gLC7&FTvu~R|1LhrbxZbBirW`Yy`PW6RbPAwTxyThV=5^&&oD=DJjt zX|XagVQMLfN+b+6-3T%Y>EQ!UQ%{tiElr}(2OZER%8y{|1yYpl@}T)^J1yTDeXF%k z$ekyhFE5_}7^4m?T`K04=w6?DNWf5{VY_3KsUU-jn4${;4$=x>);NnPDIl_#KF)}9 zlKFewv+EvFOPfXc_8*%5>|M)^hfA5vwdwZ=WUS_7$R@`%aN2VHB~38tFDtsAHlLAt z3aCHUbTooBah%sp%wKq%*X8%$8*P|At6=fDcXxGb5N@tTNd;&&^i7fJ;HxRRDY!}m zO5y8c*&(Y&&P-hlJRd>{K!~DSV}d-{X6s0(_znL|ulR3bT8Q0S*Eemg#D)0<~K$lc81ss1m7TIJO3rs9zk#H-i3xjo%>!1uD}-&^p1D$UV8IDiTKiEZ~ML5kT-t5Zg zsLb4HED>N=mbYjSS(8YWD;Ar0;Pl242bU@!s`A5iQ!m+n8gww_{m$$qqt+C;H)OlN zxhmZZ#;6e1ivP!EVcUGbP54JjHApgr4M_jOgKQ8QNJT+?PW&P`cc5TqIwMG4ag)3G zEt+6eYwMx1p2Hse+aY&Klip$GDzrmUiwIXsqO*M{^ET zvdBa#736jES96ir+JG>ej4+Dl^7TrwQ9U9)ZaQ}MQ*6Qbnm#_J_3;7bN|eSB&5{xo zNg>1=5WxY+TsV>lU~}Nek`!j*Q;0%LR4mI(Ft&>(N%$jdEGyl-6H#@V{7ieyES*~tTPSXeGe`@HPphw z*#z*aX$KRx73Bzd^sr7qNztR>`hznR%Zq$<@E6%T8%HNHbH;&n&s%X4_SajnTudvvdgsHu&1MIfp`ImWGe9Zkkt;fT zhP6OYEMgvP2h=n;Hfc{ljwm81pt*2hj!6Ow02vONBGIYc8>ZfAR=s77#p~}a^({AW zQ>`*(M7=GwLU_}29S3l^3 zsiZ;sk6Xm31%L@tPJlx^I=1_=Vh)3Qf4zFSsPV>|Vee~KYV|(Q+=p%l92TDJM-EC@ zGh|)Nv=bZ#G(=E}GnnR4z>B~#1J#=_0^w9CW`fYui!It@-#YjE*NM67e$Q0p;j{*s zCWVKap`AtbuRPx6=x6VEod@fvttDUp3aD|RypNDc(1;Wv;86RzbwWC*m>OXF3-&?gcBYIlK5fz%6In{j+f z7BF6ev{oJmMpoE8e)oliWwZah(_ug{OTe)70CO5GHDSq0|A3TK;v=AtOl^xOX6wW{ zQ2hpME`Cp(VTq=)(Rq*Hx}X2bUb0W?wF5s5PZ~14TYi%{l}-bEaVl%TQs9(EidFQG zh(!~Ewh}O6IrCcm@D3!IF|~Y8;+RysPX*q zGanUSlH1TKbC5Zio2B$qEC^G40Thx*(RBKP7ad2nCH_d8dFc6tDDM#P;&7+R;OE#F z|8Ipl+@A7lTi-QyrNX-$bnnomr#Xp=P5DZwDPmE%;H74$3>Z#NprFaftib-rM}v{> z=!!&2ATmhWaCpk#>QCA^Hs89Z!`3@bj45g^RnO9qs8D-v7Q~aUoe>y=-ulsg~pknj^fgp ziUh1F)J<49*!nWmg~OL1iLCz+(p?y3*w~Bc#s&po02LB5P5wohfQ%FiQurT#X@p;>CRnAZ8EciM)*LFPDGUmCY%^Di|}hPer9Ewg?x9Jmv54?yP$ zjG$180g3<)Pd)CIGU0=*AM}5?&vD7jY8P&t3jET)#@dVF0p?iR_UZyqBoldr!V-W! zJRGrVfzz}qF^~n4x>e=DQkhYUC;>PcNL{9oIw9H594|ROxLW>mYiggZaxLey&W(f2 zF?1(*r05;1I!~ubG6W+anu4~)tiuw;Z4LIRmw#Vt_md6k z4!z+Wy)~q>U>{plcG3Lxox?UgJ<#BaCa})ttoIq&YgMbF5Ia5&+`6|9FKYi3wJOhv= zdQ{pnqAfnFvGZ&mC*_;FWnZq|EtdF>ESoFrKyXhpG_xdOrf#NY^su=EZ+#je1LTrC ztSH{%V$?@Q=0=sFZrkxPqXu65*V4VF_-0+56p?S;y{1LHCj0wssuEy^QZ_Fn%)HGZ z$qc7LnOkni$B>&N5yM8nUL+NlDyHz@ktu_1f~8B7`GkJB>W?^ z0MxQVZjb+_43UDJ0(gHrw4|q}eQg{vK3An~E$SYtStXm#t1)A<_>BuThcXr`;z)t0 z2$R-l65bT$l-QP>Pq3VjoA1t^Ai+IhBxtDr1a@@EJg)91*gSkx#O~sDc+zXW{b^28O zmeO^cpZngLnovcXO$TN+iSVAo55DMVn@kEN7s00qkKzIU)W;bxofnp{hF(mjz-ByR&^saE_ z?b1;!0&)Mv^3AgdIK5Lz5ox?D| z>X22Gv7h_u{~ZkY^WV`iEo!ZDrvxPFR7kTsgFxqk6e-7RFdcFKh=O6%wZ*=m)I)|? zplyTv6Q>_0AQ&6v>;$lZuF5`Gh=r_jLn?_dOs#0(UQ141Dh$00yj*}eQ0si&mc!v@zD%$9K2>nu@-_MXqzB1iIEoa8;5V)zP z5I&vS)ry`-397VpIAj>hxBQQHP5Fc}GSf0;960zt2>+>Yl{b&ZBc;l6!`h-8=q!s+=0|S6?qRa!sFd)jhG6)>ye_xzQEjXu$ z4+wCa$sfcHN9xMv?m~11sex=PGA^yjE23(Kg$yvnq^9UfX3KaMgmYt2QU#^mpoKWW z6c%3jTmW%c=kUk;QR?tU8Xk9N5vqO!_*OU&xJ8aUAOI_w z*7HEB>qVuaQ4>Y2Hf8YYYi8a&J6HF26M06YV*~5@@C46w2*%Vb-5wzNv|*kNNLZEhbJfutUIVatj3qxI8nh84 zQbi2~>))+SFz8W`njGs(eGP;#NYg#$fU;6hKVU>6a*&gnLQ^G_)Q3&<7&^n3izW~d zUsxd~a5bcsnXFI4=yizqdG+^I;tJw#BoU2OYv^%Vqzp_X;2C~$_dvk73<0*loDND` z>>Gk1R$hO{fT%#gYvMbgjvR=F@Y_+g(L>cNJ}f%4mQYauO`z5xQZ)2waMX}@oDzl_ z{`UFvD9HfoXqN(xV=A=(&+5ctjT+W1fgntGVp;@ed2E2*NfN~t-H`vsxJ=rjc}DnG3yevGPX2A&BpWkuFz zYB`oO9s{EQVn`$dQLw1E`WF8crk5HXKG8?1T#}`kC}XHBSBBuI{wX(CQB}uKcIQV# zKoL*pgtEBflh*LLCh~~<8IO~SSmx~s)QvdFhlcnhO0kBpjOR<(ICx-WwW5Eyf<^-d z=$$TK6SKyMFG;72#_B67YNmGX%gO2>5OlwZSD2kDKgYi zWsY=ofE2T}uGo+Y-Ai$m6dWW6XRBgZOt~`NlKAI>s`*b&MHqK+3_G&B~GF{L~YK^$PDqSo_DI*04VHYf{kvAaDbM~xNkk! zE(o=wB*em*XZsUj8OGe^^h#m*dt(^~?Wp+tQ+D;UI_!SdPfE;Fd@Q z9S&Wg_TVMQBldCFfO#y=JgW5x1d24QdxUzpUdB_*2#6wW$SA+DwgB*;MWBZWDjOH1 z`=q?dcoaH(rDA=deTw)IYM3+OKXyH-W0lKMFz_SbfdGqe4bGB8<<90}cmhNfQvDDP zpUlY3DIQpQcd9O92(}NyCQ^B_qDsxEqmGwmp{R?38HMPlxS{yYNXkZ4{g0(DSiT}k z$wNj`3{E4laPVzWme2?mc8X;;YDxT*yEV!UbO8KQy~j9Z2B}D=Hh`! z02&KC75_uw90If%p8oqEM+cq>3Sn9yh~;26ATVjeN0#>pbyHz=SXl5|6$ulPGn@(N z@!>JqUPLw8{LhN?|Ky)hHdLZJC&*UKt8bg2Lln8$#!3Ib ztHFJ;dug}hZUL@uU5C4RyBu-}M*TnCxvbMMr=E^)9LG8qbU5hH&Hj`9DEnfl`2%b} z*bcMxve{?2Fzb9)`>nbgKN!atOB+rYy3#X@v=k$bq^9l%iI*};LR6o>E!g1# zN6ws*hX|aa6I?JnkopKG5A_00UBJa4L&ViedsphjhWMFZzb|+i^)$(nFl+7K!4}AH zVf)O~T1LP5RMK`asPC}^wZ=;YPbB#drR@eMaVM zr!Vg7FmHcX3-q^{A&)d>o&#|c&+Sk zzxflaCpeZ({%qZ{=$yfgEG7#ix6qHGE=5g^5Uvwc;1}?BQ;n-|lKX2+LnXS3Lke;s z&~+4N=xnNWvteOEvn4NcceR~;x_b9e)A~ohw&XWiptmjT3mOs(*a#NWs0972YOx7} z497h!Pld|sAxe^)Ad@7dSrH4C=I(x}W0z(9ru!Y<_qgBG>WfA+a`U%9b?fDezecti zJP}Et8v)qy00s6H|0(fwm6K1ZVe!Xcg#%?5b8mT6>YjhW(FUg+dpCQv>qE%{NmczV zklcFtGRCFqJ7rzZjW9J-l)e3tWa6|@@UV!ag(j_#dVHJ2(uNv8YYra$zVNnzx7t0q ze>vEx@r3|OerjL{Z%^5=P}M5N;f(pBAc(`GKpKTyNuCb4cMN3#aiVrDQeg75rwXdx z3`>*kOU~YV_3rupq>{6WI0uYv-_w$h+As-ea4E)LQ#2`2E)f9B!>18vsa+|+jpSuI z=fqi0uoxw#D7_B(oSMCP)T5r44$P}_5uvj)aZ86? zt@UCXA$3kbA&1B+j&Ph)@Uh#v{M=6ogFO3`Yf}EmsetHFYw9}I4Yhc4CzOrW$P9+^ zaGdfeg0Z4$QR4nFSBvLF<8D~xjsS@OuIQS)8dd$HwZJo)SN*WR=vnkR_W zc;;*gO90EvVQHQI9lN>l(0*@wpE}p;V6Q=!ydy1;-j)X?nrW2+X{y;e;CCVy0o+Te zus%LiT>z*as1FKKR>-NQpgR0fIMc|(yfNF)|1#FB>oK;4UHeKuYDDCYY>GccT2`?(1_8D~5Wa0UJHyTp-2(NB>kbW2d?DS{eIorHWn zTy6Jstna^Xs_pA1fu%#oCGYKF@g#K**=-T`UNK@7g*}W}x6IBZKVfWMoR*3bO>ip{ zrKDF!E)j0S_b$wK<>8ab!`Du$m|nF{ukh}vJuDt{eX3xn`6q;5UFKswkVF$1C0S>~ zL7s<(qSC2TYx18g#+<6&^FZyxWW0vX&w)fMRu!2M1shI4~49SQeG7O0AslA6&W-#z2yB{ zdyc!h-I;r`q;<&$1Cy!*S+Y}ikrgf|YtS%?PD3*p!Y-(uXi1YW84r$&0GVyTC3Xh3 zK~Yv6aAI{_rT5f`4fz($d}dx0oVa^NK})fwE&-No9G=NX6|@@p=8UO^lKe$^Na!tz zUxyr%Tm?8)g4HTF4wTC;oeBHk300gnaup*IBQ;Q+ea8U5*EW^A?JVRy3p;&~{3L%D0{YSS-wa-(& z)VnrMw$~c(xhdz+;@_eyS;$^m$oAS3NH(I3+CfE3N50}`1Dl|(9Yg?hw%|rl)kBgk zwTWuiYU=6Ie_P~kcX@h&)yoby$^SVacY6ABhhR%)4%A#uBC*4jl|3&r2UXNgKs`P-#<#QAhf>EcSNcsxV6?qL&In zM%pu)aTkt;j=im%}KqTckKDFH}|7kUG~S$>}GLc^*{5)DG=-KXE5n` z#CTM+g&I+keFZ=a8<lgWb zFj8-d!lIatFu|RVTmC$(pmDLv#4qKU^xU4Hb0~Cshf=vG-?87- zF^_w4$ubvLCWX6)S{x~L;t_`c>rlDltr5u%imLEvfP~D#(Hu{`Vo@O>J_7-S0XFk%Q zt|;dvk)z2p21S@ozjFTjxi9YHW@YQPF1%*7oBP_o>KtxC!>2TK&y3$lN`hU^ zM-PxIHk75LS0i5*@LG*$+7Uu$HKN!2uws zno`>fLf0aHi#}^N^qbIU)qP8~Z0pB1DAhGjq@_8PEm|w16c#9Q2;(J73l-cI;;vXw z3Vp%vr6Vc>mR0qlFpwPk7b^SZ?0~xKdi{u)+{3NgE8j_;!4`CR3i`5t4u^A%Aaei| zBp*dE!O_^hM5ProIk6R%DM-3tMl>L0$iRy==H<64(bG_5^4;9~44q!j4BQlAX-Y@0 ztQmzvBG0AtUh86|ex`MSL{g^ap#TD+h+itTi`3c7J3u&3if(kg&%K8?S1vqNCpP8Z z6GJPUNLrph+yc=p#CPPS1iYV*U8HoB6>!UtL%vLYQUnv!sdT>mM$g#rj3_V!e$s0s zcTBsmxDA;mUdt0y<#XAiK3f6@HM#RP+|rnSm&zkyZ9tCFh~$iFE3;RL`I-!fScpZE z!8;>~n^G_;y(x#WxOsb5P0Dr0B{^*V#bVwM{D=Ruw!NPPx?2xl$$kZvMz##fbB5_E zc?_|POYsL;b;TkbMQ32yClBE}aCu^*G1qf-T@m!WprQVNlrrmU<}_IvQm_~0Bl_f+>TL6&-S5-Z6A4hNi5B>JPZqZs3g?u}=P z&t%|IFvJQVOA8l;fK8GA}zwA<4N(uaYO(epynXQXC-a~K!bhD1mX`@derOF zV#|K}`!j2gZ{2(U*Mf^)`&p_}z(HwB)5VGQrm{Qai|7oipoer2j08T~l|GJZoY+Vb zAS4Ac5imYFEwwG(ymn8IZ#9K0{|?ESt5;bhOBLEdvrSWv+Ule(SFTy zU71`cs`A?kp_VGNQ>Ff*j7K82TFh9oI06v!Wnhi4l2QalBX>+SSQ<4wdT-P*REf-w zewXGPFK<~izhUiH&uXr%@nzidT_#Iqig1bsM}jMnKx@^l3Q45S!rDhv;KGVoMF5Pk z0vCKJH(8Fua#NA3Dxf|Cz+z|${ivYO1>Vndw3j z=?`8mmNf-DT{hu@)DD5{5ATq8LV)*aafhf>WN&+qyv94bJNlfM*8ai9x*kJhI4EQygq0iP{I8~YWkNg8c zLY6ofaa>UW>1DT8T7Zokbsd?revTPYy9@8m*C|ga|2$2e`CCd5P%C*-Fvgc)aq5Ue z-CGf6$zs+J3o>CI`;8+?V0Wn^$N+P6(Lx)W)gEML^iMju@k4a`p$jY23$_%eoPdFR z41mF=MB;*~Ee(7oN@f*nQe-*^zFabhq$LC9#4!z@q40R+*RePE{Zlnwecn)|)XJ6{ zzx4J0P$p%I^#A?H|G&E1Rkt{|Os<<Ryu-Pb(^IEmPTpYtw{dvmFw7y3 z{d)VRcDL=4?VN3A+m^REZ4+qy(|WdbMXR$`LB>zUvBn~X0|q}00Q9t^a9>k9R^pox zw;Kx+B_T3NkncDwM~0__BOXvH&$FGdm}uf5iaD~(CRb5XpoLjg-BK@dr*GeSuOEf& zdE9f=$*1K^mSj3w0QmrSgDVs@lTdA-&Zqb)kyufOMS`v7mMRjv%mctU+8|0rRW>~D zV&y)EPo4b!VZ!^a?(6nt+Ip`0=|D>otzT`niHsIeMGwIhf|0tUw8FrC#*$Wj1upTJ z_J&lRvW=LSw1}eX?Rx}`&NpdI}!qd+1!2FH@qu^Il)Mr}T~t3ksu{l@o6 zZ}4MkdYC1iz`In|x&${ZZgU!C3CzE8?1lv! zJIv@9mA}(k+u2tlZkIM$deLeKB6*L%5FprLZ|W8nM`L9(gRj7h5>#CvH>n;kmJZln znjOi@C-)DlJ!7hGZM!YypdwW=*C5~1D!wea=%siGr;C|Hn-10^uqFd_n zsPS5)Z%jkH8HT|%AYC~<>G-eH_qH9eo7~W2Wb?QRKzSWrXLLB4DxGVg^LOB@WB?y$X5OIo`6!fEq8Fx^8(rxW~hY#03GC7&`Lt zy16puQ$>Or4iJht9FsHF2bbmerFa(#%aLM=wW7pMcsJ%|mdb73R^8|GW2J5M&jU{V z*vg0h~CvKWO=2zutlO>WCnF=KEP((ol;wv&&4Fc)lOkG==hXGe*ah4Lw zg_I>Yv60BA#BI`|rd2Ck_8Q&ZGX09xDeGGcCne{IwnPw8k273Ym?B%!I|F|#j_z!C znL!oO?jTI5!VyO?QM~*Bj3FwjX3~fUjpsicUUOg7XGdpS) zJn|RH2ck)eshCyLnNnl^R2u;OyHu<| z|Kp|h>9br5HO)2o!KvGwpIMnK(AAROIT^SFIKg=2%G8s3wOTB%>v)vuNKJL{RLwXf zcA6na;94l~a;NPaH{UHqiYK%gU^%<*@K`wg#oVo_s&GYvqtaAr|Oqi$d)z z#`XOAF~b_aOZCz<*NR>FkVlXW1yWEH0lIw9gQPG>73+{#NBiCGUiMpGvvSboDvf>~ zd6f0mA)hiq7Jn)X6f-#pa1&6~06cEu4N6r?z<{c)gOL9791jKJVgwk7L;SrkBL?vt z=}PjW=e75zziIG2N6Ard{J!N)c)NIhkOi{Z{Jy|HaV>^Ww9TZNMWg@%aFOY3LYfZF z7rrreo6Of>ma_s}_=ds`^sl&V-c;XVJ^hx)_S^U?-|K6!{uU@}Vb6?`gQTq}QiQzB zuLrA{0JUhm7UXs)t)SyYdX5)|jfxe?mSozC78a2S3Ddo-+P0fyed_D7&>68?o|Jxi zv-6Q43k0?3+mmpaJ=wtApwvJiT8dyLgUZ#d>%CE;2N15l8vTM-ZlJY*p0xeZh$eqyw zrWcN;BOVB95DJPQBjJH5{S5jg2oZc}2MPm&>{q$Zw}M}k=qcKx7}T4Xuub<$IWECnI6wrh z#yNyy(Zr8X3eBe@D|D>X?9lSrA1>Y9R-!@tvn^Nd1zI3&@u^O(a}&&3FPGr>SgAuq$b+?XtV3 zOSLC7+AyZ)wq|Xc6kgcX0?`II$aL(g<%8-KFFr7d@o8yBt4tQK6diFOLz40t{nBuZ z8=|QSaDv^P9JOm~u6q~SjIj(eIlXILB4uBu9u_G7;LuM`nAoh^%S&;7k)PwCBg8W$ z8Eifx4~O!o@-RBS@Z>;If@?0lm|D(jHU&8@m^Q!T;e}&9ZXS|cc5j6c3(?qpg(^ca z3vX0tJGuxX|I#=ZN^)NUSs)qdp{%2UGYExvWQYf}dr%DP%w&2!W79%|dM((KzNPW0 zVx8(74|>1B-vY4!$j2o1)br6*5rx>7g6e}oRY5<26=)I&sz=h&>McFd`5aQgmy_p==sqyh?MmPB)=$Z{?(*CG$^D${-x^9ALabe?8UySQic=lOXZCX(8XEVyuQcp&oPr5}uN-FC{j^(R*Ut94?MmA&#!NQ9Y}O;c zGq`%WoUq)9z6A&8Wh=ton^ zfP$CWQTUcnti)3zk9rWG{t$zY&?xM5TE0N|nW%}5X@artYrNos>Z)krA#BRz7IEC< zPB!{76eEBiU`1w3XGLQRAzX~?LH~;(^(nt(W6is*Q4i5Iu}#FVgc-#=bOjHdt)V3k z$E+dz5ULsEQ%s9O+Dz!R7~!cPVl3c^FkM04I@F7r&!hK0sycjAtm+I_Y#^&LRdmu0 zFWNUpsLi~hjCtyzx$mK_EuK|sz#Al=hB21fb4z* zx9K2{!_=pog~NA63K3?ZT7 zvD3keKv410&y+0*k^Zn}VGugP5MrrFNy17W6 zA6{gr5=kw=hoq9X=qJtC83@|p!L|NnshZyI1nD8SU|I}94JLj_%BP-bj5H5aeWx`u ze86YGEvo!5Y;ySHMIjhN;)29BHjVOCaqYqRNR4j5YT)m4N+4YVb)4ct$Yt0U0f>lU z{-!@R6O{|}q7*%!6&#q&#S(fH3>jwqvcS{@^$p=%;~Bv_`r+5^bt39E_gX}D+7-vE1$qBgn2#~Q|V|09I*h_eJFO|<#xuPRX* z7nDq4)16RtMSww6mXXd|<#~o*F?dPk)l1h_AyC@mz$Hlyfhqb)=mQrI2fqeoP8d zM2I?aHN`pl$Kq7*nW9xl-Xd*#ad@HGD>z3GCn(yH547}&i75)6JQ;Ua6FIh=nnGh> zjus0^crcdYUki6p7vtd7fER|>ffYxGQ%3HCj4s}2f7@k}!A8F!yfr2!0oDt#O`pd6wL?pW zdzLO07(Hdp9y$WShL0n0&BE&45cvqXUNY7f$|E#(K#ze-ciaH;6 zg>OR1y2A$~Q~5KliMyAVE06{c0MPh8=fBz#n^!;vgMs^}*s?_a>TUPOx5Vlr?<{F4 zXfrEi0{?sY(fOhLsIlg;)H%=17@`mx9Jb+)n5;XY!i7c5;0lLEBI_EA*q;Q~F*u`t zt4cQ2IfAn}v13=g+i}}`9sC?tIoh7sB%0E!f)XDVY6z!h0ZGQM=ZUJfW*D4;xKxlH zDXk@9*J8XM7BLbb4!x?ai!&dmGZVTH`hl=B`%4Yj)Q*vA3Un_Y9qQt5tu?s$g*5c2=l6)siz(J{jJw1 zeO1oosbfmCh#2G#EFsYIE24s85s)+#ua7r$R&kDr&?FpaV)LXwvv1WGmU)rdcUq7T zH5^Niey5~NUsJc&xQj{$tYP19)VC$|Bc!qVlORgAExgv;S@i1!wm1eqRM$(;u zvW;wvrMF>hd<+cTTpX>uM4J-A zj1}W_*hOuT5SfH0rWqADl<;%-Zg|rSY*0}Qe;0TH6PCdz1l1d&Bvjle!`iQZ+_YA} z1BZvYnB*V8pv6=dWjwwvt}YG@6bmlI?zAyJnaC&LK-M);|MW-E3 zbDahoPudl*bF_VJd)aoEah>ga+o84*wykX|+UB;kvUzMQY)rLUXtT*?x=kOOK%2%k zrL4bL`x=bv47vV4f{~}SuXS3tO@&j}RhU=v;JwuB4{tZ@JbLm5W3ev$QLEH32`?Ov zF6y&?k?XY0mUS%+9>$`d_#^xDDb4D&cw4&g(xM%zx|e&ut=A8u&3FE&z=gDg~TAX)IFK=wa<^mFDp6XNjaPM?XEfmp^#Ors^wd29E2wmwzf?^49|% zFQ#rNyL3tLr~LU^H$OMUSfD0<B5X7xt2smEWTa>BJB_nWtS zz0BkH_p*Y&rq-W6-Iz6nKPvHcX7=>4qw2K?ulJ+;=dXUJLL7{4H~D&ni`}~J;gtQ` zqJM3h^gkW7amua8+L_cRk>|SXF5l$)o%sRbf33e0IeT00<9xY-y{(Tv-<&?g|Fchy zgqLk21E&2ltX{}Jl&|)I_qA)jZaraH_>{!87jvu(soVCVd{Vsh*T0{fw2f+YXtTFZ zSg#XRcjleNm-CtKey`DxdA+_=s_U|(*G&H>vsxNXd&);GMz30&EqL(0!JW=Wv|3@k z<77R!N2qxsO!<86<|g(8nJ0V3jJ#nAsklAM(aOfMpx*-(k8>WNAgoH{_7*2*J$2n zc&Fw)&pkS7Id;vM=L~;TcF6Mrw?m&j_ZWO8SHI;0J3J~^!dNQEV6bZPUwrZZ`J?}N z0{^`yz-!EJ)Xw}*?dsmW{-9OAOrNLqnSAV=vGq*;s7`Iyg>gL!O@3FSueH~KUi8BQE!9$<04iyJ53eYoDAhn*Mah_<-SG&A*n1 z8S5Y9kMg!nJ{kPJPu7)-t{t3n@n-v(nXedYc9pM@l6~x*4Ns%m22NVLBK3IA0+U}E ztIG!Qn7QeDn}e0#u7A^FQRzpi7rT`C$5{O^|5SykCk~_x{p`>LO#?@qH?3NqC8^=PV^fdfqY(bcee2!on|GIcymtKeA`|kh+&9@HkFjcd z{;1LY8RGp9)q}iUJ|_BlZE|}T^*pn&=u`Qqem1*i6G!g}{1m#ZTAqQAs^(p782UM_$dXo|yeEH$T%Z0rRC<_$SKWA12hV1CU)t2)(c4Box$19K z9N1ewq_GH%#R(vpi+&P)<$1Bi9BZ6-RSk& zknK}pheoGno^eiovoLWC|4`0y=4=fX%(ir|Qf0=Gu+(R7vaU4dJj5U6xZkYkkt(M9 z+iMmXd&#-ugpkjV4U-c1OJqMgZ?%*CKcnYtnlWop@gwJkTGrRPB%kCjciyIZ*0Ozf zEFLgEb%uxUzjI^T^C#K1lxXGe<@969{&kl;&y?!-*1AhoIf(vpxVYn>*S(rK1CM5l z8nrX1RqL$jfAh~|^-XD5+c)%S!nv|5>h-)+zEAK7+j;!uvJ@FOI3#)No?f>%zh1Ha z?1%R6iyAIh74-fkPQy2if!Zl^bY99{WR z{Lewx+Rf+7xh*hS&J>^MF=NKxrO!A1yYkEnV+@lb_=o(*ce6jg-FeE&<=rq}X;fm-ePt??JMQLe`s(j%bXd(lpl*d-6T4=bNS^L*5ojZlEkD(L}Skp%jbU`wr#B2u@Y(Zik*%% zoX^SE>wbHtaiM$HsyU-J`w<>>$E7-bGE0WN9Ld3s9frkZU)OH z{;2c#-j3;aQYu&7-*?9JJoO@`RgE?b{>0boTz%0+Q_rJ=9%bEd_*=vCi&9Fj3X&($ z=~WR+QoJQ=sn1&*wlGaAwtRIN!@D#5L+xCipD4DgP?end?!`5F*`@pIUj6;{@o#E9 zC!+V%iBUBl6`V9HyT|BV-i3=9K2PHxYSp&ts4}+0ee?hF&irmu%nz@1wq2w7la_|q zh*MVM>d&lF#{6!}KO21tCy5*TwB!Zdfsye^s;e_jBrv zdvs~%)B?vV4oEmNH@bhKBY)B~Akr!N+2{wkCs*)q^mX=a*As_b1LYIHDV-~HzP+tU z=jEwG8?CkNk*P%G0rE-Lq=Gy16kA+w{Qi+S9~Iv)``eC|=#e35ZgRWLt?<65-Zx%* z^V*Co`#)@MU)ysFf6{2fLcb2Z2iG|A=+u!~uZ+&Y2lJP1$amCexqb0(&(8Gy^8N9( zvIB+>@hv&i@L;U`Q1d*m!tLVwH0m5tGWFG)i+9%CsUo|!e(}76o%|mca*6ERa(z(j zz1y{Jgs$Ps)$7r*Rb<{a!|#@|uQ{u8tI)Ya-Wv{v%SSCvS4r`%^RQs10;{^dz7$!s zSdw9NfBsGNGCyo_@cP)%!)#g|p4R^T$)W+4BY}0H#gF;)$w0f=kLL9%YLlYKFAQrJ@<-J_joo-YkM-PBEB4>NR_FM{CW-kC z4@SsG`P?o&>oMu_rO7Sxn41syaeMML!?i*DQPpw>FHb#C^1JheAMeWkXfwm@T{gq} zq5R#e+*(z;X6BeBi6?4?ZVw!P-tb~|+&uZ@$dQna1-)JB)Gyg4oBe|qJ4UU`DG&%6HyrH*#(aHPqa+8_VP-l52wQ^eBczIQNY808TXoSOj~jvmR$+0l&8CVm7m}VIIJ! zK305TVCmG2)D2Iu&tsgN8%Y_>9|n7cO~MxH5R?G5MV}i$bXuz=jLXUep$LR*1GI30 z>6NK62zErxW0ojr1Jtu4x~^PL7|NnDfQA`;3B$o{jCRTe#VtxHvC#X82CT>;e%(n) z0237*c4EJXD-q1SOdw=}n2Y`|$f>{z(~RX$aG!;*sb#?2VA1X%35?NZcz(iy5ARPQ zwzw`R7eIT%p`=S7lg9)wLu<7B{WUe$SxF-Vt_OXDa9_a^Wu^=P+2FF12th3Tb%OwK zH~Pnh_5O@{%GadnR>XT{x|M1QfLaI62QyAGBusog&2XRwB(=!Mwb|$s&#fT29JNYW z@Tiox%B+QDwu&U8ou3rj9$ac}fuh@kE3$o=(LFavGr45>D4!YC#l{=Qnz9j8sqK_mgZ zE#{5tTY_D{UNK6+OWaVn{}|>v{qaTd<{(T^w*%%L8g@L<5N{Shw$ey2t&er>;s>Y& zH^g_yDZ~-^qAY)tUNU~1+63rrC`J~##_VqEFO5rQ>kg(d=3DWTW7?R3jYOnA;Qs_YpjHC6cuhlVf#drhlPBjUK#SF`#i`3K$twVS%Dm zDFPL_rl=^i5($z;P*GC-ey>a!Go+eZ2p#A%5Xpj?tKuaFUQG}JsdU-3MnoPh7@Js{ z#Ah=+i-WyM4z)6=MM6DJ)u4ur5OpkwXvO1$M8>X0F$I81hc>-L0I?zsap0bx+%JZ4 z;EqJ$f~Enq_JQc2(AXBX3QUTFq6ZBEp?J$R(coa+l+Y1WpWk5d@;(OV?eu2+(nctil+v`EAcL{T$sm)aDW-4=-SbIAGEGy z$gwzsiQ6A|Nsyx>41ns{Cl#=usMdwM|?ph zk?TMXIRW~}Fj=KHCtM#k^HzVtR|HLmJq>B_4%(bxu^9^v_`TSLbuES}nq(zJNtHZ^ z$1$J3n%&6~eq;}{$`EQm9WgA-`R666#|X^~n3y!Jo&1gUtfM%ZP@x&$5Kw@)E5<@1 zJfrEY2rG!z!N#n`xXCYftyIv8u^9))F^ZsAMBsuVf~zway-SXZD&;^e1{4BLq)zAK*U07=$5wQE~Y#KW;>|A ztEOos#SjZ!c8t(+kO*<=!TJtxaAC!YIW|O7BQMU}8-6ESJcXI}q}tl*95(R`G`?GW zElkxWk67}4QVbPsw}WP9Y={%lP)f>H!XMy4kd`$Nv!d|7Jat}TE@Uit#-IzZ%nK8X zY5U7%wW3=IV_A*UCyI%DeBby{Q9;qpz=D(DivyE8xOn@x5+w0FuvQtkC_auNMv8_; z{Mihz`u%>QU=pDH&qLQ`1RxSaQHW4S8VsdCqWP0grLt&Lla|>j3Gs(G>=D_qjta{I zz%fW}o=7TK(2#L8O52!xh76C~KM)6)g{;0NcyzFdVL5pyJ@%MR2Y#O%l)hp~P`Zay zh7vVe`0zxb!NmN};^xTQ6p%sin@B&#dOhlBH;DMXG4Y4kn4B?dg(#Z!>>#aad)QHU zBx;yuM%Nx4c2S~(g%W8j`Sy2(L40J~Emk1d49bT|k!rQHSt;Pi!&jTDu;sEoS(Trn zG1qJrI(XoL#is-ULNJKip*%Z04fqjgOocsCotj>g%>%7)X1;Qy>) zO;=%~T^R1>n1V*8N!Zys$mj_^s7=VRL>XYS2|O%Bu=*6kgjt6YWEHh=M$|tSUoETI zGIN>j|GG~}8Z)nPO88YT618iu-|(0SQrNKVyRt|VlMk#UFe30RSdy<(bfiNFg2W+X zdJ;)OKwAsP3UBfC#PwO5e%enoW_f@3ysvlqgQ6$>ufjvy%U1?Qt3rZo%@nZ)=Q@kL zSh^~ufYedy4#k@id=j$TK+e7FV_<`v?aHqka$<(z-{N0NOx*P0dgRqyEAx8X!upwXTuSI{49}pW<*hCBumK#Yc*tJWAKs;$JU&0VWz2Iiv*Jrc4qptFE%sV z__KXCcD21-Ykv`+8}&|l)(g7oL0UW{H^{%W&I6B(Co4f|1=SG?H}qIu>}VL3D`qy1 zYYJ(>WRZ%Zmr}pDw81qGPVCmgvu;R7hqBFoKDjaKd*q;yt2s$(L@620B+i?mb4^DI zBwkip=*8)lHmcIB8)a{lK(sI9JlL{A0p2vYP?mO0w>Hl?baI7rPg^JNm|EO1ZD{4KuSm~kmP zCN=2m>wh&nEjp}91c!{ZfPjJwK3W;GDJxR4n849NBrkHt<$%F1D-cMg!0D94;44n_ za@@44%+8zD`?UEIl4nT8V^gzqy_${0WsnHd+&QhL6I29-YX6J9w+@bK3%*8Y;*Pr$ z+$FdNy|}w%l1!2zN`#Q$?(Xgm7Yk013m130%n;n&-QMc%y$9;6`l{X^zxP$Wdhh;m zE#%CZbN24ty?U+Hq&VX_<&1{#sf{{^hv#t~P3A!&EzI(yG!p0RJ(fjfN) zw)6cr)S8*C02G&;t$poxX~dZ*#MHFTY%9#s=fZrl6IfxpEBl*bNMmGqw1M_KZ334 z$v6QybtFyh6S0nB-b&S~ELN#pp&G-2^pJfG6p@W{A{76u-DkD)xO+=AvtKvK6lu1mqjLZc6k8c-H(sa4Mf4L}mC)4^U<< zgqu)0g(85z3MvX>(TW|j)EW#@32{=q`pIcc$+aIko$UX$;DLu#xNk=qN+>xiXHp4JUzD2RfHxX3W+q|>ryu%da)b*fdjUr zv?!aFqiVgmze+jxy=l1+8)SurHXNV@%fMxzN7Ln%K%U}+#Kds0ju|jx^HV{JVP}XO zQ$!{LP&PVkIFe!J{CgKmwX%GTuUviQorOouRyb%E^^++GGW!FnXq+1;$FNY4a>#x_ z_6#d7tZE2W5~=RfiaeY~Bt6lzO8l`1!=CEKlS6JjHSA5vQ)31UX}4~97-WTiwyz)Q zevyIUP)8miG*z{rCCBUSS1kNGBUkyKDi?*_i(4Q3I&^>b%;o#fgMZ1ms6`|HHCfpxTizN2YvPlu&sQv8 z%;O>#W2CkSi3-70xD3)J8i^{dDvbIbtD39+zHN@p43BmfnlnA~h32KM$A(*BlwA>~ zWe6mKPeVXP{!&^7s1z9*1w>$r4oB!xIg|`+4KZU8U}39}gUE#HYxJtV_1;=6k^X&b z!K?Ny8BxUN$GlLhBOP7}3?m4EHj0Q8^}AL6sT8#k(1AF?CW&A`R8~~(z`?TA`^NyQkv>8u zgMxVt3?aX|c?Jb#ssO$s%-zW{1fmIaDNv!ZI)e9*5}9%plJC~?*B90_Z1n8zPfO6; zCjaI-w!EUrY9MbRu?xcDf$BryfOdNv(xA!=ADN;|vQwaW7{?q4#1xTVJ7SPO$>ALP zgVwKY__;&rHgz*U-ZJo!^X#EP*9OyO1UyD50Xrb!(E@sa)gev{l^Ah|#=VPvh-W3C zwL)uMd*QN?7Sh$&c)!81iS0X_Y9DfT@8(4%Dt~mXJnC}9wLxsU!bVZ5AkhjbyP!as zr)thMyei?h&F7*>b5$y$e6r}9(XjCk*&a9X?*65W|G`|t&DHWI7aRH_+H!3mB^H9Q zX}1vF^ii@%_A3xjNNacO`JhgbI8Ttr&(g&vrLwOblkHRY62*(Ys@!sEi#PdKBt={s zKwpg{Y;G_6L0~TMgkbhPkEPMimW5;AAqPk?fwxtTCRz zW_AOWxu6_S9*)`?oV}Z;N1icFa^x*oX7J-|4lzO3lIc^R3PoT+wn3p#i6+qWW5*zs zrIXz@GIT6$(%QmAh#>bkn5mn9iJ(7&Q-}RRuhqS~f6J%pBdd7TnH+fI)21`NFM@hr zOQI0U0!}fdVPUDLJEeaBvqoeAaPK2DhW!yZwHGN5!a6*0)#R%ZPkPs%H+FT0q2q1i z+$;CaQmJ2#{(;x}kxzx1%7e%Vi$p#N$lsS1H%=CwS%B1ok4Pjd+!a#qis~Z0<*+x* z5kby3GDH_?bASEkF;jL$ZR=3dH7zIA|3wV@$^XBq&n2G-AA|Q?@A6>R2YY_^oDcv1 ze?7Xoe{i4dUJ~_xnClPMd9IaRF1UobI5{tKuH|HPvY_8T$|K z_E(@4*4YJMYmWCxHEXm;9~H0&RVorZ90G*Ni{k`CIy@QmHE6lQs!^@oP zRnYDQ3@jd^6!dUahM%N?3oJ+9cNi!+XUhSps;J*C9q?P1p1UxPKlM_B%3ZMQx?=bl>Z7+2gDn%?x{~D%c2QoGKJp$)VF-wE^ni1p9;6u zqKrmoaexM>#8%=oqz(jRaOhV$SJ71}mtK4122PFCgiOTIx!U-s6D~b_Tj85s9^U3E;%k$MAPY%s_1qeX|6CU1x?=XMjhm%}!cmQ4Ei5`P z5NI3q4u&N5YE)}M`J69%Z!B>7O8!Pi7I)ttX{|wSc50x4MnV7p=?Osg*pEDxXCc_f z1yE8)`e6!pTIhty8wmhJk$S`Wrdyts!wziqZho^yl}*!sxK~+_VR|}~wK}CA%0d*2 z9+eFeFb%eIzJy$Zt4tD1sw7Cur@44k84-`csp}bZFZ19%>D~qIAKI(rYX3@4k0Tq$rDT%Fef`VAky`bB!iuVTh-`6#R@2r^@ zdoP>+`ARQqRq{}kNFx3ap-G4kNC$DO=wpDZX@vs<&_?DFsjMo17z8F@LLwfgIU#b$ zf3LD$Gd~{DXhN%oBkO=(wsgf6leG#xDgfsQqS$~e`;W#M!^kunv?66jS}i64WLHhe z7H>2ZaM@nUD^?fj>3?MJ@*yGC$`pUp@oP@Pnxjw&o&Rt5 ze7<4eQ*7X%G6|W^Dx!I54NA`sS#hlSo@b`0v&R}NAF^Wb*EMIpB>JR3*)7moiQb90 zeRH%^5lp&T%Kn3sTAEgjf`%0LdJqve?-94PsS)zZ`meXv_Khy~^Wm`GQMqq88n-(S zt7o$M(Gx?H8o>vZF@l_$oTNr$>w-WX8Zt7GCGKW;S0#KCmmIe}g;u zUQFAXbN}4%ja8R?4X{>Z2n$7-KEsfqVZEY)CW{qz%2v)9IQdZHQiCB>`i5ew3nVT^ z100u|Odk~gD5zG$OT|ufDwpft$ATpbhgmC7WQzdf1C0h5o+OA^Fv}TLV@~IwIvbZ7 z1bDrCByLHYl8dKFaAKas*XN^doIc;xDLCJQ=3)0M6zFL!PuY^4Oh!}&oSgK1Lg>Kb ziQAZ}i}ofFfRLxL%Y7npH)MzszNwwXb>q!?QM(@e>T!Sep83!3TbJzhj;eW~m$e*i zlfIa6L*8VZ%p=kxb>!nkm1N3v1+N*&9v&-uuMsl^=P%WA0BU(AtwZ|>4{|lXaDVTl z74?03-+D8yY=eC!Ygu~fC@!h#EMC-_(ZWUtLKQOMG2yGRET7wUNZIkWsne%}mv9_b zzw>{1rO}>wKGpnBO-;I%@FAs}HON|qQdjBpt6>EQsAQ=}okm(^yq}TSK+;Q1E`ECG zqau}*4kIe4s8Aq7Y8de8*`AO0g}p1dyZeRd?Po4}mpR~O_CRZC+9NhJ;&73J`$Pj=F2;KF}T(A3?@Ub;(I!nd4O;3Jc;~B$AvhHf22=|b#?4qUk_;crye_ol@&kL~`W@-&oo;BwxzQI3n61U= zi|8~#c0S$|7v6-4DAT7f%$HMxdBAKyGED&=!5W7U)cuHk>KqFM=#iz=&0`%VY@e3& zc}v*MjE}GSR9&<#$Xb*aHOhOM_XNY6NDQn(ZbPPDs3js$o5hqlJoV!iEakd8KH-4x! zqx_rGV;6)T1juFeC9r{4mcp`P$lEmrIXIm>I?2`tqga_@qp;hRR-)q}5cG&Gjinca zW~Ie(+?yc3<6AHKoS8hZ-mazB(w29N4Yn4fuf(c+?p)|Cq)lP4s=<7+M}kl)wGeN> z-a*rjAD|>vV%kRm>Q>gj4y81V%+2csPNiN63U4fufAEl5#tF!X74q03og%} z6I}dAj4|}Z)_m>SpJ~57{O?ZE|Bp66mtVo>v`?7#ckemgeqLw1!cqTE^Q_=;+@lBP z|4nnR;C8{y;^u_@e>s;^EIPbvo-5&$i>V~U^=>IJM z5Nr*nZzW@Du*oz@2zV*zM%WZo7%7?*wiER^aUx^&<0L?Sg6*c}AH=am1C6;j-1OO$ zObM~Aa=*I&u;iE%Ykga6$#1g4I-7mXBdAD5;=u1oxM{+RQLfnRihvQ*h!L8;LbjKJ z4#Xx<6oldR?SP7_+JASy_M!I2Suc8aYP_}ai{8Dhu*)tFUvpCgf*~Cm+>{Ov*Z1ae-1t%cF3;IGxtyJ~F{qtbzXQ3HQ2xP9%6}M7qd$xK1aBuHc z8*cZV`|}kNC|G6Z!nkJqF*Qh8aUgLL;~^sTpq>eGFov0d!6Dpf+WV*)VO)Vq z5cPi9#Qif0T)Q3AuT;hvTlUuxh}j_;&oY$W zJfmI6rU*&Urh9;);iH8d5o-bqoD3ODw0YgPV{(gzH5MFb(Pd)8h^p;k1_xW=lT95> z6nMZi2{toDAcL|`EXL)uDgBb_TKHX{sD$Vnam7fX= z6V_~;=hduQ?dL5E7FyYMyT_7}75;u1U=5=8&ycP{yNRrftcp$`<~?c3BzM(id$3zA zA`RY|NFH>c-ew@5&$8vY~QP1-Al|yID;HrXfA2QQDjt z1AU{h)?kJvQO1G6b5syemrGd)p?iXDD3&(Tg`_yXWB;{UG2{MzQnk{SItPwKxn3K3 ze@3V^fO<5j>uJTnLl2lT4nJxJ0Dr})rTRpwpwPyAs+A!%16{#505uIR}6{5X7yR(c0~kjkVB1#R|Z? zLAx#r83BqNA(dy#vD3PGOyuxJSszyZx}f6j4UKO9*VPK{aRxt?VFeLhfvzr|1_~ik zbkIl{;?xl{ScNpeF)ZJ~7pFp&6dOqNw|uN)=i5$}D=B z0wsWLNViNC2I0n>d+{NKLy>K2q$B_cK%|{8V!SDuy$~~rqI&*Q?VgRMu4(<%H^<1% zm8#e2cdB3?vlX7$Mg0`_hG*9Pp4=(abnHdkGjhBn5LvL`G`fScID~RPHY`*AR6v8G zg8fUMaBIBw)5X^bl`1ydszzp<>g6i7PgD*>$6{eoyOWD(?q-Ui z64;wOCRPwEQ9e+9=-YAHOBcud-P-@mH?{NY9vxTQUt1&03dIMi8gB4v@D-gLQby6i z@q2nB`??_sq1dTPD@Z)ZiV3hM5%!h0G0;6r@ZhRRxxX20A)QNZ9MYl$ELCuW(~q zp+fN|6I{2LI+hvQCv%_`S}mH*B$=#Uv*>`+Tm~LBkF|t;A`@ivG08qq1JyuS_PUKPf)fp8Wp=}sr?h9BdBi|ht4G) zkYc^5rADE=BpHgKD%<4mmxrU`SO9x?zjbiEek5PPg`v9~Kc z-E%y=X1Q4L@Vr_fR!D1#qo>LC@M+SOMt!mJ<@mxfch%v`}!r#`J7eIbL3#8p)v zo>I9h;h)SuU^~B{=p>?wBay+I*;i(N*Jiz}by~g5So+5FFMUTW-Tpe%3NAgm^So$5 zS3}1V+yY{GQeLUg0Ayl>HxaM`@Q0HYJ$O_XI$93Hb?_|*zkkM7yVb)QTBW8((E%63 z+f=F5!`hnOKLRiUAo4_%S!!A*H|J2M0_xD}J^VZh`4GstG9Q7NS;E~S=gl$GpRO-D zD{hL{w`?vuhP`rKH#XhzP%C)NCH#blfY9i?JdBDVo_j*^?%iFOcQqvte% z4=6Cs26ldZ^u1k{=%gnHN>1zRWZalE`s9^|K@nDvi7UasoSo3^M5umY7HWMW?oh)5 z&xLz6I=N#SgWUlUp_Hm6&g6v~AYAfUqkom(emrSw=QfiUc6!$EezOf1!+Kf4ga(ie znGQmRlT$P z0C^AvLRDQ$|8nSxTs{MjUP$>+{P5w3(XD=cAM98^z}kpXafTxh(EDN~Ay82sgJ6TF z1Akm}M3Au%bV)w;Ok`mLYgLtT){Yf{iN2=L@WeN#oE~^Sb9DxbRKz?BnR?+0nCxXMRr?k7pj| zJ$87^_ZZ?~_Gso&&M}8a7Wc32x7|j$|LeZaeTsWu_ipZW-3z;Wy1j9`>bB2qsau3w z8@I}Cx!sJe4_!~YZgHLE+TYdW+Q_w}YkHRtF4tWSx~y~==MwGG-ldvLJ}?rVIG=Ui z<~+xFpmU&eW9L%N8J#{m-EcbWwAN{oQ@m4Gr@Br>oID+0J6>_z>$t>mq+@T#R{vY1 z;{X4_|F@q2d1m-+-@UQYzvDM#u6z4Oz}puu=3PIu-q@l9|7cT-^*g_gZPabw+oorZ zKM#LddAf(O?lk_<&5LI`9R75qLypigl}>;D-gLQngRy8e`O)9!I(RNyxpUaw+5;wD z$kI93(bt$Gg@1J8$;r^Xm*O_w$eoh1yV99S<6B=d>|t*!zZ-W`4^=8}bE(?-qIr}1 z<`s2*Ei%^HL{=bvHx^b|a`ne6=M(#bpB&E@bhbvCf3=2uy|nv_4gRA`JbE8-Dt_Xw zQPxT?O@@=<@}upgvp0NrVAY_%6V|5{|GTpD%D%=zC*?-Y6d{Gz@kHm&-{n4cYs{QetTr_kS>Do*JA;L+#yb1s&BX1S7K zGGFfMsl9*oy*MIZKt;!_PDQ+16q#))xScO|b#-g+?+q(1?Q!P%)?r}<{TDpiW%$up zerbHOZ*Ey$=J-6L%hbXt(*VccXQLMe=7VOaky5C2_rnqv$K@n z4S0b}Ns1{&Y9QfRDwxH2{1pn5> zriGR*d%Cga{r7pAmr2X<=+@sE4I2mZk1kAqd}p}1R>0L&-}3cr+P_`$w04Gv^W{gK zZ$>$UTRD>1@gD zUvqmEPhY*)v5{}i)#@>EQwwA58vLU(bN_YCyQhA~kcQ{o&vz@jbkLPghRgDtPPemu z_uo18pSwN#rR%*npl^nADTeKA&gFM9p?7D)>IV0=&+SrsTcb?z#Rg?GI_-SllkLYs)(}h$z(Zg7YJs9xS{&E2Q(Gb}iQL{cv=xVFsIk`W=07 z_s+a-X-TWDC$I1=H*m?-CHD;0aeNp5)_5{C?(N#i$2;!%)IX)mvu1vGD_i+4j?^tY z`Et(jM|z*R6?~w5N`2Gh{|uAL^N;>nzdS9q-QZzsb3H$LC$&d~Uh{7l^R?n19eU-_ z;eKLF!_rGOAH3q|yS8h}AY-Mh{85J%-G4W0+lr5g$%QiI@7TNN&F~r#t@)P@{bFz>ih7l<%>3__zqMNY%Hfg0`VZe<>Vv^vVc|Or zC#ww{SISzZNW_E{E^JxompZA$?4L`94*YjjlL>Ryw z<=23|6PxsW)-hk`2mZ;vehZuB&fM@`-KqJnSIV|4vFHB3J9XfD*yn$?tTp-Gqt+9q zguiPYR%colA7kO7{G+|I8SnZ8i(q!Po9TB`<{+u*t1pUzM1*=Wa>gZX3r5!*J_2GEs(=IXN~_>@A|tJyCeIp z>6+8+>0r;}Q$~DyHg?A6Wery~GOTaTzqEP^ik+D!#-=u#zS@~cZdIuysTzSUz~qwrD=TrEM70OdY}5_GxzO=Cx-`5^DV+ZSrNZ- z+^~8zn{KJ|v*v^sMS@e^(z_q!%PpU|`)AuQzjwy*w?@1xRq4@(GA9hT*)q*e-`z zRjF{f*76(QoO5T&I)JTG{T2?YT&A_p`?9V3e@|^wWcka+^*jx;kMiXf=Fe37TZ1O0 z_BJY2{dKizUV-6FTgnPs@NfPc56TzGGqT=~u+XqQ|EwR_$yiqQ&HS$g>&}~2v|!?t z(q0bRM}O*2!qM*%S8qd&ir1GEo0el{ykSx}f2+AYwny*!HFt~C`>ai0 zxA~BA-u=^qx&iOs&$z>~pn%Ve38qrPA|$VXUa#S{Ghy5Rpu zIk`BlajeVwe^28YV|~MIL(>0^(lOWu`7D?KF-%X8p?5MnNJ}*Y8D`9%c$R>1g%HOM z8}ZSY10NJEjQfNrRzj`tKk?skj}N==Px^-uzY&lnT}BHj)_^pFY?a6kOz5fzd>vv2e|Y)29V@+x248zM&{W4 zB-VMc&286!$Cpc2%YS-WfDPJN5;TLGBg`_PYXaUwZ%aYKqFhDOCz5Cjia|@%5q1R; zW;`a8!15#MPL=Qi9`$;5I~0-i+n6yW!}ok_5Nd;LwxpjDjG`eIh-*;4kve@E239sc za1+7T(v79KgeV|)gtdaOB#~C$w3w#0KTZ(RPr-~es|{Uj<+_LZP3t?rddX^k!>LGcA}h#TMW~vg*U02nV@fwe459Y z0Kp^82>JLBTqt)qrr+~zTUPkEyDxv3XHl2NbxM6}SFxMTgZkz~nc_*YYsO~0?DsA6%niXdC}>IC9E~N5rO5g+2)tNuPhhDAgPD$oc42R7 zG=nmU1&?=)b&2IkZCy}%!D=AxVp_TCwK9GA?9@Ht)xHhynz=UYknf*fHWzAY5F1Cx zaGFy9mIm0l0tQFISOi23jw$T>rk^bka1c5r91&T2`HanXc851kmn$gZSfyeaUe_hBO6l(fbDu(Eja9!VX!?}Vj+6USk=|j-r5znXSD1;Z9q)I%} zQ0PCgm`RxlR2~LB2tl|Vw78iftX-S1*6@dwyXBUlTZ;cEQ1k4(q`}S4dX_%>)4VCI zw+#Z?^58d1TL(P}Pzo@dlH4#LVZ{-k*`CbX0B;(E1D?$a4x`L<*8X$$xj`J3E_B#7 zC~DyIH{Cz%9J?d#cJ0&4M`jJM896c&$(yErfpA9wlSXhWS(S`((!wp$KH{0m_rl-t zsD+v$)_u#W=2*Stn{|Bs@=MyC`EkEu3Aa4EO*R8JoFs5br~nl^4v!5N!=~4P7iSI6 zDMa)|7*?Z>NIkDa)_7ag7d?>rIB%&@hr8@4vHJ7A1Z()D{MR5lhLkoBNB~%odQcfH zIOWcSVO_Xs?vUVi(Te6jDEe`nEN1ZJL$pG&s6_@4y>7 zepCpwLPuKwgl6o1H7H#8oHDEpGW1Y$82T`BvY8`e&{qZ!p%2exV%x7~ zeQR{=bJ99}@~#=qje77jZ-5o*Syndd5TV+A*b)f+iXNxz`*^B!JUm`FH(=rm z+eg9n4)3Km3|)4c{#`jTv`GAoUY`ba`Et2&UElGZ!B&W73;Tsq+*Vu;HlR}eT&mS5 z`2=iUxtmJ6TkFpiX@Z5?`0CV{bnP4T_nK0Uf;d{T{QHvLM)rh4^|g} zV4{H;FWiD4YA@k66TbuPE!Gv#&Q=?T#f{qaO&H@$v>scgO4Dx1y_SE?QK@6i0kx(* z&sB9wuoXgC^h=3Ag7Cm9YI2Ja(T%nN78?Vlh*9|(NCjY|D~W-Md{~T_saeHy)0d13 z5;6`rpE%=1TC)jr9$LbzP|AV^KvACprIK2~UY8Udfe9>N#EKQpHXmbP&9avW%Y3K- z%7hS2L=wloyFrE{(IJkH3kLSzQ}l6_unFhRj_+ZGW)|biiQ+5Ph>{>NDGyF?CMbT8 z@!|LYWle>3?Vl=~9^$Z}p$lUDd_$3X(-SLE4~@ovQQ6eA~*kOhcoFT zOdL^ox;kAK+8&+=3;$0r*_1sLwkMqPC{iY1Qe;y0&z@o3O5b;PTu^BF?1nBq z*K|)C8D#BCnUUi5BIe+Tvr9i{e-Lc}`6Ok-u%pFFz$ukT(iNf|U~drrQoH#w|E)mh zF&7M#zP-J+rEA5wd@*mo=58@6*qT7^ojKTQC1GQ*K2cMekUHQ*le`+|J_uUOD^%1N za9?=O<7kCPL^PNb2lm$~aAEsN)4U5;YQ@~Y9GGrj>L80Xp8Crm2a!y{o=AiD0DeRe z({u3YSV!Pea0Y~9M_VIljuTv#>1wcKD zaq0A-i(^`!ZeOiS{&U5qgji$gJXS-c^-N0P#ghXmi|x1_u1Bxh+w@Va64m4h?I4M; znUsIc+36umGaEM+&vbJ5RNsRiUv#__VC_SZ%#6Wxp<(&i8A|z#>eOD=HZqP_Sea1< zjy^Q;gT+7^-x88!NZfIfhD3+<%5?I0ry18BmW4JhHY9S^>ZglxS^}*x6p@h*;Sq^} zEHhNJ$pwowAP!4LH(fF({a#c{p6OMrYZEqA^?I*g{8%5puXs>$hgQy+aymHN?_;q> z(=rg}PaHo&z96BmHp~-grWnw`^#Tl$Xv$f*!jiQxpL8-;GO4)Cs7f^Hq5KdUZ9ZqE)vz_sIl*xdlgD4YEI&QYs zRz7kj$Fu(&Jve!B!$PN@w02Lu!x8>nCBp;Ls07l7Y<+ zPEN2%Gv(R10Zd|SSkk^h_>UzGRubNurZ>@xI?yEBluWn6GS(jA5paA+_nHk#R!C3v ze-Xnj;{TWRIpNdG`@Q#g@51o^Ywmdu&i}bQ_Ik8(zwh4PJ(Jr;H-FbO*8#3sUADV4 zb-wL9$T@@4I;TdC_Zp%e_(BrK%riFZQLZ^$97C_2 zIH25&Zi_6AudcOU^v{%4v%Tik9MmJg20?5dh?JqCp$v`$gevQRCK$^F`3hM};oK7% zC(q{$BaIRt#fV|j<^}dWO$r?>f^HoZuT@b8M1z;bH;v?gKdz*LVAUIktWUK zngD0;GLSjIbA_`N(ywXvWe$`T#>8)u33ra@?IUE ztMBn2XIqEZpom2!L`6RzIuuO!`eaok5gr8zlVM>wMAZmvnD~t_n_C=@gYX-m=M#B8 z2E4VHy*E7jxn{xvb0M`)L; z25oG42$k7A7x)7_=1`duw^Pli*H?%psvNQ!@cFRDg{cN{H>s^8D}+`B?+S7W&`%>w zJ8#`ca9)2p`OL2xAKqSHH)PxWhNpYkO3|A^1^}P!{BU=`(G$(Nq=oj65<&s?1iuTH zLcNbn0H+N0CaKlLrj`ey(=74Qy*lS_ecr8a#;SXpj#)f>?WEIHUWVErjm1)?$3T}C z+Nv`3)YXsxV8rFb%f~`gAfI9}qkbY6RJ#o~DAG%&nJSmNlH=LfG5!sHjWA?RUuWI! zFk1;a6YZHh8L{I*&=dq*k0OZDC)lF`qaO+s)skTBPHZALT)`QD5ARoR!`-`IK3L*t z+@>eJdXGpqEHNOkr;U}d?0LY3+0n=ku$Yi$vr#M}mIVt}^T#>QRmvw(4#DpT+eZpR zxA1J2Hij(QmlT|AT-Wi`;j>OHF9g}3jP(UI8V}vBeB?nLc=^E=+N(fQsc?rC0+i%P z?@c7j!g2t5jSWj8e^nax#rT9!bA!{KvrFf#`h2n1id^k(b$;4mr(>obHt1rr!M}`i zX{siX(BY;4RwSWLVZoKE$^=JZ36~{`OFm`Kn8+;Pgs52^<{v59%=yX5??tk>lx>jS zyWz?eLAE0FErw@(2DA(tI;fVN;!BV$BSs(}%FGb$;u1#u(zgOogjN{PZUIuPY0 zi(^d5&pI|Pc_Oi5-}iU287FpH-10!KFdGE1rTviNKpZB&A(^C_F0Lt%IHa(^D3@nh z4nS`b`>TI2@4vSh<3wmvlV4wP=O!=5G@CYU+w2{2u9y2Cd>lHibdU|2SYHU1i3^VV z#or+n0%4yle()t3LuZ#$B@H7v9*%l(jI|#I0*0_-+}kPBYUaNGrSJEMVl{mBU(40} z{j$Pln=hpn1V#m$u`RVFuAbPU^j3Inj1FRFDE$r@Rspt_oKsiN0?kkJ&2g>nvc99* z`&3*r^INi0x1Qzeh1ej6g+HxwSr98CG2!4T29yG`;wp-A2uQAD@wT+NF)A5CVaQY6 z{_*6`+n~d}yVGy{9J_92>Yj;@#-=#jc^e&OgCG{DD|@&p2L#9qu+Fr|3#*wR9F0&T zG_%i}_BLe~0EB;vJ91pmj$YoR&%+5xWhZTTxFIkmHpO?*XIoQ?EkEV@Sj4=h;Zg|K zIEb7O+oMrrkO>du9)$Oyd4uJm+c=8xE#)g!&Ny--qkz!k4FKpeC)a=fHr0G%#=U=Y z9^2pCknvr^46}RKppebxr*}CgD-zz2R|B{b1y&r`_^`SM6I_(E0-~Utp~9Vb>-Z}1 zN#mQnsWEHpr&{m4ZEovo9b8sr=eAM-w!D-$k(U{61Em2S!6ulN@a%TN!iCk1lnP%D z4}|>+u=`CB=qC{fpCoR1pan-onMN*HfAZ0g)`mit9mD$sHm#SGt=Y>!TOK<8m3EYc zFfg=%VoPlcv0~B1GP}tMQCh%n%>A%4&qX29W=j6+!9Vhi$h3P&+tB*%&D{r2x)Nf` zP3sKN7H}197xlJzEI2AXM0aBFtI-IYT`3<$jgpEX5RS3m%7%AAvyvYkDL6F2??JA= zR(6b7oEYm6ZOi2}w1%JRd1)a6PDuPB9^P+Z7I!VQd8YC~km?WhIf4RQs<48{1(40= zk=er5r1^$-S^f)}>UpC0qne+ZyH*KsT^}86%Skx_$j@|0V_pa8#l;m-6^XwESqzvm z_Q=aoAW|GlSdvmjAkmG-IH-%JO5G>CyO-^7*v{LTZj9;E<6-9@TMmjl+WY_s3&Os> zJFX^0UJ27@LP?o<9tJ5h>;PDt%?Uf2D+q1C2gvqiXYg5cZDi+}@yySKZ5r3i zu;|K{w441BMmQM@EDN(`qj;d443Vu`km2W-=nIc{a%E8KFD4p2I4pmeM4=j6NV$+1 z<4EBu2vGswGamDjelB*I->+Nv)<<{OeOuA)zybfQE(bzwStbYAb@hK#ui2EKGC{U1^p0Tq z&VoTyU-Bwny6kRwgKU}UQ$z_Vi$|=#1V2!nBOsY1REP+qase_BGRTRtMB=}=_ou8NajK#) zxO=yafF{#9gh1^<-w)ujGkLH_?$ea@o) z|Ji${cPXzUUMA14o^#;;cf!LA{r_C_|IfOGLI1x1`u~G2U7bHUPj@ctbik>-<1;Yn z^EzyCXllG^Ofq^IR{T$w|7IIButlLvhV&Riz(~<5?=3opYYU_mf%z>kQ{3Yq7 z;#W3m5om)dmb}BrC5#X+Bt}X@MBWiPE(e-ms>YDNlKUZ-L@KSu2dGJTerz78_SZt8Q0)b5ce*(XyO~_%tKmnfy+fb6h;aF^U^tsIFCr&+( zGqAgnumP&4U5+ROz&SGP-pRW?hOatyd-&>O%?3Iz-+%Y^gkT$#v4vs4%x3XAy6f*D zFN}4UT*o4o;QXar8PYBuf1>MH)-#JRdQrwP18aDMW$bpOOZKhVlOJ|$AJM(H4XW7k z&@2;<0T3ToQNSKbNc@LHVwE-k+Yod??QmuJ43La!9kp8lj8MBQGA>io=FVGtZEjh2 z|Ktv{D~-xn=~A$*GkquS{;Qpg@`N6ReOc(BYz`&4Aq5r67NoHh#xCYm$^=sX7x#%w z=INESRrV+6_*g7;-tw>hzxwa{uX2#B6GavB&!H3pu{AcHM;o9A7J*2nqOm}^z_6&a z;3;F`1*9B|DdRGu|3G^Gd#;P0E$P?V+o4aVa-U;kT2x&0!4P8WNau@mBgHQ{g1`nW zC^8t<DRnb^rCaLCeW4Id;u(1)QPPkRad0?|w{(UMO+8G(U zB00nLQa4_{EVVPp2C*zk94WihHxYvbA;YM5%(nazsgaBzIYg03*$`A(_&X!b>X}!0 z|8?>%&re0GHt~6vVa4L;Njq=l2)98gTM^o2e=#ns%P3{vOzUVK4X;IOM6F8^bvU+ zH;rXZlDKdvzXQ-aP}OQn0xlL z-8`Tm)LocbBcZ*{bcW=tUHsR?5H1>X0f&80*7lMao7d(vke*UiDv|+7$=RTPm?S6RwA((~H80Zc}C{lLG%AFG) zop^ObBNK*-;$Q*DV>dmVhKQ|9Hqp_a*>%-7UGU7Z-r;IeKFJeD+78%+seMJXx4 zccHGdkPjs~vVfdV#7R|6+dQXif1G1mpS@Xry1TZ|KV?>+4Z2w}X2uX4>hPf72JjID z6g7NkfQI<%qNXMS5h0K`sbrvva)S+thP8scNb-l6wm)=d#OisyKHhdodb#UWjS1V1 zMfb8nFbj1)qY!+Yr~#uY7AT$+VjXp}!XT>00tqBYgo%nW+bAA~2e3nwV#A;RoBVv| z088Vv1~9Rcn@NN9tl zn;8*JHMF>D(^|jPeR0<(KCe%?I`!YPrAO!Z%Fcs}m~4$`fdNfM^@&yrexHVzDpM1T z6vpd@1s^js^{hvkrlWikctR#1PYlullg)f8-GqrrKb+Q9a=kd@`>>=-6Po2Z8)}1Q zwj_y}>3G)lJy4D~O3!4;!Dk5o=kK`<$ZZnniD}bU`*zl=hIueZLQYKUUubF=9|N=`1i8ar`OLgu%MM}(Wb{pa;rd1b0SO^@mN~U2x?@0 z2W$t}Gj2~{xrlSM8<)%SCQJO~>Oa<)-xY~oV>}RS^QYVfI%X_SBu02jg7?F-6i5RW zRXlQ1#!gkZ(le4|u}CbyilgR!s3jDh5tHr~E%K^=kviZ1+E_K`&~t-6l;4>?z*dh= z8lHy=b1%kW7|hlVwF)NXPAsTBNIat9)CAyFTBmH(3lldS1+)uZ4l4gTd!769FItCm zYE)~EhtJZsDP~(;ij%~+Voz)Fs#YFW_CyU)gxhO4>$pz9Cuy&vNa)(}tng5|sX$f} zwzlt%T)UlA>D0&9)5?s?cQxKK{pAo_9WK!Og`v5DUCv<&jRfo;Hs2DCl$z>B2?oKF zoLgv&O8tx}3Hbcza8uHRn=>y5e`~*_<+ZT9ci(RwS()3@$~^d~C0DO9{bG0Anwov?_`GGihS_Q{ zCP;(iWZDrMH`y~6J%54ROn&Dz34vT)qvYX{*b2~B-544PwOHf)ufM-0!_*cjbch;jMg3XCXqIyEC{4(8K^CQJCgD1w1mtEHJKA9c5M9Y zd5!NSKYDi}X0zL}FIz&Q8U=VQ47JsuAb@vHl@U!ApqH)gPh1TY%&jtOJ)cvi9HIXS zYAd{O!Oulp(6HZ=I(hrgbrt;dMdu=1 z@ja28-~+(LP~9S+iiENeC{j z4_hFo43mp58cs2UG~Qa+xkQ#hR~P6czBou)G@J_w0pn^NPZS%}Z{w53N8{=*u3WT! z{lUx6T_F8`Im0N2bn9I&8?L*KajoU@%w;b6``?^5I0rbpIqrAbwMb#IC*dI?&85Pk^}ubh6T^AY7t_BeF_21B2YH~Y zXtLrYBz`B7DPoQpn=M2UQXCh>(XP)F#!H#op$SY_$@IizjwT`wakP|66G#;Raa8D2 zgCC85T}HZ@N8c*>nTA^uExJI{&tK@i;wL8szN**7VihLpSlHw|Xt0h)Wl_{YoascSR#5a2pUF~lSfPNI>@W&$yQX{2&wDK8^*yAwh?_#$o0q3@A$Xk~XML}9AA z)i^96(u(zwx}I2~V>4~m50SS8$HVSP1o1p32ZB=-9#e@|OreUKjAD39 z%?VArV%HrMxiB=M*T*=ZvB(<~ioSAxiqML#2IXp4_2hfVGby4>Y~)HdRC1K4Lr5qZ zzzgXnloO?~K+ZqbcL?VZD(z&WOd__hFAGi+7*GvSj}}PR_8&4FF7O^(1-t01sEzEexFDWDAZA1S=np zSHm*YxD`B~m?l``c8pLGJEPALte|Z2^|=+GPC5>9G7w7)gXLtvuTyy)XePfx#*oo! zKr;;N*9dcoC8#`e$vyVQd(gJ_YJ59W|2RIN1O$w&u_(S66U|WBQr;kpd&7dQxLh8& zHD)3w1JJ}_L5Gdf+{k8-Y!7K7W|!+8jvn72Lvn}^lfCKhy92htN2ros@RZ9qT!4x? zL^Ii41e{=kAq~I7J+ZqNo+he5Vyzp-bw`9KAYWEN`ikp=gqA8zV%lr3cg3gHa1mcp z4JM2usFSP(4d0{wxFRhwK?qn7hK0oh3{}{&C_vq?9^c6F5%mzTn&B5~c$yu>W)^vZ zWbwtiF437#$BPDXa>b;kPcno--kLbA0ws)Vv`&G`{(zIoInq=Bg3n*W(U1EL8_frlahKp>(W3_lSK5fgbJWhBrALK1alprfK-L!^gdNt_|& z)*t7JLeqH&OCn9RC0T#?5-XsdB^&n(qmMx~z@%iq$%bTH-l0GwO_=_& z(hQTSVEkF=CFL#f=k2jQ7V;k)>%<60oJ5tt+-s&m$dGK3fZ&p0yo0!aKNc@xc12{RI-LDc0!F5Em`3 zzB5eekU7}NNVqu~wS|TN(Q%mm1Z;qkBLofn`BlStc!N*%LA;ijB%mPER=F-VORVrr?1$a9ou{*rCO4Mku5b8k?1lNX;da z(pz;+WF!fNV|7j7JP2wLc%-Du5%)&Jt5<(M17t5a>J-@ye-tit(x=khR@KkoEDJ{v zver0VSjz0bq&)LF+`R72vgd3S?b;XM8g?dO~(Lp?#F&#Swjkm%-0<*7_FQI80Y z#mz934uIgHTA?p2zWaCxvUiLH@99WI_#E;N=D!PiDdJhYMFOtvJS@eA;#b4G0wtM7 zl7uXy{~YwaveBK=^^g~+i=Ya{J?IU{uKkkIi0c7zb3&sz;pX56Q-*NEi*;CzB`J*3 zDXb`O3}@J3O%t{t8U|tXM?f3E;=+3X(t)G|h_qTu@-idQy`^!s7Jq##5m5qo0k%4; zG+5PuPoarG!sC_J0OAh*hfsNyU_pb=YUG09dV5ur(xQaNFV+L#cDf8y6u4P_P^=7L zIKDT?>p0@kd{c(EGOb%AiXET-v3Qv4Elp_d3IkSy=7f?L3yOY97iqi`_k-zjIt@{F zyYw;*$A9YOjeBD`C6npYTH%3h3;^IvVwo|n$(WgL|H|Hn88#LxPMnAc!;Uq`x%0_O zmWo?3qV(Wr;5SGe*xrEOjBw*c#fcY5G*MyvAj3d_Hmqo?vt;0gc$EZ86Xv3lAY#%Y zS+NsOnCvXk79WCtA*=)EzIJEjW{ojx@b9I~j`hePV7QKwoUv%)G^v6V4-{<>tQ?tt zs9rUXIL7`$I>&8^2DARS9?u(6UyJpYYS@DESWpB^YK|$kxjtACIl#!kVmDQ&`}=w* zgD4$F;b+GC#D8HYvLpdK0)$qoXMx&NoYQYbA*!U5K$1qZ!F*2@bcHpjNppBxh)h&l zsy-w$^w5k8$1?^K9qSaqhR`ixD(F_fL>I%<@4w#*PybUiSJsRxak}nfs}ez@|8iLx zCIe9|RlTSikWxmPjZQwl_n$idCG7D&M;IaIj5cuKu&%IHPKD_GaowbPwx}_K!VGVA z=?s?ATiSWjEkrO-%}Q-^!eh*&FU?FH3TIeA3em%=E__!n6 zlzpRyT*zCCZ2ddtqE1!eKv!ZcWrg}2i9c*PFhi5ppE|4gl*WBfN-3gx!>vH{$TLXH zrLq^}xHwazdW?0fg32kvHV?hUx1~UYO|2&p&}Vf03Z7S42@(q2Q!N2qK&qXg&L_K; zSw$4Os1CD1(Kynm6^Q}hhBIF1a#T@w7=q_VIy$Pp^R?m zlwr>fyrCR&%ZhTsEhLm*1f#>D2ye$;q@V>zd?71i=ctk>w;XtO@vihA}Y&a!WjO z<-ovylFKy(MJlWm-x^hrBtMwGZ9>l+{@uQx|GXB#a}>Y{Jz-2$EjFrx@cX2o6gwPB zK(MuN+(O8WRmz<849j#}Lwd&ZS(z)Y=FBTzF!)>1SgOb(rLvbLE zG3rWKZ&=QZEKzhy(W}FvB^;8!CsL5OaKobhI@RE7QZ`0KA0q|$1sQHiC7NOrDSsk$ zNhQ-|7YXI%WK2Kt&nrTXO>=tns9QZK_6!t-k(Ky@CFGC<8&``Pxnoy-U!^o-SX~5(x%J8$wA9zA3AksGxyWYdt&7Bn-Q3(KUlFsQ8 z1(6KV8kfL2iKYle1Qdj6+DgoQW=azj@8@3B zc*(h~+kbAO+$y-XznY$90knM6RTOk4nM!ao`x-T~MC zpZ$w5ce+!;)F>8;)}P;;FbhSZt8qruYT*{DM=0E@k$h?_rLpQ0E>Gxc9PSl8PFdCH zd{wd96q3U>S)|-&LOGp0g5px+(&%@cgAiR1EeWJhNQ_9zsl>cE%F%e>>Xl#yIeod# z!|+`szFMWn(4Hv~4p7E&XPh?tKkyVeTj!^!1OO_B;cbHejHy@fXdnz&X({X^ba6>U zGm0(qczh8%2at?-RLl};)#Jz_ zp1R3ca3tqJbTL4%c!Gcha7ZY912x_dC-69FvOGDZApxU0*%ZvsIHwL2|MAj;63j_` z>~Dn-)(9+9D!cLhP{-m>M6r*Dk2o4;=L!oD5SxGkkY$vbmoFHM;o0!tw}(UvM*+BR z)XCsxF3Y&(wxln`^R~GGY(i3u&x`=kibwb0hzkzQWna@`FrWxHI$e}`yp&x6>5<8) zRHw<-6F`F%xXe^ESW9!|!0#4XUOZK5{)-p@=@qNZWXhZ(L$VSPie%(8>fJ`b)>3sV zksq4Tu8VK~FJg$Z0Yy$8M}8?LoFY^v1QSVzN`{y)Hina$O3jdhtpq9tiV|RFQoMy} zibZ5)(dKw#vpV+ery3m6f6~x5q{2MyQ02=EBoU-b?->`OBnx{KuMSU{200oVm$yGj z&aQEu;RWjP)2hLbt0AjZ-7oS!Vu39iEFn)pkWh1}jrE1fLtmGb5(K@YNF=VNHjdO? zDimE=~s;hrF>x+>Xa%N3q#YNWug)G zW;DuE8E352*NU=(a|w0)j;5SI;pO0hv^Ny6q?uc~%OZBIy1^hUdt8_6Fy+dk02%2C zl)uYV3UyuTt_ak~tsNQp8Z00Mt$m#p3a%#HZ&WxGV|a+EVflN`$K-N!bTBBuDtRn5 zLLLQ0GzC!_inKHd4I7*rLM&(a4cH`QFcttL-j)A2x>4&(SS$>XeDs z>WbWu7tMn5j~(0uZ>E4s2Q=r8Th^LCAk;n(LRC*r<(-_!BLpye6x=tYcgz6JH$g`U zycsotOXU)5453kAx_vPNVEhWrN9y`92~g@uVMrLJF49X!1e_=}nc*UmeS}lqv~l1N zDp#ln2SAF>fLlpK?`Bnb<-?RZ@zw#qP=az)^Y}ZB5BiCSeeZ zCeAP~9hZr6NuZsS6;q^DlmRL`3GEZ6LaZ4;1=a!+4fHj2#|0seP!X_<3M0^A@wOSS?+2EY}K_6I>ABMJxBtL}Bi;Tuuwfjgmw(YsZ4NvSrI*I996I1_TPj5*YKuky`|h zrP?Lsh#O{yD+;1l^t1?*sL~ zA4AuT7)EGuLpzLJX^A0zBiHH5n}V;3P)S*`7I$HhB3F!0YP}PRpiKNy1iE2-Miq*r z4Ao2~v0UWi9}keD4^^C~QK^4`QPzB8hbQDC?uO>vE8Dg?p=C9XozRWshjd0Pw& zq_XCciJ7@rwumN%&2jc8C_pXhK+x7-LP$9Iks97{MHap+=v zXPjazZ8(g}(O>54Fs?4@7ibdOQsq}84pyQgp_Y@bfw(k7u|%i{086}ceXfE+1b{<4 zUyBA$Y*{Ab&()9WUoIPazWS#@$2PnRy$;`MSbw5fN2oqKr?G(&FE%B}LR$x>I@sa( z2;uVL@WA?4*@5cXi0F(b4ClJ)Jw2pP^N6|E&sD6LtyM(D`%f+fTzACUhXolj1)SDY zQy~Rm^o4VBY3vKmq6kf*u#F~#jfc$;pUX(voAFrNfU3tUh<5 zm49Tj$*HvldX)MS|7JnZbr@Fr`t>yd?8Bob;x^bkgwIpBpxwk72&5LoM*uUD;vk+a z?gXqSLdj720dA+qE%0pC0@=C^9W*wrSn+Mo57yeT@LosX&0*IKr24UgXu^pXC>kIl zT@v{Or8D2URY% zjXJNy-9VL#02^^Uh`y3qO_&(H*x|`vL;s%Z-+Xym@7gJz{+VyJDQ338zPc!E$6~3I zgIpM0~=)WwyYYbQkKoL2is z>hKlT2wQ&|FN`ilG_WPG%4rjztwm`ZSiYRC#N$mNowDP}{iv-Fr(&Ao{^u9#wo0tk3M$$&i)Utdf5Ii_TDl&s%&c;-4*xXF2N-@0fM``J4q_;Axdc6EzmfP zyGx^uJ9OjTK&sHj8ya`%`^-7ls*`iSamPFEH^%qpj{BbR_Bl;ayY}8|t*OuR%ruhK zGD8Ff7-F1u)>9PmFgDVx-yq~=Sen7`*3{VnUx-0Kd;n?WL=v{mq3TP{?1~S_8FQg* zobPAr#aEqoOzEGQO4sKIiO#7ktJ3#TP+!S!1;xS;empqsK^~*P%BLdbdbDcelkNMJ zjcr`F)wG|MynA{*JkP_(%oJwm%iwyXWca3TY1yhl)VT^l2_6QH=h&;bPD-$_MAYh3 zs1=RVig;b#_|==+H80g#F%b0%EaIdbmDsju$O`R8~*W+E9i z)tq8mk8pxf9MsmsCWcl64v7T#cqvjaW-W+hfKJF&gXM(XK}qPQ(%tu-+4#29mr>tZ zR@r^Kz{k~Yxk57slC@E@%e7)WOJaz-#Cd0=nTCc6q6gO{FbT8k*pf?@EG^H&P74a3 zs-5)eRP{Brk7uvl*1w#SPjF@eIe}nA`q)4|VO-#h@s@zWE1FlC@XBjf$Kx+^wehHYIV3ZlO|*h_sumNV=0wa$N`QGodjeSm zLQ|~VuWBDMOIs?}Dkn+%rjF6Nq`7Y!E&bB#;EJYhF*f=atvFC=<;K<80Zg(^2y z&S4Eu8Xi&P?6yI-eVdLi^u1%_=KDO0xsIqDm>EN%4NAFa$SR>K#yTlUaFnc4IoVF8 zESMvE_$UfVxOaykB*;hcd*HDV9$?g!`GJKCF3#TRpL#j^?aTh;pDSrWQJK*s4y3X- zC|STf+E7SRbK+bE4Sv$%K`O=BQUp5t=qOSf8;2K4&+vAtM+vMS`bn`9ii~gBqEp1R zqRs!v@NN>a=h4Td$-$XXwBt1Fg%N~GO9&242B*NB6NP|lUO^!Q7*-@kRu#nW!cGE@ z3YUs}Hlq5Luo4gbQfGJk9+W@Owc(6^R^_`Mni)yw2j_uo7ENkkf2jy7D$nN=!US%O z)WpMLS+J0>J5n=L!m`v}8J?^5jaeCW%f8ziyQ|CUHeZV8Y5CTi8G#}kbjJAy6gC0N zN%a7px5K3-JS?rB3UR4jp#~t(TSj|R^;VGXhUWgs@cZTB_*nOtki9`C2YTfw9ke|> zGo1D{GCI6qoV-wO<#TqJj@P8kH1Z~DXebz`w3hLf$RNo0DqFjg&1-Vi$5pe9DCYFi z-Te6Qgm!symQB3L`983>b=_e zk9*wb+{=W_9KTe{IeGf{AkUo9nV}Q`0Q5%U0czlvDq*RDiEc(pvCfvz+e|sqWjrTd zHp7Wkn<*%+MbEDFBZ^G!(CM!w1w#);W=kLb&q;G;2(6FqALDIPwFkWyxHqah;uuoG zhd_?X7%YXQQuSKR9$-g(BzIxQA~G%nYWt7L`IN z8zL4eT1rHwT5&eYl!T33Sc8MaLonBz8lp%KDE1=VhT?_N-?c8&ztd(LX|m?ccvI;O3|0un&{kN$M6R4-M>J`U+$H?x8&Z3 zS(WFH?h^Xo?evJuAhKwPNuVUa)B}W?07Vht27tL>-Bs3a9cK{X3tFK-h|m!eI<#mk zswzVgjB3xs`KfcWT{@6j>F&%gZSQUqUPV-|Lz4qMgG^ zEvVco^afM3ze6_rL-q;wh3rn) zC2}p`{{#QvA{zDzF`d|+i=>@2+c4Y(FB>{J9Ct8~M>e2v{RvEl7B%HtF#yS#TF>Hk zu)3OtwJ10qNi$1X74DD5m8dA2A)rY0P|y(pGHJYWUBJ|QT-N*JR11HLkZM8(f$fAJ zRWv?I=!mm`uoQ&^!E@@7WTdZSYCMQxEOasm0IbBL75f2Kq1_Bf7`3rk_le70`XJfR zKq@E@;2H8M>`GJP5?tR@SLXSGoDbtXvwVYM3!y^FI41lS=GHOdi>3B)6?zUK{)jIy zl|RHi3Ux`PPZW|41ON`ZRN6@%Tx%VR9=ECs<6aT#p_HS*B;qQgaXw8>Il0rNFDvDG zF1jcmsiPxqvs33Cpl3Lf5U?N!%$2}hX1oLP`ijBo*PL5>`kH{Laxz82MoK_DC64C= zNg+W%&%iG8Q?qU*ntIp~!Ynpc_vG55?Q)Pzh4mE;6>k5s-Xa1F_%*6FYrM0rhlr3U zBGVw)Mr;OCzDQI)^V;hRqCP^=2C!~`mGi?Xa4E{mRDGmBM&XeNQzSCZ!8$?m*!>u@ zOx&r^7egokkOa0gfe@K)t!7CnTu&J7MfLdlkx^%(Za7VFL?jjK{Y%zusn9Tp5`u^Z zra~?x6GV>|9WV7S3igGiVbL^hfyqQfrLAU+++aC=`d;~;!SCEF>uhZC&s;S z24Cq*E45GjE1`$gz%^+laa7K|V@)WvU7K%SVNi$`6W<*ty_B84D5h~J-^lw$QDX_3 z$>F}DL5jFN-YJC;C?!w{CXzelC?bXS`Gjg?AB@aWWoEUEO0R;>sB3r7a-6WDblHLkSn8kG!rb$j~nq{*gKBy9Km^kxY2~vE)2q%x&BSMut$oh6wj8&%wux z5j6B7$@>6td?lH2Vd~vt6N>19HnWfvq`IV_s^GD4$gtipf`Z2pk^vyeEZMM01Q#ffSVvSTEvG;?m|IV1D9_ z&3|Nlauy=t87m@hs7GVNEzl)(63Z-EJ;y@eQmXicn}OGbbFcnLu2sFTuuXQXw4z(5)(g6Z|Ci z6p%u~$ExnKuH@mygm)n&N2ORrMN;e)r8EZXXEwf7(%zacEx|Ywgs~*3jcO4N(rQ3Z z0o{4Y!2h$Gq|(m9D$+g4=azM;H0Ml{R;0m+c%BL{s;UFb6d{Dr9h9_5U|%$7nAg_0 zJ_`G@qTWa(iKnV*dH{YS%fikC4wlNlwhe)?WGK(1iGY|gcr5rLvu&m9Pf0(X z0A({2yik?F`9)(G4PJDAWvGrQm~*78juSA2XRnG?bf= zN=g+7Tzc0E+KYpb48X1Q|NBt?zlPUEuP84k&*h%YJ#KlVd3d?6aIfu_>6Ye}$Msj& z_AU=y(p+*o?{;qQ^w?>rQxV7Qj?EozIm9`**)O%PVRyzZ#Pr!T%T&%dY4o9VKhjcx z`Y?);m8ABZ4Mhb`DE@=o5@wzXFi2g5&IC;{x=nlmC~mf6IvJD(b)`m2PaMu@l_$B1 z*R8h&>z-+Ms9vK$i!ar!u#;)j9tGf7HhP=oSDGm6b@2w*r@>zOOze_&$FQ8#l31gtr*;su%7z>UN*j1`eKvu zFH64pcSF&ZPv5pV8)Ctnt5Og&N3|VyCR8yhJ|K>fi7AMz2?J?s3!t-%9|KSW`+)Im zN&!-2SCAo1{@mBHWy{M4(Xq9b@BLcgF(4X0g}D>z>XYNkf#YVH5*AfiS@`D&3y-`KND7#OREbq% zKoklP-=uE09u7dBn`$xfY(Q9pSQ9N2>5fFphR<@IV3hmh%Tg>_z99K-f5V|iBWzo&06BS>ZAZsY-p{4t45{8^b9#L^;@HZ znL~XFUA_4v$Wnqtnq$!wqe&&`8KPo|ERsA06^qpS;%=Ji_aTttGN+JISwBD~i&636 z*!lBIZ%pWP?WaXgons1@>sp}MuWw>37b_D>NsJ{5 z;WRi!N-JuZ3JnV(oE;e;(qPo!M8I?rnoq3%;_>-!Cx83%UYjEAR?VBzZCcTW(H0-- z1i+gEaf%F!H-y&VR~!c{8DV)8>Ov)ff;d(P1f8nLupO}yNl41>zWp`qbj_w)T`C%V zE;-lf7-7NOtBQU${bZ^tBE3UL4n-t_+0dDREmA|kRXsq8g@Sg|urix5CJn`&J$8LM zXI>OGX0_*o!aw?!3F>=#NIwfkVC6CvZ-V5L;FIUmfZc_IQ~z#r7&-9Mm|y_jPc|!V054`_yQ+JDCD+2 zG!_QkSyeOOVuW(gSR9B9G5kIHrAJWCW(9{vRr%EASb9sJt&x$(f-RVVRf6V{aoVH{ zV45H)v-OztsdheaTX0*jKsa~O!9E`!(B{?0;%l9E41KWR zOod0w;!JbfppFRC;`>S@90xbCj6~O&mj!<+SiF6LBvP7>4JI(N-e50`5z&s1)gbUT& zLI;^CIdtixF_}evyZ_yHb1~0(hj!h*Q}8`bOg=g>0#CpfQ<3X1T|oi@JUq<<5Vj_4 z7<@ABT}x(wz`*RY~~j3bLLq{M7^Y5GYfd{3M25d7Hoi((SAS*>Nm)6FW z->|bt{QK1&d4|WG8FudSh|j^6+_aIUvLn#S5K#ibLL+`rL6jNg9?%j||D;9`>J`fA z2|-98WU8`Ox$K1S9zJPTa~&MlwPB>c*VFE&?FLwKQQJueH9Vt8RJBBHn&BX@qc8)i zMDzvm9Avw& z33cVDiHqa{9G-`I}Gi#IFZ)9$#X1 z+O{}qTDT=U*)EM-L!|_-cpGNJG_x0oD=n30QEzB|tY-=m%!i3Aat3 zpZG!P@IP&5{(2)Jb;)oP1{U!n8h2~Z5TWTuq<_WxY45%S0Q;|&;*zI zkQ9cfrZeia*a%9xLTx~4`q1Er@O2g6Y#0;Kr+fF(&nI+P-#j&P>ra(}Enc*Z1usIw zPVpB}vC!mwHA6+DhvHHw52bV!$BH%c&>>@dqLkB9l5kE#s+xy7?R(*$qhes^mgb&S z-20qa);ZMTN#`CG6_^z2|4J|0Rt?tALLFLFWX1nd6;GH>>om2=iRuL8Pqe#;!bgyYGs&lgR*|aCnm-}Ljbj~0abML zmgLD4Tr3m#pOTk+nJ53rJ(UKXS>@Me;M%}D`%PC;eS<7+^c%oqlL!FTJ(tC}SfjgwLZ-gNoPOo&09c_WY$k z*ZH^5zcG(Xn=LMsJ0+)}`LDqr0O3*@iK~k|Pc1aYj5CC61BXSBq5^ue<=wiz1L63K z{yyijXERryT_-5$X@f!zw{B)ehFhHJ?4!p)BoQGwP^^H}5R{5-hmkfda!_t}>HdJC zxU$?Wr;CK}CcP*@!Fi^XBy%b9Y&%rn5Kte%qa6)}|oJ|4SP?4DYGl z<-Gpz3ikZhbAe|~k8>VT9`^2wF#qoo=Kp;I{=bsTaThc2|I?f+IvsQB<@nlhq+*LlBht22Xqi(k!VAN6^f{RaE~mQHki z244dBO7&h9MnhOiibBk1!P24`&nme1H5M@9@nV2UN3ywwB*4Firv}c>K4r_$uYs+8 zjL*4b$+?#o!Yp9yl*9BgSV0u4B-R!DEMhL~7uDC%oiC{^$!o)DO{4gP>xI((kqZC# z=Mhn_cU;-@d*K>szpeXHwD|3Dr^791c+#wKp|dC=0py2}%)?(J&$4Aoo{NZ6I|Fki<1p z*?w0Z&j`L){GT_Giykd(zPj#bvjrt`pu0p`kV%W0`^pR>Y-zf6;mA`gFUH@-vy%6S zm&Pqo#E$zWY@NGgS)HpxpN$P$Tc$;=@Y}-U#j&<5|dHCsXP5mP7jka^H99%Zm zg4tNL{9vUS4-gDzfNLNaCS_z5FKBd%Iu^hfp~MM`#lkZ@v_=P~=x~iDgD>&tB z`9s~_+;VK*GW)SVGcPXvmMz4BIawI{r0~JOy<)YwYR_b0Ej)>;7M4j1?I>^~u8lG+ z9C#2(pKz3n7ZOx(%eBH|hPn*tQYh+azX7X8Z}r&HEy98!S(rIahXcrJfj21Llu|N8 zjY+BoM|RizmefGxfIhOj?zC#3$Gf z%JV((%V}fDokfo)UKYFtI0)4a+s@HZR;bx~?qy&M2>Li!U|)t8dj@`{Ix6YSP#z$B!cc`GPGNnT0GLuNHTO zCt|cZTO6z;4lK4bIW!sVLft%UNd(1^%7X_%t&XiByes}#Ov1QsBS*Zqu9;V6{vU4j zKjm6?uX@JpFiTwu44|vSIvA>`EZ$VKJZ=3f1pJ&?(&$#*Dq;318()RTV>yQEZ9XRT z+*0@6sGE~FY#O_Ac#{>elOip3$nl5^R2ssn*owLY>;UYfZOgMl7g8pgd#pH!jJgvN zfP!R+K?)f&)j8+3E)6~nUGb#*p?yyKJMC*9V5v>pjc0i1xCKrC#u>3W6AMAUA!Cd} z0ES%vJk3mR4Mss6NkA8R$LQ81kD2@+HlpQ$mEG3&S+{#mw;GLKpSc`v@u%&k+u`_Y zY;y&dg4!DFCQ4CkB)E%>2S!S$j6^UBK}4yLVpkHY!STF0=Seje7pks})6?tI*FW!kr-&_p%)a=c@FpD2;Xx&lJJYA)~ zC3O~QhO+fmTJM69llC=r0SWMi69P-lwo}Ax+m8kC-YS{e9V^W z6t1DD!+l|eS_Cx_bTR>2VLOxeXE|mSys2s$D`v7qP;Fb{ttR*<99~q>`Oj|s+-6nz zYeuB~ln3X6EY&#i=b(()yrekA_8wg?CgvfRcl4@ELfm6gy#p~9#s1fXAq4}EC~@kR5&eNvpLQI<2-s~tMJ#r zlIJ%45iqfU_4t^0OGTj1FmGI>?Ks|2@dk z4*dUSZg<^=y5)1-=GxKau}iv(xAS)Aj+hWQ-pR-DkYhiGFAlRDs@tEp?{D|sZmL~5 z(>_xVVJ8Ci z`6ssBJErU2vl=7BBwNq_Q@x_$YMsOf`wbG&9L6mrQln^3S+MV3JaHGxVC$(?&WY%%#6TdBP{d#4zC6#(8 zX(C!gJq0aU&($wmkx>!9;I=X1h0X>dV%39(5n(Y1t!JWin;f01wVOM$;=1$~zm!kQ zoSQwotmQYeC587tOYZQvF61ak)q`Tp=oTX$q2-c!VE+jc3F1ZprLw@to0-MNse|*Z zjHMziIjBkYDDREdyzhvZw?@O$p=zadB+UsZUI_Aj(wnJ9L)V86v4;J$FRq znq3O$N~1Z8v-=mmcW-A%o$Eu|Rv0w?aItIyED78kqLRAObY?ILGr-7_vxYkhk_g;i z@sd<$OV{|tNc+BiTFI{7YM&PECy#P9x^C??=4HEJ3rMylzQ?QHa#uR11mFk~5xFrKE&|3y!Ye&N_T~tqqp+;_gl^f5yx!e$PKK z&=N}#kFG^gMyO^TFi^>6%0*;``2i?42+>4zcD0j--%1Q!zjVOzgU&VIKX8sriFjT7 z{q}arYX(?iXf1T0oJ>*{Oqds>9~WMjMT~VJiV9D^4hl}ek)=0AgbUSSM{#nU!@9lojKU*nE$?DBJv1x#9+ zLQV~9rLBwV6WMd=t>GQvaEVwe0zdq@p!E<7A>9HigW@UiNWh1NOnGW<+InDku7)4F zr5lf{{28)#an~RVShPj`U{ZXdQObqbD>$(X$kLc#C4fQVK}>|O4dWBGmS7DKN}<|B zmFDi-D|Kn{``*M}gW@Y)`nTM>=1Yh5wtz(Ijm9Pde_{`?L?~l@ZLlC#MS|f9!adV_ zm!}b7F{UF@SvTLg9|uw{7j3j8?!?AA8Ntnx?|l0lZUJqUx|-DQA>|n^;OI@lxN_AT zw55j1y~rlk)`KR&6SNp-6Yt^oSf%ThJx0LCeJ~+@4%t;R8jzK?bO+=j% zCXf5Y(%tTpZ_fT}dEWOfbnwyiy^EGbJ*j7P3$}njTLMi@>RyQ*S7Hf-k47ksdBh64 zm%yPmO6pYMV4$~#Qc^%gv!D}xbI(D10{7K*6+FE>P`$RaKEnv|i!y(6n zZ6q3&JXS;K8q6?c>YPmUmiC>vLK;Pc9j99?qN%XFQ||R89IFT2+dbfGyGtLI`j#o( zJJ1qHZwrW3)?8!w6t`E1W1zZ;nk7zoBf=JHxas~RwrD6`Csm>Nc}6zOXzAP_-?)tv zYVOTg|M;)!PUqVMSwNsI3Pv-Gg#;106NKIoQI1h0LF5Ep1Uq1fX%Z0Rh>7Qkwy=7@ zjEgw^jeG|jr{|3x*?fKEq$5{TJ=?CSk*|+s0JVP!vy19Kb?3O1p{ApgD`#8|r4BR_ zfYS$+GU_o9Vqd3~>ox1hm|-iccsKGZQ{wFTrNyV;9AE(V>LMgxKIO(PTV22e(%S~mloRp&*@SQ@y7w2-)5t3Q9OvOTS1p=G_fDS5tLZHm3<*W7x1 zb&2wy3r%Qp`qcc`OW_t^<9+>Ls%UgaPzwL~m3oe0UK&@e!nRg4&Q4 z28Z(+#X{4UW&D#(`Ut;8P%P};eLsrys^E9!{-7cAj+cAVtZP883%$cEz@(S+<7%de zW7<$U!m6=MSbA$1g(2Fx!ZA0nUxmmGF;)ya1w{I?zGZxrZs zZ|j`ajlvg)TYA!av*oRVqN0Zoc_f6Il!w4b(@a7vG4mTF5Jtxh`i^GV9%yqjwwB9z zDXxe`Aq`(*IVXwUAD0NFaB{~X}*At9uas&GhLqJ)gXLd}WD76}DO zE>+FORP-l2eQ2I8zpl90>g$xv^Q#TW*=P9Du%k_%x|!M;BTZEehmQ_cha(QF9i})8 z#0-FX4#gat?O)hmvEO6A*nSMA0d(+w=zZGzSMS;0gP|1A%)7jIHm~pZ4;BzyGnXy{SW!_EDb z`!)By?n~Uqx<|Wra zYm#ek*9NX7U0v-qy1aC`>axdWvCC-a5Oi>UYV-v9gt_|?axy;#!WeGT`)Z{J?-KVZi5mQ{+F+8yPOrg>d$ z-l0sXAtA%Q6?r{yXY=BvN158*;g6DI#!jmfoin5D)gSM>HGN(@YThbS+X?(pXpYZy zZWu3%CS96k9&OG1r$p_=rl#fjqa>%7bL>~zubTIHSN_-g7mT?%{)ee?NB*d9&3-ec zmFehOwDg=pSG(`67X9O#so{S9sH$($(X~TPU7S^A;F0^;zF(eaZ^dmNDvn(R4 z9erzYnmy0)_Z1jEa+a_2*>ws&@-d__hAMeN76TIyl$lbeHd9#*V}bt5?0Tlo>yL zWc9E0yWRRYJ@zhtX-4|jE|+^H^!n+_sdf#YUEI^^Kn+u_@A6S_y;qCkJWJhK+I7>f z7w(+QbopwGa+2%WyJXA$#QfL9*AWeNM|W<1wr*sRH2!4z;0AkF-m|n_+{gX0UA3iE zoZbc$+{2ff_ImZ!4O4%AnI1dKclD$}&6FAF|)>u`l0cYPv_ zjeqh-6DDO>wdXq?T-AC1Yj`gEiF0|wo^nYZG;^67&;PTe)O zUEq&K-+nT<{qlX|@8${nzBIetpA-7tGWo{x%NTvIlUv2>Q-;jix_4sLYWH8{PHLBZ zDSv6??4j>xZhOALv)}0C_^pfYU0v1Cl)D9gX;|#eJatz0Z*Z`Gm5JBWcJ{k{CfM&0 ze`!emp|gK@R4DK)Fg4f5(A{~)UJo&D8vN1VffsvMpI)J1YPltU|DLU6?(b*E8~b|8 zM-_IZ26x%yFl)nvMZVlP{8Q-{c8m)@5bhfg`ucIb@LRoiHEGr6nS63S zp~0sPt^-1{7aZQ|k7lX2o0hE`!3`1! zTxq)PLY_6VO}`x-v$kLRoD29%DJM^44*P57`r|#0)qi#L=<#{oPa0!3@JGoPhxJOI zHU8)8S1$P{j(_Q3-*1G;^)P>wfC4&KlLFo^Rvs)6L7|dPNGp&Ycv0bJ^^@KaM6xx41vS$C!DKKT0TFwQ%k|_x^UN z*<*iy^Pqgy#@sgMz2cAJt!2iSd6fR4&5kqHreA9>-JBR^s_M!g#l8PGWLLTIMY?CS z3%wF_q@4HA^~N?i2eAbf#jM=0vyMmEvhCNI>c9Q^^)Hi0VZOoG0zF$zZ#}z4kBo)= z_q?ukXG^?WQ!%yC*So*!@pS#07FTaxSi8yP)}{^>j7Np|OVOs3hy8jytoHT5!RgB{ zH$K<--dE>Md=pW5>vz9e!*Zqkg_DzJ&ztsl2d{y~+#md zXyLL8A5P}nu=cNr`HPL2KW*4PvrR63q`;QOZPc@wC~SUwJ2Fe7NVeqfLo!t&9bC_)C4y ze)zLuw#_}vM{oQSmN7KLe5XnEH+*Tx;AP7cmj{KUG@Ksg^0*XLQoztrtWSN9E#`ltGwJJYXA^(C>jHvMXxza<}SeA=+n?5L3~ygwA! zIkWKYE?rle{PW32_T#?z92!1(d6@x8%iQ{{UY`G~vE&N>RM$I6E#t}_TGZmmw5UGi z(<`|2Jzc*Xf70c13GX*Ee4I}8GVOa6yeqUusj=;R`Ep$bl{(eAQMoY(yoWXFTf2Rc z>$MLUe-xFETIVSFy!`c>-It81TXUt;OrIrFj1fL^J)hn6?sW8-HmHGdzi*Y7lWH$M zkRsRXe0S@Ly-VNyaxMK!g|?js+GmW(V_ZJPU+TDa+WZ2yC+~@EwPN<04^9qCr+(~l zQ$CrI@cC=V_`ciS4vs#w^j(of0nJk-=<1l^{AOD0iGH>BmD%rgrs%i>>noUQijn9z zu4V1|$qgH>unhib^6LppR$e-9WYpw4@9?$K<^%8Y=bZic@rmtem1@s;d&_ttUZBJD zw5H}w-it%0r#>F!V{#F<(l&Wp zTc598-JZ9+|De#Bk=Ztf95jBHh^)=)mLFFqFT0VW^@YSjW6j+v-#b}KoNpVq?YY~| z_i?T19DV=IFwcO^lh>Hs()g!Zr)76aI5M@$;^u|!Iq!%K2=GoaRa`G0)ronv<;30U z`R>d=mR5OQsm%*l8;APyYif0*VJu>UEq1%Z$_@4zV{*y$Ah&qzRs-rh{}Hyr&)%hW z$+XXPS67;Q&Q#f(uh-nSY*3FyrLT1?(ROa-oQ3=63YuzccjN0d&wsN-!M0A1d(5ac zf55KlqlV0l@ps@)njJblxatf`onuX{1xD8m9&xB%*T^Y+xuzlCqP^O9e9O3=aq#fp zhpu-UyTbTiuYbTh> zSoo>@7iRmXZ`jE7dnQ*J{@J_Pr>4C}_(sSlt&$VY*@Xw4-!|z%mEWuFckNWK{y@Ho zTCE~3b~~5jTCu9Li?uvEbz|{r{>Jkr{85cn*T(1m&|_P<565>sO8ztA_I`WgrW~Cb z)lS7{9By>fF-OHJGfIB{JifZGFJcl>FNrx+rij<*SLYsY&9DzBY>aBpcTw$W z)8Q_Y`{&u-%Dsp~LSnfYrB~M}&OcRc$MbjdTmSsb_14e^VUM0HeDpM*@q4;_w5dqc z(!6o4h9BwFf7sDG-A9!xVXEGb^#3ZDRv6yvy}Nsxyw-X3@ciNY+;fd*Pfs`Jg&w;- zB0O@sA2zLk24E4l3vR>Qs<_^Eo#Fe?ryTm23;T5GaaDuTOxqA|`V_n|I{ z(j!A!0D)o!wU~6ERv{G+lpaxr(CDIOwoobgC1_|nAQh}72DTCdm%0@|#j~QmQ{e$9 z=m+{1gSpeEC=6UFCLAEf&QJ$lUs4HZ1*ennoC4t+Co6y_Qp|Q;lKw=41kgN4jsoJy zP1o*Oh9@7PSg}`#XDr-0@Q4Q z9>*P_BTW;KqA13J?xXYQHi@8?#G< z(~L4Rff1x@PL1ZuaiD(`)_03iJL4@$z)lIj0kn)sE;6G6)nY;q2-bj}pIh(%w-U4- zZ#6Nxa6Xh*%jwI>kayCCMUNgb2Sga*Zi19w(fFl;ts+4wVwTp5VoeX>hPxd_X}FwT zBWa83W_2(?TGS6i`zi#tMmMOhfW^b)A(=u=431 zic~0C{c3V6){C?ug^k7yTqRheqyvoei?W`5HRlZHh}qCI#29@mNO91#UkJ&lp}0zd z5KR;idhm{!tfCQ|1nV<3+5$pQVR{LCyLjOm%}-ruM(9Sw)1WKc3hFD%en-cR4j;PlXv2gW`&;UB%D0OXN}YJp z@d+W85cTWO7NXOxM)t-9fp?|ff@-(GE>Z`9JC~+Py|Gox=sV^+0~nCL#I1kAP6yfa z;Fa2(+8?^t1-?G|P%QU|Lx5tvvF>%&Rg&Ry02dRH01L(qR+zJR2+ae*hND0-?5Y!> zhiz)W5D_U&)xyD<%CB$ICIlyad&Y8;c)JX1;z>a~-XsLwm@ps0L~Gd%hV7bsvO{pL zl)hRD8vTSjw*ils@5WS3*myK^0UE%0B|S3zz{G??t1&qeoMjOgH5IbQHOtV6r-dQ# zmRJR}gwdjv4zrmEE?6XP(AZF4A`!_QJ2b|476C9Qp|FRw?r1B**JRsC!cn$niSEOv z5D&&?kXW4yyGX>dSlXXSS<>JxH8JZBvf+4?iKTurX3xMGDT9aIPCE)lfrpY~mGK0? z$tWN=JfAUlLDn547!zVgz&FcmFA>I6;9T$vX(y9n8dOR{u=7PwN~xA@B#pj8$DGCk}8uA8C|5d$Ls$M?mcjGWc8o){Tju1pfT zwE!-I-hzKp|1Oed28f>*6>qtoKp_FMr^t!CfHAf?g050}Jpl40&<^$!j+CCO1r$1l zIAbl;-hri{VTJfP7-kYDL6}*;4fKsq82U^QBBfxC+Z5fy0w5&QZ%(OnT5tz zRA3Y(;KMJ}xV!ZWuiwz14iT`2%Jgv3@xsvh1fD;z};H$xGVn@KZ2y_*=*bw7zcThd^ z+P01`##oUM{1Ac}lP;U|`t_7G>4CG)ARS7=FadjyNWtW}@4p_G0GG6BV!#)tQvxDK z40c)?gnC4P7ket5T`OK7fv!l%Lk%xs^IZ2u);NG>Ss_YM7*{3o0p$Vg83hv8V}=kY z|k0@5yb;Pjz_ep@jAj95d<{S4D3j}Dv)y&=1u`y@;1(TtJ*AKFXNt( zWYG5@Ns8^Ej3z=bco72Ja}Y|O%gazqIzVsn7DJ7RU9%oa4yjo2QYpMCS{s5nm@e$` zIBWv!9;5-dpv>6j2wBFxN`P*B4bAFu5HG+EVpUV={b@WEya6sK3U(p=*NnDT7WJvtvB< z7W{t?!+V~$uh(I(?w&6_$9NV&-rvRjZ}$xM(r)|RI=DV|9p+lZWrs_1XRC9Xb8e>{ zPMsYeI!<&f=Wy5|$o`Z4H2cza2kd&8-a!AajB(KD5o+}zxE*tAu_WaD6A(3mu&Acg zsDu;tkF6=Db#rw%=~qW|%xbH=8}CzkODvlb;t ziQ+s6`vsV49G_H#3>1+tY#Vx2k)$rcuL}t(_$2s<#cFUxu9X54i}68x$Fe{lnBjyQys)N=1&l>iXWv^l7$zsiKJmulKb=77M&)a$Rcp37JEN zG%?x>kuu&TNrH;9jl%3G&2!d-5$b7!Kl^VEFWPh9+wt=odJm|%xO{q%s9-BgVF~_8 z>qZO3Ap{0C7UW*We5puF1fSq|$vH}#6&4TUNs?ho&u(J=6R!>j) zRJ7VFvlUWU#UN4!1Ekz8Y+D2_9tK*f%#)Y%1Vj09{I4{P0 z;U*BYfj3PUV+yH&o+QOh7Byi;I*=Te(so%{)ETHlRQJ(DhLU#{?{ znUlT3ta<5&bfP5eYBgM!vkIg^ICzYlV%#gj2a=VyLF(ZBh~fx|Uj)f0bWli^S+s9~ z3_qv!5m#?5US)S9cEg%M*ZW!XkPMRy%fMR!u#+YRU{l}?5!D#Jkh$0R8x)l>X&zuF zIv*tbD*CGoQlDMw&mLjLs*394A zn$rQOZk%$2SS0j;U;{?RoPr=NK8&#YTpnk6jRH%|ZNIiTic^fEnrvsf*V~v<6jaLlW_2-4kB zMT1>sPi5bE=3syOx({7{`*oamJ=Z{McEa?Eax2ymc0;Kn;Dzv*Bdk1}5*`#sl+~KK zz@|4eBmJSu2(}Gz4d)%L(PV11pXwej>lMGtvBuT|^>1AtV9mw^6hZL`t&<_IY!Oi~ zfU{?^A*>pLGA=uE%Ein9jGvP~s+Ab#GGY4abJOovNe!@i6IxZ` zLw3Kw;)ha#$lP?KB80(1>aE#`c^aV%Gv+KSUpIP2g{2b4f!CXPhj*t!s{kk2tX=95T}ENDnxvo z(PLr%8LvvNa-3glN{aI@PoJK0+tS|(U97x*x!Q3eO~$ElUq-%`D{t~;k}E0 zAG^0qh!wI}rJztoLP4t9B?58m+1WJ--%b;BDKmqNv@MX~CzVtJVGl~sALrzGxGHAn z-`TUT+c|$y@TZcS_f(3uLKv$$w92S*f}M~;H4iSE;!jc`SVEk1oO~*_N%2bOVt~Zb z3zWgKB8H;)cgQF40)NgmGhoZyUYlw^IlimrkDZyDdt?p?w>r^2fI&lA2ni&tv5p%M zSQ0NtO`YW9i8z3#7osr%p(6q!3{PDy>;TW?A_ug zZOvDQ4a|47#QA13+T5<1|7}m}l=7VWw34L#7j#+^tRT zp%+JXdG+typaP}p$JZ{JEwfLAWjJZ&uy+cE?h?&8gaQ~c4U`heFS8c2EhA>Pqu7m_ z_=NR=34!TFF-41!R4l&foyVBtEu0GvpPux~m|Z{lzeo?Z45J59qrQ14n4+dDsSOds zlyFm$Q8lFrhc^~kIgKRHK_^T?P;`)Q;+e$AofF@cEc3bL#E$E3CJ%^=uneUKf=(7y zwQ+Aang4fXJk7g8AqFUU=#=|tbtzaEyv$(cqo{EVGK6^Lh!O2R)Ebwg=c8Iqe{L9> z8glM#hX~6M`YC<2aEKCM1r(nHg*2MtNa_Kql1Qc=H=`066n;3Hjx>WD1p`b|D1Ic462-@zSY2>RnW9)}YdtE~e&Xg~ zGAbFf4EJ5`ecV0W_P9lW?SH^E$~CX|W|uQAsV+Xwr=63X3p-tM8tzol@w(#(M?Z)A z4&xmv7_;o}*k^bo+t;;wYB$raq3N+{rm3d!%zJ>hz1J4608A0w?iuEp+vC*#(BJ-F zKITe?s-&v!CES~gBVzSd<=D~rVF_rGMk5IsykKs~iVC2vwz1DM)vXGFwcPgVsyJON z@BurF{Vb9%h>EZYc<3cJ6XDPiQScyP;*9XrY4%yh=3Zb-)wa{u;uahN))a2N!gdp} zNAd${D6uT4)(X*@qndGMbFc__NH?s*Las>JE)KT7Dxu@F!E8aZ1{+wsgRR^EV@WbF z`r0U1;uctjnDQOt@McucD%);2h?u)Pky=Vqh|GxFMH*XzE0D$=4hOuB(1R6A#-`sV z=crx_+-n(qT`7|NS1(d&{oqypN2^8#m5P=iA_*R*NM=G2?8?2u^84DZsax8}dk`OJ z?l{^j+!Q3OY{k!KIR(WER=KIp)sPM5osWWX34K)^bU-1Cd)Q3l=gL747Lz;n$VsR% zh3!XOc~UTtP6!M=(g4y6Y{n&My-g)}V^vN`+>p&8N>c-a)ZkI66mTV42$)Qgj|q*W z*x0b7{MQw^jDSKtmOv>EN|!(hC&4nHz)3q(jdp?n9imdjkb_Z3j5S`I;i9&_N7O`- zjU`2(*h0A%3>2P@*~6TMNu@#geHA4MK`#M(V&ha%5bUb!6Qp={BC3RMGCfIm(z!47 zAL<*`E0utQ;%=$jK=rvzWr@4@pG&g@ri7%VqXaidD}ZDNUK{aI{V{1taKMFj(Nc(@ZY;0|P%s2Uwh%{LpBD?TR7See9yeYHSJmrUDk#b4v3 zR(@ZnYbupfmz8W#xQmR2Fy$GW)r6u$fIA|G8VW5=v zpGHIfNE`sYm$)cwLLJ|l94f0q9zPP0 z0Um~n^M&U^aV8Z3hv&|$R}}pK+zj-sR9!;R|9Ie_w7j{zqAm!hfSjAUb<}YkMANZ` z|E?B??~#QqP|6@9!37S{UBK2tUPG-G)wdZ;l`aaUMcrItE93P|EKWt(47nDC<2rT@ zrqUw9h&WhCJf*~4hzI~@P<;r!Bp7KO57Dq0NyGMgECLVzs3=V7$ zoDg?Nwa9fyDEt{!N=Q#iUlu8UJWerzFc9b_B^xseE3?U0)tMvn1rhpCyC@m7Nw^YX zjpH5j&_#qtuy)uI+y=$YByWgYL12f0&Irt62WGujHK1LW=@rvdVar&cLuJ+=E%M+{ zzN`c(SXIPgET3=O8O;q6^UlJ!&XSIMUT5ryzS~HaA(ToE*H&;|2uVbTj+zj#P9oPNmsH0S8xvU%1#G4YPtoZw z3g02O%2h!X*-0QQnji2ZEVLU5Yz?V+aa{(BgbV?CC_bT-CL&&lA7rc@sUi=#AR&_U z+!YG&N%fE|I>Isl*3j#xEuGF}Wl1c#`iVWlxB%(3mdvlbC0|oC?XcQ(7DP!~h}VJy z6p2^CI&wKf&Q~~cPwcE{V~#Hf?=|!j%R}Pv5Tw}xJ}Ft*+7l>HCsH9vNy^GF8Rd=R z%^b}mSb;W$$)!GYd~<8VqH7Kg`v1EhV!BUris0j1}fgK}dikk6=zLWl! z2>k!-A27f`2PM#EM@I(G9qM>+qzo^QTuL-Q5Fl!NCAksu-?sNBevc{8ruO5rI%Nbx zfX1=0h26nEO{3_IP;H1anW-&ah9L*wmZLrwOV}TLDJ9P2=ESfRb%VNNDa($?828Db zO$?5Pfg;$Zj=6~BFY1}PJ}(u4oKv!fVFHMP+aP>P1Yuz$aG^1Gh)r;`s6mO)4p&35 zjI2J41b&rajK^Lr)6pu}<;UutFsLJD(DOAv}j$<`+zwryKwXaHT#=}00gV1l1>&a?Z7$Qz2 zt-3(3#tpK;z)De1nAKiVP>6u7Mo7j0^wBS<|I>z+;DSEH(q&z^Y!4jfDon)LZ%S= zA}HCo;-C@)Hc%=<;f@3VFBq3ZI48tI;_|5A#MN=wJ4Ut%L{c@Yr6pr(*pTr5RgBpV zu1#EC+l_PS;9kkCs`EGJHO^g}9i4VLg*#<;Jm#3@=;LtFVYEXvY)H*0u!BRLVu7Dv~oEX^c1Zl@%vh)nadOce-W4+ zsh@DM2&an0fz<43QxSiTv-I`UuxiFP>p~yNl#6~ASp{ySX+>fFdot5U4|$`|!`N7w z!)x`>7@`j2$^A!3`cNstA>k<@8kP)EaO^b>P13mMM6g;0P6<@E%)u;6!=VCAmZg~| zuum|cG__kIUWG(L@g7!9D&EMU%{0(2g+OJndIEzmx!-r`p{|PC(bHj}nn=|LNX7x$ z$*8;-pQmtzMqN}vLm38-pfH6}H|mW*BMSoGnoJ;?Og2ykY;^<%R%7C?l39<08O|NS zftR{&L}VJDKBl@A47$RjQ3X?4n%E6wa_D*-K1cxyeGWLs{C4Vsw2$PKV3#BA!xpfG zzywd073W4Frw1&bDJ&C8_EJh_IhTMWl9E>{L%<|<6LEj}))xUB^;rJ;x-08SJdz9=3_ zrAQFxq#~qQLa3&78kEHl>9URn0$|JtOf1|JZWujco+pnKoB%caXY7f!y`mx`puf}z zG>%SUf`Gb5e1+VULVDm_P~FrFWUmH$;4m@M$0l8a&#A{7Par&?;u;jsV$3;AN=IUW zctlOm6OxFTjX0~Gu`(sz9u^7ms$|bpfJ4J9jbHt;>_D#f>eW@Km@txchKi=ZvY~ee zLn;_uTl*y_92X_6O5d|ttb^__Q%4^sjBr6>xfwc$cFjGeh=NVRC>f*Mfhg__f z7mm+(mN7l8E{zHL%Kx@#Uy0n#y#$Z_4hOmLkZm_FqTXvgre+2{*7m%avX1*{)#Cfp71lc zU>I&p`#mNf5Fm(bp^Tw`8-v@YJ7zn``15!uc~QJ>tnLU0P*Fz|sLGImCT}X78wELx zV{vRO9?t{7?olByY!ZS`o{gqMd}%qGnmovIRtQ_fRvO+FyHQgRl!`ku6a+iPH4)W3 z*JDU(n4 zMXUtc8Du2zfJhTy5V&3iRspPS1Ql`AL=u_#K#)R7nvml0^k}I^nyO5Nam=sYsSK!M4~|DA>K@ONA@^l52<*IY=l4@rjEf`+%pF*YM>gy$Z3I_ZOq$Fc) zzN|Nhkbo52VQ+%4wPiXzZR#|1An^~ff!NH*p3sy;eure3wA`h^EVCF)1Sl#YVDE|k(RL!1IxZZuKG zjnoO55wEfC$m#i6@VoF;vybA5iz%BUCK-V)#iC;SWbY!4iX@&90!A1SDgaUL5sx&1 zX3b%A6MB;57D2tp-bd{kD)@;&ITnlf4zPlh;88VF3mH>nCOITuO~&==?XN0C#^{(V z?@&*B)|FvkT&OKd=Nm>3=7qU@x`iwv=HcWlfwd+zN>rTV!j1RCxI8Im54G3CaD)X^ z5WyHAYM+xDA6_Gh{8Zh?3!#ZK6r0PcsE^g51i~8YGB8h;TtVk0j3$$Eh7CKRb30W) z!2YKAhf`i8dRQ}Q3?N@K{wkZrO5!g@!s4p7kIdpDH?8_%s^+d^f60&3U<_TLRu<5> zcsWb>Ej1pvOe*c;-NQx1QpK5u?CQx^s(r+RedJD2Az+C@X+f*?Y!{U!&;xiJ@MqOH z6Rez4nH61gskX!bGes1jK?;Y%js=Ap>J2^S7bgbu)>5%B)YQM(5k=+%$I6G(4Vjuu6 z8L|Hl*?y1}&Jv?6c~YB{kPx>~$(L~sc;x~OV|VT_VdWNh(HM!7kuPvv)$^7#`u~ z{c~)QhI6L%%{_Bu!4>nymoBz(lG%y@T3`j>W$REBZ6<6~=4xnQ7^(yWHd03pPl9j< zDH%LBk{1Dhkg_t?ENss3Cu?HA9k=sK3#(fCQCx$$vnDi%vSNl-RX=!7ERU-6>8cBk zGwN>SfAMW{E%+}y1fn*e>kz2PFcnp9C;zMDNRe}cypZWoKEHWty(j+u_qQJX>vG8V zh|M9^M)cy4T+s+?=Icm5615OgC)SZSpHkH>Qm9`JpY?EgiXGrFh-uot{p-R50&8xY zG4sHJwf4hXd#vkb1XvpqBANX*Gc@so)TKp0$KWv77KSb08k|#Ln;_(dy&&Zp4G~>lfUubmv>iym#=ttj zyU^q+tN{!oHJ~(-OEll9y^dE%e-&>i9{&)~w_Znt8`Kl}CGcU6&v`uZ=1l7ReB#z) z8Pfxt?*&-v(JPawJjIfqc5Z|s9-M{tvBU|Se@gZI<#>ah)`dHj-txe|=Un6^{ z8ZEFe^z;a3$RBeWsNk0BzjSe$@n>A&r1lplmyfP5zSga}^UVs+dsPcuKJU`t$3t$0 zSpDg!s8j|;CcHCp@96WT&@QHq0x|_SE{?FV4*YtshZErq5sFbFN~Lci{FCNMa%i8^ z%SH?@IbcPN2?1A^&3c^~Rkv+$w6zvtkdfKLaOgZi>Ta-RJnWLEGC}hnyH1wN<|zT^ zOeAd|J_Hb-i)%=Bx55HpvIR<6_JrO zTy>`l+JqpO2dB4$GCTy4*W*|5vI%-L+VU>CeB9RcJMaBbGQuVBMVCjd)(2T@u>XYk z2Q?7{T}eGLcvMPorCDHuS~GV<&JjXQrc_{`122wlD%Le7W|d3G`eOa7{M)s{go78^`p&3q5>+A(h0BbsdHnM;nP%+LzRuPXt)R+D#|%VCk}h-#CP8t~ zl;DGYP-^{xqNKor*oWle5oyurwK0oA{SLp)EHM6hTG`KgZk{gIAbLiywJL#Yxl_PL zM<>Io?~SEE;!4#s^xwD=i*>^ul|D@v6UQmxLF|oc2;NV6z#6|5=60JZc0Rl9a`8%| z;=}tm^$D_8p-mvE2}er60?^a|uBt1VmTp1l4xLh)pnwH0g!Qw@z7bf@F0N_$e8-yj zeD3P7t9>=!hiT@oK?AIniDAQ-U#SAgok|}Jdm0BvVLaeh=(V8X5RSwV7ex9Y^gf!T zg>w`-`{TBs_l$dBe9b;}K)d&jV-J-dT5o{068(V8IYy)bUM=p53bWi7LG6PoSY-B9 zJrO}T;WdbOA!iM32V{bA6lrYPQ|I*L26L0o|MQ^FvGUUbVd!=oDNr z)zpY0%^kt*!>XXzihm+$;r9c=6;&Dmk``o4!2Xu2{^;Go<9nH=$A`z>^+g#pbL~JOzrIy#1opbKBk4kbwLCos3h^9W$$}sQ!j9qzM`Avx0;=mkN`?jz z5j7iR1UWa7;4J=qb1Z+vPfJ?Zf2e%*_>{|+Ph5T-7i=v@&mrJN)Iwlacc@Hiy za#^3Lw{G?hvzDPCNh%bG%87`>Y+5VuTaUH_9WF4p6gP;}kgtlx79W`hwlU zmM{2!vGb6?%6e%)PN)j#@Gy>sqiv)8xx+H1`Zp?N*__W2Yz{Ed_uyN z&~G7$y+^Hb9XP(klyOae9`5R)t4eG=WPOMt&z1gI*bZ{HL#cS8wP4Xo0XR6&IGGZi zLR^YpiA6;y`y8A!G{S#eo#oa`R^&B0-gjM@%?XbS1?udHe5gUvu=deWTn;Z?d>PGR zalqXZxQhI>4M!Ii2v!pCBQ3DywKRO+OeMM98~gNyw|B%@PF>sJV(L|2T@{)(xDFh& zBNK&%Ao4cSGs1eN_L~wXAe8cD!kMbvyb=Zt$~Muk7KQZ%&y*q~Bp)Fog{qL?Ji-40 ztb=2H#zbSmQ4$Qk=jA#H4<7Vd)3~$yjY2DjUhGrxa*=aX|F3Qkt=MMSy4f1rY_=I} zqgZEI53)A1+G#b^%EYXU1KJj% z%)IbtJ%I#3TrYwyAyA@RAAoKtbq%#K$vKpNL4uv(9g!jc`xO$a46g$X?kI~%05mO4 zC;+lBcU)^%I|XpUQ5%?DoqX5;V1tx-qwOwGo=EOMj|)9v0pNfa!1+;7&xms=`?1Ni zv9e_Y=dAO`WxN_rBMKB`<%4!Hdv+r3gLM%jie6=Kx-$dX;ui(10zkfgpm*#;)AE>+k!8Tx(dOAMafaxPwvVKa3yGyaBP7q#m|J0 zgr0P;(Zz~NHsWVA;GS|+K}(~i+Sk&WR>}&D0BYw4WC-`kJw!o<%TuClvK(DY^n#Fu zUq(nsuCwY~1jvsD268f_`y_e|Yl>0f0r2`Lq0hA$wvr<0E;~YM9YEpoAP7cuC17AR z+i`6-e`Hj>Vg3dG90@auz^DV4q`l=IcwsRFKNGzsMMFf+tP2jFvb-M`=;WZ{h=8k0 zKTEJ>jT68D9!^c7o(auIwDp2-fqDahNZ_(^(p|OnV{Vc*oPfUyEWQRZ%TEdvyM8J-v+662eQdC&X@$l>LYoc^42NgUJ zsy$%&g1)G{+{ZO0`Jjvl!c$=eWG+O*oHD&xa{Q6g4gruBjt~}%fD!;zgkNL8q}pa~ z!FR`|5~vyEe`WoTQ7u4%k3t?55&{%t`o^O_9X>OKXPQ_R@;X=-fSn-8)%O?bofWjz z1BeomT(WQ=aNtO}Qf!JggfY?7)=iXG1RF_17zq{#_nq?VgM-8=e>?$&lZ+tE6v;#% z<+_9gK)Q!pT4_Q|NWg@nkb#^T#g-yx-ga%lkA$|VaLJJ}B-aly3Dt5iZI~)8O@yoj zW%*^4MQL?dRROaRBz%;9)Ec#W;q5g;Ds3_r8lW_iWlqKjeq$U^S}2Pmoy&Q8d0KI- zO8970SOH853sy_sD!7LPLQQTpF2}=p$C2lS7MU;z9H_xilQu!VoTNxaGVdhDKR8}B z9$)Ybp16Ql4SO3G!mK7L}{2IGK4C}$n*^Iyuz9aJ@#+!F8R>1%NqedsDIxG>ZOR#66T8} z+)xrk(w>HcN*ts21t^_$qLha>3LeX_Rh%H!vga~F6ZK>vdd5j3jm;{gU2%Y7Z3u~2 zEGcTnI5ec-SI5V|M#0J;Pz+J2xlp6g$YnX4<0{^k0xKjr1_CdoTpS0`S50j$MZnB> zJp^P73)B)~aw*8!hQ|QMMnDFIb%SvbbJ&mrhm z7L6_Vkq{Y^o4}2;;rtNWALj^Z{0KoC(G60XdCqPF=nYpGHU{pa=$opV&xR?ft1VNR zaR>bZ$qQEhm5#Fp=g^)}3Jb|cMcthlJ#Y!&fv{>y)u4js61@x3fvl_}{Xb4ga%)+t z_OjYI=29q%veDLbr$sH4l9WY@7c}>tNYDYS4JfK`W|3cmr9z2~^i>$p4ub&=8$v+9 zILaoc@Gr2;@D-&MQzlm{`2EELAwf+m6^U(RAo%!Wu?h(UkRGla60i^An9u?h$AR*# zDaHS@)E0{EQ`_?fskYl}7ut@o^|W=dtz}!-=Btg)=7`NY17Dk&Hc>WiHmz;!!Rmi+ zebYL}I^BAzb*Ob;>n3WXdd%9++Q90W)p@IJR*S60Sb18xr~|EPTNSbVW~sA0YPrrb z*)qa%pk)ioN|q*Sro}6Z%N9E=mRclQ_*rzfa00&ImwB%FY4gqI^UOz@yBl0FZ*N}R zpsTr!*(bBxW(UkN%%+)zn)NknYF5F_*z~38CDWazOHC8OK=@(mYueQy(X^gHG1C$z zKTRH)oHW^JGRI(-NsP%5lQsr*O&mlCBGo$lH z+l&^eEsVw(c^SDF)iNq<_{~sfc*Jn6;S9qF!-0k^3@aI$7`#@ia~P{0`!CTfwN=Qt zGUmdBjteW!s{JZqdU!X3mR_zy)n51b(t>gC?)%)C+i-Bxj_wZ(8kK&r4i4*vj^Dq1TDcr!}`8n>u64@=|L1O?+uz-6FL+ zzG*o8(4A=;!fy9!Qq_Ba+WHq?+CO58tDDEQw2pDzD=cjtw7celShY2GTg%8v+kN2g z<_hT!JBrUqOnUL_easuRb#K1w_@Up+25f6^t+Lso3)^~j$ZDwDt+q1bOB;SX-WqOF zYs=?|F%K*6emv-y(z{5xOE_qK|^cyM_9fEANe=lOgo$LfBBb?2Jj z?IJD@*PAZsxijH|T7NBH8q+Lo^uF||9g=4yHv6-)uE(yfd8(6muOpLcynl4Z>1esy z15)0+S#FzozLx4VL_Egu-(F_cSO0o+d0KOS*pbxZ1L~?yovC+5#=Q@DGebJI-+86V z2`i()gSz_Fvu!<#Z@Txm!{M!uYfhS;*l2gKQn+^;&jrejG`@7_*;Ft8+`Q$f?-!)m zc$B|1?8r6cAMQ(%ac9(nb7>R$9B;0F+1qd6fa43w9aP3&=AXJ_oU{JEO}oAKwoLe4 zYG&@ONB+*O0{P1AZqsih#yi_=oAl$BS@R<`bH5)O;K#poEBGS53s4xp~N;?!M{P*Zf`dwv*nk{@rP+k1}Ht-*w~t&e751idwvMC^4yC=jYRn z%f3*Koa0N^Yn;6Q#l*m7Vs`f0dG3{~HUIcS+0>h#;@bNL2d=nY+1>p3%Py@~5Bq+t zq^=$JDa^RGv{~@mUiEbz4`WxqG`{=UtY=;26E`!-xaM@exp79#+e=NN&h2Q`+r_?d zQ`KM||J2nj9v`m;m5(^*Fv;Yl$?;he`z})QCh<>QtrNZ6q?27(WZtTv#z`GwcHNm6 zU?6UC%)GRsth4cn%a<=tuD$ZWirROS^c=qFvQc^KnM2=C+J5LnuYRxlWz{;EQ@AtV zbgBE27GJHG*R8WPE6XnM_Plk$iomLQ=d}S-Z(M0Q|ENI2Tt23)mnW)r{wGsmy+x|Q`xkaFP+@<#A@uC(fxHJ zZVU@Mu7k^^A|#A2i%J({D`ldXzIT3UB;hBOQL&R zJU;7m<>$q&G>qlpj_oPbdDWxf$!&LR>``cHY2(3rTPWMPp-;w9-Q^nxn;Dd>;&=1N zy(7Br*WE|?8S+g>)A#ku+3zzy^gv^`F0N;KCi@>!jtEn6=*H_i!3Qm$%(493t@5w8 zLtOhWQLX0iT?cd0BF9Zg&kU&YEB?0ov-7z-yQ`MV#iji-i=4};UccYy`J1+MN-kA( z-XP_B1%C8{Z7LVNZZ|Z!!s%0CoxWTw8#>CQ2)8N9IPhc0=F)wa>rK2pO?pQbZ{qf| zudG{ZXEI|R7q`Jhlk46!Ps5AIeWH|g=Ciao7I zJ?3Y~F)Puu_dkaR)G+rw9--P_8mvYu6SMfz{v8)P)a&)UhV`_iR=ej-C~D#CuU7oX zy(H|nR0Vgvp>bvqObU- zJ%vn?_TCXq*iUimv&uW z?>}$TwIW{Sb{+IH%3BhY0z%V89&g9pZx^!nKVLg|Lzq#mau=%aJCsnh3t!nj(JNwH zvFYD!9xS`vVB~{2ht0#93uBr+YFu?w%k8_Cgq>aa$-8x*R)eS7jpHBM*3BgEQ{651 zBWF!L@N%BF;u^4CS=)~+cLa+HY>e;UYA}85 zPGj!=n6b9!l?x?LgxKA-ay_hHS)#_va&Ohb&H3?GM>dVEHzcTgde1I>lGi+bZlBu`0LnS{7V^gUwpW;siyzM3|-UK{olrB&W~o*EN6sDy3XZ;!7)k zuB-d}RDmvrP_JO2c=ng?G>+jr*5leg!mE=l)vSv#(GyDg0?Dd)fNr4@lL zH|-8>{%-%^w$q5k4c5C9U#hIH&zF{W%*y%kYtxDp|7G@ORxgJBENr6O3FQ}F=4w)T zbM5oxhegaBs6KjnFXZUoKF)m8(v`Z~;V1jNTwiY8s zHFPZezJ2AMS+%*zZN`E^kz3~7w;Ne3u0`qDJMVT4-Z_2XR~~Nulbjvrhfcjx?fbSU zm+)7)Pf9NtXx4CT{GN6TQPRsE<;%Q`=B37$A56Hcd_`jXJbcK zg5m!pe5*NI{~lX=V#qsdUCF+)X6LSpP>w05g7~i4B}#2ta_!ciq2Hr=t_eMx_OU{u zar1uCT&yG>=hvF~_w;_t6(yhcu2EuH!qquosMsgA3(ud0)c-6ybz}f!IYTnyFrKvZ! zbIzD)^kmc(PwiQ;AE%~O&uj6-?&8^nou>Fe_f1W)!HlR$kzTBEOWAatwhLyZ; zl`eD0cFKtfXKq;kdfolBxW~%6TAA8x{Raqtc z^Rd;(e6VeD(0;;@yRoIVTt2&_iN9+1n8zEr&AH*1p!2i)>Z|zP`@Sc!L2SaPTYM#M ziVz5cvrCRg_sEwMJU4 zEEo+XCFpB#W<}niC3!Rz08Iy7$aCg54TCNRYkwd$gPIw46Cq_cq@s}H_VQJmmE|Iw z9F!o>ly?%CH`2fo9jd4vP247}gOfNO_W!hYuh+%FgYa{K_AWF|8=9B1z~A zSSjI(1&5G9Ppp^+U3k=2fVo5tn{aI`Q;7mc7%>#1qSR_9K)h>gAcq7Q2XzwQp-~wk z;ErS+g3f>}o9x<#@*BWZNK$fENM;E0>#8WRGqEQeN%;1wx)TLk8ynM4b!dTBq*{We zjzIMjQ9fe+s+dd-X-GU=)PX{sx)uCBNHb)vn&=R>&7T;P(h!ez@ogX z;bd4Rp5V($hC3G?wI^h%K1y+@tD@{mI3*;Jk%`kvDade9p${LCQllusp+G_CVkUS< zG?rRCjH}Svn6idL$Sw4{k}Vp-yiiO>br6+)^{V}d3$fxC!wWNjv8V>~y z)I&T0)gnQ5VmKk^s#V-UHz&lXXh|mbLu#MR3WND?Zsp_PnbfXlrE~o3@i(z0vQ)&1s9oVgMcj%tVB6q z5;m@?Hj%Vo(Z5nqf*30xTiIy*B-{%eH{nHLw=k$6BVen^ML@cKE?e-iD`+cIPIRYbEks^bgfxU%;M)9>V42Y3Rd!j?y9rwfpQRM+4x zYFbvGYtT$kVC8VabgDJ4ab;MJCAhjm-$Qg*)R-ywrlPPBN8nJcW{p_{p&xZ?EXyeB zhsITZZVK1Xjb+@lfdQ%jdbL*?<7 zm~3TT5SQSzp$P#*>lgfK;xcO8^B5Er2-#^sc%asZdIXVw^2MF--C)*)#zjmtlN{C3 zV_}VIYF`R4NSxNdM6eP)%zFTE64=QoYEUzo{0}4gKGDI2lNZExFU*5Ln+ledl2W{& znP(NmfAv}*HG0or)H%rP{IAgjSXch9npI%|NSK4*OM?{ zEZ?UMDNjE_Mk91YISdg{K$hL`V$F5>gbQmo!XikZ+;Q1<#>;^09D* zBnLr?1=xy@I5LJ8Diibo@c)V~u zfwO{h0+y}tq?F55xlkyV(!VIp3T9 z0(PdbwxC-S_RzeVF3=p+AJTJ~we|qd<~o12*BAnG@dBA0B({e|hcylmAt48?&Z|gtGu_ z!dMqT5%9Q>I|W7w4uUTUad4?q9E>Q_W0hd`*`Uxdr1lyo&swZf4Yvb%fk-S50UV6t zN-PsM(3XWv3t@q80}fC2%3*4-DC2y=^TBr3 zpxyA@s7DsMVc@3`{?Oh-iQK2eBvz>tPbyt^&5zb3mnbxm(w+j8Ax#0^U1$|xT7jm? zZvZ1@{2lKyLPxG@l>Y%?#UTc@4166@xfQVd@IaVs_#oihQ2SYkzTo{ak1S~~8mI~1wI%92PJqDpNE;MtZ_>q7DDuq^e|3*{2nW2kcxuZJ)+>ELK&XA zwaP<;dxo=MLVkmBX$>_oP>q#OW|$|J8r~|ecNRQ)wZ(}&gPkl)f(D=fjUv2aXbg@P zgpFFuaq*}^ml(^B(f`uCD|d%uU+4QW0_C82;6T_Bc!Yf$Fs|YD!{@|?z}}=aA>t}J z-Ffi|eNW}Ic|m7W)7+B19eKZK(rIA#6QoNxpFlsbzlFI3I0qO6xb(1S02bo}P+L_e z{=b7VS+QMe+t}ucO@Q@J>lE<+&shan8CuS_tYLA;qM!L|^C{+K&9sx{g4BgZ%{`)rw&oxW3d0#{|nHC5S;>OMiL>*?=YSp*=Txd z8q88SkrbZ`YBCR z4JZ`3#iX4fESY6K) zZePD!F{?w>+6+Bb<>zRZcNHso=tdBiN1%fxAze7Jfbas306Xx62m_80e)7B%ZL=VT4RHckpH$4OuGe z4y^JRI*zf{u*qawR7fEbp^gOtN5C1v-@{Id4lwbH4z|s?-rDz?*WXj|(;~**YJ+Bb z0YtLWXrK9jB<{k@xFC(q1(OdGkG{Xe`QoM_LM{eo9$+vL$>4UP5m&n6(09Y-$mzvm z+gvcRs2!JEXes(g`BVQO4KbT9r%jQsmaHJ(ALfvx0zxFd6+m}vS;Pte;9^eUnn;ERX?3z|#XvoV4wcoe~=zMv3lNo|Nfy{w75m@OR z*s4k9HXKRnn~wfjp_J%C(gV;$3Ju^%0?irXNp^^g9U*OT2)Q^}Eor?Y&h@9IY3caH z(f^!s+nzH*=flirqHvjOmba62kpClfQs!ffONkjE&S z(4b{R^rrO}Hm)4pvGV3yhhJ^TYdNdT;X0dqbzV&9r^AP2{TY20O1+V=D}frsx?o-x zDJxR2L3+Wku7Jo#Ft#m!fxrYe$~1Ys80C)w+a zIfFP>IT8ej@>tNBPU}t>x|_eh|Ig)$l_xxIQ^d(nH;lH9V1D9kWE?UUDy2GT<&k$M z<3VIqN%{m&LJpl2sS5^~9*%du5+hr?1y}2>bhw!C$f8n_GRK0av{+}hX0MkH`M^rf zvfVScd=cna z`;u_Fg8KtM9q1V#kb)0P-3M5mPIIF8PCrs<6=9t>_@&^xVx=Mc#CtItk>IARU;-Kd zQ3OH|o>fmdn$Yfc>))Rfo?TuX@!tH!(#saXIudGgmgr5%HH1by!Gr^ez92Z-IFiU8 z&<2ernP)6M4LuGI3*HDmt85;Q2sYT)Vt=1YH|O^|;rMGnV2NtRvE6<9c?as?N!3Mz zGzL{`J^aKe0JJ0gDT-TQ^hhHMmYI>zVvz`xxu&G3ohVZ7CCm1LQ{%b0(?8UX`sZoD zf!&Sj4{PMB18B7xi2amHjG|1EmIM|W5)!gaO}<39rNJ{rk_tg*2oy^4X`AebJN!96 z<8rMn4mte-KcqOdIoGy-`{0lO!b62j!lIXgijrPuE_<@C2v}k85TQz+^2eP+K-=8Q~XF<}* zYvnrj{E~3>aexjXP;GQg!=q!R3C%IFKC#B2- zBX70zKBkKeI5)_a;!;R9c>CV@HEB!9S{92RIsAS4;c4aeChZQL_R}E%sqXAUUk68K zFj+f)7-_7p(s{j;)KWuX}uouT{KB> zkcv)&+`cY7@V5RZ!}i{o$UXqH@cUP4lym%o{%vKgf)|L-=gsci;Zci32w506A0WU3g2OMsWt|CM4d0O zv-yi*F~<7lwp-${sGD8P%LaQ_{I&C~clTSsJ#QV@p;ge<9Jg>L9-bHomQ>sm&SySZ zfTD^pydi%Y*)UUP)J1&^nJ)C*^u3!}zImBfZA-oCZ;;Y2vDwsv`lHuJ=uq}UPxC-J zWKf<%CISyjISoF^aYEr0nFp`YXqXft%S`#fH%Mkk!U?4?H1UREY__HF`${>#YfNhB z^=$1wdmeu_n>yW3hg5uZXI2iAc{@?4(voag(IVLmOG}|I)D`?;BAA>Dz5q3FMXDEp z2^kcYx(^%Y9Fd9y@m8TTOR+jF*C!2lQy20$m+aT0)+MceS>-}8FiSmPHQ#Ea)iA5}R*qKImLDx|Sst*= zu$*ccYT3`SsbzUfLyPAY7cF*JEU`$i@UiG(QOBaF`FC@@`7!fM^I7K6=5FS#%4JIMbn~ZA~3atxP_c+%m~QHew3& z2z!|{GAU=G8b38YXS~gLk?|N~PvcI;wTufJeKpb<9YLld8Cr#dj9MAlL$UCk+SKs6 z;asN(|9gMN zKh(|kkYVR7j%AZiZR(TwsPyU+ZZFiH8GNZ>o_A7ajS24ycg-l?<4exrzSo~Cr+oNQ zgH8HJrETVa-8(Ap@LuyVC7$hjr&fq0oJjVpeMRq>?i*A5&58+cuk_1aU}ihVx)$G* zoZ_h+HLeGRIc9@y8#rR40LX?j+UL4;fFtabD6E%Lus^==dMQ|-I(P1B~Ia4mcC z_1vUS$3jb;SZ>)9eJV?i5|^f0Ju=J(h6_CRNBi?oN zeQ5b1uhdw}8h4db&-hZ(ZTCFS{ktQ6Oe=2mO4sn=-2Dd16sBopCmr)yKF9fzd%xh6 zH3t1mD^K3IM6Jq5+Uz7BmlBpXYVS6E!tOLr@Y+$+F;htm|Sc}{Dzla^ore+Qq#N^gZr{41P9zMvh?7^#HX{on#9JK z+nzHR#c1;E@ewtgEA6S2>~yjGko$$p$K2{=thPMMzmzcC=6ccXOV+AqVhg4CSvS3% zvrMfxh<|CU&z*+F7Y@4L<;!BX>chuf`&PJj_)WfPOi-r_X08jCKHF`2tEn1NdUtwX zC5aLI*`ouF*gAjNH>B9-(?LlSCT*SURnoH&4>!8eiL<)~X1>~Fk}%|CR%lvQgjMB4 zz7l_K)#5D%dAyinh4#LCyY%pD=j6LRx@MILb8ey}uIBNgvU*(64O8Rn z2i)w|xNmIeg0&ADFk>b=%I#^zT&I-v9v2;L)@O_?;+=C=tzggNg`ZD%e~}h6(P`bx zo+su#aj4NHP+86Z^z87iQ(HOBaQRx}=C<*J^^=av#|cjr=blW)(IZ;`bf2v8%rp=qxhGC?ryG{wAac1ZodhAFLwMJ>aeqw z@^rVj)XU1O+4)@C_{-}X#m`%sHpV4g`6(tTsB^?=pCA4s?+mimF#eH>af`gq`Q?D(sR=^pjU_ITiCAI~=h zxJUXMRy6JY?q2_XCsp^+N0RC(pX&2X{%0@51g}aPdS>I2B27!(tUDz;GlcPi+5UbN zn{6ynZ-4uNJ@$QZ@HLJ&xKGJm$m97t_bwOs;GcQ(Qa|;qWO}px_6PTrmm~R-UlXTN z(;XkqY`#xt)a+c#gl%1#sHM&Lr+l|>OI6xUNq<`0zMa{&#!-#|W)6q>iq9J78g<{a z?OSErsX?zMorxOT9wY@G&Zpdl%CknA4tY{N#Ju#V?iYWa?5cK3;!ED{eP8UKdN=*p z{8gvVnJvC@^p2wJJWs5ist!d=w zS1C}I@}*&qincsl?RLW*6PjEoSEuyvenw0k({9sd$1&XktbRBon^oqY8mg-{-OP5tw)akdT&p+!6796? zs*=k@mh7PempAn;RsP4oiA%SxPI~*I<<|~sQBxjoXyL8yVQX?5O#S-L_DeOgC)kwC ziniw~Lx#2q@M|_WJ0{%i`m1kw!ACxCtbK-mX-LJENrmm3RuB2}!asKUz%%{F`zW8% z_@=?lHl4it`^>Grk>%?Kf9gDc(i|60aVQQt?QlQnROZb?o7{{}=B?|L8*x$jS%b$L zv?Kgs>tR79llLV!?JH%TUUg4xW$STqsk7V2xhXSG-U;}p z_QGya?T}6_Djmpvbw#c5o5$(U-733w*w0t`{^(OUaO;PX zu8ysXS^8Zs+1LE~K(S5x>{*gI;O8OlT3?IRAH6s=wz`s_ocPX9-e>ipl2dM^l@9td zGbGyj=aer)jg$wI_)_m-2SOU2J|3Fo_G63Plf5|)9?ekhFjzmk*O0{PS$m#b$~)w1 zoALI?^494~l-YOqDSCA~l5#pJyPfT3!!4&mcADt<@l1RP6-!JZr*UMTGRD^lI^V3LQ8l!*L#^EhLw9=E;7ku?e`lV zIb|O$>d!o&Y}aLX)DByk9WVdOx{mH_dZ$tQCJzYV;d=ZN_M%sxdYP-jT=cHvN(}3> zKT5eI{Hq?p)sL=>uOHqs)cx?U*NY#_*`Ka95R0Nm(G5F#*8gzwUP^XqAA^eyvF9(T z4My{T-A@l0m0EGu#LcG~WFFh;*)qMer?SvnTx#Di&9#%Azunq*x-qvcoJP-hsNDa~ zkKQf2_Q%|J)=eGDhfWC34cXjf#gopApU>{*{l>%QVK=M(FG3;$kA!zEI;fM{-IK@b zR(}2QxEH%3mcC9nbLBy>f#;X5%Bhz;Ue~XqhLj52xA4xh(_3aO{Ay9T$klS@;>x|9 zuiWh29?YD(>QK?$cbl#$-DunsI{mq#?BqR#lRt#Z3)+pbZfhG zd~&dmeQ~jx+m*@f`eFZt8ztu#p5VCV_>_(2-o?)I7#-9o<9DMYXMelY7$&r>ndNfZo7l&fY4&|>LnEbK)kp{PNPX?U%{`>G)Wv+13 zTRe&O_x`;C)k^Q@+%AJ}m%D&4KfKtET$scAX){+91X4|);#+HkLhNpAYMp~}I={18pLeZI8Y z#`RU3xH9qEAp?>N! z^FhY~vby~|w66B%$(5MHl-;}MP<59-;6xOOOmB;_>@Yd8(>G$9M%m2;4 zXq#y|_rLj9|MOoHSTd4A zjF3JoYXG1pk4<-q0zC?)_}{XwiC-aQ86g|2^^9YnfB;dj6lRF0&#=?~_M{T|gbFf> zs)RO&gq19_66hWYC{3)0QBwlJC<^kT{(<}$_2gA9-xc6gF|S&G8mcx^hezDOy^~P6 zVwE+~vkV{wAeM#T3b#ZH7i~X8Pt`E)@~f6_gDDW;WHAF;gDugMPUOk)t7!ToiVUKo zDZU*QVg*WOgwX0?xi8W2IifsDMuMDFL~@qc7?5!ZggBlA z^CQcMI<9K@(l%4s_@9^Z|8svTKuvOQaic_(05H2)nE-!M`ye1bcv1N?7*MPRR`k+3 zYDkWjKMgVeAsfuQEiE67H;%{xfh7b~EL z;vuo-q4lTL1@gu2xIPAHD3rzG!T??ut0$awu|-}On-C~H2_0%=Q*em_s>di_R?+0H zPK>z8e}jM+FOVLn+Kb8(WZ$Tx49evA10<~_CPvAusBKbyd7ugKEV7i3LNwb5)ZCEN z23nB58gBGLR-Gk*d0fFs(lClZJMnL-W$Funo7z+zTuu4$AA^AQ+A4A@UiqWE*1(pS#54&FIL6M-Kr0RelY(LGYYIK@+}H50H04t!G{`NNI{8P$ zEXXpd{FhK#R}N0nK0k>S%9g=6kU~HOL)irfsQh#&nPF~Nrx3+C?z+MU6luc&3;^IR z>5_T-Dx2zPpPxp|xAPzw0+d7OIrIdEl#^%+CZP;M20WZQN8ly7VJ595z>k1F2n=mj zPOz@zK?0FGs%rrmCAzi%X-X>fz$p+~21q5~*I1ui9GI9=R6d2&q2MZ2JE?tkdPWU( zsTmaRSgjpYOb=j6mf(rVA)z=5T}M$EW~7#~WqwrBt|SUTxO`io!!ChASg}MJga3d} z2fHmvesXleY5+?lN|;zZ!M;k;1RPJ*X{plSG2y&(M4vkpa>wdFV`>SWlYCBr2jI^{D^)}J;jDnS2y70@ssun3V?pYe_XwRMFk;HHR+{rjj|aXF zfMLSmz>Pw&RzlJkCI%f|ta{=LVF5ApUSO2OK|_DV>t@0IBimhLt)V$=C`>QGz3G`G z+8ZW~3jJhac{J(e2)|8iOy&CXg8PRZG*&#*Trd6)O-2wEcp3^{0YfpDM5}h=xW^Po z#rIyKv7`LX*e7@jxbUC~lWHJtCz*A0!6JCB?CV5)HF85QrK(d=sfLsqgezh zoxmFs0*TcKuwNXlb7tVcP?3bDkn+cK3N=u%s+>5Vi;@9>w*9**3ZB7UxcfQMFs#VU<)`T zhy)AZGd{NnZa|Ji9hzwg{8k{>mQK*lB6@Y0L~^NwEewe9V>wvr4GcFAZ$*uXJW0G> zVi0|3XsyYf;41{gMBxDNQS@$tswy6m87xH6k|0wAU_cea0RhSidyX0$O3WI6b`8?P z-A{RWD(|*%D^vN=wO*(g9cEGzctj^F%oJQ4*jivlu&sf$0x|+J-E2zf1fl>7!2p&C zs6w!#zREenf(9Ay0pxQkv?J??Z z_}DPPu!O;SgLdj&HAXG0toy$ZWccfg5>#8_meJsudXIR=gp2k~P@iS84xDm=#tJBh zXE6BKB0d$NE<^YZCi%lI5>PofF`3igr)KqyQ0u-XJzv&Aki8H!v2E64?`~xoVco6gWwVza*PGlt ze5=aaYwvvZg$V5@P*w<-fbe0cHUO?Liuqm=i9rhSbMgprMf``h+L4HWkU4NsK0xnB zO|YN$@mutaI;r&wl}yMvyzI*GHsyWvw%pDMVg)b`!h;J8711fo4l}7W9=$*k@@wQX ztmMxU@)VpPBI-w*tqBUbQnppoJxxDO_)l6x*_q(0x2Cz1 zU8#^JAa1Sn4Dyl0fMQXMPrW~YIU{`o%$|llWNl>_6G`@fl{B#*H0)bm&3QkjKZ$cN zs^8sqZTu52y%jA44W=Vi*a>H@^`;~eTNF1M5D*}#7s@#jagd0$DJ4c5%yI|+@vwnI@f?ffoN)ib8ikKCx)gqQK(h-^%xFB$7yytpe1%ZHZToYXkY?};$SHjP`_QXOh0>-j{8_$VLR$a0 zh*9NF4%3@auPW}NhughDAq%E41SOh9mxWI#C^W*YVm`U-K_NV2#G3GBhj;5RxsZqJ z`x5bK5zZ?rexeS=01)>N;C*4wLm!wVgwL?h=5@CfT|^;l%UTFqJa3i ze%wr099Z4zdS6^R^`8w@X75OCd27dG`#RzE{PZTY6xg}aQhv-Dr3|Dv$GD4wXqh9K z2pZ8<0%W=$ZuGDT|uEdSlu^S~>yE57220 z7871Ij(UP#66sWW$eJcEu!D&4U_&74i_VMO!xrfRvMLm2w%SlCT4&ZquJrc=K?v4GO4uav01?i!#?JKM@S$e7f1OHjpxc9 zS?2n?t22t<-MGuJaliM=J>TzXKj)3F-jH5MdVk!I8c`3@vG@*@`ha7IBO6#u=*d(R zc|TYj>L!k8&KE&EMG0CPZwav+IrdZh=hfNo$8}GiJ9lIck6OJR@Ac6ekTn&MWtqX@ zsGqeb;1p6{Y1thMQ9Gq?xC>~0w2QNjlE;GP#aP(bO2!weCT>ht|GG`R(RkqC-?jG* z8KzfxO=Dt+fc`)gd>G990K6-`T15z$%cTWyAGCvpt%<3>^;z(BoLObYZL0bmy8cD@y=Pp8i z!UIdU3NPgyWdG^f)o&*X-e@6;3lA2p`Gc_t@Re-F^mC816W|TH6C}?V6jnJ=w-h zmtZvB7A?#WHG=2~R~{o`eu!5UMm@Qh6@>qU^D5giiXUZhjyU4|F~!C^?#5vEjHP!h z?mulDS+7fOoNcv1p1QH*Z?F^KEh)=%SR;9a=t#8S#B~n0zhonHzXFd5QgS-5BB<0fG!qQL5<+5*i#)j6p5Hxu1p1;EOB) z#}Y!nKoapq0R3zAkD@cX-}%%l&+JFnLDMW|zFJa0P&b-PKifr_?9ukT%-(3tP^BG) zvJrQ?fqBMv@$!XYhe&VBhCNbg;zD4lNyEz2&iK-G|Bjaz*Vx4LbDQR&8%5Y#UY6YA zUUR<$VGXlDeSNs!Gs1Evv_*mVf?Y^yGp3^>sKK`)B(bk=pJUO}N|&8q@p1O~9R^!l zuZ?ju^$*m=)9Xm0u3YqzD}x**salLgt2zn;1jXBr0&PiYmh~7l4;w=qui&wJ~q8%ZWiYqXN@)W%VI750Pd3N2(hy*2VU6RT*kV9U@p zVURdcsPW!`k(dY5eZJoNE8M&8J1F8sK&egHY2~L!`sm_lO9~yDFxi%nG?YN3r6tNm z3n>vHx~0qA7mG+EKktX{hrJ4`8W}h0KK^NcE>!oaI*{(Ru|Bjq?^chiG=2wA3^f4I*r0(6Rgx`%tND-lG;xPt*!B zOX_$yrg}z5kt3___VL%nFmsc*8Z>u5Bk)uvO4*M(qr#~+Edn6vKqCe$oxMmhwGwg>tN**CkID92A4cm!JB1~ zAQ2KHQ3Jme%xjPrxHUWQ+DyD9+5|gOnJZ$4wRD)ar1z`Gv1hMDz8e_&ZDxQjiaZ%q z5-1g-aVSM=2D}GCW-U!YJy%Fj0^L?2oWqn_cxKW1)mpg`+TU85Sm^>U$^xo=jzIkMhla}bP?2hHDCKFKl~;VL;+eiB~-MN9xg2^ zlSl~Q<6z-yf<*aja9OE@ZMr@3dSXncX8qRae+~FH-fG#mJ~fZ}>B8yNh*?YtKpL0= zYOz}3!N7^g-&v9kC%>uIpNC5fT6m1wWpH%Bh%57+Hn+^4ek!-E@#CV~D*hP#F4<2P zMn5EyKJYzZ{jqh4+omz2I2DZI0FlEut)HKdlw=G#zrlzjWjkr0(oR;mv}O2x$Ad2{ zZ?$^fHqb4(QbTWDC>?F^jLBQZ@hMD}=&MVf4jM7y$l($$We#PkNU~ixSIW)@SrO51 zX1e})X7O{iT6MT}viP!x2G%>%;0=>tO3* zR>!Tvt;$=TwT!haW^uwI#-fV(W%F_7wagxx%`)q3`rUN3=>YY&sbaFzWVnf~agK3> zab=^cM&pg@D?w1wpJmv_;H|+@gD%ho+^=x@MzCpRRfCHyzW%6xqy8dG>BxOhUkJhR zklHbtcoZkAXm!T=s%_GcW?1xpA=U7IIwYqj#cPUlROE)yb^+S~nId>|$ahK3u+|4! zQb%N@Ddan%a5#bF8W(2vg6qU$OzaKnJtI`wgxAZdMA3#+Qc|@ZGZ>Eykd!KrF1{=p zsGaw7)>sUy5tjmp&<4FUDr^d_k+6BpZj#eS*C zYY?^&*#uH7lEqsT->7G~bTaMCI9V=7gmWbe_4%fTb$LWhnB~T3&x;O4=*|i=4O2z@ z?ig=+N{j4&8;w2ySk5pmqRS9mZ_F(#gK3J4Xr-ogJmLCDzm~!ub`_qenqySM#~*?DukCwUA{+za&cWO8cz9g@;3_E3Cy)< zT|%wZ;2ons9oxQ0BQ2M3D7wjBDL9WT*yUP~G zh#)W&Od|e;F4?SB0sDq(BSn|kSjzfG1Q_YuyomLdf zn%~!x5hs4+$O%7Kv`fQkM6^Qth&?=bXq*usAyPzyQ2Gi)griJuh&oagITU!hocn@p zgwH96&e7C43Yi$_gi>9R&p@F8f(;C{6if)IYl+|j)vsXXn@fSomu(P1IVq8V}t(%bqW`G;wNi-y>U+Tx)d9ae;>5pv^1 zobW881pk$sb0)qC^1a6VLnBG_HF{8B4#hd>Pf<4NG)T>eY^o=UV^}DZwu6gAIWC$E zR$lT)P7aR~1FE0$ajESVu3w$GlMYP_q zWl6pzN)~Oj7Tnm30Z_7ocOyiNun&BFT^n>X8gDM=Q7UP1m7Go=jEP$~-l9g8T=C;k zq98&>|1VJ=%HfV&-w;nF&=oR8E>H=05m7p1sX)!UYF|VKFAM2q5`W_AX=&?6|jCXlGIZEnhA!pj`^k}Gn1c@!(zoP znHhi~$AfS-1HBTJ)FlOMf~4>Z*+Q9i$uC$FHiaw}$}_&7}`QxZ^oW#pG| zS6JdsW>DG83Xw>RN0nRN@nRpAbShw?5&WQ=CPm|PB51J%EGm}>qTwzx&s&ZqvKvxA zM(C|6)7nFvrLA1cS~?%Oe}TFZZCqigkXnq=im002g_a(}HOXZT3qb@x>O?|Onltvw z&;A8`M49gptva!=VLW-OATtA-jE~N0FftpAua4v?geVx^fRs5b2&Jt8y^Zeu+nCS? zMV?SpX9X3QbDok_Ew-S@9%@5MJrUj$GIbOCroes_8c3Kb0$_@su%aKm2I(Sr7WTOi zSk&MQl!T*lzG!T!JpWt3mWlGNa0A)YXh0KW|0|9n>1!WyB#>j1RvK zk=XX}z6A~~Q4&Gw9DfbDQNkr)2g8fvdOer1Wqug%Kwn17g8SjUSV>B4})TUvMjt8NlD+PY-%N`OJK-Vsa_H193u(#i

  • C2FV=bSXjpdyS@CfV`d4oeO5?DKJ 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