Skip to content

Commit e07163f

Browse files
committed
test: add transaction signing and PDA derivation tests
Using the Ed25519 shadow, these tests cover previously-blocked crypto paths: transaction construction, signing, verification, encode/decode roundtrips, and all PDA derivation functions (associated accounts, VM accounts, deposit, timelock, swap, omnibus).
1 parent f4e939b commit e07163f

2 files changed

Lines changed: 462 additions & 0 deletions

File tree

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
package com.getcode.solana
2+
3+
import com.getcode.ed25519.Ed25519
4+
import com.getcode.solana.keys.AccountMeta
5+
import com.getcode.solana.keys.Hash
6+
import com.getcode.solana.keys.PublicKey
7+
import com.getcode.solana.keys.Signature
8+
import kotlin.test.Test
9+
import kotlin.test.assertEquals
10+
import kotlin.test.assertNotNull
11+
import kotlin.test.assertTrue
12+
import kotlin.test.assertFailsWith
13+
14+
class SolanaTransactionTest {
15+
16+
private fun testHash(): Hash =
17+
Hash(ByteArray(32) { 0xAB.toByte() }.toList())
18+
19+
// --- Construction ---
20+
21+
@Test
22+
fun newInstanceCreatesTransactionWithPlaceholderSignatures() {
23+
val seed = ByteArray(32) { 1 }
24+
val keyPair = Ed25519.createKeyPair(seed)
25+
val payer = PublicKey(keyPair.publicKeyBytes.toList())
26+
27+
val tx = SolanaTransaction.newInstance(
28+
payer = payer,
29+
recentBlockhash = testHash(),
30+
instructions = emptyList()
31+
)
32+
33+
assertEquals(1, tx.signatures.size)
34+
assertEquals(Signature.zero, tx.signatures[0])
35+
}
36+
37+
@Test
38+
fun newInstanceWithInstructionIncludesProgram() {
39+
val seed = ByteArray(32) { 1 }
40+
val keyPair = Ed25519.createKeyPair(seed)
41+
val payer = PublicKey(keyPair.publicKeyBytes.toList())
42+
val program = PublicKey(ByteArray(32) { 2 }.toList())
43+
44+
val instruction = Instruction(
45+
program = program,
46+
accounts = emptyList(),
47+
data = listOf(0x01)
48+
)
49+
50+
val tx = SolanaTransaction.newInstance(
51+
payer = payer,
52+
recentBlockhash = testHash(),
53+
instructions = listOf(instruction)
54+
)
55+
56+
assertTrue(tx.message.accounts.any { it.publicKey == program })
57+
}
58+
59+
// --- Signing ---
60+
61+
@Test
62+
fun signProducesValidSignature() {
63+
val seed = ByteArray(32) { 1 }
64+
val keyPair = Ed25519.createKeyPair(seed)
65+
val payer = PublicKey(keyPair.publicKeyBytes.toList())
66+
67+
val tx = SolanaTransaction.newInstance(
68+
payer = payer,
69+
recentBlockhash = testHash(),
70+
instructions = emptyList()
71+
)
72+
73+
val signatures = tx.sign(keyPair)
74+
assertEquals(1, signatures.size)
75+
assertEquals(64, signatures[0].bytes.size)
76+
// Signature should not be all zeros
77+
assertTrue(signatures[0] != Signature.zero)
78+
}
79+
80+
@Test
81+
fun signatureIsVerifiable() {
82+
val seed = ByteArray(32) { 1 }
83+
val keyPair = Ed25519.createKeyPair(seed)
84+
val payer = PublicKey(keyPair.publicKeyBytes.toList())
85+
86+
val tx = SolanaTransaction.newInstance(
87+
payer = payer,
88+
recentBlockhash = testHash(),
89+
instructions = emptyList()
90+
)
91+
92+
val signatures = tx.sign(keyPair)
93+
val messageData = tx.message.encode()
94+
95+
assertTrue(
96+
Ed25519.verify(
97+
signatures[0].bytes.toByteArray(),
98+
messageData,
99+
keyPair.publicKeyBytes
100+
)
101+
)
102+
}
103+
104+
@Test
105+
fun signIsDeterministic() {
106+
val seed = ByteArray(32) { 1 }
107+
val keyPair = Ed25519.createKeyPair(seed)
108+
val payer = PublicKey(keyPair.publicKeyBytes.toList())
109+
110+
val tx1 = SolanaTransaction.newInstance(
111+
payer = payer,
112+
recentBlockhash = testHash(),
113+
instructions = emptyList()
114+
)
115+
116+
val tx2 = SolanaTransaction.newInstance(
117+
payer = payer,
118+
recentBlockhash = testHash(),
119+
instructions = emptyList()
120+
)
121+
122+
val sig1 = tx1.sign(keyPair)
123+
val sig2 = tx2.sign(keyPair)
124+
assertEquals(sig1[0].bytes, sig2[0].bytes)
125+
}
126+
127+
@Test
128+
fun signRejectsKeyNotInAccountList() {
129+
val seed1 = ByteArray(32) { 1 }
130+
val seed2 = ByteArray(32) { 2 }
131+
val payerKp = Ed25519.createKeyPair(seed1)
132+
val otherKp = Ed25519.createKeyPair(seed2)
133+
val payer = PublicKey(payerKp.publicKeyBytes.toList())
134+
135+
val tx = SolanaTransaction.newInstance(
136+
payer = payer,
137+
recentBlockhash = testHash(),
138+
instructions = emptyList()
139+
)
140+
141+
assertFailsWith<Exception> {
142+
tx.sign(otherKp)
143+
}
144+
}
145+
146+
@Test
147+
fun signRejectsTooManySigners() {
148+
val seed1 = ByteArray(32) { 1 }
149+
val seed2 = ByteArray(32) { 2 }
150+
val kp1 = Ed25519.createKeyPair(seed1)
151+
val kp2 = Ed25519.createKeyPair(seed2)
152+
val payer = PublicKey(kp1.publicKeyBytes.toList())
153+
154+
// Transaction with only 1 required signature (payer)
155+
val tx = SolanaTransaction.newInstance(
156+
payer = payer,
157+
recentBlockhash = testHash(),
158+
instructions = emptyList()
159+
)
160+
161+
assertFailsWith<Exception> {
162+
tx.sign(kp1, kp2)
163+
}
164+
}
165+
166+
// --- Encode / Decode roundtrip ---
167+
168+
@Test
169+
fun encodeDecodeRoundtrip() {
170+
val seed = ByteArray(32) { 1 }
171+
val keyPair = Ed25519.createKeyPair(seed)
172+
val payer = PublicKey(keyPair.publicKeyBytes.toList())
173+
174+
val tx = SolanaTransaction.newInstance(
175+
payer = payer,
176+
recentBlockhash = testHash(),
177+
instructions = emptyList()
178+
)
179+
180+
val encoded = tx.encode()
181+
val decoded = SolanaTransaction.fromList(encoded)
182+
183+
assertNotNull(decoded)
184+
assertEquals(tx.message.header, decoded.message.header)
185+
assertEquals(tx.message.recentBlockhash, decoded.message.recentBlockhash)
186+
assertEquals(tx.signatures.size, decoded.signatures.size)
187+
}
188+
189+
@Test
190+
fun encodeDecodeWithSignatureRoundtrip() {
191+
val seed = ByteArray(32) { 1 }
192+
val keyPair = Ed25519.createKeyPair(seed)
193+
val payer = PublicKey(keyPair.publicKeyBytes.toList())
194+
195+
val tx = SolanaTransaction.newInstance(
196+
payer = payer,
197+
recentBlockhash = testHash(),
198+
instructions = emptyList()
199+
)
200+
201+
val signatures = tx.sign(keyPair)
202+
val signedTx = tx.copy(signatures = signatures)
203+
204+
val encoded = signedTx.encode()
205+
val decoded = SolanaTransaction.fromList(encoded)
206+
207+
assertNotNull(decoded)
208+
assertEquals(signatures[0].bytes, decoded.signatures[0].bytes)
209+
}
210+
211+
@Test
212+
fun encodeDecodeWithInstructionRoundtrip() {
213+
val seed = ByteArray(32) { 1 }
214+
val keyPair = Ed25519.createKeyPair(seed)
215+
val payer = PublicKey(keyPair.publicKeyBytes.toList())
216+
val program = PublicKey(ByteArray(32) { 3 }.toList())
217+
218+
val instruction = Instruction(
219+
program = program,
220+
accounts = listOf(AccountMeta.payer(payer)),
221+
data = listOf(0xCA.toByte(), 0xFE.toByte())
222+
)
223+
224+
val tx = SolanaTransaction.newInstance(
225+
payer = payer,
226+
recentBlockhash = testHash(),
227+
instructions = listOf(instruction)
228+
)
229+
230+
val encoded = tx.encode()
231+
val decoded = SolanaTransaction.fromList(encoded)
232+
233+
assertNotNull(decoded)
234+
assertEquals(1, decoded.message.instructions.size)
235+
assertEquals(
236+
listOf(0xCA.toByte(), 0xFE.toByte()),
237+
decoded.message.instructions[0].data
238+
)
239+
}
240+
241+
// --- Blockhash ---
242+
243+
@Test
244+
fun recentBlockhashIsSettable() {
245+
val seed = ByteArray(32) { 1 }
246+
val keyPair = Ed25519.createKeyPair(seed)
247+
val payer = PublicKey(keyPair.publicKeyBytes.toList())
248+
249+
val tx = SolanaTransaction.newInstance(
250+
payer = payer,
251+
recentBlockhash = testHash(),
252+
instructions = emptyList()
253+
)
254+
255+
val newHash = Hash(ByteArray(32) { 0xCD.toByte() }.toList())
256+
tx.recentBlockhash = newHash
257+
assertEquals(newHash, tx.recentBlockhash)
258+
}
259+
}

0 commit comments

Comments
 (0)