Skip to content

Commit

Permalink
[WIP]Pegin (#161)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
sekulicd and tiero authored May 6, 2021
1 parent 6164d4b commit fc6154a
Show file tree
Hide file tree
Showing 8 changed files with 1,046 additions and 10 deletions.
18 changes: 9 additions & 9 deletions address/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func ToBase58(b *Base58) string {
}

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

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

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

return &Bech32{prefix, version, regrouped}, nil
Expand Down Expand Up @@ -207,7 +207,7 @@ func ToBase58Confidential(b *Base58Confidential) string {
}

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

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

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

Expand Down Expand Up @@ -557,7 +557,7 @@ func decodeBlech32(address string, net network.Network) (int, error) {
case 32:
return ConfidentialP2Wsh, nil
default:
return 0, errors.New("invalid program length")
return 0, errors.New("invalid program Length")
}
}

Expand All @@ -576,7 +576,7 @@ func decodeBech32(address string, net network.Network) (int, error) {
case 32:
return P2Wsh, nil
default:
return 0, errors.New("invalid program length")
return 0, errors.New("invalid program Length")
}
}

Expand Down
303 changes: 303 additions & 0 deletions address/opcode.go

Large diffs are not rendered by default.

256 changes: 256 additions & 0 deletions address/script.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
package address

import (
"errors"
"fmt"

"github.com/btcsuite/btcd/txscript"
)

// ScriptClass is an enumeration for the list of standard types of script.
type ScriptClass byte

// Classes of script payment known about in the blockchain.
const (
NonStandardTy ScriptClass = iota // None of the recognized forms.
PubKeyTy // Pay pubkey.
PubKeyHashTy // Pay pubkey hash.
WitnessV0PubKeyHashTy // Pay witness pubkey hash.
ScriptHashTy // Pay to script hash.
WitnessV0ScriptHashTy // Pay to witness script hash.
MultiSigTy // Multi signature.
NullDataTy // Empty Data-only (provably prunable).
)

const (
// MaxDataCarrierSize is the maximum number of bytes allowed in pushed
// Data to be considered a nulldata transaction
MaxDataCarrierSize = 80
)

// parseScript preparses the script in bytes into a list of parsedOpcodes while
// applying a number of sanity checks.
func ParseScript(script []byte) ([]ParsedOpcode, error) {
return parseScriptTemplate(script, &opcodeArray)
}

// scriptType returns the type of the script being inspected from the known
// standard types.
func TypeOfScript(pops []ParsedOpcode) ScriptClass {
if IsPubkey(pops) {
return PubKeyTy
} else if IsPubkeyHash(pops) {
return PubKeyHashTy
} else if IsWitnessPubKeyHash(pops) {
return WitnessV0PubKeyHashTy
} else if IsScriptHash(pops) {
return ScriptHashTy
} else if IsWitnessScriptHash(pops) {
return WitnessV0ScriptHashTy
} else if IsMultiSig(pops) {
return MultiSigTy
} else if isNullData(pops) {
return NullDataTy
}
return NonStandardTy
}

// IsPubkey returns true if the script passed is a pay-to-pubkey transaction,
// false otherwise.
func IsPubkey(pops []ParsedOpcode) bool {
// Valid pubkeys are either 33 or 65 bytes.
return len(pops) == 2 &&
(len(pops[0].Data) == 33 || len(pops[0].Data) == 65) &&
pops[1].Opcode.Value == txscript.OP_CHECKSIG
}

// IsPubkeyHash returns true if the script passed is a pay-to-pubkey-hash
// transaction, false otherwise.
func IsPubkeyHash(pops []ParsedOpcode) bool {
return len(pops) == 5 &&
pops[0].Opcode.Value == txscript.OP_DUP &&
pops[1].Opcode.Value == txscript.OP_HASH160 &&
pops[2].Opcode.Value == txscript.OP_DATA_20 &&
pops[3].Opcode.Value == txscript.OP_EQUALVERIFY &&
pops[4].Opcode.Value == txscript.OP_CHECKSIG

}

// IsMultiSig returns true if the passed script is a multisig transaction, false
// otherwise.
func IsMultiSig(pops []ParsedOpcode) bool {
// The absolute minimum is 1 pubkey:
// OP_0/OP_1-16 <pubkey> OP_1 OP_CHECKMULTISIG
l := len(pops)
if l < 4 {
return false
}
if !isSmallInt(pops[0].Opcode) {
return false
}
if !isSmallInt(pops[l-2].Opcode) {
return false
}
if pops[l-1].Opcode.Value != txscript.OP_CHECKMULTISIG {
return false
}

// Verify the number of pubkeys specified matches the actual number
// of pubkeys provided.
if l-2-1 != asSmallInt(pops[l-2].Opcode) {
return false
}

for _, pop := range pops[1 : l-2] {
// Valid pubkeys are either 33 or 65 bytes.
if len(pop.Data) != 33 && len(pop.Data) != 65 {
return false
}
}
return true
}

// IsWitnessPubKeyHash returns true if the passed script is a
// pay-to-witness-pubkey-hash, and false otherwise.
func IsWitnessPubKeyHash(pops []ParsedOpcode) bool {
return len(pops) == 2 &&
pops[0].Opcode.Value == txscript.OP_0 &&
pops[1].Opcode.Value == txscript.OP_DATA_20
}

// isSmallInt returns whether or not the Opcode is considered a small integer,
// which is an OP_0, or OP_1 through OP_16.
func isSmallInt(op *Opcode) bool {
if op.Value == txscript.OP_0 || (op.Value >= txscript.OP_1 && op.Value <= txscript.OP_16) {
return true
}
return false
}

// IsScriptHash returns true if the script passed is a pay-to-script-hash
// transaction, false otherwise.
func IsScriptHash(pops []ParsedOpcode) bool {
return len(pops) == 3 &&
pops[0].Opcode.Value == txscript.OP_HASH160 &&
pops[1].Opcode.Value == txscript.OP_DATA_20 &&
pops[2].Opcode.Value == txscript.OP_EQUAL
}

// IsWitnessScriptHash returns true if the passed script is a
// pay-to-witness-script-hash transaction, false otherwise.
func IsWitnessScriptHash(pops []ParsedOpcode) bool {
return len(pops) == 2 &&
pops[0].Opcode.Value == txscript.OP_0 &&
pops[1].Opcode.Value == txscript.OP_DATA_32
}

// isNullData returns true if the passed script is a null Data transaction,
// false otherwise.
func isNullData(pops []ParsedOpcode) bool {
// A nulldata transaction is either a single OP_RETURN or an
// OP_RETURN SMALLDATA (where SMALLDATA is a Data push up to
// MaxDataCarrierSize bytes).
l := len(pops)
if l == 1 && pops[0].Opcode.Value == txscript.OP_RETURN {
return true
}

return l == 2 &&
pops[0].Opcode.Value == txscript.OP_RETURN &&
(isSmallInt(pops[1].Opcode) || pops[1].Opcode.Value <=
txscript.OP_PUSHDATA4) &&
len(pops[1].Data) <= MaxDataCarrierSize
}

// asSmallInt returns the passed Opcode, which must be true according to
// isSmallInt(), as an integer.
func asSmallInt(op *Opcode) int {
if op.Value == txscript.OP_0 {
return 0
}

return int(op.Value - (txscript.OP_1 - 1))
}

// parseScriptTemplate is the same as parseScript but allows the passing of the
// template list for testing purposes. When there are parse errors, it returns
// the list of parsed opcodes up to the point of failure along with the error.
func parseScriptTemplate(script []byte, opcodes *[256]Opcode) ([]ParsedOpcode, error) {
retScript := make([]ParsedOpcode, 0, len(script))
for i := 0; i < len(script); {
instr := script[i]
op := &opcodes[instr]
pop := ParsedOpcode{Opcode: op}

// Parse Data out of instruction.
switch {
// No additional Data. Note that some of the opcodes, notably
// OP_1NEGATE, OP_0, and OP_[1-16] represent the Data
// themselves.
case op.Length == 1:
i++

// Data pushes of specific lengths -- OP_DATA_[1-75].
case op.Length > 1:
if len(script[i:]) < op.Length {
str := fmt.Sprintf("Opcode %s requires %d "+
"bytes, but script only has %d remaining",
op.Name, op.Length, len(script[i:]))
return nil, errors.New(str)
}

// Slice out the Data.
pop.Data = script[i+1 : i+op.Length]
i += op.Length

// Data pushes with parsed lengths -- OP_PUSHDATAP{1,2,4}.
case op.Length < 0:
var l uint
off := i + 1

if len(script[off:]) < -op.Length {
str := fmt.Sprintf("Opcode %s requires %d "+
"bytes, but script only has %d remaining",
op.Name, -op.Length, len(script[off:]))
return nil, errors.New(str)
}

// Next -Length bytes are little endian Length of Data.
switch op.Length {
case -1:
l = uint(script[off])
case -2:
l = (uint(script[off+1]) << 8) |
uint(script[off])
case -4:
l = (uint(script[off+3]) << 24) |
(uint(script[off+2]) << 16) |
(uint(script[off+1]) << 8) |
uint(script[off])
default:
str := fmt.Sprintf("invalid Opcode Length %d",
op.Length)
return nil, errors.New(str)
}

// Move offset to beginning of the Data.
off += -op.Length

// Disallow entries that do not fit script or were
// sign extended.
if int(l) > len(script[off:]) || int(l) < 0 {
str := fmt.Sprintf("Opcode %s pushes %d bytes, "+
"but script only has %d remaining",
op.Name, int(l), len(script[off:]))
return nil, errors.New(str)
}

pop.Data = script[off : off+int(l)]
i += 1 - op.Length + int(l)
}

retScript = append(retScript, pop)
}

return retScript, nil
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ require (
github.com/btcsuite/btcutil/psbt v1.0.2
github.com/stretchr/testify v1.7.0
github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941
github.com/vulpemventures/go-secp256k1-zkp v1.1.0
github.com/vulpemventures/go-secp256k1-zkp v1.1.1-0.20210422114014-d9d676d706c1
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,8 @@ github.com/vulpemventures/go-secp256k1-zkp v1.0.2 h1:K8zh2NegPd3JzZXWMv+ikWPHx/y
github.com/vulpemventures/go-secp256k1-zkp v1.0.2/go.mod h1:zo7CpgkuPgoe7fAV+inyxsI9IhGmcoFgyD8nqZaPSOM=
github.com/vulpemventures/go-secp256k1-zkp v1.1.0 h1:Z3qKc/lYxEQ1cukwwjD4n7EBCrg7N6of4hylVsD+lOA=
github.com/vulpemventures/go-secp256k1-zkp v1.1.0/go.mod h1:zo7CpgkuPgoe7fAV+inyxsI9IhGmcoFgyD8nqZaPSOM=
github.com/vulpemventures/go-secp256k1-zkp v1.1.1-0.20210422114014-d9d676d706c1 h1:9rPKzVfNwHve4TkCtcECzT1/zTCuN90K08VXjaI0bb0=
github.com/vulpemventures/go-secp256k1-zkp v1.1.1-0.20210422114014-d9d676d706c1/go.mod h1:zo7CpgkuPgoe7fAV+inyxsI9IhGmcoFgyD8nqZaPSOM=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
Expand Down
Loading

0 comments on commit fc6154a

Please sign in to comment.