@@ -66,6 +66,69 @@ def _emitprimitivetype(self, ksy, bitwise):
6666 return "asn1_der_len"
6767
6868
69+ # A byte with value < 0x80
70+ @singleton
71+ class LowByte (Construct ):
72+ def _parse (self , stream , context , path ):
73+ b = byte2int (stream_read (stream , 1 , path ))
74+ if b >= 0x80 :
75+ raise IntegerError ("BIP32 path length prefix must be < 0x80" )
76+ return b
77+
78+ def _build (self , obj , stream , context , path ):
79+ if not isinstance (obj , int ):
80+ raise IntegerError ("length must be an integer" )
81+ if not (0 <= obj < 0x80 ):
82+ raise IntegerError ("BIP32 path length must be < 0x80" )
83+ stream_write (stream , int2byte (obj ), 1 , path )
84+ return obj
85+
86+
87+ # A byte with value >= 0x80, decoded as (byte - 0x80)
88+ # This is used to encode the length of SLIP-21 paths
89+ @singleton
90+ class Slip21LenByte (Construct ):
91+ def _parse (self , stream , context , path ):
92+ b = byte2int (stream_read (stream , 1 , path ))
93+ if b < 0x80 :
94+ raise IntegerError ("SLIP-21 length prefix must be >= 0x80" )
95+ return b - 0x80
96+
97+ def _build (self , obj , stream , context , path ):
98+ if not isinstance (obj , int ):
99+ raise IntegerError ("length must be an integer" )
100+ if not (0 <= obj <= 0x7F ):
101+ raise IntegerError ("SLIP-21 decoded length must be in [0, 0x7F]" )
102+ stream_write (stream , int2byte (0x80 + obj ), 1 , path )
103+ return obj
104+
105+
106+ class Slip21PathAdapter (Adapter ):
107+ def _decode (self , obj , context , path ):
108+ # obj is now a list/array of bytes (ints 0–255)
109+ if not obj :
110+ return str ()
111+
112+ # First byte must be zero prefix
113+ if obj [0 ] != 0x00 :
114+ raise IntegerError ("invalid SLIP-21 path prefix" )
115+
116+ # Remaining bytes are UTF-8 chars
117+ return bytes (obj [1 :]).decode ("utf-8" )
118+
119+ def _encode (self , obj , context , path ):
120+ if not isinstance (obj , str ):
121+ raise IntegerError ("SLIP-21 path must be a string" )
122+
123+ payload = b"\0 " + obj .encode ("utf-8" )
124+ length = len (payload )
125+ if length > 0x7F :
126+ raise IntegerError ("SLIP-21 path too long" )
127+
128+ # Return list of ints so PrefixedArray(Slip21LenByte, Byte) can handle it
129+ return list (payload )
130+
131+
69132# noinspection PyAbstractClass
70133class Bip32PathAdapter (Adapter ):
71134 def _decode (self , obj , context , path ):
@@ -90,7 +153,8 @@ def _encode(self, obj, context, path):
90153 return out
91154
92155
93- Bip32Path = Bip32PathAdapter (PrefixedArray (Byte , Int32ub ))
156+ Bip32Path = Bip32PathAdapter (PrefixedArray (LowByte , Int32ub ))
157+ Slip21Path = Slip21PathAdapter (PrefixedArray (Slip21LenByte , Byte ))
94158
95159PrefixedString = PascalString (Asn1Length , "utf8" )
96160
@@ -101,18 +165,27 @@ def _encode(self, obj, context, path):
101165CURVE_SECP256K1 = 1
102166CURVE_PRIME256R1 = 2
103167CURVE_ED25519 = 4
168+ CURVE_SLIP21 = (
169+ 8 # not really a curve, but used to indicate the presence of SLIP-21 paths
170+ )
104171CURVE_BLS12381G1 = 16
105172
106173Curve = FlagsEnum (
107174 Byte ,
108175 secp256k1 = CURVE_SECP256K1 ,
109176 prime256r1 = CURVE_PRIME256R1 ,
110177 ed25519 = CURVE_ED25519 ,
178+ slip21 = CURVE_SLIP21 ,
111179 bls12381g1 = CURVE_BLS12381G1 ,
112180)
113181
114182DerivationPath = Prefixed (
115- Asn1Length , Struct (curve = Curve , paths = Optional (GreedyRange (Bip32Path )))
183+ Asn1Length ,
184+ Struct (
185+ curve = Curve ,
186+ paths = Optional (GreedyRange (Bip32Path )),
187+ paths_slip21 = Optional (GreedyRange (Slip21Path )),
188+ ),
116189)
117190
118191Dependency = Prefixed (
@@ -163,8 +236,9 @@ def main():
163236 {
164237 "type_" : "BOLOS_TAG_DERIVEPATH" ,
165238 "value" : {
166- "curve" : Curve .prime256r1 | Curve .ed25519 ,
239+ "curve" : Curve .prime256r1 | Curve .ed25519 | Curve . slip21 ,
167240 "paths" : ["44'/535348'" , "13'" , "17'" ],
241+ "paths_slip21" : ["MYPATH" ],
168242 },
169243 },
170244 ]
@@ -175,8 +249,9 @@ def main():
175249 b"\x02 \x05 \x30 \x2E \x30 \x2E \x34 \x03 \x29 \x01 \x00 \x00 \x00 \x00 \xFF "
176250 b"\xFF \xFF \x00 \x00 \x18 \xFC \x24 \x02 \x24 \x0A \x24 \x1A \x7E \x32 \x66 "
177251 b"\x62 \x6E \x62 \x7E \x32 \x00 \x1A \x40 \x0A \x5F \x02 \x5F \x02 \x40 \x02 "
178- b"\x40 \xFE \x7F \x00 \x00 \x04 \x14 \x06 \x02 \x80 \x00 \x00 \x2C \x80 \x08 "
179- b"\x2B \x34 \x01 \x80 \x00 \x00 \x0D \x01 \x80 \x00 \x00 \x11 "
252+ b"\x40 \xFE \x7F \x00 \x00 \x04 \x1c \x0E \x02 \x80 \x00 \x00 \x2C \x80 \x08 "
253+ b"\x2B \x34 \x01 \x80 \x00 \x00 \x0D \x01 \x80 \x00 \x00 \x11 \x87 \x00 \x4D "
254+ b"\x59 \x50 \x41 \x54 \x48 "
180255 )
181256 assert params1 == params2
182257
0 commit comments