-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
158 additions
and
1 deletion.
There are no files selected for viewing
33 changes: 33 additions & 0 deletions
33
blockchain/src/main/java/com/tangem/blockchain/blockchains/casper/CasperAddressService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package com.tangem.blockchain.blockchains.casper | ||
|
||
import com.tangem.blockchain.blockchains.casper.cashaddr.CasperAddressUtils.checksum | ||
import com.tangem.blockchain.common.address.AddressService | ||
import com.tangem.blockchain.extensions.isSameCase | ||
import com.tangem.common.card.EllipticCurve | ||
import com.tangem.common.extensions.hexToBytes | ||
|
||
internal class CasperAddressService : AddressService() { | ||
override fun makeAddress(walletPublicKey: ByteArray, curve: EllipticCurve?): String { | ||
val prefix = CasperConstants.getAddressPrefix(curve!!) | ||
val bytes = prefix.hexToBytes() + walletPublicKey | ||
return bytes.checksum() | ||
} | ||
|
||
override fun validate(address: String): Boolean { | ||
val isCorrectEd25519Address = address.length == CasperConstants.ED25519_LENGTH && | ||
address.startsWith(CasperConstants.ED25519_PREFIX) | ||
val isCorrectSecp256k1Address = address.length == CasperConstants.SECP256K1_LENGTH && | ||
address.startsWith(CasperConstants.SECP256K1_PREFIX) | ||
|
||
if (!isCorrectEd25519Address && !isCorrectSecp256k1Address) { | ||
return false | ||
} | ||
|
||
// don't check checksum if it's not mixed case | ||
if (address.isSameCase()) { | ||
return true | ||
} | ||
|
||
return address == address.hexToBytes().checksum() | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
blockchain/src/main/java/com/tangem/blockchain/blockchains/casper/CasperConstants.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package com.tangem.blockchain.blockchains.casper | ||
|
||
import com.tangem.common.card.EllipticCurve | ||
|
||
internal object CasperConstants { | ||
const val ED25519_PREFIX = "01" | ||
const val ED25519_LENGTH = 66 | ||
|
||
const val SECP256K1_PREFIX = "02" | ||
const val SECP256K1_LENGTH = 68 | ||
|
||
fun getAddressPrefix(curve: EllipticCurve) = when (curve) { | ||
EllipticCurve.Ed25519, | ||
EllipticCurve.Ed25519Slip0010, | ||
-> ED25519_PREFIX | ||
EllipticCurve.Secp256k1, | ||
-> SECP256K1_PREFIX | ||
// added as unsupported for now, need to research | ||
EllipticCurve.Secp256r1, | ||
EllipticCurve.Bls12381G2, | ||
EllipticCurve.Bls12381G2Aug, | ||
EllipticCurve.Bls12381G2Pop, | ||
EllipticCurve.Bip0340, | ||
-> error("${curve.curve} is not supported") | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
...ain/src/main/java/com/tangem/blockchain/blockchains/casper/cashaddr/CasperAddressUtils.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package com.tangem.blockchain.blockchains.casper.cashaddr | ||
|
||
import org.bouncycastle.jcajce.provider.digest.Blake2b | ||
|
||
/** | ||
* @see <a href="https://github.com/casper-ecosystem/casper-js-sdk/blob/dev/src/lib/ChecksummedHex.ts">Source</a> | ||
*/ | ||
object CasperAddressUtils { | ||
// Ed25519: encode([0x01]) + encode(<public key bytes>) | ||
// or | ||
// Secp256k1: encode([0x02]) + encode(<public key bytes>) | ||
fun ByteArray.checksum(): String = encode(byteArrayOf(first())) + encode(drop(1).toByteArray()) | ||
|
||
// Separate bytes inside ByteArray to nibbles | ||
// E.g. [0x01, 0x55, 0xFF, ...] -> [0x00, 0x01, 0x50, 0x05, 0xF0, 0x0F, ...] | ||
@Suppress("MagicNumber") | ||
private fun bytesToNibbles(bytes: ByteArray): ByteArray = bytes | ||
.flatMap { byte -> | ||
listOf( | ||
(byte.toInt() and 0xff shr 4).toByte(), | ||
(byte.toInt() and 0x0f).toByte(), | ||
) | ||
}.toByteArray() | ||
|
||
// Separate bytes inside ByteArray to bits array | ||
// E.g. [0x01, ...] -> [false, false, false, false, false, false, false, true, ...] | ||
// E.g. [0xAA, ...] -> [true, false, true, false, true, false, true, false, ...] | ||
@Suppress("MagicNumber") | ||
private fun ByteArray.toBitArray(): BooleanArray = this | ||
.flatMap { byte -> | ||
List(8) { i -> | ||
byte.toInt() shr i and 0x01 == 0x01 | ||
} | ||
}.toBooleanArray() | ||
|
||
private fun byteHash(bytes: ByteArray): ByteArray = Blake2b.Blake2b256().digest(bytes) | ||
|
||
private fun encode(input: ByteArray): String { | ||
val inputNibbles = bytesToNibbles(input) | ||
val hash = byteHash(input) | ||
val hashBits = hash.toBitArray() | ||
val hashBitsValues = hashBits.iterator() | ||
return inputNibbles.fold(StringBuilder()) { accum, nibble -> | ||
val c = "%x".format(nibble) | ||
|
||
if (Regex("^[a-zA-Z()]+\$").matches(c) && hashBitsValues.hasNext() && hashBitsValues.next()) { | ||
accum.append(c.uppercase()) | ||
} else { | ||
accum.append(c.lowercase()) | ||
} | ||
accum | ||
}.toString() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
blockchain/src/test/java/com/tangem/blockchain/blockchains/casper/CasperAddressTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package com.tangem.blockchain.blockchains.casper | ||
|
||
import com.google.common.truth.Truth | ||
import com.tangem.common.card.EllipticCurve | ||
import com.tangem.common.extensions.hexToBytes | ||
import org.junit.Test | ||
|
||
class CasperAddressTest { | ||
|
||
private val addressService = CasperAddressService() | ||
|
||
@Test | ||
fun makeAddressFromCorrectEd25519PublicKey() { | ||
val walletPublicKey = "98C07D7E72D89A681D7227A7AF8A6FD5F22FE0105C8741D55A95DF415454B82E".hexToBytes() | ||
val expected = "0198c07D7e72D89A681d7227a7Af8A6fd5F22fe0105c8741d55A95dF415454b82E" | ||
|
||
Truth.assertThat(addressService.makeAddress(walletPublicKey, EllipticCurve.Ed25519)).isEqualTo(expected) | ||
} | ||
|
||
@Test | ||
fun validateCorrectEd25519Address() { | ||
val address = "0198c07D7e72D89A681d7227a7Af8A6fd5F22fe0105c8741d55A95dF415454b82E" | ||
|
||
Truth.assertThat(addressService.validate(address)).isTrue() | ||
} | ||
|
||
@Test | ||
fun makeAddressFromCorrectSecp256k1PublicKey() { | ||
val walletPublicKey = "021F997DFBBFD32817C0E110EAEE26BCBD2BB70B4640C515D9721C9664312EACD8".hexToBytes() | ||
val expected = "02021f997DfbbFd32817C0E110EAeE26BCbD2BB70b4640C515D9721c9664312eaCd8" | ||
|
||
Truth.assertThat(addressService.makeAddress(walletPublicKey, EllipticCurve.Secp256k1)).isEqualTo(expected) | ||
} | ||
|
||
@Test | ||
fun validateCorrectSecp256k1Address() { | ||
val address = "02021f997DfbbFd32817C0E110EAeE26BCbD2BB70b4640C515D9721c9664312eaCd8" | ||
|
||
Truth.assertThat(addressService.validate(address)).isTrue() | ||
} | ||
} |