diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b497910..39c7a94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,11 +20,8 @@ jobs: - name: Check out code into the Go module directory uses: actions/checkout@v2 - - name: Install Nigiri - run: | - mkdir ~/.nigiri; cd ~/.nigiri - curl https://travis.nigiri.network | bash; cd - docker-compose -f ~/.nigiri/docker-compose.yml up -d + - name: Run Nigiri + uses: vulpemventures/nigiri-github-action@v1 - name: Get dependencies run: go get -v -t -d ./... diff --git a/block/serialize.go b/block/serialize.go index 6099473..92698e3 100644 --- a/block/serialize.go +++ b/block/serialize.go @@ -10,12 +10,12 @@ func (b *Block) SerializeBlock() ([]byte, error) { return nil, err } - err = b.Header.SerializeHeader(s) + err = b.Header.Serialize(s) if err != nil { return nil, err } - err = b.TransactionsData.SerializeTransactions(s) + err = b.TransactionsData.Serialize(s) if err != nil { return nil, err } @@ -23,7 +23,7 @@ func (b *Block) SerializeBlock() ([]byte, error) { return s.Bytes(), nil } -func (t *Transactions) SerializeTransactions( +func (t *Transactions) Serialize( s *bufferutil.Serializer, ) error { err := s.WriteVarInt(uint64(len(t.Transactions))) @@ -45,7 +45,7 @@ func (t *Transactions) SerializeTransactions( return nil } -func (h *Header) SerializeHeader( +func (h *Header) Serialize( s *bufferutil.Serializer, ) error { if h.ExtData.IsDyna { diff --git a/confidential/blind_service.go b/confidential/blind_service.go new file mode 100644 index 0000000..125a773 --- /dev/null +++ b/confidential/blind_service.go @@ -0,0 +1,248 @@ +package confidential + +import ( + "github.com/vulpemventures/go-elements/elementsutil" + "github.com/vulpemventures/go-elements/transaction" +) + +type Blinder interface { + AssetCommitment(asset, factor []byte) ([]byte, error) + ValueCommitment(value uint64, generator, factor []byte) ([]byte, error) + NonceHash(pubKey, privKey []byte) ([32]byte, error) + RangeProof(value uint64, + nonce [32]byte, + asset []byte, + assetBlindingFactor []byte, + valueBlindFactor [32]byte, + valueCommit []byte, + scriptPubkey []byte, + minValue uint64, + exp int, + minBits int, + ) ([]byte, error) + SurjectionProof( + outputAsset []byte, + outputAssetBlindingFactor []byte, + inputAssets [][]byte, + inputAssetBlindingFactors [][]byte, + seed []byte, + numberOfTargets int, + ) ([]byte, bool) + VerifySurjectionProof( + inputAssets [][]byte, + inputAssetBlindingFactors [][]byte, + outputAsset []byte, + outputAssetBlindingFactor []byte, + proof []byte, + ) bool + SubtractScalars(a []byte, b []byte) ([]byte, error) + ComputeAndAddToScalarOffset( + scalar []byte, + value uint64, + assetBlinder []byte, + valueBlinder []byte, + ) ([]byte, error) + CreateBlindValueProof( + rng func() ([]byte, error), + valueBlindingFactor []byte, + amount uint64, + valueCommitment []byte, + assetCommitment []byte, + ) ([]byte, error) + CreateBlindAssetProof( + asset []byte, + assetCommitment []byte, + assetBlinder []byte, + ) ([]byte, error) + VerifyBlindValueProof( + value int64, + valueCommitment []byte, + blindValueProof []byte, + assetCommitment []byte, + ) (bool, error) + VerifyBlindAssetProof( + asset []byte, + blindAssetProof []byte, + assetCommitment []byte, + ) (bool, error) + UnblindOutputWithKey( + out *transaction.TxOutput, + blindKey []byte, + ) (uint64, []byte, []byte, []byte, error) +} + +type blinder struct{} + +func NewBlinder() Blinder { + return blinder{} +} + +func (b blinder) AssetCommitment( + asset, + factor []byte, +) ([]byte, error) { + return AssetCommitment(asset, factor) +} + +func (b blinder) ValueCommitment( + value uint64, + generator, + factor []byte, +) ([]byte, error) { + return ValueCommitment(value, generator, factor) +} + +func (b blinder) NonceHash( + pubKey, + privKey []byte, +) ([32]byte, error) { + return NonceHash(pubKey, privKey) +} + +func (b blinder) RangeProof( + value uint64, + nonce [32]byte, + asset []byte, + assetBlindingFactor []byte, + valueBlindFactor [32]byte, + valueCommit []byte, + scriptPubkey []byte, + minValue uint64, + exp int, + minBits int, +) ([]byte, error) { + + rangeProofArgs := RangeProofArgs{ + Value: value, + Nonce: nonce, + Asset: asset, + AssetBlindingFactor: assetBlindingFactor, + ValueBlindFactor: valueBlindFactor, + ValueCommit: valueCommit, + ScriptPubkey: scriptPubkey, + MinValue: minValue, + Exp: exp, + MinBits: minBits, + } + return RangeProof(rangeProofArgs) +} + +func (b blinder) SurjectionProof( + outputAsset []byte, + outputAssetBlindingFactor []byte, + inputAssets [][]byte, + inputAssetBlindingFactors [][]byte, + seed []byte, + numberOfTargets int, +) ([]byte, bool) { + surjectionProofArgs := SurjectionProofArgs{ + OutputAsset: outputAsset, + OutputAssetBlindingFactor: outputAssetBlindingFactor, + InputAssets: inputAssets, + InputAssetBlindingFactors: inputAssetBlindingFactors, + Seed: seed, + NumberOfTargets: numberOfTargets, + } + + return SurjectionProof(surjectionProofArgs) +} + +func (b blinder) VerifySurjectionProof( + inputAssets [][]byte, + inputAssetBlindingFactors [][]byte, + outputAsset []byte, + outputAssetBlindingFactor []byte, + proof []byte, +) bool { + arg := VerifySurjectionProofArgs{ + InputAssets: inputAssets, + InputAssetBlindingFactors: inputAssetBlindingFactors, + OutputAsset: outputAsset, + OutputAssetBlindingFactor: outputAssetBlindingFactor, + Proof: proof, + } + return VerifySurjectionProof(arg) +} + +func (b blinder) SubtractScalars( + aScalar []byte, + bScalar []byte, +) ([]byte, error) { + return SubtractScalars(aScalar, bScalar) +} + +func (b blinder) ComputeAndAddToScalarOffset( + scalar []byte, + value uint64, + assetBlinder []byte, + valueBlinder []byte, +) ([]byte, error) { + return ComputeAndAddToScalarOffset(scalar, value, assetBlinder, valueBlinder) +} + +func (b blinder) CreateBlindValueProof( + rng func() ([]byte, error), + valueBlindingFactor []byte, + amount uint64, + valueCommitment []byte, + assetCommitment []byte, +) ([]byte, error) { + return CreateBlindValueProof( + rng, + valueBlindingFactor, + amount, + valueCommitment, + assetCommitment, + ) +} + +func (b blinder) CreateBlindAssetProof( + asset []byte, + assetCommitment []byte, + assetBlinder []byte, +) ([]byte, error) { + return CreateBlindAssetProof(asset, assetCommitment, assetBlinder) +} + +func (b blinder) VerifyBlindValueProof( + value int64, + valueCommitment []byte, + blindValueProof []byte, + assetCommitment []byte, +) (bool, error) { + return VerifyBlindValueProof( + value, + valueCommitment, + blindValueProof, + assetCommitment, + ) +} + +func (b blinder) VerifyBlindAssetProof( + asset []byte, + blindAssetProof []byte, + assetCommitment []byte, +) (bool, error) { + return VerifyBlindAssetProof(asset, blindAssetProof, assetCommitment) +} + +func (b blinder) UnblindOutputWithKey( + out *transaction.TxOutput, + blindKey []byte, +) (uint64, []byte, []byte, []byte, error) { + if out.IsConfidential() { + result, err := UnblindOutputWithKey(out, blindKey) + if err != nil { + return 0, nil, nil, nil, err + } + + return result.Value, result.Asset, result.ValueBlindingFactor, result.AssetBlindingFactor, err + } + + satoshiValue, err := elementsutil.ElementsToSatoshiValue(out.Value) + if err != nil { + return 0, nil, nil, nil, err + } + + return satoshiValue, out.Asset[1:], make([]byte, 32), make([]byte, 32), nil +} diff --git a/confidential/confidential.go b/confidential/confidential.go index 1ea1095..6f07f06 100644 --- a/confidential/confidential.go +++ b/confidential/confidential.go @@ -1,13 +1,34 @@ package confidential import ( + "bytes" "crypto/sha256" + "encoding/binary" "errors" "github.com/vulpemventures/go-elements/transaction" "github.com/vulpemventures/go-secp256k1-zkp" ) +var ( + ErrPrivKeyMult = errors.New("privKey mult error") + ErrPrivKeyTweakAdd = errors.New("privKey tweak add error") + ErrPrivKeyNegate = errors.New("privKey negate error") + ErrInvalidValueBlinder = errors.New("invalid value blinder") +) + +const ( + maxSurjectionTargets = 3 +) + +var ( + Zero = []byte{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } +) + // NonceHash method generates hashed secret based on ecdh. func NonceHash(pubKey, privKey []byte) ( result [32]byte, @@ -135,6 +156,7 @@ func (a RangeProofArgs) minBits() int { // RangeProof method calculates range proof func RangeProof(args RangeProofArgs) ([]byte, error) { + return rangeProof(args) } @@ -144,13 +166,21 @@ type SurjectionProofArgs struct { InputAssets [][]byte InputAssetBlindingFactors [][]byte Seed []byte + NumberOfTargets int } func (a SurjectionProofArgs) nInputsToUse() int { - if len(a.InputAssets) >= 3 { - return 3 + numOfTargets := maxSurjectionTargets + if a.NumberOfTargets > 0 { + numOfTargets = a.NumberOfTargets + } + + min := numOfTargets + if len(a.InputAssets) < min { + min = len(a.InputAssets) } - return len(a.InputAssets) + + return min } //SurjectionProof method generates surjection proof @@ -386,6 +416,10 @@ func rangeProof(args RangeProofArgs) ([]byte, error) { } func surjectionProof(args SurjectionProofArgs) ([]byte, bool) { + if args.NumberOfTargets > maxSurjectionTargets { + return nil, false + } + ctx, _ := secp256k1.ContextCreate(secp256k1.ContextBoth) defer secp256k1.ContextDestroy(ctx) @@ -492,7 +526,7 @@ func inAssetGenerators(inAssets, inAssetBlinders [][]byte) ([]*secp256k1.Generat inGenerators := make([]*secp256k1.Generator, 0, len(inAssets)) for i, inAsset := range inAssets { - gen, err := secp256k1.GeneratorGenerateBlinded( + gen, err := secp256k1.GeneratorGenerateBlinded( //TODO in elements repo generate_blinded is not used? ctx, inAsset, inAssetBlinders[i], @@ -564,3 +598,431 @@ func calcIssuance(in *transaction.TxInput) *transaction.TxIssuanceExtended { } return issuance } + +// CalculateScalarOffset computes the scalar offset used for the final blinder computation +// value * asset_blinder + value_blinder +func CalculateScalarOffset( + amount uint64, + assetBlinder []byte, + valueBlinder []byte, +) ([]byte, error) { + var result []byte + + var ab []byte + if assetBlinder != nil { + ab = make([]byte, len(assetBlinder)) + copy(ab, assetBlinder) + } + + var vb []byte + if valueBlinder != nil { + vb = make([]byte, len(valueBlinder)) + copy(vb, valueBlinder) + } + + if ab == nil { + return vb, nil + } + + ctx, _ := secp256k1.ContextCreate(secp256k1.ContextBoth) + defer secp256k1.ContextDestroy(ctx) + + result = ab + + val := make([]byte, 32) + binary.BigEndian.PutUint64(val[24:], amount) + + if amount > 0 { + r, err := secp256k1.EcPrivKeyTweakMul(ctx, result, val) + if err != nil { + return nil, err + } + if r != 1 { + return nil, ErrPrivKeyMult + } + } else { + return vb, nil + } + + if vb == nil { + return nil, ErrInvalidValueBlinder + } + + var vn []byte + if valueBlinder != nil { + vn = make([]byte, len(valueBlinder)) + copy(vn, valueBlinder) + } + r, err := secp256k1.EcPrivKeyNegate(ctx, vn) + if err != nil { + return nil, err + } + if r != 1 { + return nil, ErrPrivKeyNegate + } + + if bytes.Equal(vn, result) { + return Zero, nil + } + + r, err = secp256k1.EcPrivKeyTweakAdd(ctx, result, vb) + if err != nil { + return nil, err + } + if r != 1 { + return nil, ErrPrivKeyTweakAdd + } + + return result, nil +} + +// SubtractScalars subtract b from a in place +func SubtractScalars(a []byte, b []byte) ([]byte, error) { + var aa []byte + if a != nil { + aa = make([]byte, len(a)) + copy(aa, a) + } + + var bb []byte + if b != nil { + bb = make([]byte, len(b)) + copy(bb, b) + } + + ctx, _ := secp256k1.ContextCreate(secp256k1.ContextBoth) + defer secp256k1.ContextDestroy(ctx) + + if bb == nil { + return aa, nil + } + + r, err := secp256k1.EcPrivKeyNegate(ctx, bb) + if err != nil { + return nil, err + } + if r != 1 { + return nil, ErrPrivKeyNegate + } + + if aa == nil { + return bb, nil + } + + r, err = secp256k1.EcPrivKeyTweakAdd(ctx, aa, bb) + if err != nil { + return nil, err + } + if r != 1 { + return nil, ErrPrivKeyTweakAdd + } + + return aa, nil +} + +// ComputeAndAddToScalarOffset computes a scalar offset and adds it to another existing one +func ComputeAndAddToScalarOffset( + scalar []byte, + value uint64, + assetBlinder []byte, + valueBlinder []byte, +) ([]byte, error) { + var s []byte + if scalar != nil { + s = make([]byte, len(scalar)) + copy(s, scalar) + } + + var ab []byte + if assetBlinder != nil { + ab = make([]byte, len(assetBlinder)) + copy(ab, assetBlinder) + } + + var vb []byte + if valueBlinder != nil { + vb = make([]byte, len(valueBlinder)) + copy(vb, valueBlinder) + } + + // If both asset and value blinders are null, 0 is added to the offset, so nothing actually happens + if ab == nil && vb == nil { + return s, nil + } + + scalarOffset, err := CalculateScalarOffset(value, ab, vb) + if err != nil { + return nil, err + } + + // When we start out, the result (a) is 0, so just set it to the scalar we just computed. + if s == nil { + return scalarOffset, nil + } else { + ctx, _ := secp256k1.ContextCreate(secp256k1.ContextBoth) + defer secp256k1.ContextDestroy(ctx) + + var nv []byte + if scalarOffset != nil { + nv = make([]byte, len(scalarOffset)) + copy(nv, scalarOffset) + } + r, err := secp256k1.EcPrivKeyNegate(ctx, nv) + if err != nil { + return nil, err + } + if r != 1 { + return nil, ErrPrivKeyNegate + } + + if bytes.Equal(s, nv) { + return Zero, nil + } + + r, err = secp256k1.EcPrivKeyTweakAdd(ctx, s, scalarOffset) + if err != nil { + return nil, err + } + if r != 1 { + return nil, ErrPrivKeyTweakAdd + } + } + + return s, nil +} + +func CreateBlindValueProof( + rng func() ([]byte, error), + valueBlindingFactor []byte, + amount uint64, + valueCommitment []byte, + assetCommitment []byte, +) ([]byte, error) { + var vbf []byte + if valueBlindingFactor != nil { + vbf = make([]byte, len(valueBlindingFactor)) + copy(vbf, valueBlindingFactor) + } + + var vc []byte + if valueCommitment != nil { + vc = make([]byte, len(valueCommitment)) + copy(vc, valueCommitment) + } + + var ac []byte + if assetCommitment != nil { + ac = make([]byte, len(assetCommitment)) + copy(ac, assetCommitment) + } + + ctx, _ := secp256k1.ContextCreate(secp256k1.ContextBoth) + defer secp256k1.ContextDestroy(ctx) + + r, err := rng() + if err != nil { + return nil, err + } + + var nonce [32]byte + copy(nonce[:], r) + + commit, err := secp256k1.CommitmentParse(ctx, vc) + if err != nil { + return nil, err + } + + gen, err := secp256k1.GeneratorParse(ctx, ac) + if err != nil { + return nil, err + } + + var vbf32 [32]byte + copy(vbf32[:], vbf) + + return secp256k1.RangeProofSign( + ctx, + amount, + commit, + vbf32, + nonce, + -1, + 0, + amount, + nil, + nil, + gen, + ) +} + +func CreateBlindAssetProof( + asset []byte, + assetCommitment []byte, + assetBlinder []byte, +) ([]byte, error) { + var a []byte + if asset != nil { + a = make([]byte, len(asset)) + copy(a, asset) + } + + var ac []byte + if assetCommitment != nil { + ac = make([]byte, len(assetCommitment)) + copy(ac, assetCommitment) + } + + var ab []byte + if assetBlinder != nil { + ab = make([]byte, len(assetBlinder)) + copy(ab, assetBlinder) + } + + ctx, _ := secp256k1.ContextCreate(secp256k1.ContextBoth) + defer secp256k1.ContextDestroy(ctx) + + fixedAssetTag, err := secp256k1.FixedAssetTagParse(a) + if err != nil { + return nil, err + } + fixedInputTags := []*secp256k1.FixedAssetTag{fixedAssetTag} + + maxIterations := 100 + proof, inputIndex, err := secp256k1.SurjectionProofInitialize( + ctx, + fixedInputTags, + 1, + fixedAssetTag, + maxIterations, + Zero, + ) + if err != nil { + return nil, err + } + + gen, err := secp256k1.GeneratorGenerate(ctx, a) + if err != nil { + return nil, err + } + assetGen := []*secp256k1.Generator{gen} + + blindedAssetGen, err := secp256k1.GeneratorParse(ctx, ac) + if err != nil { + return nil, err + } + + err = secp256k1.SurjectionProofGenerate( + ctx, + proof, + assetGen, + blindedAssetGen, + inputIndex, + Zero, + ab, + ) + if err != nil { + return nil, err + } + + if !secp256k1.SurjectionProofVerify( + ctx, + proof, + assetGen, + blindedAssetGen, + ) { + return nil, errors.New("invalid surjection proof") + } + + return proof.Bytes(), nil +} + +func VerifyBlindValueProof( + value int64, + valueCommitment []byte, + blindValueProof []byte, + assetCommitment []byte, +) (bool, error) { + var vc []byte + if valueCommitment != nil { + vc = make([]byte, len(valueCommitment)) + copy(vc, valueCommitment) + } + + var bvp []byte + if blindValueProof != nil { + bvp = make([]byte, len(blindValueProof)) + copy(bvp, blindValueProof) + } + + var ac []byte + if assetCommitment != nil { + ac = make([]byte, len(assetCommitment)) + copy(ac, assetCommitment) + } + + ctx, _ := secp256k1.ContextCreate(secp256k1.ContextBoth) + defer secp256k1.ContextDestroy(ctx) + + commitment, err := secp256k1.CommitmentParse(ctx, vc) + if err != nil { + return false, err + } + + assetGenerator, err := secp256k1.GeneratorParse(ctx, ac) + if err != nil { + return false, err + } + + valid, minValue, _ := secp256k1.RangeProofVerify( + ctx, + bvp, + commitment, + nil, + assetGenerator, + ) + + return valid && int64(minValue) == value, nil +} + +func VerifyBlindAssetProof( + asset []byte, + blindAssetProof []byte, + assetCommitment []byte, +) (bool, error) { + var bap []byte + if blindAssetProof != nil { + bap = make([]byte, len(blindAssetProof)) + copy(bap, blindAssetProof) + } + + var ac []byte + if assetCommitment != nil { + ac = make([]byte, len(assetCommitment)) + copy(ac, assetCommitment) + } + + ctx, _ := secp256k1.ContextCreate(secp256k1.ContextBoth) + defer secp256k1.ContextDestroy(ctx) + + surjectionProof, err := secp256k1.SurjectionProofParse(ctx, bap) + if err != nil { + return false, err + } + + blindAssetGen, err := secp256k1.GeneratorParse(ctx, ac) + if err != nil { + return false, err + } + + assetGen, err := secp256k1.GeneratorGenerate(ctx, asset) + if err != nil { + return false, err + } + generators := []*secp256k1.Generator{assetGen} + + if !secp256k1.SurjectionProofVerify(ctx, surjectionProof, generators, blindAssetGen) { + return false, nil + } + + return true, nil +} diff --git a/confidential/confidential_test.go b/confidential/confidential_test.go index f9d9e28..fa9e792 100644 --- a/confidential/confidential_test.go +++ b/confidential/confidential_test.go @@ -377,3 +377,67 @@ func TestVerifySurjectionProof(t *testing.T) { assert.Equal(t, expectedValid, isValid) } } + +func TestSurjectionProofArgsInputsToUse(t *testing.T) { + type fields struct { + OutputAsset []byte + OutputAssetBlindingFactor []byte + InputAssets [][]byte + InputAssetBlindingFactors [][]byte + Seed []byte + NumberOfTargets int + } + tests := []struct { + name string + fields fields + want int + }{ + { + name: "1", + fields: fields{ + InputAssets: [][]byte{{}, {}}, + NumberOfTargets: 0, + }, + want: 2, + }, + { + name: "2", + fields: fields{ + InputAssets: [][]byte{{}, {}}, + NumberOfTargets: 1, + }, + want: 1, + }, + { + name: "3", + fields: fields{ + InputAssets: [][]byte{{}, {}, {}}, + NumberOfTargets: 0, + }, + want: 3, + }, + { + name: "4", + fields: fields{ + InputAssets: [][]byte{{}, {}, {}, {}}, + NumberOfTargets: 0, + }, + want: 3, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := SurjectionProofArgs{ + OutputAsset: tt.fields.OutputAsset, + OutputAssetBlindingFactor: tt.fields.OutputAssetBlindingFactor, + InputAssets: tt.fields.InputAssets, + InputAssetBlindingFactors: tt.fields.InputAssetBlindingFactors, + Seed: tt.fields.Seed, + NumberOfTargets: tt.fields.NumberOfTargets, + } + if got := a.nInputsToUse(); got != tt.want { + t.Errorf("nInputsToUse() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/go.mod b/go.mod index 61911c2..b47c42a 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,9 @@ require ( github.com/btcsuite/btcd v0.21.0-beta github.com/btcsuite/btcutil v1.0.2 github.com/btcsuite/btcutil/psbt v1.0.2 + github.com/go-ozzo/ozzo-validation/v4 v4.3.0 github.com/stretchr/testify v1.7.0 github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941 - github.com/vulpemventures/go-secp256k1-zkp v1.1.3 + github.com/vulpemventures/go-secp256k1-zkp v1.1.5 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad ) diff --git a/go.sum b/go.sum index f6225c3..dc5dff6 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.21.0-beta h1:At9hIZdJW0s9E/fAz28nrz6AmcNlSVucCH796ZteX1M= github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= @@ -25,6 +27,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -42,6 +46,7 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -49,6 +54,10 @@ github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941 h1:CTcw8 github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941/go.mod h1:GXBJykxW2kUcktGdsgyay7uwwWvkljASfljNcT0mbh8= github.com/vulpemventures/go-secp256k1-zkp v1.1.3 h1:Jy/OxkPYJYIFrwJkXr5vk86h9ObpqLR+3L5KHWX7Ah0= github.com/vulpemventures/go-secp256k1-zkp v1.1.3/go.mod h1:zo7CpgkuPgoe7fAV+inyxsI9IhGmcoFgyD8nqZaPSOM= +github.com/vulpemventures/go-secp256k1-zkp v1.1.4 h1:qXK7dTXQ4xTyN8xHjx5wXsjNbo5h6gTWCrZf1YoDPx4= +github.com/vulpemventures/go-secp256k1-zkp v1.1.4/go.mod h1:zo7CpgkuPgoe7fAV+inyxsI9IhGmcoFgyD8nqZaPSOM= +github.com/vulpemventures/go-secp256k1-zkp v1.1.5 h1:oG1kO8ibVQ1wOvYcnFyuI+2YqnEZluXdRwkOPJlHBQM= +github.com/vulpemventures/go-secp256k1-zkp v1.1.5/go.mod h1:zo7CpgkuPgoe7fAV+inyxsI9IhGmcoFgyD8nqZaPSOM= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/internal/bufferutil/deserializer.go b/internal/bufferutil/deserializer.go index 5fe8a79..85a5dad 100644 --- a/internal/bufferutil/deserializer.go +++ b/internal/bufferutil/deserializer.go @@ -15,6 +15,11 @@ func NewDeserializer(buffer *bytes.Buffer) *Deserializer { return &Deserializer{buffer} } +// ReadToEnd returns bytes left in buffer +func (d *Deserializer) ReadToEnd() []byte { + return d.buffer.Bytes() +} + // ReadUint8 reads a uint8 value from reader's buffer. func (d *Deserializer) ReadUint8() (uint8, error) { return BinarySerializer.Uint8(d.buffer) diff --git a/pset/pset_test.go b/pset/pset_test.go index 62d67d0..8942512 100644 --- a/pset/pset_test.go +++ b/pset/pset_test.go @@ -1544,7 +1544,7 @@ func signTransaction( } for i, in := range p.Inputs { - if err := updater.AddInSighashType(txscript.SigHashAll, i); err != nil { + if err := updater.AddInSighashType(txscript.SigHashAll|transaction.SighashRangeproof, i); err != nil { return err } @@ -1564,10 +1564,10 @@ func signTransaction( i, script, prevout.Value, - txscript.SigHashAll, + txscript.SigHashAll|transaction.SighashRangeproof, ) } else { - sigHash, err = p.UnsignedTx.HashForSignature(i, script, txscript.SigHashAll) + sigHash, err = p.UnsignedTx.HashForSignature(i, script, txscript.SigHashAll|transaction.SighashRangeproof) if err != nil { return err } @@ -1577,7 +1577,7 @@ func signTransaction( if err != nil { return err } - sigWithHashType := append(sig.Serialize(), byte(txscript.SigHashAll)) + sigWithHashType := append(sig.Serialize(), byte(txscript.SigHashAll|transaction.SighashRangeproof)) var witPubkeyScript []byte var witScript []byte diff --git a/pset/updater.go b/pset/updater.go index e2d3bb1..4874ec9 100644 --- a/pset/updater.go +++ b/pset/updater.go @@ -36,7 +36,7 @@ type Updater struct { } // NewUpdater returns a new instance of Updater, if the passed Psbt struct is -// in a valid form, else an error. +// in a valid form, else an error.a func NewUpdater(p *Pset) (*Updater, error) { if err := p.SanityCheck(); err != nil { return nil, err diff --git a/psetv2-test/psetv2_test.go b/psetv2-test/psetv2_test.go new file mode 100644 index 0000000..ff60b37 --- /dev/null +++ b/psetv2-test/psetv2_test.go @@ -0,0 +1,184 @@ +package psetv2test + +import ( + "testing" + "time" + + "github.com/vulpemventures/go-elements/confidential" + + "github.com/vulpemventures/go-elements/psetv2" + + "github.com/btcsuite/btcd/btcec" + "github.com/vulpemventures/go-elements/elementsutil" + "github.com/vulpemventures/go-elements/network" + "github.com/vulpemventures/go-elements/payment" + "github.com/vulpemventures/go-elements/transaction" +) + +func TestBroadcastBlindedTx(t *testing.T) { + blindingPrivateKey, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Fatal(err) + } + blindingPublicKey := blindingPrivateKey.PubKey() + + privkey, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Fatal(err) + } + pubkey := privkey.PubKey() + p2wpkh := payment.FromPublicKey(pubkey, &network.Regtest, blindingPublicKey) + address, _ := p2wpkh.ConfidentialWitnessPubKeyHash() + + // Fund sender address. + if _, err := faucet(address); err != nil { + t.Fatal(err) + } + time.Sleep(time.Second * 3) + + // Retrieve sender utxos. + utxos, err := unspents(address) + if err != nil { + t.Fatal(err) + } + + creatorRole, err := psetv2.NewCreatorRole(nil) + if err != nil { + t.Fatal(err) + } + + pset, err := creatorRole.Create() + if err != nil { + t.Fatal(err) + } + + // The transaction will have 1 input and 3 outputs. + txInputHash := elementsutil.ReverseBytes(h2b(utxos[0]["txid"].(string))) + txInputIndex := uint32(utxos[0]["vout"].(float64)) + txInput := transaction.NewTxInput(txInputHash, txInputIndex) + + receiverValue, _ := elementsutil.SatoshiToElementsValue(60000000) + receiverScript := h2b("76a91439397080b51ef22c59bd7469afacffbeec0da12e88ac") + receiverOutput := transaction.NewTxOutput(lbtc, receiverValue, receiverScript) + + changeScript := p2wpkh.WitnessScript + changeValue, _ := elementsutil.SatoshiToElementsValue(39999500) + changeOutput := transaction.NewTxOutput(lbtc, changeValue, changeScript) + + inputArgs := []psetv2.InputArg{ + { + TimeLock: nil, + TxInput: *txInput, + }, + } + outputBlindingPrivKey1, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Fatal(err) + } + outputBlindingPubKey1 := outputBlindingPrivKey1.PubKey().SerializeCompressed() + + outputBlindingPrivKey2, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + t.Fatal(err) + } + outputBlindingPubKey2 := outputBlindingPrivKey2.PubKey().SerializeCompressed() + + outputArgs := []psetv2.OutputArg{ + { + BlinderIndex: 0, + BlindingPubKey: outputBlindingPubKey1, + TxOutput: *receiverOutput, + }, + { + BlinderIndex: 0, + BlindingPubKey: outputBlindingPubKey2, + TxOutput: *changeOutput, + }, + } + constructor, err := psetv2.NewConstructorRole( + pset, + inputArgs, + outputArgs, + false, + ) + if err != nil { + t.Fatal(err) + } + if err = constructor.Construct(); err != nil { + t.Fatal(err) + } + + updaterRole, err := psetv2.NewUpdaterRole(pset) + if err != nil { + t.Fatal(err) + } + + prevTxHex, err := fetchTx(utxos[0]["txid"].(string)) + if err != nil { + t.Fatal(err) + } + prevTx, _ := transaction.NewTxFromHex(prevTxHex) + assetCommitment := h2b(utxos[0]["assetcommitment"].(string)) + valueCommitment := h2b(utxos[0]["valuecommitment"].(string)) + witnessUtxo := &transaction.TxOutput{ + Asset: assetCommitment, + Value: valueCommitment, + Script: p2wpkh.WitnessScript, + Nonce: prevTx.Outputs[txInputIndex].Nonce, + RangeProof: prevTx.Outputs[txInputIndex].RangeProof, + SurjectionProof: prevTx.Outputs[txInputIndex].SurjectionProof, + } + if err := updaterRole.AddInWitnessUtxo(witnessUtxo, 0); err != nil { + t.Fatal(err) + } + + blinderSvc := confidential.NewBlinder() + prevOutUnBlindingInfos := []psetv2.UnBlindingInfo{ + { + OutIndex: 0, + OutPrivateBlindingKey: blindingPrivateKey.Serialize(), + }, + } + blinderRole, err := psetv2.NewBlinderRole( + pset, + blinderSvc, + prevOutUnBlindingInfos, + psetv2.IssuanceBlindingPrivateKeys{}, + nil, + ) + if err != nil { + t.Fatal(err) + } + + if err := blinderRole.Blind(); err != nil { + t.Fatal(err) + } + + outUnblindingInfo := []psetv2.UnBlindingInfo{ + { + OutIndex: 0, + OutPrivateBlindingKey: outputBlindingPrivKey1.Serialize(), + }, + { + OutIndex: 1, + OutPrivateBlindingKey: outputBlindingPrivKey2.Serialize(), + }, + } + if !blinderRole.Verify(outUnblindingInfo) { + t.Fatal("blinding invalid") + } + + if err := addFeesToTransaction(pset, 500); err != nil { + t.Fatal(err) + } + + prvKeys := []*btcec.PrivateKey{privkey} + scripts := [][]byte{p2wpkh.Script} + if err := signTransaction(pset, prvKeys, scripts, true, nil, blinderSvc); err != nil { + t.Fatal(err) + } + + if _, err = broadcastTransaction(pset, blinderSvc); err != nil { + t.Fatal(err) + } +} diff --git a/psetv2-test/test_util.go b/psetv2-test/test_util.go new file mode 100644 index 0000000..8e1e4ca --- /dev/null +++ b/psetv2-test/test_util.go @@ -0,0 +1,280 @@ +package psetv2test + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "os" + "strings" + "time" + + "github.com/vulpemventures/go-elements/confidential" + + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/txscript" + + "github.com/vulpemventures/go-elements/psetv2" + "github.com/vulpemventures/go-elements/transaction" + + "github.com/vulpemventures/go-elements/elementsutil" + "github.com/vulpemventures/go-elements/network" +) + +var lbtc = append( + []byte{0x01}, + elementsutil.ReverseBytes(h2b(network.Regtest.AssetID))..., +) + +func faucet(address string) (string, error) { + baseURL, err := apiBaseUrl() + if err != nil { + return "", err + } + url := fmt.Sprintf("%s/faucet", baseURL) + payload := map[string]string{"address": address} + body, _ := json.Marshal(payload) + + resp, err := http.Post(url, "application/json", bytes.NewBuffer(body)) + if err != nil { + return "", err + } + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + if res := string(data); len(res) <= 0 || strings.Contains(res, "sendtoaddress") { + return "", fmt.Errorf("cannot fund address with faucet: %s", res) + } + + respBody := map[string]string{} + if err := json.Unmarshal(data, &respBody); err != nil { + return "", err + } + return respBody["txId"], nil +} + +func apiBaseUrl() (string, error) { + u, ok := os.LookupEnv("API_URL") + if !ok { + return "", errors.New("API_URL environment variable is not set") + } + return u, nil +} + +func unspents(address string) ([]map[string]interface{}, error) { + getUtxos := func(address string) ([]interface{}, error) { + baseUrl, err := apiBaseUrl() + if err != nil { + return nil, err + } + url := fmt.Sprintf("%s/address/%s/utxo", baseUrl, address) + resp, err := http.Get(url) + if err != nil { + return nil, err + } + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var respBody interface{} + if err := json.Unmarshal(data, &respBody); err != nil { + return nil, err + } + return respBody.([]interface{}), nil + } + + utxos := []map[string]interface{}{} + for len(utxos) <= 0 { + time.Sleep(1 * time.Second) + u, err := getUtxos(address) + if err != nil { + return nil, err + } + for _, unspent := range u { + utxo := unspent.(map[string]interface{}) + utxos = append(utxos, utxo) + } + } + + return utxos, nil +} + +func b2h(buf []byte) string { + return hex.EncodeToString(buf) +} + +func h2b(str string) []byte { + buf, _ := hex.DecodeString(str) + return buf +} + +func addFeesToTransaction(p *psetv2.Pset, feeAmount uint64) error { + feeScript := []byte{} + feeValue, _ := elementsutil.SatoshiToElementsValue(feeAmount) + feeOutput := transaction.NewTxOutput(lbtc, feeValue, feeScript) + + outputArg := psetv2.OutputArg{TxOutput: *feeOutput} + updaterRole, _ := psetv2.NewUpdaterRole(p) + + return updaterRole.AddOutput(outputArg) +} + +func fetchTx(txId string) (string, error) { + baseUrl, err := apiBaseUrl() + if err != nil { + return "", err + } + url := fmt.Sprintf("%s/tx/%s/hex", baseUrl, txId) + + resp, err := http.Get(url) + if err != nil { + return "", err + } + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + + return string(data), nil +} + +type signOpts struct { + pubkeyScript []byte + script []byte +} + +func signTransaction( + p *psetv2.Pset, + privKeys []*btcec.PrivateKey, + scripts [][]byte, + forWitness bool, + opts *signOpts, + blinderSvc confidential.Blinder, +) error { + updaterRole, err := psetv2.NewUpdaterRole(p) + if err != nil { + return err + } + + signerRole, err := psetv2.NewSignerRole(p, blinderSvc) + if err != nil { + return err + } + + for k, v := range p.Inputs { + updaterRole.AddInSighashType(txscript.SigHashAll|transaction.SighashRangeproof, k) + + var prevout *transaction.TxOutput + if v.WitnessUtxo() != nil { + prevout = v.WitnessUtxo() + } else { + prevout = v.NonWitnessUtxo().Outputs[k] + } + prvkey := privKeys[k] + pubkey := prvkey.PubKey() + script := scripts[k] + + var sigHash [32]byte + tx, err := p.UnsignedTx(blinderSvc) + if err != nil { + return err + } + if forWitness { + sigHash = tx.HashForWitnessV0( + k, + script, + prevout.Value, + txscript.SigHashAll|transaction.SighashRangeproof, + ) + } else { + sigHash, err = tx.HashForSignature(k, script, txscript.SigHashAll|transaction.SighashRangeproof) + if err != nil { + return err + } + } + + sig, err := prvkey.Sign(sigHash[:]) + if err != nil { + return err + } + sigWithHashType := append(sig.Serialize(), byte(txscript.SigHashAll|transaction.SighashRangeproof)) + + var witPubkeyScript []byte + var witScript []byte + if opts != nil { + witPubkeyScript = opts.pubkeyScript + witScript = opts.script + } + + if _, err := signerRole.SignInput( + k, + sigWithHashType, + pubkey.SerializeCompressed(), + witPubkeyScript, + witScript, + ); err != nil { + return err + } + } + + valid, err := signerRole.ValidateAllSignatures() + if err != nil { + return err + } + if !valid { + return errors.New("invalid signatures") + } + + return nil +} + +func broadcastTransaction(p *psetv2.Pset, blinderSvc psetv2.Blinder) (string, error) { + finalizerRole := psetv2.NewFinalizerRole(p) + if err := finalizerRole.FinalizeAll(p); err != nil { + return "", err + } + // Extract the final signed transaction from the Pset wrapper. + + extractorRole := psetv2.NewExtractorRole(p, blinderSvc) + finalTx, err := extractorRole.Extract() + if err != nil { + return "", err + } + // Serialize the transaction and try to broadcast. + txHex, err := finalTx.ToHex() + if err != nil { + return "", err + } + + return broadcast(txHex) +} + +func broadcast(txHex string) (string, error) { + baseUrl, err := apiBaseUrl() + if err != nil { + return "", err + } + url := fmt.Sprintf("%s/tx", baseUrl) + + resp, err := http.Post(url, "text/plain", strings.NewReader(txHex)) + if err != nil { + return "", err + } + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + + res := string(data) + if len(res) <= 0 || strings.Contains(res, "sendrawtransaction") { + return "", fmt.Errorf("failed to broadcast tx: %s", res) + } + return res, nil +} diff --git a/psetv2/bip32.go b/psetv2/bip32.go new file mode 100644 index 0000000..9c553fc --- /dev/null +++ b/psetv2/bip32.go @@ -0,0 +1,77 @@ +package psetv2 + +import ( + "bytes" + "encoding/binary" +) + +//TODO add ref to btcutil + +// DerivationPathWithPubKey encapsulates the data for the input and output +// DerivationPathWithPubKey key-value fields. +type DerivationPathWithPubKey struct { + // PubKey is the raw pubkey serialized in compressed format. + PubKey []byte + + // MasterKeyFingerprint is the finger print of the master pubkey. + MasterKeyFingerprint uint32 + + // Bip32Path is the BIP 32 path with child index as a distinct integer. + Bip32Path []uint32 +} + +// checkValid ensures that the PubKey in the DerivationPathWithPubKey struct is valid. +func (pb *DerivationPathWithPubKey) checkValid() bool { + return validatePubkey(pb.PubKey) +} + +// Bip32Sorter implements sort.Interface for the DerivationPathWithPubKey struct. +type Bip32Sorter []*DerivationPathWithPubKey + +func (s Bip32Sorter) Len() int { return len(s) } + +func (s Bip32Sorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func (s Bip32Sorter) Less(i, j int) bool { + return bytes.Compare(s[i].PubKey, s[j].PubKey) < 0 +} + +// readBip32Derivation deserializes a byte slice containing chunks of 4 byte +// little endian encodings of uint32 values, the first of which is the +// masterkeyfingerprint and the remainder of which are the derivation path. +func readBip32Derivation(path []byte) (uint32, []uint32, error) { + + if len(path)%4 != 0 || len(path)/4-1 < 1 { + return 0, nil, ErrInvalidPsbtFormat + } + + masterKeyInt := binary.LittleEndian.Uint32(path[:4]) + + var paths []uint32 + for i := 4; i < len(path); i += 4 { + paths = append(paths, binary.LittleEndian.Uint32(path[i:i+4])) + } + + return masterKeyInt, paths, nil +} + +// SerializeBIP32Derivation takes a master key fingerprint as defined in BIP32, +// along with a path specified as a list of uint32 values, and returns a +// bytestring specifying the derivation in the format required by BIP174: // +// master key fingerprint (4) || child index (4) || child index (4) || .... +func SerializeBIP32Derivation(masterKeyFingerprint uint32, + bip32Path []uint32) []byte { + + var masterKeyBytes [4]byte + binary.LittleEndian.PutUint32(masterKeyBytes[:], masterKeyFingerprint) + + derivationPath := make([]byte, 0, 4+4*len(bip32Path)) + derivationPath = append(derivationPath, masterKeyBytes[:]...) + for _, path := range bip32Path { + var pathbytes [4]byte + binary.LittleEndian.PutUint32(pathbytes[:], path) + derivationPath = append(derivationPath, pathbytes[:]...) + } + + return derivationPath +} diff --git a/psetv2/blinder_role.go b/psetv2/blinder_role.go new file mode 100644 index 0000000..f352517 --- /dev/null +++ b/psetv2/blinder_role.go @@ -0,0 +1,880 @@ +package psetv2 + +import ( + "bytes" + "errors" + + "github.com/vulpemventures/go-elements/elementsutil" + + "github.com/btcsuite/btcd/btcec" + + "github.com/vulpemventures/go-elements/transaction" +) + +var ( + ErrOutputsToBeBlindedNotOwned = errors.New("outputs that are supposed to be blinded are not owned by blinder") + ErrOwnerDidntProvidedOutputBlindingData = errors.New("owner didnt provided output blinding data") + ErrNeedUtxo = errors.New("input needs utxo") + ErrInvalidBlinder = errors.New("invalid blinder") + ErrGenerateSurjectionProof = errors.New("failed to generate surjection proof, please retry") +) + +type Blinder interface { + AssetCommitment(asset, factor []byte) ([]byte, error) + ValueCommitment(value uint64, generator, factor []byte) ([]byte, error) + NonceHash(pubKey, privKey []byte) ([32]byte, error) + RangeProof(value uint64, + nonce [32]byte, + asset []byte, + assetBlindingFactor []byte, + valueBlindFactor [32]byte, + valueCommit []byte, + scriptPubkey []byte, + minValue uint64, + exp int, + minBits int, + ) ([]byte, error) + SurjectionProof( + outputAsset []byte, + outputAssetBlindingFactor []byte, + inputAssets [][]byte, + inputAssetBlindingFactors [][]byte, + seed []byte, + numberOfTargets int, + ) ([]byte, bool) + VerifySurjectionProof( + inputAssets [][]byte, + inputAssetBlindingFactors [][]byte, + outputAsset []byte, + outputAssetBlindingFactor []byte, + proof []byte, + ) bool + SubtractScalars(a []byte, b []byte) ([]byte, error) + ComputeAndAddToScalarOffset( + scalar []byte, + value uint64, + assetBlinder []byte, + valueBlinder []byte, + ) ([]byte, error) + CreateBlindValueProof( + rng func() ([]byte, error), + valueBlindingFactor []byte, + amount uint64, + valueCommitment []byte, + assetCommitment []byte, + ) ([]byte, error) + CreateBlindAssetProof( + asset []byte, + assetCommitment []byte, + assetBlinder []byte, + ) ([]byte, error) + VerifyBlindValueProof( + value int64, + valueCommitment []byte, + blindValueProof []byte, + assetCommitment []byte, + ) (bool, error) + VerifyBlindAssetProof( + asset []byte, + blindAssetProof []byte, + assetCommitment []byte, + ) (bool, error) + UnblindOutputWithKey( + out *transaction.TxOutput, + blindKey []byte, + ) (uint64, []byte, []byte, []byte, error) +} + +type BlinderRole struct { + pset *Pset + blinderSvc Blinder + issuanceBlindingPrivateKeys IssuanceBlindingPrivateKeys + rng randomNumberGenerator + inputTxOutSecrets map[psetInputIndex]InputSecrets +} + +// NewBlinderRole provides methods to Blind pset and to verify it blinding was correct +//blinderSvc is used in order to decouple from confidential pkg +//prevOutBlindingInfos is used to find secrets data that are going to be used for blinding corresponding output +//issuanceBlindingPrivateKeys is used to blind asset and token if it is issuance transaction +//rng allows used to pass custom random number generator function +func NewBlinderRole( + pset *Pset, + blinderSvc Blinder, + prevOutBlindingInfos []UnBlindingInfo, + issuanceBlindingPrivateKeys IssuanceBlindingPrivateKeys, + rng randomNumberGenerator, +) (*BlinderRole, error) { + var gen randomNumberGenerator + if rng == nil { + gen = generateRandomNumber + } else { + gen = rng + } + + //unblind previous outputs + inputTxOutSecrets, err := getInputSecrets(pset, prevOutBlindingInfos, blinderSvc) + if err != nil { + return nil, err + } + + return &BlinderRole{ + pset: pset, + blinderSvc: blinderSvc, + issuanceBlindingPrivateKeys: issuanceBlindingPrivateKeys, + rng: gen, + inputTxOutSecrets: inputTxOutSecrets, + }, nil +} + +func (b *BlinderRole) Blind() error { + toBeOrAlreadyBlinded, numOfBlindedOutputs, ownedOutputsToBeBlindedIndexes := b.blindCheck() + + //if all blinded return + if numOfBlindedOutputs == toBeOrAlreadyBlinded { + return nil + } + + //if not all outputs are blinded but inputs secrets are not provided return error + if len(ownedOutputsToBeBlindedIndexes) == 0 { + return ErrOutputsToBeBlindedNotOwned + } + + inputScalar, inputAssets, inputAssetBlinders, err := b.processInputs() + if err != nil { + return err + } + + //blind outputs + outputScalar, lastBlinded, err := b.blindOutputs( + ownedOutputsToBeBlindedIndexes, + toBeOrAlreadyBlinded, + numOfBlindedOutputs, + inputScalar, + inputAssets, + inputAssetBlinders, + ) + if err != nil { + return err + } + + if !lastBlinded && outputScalar != nil { + offset, err := b.blinderSvc.SubtractScalars(outputScalar, inputScalar) + if err != nil { + return err + } + + b.pset.Global.scalars = append(b.pset.Global.scalars, offset) + } + + return nil +} + +func (b *BlinderRole) Verify(outUnBlindingInfos []UnBlindingInfo) bool { + if len(b.pset.Inputs) != len(b.inputTxOutSecrets) { + return false + } + + outCount := 0 + for _, out := range b.pset.Outputs { + if out.IsBlinded() { + outCount++ + } + } + if len(outUnBlindingInfos) != outCount { + return false + } + + inAssets := make([][]byte, 0, len(b.pset.Inputs)) + inIssuanceAssets := make([][]byte, 0) + inAssetBlinders := make([][]byte, 0, len(b.pset.Inputs)) + inIssuanceAssetBlinders := make([][]byte, 0) + for i, inputBlindData := range b.inputTxOutSecrets { + inAssets = append(inAssets, inputBlindData.Asset) + inAssetBlinders = append(inAssetBlinders, inputBlindData.AssetBlindingFactor) + + if input := b.pset.Inputs[i]; input.isIssuance() { + if input.isIssuanceBlinded() { + issuanceUnBlindingKeys := [][]byte{ + b.issuanceBlindingPrivateKeys.AssetKey, + b.issuanceBlindingPrivateKeys.TokenKey, + } + unblinded, err := b.unblindIssuance(input, issuanceUnBlindingKeys) + if err != nil { + return false + } + inIssuanceAssets = append(inIssuanceAssets, unblinded.Asset.Asset) + inIssuanceAssetBlinders = append( + inIssuanceAssetBlinders, + unblinded.Asset.AssetBlindingFactor, + ) + + if input.issuanceInflationKeys != nil { + inIssuanceAssets = append(inIssuanceAssets, unblinded.Token.Asset) + inIssuanceAssetBlinders = append( + inIssuanceAssetBlinders, + unblinded.Token.AssetBlindingFactor, + ) + } + } else { + iss, err := transaction.NewTxIssuanceFromInput(&transaction.TxInput{ + Hash: input.previousTxid, + Index: *input.previousOutputIndex, + Issuance: &transaction.TxIssuance{ + AssetEntropy: input.issuanceAssetEntropy, + }, + }) + if err != nil { + return false + } + asset, err := iss.GenerateAsset() + if err != nil { + return false + } + inIssuanceAssets = append(inIssuanceAssets, asset) + inIssuanceAssetBlinders = append( + inIssuanceAssetBlinders, + transaction.Zero[:], + ) + + if input.issuanceInflationKeys != nil { + token, err := iss.GenerateReissuanceToken(0) + if err != nil { + return false + } + inIssuanceAssets = append(inIssuanceAssets, token) + inIssuanceAssetBlinders = append( + inIssuanceAssetBlinders, + transaction.Zero[:], + ) + } + } + } + } + + inAssets = append(inAssets, inIssuanceAssets...) + inAssetBlinders = append(inAssetBlinders, inIssuanceAssetBlinders...) + for _, v := range outUnBlindingInfos { + out := b.pset.Outputs[v.OutIndex] + if out.IsBlinded() { + _, asset, _, abf, err := b.blinderSvc.UnblindOutputWithKey( + &transaction.TxOutput{ + Asset: out.outputAssetCommitment, + Value: out.outputValueCommitment, + Script: out.outputScript, + Nonce: out.outputEcdhPubkey, + RangeProof: out.outputValueRangeproof, + SurjectionProof: out.outputAssetSurjectionProof, + }, + v.OutPrivateBlindingKey, + ) + if err != nil { + return false + } + + if !b.blinderSvc.VerifySurjectionProof( + inAssets, + inAssetBlinders, + asset, + abf, + out.outputAssetSurjectionProof, + ) { + return false + } + } + } + + return true +} + +type randomNumberGenerator func() ([]byte, error) + +type psetInputIndex int + +// UnBlindingInfo holds data necessary for unblinding outputs +type UnBlindingInfo struct { + // OutIndex defines which previous output of pset input in going to be unblinded + OutIndex int + // OutPrivateBlindingKey is private blinding key of inputs previous output + OutPrivateBlindingKey []byte +} + +// IssuanceBlindingPrivateKeys stores the AssetKey and TokenKey that will be used in the BlinderRole. +type IssuanceBlindingPrivateKeys struct { + AssetKey []byte + TokenKey []byte +} + +// InputSecrets is the type returned by the functions that unblind tx +// outs. It contains the unblinded asset and value and also the respective +// blinding factors. +type InputSecrets struct { + Value uint64 + Asset []byte + ValueBlindingFactor []byte + AssetBlindingFactor []byte +} + +//getInputSecrets un-blinds previous outputs +func getInputSecrets( + pset *Pset, + prevOutBlindingInfos []UnBlindingInfo, + blinderSvc Blinder, +) (map[psetInputIndex]InputSecrets, error) { + inputTxOutSecrets := make(map[psetInputIndex]InputSecrets) + for _, v := range prevOutBlindingInfos { + if !pset.OwnerProvidedOutputBlindingInfo(v.OutIndex) { + return nil, ErrOwnerDidntProvidedOutputBlindingData + } + + input := pset.Inputs[v.OutIndex] + var prevout *transaction.TxOutput + + if input.nonWitnessUtxo != nil { + vout := input.previousOutputIndex + prevout = input.nonWitnessUtxo.Outputs[int(*vout)] + } else { + prevout = pset.Inputs[v.OutIndex].witnessUtxo + } + + value, asset, vbf, abf, err := blinderSvc.UnblindOutputWithKey( + prevout, + v.OutPrivateBlindingKey, + ) + if err != nil { + return nil, err + } + + inputTxOutSecrets[psetInputIndex(v.OutIndex)] = InputSecrets{ + Value: value, + Asset: asset, + ValueBlindingFactor: vbf, + AssetBlindingFactor: abf, + } + } + + return inputTxOutSecrets, nil +} + +func (b *BlinderRole) blindCheck() (int, int, []int) { + toBeOrAlreadyBlinded := 0 //number of outputs that are to be blinded or that are already blinded + numOfBlindedOutputs := 0 + ownedOutputsToBeBlindedIndexes := make([]int, 0) + for i, v := range b.pset.Outputs { + if v.IsBlinded() { + numOfBlindedOutputs++ + } + if v.ToBlind() { + toBeOrAlreadyBlinded++ + if _, ok := b.inputTxOutSecrets[psetInputIndex(*v.outputBlinderIndex)]; ok { + ownedOutputsToBeBlindedIndexes = append(ownedOutputsToBeBlindedIndexes, i) + } + } + } + + return toBeOrAlreadyBlinded, numOfBlindedOutputs, ownedOutputsToBeBlindedIndexes +} + +//processInputs loops through pset inputs in order to gather input assets, blinders +//and scalar that are to be used for blinding outputs, it also blinds owned issuance is any +func (b *BlinderRole) processInputs() ([]byte, [][]byte, [][]byte, error) { + var inputScalar []byte + inputAssets := make([][]byte, 0) + inputAssetBlinders := make([][]byte, 0) + + for i, v := range b.pset.Inputs { + utxo := v.GetUtxo() //TODO check if utxo's are blinded or not + if utxo == nil { + return nil, nil, nil, ErrNeedUtxo + } + asset := utxo.Asset + + if i <= len(b.inputTxOutSecrets)-1 { + offset, err := b.blinderSvc.ComputeAndAddToScalarOffset( + inputScalar, + b.inputTxOutSecrets[psetInputIndex(i)].Value, + b.inputTxOutSecrets[psetInputIndex(i)].AssetBlindingFactor, + b.inputTxOutSecrets[psetInputIndex(i)].ValueBlindingFactor, + ) + if err != nil { + return nil, nil, nil, err + } + inputScalar = offset + + inputAssets = append(inputAssets, b.inputTxOutSecrets[psetInputIndex(i)].Asset) + inputAssetBlinders = append(inputAssetBlinders, b.inputTxOutSecrets[psetInputIndex(i)].AssetBlindingFactor) + } else { + inputAssets = append(inputAssets, asset) + } + + offset, issuanceAsset, issuanceToken, issuanceAssetBlindingFactor, issuanceTokenBlindingFactor, err := + b.handleIssuance(inputScalar, v) + if err != nil { + return nil, nil, nil, err + } + inputScalar = offset + + if issuanceAsset != nil { + inputAssets = append(inputAssets, issuanceAsset) + } + if issuanceToken != nil { + inputAssets = append(inputAssets, issuanceToken) + } + if issuanceAssetBlindingFactor != nil { + inputAssetBlinders = append(inputAssetBlinders, issuanceAssetBlindingFactor) + } + if issuanceTokenBlindingFactor != nil { + inputAssetBlinders = append(inputAssetBlinders, issuanceTokenBlindingFactor) + } + } + + return inputScalar, inputAssets, inputAssetBlinders, nil +} + +//blindOutputs blinds owned outputs and return output scalar +func (b *BlinderRole) blindOutputs( + ownedOutputsToBeBlindedIndexes []int, + toBeOrAlreadyBlinded int, + numOfBlindedOutputs int, + inputScalar []byte, + inputAssets [][]byte, + inputAssetBlinders [][]byte, +) ([]byte, bool, error) { + var outputScalar []byte + lastOutputToBeBlinded := false + lastBlinded := false + for _, v := range ownedOutputsToBeBlindedIndexes { + output := b.pset.Outputs[v] + + if toBeOrAlreadyBlinded-numOfBlindedOutputs == 1 { + lastOutputToBeBlinded = true + } + + valueBlindingFactor, err := b.rng() + if err != nil { + return nil, false, err + } + + assetBlindingFactor, err := b.rng() + if err != nil { + return nil, false, err + } + + offset, err := b.blinderSvc.ComputeAndAddToScalarOffset( + outputScalar, + uint64(*output.outputAmount), + assetBlindingFactor, + valueBlindingFactor, + ) + outputScalar = offset + if err != nil { + return nil, false, err + } + + if lastOutputToBeBlinded { + subs, err := b.blinderSvc.SubtractScalars(outputScalar, inputScalar) + if err != nil { + return nil, false, err + } + outputScalar = subs + + subs, err = b.blinderSvc.SubtractScalars(valueBlindingFactor, outputScalar) + if err != nil { + return nil, false, err + } + valueBlindingFactor = subs + + for _, v := range b.pset.Global.scalars { + subs, err = b.blinderSvc.SubtractScalars(valueBlindingFactor, v) + if err != nil { + return nil, false, err + } + valueBlindingFactor = subs + } + + if bytes.Equal(valueBlindingFactor, transaction.Zero[:]) { + return nil, false, ErrInvalidBlinder + } + + b.pset.Global.scalars = nil + + lastBlinded = true + } + + assetCommitment, err := b.blinderSvc.AssetCommitment( + output.outputAsset[1:], + assetBlindingFactor, + ) + if err != nil { + return nil, false, err + } + + valueCommitment, err := b.blinderSvc.ValueCommitment( + uint64(*output.outputAmount), + assetCommitment[:], + valueBlindingFactor, + ) + if err != nil { + return nil, false, err + } + + var vbf32 [32]byte + copy(vbf32[:], valueBlindingFactor) + + ephemeralPrivKey, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + return nil, false, err + } + outputNonce := ephemeralPrivKey.PubKey().SerializeCompressed() + + nonce, err := b.blinderSvc.NonceHash( + output.outputBlindingPubkey, + ephemeralPrivKey.Serialize(), + ) + if err != nil { + return nil, false, err + } + + rangeProof, err := b.blinderSvc.RangeProof( + uint64(*output.outputAmount), + nonce, + output.outputAsset[1:], + assetBlindingFactor, + vbf32, + valueCommitment[:], + output.outputScript, + 1, + 0, + 52, + ) + if err != nil { + return nil, false, err + } + + blindValueProof, err := b.blinderSvc.CreateBlindValueProof( + b.rng, + valueBlindingFactor, + uint64(*output.outputAmount), + valueCommitment, + assetCommitment, + ) + if err != nil { + return nil, false, err + } + + randomSeed, err := b.rng() + if err != nil { + return nil, false, err + } + + surjectionProof, ok := b.blinderSvc.SurjectionProof( + output.outputAsset[1:], + assetBlindingFactor, + inputAssets, + inputAssetBlinders, + randomSeed, + 0, + ) + if !ok { + return nil, false, ErrGenerateSurjectionProof + } + + blindAssetProof, err := b.blinderSvc.CreateBlindAssetProof( + output.outputAsset[1:], + assetCommitment, + assetBlindingFactor, + ) + if err != nil { + return nil, false, err + } + + output.outputAssetCommitment = assetCommitment + output.outputValueCommitment = valueCommitment + output.outputEcdhPubkey = outputNonce + output.outputValueRangeproof = rangeProof + output.outputAssetSurjectionProof = surjectionProof + output.outputBlindValueProof = blindValueProof + output.outputBlindAssetProof = blindAssetProof + + b.pset.Outputs[v] = output + numOfBlindedOutputs++ + } + + return outputScalar, lastBlinded, nil +} + +func (b *BlinderRole) handleIssuance( + inputScalar []byte, + input Input, +) ([]byte, []byte, []byte, []byte, []byte, error) { + var err error + var issuanceAsset []byte + var issuanceToken []byte + var issuanceAssetBlindingFactor []byte + var issuanceTokenBlindingFactor []byte + + if input.isIssuance() && !input.isIssuanceBlinded() { + var entropy []byte + + if input.isReIssuance() { + entropy = input.issuanceAssetEntropy + } else { + issuanceInput := &transaction.TxInput{ + Index: *input.previousOutputIndex, + Hash: input.previousTxid, + Issuance: &transaction.TxIssuance{ + AssetEntropy: input.issuanceAssetEntropy, + }, + } + + issuance, err := transaction.NewTxIssuanceFromInput(issuanceInput) + if err != nil { + return nil, nil, nil, nil, nil, err + } + entropy = issuance.TxIssuance.AssetEntropy + } + + issuance := transaction.NewTxIssuanceFromEntropy(entropy) + issuanceAsset, err = issuance.GenerateAsset() + if err != nil { + return nil, nil, nil, nil, nil, err + } + + if input.issuanceBlindingNonce == nil && input.issuanceInflationKeys != nil { + var tokenFLag uint = 0 + if b.issuanceBlindingPrivateKeys.TokenKey != nil { + tokenFLag = 1 + } + + issuanceToken, err = issuance.GenerateReissuanceToken(tokenFLag) + if err != nil { + return nil, nil, nil, nil, nil, err + } + } + + //blind issuance + if b.shouldBlindIssuance() { + //blind issuance asset + valueCommitment, rangeProof, blindValueProof, abf, offset, err := b.blindIssuanceAsset( + inputScalar, + issuanceAsset, + uint64(*input.issuanceValue), + ) + if err != nil { + return nil, nil, nil, nil, nil, err + } + + issuanceAssetBlindingFactor = abf + inputScalar = offset + + input.issuanceValueCommitment = valueCommitment + input.issuanceValueRangeproof = rangeProof + input.issuanceBlindValueProof = blindValueProof + + //blind issuance token + valueCommitment, rangeProof, blindValueProof, abf, offset, err = b.blindIssuanceAsset( + inputScalar, + issuanceToken, + uint64(*input.issuanceInflationKeys), + ) + if err != nil { + return nil, nil, nil, nil, nil, err + } + + issuanceTokenBlindingFactor = abf + inputScalar = offset + + input.issuanceInflationKeysCommitment = valueCommitment + input.issuanceKeysRangeproof = rangeProof + input.issuanceBlindInflationKeysProof = blindValueProof + } + } + + return inputScalar, issuanceAsset, issuanceToken, issuanceAssetBlindingFactor, issuanceTokenBlindingFactor, nil +} + +func (b *BlinderRole) shouldBlindIssuance() bool { + return b.issuanceBlindingPrivateKeys.AssetKey != nil && + b.issuanceBlindingPrivateKeys.TokenKey != nil +} + +func (b *BlinderRole) blindIssuanceAsset( + inputScalar []byte, + asset []byte, + value uint64, +) ([]byte, []byte, []byte, []byte, []byte, error) { + vbf, err := b.rng() + if err != nil { + return nil, nil, nil, nil, nil, err + } + + abf := make([]byte, 32) + assetCommitment, err := b.blinderSvc.AssetCommitment(asset, abf) + if err != nil { + return nil, nil, nil, nil, nil, err + } + + offset, err := b.blinderSvc.ComputeAndAddToScalarOffset(inputScalar, value, abf, vbf) + if err != nil { + return nil, nil, nil, nil, nil, err + } + inputScalar = offset + + valueCommitment, err := b.blinderSvc.ValueCommitment( + value, + assetCommitment[:], + vbf, + ) + if err != nil { + return nil, nil, nil, nil, nil, err + } + + var vbf32 [32]byte + copy(vbf32[:], vbf) + + var nonce [32]byte + copy(nonce[:], b.issuanceBlindingPrivateKeys.AssetKey[:]) + + rangeProof, err := b.blinderSvc.RangeProof( + value, + nonce, + asset, + abf, + vbf32, + valueCommitment[:], + []byte{}, + 1, + 0, + 52, + ) + if err != nil { + return nil, nil, nil, nil, nil, err + } + + blindValueProof, err := b.blinderSvc.CreateBlindValueProof( + b.rng, + vbf, + value, + valueCommitment, + assetCommitment, + ) + if err != nil { + return nil, nil, nil, nil, nil, err + } + + return valueCommitment, rangeProof, blindValueProof, abf, offset, nil +} + +type UnblindIssuanceResult struct { + Asset *InputSecrets + Token *InputSecrets +} + +func (b *BlinderRole) unblindIssuance( + in Input, + blindKeys [][]byte, +) (*UnblindIssuanceResult, error) { + if len(blindKeys) <= 1 { + return nil, errors.New("missing asset blind private key") + } + if !in.isIssuance() { + return nil, errors.New("missing input issuance") + } + if !in.isIssuanceBlinded() { + return nil, errors.New("missing asset range proof") + } + + if in.issuanceInflationKeys != nil { + if len(in.issuanceBlindInflationKeysProof) <= 0 { + return nil, errors.New("missing token range proof") + } + if len(blindKeys) < 1 { + return nil, errors.New("missing token blind private key") + } + } + + asset, err := calcAssetHash(in) + if err != nil { + return nil, err + } + + amount, err := elementsutil.SatoshiToElementsValue(uint64(*in.issuanceValue)) + if err != nil { + return nil, err + } + outs := []*transaction.TxOutput{ + { + Asset: asset, + Value: amount, + RangeProof: in.issuanceValueRangeproof, + Script: make([]byte, 0), + }, + } + if in.issuanceInflationKeys != nil { + token, err := calcTokenHash(in) + if err != nil { + return nil, err + } + + amount, err := elementsutil.SatoshiToElementsValue(uint64(*in.issuanceInflationKeys)) + if err != nil { + return nil, err + } + + outs = append(outs, &transaction.TxOutput{ + Asset: token, + Value: amount, + RangeProof: in.issuanceKeysRangeproof, + Script: make([]byte, 0), + }) + } + + res := &UnblindIssuanceResult{} + for i, out := range outs { + v, a, _, _, err := b.blinderSvc.UnblindOutputWithKey(out, blindKeys[i]) + if err != nil { + return nil, err + } + //TODO check bellow + if i == 0 { + res.Asset.Asset = a + res.Asset.Value = v + res.Asset.AssetBlindingFactor = make([]byte, 32) + } else { + res.Token.Asset = a + res.Token.Value = v + res.Token.AssetBlindingFactor = make([]byte, 32) + } + } + return res, nil +} + +func calcAssetHash(in Input) ([]byte, error) { + iss, err := transaction.NewTxIssuanceFromInput(&transaction.TxInput{ + Hash: in.previousTxid, + Index: *in.previousOutputIndex, + Issuance: &transaction.TxIssuance{ + AssetEntropy: in.issuanceAssetEntropy, + }, + }) + if err != nil { + return nil, err + } + + return iss.GenerateAsset() +} + +func calcTokenHash(in Input) ([]byte, error) { + iss, err := transaction.NewTxIssuanceFromInput(&transaction.TxInput{ + Hash: in.previousTxid, + Index: *in.previousOutputIndex, + Issuance: &transaction.TxIssuance{ + AssetEntropy: in.issuanceAssetEntropy, + }, + }) + if err != nil { + return nil, err + } + + return iss.GenerateReissuanceToken(1) +} diff --git a/psetv2/constructor_role.go b/psetv2/constructor_role.go new file mode 100644 index 0000000..d4c41a1 --- /dev/null +++ b/psetv2/constructor_role.go @@ -0,0 +1,55 @@ +package psetv2 + +import ( + "errors" +) + +var ( + ErrPsetAlreadyConstructed = errors.New("pset already constructed") +) + +type ConstructorRole struct { + pset *Pset + inputArgs []InputArg + outputArgs []OutputArg + lockForModification bool +} + +func NewConstructorRole( + pset *Pset, + inputArgs []InputArg, + outputArgs []OutputArg, + lockForModification bool, +) (*ConstructorRole, error) { + return &ConstructorRole{ + inputArgs: inputArgs, + outputArgs: outputArgs, + lockForModification: lockForModification, + pset: pset, + }, nil +} + +type TimeLock struct { + RequiredTimeLock *uint32 + RequiredHeightTimeLock *uint32 +} + +func (c *ConstructorRole) Construct() error { + for _, v := range c.inputArgs { + if err := c.pset.addInput(v); err != nil { + return err + } + } + + for _, v := range c.outputArgs { + if err := c.pset.addOutput(v); err != nil { + return err + } + } + + if c.lockForModification { + c.pset.LockForModification() + } + + return nil +} diff --git a/psetv2/creator_role.go b/psetv2/creator_role.go new file mode 100644 index 0000000..b1f13ea --- /dev/null +++ b/psetv2/creator_role.go @@ -0,0 +1,37 @@ +package psetv2 + +type CreatorRole struct { + globalFallbackLockTime *uint32 +} + +func NewCreatorRole(globalFallbackLockTime *uint32) (*CreatorRole, error) { + return &CreatorRole{ + globalFallbackLockTime: globalFallbackLockTime, + }, nil +} + +func (c *CreatorRole) Create() (*Pset, error) { + var psetVersion uint32 = 2 + var txVersion uint32 = 2 + var inputCount uint64 = 0 + var outputCount uint64 = 0 + var elementsTxModifiableFlag uint8 = 0 //0000 0000 + var txModifiableFlag uint8 = 3 //0000 0011 + return &Pset{ + Global: &Global{ + txInfo: TxInfo{ + version: &txVersion, + fallBackLockTime: c.globalFallbackLockTime, + inputCount: &inputCount, + outputCount: &outputCount, + txModifiable: &txModifiableFlag, + }, + version: &psetVersion, + xPub: make([]DerivationPathWithXPub, 0), + scalars: make([][]byte, 0), + elementsTxModifiableFlag: &elementsTxModifiableFlag, + proprietaryData: make([]proprietaryData, 0), + unknowns: make([]keyPair, 0), + }, + }, nil +} diff --git a/psetv2/extractor_role.go b/psetv2/extractor_role.go new file mode 100644 index 0000000..56ab1fa --- /dev/null +++ b/psetv2/extractor_role.go @@ -0,0 +1,165 @@ +package psetv2 + +import ( + "bytes" + + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/vulpemventures/go-elements/elementsutil" + "github.com/vulpemventures/go-elements/transaction" +) + +type ExtractorRole struct { + pset *Pset + blinderSvc Blinder +} + +func NewExtractorRole(pset *Pset, blinderSvc Blinder) *ExtractorRole { + return &ExtractorRole{ + pset: pset, + blinderSvc: blinderSvc, + } +} + +func (e *ExtractorRole) Extract() (*transaction.Transaction, error) { + tx := transaction.NewTx(int32(*e.pset.Global.version)) + + if e.pset.CalculateTimeLock() != nil { + tx.Locktime = *e.pset.CalculateTimeLock() + } + + for _, v := range e.pset.Inputs { + txInput := &transaction.TxInput{} + txInput.Hash = v.previousTxid + txInput.Index = *v.previousOutputIndex + txInput.Sequence = *v.sequence + if txInput.Issuance != nil { + txInput.Issuance.AssetBlindingNonce = v.issuanceBlindingNonce + txInput.Issuance.AssetEntropy = v.issuanceAssetEntropy + if v.issuanceValue != nil { + issuanceValue, err := elementsutil.SatoshiToElementsValue(uint64(*v.issuanceValue)) + if err != nil { + return nil, err + } + txInput.Issuance.AssetAmount = issuanceValue + } + if v.issuanceValueCommitment != nil { + txInput.Issuance.AssetAmount = v.issuanceValueCommitment + txInput.IssuanceRangeProof = v.issuanceValueRangeproof + } + + if v.issuanceInflationKeys != nil { + tokenValue, err := elementsutil.SatoshiToElementsValue(uint64(*v.issuanceInflationKeys)) + if err != nil { + return nil, err + } + txInput.Issuance.TokenAmount = tokenValue + } + if v.issuanceInflationKeysCommitment != nil { + txInput.Issuance.TokenAmount = v.issuanceInflationKeysCommitment + txInput.InflationRangeProof = v.issuanceInflationKeysCommitment + } + } + txInput.IsPegin = v.peginWitness != nil + if txInput.IsPegin { + txInput.PeginWitness = [][]byte{v.peginWitness} + } + + if v.finalScriptSig != nil { + txInput.Script = v.finalScriptSig + } + + if v.finalScriptWitness != nil { + // In order to set the witness, need to re-deserialize + // the field as encoded within the PSET packet. For + // each input, the witness is encoded as a stack with + // one or more items. + witnessReader := bytes.NewReader( + v.finalScriptWitness, + ) + + // First we extract the number of witness elements + // encoded in the above witnessReader. + witCount, err := wire.ReadVarInt(witnessReader, 0) + if err != nil { + return nil, err + } + + // Now that we know how may inputs we'll need, we'll + // construct a packing slice, then read out each input + // (with a varint prefix) from the witnessReader. + txInput.Witness = make(transaction.TxWitness, witCount) + for j := uint64(0); j < witCount; j++ { + wit, err := wire.ReadVarBytes( + witnessReader, 0, txscript.MaxScriptSize, "witness", + ) + if err != nil { + return nil, err + } + txInput.Witness[j] = wit + } + } + + tx.AddInput(txInput) + } + + for _, v := range e.pset.Outputs { + txOutput := &transaction.TxOutput{} + txOutput.Script = v.outputScript + + expValue := v.outputValueCommitment == nil + expValue = expValue && v.outputAmount != nil + if v.outputValueCommitment != nil && v.outputAmount != nil { + expValue = expValue && v.outputBlindValueProof != nil + expValue = expValue && v.outputAssetCommitment != nil + valid, err := e.blinderSvc.VerifyBlindValueProof( + *v.outputAmount, + v.outputValueCommitment, + v.outputBlindValueProof, + v.outputAssetCommitment, + ) + if err != nil { + return nil, err + } + expValue = expValue && valid + } + if expValue { + value, err := elementsutil.SatoshiToElementsValue(uint64(*v.outputAmount)) + if err != nil { + return nil, err + } + txOutput.Value = value + } else { + txOutput.Value = v.outputValueCommitment + txOutput.RangeProof = v.outputValueRangeproof + } + + expAsset := v.outputAssetCommitment == nil + expAsset = expAsset && v.outputAsset != nil + if v.outputAssetCommitment != nil && v.outputAsset != nil { + expAsset = expAsset && v.outputBlindAssetProof != nil + expAsset = expAsset && v.outputAsset != nil + valid, err := e.blinderSvc.VerifyBlindAssetProof( + v.outputAsset[1:], + v.outputBlindAssetProof, + v.outputAssetCommitment, + ) + if err != nil { + return nil, err + } + expAsset = expAsset && valid + } + if expAsset { + txOutput.Asset = v.outputAsset + } else { + txOutput.Asset = v.outputAssetCommitment + txOutput.SurjectionProof = v.outputAssetSurjectionProof + } + + txOutput.Nonce = v.outputEcdhPubkey + + tx.AddOutput(txOutput) + } + + return tx, nil +} diff --git a/psetv2/finalizer_role.go b/psetv2/finalizer_role.go new file mode 100644 index 0000000..d3f5642 --- /dev/null +++ b/psetv2/finalizer_role.go @@ -0,0 +1,490 @@ +// Copyright (c) 2018 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package psetv2 + +// The FinalizerRole requires provision of a single PSET input +// in which all necessary signatures are encoded, and +// uses it to construct valid final sigScript and scriptWitness +// fields. +// NOTE that p2sh (legacy) and p2wsh currently support only +// multisig and no other custom script. + +import ( + "errors" + + "github.com/btcsuite/btcd/txscript" +) + +var ( + // ErrInvalidSigHashFlags indicates that a signature added to the PSBT + // uses Sighash flags that are not in accordance with the requirement + // according to the entry in PsbtInSighashType, or otherwise not the + // default value (SIGHASH_ALL) + ErrInvalidSigHashFlags = errors.New("invalid sighash flags") + // ErrNotFinalizable indicates that the PSBT struct does not have + // sufficient data (e.g. signatures) for finalization + ErrNotFinalizable = errors.New("PSBT is not finalizable") + // ErrInputAlreadyFinalized indicates that the PSBT passed to a FinalizerRole + // already contains the finalized scriptSig or witness. + ErrInputAlreadyFinalized = errors.New("cannot finalize PSBT, finalized " + + "scriptSig or scriptWitnes already exists") +) + +type FinalizerRole struct { + pset *Pset +} + +func NewFinalizerRole(pset *Pset) *FinalizerRole { + + return &FinalizerRole{ + pset: pset, + } +} + +// FinalizeAll finalizes all inputs of a partial elements transaction by +// calling the Finalize function for every partial input +func (f *FinalizerRole) FinalizeAll(p *Pset) error { + for inIndex := range p.Inputs { + err := f.Finalize(p, inIndex) + if err != nil { + return err + } + } + return nil +} + +// Finalize assumes that the provided pset.Pset struct has all partial +// signatures and redeem scripts/witness scripts already prepared for the +// specified input, and so removes all temporary data and replaces them with +// completed sigScript and witness fields, which are stored in key-types 07 and +// 08. The witness/non-witness utxo fields in the inputs (key-types 00 and 01) +// are left intact as they may be needed for validation (?). If there is any +// invalid or incomplete data, an error is returned. +func (f *FinalizerRole) Finalize(p *Pset, inIndex int) error { + input := p.Inputs[inIndex] + + // Depending on the UTXO type, we either attempt to finalize it as a + // witness or legacy UTXO. + switch { + case input.witnessUtxo != nil: + if err := finalizeWitnessInput(p, inIndex); err != nil { + return err + } + + case input.nonWitnessUtxo != nil: + if err := finalizeNonWitnessInput(p, inIndex); err != nil { + return err + } + + default: + return ErrInvalidPsbtFormat + } + + // Before returning we sanity check the PSET to ensure we don't extract + // an invalid transaction or produce an invalid intermediate state. + if err := p.SanityCheck(); err != nil { + return err + } + + return nil +} + +// MaybeFinalizeAll attempts to finalize all inputs of the pset.Pset that are +// not already finalized, and returns an error if it fails to do so. +func (f *FinalizerRole) MaybeFinalizeAll(p *Pset) error { + + for i := range p.Inputs { + success, err := f.MaybeFinalize(p, i) + if err != nil || !success { + return err + } + } + + return nil +} + +// MaybeFinalize attempts to finalize the input at index inIndex in the PSET p, +// returning true with no error if it succeeds, OR if the input has already +// been finalized. +func (f *FinalizerRole) MaybeFinalize(p *Pset, inIndex int) (bool, error) { + if isFinalized(p, inIndex) { + return true, nil + } + + if !isFinalizable(p, inIndex) { + return false, ErrNotFinalizable + } + + if err := f.Finalize(p, inIndex); err != nil { + return false, err + } + + return true, nil +} + +// isFinalized considers this input finalized if it contains at least one of +// the FinalScriptSig or FinalScriptWitness are filled (which only occurs in a +// successful call to Finalize*). +func isFinalized(p *Pset, inIndex int) bool { + input := p.Inputs[inIndex] + return input.finalScriptSig != nil || input.finalScriptWitness != nil +} + +// isFinalizableWitnessInput returns true if the target input is a witness UTXO +// that can be finalized. +func isFinalizableWitnessInput(input *Input) bool { + pkScript := input.witnessUtxo.Script + + switch { + // If this is a native witness output, then we require both + // the witness script, but not a redeem script. + case txscript.IsWitnessProgram(pkScript): + if txscript.IsPayToWitnessScriptHash(pkScript) { + if input.witnessScript == nil || + input.redeemScript != nil { + return false + } + } else { + // A P2WKH output on the other hand doesn't need + // neither a witnessScript or redeemScript. + if input.witnessScript != nil || + input.redeemScript != nil { + return false + } + } + + // For nested P2SH inputs, we verify that a witness script is known. + case txscript.IsPayToScriptHash(pkScript): + if input.redeemScript == nil { + return false + } + + // If this is a nested P2SH input, then it must also have a + // witness script, while we don't need one for P2WKH. + if txscript.IsPayToWitnessScriptHash(input.redeemScript) { + if input.witnessScript == nil { + return false + } + } else if txscript.IsPayToWitnessPubKeyHash(input.redeemScript) { + if input.witnessScript != nil { + return false + } + } else { + // unrecognized type + return false + } + + // If this isn't a nested nested P2SH output or a native witness + // output, then we can't finalize this input as we don't understand it. + default: + return false + } + + return true +} + +// isFinalizableLegacyInput returns true of the passed input a legacy input +// (non-witness) that can be finalized. +func isFinalizableLegacyInput(p *Pset, input *Input, inIndex int) bool { + // If the input has a witness, then it's invalid. + if input.witnessScript != nil { + return false + } + + // Otherwise, we'll verify that we only have a RedeemScript if the prev + // output script is P2SH. + outIndex := p.Inputs[inIndex].previousOutputIndex + if txscript.IsPayToScriptHash(input.nonWitnessUtxo.Outputs[*outIndex].Script) { + if input.redeemScript == nil { + return false + } + } else { + if input.redeemScript != nil { + return false + } + } + + return true +} + +// isFinalizable checks whether the structure of the entry for the input of the +// pset.Pset at index inIndex contains sufficient information to finalize +// this input. +func isFinalizable(p *Pset, inIndex int) bool { + input := p.Inputs[inIndex] + + // The input cannot be finalized without any signatures + if input.partialSigs == nil { + return false + } + + // For an input to be finalized, we'll one of two possible top-level + // UTXOs present. Each UTXO type has a distinct set of requirements to + // be considered finalized. + switch { + + // A witness input must be either native P2WSH or nested P2SH with all + // relevant sigScript or witness data populated. + case input.witnessUtxo != nil: + if !isFinalizableWitnessInput(&input) { + return false + } + + case input.nonWitnessUtxo != nil: + if !isFinalizableLegacyInput(p, &input, inIndex) { + return false + } + + // If neither a known UTXO type isn't present at all, then we'll + // return false as we need one of them. + default: + return false + } + + return true +} + +// checkFinalScriptSigWitness checks whether a given input in the pset.Pset +// struct already has the fields 07 (FinalInScriptSig) or 08 (FinalInWitness). +// If so, it returns true. It does not modify the Pset. +func checkFinalScriptSigWitness(p *Pset, inIndex int) bool { + input := p.Inputs[inIndex] + + if input.finalScriptSig != nil { + return true + } + + if input.finalScriptWitness != nil { + return true + } + + return false +} + +// finalizeNonWitnessInput attempts to create a PsetInFinalScriptSig field for +// the input at index inIndex, and removes all other fields except for the UTXO +// field, for an input of type non-witness, or returns an error. +func finalizeNonWitnessInput(p *Pset, inIndex int) error { + // If this input has already been finalized, then we'll return an error + // as we can't proceed. + if checkFinalScriptSigWitness(p, inIndex) { + return ErrInputAlreadyFinalized + } + + // Our goal here is to construct a sigScript given the pubkey, + // signature (keytype 02), of which there might be multiple, and the + // redeem script field (keytype 04) if present (note, it is not present + // for p2pkh type inputs). + var sigScript []byte + + input := p.Inputs[inIndex] + containsRedeemScript := input.redeemScript != nil + + var ( + pubKeys [][]byte + sigs [][]byte + ) + for _, ps := range input.partialSigs { + pubKeys = append(pubKeys, ps.PubKey) + + sigOK := checkSigHashFlags(ps.Signature, input) + if !sigOK { + return ErrInvalidSigHashFlags + } + + sigs = append(sigs, ps.Signature) + } + + // We have failed to identify at least 1 (sig, pub) pair in the PSET, + // which indicates it was not ready to be finalized. As a result, we + // can't proceed. + if len(sigs) < 1 || len(pubKeys) < 1 { + return ErrNotFinalizable + } + + // If this input doesn't need a redeem script (P2PKH), then we'll + // construct a simple sigScript that's just the signature then the + // pubkey (OP_CHECKSIG). + var err error + if !containsRedeemScript { + // At this point, we should only have a single signature and + // pubkey. + if len(sigs) != 1 || len(pubKeys) != 1 { + return ErrNotFinalizable + } + + // In this case, our sigScript is just: . + builder := txscript.NewScriptBuilder() + builder.AddData(sigs[0]).AddData(pubKeys[0]) + sigScript, err = builder.Script() + if err != nil { + return err + } + } else { + // This is assumed p2sh multisig Given redeemScript and pubKeys + // we can decide in what order signatures must be appended. + orderedSigs, err := extractKeyOrderFromScript( + input.redeemScript, pubKeys, sigs, + ) + if err != nil { + return err + } + + // At this point, we assume that this is a mult-sig input, so + // we construct our sigScript which looks something like this + // (mind the extra element for the extra multi-sig pop): + // * + // + // TODO(waxwing): the below is specific to the multisig case. + builder := txscript.NewScriptBuilder() + builder.AddOp(txscript.OP_FALSE) + for _, os := range orderedSigs { + builder.AddData(os) + } + builder.AddData(input.redeemScript) + sigScript, err = builder.Script() + if err != nil { + return err + } + } + + if len(sigScript) > 0 { + p.Inputs[inIndex].finalScriptSig = sigScript + } + + return nil +} + +// finalizeWitnessInput attempts to create PsetInFinalScriptSig field and +// PsetInFinalScriptWitness field for input at index inIndex, and removes all +// other fields except for the utxo field, for an input of type witness, or +// returns an error. +func finalizeWitnessInput(p *Pset, inIndex int) error { + // If this input has already been finalized, then we'll return an error + // as we can't proceed. + if checkFinalScriptSigWitness(p, inIndex) { + return ErrInputAlreadyFinalized + } + + // Depending on the actual output type, we'll either populate a + // serializedWitness or a witness as well asa sigScript. + var ( + sigScript []byte + serializedWitness []byte + ) + + input := p.Inputs[inIndex] + + // First we'll validate and collect the pubkey+sig pairs from the set + // of partial signatures. + var ( + pubKeys [][]byte + sigs [][]byte + ) + for _, ps := range input.partialSigs { + pubKeys = append(pubKeys, ps.PubKey) + + sigOK := checkSigHashFlags(ps.Signature, input) + if !sigOK { + return ErrInvalidSigHashFlags + + } + + sigs = append(sigs, ps.Signature) + } + + // If at this point, we don't have any pubkey+sig pairs, then we bail + // as we can't proceed. + if len(sigs) == 0 || len(pubKeys) == 0 { + return ErrNotFinalizable + } + + containsRedeemScript := input.redeemScript != nil + containsWitnessScript := input.witnessScript != nil + + // If there's no redeem script, then we assume that this is native + // segwit input. + var err error + if !containsRedeemScript { + // If we have only a sigley pubkey+sig pair, and no witness + // script, then we assume this is a P2WKH input. + if len(pubKeys) == 1 && len(sigs) == 1 && + !containsWitnessScript { + + serializedWitness, err = writePKHWitness( + sigs[0], pubKeys[0], + ) + if err != nil { + return err + } + } else { + // Otherwise, we must have a witnessScript field, so + // we'll generate a valid multi-sig witness. + // + // NOTE: We tacitly assume multisig. + // + // TODO(roasbeef): need to add custom finalize for + // non-multisig P2WSH outputs (HTLCs, delay outputs, + // etc). + if !containsWitnessScript { + return ErrNotFinalizable + } + + serializedWitness, err = getMultisigScriptWitness( + input.witnessScript, pubKeys, sigs, + ) + if err != nil { + return err + } + } + } else { + // Otherwise, we assume that this is a p2wsh multi-sig output, + // which is nested in a p2sh, or a p2wkh nested in a p2sh. + // + // In this case, we'll take the redeem script (the witness + // program in this case), and push it on the stack within the + // sigScript. + builder := txscript.NewScriptBuilder() + builder.AddData(input.redeemScript) + sigScript, err = builder.Script() + if err != nil { + return err + } + + // If don't have a witness script, then we assume this is a + // nested p2wkh output. + if !containsWitnessScript { + // Assumed p2sh-p2wkh Here the witness is just (sig, + // pub) as for p2pkh case + if len(sigs) != 1 || len(pubKeys) != 1 { + return ErrNotFinalizable + } + + serializedWitness, err = writePKHWitness(sigs[0], pubKeys[0]) + if err != nil { + return err + } + + } else { + // Otherwise, we assume that this is a p2wsh multi-sig, + // so we generate the proper witness. + serializedWitness, err = getMultisigScriptWitness( + input.witnessScript, pubKeys, sigs, + ) + if err != nil { + return err + } + } + } + + if len(sigScript) > 0 { + p.Inputs[inIndex].finalScriptSig = sigScript + } + + if len(serializedWitness) > 0 { + p.Inputs[inIndex].finalScriptWitness = serializedWitness + } + return nil +} diff --git a/psetv2/global.go b/psetv2/global.go new file mode 100644 index 0000000..dbe31a9 --- /dev/null +++ b/psetv2/global.go @@ -0,0 +1,397 @@ +package psetv2 + +import ( + "bytes" + "encoding/binary" + "errors" + + "github.com/btcsuite/btcd/wire" + + "github.com/vulpemventures/go-elements/internal/bufferutil" + + "github.com/btcsuite/btcutil/base58" + "github.com/btcsuite/btcutil/hdkeychain" +) + +const ( + //Per output types: BIP 174, 370, 371 + PsbtGlobalUnsignedTx = 0x00 //BIP 174 + PsbtGlobalXpub = 0x01 //BIP 174 + PsbtGlobalTxVersion = 0x02 //BIP 370 + PsbtGlobalFallbackLocktime = 0x03 //BIP 370 + PsbtGlobalInputCount = 0x04 //BIP 370 + PsbtGlobalOutputCount = 0x05 //BIP 370 + PsbtGlobalTxModifiable = 0x06 //BIP 370 + PsbtGlobalSighashSingleInputs = 0x07 //BIP 370 + PsbtGlobalVersion = 0xFB //BIP 174 + PsbtGlobalProprietary = 0xFC //BIP 174 + + //Elements Proprietary types + PsetElementsGlobalScalar = 0x00 + PsetElementsGlobalTxModifiable = 0x01 + + //78 byte serialized extended public key as defined by BIP 32. + pubKeyLength = 78 +) + +var ( + ErrInvalidElementsTxModifiableValue = errors.New("invalid elements tx modifiable value") + ErrInvalidXPub = errors.New("invalid xpub") + ErrInvalidXPubDerivationPathLength = errors.New("incorrect length of global xpub derivation data") + ErrInvalidPsetVersion = errors.New("incorrect pset version") + ErrInvalidTxVersion = errors.New("incorrect tx version") + ErrInvalidScalarLength = errors.New("invalid scalar length") +) + +type Global struct { + // global transaction data + txInfo TxInfo + // the version number of this PSET. Must be present. + version *uint32 + // a global map from extended public keys to the used key fingerprint and + // derivation path as defined by BIP 32 + xPub []DerivationPathWithXPub + // scalars used for blinding + scalars [][]byte + // elements tx modifiable flag + elementsTxModifiableFlag *uint8 + // other proprietaryData fields + proprietaryData []proprietaryData + // unknowns global key-value pairs. + unknowns []keyPair +} + +// TxInfo represents global information about the transaction +type TxInfo struct { + // Transaction version. Must be 2. + version *uint32 + // Locktime to use if no inputs specify a minimum locktime to use. + // May be omitted in which case it is interpreted as 0. + fallBackLockTime *uint32 + // Number of inputs in the transaction + // Not public. Users should not be able to mutate this directly + // This will be automatically whenever pset inputs are added + inputCount *uint64 + // Number of outputs in the transaction + // Not public. Users should not be able to mutate this directly + // This will be automatically whenever pset inputs are added + outputCount *uint64 + // Flags indicating that the transaction may be modified. + // May be omitted in which case it is interpreted as 0. + txModifiable *uint8 +} + +// DerivationPathWithXPub global information about xpub keypair +type DerivationPathWithXPub struct { + // extendedPubKey extended public key as defined by BIP 32 + extendedPubKey *hdkeychain.ExtendedKey + //masterKeyFingerPrint master key fingerprint as defined by BIP 32 + masterKeyFingerPrint *uint32 + // derivationPath derivation path of the public key + derivationPath []uint32 +} + +func (g *Global) serialize(s *bufferutil.Serializer) error { + globalKeyPairs, err := g.getKeyPairs() + if err != nil { + return err + } + + for _, v := range globalKeyPairs { + if err := serializeKeyPair(v, s); err != nil { + return err + } + } + + if err := s.WriteUint8(separator); err != nil { + return err + } + + return nil +} + +func (g Global) getKeyPairs() ([]keyPair, error) { + keyPairs := make([]keyPair, 0) + + for _, v := range g.xPub { + xPubKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalXpub, + keyData: SerializeBIP32Derivation( + *v.masterKeyFingerPrint, + v.derivationPath, + ), + }, + value: base58.Decode(v.extendedPubKey.String()), + } + keyPairs = append(keyPairs, xPubKeyPair) + } + + if g.txInfo.version != nil { + globalTxVersion := make([]byte, 4) + binary.LittleEndian.PutUint32(globalTxVersion, *g.txInfo.version) + versionKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalTxVersion, + keyData: nil, + }, + value: globalTxVersion, + } + keyPairs = append(keyPairs, versionKeyPair) + } + + if g.txInfo.fallBackLockTime != nil { + fallBackLockTime := make([]byte, 4) + binary.LittleEndian.PutUint32(fallBackLockTime, *g.txInfo.fallBackLockTime) + fallBackLockTimeKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalFallbackLocktime, + keyData: nil, + }, + value: fallBackLockTime, + } + keyPairs = append(keyPairs, fallBackLockTimeKeyPair) + } + + if g.txInfo.inputCount != nil { + inputCount := new(bytes.Buffer) + if err := wire.WriteVarInt(inputCount, 0, *g.txInfo.inputCount); err != nil { + return nil, err + } + inputCountKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalInputCount, + keyData: nil, + }, + value: inputCount.Bytes(), + } + keyPairs = append(keyPairs, inputCountKeyPair) + } + + if g.txInfo.outputCount != nil { + outputCount := new(bytes.Buffer) + if err := wire.WriteVarInt(outputCount, 0, *g.txInfo.outputCount); err != nil { + return nil, err + } + outputCountKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalOutputCount, + keyData: nil, + }, + value: outputCount.Bytes(), + } + keyPairs = append(keyPairs, outputCountKeyPair) + } + + if g.txInfo.txModifiable != nil { + txModifiable := new(bytes.Buffer) + if err := binary.Write(txModifiable, binary.LittleEndian, g.txInfo.txModifiable); err != nil { + return nil, err + } + txModifiableKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalTxModifiable, + keyData: nil, + }, + value: txModifiable.Bytes(), + } + keyPairs = append(keyPairs, txModifiableKeyPair) + } + + if g.version != nil { + globalVersion := make([]byte, 4) + binary.LittleEndian.PutUint32(globalVersion, *g.version) + globalVersionKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalVersion, + keyData: nil, + }, + value: globalVersion, + } + keyPairs = append(keyPairs, globalVersionKeyPair) + } + + for _, v := range g.scalars { + scalarKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsGlobalScalar, v), + }, + value: nil, + } + keyPairs = append(keyPairs, scalarKeyPair) + } + + if g.elementsTxModifiableFlag != nil { + elementsTxModifiableFlag := new(bytes.Buffer) + if err := binary.Write(elementsTxModifiableFlag, binary.LittleEndian, g.elementsTxModifiableFlag); err != nil { + return nil, err + } + elementsTxModifiableKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsGlobalTxModifiable, nil), + }, + value: elementsTxModifiableFlag.Bytes(), + } + keyPairs = append(keyPairs, elementsTxModifiableKeyPair) + } + + for _, v := range g.proprietaryData { + kp := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(v.subtype, v.keyData), + }, + value: v.value, + } + keyPairs = append(keyPairs, kp) + } + + for _, v := range g.unknowns { + keyPairs = append(keyPairs, v) + } + + return keyPairs, nil +} + +func deserializeGlobal(buf *bytes.Buffer) (*Global, error) { + global := &Global{} + kp := &keyPair{} + + //read bytes and do the deserialization until separator is found at the + //end of global map + for { + if err := kp.deserialize(buf); err != nil { + if err == ErrNoMoreKeyPairs { + break + } + return nil, err + } + + switch kp.key.keyType { + case PsbtGlobalTxVersion: + version := binary.LittleEndian.Uint32(kp.value) + global.txInfo.version = &version + case PsbtGlobalFallbackLocktime: + fallBackLockTime := binary.LittleEndian.Uint32(kp.value) + global.txInfo.fallBackLockTime = &fallBackLockTime + case PsbtGlobalInputCount: + tmp := make([]byte, 8) + for i, v := range kp.value { + tmp[i] = v + } + ic := binary.LittleEndian.Uint64(tmp) + global.txInfo.inputCount = &ic + case PsbtGlobalOutputCount: + tmp := make([]byte, 8) + for i, v := range kp.value { + tmp[i] = v + } + + oc := binary.LittleEndian.Uint64(tmp) + global.txInfo.outputCount = &oc + case PsbtGlobalTxModifiable: + var tm uint8 + buf := bytes.NewReader(kp.value) + if err := binary.Read(buf, binary.LittleEndian, tm); err != nil { + return nil, err + } + + global.txInfo.txModifiable = &tm + case PsbtGlobalXpub: + if len(kp.key.keyData) != pubKeyLength { + return nil, ErrInvalidXPub + } + xpubStr := base58.Encode(kp.key.keyData) + extendedPubKey, err := hdkeychain.NewKeyFromString(xpubStr) + if err != nil { + return nil, err + } + + if len(kp.value) == 0 || len(kp.value)%4 != 0 { + return nil, ErrInvalidXPubDerivationPathLength + } + + master, derivationPath, err := readBip32Derivation(kp.value) + if err != nil { + return nil, err + } + + global.xPub = append(global.xPub, DerivationPathWithXPub{ + extendedPubKey: extendedPubKey, + masterKeyFingerPrint: &master, + derivationPath: derivationPath, + }) + + case PsbtGlobalVersion: + version := binary.LittleEndian.Uint32(kp.value) + global.version = &version + case PsbtGlobalProprietary: + pd := &proprietaryData{} + if err := pd.proprietaryDataFromKeyPair(*kp); err != nil { + return nil, err + } + + if bytes.Equal(pd.identifier, psetMagic) { + switch pd.subtype { + case PsetElementsGlobalScalar: + scalar := pd.keyData + if len(scalar) != 32 { + return nil, ErrInvalidScalarLength + } + + if global.scalars == nil { + global.scalars = make([][]byte, 0) + } + + global.scalars = append(global.scalars, scalar) + case PsetElementsGlobalTxModifiable: + elementsTxModifiable := pd.value + if len(elementsTxModifiable) != 1 { + return nil, ErrInvalidElementsTxModifiableValue + } + + var etm uint8 + buf := bytes.NewReader(kp.value) + if err := binary.Read(buf, binary.LittleEndian, &etm); err != nil { + return nil, err + } + + global.elementsTxModifiableFlag = &etm + default: + if global.proprietaryData == nil { + global.proprietaryData = make([]proprietaryData, 0) + } + global.proprietaryData = append(global.proprietaryData, *pd) + } + } + + default: + unknowns, err := deserializeUnknownKeyPairs(buf) + if err != nil { + return nil, err + } + global.unknowns = unknowns + } + + } + + //check mandatory fields + if global.version == nil && *global.version != 2 { + return nil, ErrInvalidPsetVersion + } + if global.txInfo.version == nil && *global.txInfo.version == 0 { + return nil, ErrInvalidTxVersion + } + + if global.txInfo.inputCount == nil && *global.txInfo.inputCount == 0 { + return nil, ErrInvalidTxVersion + } + + if global.txInfo.outputCount == nil && *global.txInfo.outputCount == 0 { + return nil, ErrInvalidTxVersion + } + + return global, nil +} diff --git a/psetv2/input.go b/psetv2/input.go new file mode 100644 index 0000000..0c9369e --- /dev/null +++ b/psetv2/input.go @@ -0,0 +1,319 @@ +package psetv2 + +import ( + "bytes" + "errors" + + "github.com/vulpemventures/go-elements/internal/bufferutil" + + "github.com/vulpemventures/go-elements/elementsutil" + + "github.com/btcsuite/btcd/txscript" + "github.com/vulpemventures/go-elements/transaction" +) + +var ( + // ErrInvalidPsbtFormat is a generic error for any situation in which a + // provided Psbt serialization does not conform to the rules of BIP174. + ErrDuplicatePubKeyInPartSig = errors.New("duplicate pubkey in partial signature") + ErrDuplicatePubKeyInBip32DerPath = errors.New("duplicate pubkey in bip32 der path") + // ErrInvalidKeydata indicates that a key-value pair in the PSBT + // serialization contains data in the key which is not valid. + ErrInvalidKeydata = errors.New("invalid key data") + ErrMissingPrevTxID = errors.New("missing previous tx id") + ErrMissingOutputIndex = errors.New("missing output index") + ErrInvalidPrevTxIdLength = errors.New("invalid previous tx id length") + ErrInvalidIssuanceValueCommitmentLength = errors.New("invalid issuance value commitment length") + ErrInvalidPeginGenesisHashLength = errors.New("invalid pegin genesis hash length") + ErrInvalidIssuanceInflationKeysCommitmentLength = errors.New("invalid issuance inflation keys commitment") + ErrInvalidIssuanceBlindingNonceLength = errors.New("invalid issuance blinding nonce length") + ErrInvalidIssuanceAssetEntropyLength = errors.New("invalid issuance asset entropy length") + ErrInvalidAssetCommitmentLength = errors.New("invalid asset commitment length") + ErrInvalidTokenCommitmentLength = errors.New("invalid token commitment length") +) + +const ( + //Per input types: BIP 127, 174, 370, 371 + PsbtInNonWitnessUtxo = 0x00 //BIP 174 + PsbtInWitnessUtxo = 0x01 //BIP 174 + PsbtInPartialSig = 0x02 //BIP 174 + PsbtInSighashType = 0x03 //BIP 174 + PsbtInRedeemScript = 0x04 //BIP 174 + PsbtInWitnessScript = 0x05 //BIP 174 + PsbtInBip32Derivation = 0x06 //BIP 174 + PsbtInFinalScriptsig = 0x07 //BIP 174 + PsbtInFinalScriptwitness = 0x08 //BIP 174 + PsbtInPorCommitment = 0x09 //BIP 127 + PsbtInRipemd160 = 0x0a //BIP 174 + PsbtInSha256 = 0x0b //BIP 174 + PsbtInHash160 = 0x0c //BIP 174 + PsbtInHash256 = 0x0d //BIP 174 + PsbtInPreviousTxid = 0x0e //BIP 370 + PsbtInOutputIndex = 0x0f //BIP 370 + PsbtInSequence = 0x10 //BIP 370 + PsbtInRequiredTimeLocktime = 0x11 //BIP 370 + PsbtInRequiredHeightLocktime = 0x12 //BIP 370 + PsbtInTapKeySig = 0x13 //BIP 371 + PsbtInTapScriptSig = 0x14 //BIP 371 + PsbtInTapLeafScript = 0x15 //BIP 371 + PsbtInTapBip32Derivation = 0x16 //BIP 371 + PsbtInTapInternalKey = 0x17 //BIP 371 + PsbtInTapMerkleRoot = 0x18 //BIP 371 + PsbtInProprietary = 0xFC //BIP 174 + + //Elements Proprietary types + PsetElementsInIssuanceValue = 0x00 + PsetElementsInIssuanceValueCommitment = 0x01 + PsetElementsInIssuanceValueRangeproof = 0x02 + PsetElementsInIssuanceKeysRangeproof = 0x03 + PsetElementsInPegInTx = 0x04 + PsetElementsInPegInTxoutProof = 0x05 + PsetElementsInPegInGenesis = 0x06 + PsetElementsInPegInClaimScript = 0x07 + PsetElementsInPegInValue = 0x08 + PsetElementsInPegInWitness = 0x09 + PsetElementsInIssuanceInflationKeys = 0x0a + PsetElementsInIssuanceInflationKeysCommitment = 0x0b + PsetElementsInIssuanceBlindingNonce = 0x0c + PsetElementsInIssuanceAssetEntropy = 0x0d + PsetElementsInUtxoRangeProof = 0x0e + PsetElementsInIssuanceBlindValueProof = 0x0f + PsetElementsInIssuanceBlindInflationKeysProof = 0x10 +) + +type Input struct { + // The non-witness transaction this input spends from. Should only be + // [std::option::Option::Some] for inputs which spend non-segwit outputs or + // if it is unknowns whether an input spends a segwit output. + nonWitnessUtxo *transaction.Transaction + // The transaction output this input spends from. Should only be + // [std::option::Option::Some] for inputs which spend segwit outputs, + // including P2SH embedded ones. + witnessUtxo *transaction.TxOutput + // A map from public keys to their corresponding signature as would be + // pushed to the stack from a scriptSig or witness. + partialSigs []PartialSig + // The sighash type to be used for this input. Signatures for this input + // must use the sighash type. + sigHashType *txscript.SigHashType + // The redeem script for this input. + redeemScript []byte + /// The witness script for this input. + witnessScript []byte + // A map from public keys needed to sign this input to their corresponding + // master key fingerprints and derivation paths. + bip32Derivation []DerivationPathWithPubKey + // The finalized, fully-constructed scriptSig with signatures and any other + // scripts necessary for this input to pass validation. + finalScriptSig []byte + // The finalized, fully-constructed scriptWitness with signatures and any + // other scripts necessary for this input to pass validation. + finalScriptWitness []byte + // RIPEMD160 hash to preimage map + ripemd160Preimages map[[20]byte][]byte + // SHA256 hash to preimage map + sha256Preimages map[[32]byte][]byte + // HSAH160 hash to preimage map + hash160Preimages map[[20]byte][]byte + // HAS256 hash to preimage map + hash256Preimages map[[32]byte][]byte + // (PSET2) Prevout TXID of the input + previousTxid []byte + // (PSET2) Prevout vout of the input + previousOutputIndex *uint32 + // (PSET2) Sequence number. If omitted, defaults to 0xffffffff + sequence *uint32 + // (PSET2) Minimum required locktime, as a UNIX timestamp. If present, must be greater than or equal to 500000000 + requiredTimeLocktime *uint32 + // (PSET2) Minimum required locktime, as a blockheight. If present, must be less than 500000000 + requiredHeightLocktime *uint32 + // Proprietary key-value pairs for this input. + // The issuance value + issuanceValue *int64 + // Issuance value commitment + issuanceValueCommitment []byte + // Issuance value rangeproof + issuanceValueRangeproof []byte + // Issuance keys rangeproof + issuanceKeysRangeproof []byte + // Pegin Transaction. Should be a bitcoin::Transaction + peginTx *transaction.Transaction + // Pegin Transaction proof + // TODO: Look for Merkle proof structs + peginTxoutProof []byte + // Pegin genesis hash + peginGenesisHash []byte + // Claim script + peginClaimScript []byte + // Pegin Value + peginValue *int64 + // Pegin Witness + peginWitness []byte + // Issuance inflation keys + issuanceInflationKeys *int64 + // Issuance inflation keys commitment + issuanceInflationKeysCommitment []byte + // Issuance blinding nonce + issuanceBlindingNonce []byte + // Issuance asset entropy + issuanceAssetEntropy []byte + // Input utxo rangeproof + inUtxoRangeProof []byte + // IssuanceBlindValueProof + issuanceBlindValueProof []byte + // Issuance blind inflation keys proof + issuanceBlindInflationKeysProof []byte + // Other fields + proprietaryData []proprietaryData + // Unknown key-value pairs for this input. + unknowns []keyPair +} + +func psetInputFromTxInput(input transaction.TxInput) (*Input, error) { + previousOutputIndex := input.Index + previousTxid := input.Hash + sequence := input.Sequence + + var issuanceValue *int64 + var issuanceCommitment []byte + var tokenValue *int64 + var tokenCommitment []byte + var issuanceBlindingNonce []byte + var issuanceAssetEntropy []byte + if input.HasIssuance() { + //if non confidential + if len(input.Issuance.AssetAmount) == 9 && input.Issuance.AssetAmount[0] == 1 { + i, err := elementsutil.ElementsToSatoshiValue(input.Issuance.AssetAmount) + if err != nil { + return nil, err + } + iv := int64(i) + issuanceValue = &iv + } else { + if len(input.Issuance.AssetAmount) != 33 { + return nil, ErrInvalidAssetCommitmentLength + } + + issuanceCommitment = input.Issuance.AssetAmount + } + + //TODO: verify if token is inflation key + if len(input.Issuance.TokenAmount) == 9 && input.Issuance.TokenAmount[0] == 1 { + t, err := elementsutil.ElementsToSatoshiValue(input.Issuance.TokenAmount) + if err != nil { + return nil, err + } + tv := int64(t) + tokenValue = &tv + } else { + if len(input.Issuance.TokenAmount) != 33 { + return nil, ErrInvalidAssetCommitmentLength + } + + tokenCommitment = input.Issuance.TokenAmount + } + + if input.Issuance.IsReissuance() { + issuanceBlindingNonce = input.Issuance.AssetBlindingNonce + } + + if !bytes.Equal(input.Issuance.AssetEntropy, transaction.Zero[:]) { + issuanceAssetEntropy = input.Issuance.AssetEntropy + } + } + + var peginWitness []byte + if input.IsPegin { + //TODO: verify bellow + s, err := bufferutil.NewSerializer(nil) + if err != nil { + return nil, err + } + + if err := s.WriteVector(input.PeginWitness); err != nil { + return nil, err + } + + peginWitness = s.Bytes() + } + + return &Input{ + previousTxid: previousTxid, + previousOutputIndex: &previousOutputIndex, + sequence: &sequence, + issuanceValue: issuanceValue, + issuanceValueCommitment: issuanceCommitment, + peginWitness: peginWitness, + issuanceInflationKeys: tokenValue, + issuanceInflationKeysCommitment: tokenCommitment, + issuanceBlindingNonce: issuanceBlindingNonce, + issuanceAssetEntropy: issuanceAssetEntropy, + }, nil +} + +func (i *Input) GetUtxo() *transaction.TxOutput { + var txOut *transaction.TxOutput + if i.nonWitnessUtxo != nil { + txOut = i.nonWitnessUtxo.Outputs[*i.previousOutputIndex] + } else if i.witnessUtxo != nil { + txOut = i.witnessUtxo + } + + return txOut +} + +func (i *Input) IsSane() bool { + + if i.nonWitnessUtxo != nil && i.witnessUtxo != nil { + return false + } + if i.witnessUtxo == nil && i.witnessScript != nil { + return false + } + if i.witnessUtxo == nil && i.finalScriptWitness != nil { + return false + } + + return true +} + +// checkSigHashFlags compares the sighash flag byte on a signature with the +// value expected according to any PsbtInSighashType field in this section of +// the PSBT, and returns true if they match, false otherwise. +// If no SighashType field exists, it is assumed to be SIGHASH_ALL. +// +// TODO(waxwing): sighash type not restricted to one byte in future? +func checkSigHashFlags(sig []byte, input Input) bool { + expectedSighashType := txscript.SigHashAll + if input.sigHashType != nil { + expectedSighashType = *input.sigHashType + } + + return expectedSighashType == txscript.SigHashType(sig[len(sig)-1]) +} + +func (i *Input) isIssuance() bool { + return i.issuanceValue != nil +} + +func (i *Input) isIssuanceBlinded() bool { + return i.issuanceValueCommitment == nil && + len(i.issuanceValueRangeproof) == 0 && + len(i.issuanceBlindInflationKeysProof) == 0 +} + +func (i *Input) isReIssuance() bool { + if i.issuanceBlindingNonce != nil { + return !bytes.Equal(i.issuanceBlindingNonce, transaction.Zero[:]) + } + + return false +} + +func (i *Input) WitnessUtxo() *transaction.TxOutput { + return i.witnessUtxo +} + +func (i *Input) NonWitnessUtxo() *transaction.Transaction { + return i.nonWitnessUtxo +} diff --git a/psetv2/input_deserialization.go b/psetv2/input_deserialization.go new file mode 100644 index 0000000..cba3d09 --- /dev/null +++ b/psetv2/input_deserialization.go @@ -0,0 +1,249 @@ +package psetv2 + +import ( + "bytes" + "encoding/binary" + + "github.com/btcsuite/btcd/txscript" + "github.com/vulpemventures/go-elements/transaction" +) + +func deserializeInput(buf *bytes.Buffer) (*Input, error) { + input := Input{ + partialSigs: make([]PartialSig, 0), + bip32Derivation: make([]DerivationPathWithPubKey, 0), + } + + kp := &keyPair{} + + outputIndexFound := false + prevTxIDFound := false + + //read bytes and do the deserialization until separator is found at the + //end of global map + for { + if err := kp.deserialize(buf); err != nil { + if err == ErrNoMoreKeyPairs { + break + } + return nil, err + } + + switch kp.key.keyType { + case PsbtInNonWitnessUtxo: + tx, err := transaction.NewTxFromBuffer(bytes.NewBuffer(kp.value)) + if err != nil { + return nil, err + } + + input.nonWitnessUtxo = tx + case PsbtInWitnessUtxo: + txOut, err := readTxOut(kp.value) + if err != nil { + return nil, err + } + + input.witnessUtxo = txOut + case PsbtInPartialSig: + partialSignature := PartialSig{ + PubKey: kp.key.keyData, + Signature: kp.value, + } + + if !partialSignature.checkValid() { + return nil, ErrInvalidPsbtFormat + } + + // Duplicate keys are not allowed + for _, v := range input.partialSigs { + if bytes.Equal(v.PubKey, partialSignature.PubKey) { + return nil, ErrDuplicatePubKeyInPartSig + } + } + + input.partialSigs = append(input.partialSigs, partialSignature) + case PsbtInSighashType: + if len(kp.value) != 4 { + return nil, ErrInvalidKeydata + } + + sigHashType := txscript.SigHashType( + binary.LittleEndian.Uint32(kp.value), + ) + + input.sigHashType = &sigHashType + case PsbtInRedeemScript: + input.redeemScript = kp.value + case PsbtInWitnessScript: + input.witnessScript = kp.value + case PsbtInBip32Derivation: + if !validatePubkey(kp.key.keyData) { + return nil, ErrInvalidPsbtFormat + } + master, derivationPath, err := readBip32Derivation(kp.value) + if err != nil { + return nil, err + } + + // Duplicate keys are not allowed + for _, x := range input.bip32Derivation { + if bytes.Equal(x.PubKey, kp.key.keyData) { + return nil, ErrDuplicatePubKeyInBip32DerPath + } + } + + input.bip32Derivation = append( + input.bip32Derivation, + DerivationPathWithPubKey{ + PubKey: kp.key.keyData, + MasterKeyFingerprint: master, + Bip32Path: derivationPath, + }, + ) + case PsbtInFinalScriptsig: + input.finalScriptSig = kp.value + case PsbtInFinalScriptwitness: + input.finalScriptWitness = kp.value + case PsbtInRipemd160: + ripemd160Preimages := make(map[[20]byte][]byte) + var hash [20]byte + copy(hash[:], kp.key.keyData[:]) + ripemd160Preimages[hash] = kp.value + input.ripemd160Preimages = ripemd160Preimages + case PsbtInSha256: + sha256Preimages := make(map[[32]byte][]byte) + var hash [32]byte + copy(hash[:], kp.key.keyData[:]) + sha256Preimages[hash] = kp.value + input.sha256Preimages = sha256Preimages + case PsbtInHash160: + hash160Preimages := make(map[[20]byte][]byte) + var hash [20]byte + copy(hash[:], kp.key.keyData[:]) + hash160Preimages[hash] = kp.value + input.hash160Preimages = hash160Preimages + case PsbtInHash256: + hash256Preimages := make(map[[32]byte][]byte) + var hash [32]byte + copy(hash[:], kp.key.keyData[:]) + input.hash256Preimages = hash256Preimages + case PsbtInPreviousTxid: + previousTxid := kp.value + if len(previousTxid) != 32 { + return nil, ErrInvalidPrevTxIdLength + } + + input.previousTxid = previousTxid + prevTxIDFound = true + case PsbtInOutputIndex: + prevOutIndex := binary.LittleEndian.Uint32(kp.value) + input.previousOutputIndex = &prevOutIndex + outputIndexFound = true + case PsbtInSequence: + sequence := binary.LittleEndian.Uint32(kp.value) + input.sequence = &sequence + case PsbtInRequiredTimeLocktime: + requiredTimeLocktime := binary.LittleEndian.Uint32(kp.value) + input.requiredTimeLocktime = &requiredTimeLocktime + case PsbtInRequiredHeightLocktime: + requiredHeightLocktime := binary.LittleEndian.Uint32(kp.value) + input.requiredHeightLocktime = &requiredHeightLocktime + case PsbtGlobalProprietary: + pd := &proprietaryData{} + if err := pd.proprietaryDataFromKeyPair(*kp); err != nil { + return nil, err + } + + if bytes.Equal(pd.identifier, psetMagic) { + switch pd.subtype { + case PsetElementsInIssuanceValue: + issuanceValue := int64(binary.LittleEndian.Uint64(kp.value)) + input.issuanceValue = &issuanceValue + case PsetElementsInIssuanceValueCommitment: + issuanceValueCommitment := kp.value + if len(issuanceValueCommitment) != 33 { + return nil, ErrInvalidIssuanceValueCommitmentLength + } + + input.issuanceValueCommitment = issuanceValueCommitment + case PsetElementsInIssuanceValueRangeproof: + input.issuanceValueRangeproof = kp.value + case PsetElementsInIssuanceKeysRangeproof: + input.issuanceKeysRangeproof = kp.value + case PsetElementsInPegInTx: + tx, err := transaction.NewTxFromBuffer(bytes.NewBuffer(kp.value)) + if err != nil { + return nil, err + } + + input.peginTx = tx + case PsetElementsInPegInTxoutProof: + input.peginTxoutProof = kp.value + case PsetElementsInPegInGenesis: + peginGenesisHash := kp.value[:] + if len(peginGenesisHash) != 32 { + return nil, ErrInvalidPeginGenesisHashLength + } + + input.peginGenesisHash = peginGenesisHash + case PsetElementsInPegInClaimScript: + input.peginClaimScript = kp.value + case PsetElementsInPegInValue: + peginValue := int64(binary.LittleEndian.Uint64(kp.value)) + input.peginValue = &peginValue + case PsetElementsInPegInWitness: + input.peginWitness = kp.value + case PsetElementsInIssuanceInflationKeys: + issuanceInflationKeys := int64(binary.LittleEndian.Uint64(kp.value)) + input.issuanceInflationKeys = &issuanceInflationKeys + case PsetElementsInIssuanceInflationKeysCommitment: + issuanceInflationKeysCommitment := kp.value[:] + if len(issuanceInflationKeysCommitment) != 33 { + return nil, ErrInvalidIssuanceInflationKeysCommitmentLength + } + + input.issuanceInflationKeysCommitment = issuanceInflationKeysCommitment + case PsetElementsInIssuanceBlindingNonce: + issuanceBlindingNonce := kp.value[:] + if len(issuanceBlindingNonce) != 32 { + return nil, ErrInvalidIssuanceBlindingNonceLength + } + + input.issuanceBlindingNonce = issuanceBlindingNonce + case PsetElementsInIssuanceAssetEntropy: + issuanceAssetEntropy := kp.value[:] + if len(issuanceAssetEntropy) != 32 { + return nil, ErrInvalidIssuanceAssetEntropyLength + } + + input.issuanceAssetEntropy = issuanceAssetEntropy + case PsetElementsInUtxoRangeProof: + input.inUtxoRangeProof = kp.value + case PsetElementsInIssuanceBlindValueProof: + input.issuanceBlindValueProof = kp.value + case PsetElementsInIssuanceBlindInflationKeysProof: + input.issuanceBlindInflationKeysProof = kp.value + default: + input.proprietaryData = append(input.proprietaryData, *pd) + } + } + default: + unknowns, err := deserializeUnknownKeyPairs(buf) + if err != nil { + return nil, err + } + input.unknowns = unknowns + } + } + + //validate mandatory fields + if !prevTxIDFound { + return nil, ErrMissingPrevTxID + } + + if !outputIndexFound { + return nil, ErrMissingOutputIndex + } + + return &input, nil +} diff --git a/psetv2/input_serialization.go b/psetv2/input_serialization.go new file mode 100644 index 0000000..5c8510b --- /dev/null +++ b/psetv2/input_serialization.go @@ -0,0 +1,475 @@ +package psetv2 + +import ( + "encoding/binary" + + "github.com/vulpemventures/go-elements/internal/bufferutil" +) + +func (i Input) serialize(s *bufferutil.Serializer) error { + inputKeyPairs, err := i.getKeyPairs() + if err != nil { + return err + } + + for _, v := range inputKeyPairs { + if err := serializeKeyPair(v, s); err != nil { + return err + } + } + + if err := s.WriteUint8(separator); err != nil { + return err + } + + return nil +} + +func (i *Input) getKeyPairs() ([]keyPair, error) { + keyPairs := make([]keyPair, 0) + + if i.nonWitnessUtxo != nil { + nonWitnessUtxoBytes, err := i.nonWitnessUtxo.Serialize() + if err != nil { + return nil, err + } + nonWitnessKeyPair := keyPair{ + key: key{ + keyType: PsbtInNonWitnessUtxo, + keyData: nil, + }, + value: nonWitnessUtxoBytes, + } + keyPairs = append(keyPairs, nonWitnessKeyPair) + } + + if i.witnessUtxo != nil { + witnessUtxoBytes, err := writeTxOut(i.witnessUtxo) + if err != nil { + return nil, err + } + nonWitnessKeyPair := keyPair{ + key: key{ + keyType: PsbtInWitnessUtxo, + keyData: nil, + }, + value: witnessUtxoBytes, + } + keyPairs = append(keyPairs, nonWitnessKeyPair) + } + + if i.partialSigs != nil { + for _, v := range i.partialSigs { + partialSigKeyPair := keyPair{ + key: key{ + keyType: PsbtInPartialSig, + keyData: v.PubKey, + }, + value: v.Signature, + } + keyPairs = append(keyPairs, partialSigKeyPair) + } + } + + if i.sigHashType != nil { + sigHashTypeBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(sigHashTypeBytes, uint32(*i.sigHashType)) + fallBackLockTimeKeyPair := keyPair{ + key: key{ + keyType: PsbtInSighashType, + keyData: nil, + }, + value: sigHashTypeBytes, + } + keyPairs = append(keyPairs, fallBackLockTimeKeyPair) + } + + if i.redeemScript != nil { + redeemScriptKeyPair := keyPair{ + key: key{ + keyType: PsbtInRedeemScript, + keyData: nil, + }, + value: i.redeemScript, + } + keyPairs = append(keyPairs, redeemScriptKeyPair) + } + + if i.witnessScript != nil { + witnessScriptKeyPair := keyPair{ + key: key{ + keyType: PsbtInWitnessScript, + keyData: nil, + }, + value: i.witnessScript, + } + keyPairs = append(keyPairs, witnessScriptKeyPair) + } + + if i.bip32Derivation != nil { + for _, v := range i.bip32Derivation { + bip32DerivationPathKeyPair := keyPair{ + key: key{ + keyType: PsbtInBip32Derivation, + keyData: v.PubKey, + }, + value: SerializeBIP32Derivation(v.MasterKeyFingerprint, v.Bip32Path), + } + keyPairs = append(keyPairs, bip32DerivationPathKeyPair) + } + } + + if i.finalScriptSig != nil { + finalScriptSigKeyPair := keyPair{ + key: key{ + keyType: PsbtInFinalScriptsig, + keyData: nil, + }, + value: i.finalScriptSig, + } + keyPairs = append(keyPairs, finalScriptSigKeyPair) + } + + if i.finalScriptWitness != nil { + finalScriptWitnessKeyPair := keyPair{ + key: key{ + keyType: PsbtInFinalScriptwitness, + keyData: nil, + }, + value: i.finalScriptWitness, + } + keyPairs = append(keyPairs, finalScriptWitnessKeyPair) + } + + if i.ripemd160Preimages != nil { + for k, v := range i.ripemd160Preimages { + ripemd160PreimagesKeyPair := keyPair{ + key: key{ + keyType: PsbtInRipemd160, + keyData: k[:], + }, + value: v, + } + keyPairs = append(keyPairs, ripemd160PreimagesKeyPair) + } + } + + if i.sha256Preimages != nil { + for k, v := range i.sha256Preimages { + sha256PreimagesKeyPair := keyPair{ + key: key{ + keyType: PsbtInSha256, + keyData: k[:], + }, + value: v, + } + keyPairs = append(keyPairs, sha256PreimagesKeyPair) + } + } + + if i.hash160Preimages != nil { + for k, v := range i.hash160Preimages { + hash160PreimagesKeyPair := keyPair{ + key: key{ + keyType: PsbtInHash160, + keyData: k[:], + }, + value: v, + } + keyPairs = append(keyPairs, hash160PreimagesKeyPair) + } + } + + if i.hash256Preimages != nil { + for k, v := range i.hash256Preimages { + hash256PreimagesKeyPair := keyPair{ + key: key{ + keyType: PsbtInHash256, + keyData: k[:], + }, + value: v, + } + keyPairs = append(keyPairs, hash256PreimagesKeyPair) + } + } + + if i.previousTxid != nil { + previousTxidKeyPair := keyPair{ + key: key{ + keyType: PsbtInPreviousTxid, + keyData: nil, + }, + value: i.previousTxid, + } + keyPairs = append(keyPairs, previousTxidKeyPair) + } + + if i.previousOutputIndex != nil { + previousOutputIndexBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(previousOutputIndexBytes, *i.previousOutputIndex) + previousOutputIndexKeyPair := keyPair{ + key: key{ + keyType: PsbtInOutputIndex, + keyData: nil, + }, + value: previousOutputIndexBytes, + } + keyPairs = append(keyPairs, previousOutputIndexKeyPair) + } + + if i.sequence != nil { + sequenceBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(sequenceBytes, *i.sequence) + sequenceKeyPair := keyPair{ + key: key{ + keyType: PsbtInSequence, + keyData: nil, + }, + value: sequenceBytes, + } + keyPairs = append(keyPairs, sequenceKeyPair) + } + + if i.requiredTimeLocktime != nil { + requiredTimeLocktimeBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(requiredTimeLocktimeBytes, *i.requiredTimeLocktime) + requiredTimeLocktimeKeyPair := keyPair{ + key: key{ + keyType: PsbtInRequiredTimeLocktime, + keyData: nil, + }, + value: requiredTimeLocktimeBytes, + } + keyPairs = append(keyPairs, requiredTimeLocktimeKeyPair) + } + + if i.requiredHeightLocktime != nil { + requiredHeightLocktimeBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(requiredHeightLocktimeBytes, *i.requiredHeightLocktime) + requiredHeightLocktimeKeyPair := keyPair{ + key: key{ + keyType: PsbtInRequiredTimeLocktime, + keyData: nil, + }, + value: requiredHeightLocktimeBytes, + } + keyPairs = append(keyPairs, requiredHeightLocktimeKeyPair) + } + + if i.issuanceValue != nil { + issuanceValueBytes := make([]byte, 4) + binary.LittleEndian.PutUint64(issuanceValueBytes, uint64(*i.issuanceValue)) + issuanceValueKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsInIssuanceValue, nil), + }, + value: issuanceValueBytes, + } + keyPairs = append(keyPairs, issuanceValueKeyPair) + } + + if i.issuanceValueCommitment != nil { + issuanceValueCommitmentKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsInIssuanceValueCommitment, nil), + }, + value: i.issuanceValueCommitment, + } + keyPairs = append(keyPairs, issuanceValueCommitmentKeyPair) + } + + if i.issuanceValueRangeproof != nil { + issuanceValueRangeproofKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsInIssuanceValueRangeproof, nil), + }, + value: i.issuanceValueRangeproof, + } + keyPairs = append(keyPairs, issuanceValueRangeproofKeyPair) + } + + if i.issuanceKeysRangeproof != nil { + issuanceKeysRangeproofKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsInIssuanceKeysRangeproof, nil), + }, + value: i.issuanceKeysRangeproof, + } + keyPairs = append(keyPairs, issuanceKeysRangeproofKeyPair) + } + + if i.peginTx != nil { + peginTxBytes, err := i.peginTx.Serialize() + if err != nil { + return nil, err + } + + peginTxKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsInPegInTx, nil), + }, + value: peginTxBytes, + } + keyPairs = append(keyPairs, peginTxKeyPair) + } + + if i.peginTxoutProof != nil { + peginTxoutProofKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsInPegInTxoutProof, nil), + }, + value: i.peginTxoutProof, + } + keyPairs = append(keyPairs, peginTxoutProofKeyPair) + } + + if i.peginGenesisHash != nil { + peginGenesisHashKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsInPegInGenesis, nil), + }, + value: i.peginGenesisHash, + } + keyPairs = append(keyPairs, peginGenesisHashKeyPair) + } + + if i.peginClaimScript != nil { + peginClaimScriptKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsInPegInClaimScript, nil), + }, + value: i.peginClaimScript, + } + keyPairs = append(keyPairs, peginClaimScriptKeyPair) + } + + if i.peginValue != nil { + var peginValueBytes []byte + binary.LittleEndian.PutUint64(peginValueBytes, uint64(*i.peginValue)) + + peginValueKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsInPegInValue, nil), + }, + value: peginValueBytes, + } + keyPairs = append(keyPairs, peginValueKeyPair) + } + + if i.peginWitness != nil { + peginWitnessKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsInPegInWitness, nil), + }, + value: i.peginWitness, + } + keyPairs = append(keyPairs, peginWitnessKeyPair) + } + + if i.issuanceInflationKeys != nil { + var issuanceInflationKeysBytes []byte + binary.LittleEndian.PutUint64(issuanceInflationKeysBytes, uint64(*i.issuanceInflationKeys)) + + issuanceInflationKeysKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsInIssuanceInflationKeys, nil), + }, + value: issuanceInflationKeysBytes, + } + keyPairs = append(keyPairs, issuanceInflationKeysKeyPair) + } + + if i.issuanceInflationKeysCommitment != nil { + issuanceInflationKeysCommitmentKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsInIssuanceInflationKeysCommitment, nil), + }, + value: i.issuanceInflationKeysCommitment, + } + keyPairs = append(keyPairs, issuanceInflationKeysCommitmentKeyPair) + } + + if i.issuanceBlindingNonce != nil { + issuanceBlindingNonceKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsInIssuanceBlindingNonce, nil), + }, + value: i.issuanceBlindingNonce, + } + keyPairs = append(keyPairs, issuanceBlindingNonceKeyPair) + } + + if i.issuanceAssetEntropy != nil { + issuanceAssetEntropyKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsInIssuanceAssetEntropy, nil), + }, + value: i.issuanceAssetEntropy, + } + keyPairs = append(keyPairs, issuanceAssetEntropyKeyPair) + } + + if i.inUtxoRangeProof != nil { + inUtxoRangeProofKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsInUtxoRangeProof, nil), + }, + value: i.inUtxoRangeProof, + } + keyPairs = append(keyPairs, inUtxoRangeProofKeyPair) + } + + if i.issuanceBlindValueProof != nil { + issuanceBlindValueProofKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsInIssuanceBlindValueProof, nil), + }, + value: i.issuanceBlindValueProof, + } + keyPairs = append(keyPairs, issuanceBlindValueProofKeyPair) + } + + if i.issuanceBlindInflationKeysProof != nil { + issuanceBlindInflationKeysProofKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsInIssuanceBlindInflationKeysProof, nil), + }, + value: i.issuanceBlindInflationKeysProof, + } + keyPairs = append(keyPairs, issuanceBlindInflationKeysProofKeyPair) + } + + for _, v := range i.proprietaryData { + kp := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(v.subtype, v.keyData), + }, + value: v.value, + } + keyPairs = append(keyPairs, kp) + } + + for _, v := range i.unknowns { + keyPairs = append(keyPairs, v) + } + + return keyPairs, nil +} diff --git a/psetv2/key_pair.go b/psetv2/key_pair.go new file mode 100644 index 0000000..8ec65f7 --- /dev/null +++ b/psetv2/key_pair.go @@ -0,0 +1,170 @@ +package psetv2 + +import ( + "bytes" + + "github.com/vulpemventures/go-elements/internal/bufferutil" +) + +// keyPair format: +// := +// := +// := +type keyPair struct { + key key + value []byte +} + +type key struct { + keyType uint8 + keyData []byte +} + +func serializeKeyPair(kp keyPair, s *bufferutil.Serializer) error { + if err := s.WriteVarInt(uint64(len(kp.key.keyData) + 1)); err != nil { + return err + } + + if err := s.WriteUint8(kp.key.keyType); err != nil { + return err + } + + if err := s.WriteSlice(kp.key.keyData); err != nil { + return err + } + + if err := s.WriteVarInt(uint64(len(kp.value))); err != nil { + return err + } + + if err := s.WriteSlice(kp.value); err != nil { + return err + } + + return nil +} + +func (k *keyPair) deserialize(buf *bytes.Buffer) error { + if err := k.key.deserialize(buf); err != nil { + return err + } + + d := bufferutil.NewDeserializer(buf) + valueSize, err := d.ReadVarInt() + if err != nil { + return err + } + + valueData, err := d.ReadSlice(uint(valueSize)) + if err != nil { + return err + } + + k.value = valueData + + return nil +} + +func (k *key) deserialize(buf *bytes.Buffer) error { + d := bufferutil.NewDeserializer(buf) + + keyByteSize, err := d.ReadVarInt() + if err != nil { + return err + } + + if keyByteSize == 0 { + return ErrNoMoreKeyPairs + } + + if keyByteSize > maxPsbtKeyLength { + return ErrInvalidKeySize + } + + key, err := d.ReadSlice(uint(keyByteSize)) + if err != nil { + return err + } + + keyType := key[0] + k.keyType = keyType + k.keyData = nil + if len(key) > 1 { + k.keyData = key[1:] + } + + return nil +} + +type proprietaryData struct { + identifier []byte + subtype uint8 + keyData []byte + value []byte +} + +func (p *proprietaryData) proprietaryDataFromKeyPair(keyPair keyPair) error { + d := bufferutil.NewDeserializer(bytes.NewBuffer(keyPair.key.keyData)) + + if keyPair.key.keyType != 0xFC { + return ErrInvalidProprietaryKey + } + + identifierByteSize, err := d.ReadVarInt() + if err != nil { + return err + } + + if identifierByteSize == 0 { + return ErrInvalidProprietaryIdentifier + } + + identifier, err := d.ReadSlice(uint(identifierByteSize)) + if err != nil { + return err + } + + subType, err := d.ReadUint8() + if err != nil { + return err + } + + keyData := d.ReadToEnd() + + value := keyPair.value + + p.identifier = identifier + p.subtype = subType + p.keyData = keyData + p.value = value + + return nil +} + +func proprietaryKey(subType uint8, keyData []byte) []byte { + result := make([]byte, 0) + result = append(result, byte(len(psetMagic)-1)) + result = append(result, psetMagic[:len(psetMagic)-1]...) + result = append(result, subType) + if keyData != nil { + result = append(result, keyData...) + } + + return result +} + +func deserializeUnknownKeyPairs(buf *bytes.Buffer) ([]keyPair, error) { + unknowns := make([]keyPair, 0) + for { + kp := &keyPair{} + if err := kp.deserialize(buf); err != nil { + if err == ErrNoMoreKeyPairs { + break + } + return nil, err + } + unknowns = append(unknowns, *kp) + } + + return unknowns, nil +} diff --git a/psetv2/output.go b/psetv2/output.go new file mode 100644 index 0000000..a5399e0 --- /dev/null +++ b/psetv2/output.go @@ -0,0 +1,140 @@ +package psetv2 + +import ( + "errors" + + "github.com/vulpemventures/go-elements/elementsutil" + + "github.com/vulpemventures/go-elements/transaction" +) + +const ( + //Per output types: BIP 174, 370, 371 + PsbtOutRedeemScript = 0x00 //BIP 174 + PsbtOutWitnessScript = 0x01 //BIP 174 + PsbtOutBip32Derivation = 0x02 //BIP 174 + PsbtOutAmount = 0x03 //BIP 370 + PsbtOutScript = 0x04 //BIP 370 + PsbtOutTapInternalKey = 0x05 //BIP 371 + PsbtOutTapTree = 0x06 //BIP 371 + PsbtOutTapLeafScript = 0x06 //BIP 371 //TODO is duplicate key type allowed? + PsbtOutTapBip32Derivation = 0x07 //BIP 371 + PsbtOutProprietary = 0xFC //BIP 174 + + //Elements Proprietary types + PsetElementsOutValueCommitment = 0x01 + PsetElementsOutAsset = 0x02 + PsetElementsOutAssetCommitment = 0x03 + PsetElementsOutValueRangeproof = 0x04 + PsetElementsOutAssetSurjectionProof = 0x05 + PsetElementsOutBlindingPubkey = 0x06 + PsetElementsOutEcdhPubkey = 0x07 + PsetElementsOutBlinderIndex = 0x08 + PsetElementsOutBlindValueProof = 0x09 + PsetElementsOutBlindAssetProof = 0x0a +) + +var ( + ErrMissingOutAsset = errors.New("missing output asset") + ErrMissingOutAmount = errors.New("missing output amount") + ErrMissingScript = errors.New("missing output script") + ErrInvalidValueCommitmentLength = errors.New("invalid value commitment length") + ErrInvalidOutAssetLength = errors.New("invalid output asset length") + ErrInvalidOutAssetCommitmentLength = errors.New("invalid output asset commitment length") + ErrInvalidOutputScriptLength = errors.New("invalid output script length") +) + +type Output struct { + // The redeem script for this output. + redeemScript []byte + // The witness script for this output. + witnessScript []byte + // A map from public keys needed to spend this output to their + // corresponding master key fingerprints and derivation paths. + bip32Derivation []DerivationPathWithPubKey + // (PSET2) The amount of the output + outputAmount *int64 + // (PSET2) The output script + outputScript []byte + // The 33 byte Value Commitment for this output. + outputValueCommitment []byte + // The explicit 32 byte asset tag for this output. + outputAsset []byte + // The 33 byte Asset Commitment + outputAssetCommitment []byte + // The rangeproof for the value of this output. + outputValueRangeproof []byte + // The asset surjection proof for this output's asset. + outputAssetSurjectionProof []byte + // The 33 byte blinding pubkey to be used when blinding this output. + outputBlindingPubkey []byte + // The 33 byte ephemeral pubkey used for ECDH in the blinding of this output + outputEcdhPubkey []byte + // Index of the input whose owner should blind this output. + outputBlinderIndex *uint32 + // An explicit value rangeproof that proves that the value commitment in + //PSBT_ELEMENTS_OUT_VALUE_COMMITMENT matches the explicit value in PSBT_OUT_VALUE + outputBlindValueProof []byte + // An asset surjection proof with this output's asset as the only asset in + //the input set in order to prove that the asset commitment in + //PSBT_ELEMENTS_OUT_ASSET_COMMITMENT matches the explicit asset in PSBT_ELEMENTS_OUT_ASSET + outputBlindAssetProof []byte + proprietaryData []proprietaryData + // Unknown key-value pairs for this input. + unknowns []keyPair +} + +func (o *Output) IsBlinded() bool { + return o.outputValueCommitment != nil && + o.outputAssetCommitment != nil && + o.outputValueRangeproof != nil && + o.outputAssetSurjectionProof != nil && + o.outputEcdhPubkey != nil && + o.outputBlindingPubkey != nil +} + +func (o *Output) ToBlind() bool { + return o.outputBlindingPubkey != nil && o.outputBlinderIndex != nil +} + +func psetOutputFromTxOutput(output transaction.TxOutput) (*Output, error) { + script := output.Script + + var outputAmount *int64 + var outputCommitment []byte + if len(output.Value) == 9 && output.Value[0] == 1 { + o, err := elementsutil.ElementsToSatoshiValue(output.Value) + if err != nil { + return nil, err + } + ov := int64(o) + outputAmount = &ov + } else { + if len(output.Value) != 33 { + return nil, ErrInvalidAssetCommitmentLength + } + + outputCommitment = output.Value + } + + var outputAsset []byte + var outputAssetCommitment []byte + if isAssetExplicit(output.Asset) { + outputAsset = output.Asset + } else { + if len(output.Value) != 33 { + return nil, ErrInvalidAssetCommitmentLength + } + + outputAssetCommitment = output.Asset + } + + return &Output{ + outputScript: script, + outputAmount: outputAmount, + outputValueCommitment: outputCommitment, + outputAsset: outputAsset, + outputEcdhPubkey: output.Nonce, + outputAssetCommitment: outputAssetCommitment, + }, nil +} diff --git a/psetv2/output_deserialization.go b/psetv2/output_deserialization.go new file mode 100644 index 0000000..34942ef --- /dev/null +++ b/psetv2/output_deserialization.go @@ -0,0 +1,148 @@ +package psetv2 + +import ( + "bytes" + "encoding/binary" +) + +func deserializeOutput(buf *bytes.Buffer) (*Output, error) { + output := &Output{} + + kp := &keyPair{} + + outputAssetFound := false + outputAmountFound := false + outputScriptFound := false + + //read bytes and do the deserialization until separator is found at the + //end of global map + for { + if err := kp.deserialize(buf); err != nil { + if err == ErrNoMoreKeyPairs { + break + } + return nil, err + } + + switch kp.key.keyType { + case PsbtOutRedeemScript: + output.redeemScript = kp.value + case PsbtOutWitnessScript: + output.witnessScript = kp.value + case PsbtOutBip32Derivation: + if !validatePubkey(kp.key.keyData) { + return nil, ErrInvalidPsbtFormat + } + master, derivationPath, err := readBip32Derivation(kp.value) + if err != nil { + return nil, err + } + + // Duplicate keys are not allowed + for _, x := range output.bip32Derivation { + if bytes.Equal(x.PubKey, kp.key.keyData) { + return nil, ErrDuplicatePubKeyInBip32DerPath + } + } + + output.bip32Derivation = append( + output.bip32Derivation, + DerivationPathWithPubKey{ + PubKey: kp.key.keyData, + MasterKeyFingerprint: master, + Bip32Path: derivationPath, + }, + ) + case PsbtOutAmount: + outputAmount := int64(binary.LittleEndian.Uint64(kp.value)) + output.outputAmount = &outputAmount + outputAmountFound = true + case PsbtOutScript: + //TODO check if bellow validation is needed + //if len(kp.value) == 0 { + // return nil, ErrInvalidOutputScriptLength + //} + output.outputScript = kp.value + outputScriptFound = true + case PsbtGlobalProprietary: + pd := &proprietaryData{} + if err := pd.proprietaryDataFromKeyPair(*kp); err != nil { + return nil, err + } + + if bytes.Equal(pd.identifier, psetMagic[:len(psetMagic)-1]) { + switch pd.subtype { + case PsetElementsOutValueCommitment: + outValueCommitment := kp.value + if len(outValueCommitment) != 33 { + return nil, ErrInvalidValueCommitmentLength + } + + outputAmountFound = true + output.outputValueCommitment = outValueCommitment + case PsetElementsOutAsset: + outputAsset := kp.value + if len(outputAsset) != 32 { + return nil, ErrInvalidOutAssetLength + } + output.outputAsset = outputAsset + outputAssetFound = true + case PsetElementsOutAssetCommitment: + outputAssetCommitment := kp.value + if len(outputAssetCommitment) != 33 { + return nil, ErrInvalidOutAssetCommitmentLength + } + outputAssetFound = true + + output.outputAssetCommitment = outputAssetCommitment + case PsetElementsOutValueRangeproof: + output.outputValueRangeproof = kp.value + case PsetElementsOutAssetSurjectionProof: + output.outputAssetSurjectionProof = kp.value + case PsetElementsOutBlindingPubkey: + if !validatePubkey(kp.value) { + return nil, ErrInvalidPsbtFormat + } + + output.outputBlindingPubkey = kp.value + case PsetElementsOutEcdhPubkey: + if !validatePubkey(kp.value) { + return nil, ErrInvalidPsbtFormat + } + + output.outputEcdhPubkey = kp.value + case PsetElementsOutBlinderIndex: + outputBlinderIndex := binary.LittleEndian.Uint32(kp.value) + output.outputBlinderIndex = &outputBlinderIndex + case PsetElementsOutBlindValueProof: + output.outputBlindValueProof = kp.value + case PsetElementsOutBlindAssetProof: + output.outputBlindAssetProof = kp.value + default: + output.proprietaryData = append(output.proprietaryData, *pd) + } + } + default: + unknowns, err := deserializeUnknownKeyPairs(buf) + if err != nil { + return nil, err + } + output.unknowns = unknowns + } + } + + //validate mandatory fields + if !outputAssetFound { + return nil, ErrMissingOutAsset + } + + if !outputAmountFound { + return nil, ErrMissingOutAmount + } + + if !outputScriptFound { + return nil, ErrMissingScript + } + + return output, nil +} diff --git a/psetv2/output_serialization.go b/psetv2/output_serialization.go new file mode 100644 index 0000000..7f460da --- /dev/null +++ b/psetv2/output_serialization.go @@ -0,0 +1,219 @@ +package psetv2 + +import ( + "encoding/binary" + + "github.com/vulpemventures/go-elements/internal/bufferutil" +) + +func (o *Output) serialize(s *bufferutil.Serializer) error { + outputKeyPairs, err := o.getKeyPairs() + if err != nil { + return err + } + + for _, v := range outputKeyPairs { + if err := serializeKeyPair(v, s); err != nil { + return err + } + } + + if err := s.WriteUint8(separator); err != nil { + return err + } + + return nil +} + +func (o *Output) getKeyPairs() ([]keyPair, error) { + keyPairs := make([]keyPair, 0) + + if o.redeemScript != nil { + redeemScriptKeyPair := keyPair{ + key: key{ + keyType: PsbtOutRedeemScript, + keyData: nil, + }, + value: o.redeemScript, + } + keyPairs = append(keyPairs, redeemScriptKeyPair) + } + + if o.witnessScript != nil { + witnessScriptKeyPair := keyPair{ + key: key{ + keyType: PsbtOutWitnessScript, + keyData: nil, + }, + value: o.witnessScript, + } + keyPairs = append(keyPairs, witnessScriptKeyPair) + } + + if o.bip32Derivation != nil { + for _, v := range o.bip32Derivation { + bip32DerivationPathKeyPair := keyPair{ + key: key{ + keyType: PsbtOutBip32Derivation, + keyData: v.PubKey, + }, + value: SerializeBIP32Derivation(v.MasterKeyFingerprint, v.Bip32Path), + } + keyPairs = append(keyPairs, bip32DerivationPathKeyPair) + } + } + + if o.outputScript != nil { + outputScriptKeyPair := keyPair{ + key: key{ + keyType: PsbtOutScript, + keyData: nil, + }, + value: o.outputScript, + } + keyPairs = append(keyPairs, outputScriptKeyPair) + } + + if o.outputValueCommitment != nil { + outputValueCommitmentKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsOutValueCommitment, nil), + }, + value: o.outputValueCommitment, + } + keyPairs = append(keyPairs, outputValueCommitmentKeyPair) + } + + if o.outputAmount != nil { + outputAmountBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(outputAmountBytes, uint64(*o.outputAmount)) + outputAmountKeyPair := keyPair{ + key: key{ + keyType: PsbtOutAmount, + keyData: nil, + }, + value: outputAmountBytes, + } + keyPairs = append(keyPairs, outputAmountKeyPair) + } + + if o.outputAssetCommitment != nil { + outputAssetCommitmentKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsOutAssetCommitment, nil), + }, + value: o.outputAssetCommitment, + } + keyPairs = append(keyPairs, outputAssetCommitmentKeyPair) + } + + if o.outputAsset != nil { + outputAssetKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsOutAsset, nil), + }, + value: o.outputAsset, + } + keyPairs = append(keyPairs, outputAssetKeyPair) + } + + if o.outputValueRangeproof != nil { + outputValueRangeproofKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsOutValueRangeproof, nil), + }, + value: o.outputValueRangeproof, + } + keyPairs = append(keyPairs, outputValueRangeproofKeyPair) + } + + if o.outputAssetSurjectionProof != nil { + outputAssetSurjectionProofKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsOutAssetSurjectionProof, nil), + }, + value: o.outputAssetSurjectionProof, + } + keyPairs = append(keyPairs, outputAssetSurjectionProofKeyPair) + } + + if o.outputBlindingPubkey != nil { + outputBlindingPubkeyKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsOutBlindingPubkey, nil), + }, + value: o.outputBlindingPubkey, + } + keyPairs = append(keyPairs, outputBlindingPubkeyKeyPair) + } + + if o.outputEcdhPubkey != nil { + outputEcdhPubkeyKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsOutEcdhPubkey, nil), + }, + value: o.outputEcdhPubkey, + } + keyPairs = append(keyPairs, outputEcdhPubkeyKeyPair) + } + + if o.outputBlinderIndex != nil { + outputBlinderIndexBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(outputBlinderIndexBytes, *o.outputBlinderIndex) + + outputBlinderIndexKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsOutBlinderIndex, nil), + }, + value: outputBlinderIndexBytes, + } + keyPairs = append(keyPairs, outputBlinderIndexKeyPair) + } + + if o.outputBlindValueProof != nil { + outputBlindValueProofKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsOutBlindValueProof, nil), + }, + value: o.outputBlindValueProof, + } + keyPairs = append(keyPairs, outputBlindValueProofKeyPair) + } + + if o.outputBlindAssetProof != nil { + outputBlindAssetProofKeyPair := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(PsetElementsOutBlindAssetProof, nil), + }, + value: o.outputBlindAssetProof, + } + keyPairs = append(keyPairs, outputBlindAssetProofKeyPair) + } + + for _, v := range o.proprietaryData { + kp := keyPair{ + key: key{ + keyType: PsbtGlobalProprietary, + keyData: proprietaryKey(v.subtype, v.keyData), + }, + value: v.value, + } + keyPairs = append(keyPairs, kp) + } + + for _, v := range o.unknowns { + keyPairs = append(keyPairs, v) + } + + return keyPairs, nil +} diff --git a/psetv2/partialsig.go b/psetv2/partialsig.go new file mode 100644 index 0000000..842d578 --- /dev/null +++ b/psetv2/partialsig.go @@ -0,0 +1,60 @@ +package psetv2 + +import ( + "bytes" + + "github.com/btcsuite/btcd/btcec" +) + +//TODO add ref to btcutil + +// PartialSig encapsulate a (BTC public key, ECDSA signature) +// pair, note that the fields are stored as byte slices, not +// btcec.PublicKey or btcec.Signature (because manipulations will +// be with the former not the latter, here); compliance with consensus +// serialization is enforced with .checkValid() +type PartialSig struct { + PubKey []byte + Signature []byte +} + +// PartialSigSorter implements sort.Interface for PartialSig. +type PartialSigSorter []*PartialSig + +func (s PartialSigSorter) Len() int { return len(s) } + +func (s PartialSigSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func (s PartialSigSorter) Less(i, j int) bool { + return bytes.Compare(s[i].PubKey, s[j].PubKey) < 0 +} + +// validatePubkey checks if pubKey is *any* valid pubKey serialization in a +// Bitcoin context (compressed/uncomp. OK). +func validatePubkey(pubKey []byte) bool { + _, err := btcec.ParsePubKey(pubKey, btcec.S256()) + if err != nil { + return false + } + return true +} + +// validateSignature checks that the passed byte slice is a valid DER-encoded +// ECDSA signature, including the sighash flag. It does *not* of course +// validate the signature against any message or public key. +func validateSignature(sig []byte) bool { + _, err := btcec.ParseDERSignature(sig, btcec.S256()) + if err != nil { + return false + } + return true +} + +// checkValid checks that both the pbukey and sig are valid. See the methods +// (PartialSig, validatePubkey, validateSignature) for more details. +// +// TODO(waxwing): update for Schnorr will be needed here if/when that +// activates. +func (ps *PartialSig) checkValid() bool { + return validatePubkey(ps.PubKey) && validateSignature(ps.Signature) +} diff --git a/psetv2/pset.go b/psetv2/pset.go new file mode 100644 index 0000000..d4d2275 --- /dev/null +++ b/psetv2/pset.go @@ -0,0 +1,442 @@ +package psetv2 + +import ( + "bytes" + "encoding/base64" + "encoding/hex" + "errors" + + "github.com/vulpemventures/go-elements/elementsutil" + + "github.com/btcsuite/btcutil/psbt" + + "github.com/vulpemventures/go-elements/transaction" +) + +const ( + separator = 0x00 + maxPsbtKeyLength = 10000 + minTimeLockTime = 500000000 + maxHeightLockTime = 499999999 +) + +var ( + psetMagic = []byte{0x70, 0x73, 0x65, 0x74, 0xFF} //'pset' string with magic separator 0xFF + ErrInvalidPsbtFormat = errors.New("invalid PSBT serialization format") + ErrNoMoreKeyPairs = errors.New("no more key-pairs") + ErrInvalidKeySize = errors.New("invalid key size") + ErrInvalidProprietaryKey = errors.New("invalid proprietaryData key") + ErrInvalidProprietaryIdentifier = errors.New("invalid proprietaryData identifier") + ErrInvalidMagicBytes = errors.New("invalid magic bytes") + ErrNotModifiable = errors.New("pset not modifiable") + ErrInputAlreadyExist = errors.New("input already exists") + ErrInvalidLockTimeType = errors.New("invalid locktime type") + ErrInvalidLockTime = errors.New("invalid lock time") + ErrMissingMandatoryFields = errors.New("missing mandatory fields") + // ErrInvalidSignatureForInput indicates that the signature the user is + // trying to append to the PSBT is invalid, either because it does + // not correspond to the previous transaction hash, or redeem script, + // or witness script. + // NOTE this does not include ECDSA signature checking. + ErrInvalidSignatureForInput = errors.New("Signature does not correspond " + + "to this input") +) + +// Pset - Partially signed Element's transaction +//Format: +// := * * +// := 0x70 0x73 0x65 0x74 0xFF -> pset starts with magic bytes, after which goes global map +//followed by more input-map's and output-map's +// := * 0x00 -> there is one global-map, there can be many keypair's, global map ends with separator +// := * 0x00 -> there can be many input-map's, there can be many keypair's, input map ends with separator +// := * 0x00 -> there can be many output-map's, there can be many keypair's, output map ends with separator +// := +// := +// := +// Each map can contain proprietaryData data and unknowns keypair's +// Full spec: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki +type Pset struct { + Global *Global + Inputs []Input + Outputs []Output +} + +func NewFromBuffer(buf *bytes.Buffer) (*Pset, error) { + return deserialize(buf) +} + +func NewFromHex(h string) (*Pset, error) { + hexBytes, err := hex.DecodeString(h) + if err != nil { + return nil, err + } + buf := bytes.NewBuffer(hexBytes) + return NewFromBuffer(buf) +} + +func NewPsetFromBase64(psetBase64 string) (*Pset, error) { + psetBytes, err := base64.StdEncoding.DecodeString(psetBase64) + if err != nil { + return nil, err + } + + buf := bytes.NewBuffer(psetBytes) + return NewFromBuffer(buf) +} + +func (p *Pset) ToBase64() (string, error) { + buf, err := p.serialize() + if err != nil { + return "", err + } + + return base64.StdEncoding.EncodeToString(buf), nil +} + +func (p *Pset) ToHex() (string, error) { + buf, err := p.serialize() + if err != nil { + return "", err + } + + return hex.EncodeToString(buf), nil +} + +type InputArg struct { + TimeLock *TimeLock + TxInput transaction.TxInput +} + +func (p *Pset) addInput(inputArg InputArg) error { + if !p.IsInputModifiable() { + return ErrNotModifiable + } + + if p.IsInputDuplicate(inputArg.TxInput) { + return ErrInputAlreadyExist + } + + input, err := psetInputFromTxInput(inputArg.TxInput) + if err != nil { + return err + } + + if inputArg.TimeLock != nil { + if inputArg.TimeLock.RequiredTimeLock != nil { + input.requiredTimeLocktime = inputArg.TimeLock.RequiredTimeLock + } + if inputArg.TimeLock.RequiredHeightTimeLock != nil { + input.requiredHeightLocktime = inputArg.TimeLock.RequiredHeightTimeLock + } + } + + if input != nil { + if err := p.validateInputTimeLock(*input); err != nil { + return err + } + + p.Inputs = append(p.Inputs, *input) + *p.Global.txInfo.inputCount++ + } + + return nil +} + +type OutputArg struct { + BlinderIndex uint32 + BlindingPubKey []byte + TxOutput transaction.TxOutput +} + +func (p *Pset) addOutput(outputArg OutputArg) error { + if !p.IsOutputModifiable() { + return ErrNotModifiable + } + + output, err := psetOutputFromTxOutput(outputArg.TxOutput) + if err != nil { + return err + } + + if output != nil { + if output.outputAmount == nil || output.outputScript == nil { + return ErrMissingMandatoryFields + } + + if outputArg.BlindingPubKey != nil { + p.BlindingNeeded() + output.outputBlindingPubkey = outputArg.BlindingPubKey + output.outputBlinderIndex = &outputArg.BlinderIndex + } + + p.Outputs = append(p.Outputs, *output) + *p.Global.txInfo.outputCount++ + } + + return nil +} + +func (p *Pset) LockForModification() { + var lockedForModification uint8 = 0 //0000 0000 + p.Global.txInfo.txModifiable = &lockedForModification +} + +func (p *Pset) BlindingNeeded() { + var blindingNeeded uint8 = 1 //0000 0001 + p.Global.elementsTxModifiableFlag = &blindingNeeded +} + +func (p *Pset) IsInputModifiable() bool { + if p.Global.txInfo.txModifiable == nil { + return true + } + + return *p.Global.txInfo.txModifiable&1 == 1 // 0000 0001 +} + +func (p *Pset) IsInputDuplicate(txInput transaction.TxInput) bool { + for _, v := range p.Inputs { + if *v.previousOutputIndex == txInput.Index && bytes.Equal(v.previousTxid, txInput.Hash) { + return true + } + } + + return false +} + +func (p *Pset) IsOutputModifiable() bool { + if p.Global.txInfo.txModifiable == nil { + return true + } + + return *p.Global.txInfo.txModifiable&2 == 2 // 0000 0010 +} + +func (p *Pset) CalculateTimeLock() *uint32 { + var heightLockTime uint32 + var timeLockTime uint32 + for _, v := range p.Inputs { + if v.requiredTimeLocktime != nil { + if *v.requiredTimeLocktime > timeLockTime { + timeLockTime = *v.requiredTimeLocktime + } + } + if v.requiredHeightLocktime != nil { + if *v.requiredHeightLocktime > heightLockTime { + heightLockTime = *v.requiredHeightLocktime + } + } + } + + if heightLockTime > 0 { + return &heightLockTime + } + + if timeLockTime > 0 { + return &timeLockTime + } + + if p.Global.txInfo.fallBackLockTime != nil { + return p.Global.txInfo.fallBackLockTime + } + + return nil +} + +// OwnerProvidedOutputBlindingInfo verifies if owner of the input/output provided +//necessary output blinding +func (p *Pset) OwnerProvidedOutputBlindingInfo(blinderIndex int) bool { + var bi = uint32(blinderIndex) + for _, v := range p.Outputs { + if v.outputBlinderIndex != nil { + if *v.outputBlinderIndex == bi { + return v.outputBlindingPubkey != nil + } + } + } + + return false +} + +func (p *Pset) validateInputTimeLock(input Input) error { + shouldIterate := input.requiredTimeLocktime != nil || input.requiredHeightLocktime != nil + var timeLocktime uint32 + var heightLocktime uint32 + hasSigs := false + if shouldIterate { + if input.requiredTimeLocktime != nil || input.requiredHeightLocktime != nil { + oldTimeLock := p.CalculateTimeLock() + for _, v := range p.Inputs { + if v.requiredTimeLocktime != nil && v.requiredHeightLocktime == nil { + if input.requiredTimeLocktime == nil { + return ErrInvalidLockTimeType + } + } + + if v.requiredTimeLocktime == nil && v.requiredHeightLocktime != nil { + if input.requiredHeightLocktime == nil { + return ErrInvalidLockTimeType + } + } + + if v.requiredTimeLocktime != nil && input.requiredTimeLocktime != nil { + timeLocktime = *v.requiredTimeLocktime + if *input.requiredTimeLocktime > timeLocktime { + timeLocktime = *input.requiredTimeLocktime + } + } + + if v.requiredHeightLocktime != nil && input.requiredHeightLocktime != nil { + heightLocktime = *v.requiredHeightLocktime + if *input.requiredHeightLocktime > heightLocktime { + heightLocktime = *input.requiredHeightLocktime + } + } + + if v.partialSigs != nil { + hasSigs = true + } + } + + var newTimeLock uint32 + if p.Global.txInfo.fallBackLockTime != nil { + newTimeLock = *p.Global.txInfo.fallBackLockTime + } + + if heightLocktime > 0 { + newTimeLock = heightLocktime + } + + if timeLocktime > 0 { + newTimeLock = timeLocktime + } + + if oldTimeLock != nil { + if hasSigs && *oldTimeLock != newTimeLock { + return ErrInvalidLockTime + } + } + } + } + + return nil +} + +func (p *Pset) SanityCheck() error { + for _, tin := range p.Inputs { + if !tin.IsSane() { + return psbt.ErrInvalidPsbtFormat + } + } + + return nil +} + +func (p *Pset) blinded() bool { + for _, v := range p.Outputs { + if v.ToBlind() { + if !v.IsBlinded() { + return false + } + } + } + return true +} + +func (p *Pset) UnsignedTx(blinderSvc Blinder) (*transaction.Transaction, error) { + tx := transaction.NewTx(int32(*p.Global.version)) + + for _, v := range p.Inputs { + txInput := &transaction.TxInput{} + txInput.Hash = v.previousTxid + txInput.Index = *v.previousOutputIndex + txInput.Sequence = *v.sequence + if txInput.Issuance != nil { + txInput.Issuance.AssetBlindingNonce = v.issuanceBlindingNonce + txInput.Issuance.AssetEntropy = v.issuanceAssetEntropy + if v.issuanceValue != nil { + issuanceValue, err := elementsutil.SatoshiToElementsValue(uint64(*v.issuanceValue)) + if err != nil { + return nil, err + } + txInput.Issuance.AssetAmount = issuanceValue + } + if v.issuanceValueCommitment != nil { + txInput.Issuance.AssetAmount = v.issuanceValueCommitment + } + + if v.issuanceInflationKeys != nil { + tokenValue, err := elementsutil.SatoshiToElementsValue(uint64(*v.issuanceInflationKeys)) + if err != nil { + return nil, err + } + txInput.Issuance.TokenAmount = tokenValue + } + if v.issuanceInflationKeysCommitment != nil { + txInput.Issuance.TokenAmount = v.issuanceInflationKeysCommitment + } + } + + tx.AddInput(txInput) + } + + for _, v := range p.Outputs { + txOutput := &transaction.TxOutput{} + txOutput.Script = v.outputScript + + expValue := v.outputValueCommitment == nil + expValue = expValue && v.outputAmount != nil + if v.outputValueCommitment != nil && v.outputAmount != nil { + expValue = expValue && v.outputBlindValueProof != nil + expValue = expValue && v.outputAssetCommitment != nil + valid, err := blinderSvc.VerifyBlindValueProof( + *v.outputAmount, + v.outputValueCommitment, + v.outputBlindValueProof, + v.outputAssetCommitment, + ) + if err != nil { + return nil, err + } + expValue = expValue && valid + } + if expValue { + value, err := elementsutil.SatoshiToElementsValue(uint64(*v.outputAmount)) + if err != nil { + return nil, err + } + txOutput.Value = value + } else { + txOutput.Value = v.outputValueCommitment + txOutput.RangeProof = v.outputValueRangeproof + } + + expAsset := v.outputAssetCommitment == nil + expAsset = expAsset && v.outputAsset != nil + if v.outputAssetCommitment != nil && v.outputAsset != nil { + expAsset = expAsset && v.outputBlindAssetProof != nil + expAsset = expAsset && v.outputAsset != nil + valid, err := blinderSvc.VerifyBlindAssetProof( + v.outputAsset, + v.outputBlindAssetProof, + v.outputAssetCommitment, + ) + if err != nil { + return nil, err + } + expAsset = expAsset && valid + } + if expAsset { + txOutput.Asset = v.outputAsset + } else { + txOutput.Asset = v.outputAssetCommitment + txOutput.SurjectionProof = v.outputAssetSurjectionProof + } + + txOutput.Nonce = v.outputEcdhPubkey + + tx.AddOutput(txOutput) + } + + return tx, nil +} diff --git a/psetv2/pset_deserialization.go b/psetv2/pset_deserialization.go new file mode 100644 index 0000000..9f8d604 --- /dev/null +++ b/psetv2/pset_deserialization.go @@ -0,0 +1,51 @@ +package psetv2 + +import ( + "bytes" + + "github.com/vulpemventures/go-elements/internal/bufferutil" +) + +func deserialize(buf *bytes.Buffer) (*Pset, error) { + d := bufferutil.NewDeserializer(buf) + + magic, err := d.ReadSlice(uint(len(psetMagic))) + if err != nil { + return nil, err + } + + if !bytes.Equal(magic, psetMagic) { + return nil, ErrInvalidMagicBytes + } + + global, err := deserializeGlobal(buf) + if err != nil { + return nil, err + } + + inputs := make([]Input, 0) + for i := 0; i < int(*global.txInfo.inputCount); i++ { + input, err := deserializeInput(buf) + if err != nil { + return nil, err + } + + inputs = append(inputs, *input) + } + + outputs := make([]Output, 0) + for i := 0; i < int(*global.txInfo.outputCount); i++ { + output, err := deserializeOutput(buf) + if err != nil { + return nil, err + } + + outputs = append(outputs, *output) + } + + return &Pset{ + Global: global, + Inputs: inputs, + Outputs: outputs, + }, nil +} diff --git a/psetv2/pset_serialization.go b/psetv2/pset_serialization.go new file mode 100644 index 0000000..e8372aa --- /dev/null +++ b/psetv2/pset_serialization.go @@ -0,0 +1,40 @@ +package psetv2 + +import ( + "errors" + + "github.com/vulpemventures/go-elements/internal/bufferutil" +) + +func (p *Pset) serialize() ([]byte, error) { + if p == nil { + return nil, errors.New("") + } + + s, err := bufferutil.NewSerializer(nil) + if err != nil { + return nil, err + } + + if err := s.WriteSlice(psetMagic); err != nil { + return nil, err + } + + if err := p.Global.serialize(s); err != nil { + return nil, err + } + + for _, v := range p.Inputs { + if err := v.serialize(s); err != nil { + return nil, err + } + } + + for _, v := range p.Outputs { + if err := v.serialize(s); err != nil { + return nil, err + } + } + + return s.Bytes(), nil +} diff --git a/psetv2/pset_test.go b/psetv2/pset_test.go new file mode 100644 index 0000000..36fb631 --- /dev/null +++ b/psetv2/pset_test.go @@ -0,0 +1,191 @@ +package psetv2 + +import ( + "encoding/json" + "io/ioutil" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDeserializationAndSerialization(t *testing.T) { + file, err := ioutil.ReadFile("testdata/deserialize.json") + if err != nil { + t.Fatal(err) + } + var tests []map[string]interface{} + err = json.Unmarshal(file, &tests) + + for _, v := range tests { + testName := v["name"].(string) + t.Run(testName, func(t *testing.T) { + ptx, err := NewPsetFromBase64(v["base64"].(string)) + if err != nil { + t.Errorf("test: %v, err: %v", testName, err) + return + } + + b, err := ptx.ToBase64() + if err != nil { + t.Errorf("test: %v, err: %v", testName, err) + return + } + + assert.Equal(t, v["base64"].(string), b) + }) + } +} + +func TestPsetValidateInputTimeLock(t *testing.T) { + type fields struct { + Global *Global + Inputs []Input + Outputs []Output + } + type args struct { + input Input + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "first input", + fields: fields{ + Global: &Global{ + txInfo: TxInfo{ + fallBackLockTime: uint32Ptr(2), + }, + }, + }, + args: args{ + input: Input{ + requiredHeightLocktime: uint32Ptr(1), + }, + }, + wantErr: false, + }, + { + name: "provided height with existing time-lock", + fields: fields{ + Inputs: []Input{ + { + requiredTimeLocktime: uint32Ptr(10), + }, + { + requiredTimeLocktime: uint32Ptr(11), + }, + }, + }, + args: args{ + input: Input{ + requiredHeightLocktime: uint32Ptr(1), + }, + }, + wantErr: true, + }, + { + name: "provided time with existing height-lock", + fields: fields{ + Inputs: []Input{ + { + requiredHeightLocktime: uint32Ptr(10), + }, + }, + }, + args: args{ + input: Input{ + requiredTimeLocktime: uint32Ptr(1), + }, + }, + wantErr: true, + }, + { + name: "provided time-lock greater then existing", + fields: fields{ + Global: &Global{ + txInfo: TxInfo{ + fallBackLockTime: uint32Ptr(2), + }, + }, + Inputs: []Input{ + { + requiredTimeLocktime: uint32Ptr(10), + }, + }, + }, + args: args{ + input: Input{ + requiredTimeLocktime: uint32Ptr(11), + }, + }, + wantErr: false, + }, + { + name: "provided height-lock greater then existing", + fields: fields{ + Global: &Global{ + txInfo: TxInfo{ + fallBackLockTime: uint32Ptr(2), + }, + }, + Inputs: []Input{ + { + requiredHeightLocktime: uint32Ptr(10), + }, + }, + }, + args: args{ + input: Input{ + requiredHeightLocktime: uint32Ptr(11), + }, + }, + wantErr: false, + }, + { + name: "has partial signature", + fields: fields{ + Global: &Global{ + txInfo: TxInfo{ + fallBackLockTime: uint32Ptr(2), + }, + }, + Inputs: []Input{ + { + partialSigs: []PartialSig{ + { + PubKey: []byte{11}, + Signature: []byte{11}, + }, + }, + requiredHeightLocktime: uint32Ptr(10), + }, + }, + }, + args: args{ + input: Input{ + requiredHeightLocktime: uint32Ptr(11), + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Pset{ + Global: tt.fields.Global, + Inputs: tt.fields.Inputs, + Outputs: tt.fields.Outputs, + } + if err := p.validateInputTimeLock(tt.args.input); (err != nil) != tt.wantErr { + t.Errorf("validateInputTimeLock() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func uint32Ptr(x uint32) *uint32 { + return &x +} diff --git a/psetv2/signer_role.go b/psetv2/signer_role.go new file mode 100644 index 0000000..a223dff --- /dev/null +++ b/psetv2/signer_role.go @@ -0,0 +1,463 @@ +// Copyright (c) 2018 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package psetv2 + +// signer encapsulates the role 'SignerRole' as specified in BIP174; it controls +// the insertion of signatures; the Sign() function will attempt to insert +// signatures using UpdaterRole.addPartialSignature, after first ensuring the Psbt +// is in the correct state. + +import ( + "bytes" + "encoding/hex" + "errors" + "strings" + + "github.com/vulpemventures/go-elements/address" + "github.com/vulpemventures/go-elements/payment" + + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/txscript" +) + +const ( + // SignSuccesful indicates that the partial signature was successfully + // attached. + SignSuccesful = 0 + + // SignFinalized indicates that this input is already finalized, so the provided + // signature was *not* attached + SignFinalized = 1 + + // SignInvalid indicates that the provided signature data was not valid. In this case + // an error will also be returned. + SignInvalid = -1 + + // SignBlindingNotDone indicates that blinding is not done + SignBlindingNotDone = 2 + + // SignBlindingProofsInvalid indicates that blinding proofs are invalid + SignBlindingProofsInvalid = 3 +) + +type SignerRole struct { + pset *Pset + blinderSvc Blinder + updater *UpdaterRole +} + +func NewSignerRole(pset *Pset, blinderSvc Blinder) (*SignerRole, error) { + updater, err := NewUpdaterRole(pset) + if err != nil { + return nil, err + } + + return &SignerRole{ + pset: pset, + blinderSvc: blinderSvc, + updater: updater, + }, nil +} + +// SignOutcome is a enum-like value that expresses the outcome of a call to the +// Sign method. +type SignOutcome int + +// SignInput allows the caller to sign a PSBT at a particular input; they +// must provide a signature and a pubkey, both as byte slices; they can also +// optionally provide both witnessScript and/or redeemScript, otherwise these +// arguments must be set as nil (and in that case, they must already be present +// in the PSBT if required for signing to succeed). +// +// This serves as a wrapper around UpdaterRole.addPartialSignature; it ensures that +// the redeemScript and witnessScript are updated as needed (note that the +// UpdaterRole is allowed to add redeemScripts and witnessScripts independently, +// before signing), and ensures that the right form of utxo field +// (NonWitnessUtxo or WitnessUtxo) is included in the input so that signature +// insertion (and then finalization) can take place. +func (s *SignerRole) SignInput( + inIndex int, + sig []byte, + pubKey []byte, + redeemScript []byte, + witnessScript []byte, +) (SignOutcome, error) { + if isFinalized(s.pset, inIndex) { + return SignFinalized, nil + } + + if !s.pset.blinded() { + return SignBlindingNotDone, nil + } + + proofsValid, err := s.blindProofsValid() + if err != nil { + return 0, err + } + if !proofsValid { + return SignBlindingProofsInvalid, nil + } + + // Add the witnessScript to the PSBT in preparation. If it already + // exists, it will be overwritten. + if witnessScript != nil { + err := s.updater.AddInWitnessScript(witnessScript, inIndex) + if err != nil { + return SignInvalid, err + } + } + + // Add the redeemScript to the PSBT in preparation. If it already + // exists, it will be overwritten. + if redeemScript != nil { + err := s.updater.AddInRedeemScript(redeemScript, inIndex) + if err != nil { + return SignInvalid, err + } + } + + // At this point, the PSBT must have the requisite witnessScript or + // redeemScript fields for signing to succeed. + // + // Case 1: if witnessScript is present, it must be of type witness; + // if not, signature insertion will of course fail. + switch { + case s.pset.Inputs[inIndex].witnessScript != nil: + if s.pset.Inputs[inIndex].witnessUtxo == nil { + err := nonWitnessToWitness(s.pset, inIndex) + if err != nil { + return SignInvalid, err + } + } + + err := s.updater.addPartialSignature(inIndex, sig, pubKey) + if err != nil { + return SignInvalid, err + } + + // Case 2: no witness script, only redeem script; can be legacy p2sh or + // p2sh-wrapped p2wkh. + case s.pset.Inputs[inIndex].redeemScript != nil: + // We only need to decide if the input is witness, and we don't + // rely on the witnessutxo/nonwitnessutxo in the PSBT, instead + // we check the redeemScript content. + if txscript.IsWitnessProgram(redeemScript) { + if s.pset.Inputs[inIndex].witnessUtxo == nil { + err := nonWitnessToWitness(s.pset, inIndex) + if err != nil { + return SignInvalid, err + } + } + } + + // If it is not a valid witness program, we here assume that + // the provided WitnessUtxo/NonWitnessUtxo field was correct. + err := s.updater.addPartialSignature(inIndex, sig, pubKey) + if err != nil { + return SignInvalid, err + } + + // Case 3: Neither provided only works for native p2wkh, or non-segwit + // non-p2sh. To check if it's segwit, check the scriptPubKey of the + // output. + default: + if s.pset.Inputs[inIndex].witnessUtxo == nil { + outIndex := s.pset.Inputs[inIndex].previousOutputIndex + script := s.pset.Inputs[inIndex].nonWitnessUtxo.Outputs[*outIndex].Script + + if txscript.IsWitnessProgram(script) { + err := nonWitnessToWitness(s.pset, inIndex) + if err != nil { + return SignInvalid, err + } + } + } + + err := s.updater.addPartialSignature(inIndex, sig, pubKey) + if err != nil { + return SignInvalid, err + } + } + + return SignSuccesful, nil +} + +// nonWitnessToWitness extracts the TxOut from the existing NonWitnessUtxo +// field in the given PSBT input and sets it as type witness by replacing the +// NonWitnessUtxo field with a WitnessUtxo field. See +// https://github.com/bitcoin/bitcoin/pull/14197. +func nonWitnessToWitness(p *Pset, inIndex int) error { + outIndex := p.Inputs[inIndex].previousOutputIndex + txout := p.Inputs[inIndex].nonWitnessUtxo.Outputs[*outIndex] + + // Remove the non-witness first, else sanity check will not pass: + p.Inputs[inIndex].nonWitnessUtxo = nil + u := UpdaterRole{ + pset: p, + } + + return u.AddInWitnessUtxo(txout, inIndex) +} + +func (s *SignerRole) blindProofsValid() (bool, error) { + for _, v := range s.pset.Outputs { + if v.ToBlind() { + if !v.IsBlinded() { + return false, nil + } + + if v.outputAmount != nil { + valid, err := s.blinderSvc.VerifyBlindValueProof( + *v.outputAmount, + v.outputValueCommitment, + v.outputBlindValueProof, + v.outputAssetCommitment, + ) + if err != nil { + return false, err + } + + if !valid { + return false, nil + } + } + + if v.outputAsset != nil { + valid, err := s.blinderSvc.VerifyBlindAssetProof( + v.outputAsset[1:], + v.outputBlindAssetProof, + v.outputAssetCommitment, + ) + if err != nil { + return false, err + } + + if !valid { + return false, nil + } + } + } + } + return true, nil +} + +func (s *SignerRole) ValidateAllSignatures() (bool, error) { + for i := range s.pset.Inputs { + valid, err := s.ValidateInputSignatures(i) + if err != nil { + return false, err + } + if !valid { + return false, nil + } + } + return true, nil +} + +func (s *SignerRole) ValidateInputSignatures(inputIndex int) ( + bool, + error, +) { + if len(s.pset.Inputs[inputIndex].partialSigs) > 0 { + for _, v := range s.pset.Inputs[inputIndex].partialSigs { + valid, err := s.validatePartialSignature(inputIndex, v) + if err != nil { + return false, err + } + if !valid { + return false, nil + } + } + return true, nil + } + return false, nil +} + +func (s *SignerRole) validatePartialSignature( + inputIndex int, + partialSignature PartialSig, +) (bool, error) { + if partialSignature.PubKey == nil { + return false, errors.New("no pub key for partial signature") + } + + signatureLen := len(partialSignature.Signature) + sigHashType := partialSignature.Signature[signatureLen-1] + signatureDer := partialSignature.Signature[:signatureLen-1] + + sigHash, script, err := s.getHashAndScriptForSignature( + inputIndex, + uint32(sigHashType), + ) + if err != nil { + return false, err + } + + valid, err := s.verifyScriptForPubKey(script, partialSignature.PubKey) + if err != nil { + return false, err + } + if !valid { + return false, nil + } + + pSig, err := btcec.ParseDERSignature(signatureDer, btcec.S256()) + if err != nil { + return false, nil + } + + pubKey, err := btcec.ParsePubKey(partialSignature.PubKey, btcec.S256()) + if err != nil { + return false, nil + } + + return pSig.Verify(sigHash[:], pubKey), nil +} + +func (s *SignerRole) getHashAndScriptForSignature(inputIndex int, sigHashType uint32) ( + []byte, + []byte, + error, +) { + var hash [32]byte + var script []byte + + tx, err := s.pset.UnsignedTx(s.blinderSvc) + if err != nil { + return nil, nil, err + } + + input := s.pset.Inputs[inputIndex] + if input.nonWitnessUtxo != nil { + prevoutHash := input.previousTxid + utxoHash := input.nonWitnessUtxo.TxHash() + + if bytes.Compare(prevoutHash, utxoHash.CloneBytes()) == 1 { + return nil, nil, + errors.New("non-witness utxo hash for input doesnt match the " + + "hash specified in the prevout") + } + + prevoutIndex := input.previousOutputIndex + prevout := input.nonWitnessUtxo.Outputs[*prevoutIndex] + if input.redeemScript != nil { + script = input.redeemScript + } else { + script = prevout.Script + } + + switch address.GetScriptType(script) { + + case address.P2WshScript: + if input.witnessScript == nil { + return nil, nil, + errors.New("segwit input needs witnessScript if not p2wpkh") + } + + hash = tx.HashForWitnessV0( + inputIndex, + input.witnessScript, + prevout.Value, + txscript.SigHashType(sigHashType), + ) + script = input.witnessScript + + case address.P2WpkhScript: + pay, err := payment.FromScript( + script, + nil, + nil, + ) + if err != nil { + return nil, nil, err + } + hash = tx.HashForWitnessV0( + inputIndex, + pay.Script, + input.witnessUtxo.Value, + txscript.SigHashType(sigHashType), + ) + default: + var err error + hash, err = tx.HashForSignature( + inputIndex, + script, + txscript.SigHashType(sigHashType), + ) + if err != nil { + return nil, nil, err + } + } + } else if input.witnessUtxo != nil { + if input.redeemScript != nil { + script = input.redeemScript + } else { + script = input.witnessUtxo.Script + } + switch address.GetScriptType(script) { + + case address.P2WpkhScript: + pay, err := payment.FromScript( + script, + nil, + nil, + ) + if err != nil { + return nil, nil, err + } + hash = tx.HashForWitnessV0( + inputIndex, + pay.Script, + input.witnessUtxo.Value, + txscript.SigHashType(sigHashType), + ) + case address.P2WshScript: + hash = tx.HashForWitnessV0( + inputIndex, + input.witnessScript, + input.witnessUtxo.Value, + txscript.SigHashType(sigHashType), + ) + script = input.witnessScript + default: + return nil, nil, errors.New("inputhas witnessUtxo but non-segwit script") + } + + } else { + return nil, nil, errors.New("need a utxo input item for signing") + } + + return hash[:], script, nil +} + +func (s *SignerRole) verifyScriptForPubKey( + script []byte, + pubKey []byte, +) (bool, error) { + + pk, err := btcec.ParsePubKey(pubKey, btcec.S256()) + if err != nil { + return false, err + } + + pkHash := payment.Hash160(pubKey) + + scriptAsm, err := txscript.DisasmString(script) + if err != nil { + return false, err + } + + if strings.Contains( + scriptAsm, + hex.EncodeToString(pk.SerializeCompressed()), + ) || strings.Contains( + scriptAsm, + hex.EncodeToString(pkHash), + ) { + return true, nil + } + + return false, nil +} diff --git a/psetv2/testdata/deserialize.json b/psetv2/testdata/deserialize.json new file mode 100644 index 0000000..c5915b8 --- /dev/null +++ b/psetv2/testdata/deserialize.json @@ -0,0 +1,6 @@ +[ + { + "name": "test", + "base64": "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" + } +] \ No newline at end of file diff --git a/psetv2/updater_role.go b/psetv2/updater_role.go new file mode 100644 index 0000000..ba7adec --- /dev/null +++ b/psetv2/updater_role.go @@ -0,0 +1,687 @@ +package psetv2 + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "errors" + + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcutil" + + "github.com/vulpemventures/go-elements/address" + "github.com/vulpemventures/go-elements/elementsutil" + "github.com/vulpemventures/go-elements/transaction" +) + +// Copyright (c) 2018 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +const ( + NonConfidentialReissuanceTokenFlag = 0 + ConfidentialReissuanceTokenFlag = 1 +) + +var ( + ErrInvalidPrevOutNonWitnessTransaction = errors.New("prevout hash does " + + "not match the provided non-witness utxo serialization") + ErrDuplicateKey = errors.New("invalid psbt due to duplicate key") +) + +// UpdaterRole encapsulates the role 'UpdaterRole' as specified in BIP174; it accepts +// Psbt structs and has methods to add fields to the inputs and outputs. +type UpdaterRole struct { + pset *Pset +} + +// NewUpdaterRole returns a new instance of UpdaterRole, if the passed Psbt struct is +// in a valid form, else an error.a +func NewUpdaterRole(p *Pset) (*UpdaterRole, error) { + + return &UpdaterRole{ + pset: p, + }, nil +} + +// AddInNonWitnessUtxo adds the utxo information for an input which is +// non-witness. This requires provision of a full transaction (which is the +// source of the corresponding prevOut), and the input index. If addition of +// this key-value pair to the Psbt fails, an error is returned. +func (u *UpdaterRole) AddInNonWitnessUtxo(inIndex int, tx *transaction.Transaction) error { + if inIndex > len(u.pset.Inputs)-1 { + return ErrInvalidPrevOutNonWitnessTransaction + } + u.pset.Inputs[inIndex].nonWitnessUtxo = tx + + return nil +} + +// AddInWitnessUtxo adds the utxo information for an input which is witness. +// This requires provision of a full transaction *output* (which is the source +// of the corresponding prevOut); not the full transaction because BIP143 means +// the output information is sufficient, and the input index. If addition of +// this key-value pair to the Psbt fails, an error is returned. +func (u *UpdaterRole) AddInWitnessUtxo(txout *transaction.TxOutput, inIndex int) error { + if inIndex > len(u.pset.Inputs)-1 { + return ErrInvalidPsbtFormat + } + u.pset.Inputs[inIndex].witnessUtxo = txout + + return nil +} + +// AddInRedeemScript adds the redeem script information for an input. The +// redeem script is passed serialized, as a byte slice, along with the index of +// the input. An error is returned if addition of this key-value pair to the +// Psbt fails. +func (u *UpdaterRole) AddInRedeemScript(redeemScript []byte, inIndex int) error { + u.pset.Inputs[inIndex].redeemScript = redeemScript + + return nil +} + +// AddInWitnessScript adds the witness script information for an input. The +// witness script is passed serialized, as a byte slice, along with the index +// of the input. An error is returned if addition of this key-value pair to the +// Psbt fails. +func (u *UpdaterRole) AddInWitnessScript(witnessScript []byte, inIndex int) error { + u.pset.Inputs[inIndex].witnessScript = witnessScript + + return nil +} + +// AddInBip32Derivation takes a master key fingerprint as defined in BIP32, a +// BIP32 path as a slice of uint32 values, and a serialized pubkey as a byte +// slice, along with the integer index of the input, and inserts this data into +// that input. +// +// NOTE: This can be called multiple times for the same input. An error is +// returned if addition of this key-value pair to the Psbt fails. +func (u *UpdaterRole) AddInBip32Derivation(masterKeyFingerprint uint32, + bip32Path []uint32, pubKeyData []byte, inIndex int) error { + + bip32Derivation := DerivationPathWithPubKey{ + PubKey: pubKeyData, + MasterKeyFingerprint: masterKeyFingerprint, + Bip32Path: bip32Path, + } + + if !validatePubkey(bip32Derivation.PubKey) { + return ErrInvalidPsbtFormat + } + + // Don't allow duplicate keys + for _, x := range u.pset.Inputs[inIndex].bip32Derivation { + if bytes.Equal(x.PubKey, bip32Derivation.PubKey) { + return ErrDuplicateKey + } + } + + u.pset.Inputs[inIndex].bip32Derivation = append( + u.pset.Inputs[inIndex].bip32Derivation, bip32Derivation, + ) + + return nil +} + +// AddOutBip32Derivation takes a master key fingerprint as defined in BIP32, a +// BIP32 path as a slice of uint32 values, and a serialized pubkey as a byte +// slice, along with the integer index of the output, and inserts this data +// into that output. +// +// NOTE: That this can be called multiple times for the same output. An error +// is returned if addition of this key-value pair to the Psbt fails. +func (u *UpdaterRole) AddOutBip32Derivation(masterKeyFingerprint uint32, + bip32Path []uint32, pubKeyData []byte, outIndex int) error { + + bip32Derivation := DerivationPathWithPubKey{ + PubKey: pubKeyData, + MasterKeyFingerprint: masterKeyFingerprint, + Bip32Path: bip32Path, + } + + if !validatePubkey(bip32Derivation.PubKey) { + return ErrInvalidPsbtFormat + } + + // Don't allow duplicate keys + for _, x := range u.pset.Outputs[outIndex].bip32Derivation { + if bytes.Equal(x.PubKey, bip32Derivation.PubKey) { + return ErrDuplicateKey + } + } + + u.pset.Outputs[outIndex].bip32Derivation = append( + u.pset.Outputs[outIndex].bip32Derivation, bip32Derivation, + ) + + return nil +} + +// AddOutRedeemScript takes a redeem script as a byte slice and appends it to +// the output at index outIndex. +func (u *UpdaterRole) AddOutRedeemScript(redeemScript []byte, outIndex int) error { + u.pset.Outputs[outIndex].redeemScript = redeemScript + + return nil +} + +// AddOutWitnessScript takes a witness script as a byte slice and appends it to +// the output at index outIndex. +func (u *UpdaterRole) AddOutWitnessScript(witnessScript []byte, outIndex int) error { + u.pset.Outputs[outIndex].witnessScript = witnessScript + + return nil +} + +// AddInput adds input to the pset +func (u *UpdaterRole) AddInput(inputArg InputArg) error { + return u.pset.addInput(inputArg) +} + +// AddOutput adds output to the pset +func (u *UpdaterRole) AddOutput(outputArg OutputArg) error { + return u.pset.addOutput(outputArg) +} + +// AddIssuanceArgs is a struct encapsulating all the issuance data that +// can be attached to any specific transaction of the PSBT. +type AddIssuanceArgs struct { + Precision uint + Contract *transaction.IssuanceContract + AssetAmount uint64 + TokenAmount uint64 + AssetAddress string + TokenAddress string +} + +func (arg AddIssuanceArgs) validate() error { + if _, err := transaction.NewTxIssuance( + arg.AssetAmount, + arg.TokenAmount, + arg.Precision, + arg.Contract, + ); err != nil { + return err + } + + if len(arg.AssetAddress) <= 0 { + return errors.New("missing destination address for asset to issue") + } + + if _, err := address.DecodeType(arg.AssetAddress); err != nil { + return err + } + + if arg.TokenAmount > 0 { + if len(arg.TokenAddress) <= 0 { + return errors.New("missing destination address for token to issue") + } + if _, err := address.DecodeType(arg.TokenAddress); err != nil { + return err + } + } + if !arg.matchAddressTypes() { + return errors.New( + "asset and token destination addresses must both be confidential or " + + "non-confidential", + ) + } + + return nil +} + +func (arg AddIssuanceArgs) matchAddressTypes() bool { + if len(arg.TokenAddress) <= 0 { + return true + } + + a, _ := address.IsConfidential(arg.AssetAddress) + b, _ := address.IsConfidential(arg.TokenAddress) + // xnor -> return true only if a and b have the same value + return !((a || b) && (!a || !b)) +} + +func (arg AddIssuanceArgs) tokenFlag() uint { + if isConfidential, _ := address.IsConfidential( + arg.AssetAddress, + ); isConfidential { + return uint(ConfidentialReissuanceTokenFlag) + } + return uint(NonConfidentialReissuanceTokenFlag) +} + +// AddIssuance adds an unblinded issuance to the transaction +func (u *UpdaterRole) AddIssuance(arg AddIssuanceArgs) error { + if err := arg.validate(); err != nil { + return err + } + + if len(u.pset.Inputs) == 0 { + return errors.New("transaction must contain at least one input") + } + + issuance, _ := transaction.NewTxIssuance( + arg.AssetAmount, + arg.TokenAmount, + arg.Precision, + arg.Contract, + ) + + prevoutIndex, prevoutHash, inputIndex := findInputWithEmptyIssuance(u.pset) + if inputIndex < 0 { + return errors.New( + "transaction does not contain any input with empty issuance", + ) + } + + if err := issuance.GenerateEntropy( + prevoutHash[:], + prevoutIndex, + ); err != nil { + return err + } + + amount, err := elementsutil.ElementsToSatoshiValue(issuance.TxIssuance.AssetAmount) + if err != nil { + return err + } + issuanceValue := int64(amount) + + amount, err = elementsutil.ElementsToSatoshiValue(issuance.TxIssuance.TokenAmount) + if err != nil { + return err + } + tokenValue := int64(amount) + + u.pset.Inputs[inputIndex].issuanceAssetEntropy = issuance.ContractHash + u.pset.Inputs[inputIndex].issuanceValue = &issuanceValue + u.pset.Inputs[inputIndex].issuanceInflationKeys = &tokenValue + u.pset.Inputs[inputIndex].issuanceBlindingNonce = issuance.TxIssuance.AssetBlindingNonce + + assetHash, err := issuance.GenerateAsset() + if err != nil { + return err + } + // prepend with a 0x01 prefix + assetHash = append([]byte{0x01}, assetHash...) + + script, err := address.ToOutputScript(arg.AssetAddress) + if err != nil { + return err + } + + output := transaction.NewTxOutput( + assetHash, + issuance.TxIssuance.AssetAmount, + script, + ) + if err := u.AddOutput(OutputArg{TxOutput: *output}); err != nil { + return err + } + + if arg.TokenAmount > 0 { + tokenHash, err := issuance.GenerateReissuanceToken(arg.tokenFlag()) + if err != nil { + return err + } + tokenHash = append([]byte{byte(1)}, tokenHash...) + script, err := address.ToOutputScript(arg.TokenAddress) + if err != nil { + return err + } + + output := transaction.NewTxOutput( + tokenHash, + issuance.TxIssuance.TokenAmount, + script, + ) + if err := u.AddOutput(OutputArg{TxOutput: *output}); err != nil { + return err + } + } + + return nil +} + +// AddReissuanceArgs defines the mandatory fields that one needs to pass to +// the AddReissuance method of the *UpdaterRole type +// PrevOutHash: the prevout hash of the token that will be added as input to the tx +// PrevOutIndex: the prevout index of the token that will be added as input to the tx +// PrevOutBlinder: the asset blinder used to blind the prevout token +// WitnessUtxo: the prevout token in case it is a witness one +// NonWitnessUtxo: the prevout tx that include the token output in case it is a non witness one +// Entropy: the entropy used to generate token and asset +// AssetAmount: the amount of asset to re-issue +// TokenAmount: the same unblinded amount of the prevout token +// AssetAddress: the destination address of the re-issuing asset +// TokenAddress: the destination address of the re-issuance token +type AddReissuanceArgs struct { + PrevOutHash string + PrevOutIndex uint32 + PrevOutBlinder []byte + WitnessUtxo *transaction.TxOutput + NonWitnessUtxo *transaction.Transaction + Entropy string + AssetAmount uint64 + AssetAddress string + TokenAmount uint64 + TokenAddress string +} + +func (arg AddReissuanceArgs) validate() error { + if arg.WitnessUtxo == nil && arg.NonWitnessUtxo == nil { + return errors.New("either WitnessUtxo or NonWitnessUtxo must be defined") + } + + if buf, err := hex.DecodeString(arg.PrevOutHash); err != nil || len(buf) != 32 { + return errors.New("invalid input hash") + } + + if arg.NonWitnessUtxo != nil { + hash := arg.NonWitnessUtxo.TxHash() + if hex.EncodeToString(elementsutil.ReverseBytes(hash[:])) != arg.PrevOutHash { + return errors.New("input and non witness utxo hashes must match") + } + } + + // it's mandatory for the token prevout to be confidential. This because the + // prevout value blinder will be used as the reissuance's blinding nonce to + // prove that the spender actually owns and can unblind the token output. + if !arg.isPrevoutConfidential() { + return errors.New( + "token prevout must be confidential. You must blind your token by " + + "sending it to yourself in a confidential transaction if you want " + + "be able to reissue the relative asset", + ) + } + + if len(arg.PrevOutBlinder) != 32 { + return errors.New("invalid input blinder") + } + + if buf, err := hex.DecodeString(arg.Entropy); err != nil || len(buf) != 32 { + return errors.New("invalid asset entropy") + } + + if arg.AssetAmount <= 0 { + return errors.New("invalid asset amount") + } + + if arg.TokenAmount <= 0 { + return errors.New("invalid token amount") + } + + if len(arg.AssetAddress) <= 0 { + return errors.New("invalid destination address for asset") + } + if _, err := address.DecodeType(arg.AssetAddress); err != nil { + return err + } + if len(arg.TokenAddress) <= 0 { + return errors.New("invalid destination address for token") + } + if _, err := address.DecodeType(arg.TokenAddress); err != nil { + return err + } + if !arg.areAddressesConfidential() { + return errors.New("asset and token address must be both confidential") + } + + return nil +} + +func (arg AddReissuanceArgs) isPrevoutConfidential() bool { + if arg.WitnessUtxo != nil { + return arg.WitnessUtxo.IsConfidential() + } + return arg.NonWitnessUtxo.Outputs[arg.PrevOutIndex].IsConfidential() +} + +func (arg AddReissuanceArgs) areAddressesConfidential() bool { + a, _ := address.IsConfidential(arg.AssetAddress) + b, _ := address.IsConfidential(arg.TokenAddress) + return a && b +} + +// AddReissuance takes care of adding an input (the prevout token) and 2 +// outputs to the partial transaction. It also creates a new (re)issuance with +// the provided entropy, blinder and amounts and attaches it to the new input. +// NOTE: This transaction must be blinded later so that a new token blinding +// nonce is generated for the new token output +func (u *UpdaterRole) AddReissuance(arg AddReissuanceArgs) error { + if err := arg.validate(); err != nil { + return err + } + + if len(u.pset.Inputs) == 0 { + return errors.New( + "transaction must contain at least one input before adding a reissuance", + ) + } + + prevoutHash, _ := hex.DecodeString(arg.PrevOutHash) + prevoutHash = elementsutil.ReverseBytes(prevoutHash) + prevoutIndex := arg.PrevOutIndex + + // add input + tokenInput := transaction.NewTxInput(prevoutHash, prevoutIndex) + if err := u.AddInput(InputArg{TxInput: *tokenInput}); err != nil { + return err + } + inputIndex := len(u.pset.Inputs) - 1 + if arg.WitnessUtxo != nil { + if err := u.AddInWitnessUtxo(arg.WitnessUtxo, inputIndex); err != nil { + return err + } + } else { + if err := u.AddInNonWitnessUtxo(inputIndex, arg.NonWitnessUtxo); err != nil { + return err + } + } + + entropy, _ := hex.DecodeString(arg.Entropy) + entropy = elementsutil.ReverseBytes(entropy) + issuance := transaction.NewTxIssuanceFromEntropy(entropy) + + assetHash, _ := issuance.GenerateAsset() + assetHash = append([]byte{0x01}, assetHash...) + assetScript, _ := address.ToOutputScript(arg.AssetAddress) + assetAmount, _ := elementsutil.SatoshiToElementsValue(arg.AssetAmount) + + tokenHash, _ := issuance.GenerateReissuanceToken( + ConfidentialReissuanceTokenFlag, + ) + tokenHash = append([]byte{0x01}, tokenHash...) + tokenScript, _ := address.ToOutputScript(arg.TokenAddress) + tokenAmount, _ := elementsutil.SatoshiToElementsValue(arg.TokenAmount) + + // add outputs + reissuanceOutput := transaction.NewTxOutput( + assetHash, + assetAmount, + assetScript, + ) + if err := u.AddOutput(OutputArg{TxOutput: *reissuanceOutput}); err != nil { + return err + } + + // and the token output + tokenOutput := transaction.NewTxOutput( + tokenHash, + tokenAmount, + tokenScript, + ) + if err := u.AddOutput(OutputArg{TxOutput: *tokenOutput}); err != nil { + return err + } + + // add the (re)issuance to the token input. The token amount of the issuance + // must not be defined for reissunces. + issuanceValue := int64(arg.AssetAmount) + var issuanceInflationKeys int64 = 0 + + u.pset.Inputs[inputIndex].issuanceAssetEntropy = issuance.ContractHash + u.pset.Inputs[inputIndex].issuanceValue = &issuanceValue + u.pset.Inputs[inputIndex].issuanceInflationKeys = &issuanceInflationKeys + u.pset.Inputs[inputIndex].issuanceBlindingNonce = arg.PrevOutBlinder + + return nil +} + +func findInputWithEmptyIssuance(p *Pset) (uint32, []byte, int) { + for i, in := range p.Inputs { + if in.issuanceValue == nil { + return *in.previousOutputIndex, in.previousTxid[:], i + } + } + return 0, nil, -1 +} + +// addPartialSignature allows the UpdaterRole role to insert fields of type partial +// signature into a Pset, consisting of both the pubkey (as keydata) and the +// ECDSA signature (as value). Note that the SignerRole role is encapsulated in +// this function; signatures are only allowed to be added that follow the +// sanity-check on signing rules explained in the BIP under `SignerRole`; if the +// rules are not satisfied, an ErrInvalidSignatureForInput is returned. +// +// NOTE: This function does *not* validate the ECDSA signature itself. +func (u *UpdaterRole) addPartialSignature(inIndex int, sig []byte, + pubkey []byte) error { + + partialSig := PartialSig{ + PubKey: pubkey, Signature: sig, + } + + // First validate the passed (sig, pub). + if !partialSig.checkValid() { + return ErrInvalidPsbtFormat + } + + input := u.pset.Inputs[inIndex] + + // First check; don't add duplicates. + for _, x := range input.partialSigs { + if bytes.Equal(x.PubKey, partialSig.PubKey) { + return ErrDuplicateKey + } + } + + // Next, we perform a series of additional sanity checks. + if input.nonWitnessUtxo != nil { + if txHash := input.nonWitnessUtxo.TxHash(); !bytes.Equal(txHash[:], u.pset.Inputs[inIndex].previousTxid) { + return ErrInvalidSignatureForInput + } + + // To validate that the redeem script matches, we must pull out + // the scriptPubKey of the corresponding output and compare + // that with the P2SH scriptPubKey that is generated by + // redeemScript. + if input.redeemScript != nil { + outIndex := u.pset.Inputs[inIndex].previousOutputIndex + scriptPubKey := input.nonWitnessUtxo.Outputs[*outIndex].Script + scriptHash := btcutil.Hash160(input.redeemScript) + + scriptHashScript, err := txscript.NewScriptBuilder(). + AddOp(txscript.OP_HASH160). + AddData(scriptHash). + AddOp(txscript.OP_EQUAL). + Script() + if err != nil { + return err + } + + if !bytes.Equal(scriptHashScript, scriptPubKey) { + return ErrInvalidSignatureForInput + } + } + + } else if input.witnessUtxo != nil { + scriptPubKey := input.witnessUtxo.Script + + var script []byte + if input.redeemScript != nil { + scriptHash := btcutil.Hash160(input.redeemScript) + scriptHashScript, err := txscript.NewScriptBuilder(). + AddOp(txscript.OP_HASH160). + AddData(scriptHash). + AddOp(txscript.OP_EQUAL). + Script() + if err != nil { + return err + } + + if !bytes.Equal(scriptHashScript, scriptPubKey) { + return ErrInvalidSignatureForInput + } + + script = input.redeemScript + } else { + script = scriptPubKey + } + + // If a witnessScript field is present, this is a P2WSH, + // whether nested or not (that is handled by the assignment to + // `script` above); in that case, sanity check that `script` is + // the p2wsh of witnessScript. Contrariwise, if no + // witnessScript field is present, this will be signed as + // p2wkh. + if input.witnessScript != nil { + witnessScriptHash := sha256.Sum256(input.witnessScript) + witnessScriptHashScript, err := txscript.NewScriptBuilder(). + AddOp(txscript.OP_0). + AddData(witnessScriptHash[:]). + Script() + if err != nil { + return err + } + + if !bytes.Equal(script, witnessScriptHashScript[:]) { + return ErrInvalidSignatureForInput + } + } else { + // Otherwise, this is a p2wkh input. + pubkeyHash := btcutil.Hash160(pubkey) + pubkeyHashScript, err := txscript.NewScriptBuilder(). + AddOp(txscript.OP_0). + AddData(pubkeyHash). + Script() + if err != nil { + return err + } + + // Validate that we're able to properly reconstruct the + // witness program. + if !bytes.Equal(pubkeyHashScript, script) { + return ErrInvalidSignatureForInput + } + } + } else { + + // Attaching signature without utxo field is not allowed. + return ErrInvalidPsbtFormat + } + + u.pset.Inputs[inIndex].partialSigs = append( + u.pset.Inputs[inIndex].partialSigs, partialSig, + ) + + if err := u.pset.SanityCheck(); err != nil { + return err + } + + // Addition of a non-duplicate-key partial signature cannot violate + // sanity-check rules. + return nil +} + +// AddInSighashType adds the sighash type information for an input. The +// sighash type is passed as a 32 bit unsigned integer, along with the index +// for the input. . +func (u *UpdaterRole) AddInSighashType( + sighashType txscript.SigHashType, + inIndex int, +) { + u.pset.Inputs[inIndex].sigHashType = &sighashType +} diff --git a/psetv2/utils.go b/psetv2/utils.go new file mode 100644 index 0000000..bd6b01e --- /dev/null +++ b/psetv2/utils.go @@ -0,0 +1,256 @@ +package psetv2 + +import ( + "bytes" + "crypto/rand" + "errors" + "sort" + + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcutil/psbt" + + "github.com/vulpemventures/go-elements/internal/bufferutil" + "github.com/vulpemventures/go-elements/transaction" +) + +func writeTxOut(txout *transaction.TxOutput) ([]byte, error) { + s, err := bufferutil.NewSerializer(nil) + if err != nil { + return nil, err + } + if err := s.WriteSlice(txout.Asset); err != nil { + return nil, err + } + if err := s.WriteSlice(txout.Value); err != nil { + return nil, err + } + if err := s.WriteSlice(txout.Nonce); err != nil { + return nil, err + } + if err := s.WriteVarSlice(txout.Script); err != nil { + return nil, err + } + if txout.IsConfidential() { + if err := s.WriteVarSlice(txout.SurjectionProof); err != nil { + return nil, err + } + if err := s.WriteVarSlice(txout.RangeProof); err != nil { + return nil, err + } + } + return s.Bytes(), nil +} + +func readTxOut(txout []byte) (*transaction.TxOutput, error) { + if len(txout) < 45 { + return nil, ErrInvalidPsbtFormat + } + d := bufferutil.NewDeserializer(bytes.NewBuffer(txout)) + asset, err := d.ReadElementsAsset() + if err != nil { + return nil, err + } + value, err := d.ReadElementsValue() + if err != nil { + return nil, err + } + nonce, err := d.ReadElementsNonce() + if err != nil { + return nil, err + } + script, err := d.ReadVarSlice() + if err != nil { + return nil, err + } + surjectionProof := make([]byte, 0) + rangeProof := make([]byte, 0) + // nonce for unconf outputs is 0x00! + if len(nonce) > 1 { + surjectionProof, err = d.ReadVarSlice() + if err != nil { + return nil, err + } + rangeProof, err = d.ReadVarSlice() + if err != nil { + return nil, err + } + } + return &transaction.TxOutput{ + Asset: asset, + Value: value, + Script: script, + Nonce: nonce, + RangeProof: rangeProof, + SurjectionProof: surjectionProof, + }, nil +} + +func isAssetExplicit(asset []byte) bool { + return len(asset) == 33 && asset[0] == 1 +} + +func generateRandomNumber() ([]byte, error) { + b := make([]byte, 32) + _, err := rand.Read(b) + if err != nil { + return nil, err + } + return b, nil +} + +// extractKeyOrderFromScript is a utility function to extract an ordered list +// of signatures, given a serialized script (redeemscript or witness script), a +// list of pubkeys and the signatures corresponding to those pubkeys. This +// function is used to ensure that the signatures will be embedded in the final +// scriptSig or scriptWitness in the correct order. +func extractKeyOrderFromScript(script []byte, expectedPubkeys [][]byte, + sigs [][]byte) ([][]byte, error) { + + // If this isn't a proper finalized multi-sig script, then we can't + // proceed. + if !checkIsMultiSigScript(expectedPubkeys, sigs, script) { + return nil, psbt.ErrUnsupportedScriptType + } + + // Arrange the pubkeys and sigs into a slice of format: + // * [[pub,sig], [pub,sig],..] + type sigWithPub struct { + pubKey []byte + sig []byte + } + var pubsSigs []sigWithPub + for i, pub := range expectedPubkeys { + pubsSigs = append(pubsSigs, sigWithPub{ + pubKey: pub, + sig: sigs[i], + }) + } + + // Now that we have the set of (pubkey, sig) pairs, we'll construct a + // position map that we can use to swap the order in the slice above to + // match how things are laid out in the script. + type positionEntry struct { + index int + value sigWithPub + } + var positionMap []positionEntry + + // For each pubkey in our pubsSigs slice, we'll now construct a proper + // positionMap entry, based on _where_ in the script the pubkey first + // appears. + for _, p := range pubsSigs { + pos := bytes.Index(script, p.pubKey) + if pos < 0 { + return nil, errors.New("script does not contain pubkeys") + } + + positionMap = append(positionMap, positionEntry{ + index: pos, + value: p, + }) + } + + // Now that we have the position map full populated, we'll use the + // index data to properly sort the entries in the map based on where + // they appear in the script. + sort.Slice(positionMap, func(i, j int) bool { + return positionMap[i].index < positionMap[j].index + }) + + // Finally, we can simply iterate through the position map in order to + // extract the proper signature ordering. + sortedSigs := make([][]byte, 0, len(positionMap)) + for _, x := range positionMap { + sortedSigs = append(sortedSigs, x.value.sig) + } + + return sortedSigs, nil +} + +// checkIsMultisigScript is a utility function to check whether a given +// redeemscript fits the standard multisig template used in all P2SH based +// multisig, given a set of pubkeys for redemption. +func checkIsMultiSigScript(pubKeys [][]byte, sigs [][]byte, + script []byte) bool { + + // First insist that the script type is multisig. + if txscript.GetScriptClass(script) != txscript.MultiSigTy { + return false + } + + // Inspect the script to ensure that the number of sigs and pubkeys is + // correct + _, numSigs, err := txscript.CalcMultiSigStats(script) + if err != nil { + return false + } + + // If the number of sigs provided, doesn't match the number of required + // pubkeys, then we can't proceed as we're not yet final. + if numSigs != len(pubKeys) || numSigs != len(sigs) { + return false + } + + return true +} + +// writePKHWitness writes a witness for a p2wkh spending input +func writePKHWitness(sig []byte, pub []byte) ([]byte, error) { + witnessItems := [][]byte{sig, pub} + + return writeTxWitness(witnessItems) +} + +// writeTxWitness is a A utility function due to non-exported witness +// serialization (writeTxWitness encodes the bitcoin protocol encoding for a +// transaction input's witness into w). +func writeTxWitness(wit [][]byte) ([]byte, error) { + s, err := bufferutil.NewSerializer(nil) + if err != nil { + return nil, err + } + if err := s.WriteVarInt(uint64(len(wit))); err != nil { + return nil, err + } + + for _, item := range wit { + err := s.WriteVarSlice(item) + if err != nil { + return nil, err + } + } + return s.Bytes(), nil +} + +// getMultisigScriptWitness creates a full psbt serialized Witness field for +// the transaction, given the public keys and signatures to be appended. This +// function will only accept witnessScripts of the type M of N multisig. This +// is used for both p2wsh and nested p2wsh multisig cases. +func getMultisigScriptWitness(witnessScript []byte, pubKeys [][]byte, + sigs [][]byte) ([]byte, error) { + + // First using the script as a guide, we'll properly order the sigs + // according to how their corresponding pubkeys appear in the + // witnessScript. + orderedSigs, err := extractKeyOrderFromScript( + witnessScript, pubKeys, sigs, + ) + if err != nil { + return nil, err + } + + // Now that we know the proper order, we'll append each of the + // signatures into a new witness stack, then top it off with the + // witness script at the end, prepending the nil as we need the extra + // pop.. + witnessElements := make(transaction.TxWitness, 0, len(sigs)+2) + witnessElements = append(witnessElements, nil) + for _, os := range orderedSigs { + witnessElements = append(witnessElements, os) + } + witnessElements = append(witnessElements, witnessScript) + + // Now that we have the full witness stack, we'll serialize it in the + // expected format, and return the final bytes. + return writeTxWitness(witnessElements) +} diff --git a/transaction/transaction.go b/transaction/transaction.go index 8b27f98..cfc7ab0 100644 --- a/transaction/transaction.go +++ b/transaction/transaction.go @@ -20,6 +20,9 @@ const ( advancedTransactionFlag = uint8(0x01) advancedTransactionMarker = uint8(0x00) defaultTxInOutAlloc = 15 + + //SighashRangeproof is a flag that means the rangeproofs should be included in the sighash. + SighashRangeproof = 0x40 ) var ( @@ -352,7 +355,7 @@ func (tx *Transaction) HasWitness() bool { // TxHash generates the Hash for the transaction. func (tx *Transaction) TxHash() chainhash.Hash { // Encode the transaction and calculate double sha256 on the result. - buf, _ := tx.serialize(nil, false, true, false) + buf, _ := tx.serialize(nil, false, true, false, false) return chainhash.DoubleHashH(buf) } @@ -363,7 +366,7 @@ func (tx *Transaction) TxHash() chainhash.Hash { // is the same as its txid. func (tx *Transaction) WitnessHash() chainhash.Hash { if tx.HasWitness() { - buf, _ := tx.serialize(nil, true, true, false) + buf, _ := tx.serialize(nil, true, true, false, false) return chainhash.DoubleHashH(buf) } return tx.TxHash() @@ -497,7 +500,7 @@ func (tx *Transaction) SerializeSize(allowWitness, forSignature bool) int { // Serialize returns the serialization of the transaction. func (tx *Transaction) Serialize() ([]byte, error) { - return tx.serialize(nil, true, false, false) + return tx.serialize(nil, true, false, false, false) } // HashForSignature returns the double sha256 hash of the serialization @@ -507,7 +510,8 @@ func (tx *Transaction) Serialize() ([]byte, error) { func (tx *Transaction) HashForSignature( inIndex int, prevoutScript []byte, - hashType txscript.SigHashType) ([32]byte, error) { + hashType txscript.SigHashType, +) ([32]byte, error) { if inIndex >= len(tx.Inputs) { return One, nil } @@ -573,7 +577,8 @@ func (tx *Transaction) HashForSignature( } } - buf, err := txCopy.serialize(nil, false, true, true) + shouldCalculateRangeProofsHash := (hashType & SighashRangeproof) != 0 + buf, err := txCopy.serialize(nil, false, true, true, shouldCalculateRangeProofsHash) if err != nil { return [32]byte{}, err } @@ -585,10 +590,13 @@ func (tx *Transaction) HashForSignature( // of the transaction following the BIP-0143 specification. This hash should // then be used to produce a witness signatures for the given inIndex input. func (tx *Transaction) HashForWitnessV0(inIndex int, prevoutScript []byte, value []byte, hashType txscript.SigHashType) [32]byte { + shouldCalculateRangeProofsHash := (hashType & SighashRangeproof) != 0 + hashInputs := Zero hashSequences := Zero hashIssuances := Zero hashOutputs := Zero + hashForRangeProofs := Zero // Inputs if (hashType & txscript.SigHashAnyOneCanPay) == 0 { @@ -608,9 +616,15 @@ func (tx *Transaction) HashForWitnessV0(inIndex int, prevoutScript []byte, value if (hashType&0x1f) != txscript.SigHashSingle && (hashType&0x1f) != txscript.SigHashNone { hashOutputs = calcTxOutputsHash(tx.Outputs) + if shouldCalculateRangeProofsHash { + hashForRangeProofs = calcRangeProofsHash(tx.Outputs) + } } else { if (hashType&0x1f) == txscript.SigHashSingle && inIndex < len(tx.Outputs) { hashOutputs = calcTxOutputsHash([]*TxOutput{tx.Outputs[inIndex]}) + if shouldCalculateRangeProofsHash { + hashForRangeProofs = calcRangeProofsHash([]*TxOutput{tx.Outputs[inIndex]}) + } } } @@ -633,6 +647,9 @@ func (tx *Transaction) HashForWitnessV0(inIndex int, prevoutScript []byte, value s.WriteSlice(iss.TokenAmount) } s.WriteSlice(hashOutputs[:]) + if shouldCalculateRangeProofsHash { + s.WriteSlice(hashForRangeProofs[:]) + } s.WriteUint32(tx.Locktime) s.WriteUint32(uint32(hashType)) @@ -684,7 +701,13 @@ func (tx *Transaction) baseSize(forSignature bool) int { return size } -func (tx *Transaction) serialize(buf *bytes.Buffer, allowWitness, zeroFlag, forSignature bool) ([]byte, error) { +func (tx *Transaction) serialize( + buf *bytes.Buffer, + allowWitness, + zeroFlag, + forSignature, + withRangeProofs bool, +) ([]byte, error) { s, err := bufferutil.NewSerializer(buf) if err != nil { return nil, err @@ -740,6 +763,10 @@ func (tx *Transaction) serialize(buf *bytes.Buffer, allowWitness, zeroFlag, forS s.WriteUint64(0) } s.WriteVarSlice(txOut.Script) + if withRangeProofs { + s.WriteVarSlice(txOut.RangeProof) + s.WriteVarSlice(txOut.SurjectionProof) + } } // Locktime @@ -806,3 +833,12 @@ func calcTxOutputsHash(outs []*TxOutput) [32]byte { } return chainhash.DoubleHashH(s.Bytes()) } + +func calcRangeProofsHash(outs []*TxOutput) [32]byte { + s, _ := bufferutil.NewSerializer(nil) + for _, out := range outs { + s.WriteVarSlice(out.RangeProof) + s.WriteVarSlice(out.SurjectionProof) + } + return chainhash.DoubleHashH(s.Bytes()) +}