OSDN Git Service

feat(bcrp): add control contract action (#1909)
[bytom/bytom.git] / protocol / vm / vmutil / script.go
1 package vmutil
2
3 import (
4         "crypto/ed25519"
5
6         "github.com/bytom/bytom/consensus/bcrp"
7         "github.com/bytom/bytom/errors"
8         "github.com/bytom/bytom/protocol/vm"
9 )
10
11 // pre-define errors
12 var (
13         ErrBadValue       = errors.New("bad value")
14         ErrMultisigFormat = errors.New("bad multisig program format")
15 )
16
17 // IsUnspendable checks if a contorl program is absolute failed
18 func IsUnspendable(prog []byte) bool {
19         return len(prog) > 0 && prog[0] == byte(vm.OP_FAIL)
20 }
21
22 func (b *Builder) addP2SPMultiSig(pubkeys []ed25519.PublicKey, nrequired int) error {
23         if err := checkMultiSigParams(int64(nrequired), int64(len(pubkeys))); err != nil {
24                 return err
25         }
26
27         b.AddOp(vm.OP_TXSIGHASH) // stack is now [... NARGS SIG SIG SIG PREDICATEHASH]
28         for _, p := range pubkeys {
29                 b.AddData(p)
30         }
31         b.AddInt64(int64(nrequired))    // stack is now [... SIG SIG SIG PREDICATEHASH PUB PUB PUB M]
32         b.AddInt64(int64(len(pubkeys))) // stack is now [... SIG SIG SIG PREDICATEHASH PUB PUB PUB M N]
33         b.AddOp(vm.OP_CHECKMULTISIG)    // stack is now [... NARGS]
34         return nil
35 }
36
37 // DefaultCoinbaseProgram generates the script for contorl coinbase output
38 func DefaultCoinbaseProgram() ([]byte, error) {
39         builder := NewBuilder()
40         builder.AddOp(vm.OP_TRUE)
41         return builder.Build()
42 }
43
44 // P2WPKHProgram return the segwit pay to public key hash
45 func P2WPKHProgram(hash []byte) ([]byte, error) {
46         builder := NewBuilder()
47         builder.AddInt64(0)
48         builder.AddData(hash)
49         return builder.Build()
50 }
51
52 // P2WSHProgram return the segwit pay to script hash
53 func P2WSHProgram(hash []byte) ([]byte, error) {
54         builder := NewBuilder()
55         builder.AddInt64(0)
56         builder.AddData(hash)
57         return builder.Build()
58 }
59
60 // RetireProgram generates the script for retire output
61 func RetireProgram(comment []byte) ([]byte, error) {
62         builder := NewBuilder()
63         builder.AddOp(vm.OP_FAIL)
64         if len(comment) != 0 {
65                 builder.AddData(comment)
66         }
67         return builder.Build()
68 }
69
70 // RegisterProgram generates the script for register contract output
71 // follow BCRP(bytom contract register protocol)
72 func RegisterProgram(contract []byte) ([]byte, error) {
73         builder := NewBuilder()
74         builder.AddOp(vm.OP_FAIL)
75         builder.AddOp(vm.OP_PUSHDATA1)
76         builder.AddData([]byte(bcrp.BCRP))
77         builder.AddOp(vm.OP_PUSHDATA1)
78         builder.AddData([]byte{byte(bcrp.Version)})
79         builder.AddOp(vm.OP_PUSHDATA1)
80         builder.AddData(contract)
81         return builder.Build()
82 }
83
84 // CallContractProgram generates the script for control contract output
85 // follow BCRP(bytom contract register protocol)
86 func CallContractProgram(contractID []byte) ([]byte, error) {
87         builder := NewBuilder()
88         builder.AddOp(vm.OP_1)
89         builder.AddOp(vm.OP_PUSHDATA1)
90         builder.AddData(contractID)
91         return builder.Build()
92 }
93
94 // P2PKHSigProgram generates the script for control with pubkey hash
95 func P2PKHSigProgram(pubkeyHash []byte) ([]byte, error) {
96         builder := NewBuilder()
97         builder.AddOp(vm.OP_DUP)
98         builder.AddOp(vm.OP_HASH160)
99         builder.AddData(pubkeyHash)
100         builder.AddOp(vm.OP_EQUALVERIFY)
101         builder.AddOp(vm.OP_TXSIGHASH)
102         builder.AddOp(vm.OP_SWAP)
103         builder.AddOp(vm.OP_CHECKSIG)
104         return builder.Build()
105 }
106
107 // P2SHProgram generates the script for control with script hash
108 func P2SHProgram(scriptHash []byte) ([]byte, error) {
109         builder := NewBuilder()
110         builder.AddOp(vm.OP_DUP)
111         builder.AddOp(vm.OP_SHA3)
112         builder.AddData(scriptHash)
113         builder.AddOp(vm.OP_EQUALVERIFY)
114         builder.AddInt64(-1)
115         builder.AddOp(vm.OP_SWAP)
116         builder.AddInt64(0)
117         builder.AddOp(vm.OP_CHECKPREDICATE)
118         return builder.Build()
119 }
120
121 // P2SPMultiSigProgram generates the script for control transaction output
122 func P2SPMultiSigProgram(pubkeys []ed25519.PublicKey, nrequired int) ([]byte, error) {
123         builder := NewBuilder()
124         if err := builder.addP2SPMultiSig(pubkeys, nrequired); err != nil {
125                 return nil, err
126         }
127         return builder.Build()
128 }
129
130 // P2SPMultiSigProgramWithHeight generates the script with block height for control transaction output
131 func P2SPMultiSigProgramWithHeight(pubkeys []ed25519.PublicKey, nrequired int, blockHeight int64) ([]byte, error) {
132         builder := NewBuilder()
133         if blockHeight > 0 {
134                 builder.AddInt64(blockHeight)
135                 builder.AddOp(vm.OP_BLOCKHEIGHT)
136                 builder.AddOp(vm.OP_GREATERTHAN)
137                 builder.AddOp(vm.OP_VERIFY)
138         } else if blockHeight < 0 {
139                 return nil, errors.WithDetail(ErrBadValue, "negative blockHeight")
140         }
141         if err := builder.addP2SPMultiSig(pubkeys, nrequired); err != nil {
142                 return nil, err
143         }
144         return builder.Build()
145 }
146
147 func checkMultiSigParams(nrequired, npubkeys int64) error {
148         if nrequired < 0 {
149                 return errors.WithDetail(ErrBadValue, "negative quorum")
150         }
151         if npubkeys < 0 {
152                 return errors.WithDetail(ErrBadValue, "negative pubkey count")
153         }
154         if nrequired > npubkeys {
155                 return errors.WithDetail(ErrBadValue, "quorum too big")
156         }
157         if nrequired == 0 && npubkeys > 0 {
158                 return errors.WithDetail(ErrBadValue, "quorum empty with non-empty pubkey list")
159         }
160         return nil
161 }
162
163 // GetIssuanceProgramRestrictHeight return issuance program restrict height
164 // if height invalid return 0
165 func GetIssuanceProgramRestrictHeight(program []byte) uint64 {
166         insts, err := vm.ParseProgram(program)
167         if err != nil {
168                 return 0
169         }
170
171         if len(insts) >= 4 && insts[0].IsPushdata() && insts[1].Op == vm.OP_BLOCKHEIGHT && insts[2].Op == vm.OP_GREATERTHAN && insts[3].Op == vm.OP_VERIFY {
172                 heightInt, err := vm.AsBigInt(insts[0].Data)
173                 if err != nil {
174                         return 0
175                 }
176
177                 height, overflow := heightInt.Uint64WithOverflow()
178                 if overflow {
179                         return 0
180                 }
181
182                 return height
183         }
184         return 0
185 }