Skip to content

Commit fc6154a

Browse files
sekulicdtiero
andauthored
[WIP]Pegin (#161)
* added factory method FromPublicKeys for creating multiscript payment * getpeginaddress * test fix * sanity check * decouple cgo code from getaddress * refactor after review * refactor after review * Export ClaimWitnessScript and ClaimWitnessScript * refactor after review * refactor after review Co-authored-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>
1 parent 6164d4b commit fc6154a

8 files changed

Lines changed: 1046 additions & 10 deletions

File tree

address/address.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func ToBase58(b *Base58) string {
9595
}
9696

9797
// FromBech32 decodes a bech32 encoded string, returning the human-readable
98-
// part and the data part excluding the checksum.
98+
// part and the Data part excluding the checksum.
9999
func FromBech32(address string) (*Bech32, error) {
100100
// Bech32 encoded segwit addresses start with a human-readable part
101101
// (hrp) followed by '1'. For Liquid mainnet the hrp is "ex", and for
@@ -133,14 +133,14 @@ func FromBech32(address string) (*Bech32, error) {
133133
return nil, err
134134
}
135135

136-
// The regrouped data must be between 2 and 40 bytes.
136+
// The regrouped Data must be between 2 and 40 bytes.
137137
if len(regrouped) < 2 || len(regrouped) > 40 {
138-
return nil, errors.New("invalid data length")
138+
return nil, errors.New("invalid Data Length")
139139
}
140140

141141
// For witness version 0, address MUST be exactly 20 or 32 bytes.
142142
if version == 0 && len(regrouped) != 20 && len(regrouped) != 32 {
143-
return nil, errors.New("invalid data length for witness ")
143+
return nil, errors.New("invalid Data Length for witness ")
144144
}
145145

146146
return &Bech32{prefix, version, regrouped}, nil
@@ -207,7 +207,7 @@ func ToBase58Confidential(b *Base58Confidential) string {
207207
}
208208

209209
// FromBlech32 decodes a blech32 encoded string, returning the human-readable
210-
// part and the data part excluding the checksum.
210+
// part and the Data part excluding the checksum.
211211
func FromBlech32(address string) (*Blech32, error) {
212212
// Blech32 encoded segwit addresses start with a human-readable part
213213
// (hrp) followed by '1'. For Liquid mainnet the hrp is "ex", and for
@@ -247,12 +247,12 @@ func FromBlech32(address string) (*Blech32, error) {
247247
}
248248

249249
if len(regrouped) < 2 || len(regrouped) > 40+33 {
250-
return nil, fmt.Errorf("invalid data length")
250+
return nil, fmt.Errorf("invalid Data Length")
251251
}
252252

253253
// For witness version 0, address MUST be exactly 20+33 or 32+33 bytes.
254254
if version == 0 && len(regrouped) != 20+33 && len(regrouped) != 32+33 {
255-
return nil, fmt.Errorf("invalid data length for witness "+
255+
return nil, fmt.Errorf("invalid Data Length for witness "+
256256
"version 0: %v", len(regrouped))
257257
}
258258

@@ -557,7 +557,7 @@ func decodeBlech32(address string, net network.Network) (int, error) {
557557
case 32:
558558
return ConfidentialP2Wsh, nil
559559
default:
560-
return 0, errors.New("invalid program length")
560+
return 0, errors.New("invalid program Length")
561561
}
562562
}
563563

@@ -576,7 +576,7 @@ func decodeBech32(address string, net network.Network) (int, error) {
576576
case 32:
577577
return P2Wsh, nil
578578
default:
579-
return 0, errors.New("invalid program length")
579+
return 0, errors.New("invalid program Length")
580580
}
581581
}
582582

address/opcode.go

Lines changed: 303 additions & 0 deletions
Large diffs are not rendered by default.

address/script.go

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
package address
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
"github.com/btcsuite/btcd/txscript"
8+
)
9+
10+
// ScriptClass is an enumeration for the list of standard types of script.
11+
type ScriptClass byte
12+
13+
// Classes of script payment known about in the blockchain.
14+
const (
15+
NonStandardTy ScriptClass = iota // None of the recognized forms.
16+
PubKeyTy // Pay pubkey.
17+
PubKeyHashTy // Pay pubkey hash.
18+
WitnessV0PubKeyHashTy // Pay witness pubkey hash.
19+
ScriptHashTy // Pay to script hash.
20+
WitnessV0ScriptHashTy // Pay to witness script hash.
21+
MultiSigTy // Multi signature.
22+
NullDataTy // Empty Data-only (provably prunable).
23+
)
24+
25+
const (
26+
// MaxDataCarrierSize is the maximum number of bytes allowed in pushed
27+
// Data to be considered a nulldata transaction
28+
MaxDataCarrierSize = 80
29+
)
30+
31+
// parseScript preparses the script in bytes into a list of parsedOpcodes while
32+
// applying a number of sanity checks.
33+
func ParseScript(script []byte) ([]ParsedOpcode, error) {
34+
return parseScriptTemplate(script, &opcodeArray)
35+
}
36+
37+
// scriptType returns the type of the script being inspected from the known
38+
// standard types.
39+
func TypeOfScript(pops []ParsedOpcode) ScriptClass {
40+
if IsPubkey(pops) {
41+
return PubKeyTy
42+
} else if IsPubkeyHash(pops) {
43+
return PubKeyHashTy
44+
} else if IsWitnessPubKeyHash(pops) {
45+
return WitnessV0PubKeyHashTy
46+
} else if IsScriptHash(pops) {
47+
return ScriptHashTy
48+
} else if IsWitnessScriptHash(pops) {
49+
return WitnessV0ScriptHashTy
50+
} else if IsMultiSig(pops) {
51+
return MultiSigTy
52+
} else if isNullData(pops) {
53+
return NullDataTy
54+
}
55+
return NonStandardTy
56+
}
57+
58+
// IsPubkey returns true if the script passed is a pay-to-pubkey transaction,
59+
// false otherwise.
60+
func IsPubkey(pops []ParsedOpcode) bool {
61+
// Valid pubkeys are either 33 or 65 bytes.
62+
return len(pops) == 2 &&
63+
(len(pops[0].Data) == 33 || len(pops[0].Data) == 65) &&
64+
pops[1].Opcode.Value == txscript.OP_CHECKSIG
65+
}
66+
67+
// IsPubkeyHash returns true if the script passed is a pay-to-pubkey-hash
68+
// transaction, false otherwise.
69+
func IsPubkeyHash(pops []ParsedOpcode) bool {
70+
return len(pops) == 5 &&
71+
pops[0].Opcode.Value == txscript.OP_DUP &&
72+
pops[1].Opcode.Value == txscript.OP_HASH160 &&
73+
pops[2].Opcode.Value == txscript.OP_DATA_20 &&
74+
pops[3].Opcode.Value == txscript.OP_EQUALVERIFY &&
75+
pops[4].Opcode.Value == txscript.OP_CHECKSIG
76+
77+
}
78+
79+
// IsMultiSig returns true if the passed script is a multisig transaction, false
80+
// otherwise.
81+
func IsMultiSig(pops []ParsedOpcode) bool {
82+
// The absolute minimum is 1 pubkey:
83+
// OP_0/OP_1-16 <pubkey> OP_1 OP_CHECKMULTISIG
84+
l := len(pops)
85+
if l < 4 {
86+
return false
87+
}
88+
if !isSmallInt(pops[0].Opcode) {
89+
return false
90+
}
91+
if !isSmallInt(pops[l-2].Opcode) {
92+
return false
93+
}
94+
if pops[l-1].Opcode.Value != txscript.OP_CHECKMULTISIG {
95+
return false
96+
}
97+
98+
// Verify the number of pubkeys specified matches the actual number
99+
// of pubkeys provided.
100+
if l-2-1 != asSmallInt(pops[l-2].Opcode) {
101+
return false
102+
}
103+
104+
for _, pop := range pops[1 : l-2] {
105+
// Valid pubkeys are either 33 or 65 bytes.
106+
if len(pop.Data) != 33 && len(pop.Data) != 65 {
107+
return false
108+
}
109+
}
110+
return true
111+
}
112+
113+
// IsWitnessPubKeyHash returns true if the passed script is a
114+
// pay-to-witness-pubkey-hash, and false otherwise.
115+
func IsWitnessPubKeyHash(pops []ParsedOpcode) bool {
116+
return len(pops) == 2 &&
117+
pops[0].Opcode.Value == txscript.OP_0 &&
118+
pops[1].Opcode.Value == txscript.OP_DATA_20
119+
}
120+
121+
// isSmallInt returns whether or not the Opcode is considered a small integer,
122+
// which is an OP_0, or OP_1 through OP_16.
123+
func isSmallInt(op *Opcode) bool {
124+
if op.Value == txscript.OP_0 || (op.Value >= txscript.OP_1 && op.Value <= txscript.OP_16) {
125+
return true
126+
}
127+
return false
128+
}
129+
130+
// IsScriptHash returns true if the script passed is a pay-to-script-hash
131+
// transaction, false otherwise.
132+
func IsScriptHash(pops []ParsedOpcode) bool {
133+
return len(pops) == 3 &&
134+
pops[0].Opcode.Value == txscript.OP_HASH160 &&
135+
pops[1].Opcode.Value == txscript.OP_DATA_20 &&
136+
pops[2].Opcode.Value == txscript.OP_EQUAL
137+
}
138+
139+
// IsWitnessScriptHash returns true if the passed script is a
140+
// pay-to-witness-script-hash transaction, false otherwise.
141+
func IsWitnessScriptHash(pops []ParsedOpcode) bool {
142+
return len(pops) == 2 &&
143+
pops[0].Opcode.Value == txscript.OP_0 &&
144+
pops[1].Opcode.Value == txscript.OP_DATA_32
145+
}
146+
147+
// isNullData returns true if the passed script is a null Data transaction,
148+
// false otherwise.
149+
func isNullData(pops []ParsedOpcode) bool {
150+
// A nulldata transaction is either a single OP_RETURN or an
151+
// OP_RETURN SMALLDATA (where SMALLDATA is a Data push up to
152+
// MaxDataCarrierSize bytes).
153+
l := len(pops)
154+
if l == 1 && pops[0].Opcode.Value == txscript.OP_RETURN {
155+
return true
156+
}
157+
158+
return l == 2 &&
159+
pops[0].Opcode.Value == txscript.OP_RETURN &&
160+
(isSmallInt(pops[1].Opcode) || pops[1].Opcode.Value <=
161+
txscript.OP_PUSHDATA4) &&
162+
len(pops[1].Data) <= MaxDataCarrierSize
163+
}
164+
165+
// asSmallInt returns the passed Opcode, which must be true according to
166+
// isSmallInt(), as an integer.
167+
func asSmallInt(op *Opcode) int {
168+
if op.Value == txscript.OP_0 {
169+
return 0
170+
}
171+
172+
return int(op.Value - (txscript.OP_1 - 1))
173+
}
174+
175+
// parseScriptTemplate is the same as parseScript but allows the passing of the
176+
// template list for testing purposes. When there are parse errors, it returns
177+
// the list of parsed opcodes up to the point of failure along with the error.
178+
func parseScriptTemplate(script []byte, opcodes *[256]Opcode) ([]ParsedOpcode, error) {
179+
retScript := make([]ParsedOpcode, 0, len(script))
180+
for i := 0; i < len(script); {
181+
instr := script[i]
182+
op := &opcodes[instr]
183+
pop := ParsedOpcode{Opcode: op}
184+
185+
// Parse Data out of instruction.
186+
switch {
187+
// No additional Data. Note that some of the opcodes, notably
188+
// OP_1NEGATE, OP_0, and OP_[1-16] represent the Data
189+
// themselves.
190+
case op.Length == 1:
191+
i++
192+
193+
// Data pushes of specific lengths -- OP_DATA_[1-75].
194+
case op.Length > 1:
195+
if len(script[i:]) < op.Length {
196+
str := fmt.Sprintf("Opcode %s requires %d "+
197+
"bytes, but script only has %d remaining",
198+
op.Name, op.Length, len(script[i:]))
199+
return nil, errors.New(str)
200+
}
201+
202+
// Slice out the Data.
203+
pop.Data = script[i+1 : i+op.Length]
204+
i += op.Length
205+
206+
// Data pushes with parsed lengths -- OP_PUSHDATAP{1,2,4}.
207+
case op.Length < 0:
208+
var l uint
209+
off := i + 1
210+
211+
if len(script[off:]) < -op.Length {
212+
str := fmt.Sprintf("Opcode %s requires %d "+
213+
"bytes, but script only has %d remaining",
214+
op.Name, -op.Length, len(script[off:]))
215+
return nil, errors.New(str)
216+
}
217+
218+
// Next -Length bytes are little endian Length of Data.
219+
switch op.Length {
220+
case -1:
221+
l = uint(script[off])
222+
case -2:
223+
l = (uint(script[off+1]) << 8) |
224+
uint(script[off])
225+
case -4:
226+
l = (uint(script[off+3]) << 24) |
227+
(uint(script[off+2]) << 16) |
228+
(uint(script[off+1]) << 8) |
229+
uint(script[off])
230+
default:
231+
str := fmt.Sprintf("invalid Opcode Length %d",
232+
op.Length)
233+
return nil, errors.New(str)
234+
}
235+
236+
// Move offset to beginning of the Data.
237+
off += -op.Length
238+
239+
// Disallow entries that do not fit script or were
240+
// sign extended.
241+
if int(l) > len(script[off:]) || int(l) < 0 {
242+
str := fmt.Sprintf("Opcode %s pushes %d bytes, "+
243+
"but script only has %d remaining",
244+
op.Name, int(l), len(script[off:]))
245+
return nil, errors.New(str)
246+
}
247+
248+
pop.Data = script[off : off+int(l)]
249+
i += 1 - op.Length + int(l)
250+
}
251+
252+
retScript = append(retScript, pop)
253+
}
254+
255+
return retScript, nil
256+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ require (
88
github.com/btcsuite/btcutil/psbt v1.0.2
99
github.com/stretchr/testify v1.7.0
1010
github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941
11-
github.com/vulpemventures/go-secp256k1-zkp v1.1.0
11+
github.com/vulpemventures/go-secp256k1-zkp v1.1.1-0.20210422114014-d9d676d706c1
1212
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
1313
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,8 @@ github.com/vulpemventures/go-secp256k1-zkp v1.0.2 h1:K8zh2NegPd3JzZXWMv+ikWPHx/y
300300
github.com/vulpemventures/go-secp256k1-zkp v1.0.2/go.mod h1:zo7CpgkuPgoe7fAV+inyxsI9IhGmcoFgyD8nqZaPSOM=
301301
github.com/vulpemventures/go-secp256k1-zkp v1.1.0 h1:Z3qKc/lYxEQ1cukwwjD4n7EBCrg7N6of4hylVsD+lOA=
302302
github.com/vulpemventures/go-secp256k1-zkp v1.1.0/go.mod h1:zo7CpgkuPgoe7fAV+inyxsI9IhGmcoFgyD8nqZaPSOM=
303+
github.com/vulpemventures/go-secp256k1-zkp v1.1.1-0.20210422114014-d9d676d706c1 h1:9rPKzVfNwHve4TkCtcECzT1/zTCuN90K08VXjaI0bb0=
304+
github.com/vulpemventures/go-secp256k1-zkp v1.1.1-0.20210422114014-d9d676d706c1/go.mod h1:zo7CpgkuPgoe7fAV+inyxsI9IhGmcoFgyD8nqZaPSOM=
303305
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
304306
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
305307
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=

0 commit comments

Comments
 (0)