GithubHelp home page GithubHelp logo

algorandfoundation / bip32-ed25519-kotlin Goto Github PK

View Code? Open in Web Editor NEW
0.0 4.0 1.0 3.11 MB

Kotlin implementation of BIP32-Ed25519 Hierarchical Deterministic Keys over a Non-linear Keyspace for Algorand's ARC-52

License: Apache License 2.0

Kotlin 99.17% Shell 0.83%

bip32-ed25519-kotlin's Introduction

BIP32-ED25519-Kotlin

A Kotlin implementation of ARC-0052 Algorand, in accordance with the paper BIP32-Ed25519 Hierarchical Deterministic Keys over a Non-linear Keyspace.

Note that this library has not undergone audit and is not recommended for production use.

Currently there are two folders, one to create an .aar file for use in Android, another to prepare .jar files for use in standard Kotlin desktop use-cases.

Requirements

Requirement Version
Kotlin 1.9.22
Gradle 8.6
Android 26
Java 17

Installation

This library uses a forked version of LazySodium-Java that exposes Ed25519 operations. The fork has been added as a Git Submodule. It needs to be initialized, built and have its .jar files moved into lib/libs at the root level of this repository.

./initialize.sh

Running this will create a build/ folder containing the Bip32Ed25519-Android...aar and Bip32Ed25519-JVM-...jar files respectively. These can be imported in your project.

Note: For Linux Users

LazySodium-Java bundles up the LibSodium binaries and expose them as part of the .jar files. By default this library will attempt to use the bundled up binaries, but falls back to any system-wide installation of LibSodium.

Currently there is an issue on Linux that means Linux-users will need to rely on a system-wide installation of LibSodium.

For Debian-based Linux distros:

apt-get install -y libsodium-dev

You might be required to install using sudo privileges.

CI/CD

Github Workflows are used. ./initialize.shis called, which attempts to build the Android and Desktop libraries and output .aar respectively .jar files. By default ./gradlew build also runs ./gradlew test.

How to Use

Below we refer to Bip32Ed25519JVM but the same examples work for Bip32Ed25519Android.

Initialize an instance of Bip32Ed25519JVM with seed bytes:

    val alice = Bip32Ed25519JVM(seedBytes)

Consider using a BIP-39 compatible library like cash.z.ecc.android:kotlin-bip39 to use a seed phrase instead:

    val seed = MnemonicCode(
                "salon zoo engage submit smile frost later decide wing sight chaos renew lizard rely canal coral scene hobby scare step bus leaf tobacco slice".toCharArray())
    val alice = Bip32Ed25519JVM(seed.toSeed())

Obviously do NOT make use of that seed phrase!

ARC-0052 follows BIP-44 derivation paths, which havve the following fields:

  • Standard: 44', for BIP-44. Cannot be set manually.
  • Coin-type: Which blockchain? 283' corresponds to an Algorand address, 0' corresponds to identity. We represent this with the KeyContext enum.
  • Account: Increment to separate the seed into "accounts".
  • Change: Typically 0, but see the notes below.
  • Index: Index to increment

Regarding the apostrophes - this is referred to as "hardening", setting the top bit. 44' thus refers to 44 + 2^32.

Regarding coin-type 0': This is typically also reserved for Bitcoin, but the W3C has also granted it to identity. Note that in practice, as this is an Ed25519-based implementation, and Bitcoin relies on secp256k1, it is not possible to use this library to inadvertently generate Bitcoin-compatible addresses when generating identity addresses.

Regarding change: the original purpose was to set for "external" (= 0) and "internal" (= 1), i.e. whether the address is meant to be visible outside the wallet or not (to receive payments) or to receive change. This is relevant for the UTXO-model of chains.

In Algorand however, there is an opportunity to assign change values to specific apps that require generation of one-time keys. E.g., a private DAO voting tool using ring signatures will need to generate a new key pair and submit the PK for each vote. The DAO could choose to claim any random number between 0 - (2^32-1), so that the wallet will set that number for the change field. This would be helpful if a user loads the seed into a new wallet and wishes to interact with the same private DAO voting tool - for discovery and displaying of past voting, as well as to avoid accidentally generating and submitting the same keypair as previous.

Get publicKey

Consider the derivation path m'/44'/283'/0'/0/0. This corresponds to:

    val publicKey = alice.keyGen(KeyContext.Address, 0u, 0u, 0u)

This returns the public key.

Sign arbitrary data

The user might wish to sign a 32 byte nonce:

    val nonce = """
        {"0": 255, "1": 103, "2": 26, "3": 222, "4": 7, "5": 86, "6": 55, "7": 95,
         "8": 197, "9": 179, "10": 249, "11": 252, "12": 232, "13": 252, "14": 176,
         "15": 39, "16": 112, "17": 131, "18": 52, "19": 63, "20": 212, "21": 58,
         "22": 226, "23": 89, "24": 64, "25": 94, "26": 23, "27": 91, "28": 128,
        "29": 143, "30": 123, "31": 27}""" .trimIndent().toByteArray()

    val publicKey = alice.keyGen(KeyContext.Address, 0u, 0u, 0u)

    val authSchema =
                    JSONSchema.parseFile("src/test/resources/auth.request.json")

    val metadata = SignMetadata(Encoding.NONE, authSchema)

    val signature = alice.signData(KeyContext.Address, 0u, 0u, 0u, nonce, metadata)

    assert(alice.verifyWithPublicKey(signature, nonce, pk))

ECDH

The user might also wish to generate a shared secret with someone else:

Alice's PoV:

    val aliceKey = alice.keyGen(KeyContext.Address, 0u, 0u, 0u)
    val sharedSecret = alice.ECDH(KeyContext.Address, 0u, 0u, 0u, bobKey, true)

Bob's PoV:

    val bobKey = bob.keyGen(KeyContext.Address, 0u, 0u, 0u)
    val sharedSecret = bob.ECDH(KeyContext.Address, 0u, 0u, 0u, aliceKey, false)

Underneath the hood the Ed25519 PKs are turned into Curve25519 format.

Note that ECDH involves hashing a concatenation of a shared point with Alice's and Bob's respective public keys (converted to Curve25519). They'll need to agree before-hand whose public key should go first in this concatenation.

They can then use this shared secret for encrypting data.

Signing An Algorand Transaction

Alice can turn her public key into an Algorand encoded address using encodeAddress from utils:

val alicePK = alice.keyGen(KeyContext.Address, 0u, 0u, 0u)
val aliceAddress = encodeAddress(alicePK)
println("Alice's Algorand Address: ${aliceAddress.toString()}")

Assume that it gets funded by someone. Alice can then start signing her own transactions sending some algoAmount to some receiver:

// Let's have Alice send a tx!

// Construct the TX:
val tx =
        Transaction.PaymentTransactionBuilder()
                .lookupParams(algod) // lookup fee, firstValid, lastValid
                .sender(aliceAddress)
                .receiver(receiver)
                .amount(algoAmount)
                .noteUTF8("Keep the change!")
                .build()

// Sign the TX with this library:
val sig = alice.signAlgoTransaction(
                            KeyContext.Address,
                            0u,
                            0u,
                            0u,
                            tx.bytesToSign()
                    )

// Turn the sig into Algorand SDK's Signature type
val txSig = Signature(sig)

// Combine the sig with the constructed TX using SDK's SignedTransaction()
val stx = SignedTransaction(tx, txSig, tx.txID())

// Encode to msgpack bytes
val stxBytes = Encoder.encodeToMsgPack(stx)

// Post the SignedTransaction msgpack bytes to AlgoD
val post = algod.RawTransaction().rawtxn(stxBytes).execute()

if (!post.isSuccessful) { // Check if there was an issue
    throw RuntimeException("Failed to post transaction")
}

// We confirmed that Algod received the signed transaction. It has begun to propogate it to the network.

// We wait for confirmation:
var done = false
while (!done) {
    val txInfo = algod.PendingTransactionInformation(post.body()?.txId).execute()
    if (!txInfo.isSuccessful) {
        throw RuntimeException("Failed to check on tx progress")
    }
    if (txInfo.body()?.confirmedRound != null) {
        done = true
    }
}

println("Transaction ID: ${post.body()?.txId}")

Running tests

This Kotlin project uses Gradle.

./gradlew test

The command above will run tests expected to work without access to an Algorand Localnet/Sandbox running at localhost.

The following command however will run additional tests (testing signAlgoTransaction()) that involve making actual on-chain transactions.

./gradlew testWithAlgorandSandbox

Using algokit, run the following each time to reset account balances:

algokit localnet reset && ./gradlew testWithAlgorandSandbox

License

Copyright 2024 Algorand Foundation

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.


bip32-ed25519-kotlin's People

Contributors

hashmapsdata2value avatar

Watchers

 avatar  avatar  avatar  avatar

bip32-ed25519-kotlin's Issues

feat: Regenerate LibSodium binaries

Before going to production, we need to generate the LibSodium binaries ourselves, rather than relying on. LazySodium's pre-generated ones.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.