Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiplatform - Native #162

Closed

Conversation

luca992
Copy link
Contributor

@luca992 luca992 commented Feb 10, 2023

Added my native implementation targeting all tier 1-3 native targets.

Personally I don't agree with having a NATIVE_TARGETS_ENABLED toggle. But up to you.

Not sure why detekt fails for Pbkdf2Sha512 in the nonJvmMain due to Pbkdf2Sha512.F:

> Task :detektAll FAILED
/Users/lucaspinazzola/Projects/kotlin-bip39/bip39-lib/src/nonJvmMain/kotlin/cash/z/ecc/android/crypto/Pbkdf2Sha512.kt:54:17: Function names should match the pattern: [a-z][a-zA-Z0-9]* [FunctionNaming]

But succeeds in commonMain. I renamed both to Pbkdf2Sha512.f for consistency

Edit: linuxArm64 target wasn't added due to okio not supporting it.

@luca992 luca992 force-pushed the multiplatform-native branch 2 times, most recently from ea7f8e8 to 6b7090c Compare February 10, 2023 16:52
Copy link
Contributor

@ccjernigan ccjernigan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the effort that went into creating this PR. The changes are clear, and it is nice to have various native targets. I appreciate the care that went into generating secure random data in platform-specific ways.

I've looked at this from the general Kotlin/Gradle code review, clarity, maintainability, etc. perspective, and there are some questions inline. Another member of our team will be looking at this from a security perspective, and will chime in over the next week or so.

From my perspective, the main thing that we need to figure out is how we can reduce any third party dependency (e.g. Okio). It's great that we don't introduce dependencies in the Android targets, and I hope we can build on that.

I added a followup issue for CI jobs for other platforms, as I see that as complementary but out of scope for this initial PR. Relatedly, we may want to explicitly disable native release publishing until we get the CI jobs properly set up (we'll leave the publishing on for snapshots). That would mean adding
ORG_GRADLE_PROJECT_NATIVE_ENABLED: false
here.

bU[s.size + j] = (i shr (24 - 8 * j)).toByte()
}

val uXor = bU.toByteString().hmacSha512(key).toByteArray()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What options do we have for hmacSha512 that doesn't rely on Okio?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


import okio.ByteString.Companion.toByteString

internal actual fun ByteArray.toSha256(): ByteArray = this.toByteString().sha256().toByteArray()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What options to we have to implement sha256 without Okio (or other third party dependencies)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal var cachedList = WordList()

@Suppress("VARIABLE_IN_SINGLETON_WITHOUT_THREAD_LOCAL")
var cachedList = WordList()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we make this a val, which might help avoid the need for a @Suppress?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that would break the actual purpose of it being a var:

   var cachedList = WordList()

    fun getCachedWords(languageCode: String): List<String> {
        if (cachedList.languageCode != languageCode) {
            cachedList = WordList(languageCode)
        }
        return cachedList.words
    }

... I think I removed internal by accident. I'll fix that. However it could also be made private. It's not used anywhere else.

Copy link
Contributor Author

@luca992 luca992 Feb 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also the suppress isn't technically needed. But without it, it will print a bunch of info level logs while building saying:
With old Native GC, variable in singleton without @ThreadLocal can't be changed after initialization

Which is just annoying considering kotlin uses the new GC by default now

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks—reviewing just on GitHub hides some of the important context!

bip39-lib/build.gradle.kts Outdated Show resolved Hide resolved
@luca992
Copy link
Contributor Author

luca992 commented Feb 11, 2023

these are okios implementations. I guess they could be copied. I don't necessarily agree with the no dependencies rule you have, as java's standard library is pretty massive (essentially they already bundle things that would be a dependency in like any other language).

Plus rolling your own crypto is something I avoid; I guess if you have the team to maintain it that works. But at that point honestly I would make a new library or module and import it into this project. That way other people can at least benefit from the code if you plan on maintaining it. If every project made everything self contained it would lead to tons of code duplication. I do get you want to keep it as concise as possible though.

actual class SecureRandom {
actual fun nextBytes(bytes: ByteArray) {
val result = bytes.usePinned {
@Suppress("UNCHECKED_CAST")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What errors occur if this @Suppress is removed? Are there consequences to making the value nullable instead of suppressing the unchecked cast?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

> Task :bip39-lib:compileKotlinMingwX64 FAILED
e: warnings found and -Werror specified
w: file:///Users/lucaspinazzola/Projects/kotlin-bip39/bip39-lib/src/mingwMain/kotlin/cash/z/ecc/android/random/SecureRandom.kt:17:33 Unchecked cast: CPointer<ByteVar /* = ByteVarOf<Byte> */> to CPointer<UByteVar /* = UByteVarOf<UByte> */>

Execution failed for task ':bip39-lib:compileKotlinMingwX64'.
> Compilation finished with errors

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's supposed to be a UByte not a Byte for mingw. The suppress isn't for null-ablity

@luca992
Copy link
Contributor Author

luca992 commented Feb 21, 2023

Turns out the native tests weren't running at all. Fixed that by adding and applying the kotest plugin.

And that led me to find out there was an issue with the require statement in the native nextBytes implementation. Had to change the check: check(result < 0) -> check(result >= 0)

Also, I updated gradle to 8.0.1

@luca992
Copy link
Contributor Author

luca992 commented Feb 21, 2023

I think I've fixed everything that needs to be fixed. Up to you guys on how you want to handle hmacSha512 and sha256

Copy link
Contributor

@ccjernigan ccjernigan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for these enhancements. I have a few minor comments inline, and I'm about to trigger the CI build.

gradle/wrapper/gradle-wrapper.properties Outdated Show resolved Hide resolved
}
return ByteArray(dkLenBytes).apply {
baos.read(this)
val dkLenBytes = dkLen / 8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for rolling back use of Okio here. This minimizes the surface area of using third parties, and will make it easier to swap out Okio with something else in the future since its now just the hashing functions.

internal var cachedList = WordList()

@Suppress("VARIABLE_IN_SINGLETON_WITHOUT_THREAD_LOCAL")
var cachedList = WordList()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks—reviewing just on GitHub hides some of the important context!

@luca992
Copy link
Contributor Author

luca992 commented Feb 22, 2023

found another option for the hashing:
https://github.com/korlibs/korge/tree/main/krypto

@ccjernigan
Copy link
Contributor

@luca992 Thanks for your patience. We are still having internal security auditing discussions, and we hope to wrap those up soon. As a preview, this where I think we will land:

  • Merge this PR as-is (I might ask to tweak the readme slightly because it's explicit about no dependencies which doesn't hold true with this PR). This PR moves our multiplatform efforts forward in a positive way in the codebase, while native deployments are still initially disabled.
  • Create a followup issue to swap out Okio. We're leaning towards copying the necessary hashing functions from Okio. Then we can own and audit those as our own implementation going forward.
  • Enable deployment of native targets on CI

No action items yet, just keeping you in the loop as we're making decisions internally.

@ccjernigan
Copy link
Contributor

@luca992 Although it took a while, I got internal approval for the plan discussed above #162 (comment)

Once we resolve the conflicts, then I can merge this PR 🎉

@luca992
Copy link
Contributor Author

luca992 commented May 11, 2023

@ccjernigan I updated kotlin and all dependencies. If you don't want that lmk and I'll revert it

@ccjernigan
Copy link
Contributor

@ccjernigan I updated kotlin and all dependencies. If you don't want that lmk and I'll revert it

We don't mind updating dependencies, although I would recommend a separate PR for the dependencies. When I merge this PR, it will be squashed into a single commit. For dependencies, Kotlin 1.8.21 is good to go, and we've updated our other repos as well. (We're sticking with Gradle 7.6 until we resolve an issue with the Rust integration in another project though).

We still have merge conflicts with this PR—I think the WordList changes from the main branch need to be brought in which incorporates a significant performance improvement.

Copy link
Contributor

@ccjernigan ccjernigan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the effort that went into this PR, as well as your patience during our review process. This brings some great enhancements for the project and the ecosystem.

@ccjernigan ccjernigan mentioned this pull request May 18, 2023
9 tasks
@ccjernigan
Copy link
Contributor

I created a new PR that incorporated these changes while resolving the remaining conflict. These changes have been merged. See #188

@ccjernigan ccjernigan closed this May 18, 2023
@luca992
Copy link
Contributor Author

luca992 commented May 18, 2023

Np. Now I'll make that last pr for JS 👌

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants