API-style comments and ktlint formatting
Every comment is reduced to its first sentence plus @param, @return and @throws tags. Comment lines: 20040 to 8526. ktlint 1.8 applied with its default (official Kotlin) style across all modules; 262 files reformatted. The 264 remaining ktlint findings are naming and filename rules, which are code changes, and are left alone. Comments and formatting only. 1208 tests pass, counted from the XML. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
8a7f40fb13
commit
2fa430f21c
@@ -9,27 +9,19 @@ import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* From a recovery phrase to an address somebody can be paid at.
|
||||
*
|
||||
* The property that matters most here is that the signing key and the
|
||||
* displayed address come from the same derivation. If they ever disagreed,
|
||||
* the wallet would sign with a key that does not own the address it showed. The
|
||||
* transaction being rejected is the GOOD outcome; the bad one is signing for an
|
||||
* address somebody else controls.
|
||||
*/
|
||||
class HdAccountTest {
|
||||
|
||||
private val words by lazy { Bip39.englishWordlist() }
|
||||
private val phrase by lazy { Bip39.phrase(ByteArray(16), words) }
|
||||
private val seed by lazy { Bip39.seed(phrase) }
|
||||
|
||||
private fun account() = HdAccount(seed)
|
||||
|
||||
// ---- The path --------------------------------------------------------
|
||||
// ---- The path --------------------------------------------------------.
|
||||
|
||||
/**
|
||||
* BIP-44 with every level hardened, which is what this curve requires
|
||||
* and what every other Solana wallet uses. A different shape here derives
|
||||
* addresses nobody else can reproduce from the same phrase.
|
||||
* BIP-44 with every level hardened, which is what this curve requires and
|
||||
* what every other Solana wallet uses.
|
||||
*/
|
||||
@Test
|
||||
fun `the derivation path is the standard Solana shape`() {
|
||||
@@ -38,11 +30,10 @@ class HdAccountTest {
|
||||
assertEquals("m/44H/501H/3H/0H/0H", HdAccount(seed, account = 3).path(0))
|
||||
}
|
||||
|
||||
// ---- The property that guards the money ------------------------------
|
||||
// ---- The property that guards the money ------------------------------.
|
||||
|
||||
/**
|
||||
* The signing key owns the address that was displayed. Everything else
|
||||
* in the wallet rests on this one line being true.
|
||||
* The signing key owns the address that was displayed.
|
||||
*/
|
||||
@Test
|
||||
fun `the signing key owns the address that was shown`() {
|
||||
@@ -51,8 +42,11 @@ class HdAccountTest {
|
||||
val shown = account.receive(index)
|
||||
val signing = account.signingSeed(index)
|
||||
val fromSigningKey = Base58.encode(Ed25519.fromSeed(signing).publicKey)
|
||||
assertEquals(shown.address, fromSigningKey,
|
||||
"index $index would sign with a key that does not own its address")
|
||||
assertEquals(
|
||||
shown.address,
|
||||
fromSigningKey,
|
||||
"index $index would sign with a key that does not own its address",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,20 +59,17 @@ class HdAccountTest {
|
||||
val message = "a transaction".toByteArray()
|
||||
val signature = Ed25519.sign(seedAt0, message)
|
||||
val publicKey = Base58.decode(shown.address)!!
|
||||
// `(publicKey, signature, message)`. All three are ByteArray, so
|
||||
// getting the order wrong compiles perfectly and fails at runtime,
|
||||
// which is what happened writing this test, and is a good argument for
|
||||
// the assertion existing at all.
|
||||
assertTrue(Ed25519.verify(publicKey, signature, message),
|
||||
"the signature does not verify under the address it was made for")
|
||||
// `(publicKey, signature, message)`.
|
||||
assertTrue(
|
||||
Ed25519.verify(publicKey, signature, message),
|
||||
"the signature does not verify under the address it was made for",
|
||||
)
|
||||
}
|
||||
|
||||
// ---- Fresh addresses -------------------------------------------------
|
||||
// ---- Fresh addresses -------------------------------------------------.
|
||||
|
||||
/**
|
||||
* A new address per receive is the point. Reusing one lets anybody
|
||||
* watching a public chain group every payment somebody ever received, the
|
||||
* one linkability a wallet can prevent, since the chain will not forget.
|
||||
* A new address per receive is the point.
|
||||
*/
|
||||
@Test
|
||||
fun `every index gives a different address`() {
|
||||
@@ -109,11 +100,11 @@ class HdAccountTest {
|
||||
)
|
||||
}
|
||||
|
||||
// ---- Restoring -------------------------------------------------------
|
||||
// ---- Restoring -------------------------------------------------------.
|
||||
|
||||
/**
|
||||
* The whole promise of a recovery phrase: the same words give back the
|
||||
* same addresses, on any device and any implementation.
|
||||
* The whole promise of a recovery phrase: the same words give back the same
|
||||
* addresses, on any device and any implementation.
|
||||
*/
|
||||
@Test
|
||||
fun `the same phrase restores the same addresses`() {
|
||||
@@ -129,7 +120,7 @@ class HdAccountTest {
|
||||
assertNotEquals(account().receive(0).address, other.receive(0).address)
|
||||
}
|
||||
|
||||
// ---- What an address is, and is not ----------------------------------
|
||||
// ---- What an address is, and is not ----------------------------------.
|
||||
|
||||
/** An address is Base58 of 32 bytes, so it decodes back to the key. */
|
||||
@Test
|
||||
@@ -160,25 +151,19 @@ class HdAccountTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* The limit, asserted as a passing test so nobody reads
|
||||
* `looksLikeAddress` as stronger than it is.
|
||||
*
|
||||
* A Solana address carries NO checksum. A mistyped character that still
|
||||
* decodes to 32 bytes is a perfectly valid address that nobody holds the
|
||||
* key for, money sent there is gone, and neither this function nor the
|
||||
* network will catch it. Bitcoin's Base58Check would; this cannot.
|
||||
*
|
||||
* The only real defences are pasting rather than typing, and showing the
|
||||
* address back to be compared.
|
||||
* The limit, asserted as a passing test so nobody reads `looksLikeAddress`
|
||||
* as stronger than it is.
|
||||
*/
|
||||
@Test
|
||||
fun `a corrupted address can still look valid, and that is not fixable here`() {
|
||||
val real = account().receive(0).address
|
||||
// Walk the alphabet at one position until the result still decodes to
|
||||
// 32 bytes. On a checksummed format this search would fail.
|
||||
val nearMiss = Base58.ALPHABET.asSequence()
|
||||
.map { real.take(3) + it + real.drop(4) }
|
||||
.firstOrNull { it != real && HdAccount.looksLikeAddress(it) }
|
||||
// 32 bytes.
|
||||
val nearMiss =
|
||||
Base58.ALPHABET
|
||||
.asSequence()
|
||||
.map { real.take(3) + it + real.drop(4) }
|
||||
.firstOrNull { it != real && HdAccount.looksLikeAddress(it) }
|
||||
assertTrue(
|
||||
nearMiss != null,
|
||||
"no single-character change produced a still-valid address, if this " +
|
||||
|
||||
Reference in New Issue
Block a user