github.com/lbryio/lbcd@v0.22.119/txscript/claimscript.go (about) 1 package txscript 2 3 import ( 4 "bytes" 5 "fmt" 6 "unicode/utf8" 7 ) 8 9 const ( 10 // MaxClaimScriptSize is the max claim script size in bytes, not including the script pubkey part of the script. 11 MaxClaimScriptSize = 8192 12 13 // MaxClaimNameSize is the max claim name size in bytes, for all claim trie transactions. 14 MaxClaimNameSize = 255 15 16 ClaimIDLength = 160 / 8 17 18 claimScriptVersion = 0 19 ) 20 21 // These constants are used to identify a specific claim script Error. 22 // The error code starts from 200, which leaves enough room between the rest 23 // of script error codes (numErrorCodes) 24 const ( 25 // ErrNotClaimScript is returned when the script does not have a ClaimScript Opcode. 26 ErrNotClaimScript ErrorCode = iota + 200 27 28 // ErrInvalidClaimNameScript is returned a claim name script does not conform to the format. 29 ErrInvalidClaimNameScript 30 31 // ErrInvalidClaimSupportScript is returned a claim support script does not conform to the format. 32 ErrInvalidClaimSupportScript 33 34 // ErrInvalidClaimUpdateScript is returned a claim update script does not conform to the format. 35 ErrInvalidClaimUpdateScript 36 37 // ErrInvalidClaimName is returned when the claim name is invalid. 38 ErrInvalidClaimName 39 ) 40 41 func claimScriptError(c ErrorCode, desc string) Error { 42 return Error{ErrorCode: c, Description: desc} 43 } 44 45 // ClaimNameScript creates a claim name script. 46 func ClaimNameScript(name string, value string) ([]byte, error) { 47 return NewScriptBuilder().AddOp(OP_CLAIMNAME).AddData([]byte(name)).AddData([]byte(value)). 48 AddOp(OP_2DROP).AddOp(OP_DROP).AddOp(OP_TRUE).Script() 49 } 50 51 // ClaimSupportScript creates a support claim script. 52 func ClaimSupportScript(name string, claimID []byte, value []byte) ([]byte, error) { 53 builder := NewScriptBuilder().AddOp(OP_SUPPORTCLAIM).AddData([]byte(name)).AddData(claimID) 54 if len(value) > 0 { 55 return builder.addData(value).AddOp(OP_2DROP).AddOp(OP_2DROP).AddOp(OP_TRUE).Script() 56 } 57 return builder.AddOp(OP_2DROP).AddOp(OP_DROP).AddOp(OP_TRUE).Script() 58 } 59 60 // ClaimUpdateScript creates an update claim script. 61 func ClaimUpdateScript(name string, claimID []byte, value string) ([]byte, error) { 62 return NewScriptBuilder().AddOp(OP_UPDATECLAIM).AddData([]byte(name)).AddData(claimID).AddData([]byte(value)). 63 AddOp(OP_2DROP).AddOp(OP_2DROP).AddOp(OP_TRUE).Script() 64 } 65 66 // ClaimScript represents of one of the ClaimNameScript, ClaimSupportScript, and ClaimUpdateScript. 67 type ClaimScript struct { 68 Opcode byte 69 Name []byte 70 ClaimID []byte 71 Value []byte 72 Size int 73 } 74 75 // ExtractClaimScript exctracts the claim script from the script if it has one. 76 // The returned ClaimScript is invalidated if the given script is modified. 77 func ExtractClaimScript(script []byte) (*ClaimScript, error) { 78 79 var cs ClaimScript 80 81 tokenizer := MakeScriptTokenizer(claimScriptVersion, script) 82 if !tokenizer.Next() { 83 return nil, claimScriptError(ErrNotClaimScript, "not a claim script opcode") 84 } 85 86 cs.Opcode = tokenizer.Opcode() 87 88 switch tokenizer.Opcode() { 89 case OP_CLAIMNAME: 90 // OP_CLAIMNAME <Name> <Value> OP_2DROP OP_DROP <P2PKH> 91 if !tokenizer.Next() || len(tokenizer.Data()) > MaxClaimNameSize { 92 str := fmt.Sprintf("name size %d exceeds limit %d", len(tokenizer.data), MaxClaimNameSize) 93 return nil, claimScriptError(ErrInvalidClaimNameScript, str) 94 } 95 cs.Name = tokenizer.data 96 97 if !tokenizer.Next() { 98 return nil, claimScriptError(ErrInvalidClaimNameScript, "expect value") 99 } 100 cs.Value = tokenizer.Data() 101 102 if !tokenizer.Next() || tokenizer.Opcode() != OP_2DROP || 103 !tokenizer.Next() || tokenizer.Opcode() != OP_DROP { 104 return nil, claimScriptError(ErrInvalidClaimNameScript, "expect OP_2DROP OP_DROP") 105 } 106 107 cs.Size = int(tokenizer.ByteIndex()) 108 if cs.Size > MaxClaimScriptSize { 109 str := fmt.Sprintf("script size %d exceeds limit %d", cs.Size, MaxClaimScriptSize) 110 return nil, claimScriptError(ErrInvalidClaimNameScript, str) 111 } 112 113 return &cs, nil 114 115 case OP_SUPPORTCLAIM: 116 // OP_SUPPORTCLAIM <Name> <ClaimID> OP_2DROP OP_DROP <P2PKH> 117 // OP_SUPPORTCLAIM <Name> <ClaimID> <Value> OP_2DROP OP_2DROP <P2PKH> 118 if !tokenizer.Next() || len(tokenizer.Data()) > MaxClaimNameSize { 119 str := fmt.Sprintf("name size %d exceeds limit %d", len(tokenizer.data), MaxClaimNameSize) 120 return nil, claimScriptError(ErrInvalidClaimSupportScript, str) 121 } 122 cs.Name = tokenizer.data 123 124 if !tokenizer.Next() || len(tokenizer.Data()) != ClaimIDLength { 125 str := fmt.Sprintf("expect claim id length %d, instead of %d", ClaimIDLength, len(tokenizer.data)) 126 return nil, claimScriptError(ErrInvalidClaimSupportScript, str) 127 } 128 cs.ClaimID = tokenizer.Data() 129 130 if !tokenizer.Next() { 131 return nil, claimScriptError(ErrInvalidClaimSupportScript, "incomplete script") 132 } 133 134 switch { 135 case tokenizer.Opcode() == OP_2DROP: 136 // Case 1: OP_SUPPORTCLAIM <Name> <ClaimID> OP_2DROP OP_DROP <P2PKH> 137 if !tokenizer.Next() || tokenizer.Opcode() != OP_DROP { 138 return nil, claimScriptError(ErrInvalidClaimSupportScript, "expect OP_2DROP OP_DROP") 139 } 140 141 case len(tokenizer.Data()) != 0: 142 // Case 2: OP_SUPPORTCLAIM <Name> <ClaimID> <Dummy Value> OP_2DROP OP_2DROP <P2PKH> 143 // (old bug: non-length size dummy value?) 144 cs.Value = tokenizer.Data() 145 if !tokenizer.Next() || tokenizer.Opcode() != OP_2DROP || 146 !tokenizer.Next() || tokenizer.Opcode() != OP_2DROP { 147 return nil, claimScriptError(ErrInvalidClaimSupportScript, "expect OP_2DROP OP_2DROP") 148 } 149 default: 150 return nil, claimScriptError(ErrInvalidClaimSupportScript, "expect OP_2DROP OP_DROP") 151 } 152 153 cs.Size = int(tokenizer.ByteIndex()) 154 if cs.Size > MaxClaimScriptSize { 155 str := fmt.Sprintf("script size %d exceeds limit %d", cs.Size, MaxClaimScriptSize) 156 return nil, claimScriptError(ErrInvalidClaimSupportScript, str) 157 } 158 159 return &cs, nil 160 161 case OP_UPDATECLAIM: 162 // OP_UPDATECLAIM <Name> <ClaimID> <Value> OP_2DROP OP_2DROP <P2PKH> 163 if !tokenizer.Next() || len(tokenizer.Data()) > MaxClaimNameSize { 164 str := fmt.Sprintf("name size %d exceeds limit %d", len(tokenizer.data), MaxClaimNameSize) 165 return nil, claimScriptError(ErrInvalidClaimUpdateScript, str) 166 } 167 cs.Name = tokenizer.data 168 169 if !tokenizer.Next() || len(tokenizer.Data()) != ClaimIDLength { 170 str := fmt.Sprintf("expect claim id length %d, instead of %d", ClaimIDLength, len(tokenizer.data)) 171 return nil, claimScriptError(ErrInvalidClaimUpdateScript, str) 172 } 173 cs.ClaimID = tokenizer.Data() 174 175 if !tokenizer.Next() { 176 str := fmt.Sprintf("expect value") 177 return nil, claimScriptError(ErrInvalidClaimUpdateScript, str) 178 } 179 cs.Value = tokenizer.Data() 180 181 if !tokenizer.Next() || tokenizer.Opcode() != OP_2DROP || 182 !tokenizer.Next() || tokenizer.Opcode() != OP_2DROP { 183 str := fmt.Sprintf("expect OP_2DROP OP_2DROP") 184 return nil, claimScriptError(ErrInvalidClaimUpdateScript, str) 185 } 186 187 cs.Size = int(tokenizer.ByteIndex()) 188 if cs.Size > MaxClaimScriptSize { 189 str := fmt.Sprintf("script size %d exceeds limit %d", cs.Size, MaxClaimScriptSize) 190 return nil, claimScriptError(ErrInvalidClaimUpdateScript, str) 191 } 192 193 return &cs, nil 194 195 default: 196 return nil, claimScriptError(ErrNotClaimScript, "") 197 } 198 } 199 200 // StripClaimScriptPrefix strips prefixed claim script, if any. 201 func StripClaimScriptPrefix(script []byte) []byte { 202 cs, err := ExtractClaimScript(script) 203 if err != nil { 204 return script 205 } 206 return script[cs.Size:] 207 } 208 209 const illegalChars = "=&#:$%?/;\\\b\n\t\r\x00" 210 211 func AllClaimsAreSane(script []byte, enforceSoftFork bool) error { 212 cs, err := ExtractClaimScript(script) 213 if IsErrorCode(err, ErrNotClaimScript) { 214 return nil 215 } 216 if err != nil { 217 return err 218 } 219 if enforceSoftFork { 220 if !utf8.Valid(cs.Name) { 221 return claimScriptError(ErrInvalidClaimName, "claim name is not valid UTF-8") 222 } 223 if bytes.ContainsAny(cs.Name, illegalChars) { 224 str := fmt.Sprintf("claim name has illegal chars; it should not contain any of these: %s", illegalChars) 225 return claimScriptError(ErrInvalidClaimName, str) 226 } 227 } 228 229 return nil 230 }