1
+ package fr.acinq.bitcoin
2
+
3
+ import fr.acinq.bitcoin.DeterministicWallet.derivePrivateKey
4
+ import fr.acinq.bitcoin.DeterministicWallet.publicKey
5
+ import kotlin.jvm.JvmStatic
6
+
7
+ public object Descriptor {
8
+ private fun polyMod (cc : Long , value : Int ): Long {
9
+ var c = cc
10
+ val c0 = c shr 35
11
+ c = ((c and 0x7ffffffffL ) shl 5 ) xor value.toLong()
12
+ if ((c0 and 1L ) != 0L ) c = c xor 0xf5dee51989L
13
+ if ((c0 and 2L ) != 0L ) c = c xor 0xa9fdca3312L
14
+ if ((c0 and 4L ) != 0L ) c = c xor 0x1bab10e32dL
15
+ if ((c0 and 8L ) != 0L ) c = c xor 0x3706b1677aL
16
+ if ((c0 and 16L ) != 0L ) c = c xor 0x644d626ffdL
17
+ return c
18
+ }
19
+
20
+ @JvmStatic
21
+ public fun checksum (span : String ): String {
22
+ /* * A character set designed such that:
23
+ * - The most common 'unprotected' descriptor characters (hex, keypaths) are in the first group of 32.
24
+ * - Case errors cause an offset that's a multiple of 32.
25
+ * - As many alphabetic characters are in the same group (while following the above restrictions).
26
+ *
27
+ * If p(x) gives the position of a character c in this character set, every group of 3 characters
28
+ * (a,b,c) is encoded as the 4 symbols (p(a) & 31, p(b) & 31, p(c) & 31, (p(a) / 32) + 3 * (p(b) / 32) + 9 * (p(c) / 32).
29
+ * This means that changes that only affect the lower 5 bits of the position, or only the higher 2 bits, will just
30
+ * affect a single symbol.
31
+ *
32
+ * As a result, within-group-of-32 errors count as 1 symbol, as do cross-group errors that don't affect
33
+ * the position within the groups.
34
+ */
35
+ val INPUT_CHARSET = " 0123456789()[],'/*abcdefgh@:$%{}" + " IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~" + " ijklmnopqrstuvwxyzABCDEFGH`#\"\\ "
36
+
37
+ /* * The character set for the checksum itself (same as bech32). */
38
+ val CHECKSUM_CHARSET = " qpzry9x8gf2tvdw0s3jn54khce6mua7l" ;
39
+
40
+ var c = 1L
41
+ var cls = 0
42
+ var clscount = 0
43
+ span.forEach { ch ->
44
+ val pos = INPUT_CHARSET .indexOf(ch);
45
+ if (pos == - 1 ) return " " ;
46
+ c = polyMod(c, pos and 31 ); // Emit a symbol for the position inside the group, for every character.
47
+ cls = cls * 3 + (pos shr 5 ); // Accumulate the group numbers
48
+ clscount + = 1
49
+ if (clscount == 3 ) {
50
+ // Emit an extra symbol representing the group numbers, for every 3 characters.
51
+ c = polyMod(c, cls)
52
+ cls = 0
53
+ clscount = 0
54
+ }
55
+ }
56
+ if (clscount > 0 ) c = polyMod(c, cls)
57
+ for (j in 0 until 8 ) c = polyMod(c, 0 ) // Shift further to determine the checksum.
58
+ c = c xor 1 // Prevent appending zeroes from not affecting the checksum.
59
+
60
+ val ret = StringBuilder (" " )
61
+ for (j in 0 until 8 ) {
62
+ val pos1 = (c shr (5 * (7 - j))) and 31
63
+ val char = CHECKSUM_CHARSET [pos1.toInt()]
64
+ ret[j] = char
65
+ }
66
+ return ret.toString()
67
+ }
68
+
69
+ private fun getBIP84KeyPath (chainHash : ByteVector32 ): Pair <String , Int > = when (chainHash) {
70
+ Block .RegtestGenesisBlock .hash, Block .TestnetGenesisBlock .hash -> " 84'/1'/0'/0" to DeterministicWallet .tpub
71
+ Block .LivenetGenesisBlock .hash -> " 84'/0'/0'/0" to DeterministicWallet .xpub
72
+ else -> error(" invalid chain hash $chainHash " )
73
+ }
74
+
75
+ @JvmStatic
76
+ public fun BIP84Descriptors (chainHash : ByteVector32 , master : DeterministicWallet .ExtendedPrivateKey ): Pair <String , String > {
77
+ val (keyPath, _) = getBIP84KeyPath(chainHash)
78
+ val accountPub = publicKey(derivePrivateKey(master, KeyPath (keyPath)))
79
+ val fingerprint = DeterministicWallet .fingerprint(master) and 0xFFFFFFFFL
80
+ return BIP84Descriptors (chainHash, fingerprint, accountPub)
81
+ }
82
+
83
+ @JvmStatic
84
+ public fun BIP84Descriptors (chainHash : ByteVector32 , fingerprint : Long , accountPub : DeterministicWallet .ExtendedPublicKey ): Pair <String , String > {
85
+ val (keyPath, prefix) = getBIP84KeyPath(chainHash)
86
+ val accountDesc = " wpkh([${fingerprint.toString(16 )} /$keyPath ]${DeterministicWallet .encode(accountPub, prefix)} /0/*)"
87
+ val changeDesc = " wpkh([${fingerprint.toString(16 )} /$keyPath ]${DeterministicWallet .encode(accountPub, prefix)} /1/*)"
88
+ return Pair (
89
+ " $accountDesc #${checksum(accountDesc)} " ,
90
+ " $changeDesc #${checksum(changeDesc)} "
91
+ )
92
+ }
93
+ }
0 commit comments