├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── address ├── address.go ├── address_test.go ├── opcode.go └── script.go ├── blech32 ├── blech32.go └── blech32_test.go ├── block ├── block.go ├── deserialize.go ├── deserialize_test.go ├── hash_test.go ├── merkle_block.go ├── merkle_block_test.go ├── serialize.go ├── serialize_test.go └── testdata │ ├── deserialize.json │ └── hash.json ├── confidential ├── confidential.go ├── confidential_nocgo.go ├── confidential_test.go ├── data │ └── confidential.json ├── zkp_generator.go ├── zkp_generator_nocgo.go ├── zkp_validator.go └── zkp_validator_nocgo.go ├── descriptor ├── descriptor.go ├── parser.go ├── parser_test.go ├── util.go └── wpkh.go ├── doc.go ├── elementsutil ├── elementsutil.go └── elementsutil_test.go ├── go-elements-gopher.png ├── go.mod ├── go.sum ├── internal └── bufferutil │ ├── bufferutil.go │ ├── deserializer.go │ ├── serializer.go │ └── serializer_test.go ├── network └── network.go ├── payment ├── examples_test.go ├── p2tr.go ├── p2tr_test.go ├── payment.go └── payment_test.go ├── pegin ├── pegin.go └── pegin_test.go ├── pegincontract ├── contract.go ├── contract_nocgo.go ├── contract_test.go └── pegin_e2e_test.go ├── pset ├── README.md ├── blinder.go ├── creator.go ├── creator_test.go ├── data │ ├── creator.json │ ├── extractor.json │ ├── finalizer.json │ ├── signer.json │ └── updater.json ├── doc.go ├── extractor.go ├── extractor_test.go ├── finalizer.go ├── finalizer_test.go ├── pset.go ├── pset_input.go ├── pset_output.go ├── pset_test.go ├── signer.go ├── signer_test.go ├── updater.go ├── updater_test.go └── utils.go ├── psetv2 ├── bip32.go ├── bitset.go ├── bitset_test.go ├── blinder.go ├── creator.go ├── creator_test.go ├── extractor.go ├── finalizer.go ├── global.go ├── input.go ├── key_pair.go ├── output.go ├── partialsig.go ├── pset.go ├── pset_test.go ├── signer.go ├── testdata │ ├── bitset.json │ └── roundtrip.json ├── updater.go ├── utils.go └── utils_test.go ├── slip77 ├── data │ └── slip77.json ├── slip77.go └── slip77_test.go ├── taproot ├── taproot.go ├── taproot_e2e_test.go └── taproot_test.go └── transaction ├── data ├── discount_ct.json ├── issuance.json └── tx_valid.json ├── issuance.go ├── issuance_test.go ├── transaction.go └── transaction_test.go /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | integration: 11 | name: Integration Tests 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Set up Go 1.17.x 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: ^1.17 18 | id: go 19 | 20 | - name: Check out code into the Go module directory 21 | uses: actions/checkout@v3 22 | with: 23 | fetch-depth: 0 24 | # Check out pull request's HEAD commit instead of the merge commit to 25 | # work-around an issue where wrong a commit is being checked out. 26 | # For more details, see: 27 | # https://github.com/actions/checkout/issues/299. 28 | # ref: ${{ github.event.pull_request.head.sha }} 29 | 30 | - name: Get dependencies 31 | run: go get -v -t -d ./... 32 | 33 | - name: Run Nigiri 34 | uses: vulpemventures/nigiri-github-action@v1 35 | 36 | - name: Test 37 | run: make test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | 18 | # WebStorm IDE project config files 19 | .idea 20 | 21 | .vscode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Vulpem Ventures 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ## help: prints this help message 2 | help: 3 | @echo "Usage: \n" 4 | @sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /' 5 | 6 | test: 7 | export API_URL=http://localhost:3001; \ 8 | export API_BTC_URL=http://localhost:3000; \ 9 | go test -count=1 -v ./... 10 | 11 | ## fmt: Go Format 12 | fmt: 13 | @echo "Gofmt..." 14 | @gofmt -w -l . 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | # go-elements 6 | 7 | [![Go](https://github.com/vulpemventures/go-elements/actions/workflows/ci.yml/badge.svg)](https://github.com/vulpemventures/go-elements/actions/workflows/ci.yml) 8 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/vulpemventures/go-elements)](https://pkg.go.dev/github.com/vulpemventures/go-elements) 9 | [![Release](https://img.shields.io/github/release/vulpemventures/go-elements.svg?style=flat-square)](https://github.com/vulpemventures/go-elements/releases/latest) 10 | [![Go Report Card](https://goreportcard.com/badge/github.com/vulpemventures/go-elements)](https://goreportcard.com/report/github.com/vulpemventures/go-elements) 11 | [![Bitcoin Donate](https://badgen.net/badge/Bitcoin/Donate/F7931A?icon=bitcoin)](https://blockstream.info/address/3MdERN32qiMnQ68bSSee5CXQkrSGx1iStr) 12 | 13 | 14 | Go support for confidential transactions on Elements-based blockchains 15 | 16 | **The package is currently being developed.** For stable versions, you must refer to the [latest release](https://github.com/vulpemventures/go-elements/releases) 17 | 18 | ## Install 19 | 20 | ```sh 21 | # Install latest tagged release 22 | $ go get github.com/vulpemventures/go-elements@latest 23 | ``` 24 | 25 | ## 👀 Examples 26 | 27 | - [Broadcast unblinded transaction](pset/pset_test.go#L82) 28 | - [Broadcast blinded transaction - with unblinded input](pset/pset_test.go#L335) 29 | - [Broadcast blinded transaction - with blinded input](pset/pset_test.go#L490) 30 | - [Broadcast issuance transaction - with unblinded inputs, unblinded issuance, blinded outputs](pset/pset_test.go#L689) 31 | - [Broadcast issuance transaction - with unblinded inputs, blinded issuance, blinded outputs](pset/pset_test.go#L867) 32 | 33 | ## 🛣 Roadmap 34 | 35 | - [x] Chain parameters (prefixes, magic numbers, …) 36 | - [x] Pay to Public Key Hash 37 | - [x] Pay to Script Hash 38 | - [x] Pay to Witness Public Key Hash 39 | - [x] Pay to Witness Script Hash 40 | - [x] Tx serialization / deserialization 41 | - [x] Use of confidential values instead of pure numbers 42 | - [x] Fix order of witness in transaction serialization 43 | - [x] Add confidential fields 44 | - [x] Serialization for (witness) signature 45 | - [x] [PSET / Bip174 for Elements](https://github.com/vulpemventures/go-elements/tree/master/pset) 46 | - [x] [Blech32](https://github.com/vulpemventures/go-elements/tree/master/blech32) 47 | - [x] [CGO bindings for secp256k1-zkp](https://github.com/vulpemventures/go-secp256k1-zkp) 48 | - [x] Unblinding ins / Blinding outs / Blinding issuance ins 49 | - [x] Signing a confidential input (use 0 value amounts to produce the hash for the signature) 50 | - [x] Asset issuance 51 | - [x] Asset re-issuance 52 | - [x] Slip77 53 | - [ ] Upcoming [PSET spec](https://github.com/ElementsProject/elements/pull/951) support 54 | 55 | ## 🖥 Development 56 | 57 | * Clone repository: 58 | 59 | ```sh 60 | $ git clone https://github.com/vulpemventures/go-elements.git 61 | ``` 62 | 63 | * Enter into the project folder and install dependencies: 64 | 65 | ```sh 66 | $ cd go-elements 67 | $ go get -t -v ./... 68 | ``` 69 | 70 | * Run tests 71 | 72 | > For running tests it is required to have a running [Nigiri](https://github.com/vulpemventures/nigiri) locally, or at least a remote one reachable from the outside. 73 | To run the tests it is mandatory to export an `API_URL` environment variable pointing to the URL of `nigiri-chopsticks`. 74 | 75 | ``` 76 | $ nigiri start --liquid 77 | $ make test 78 | ``` 79 | 80 | * More detailed documentation 81 | 82 | ``` 83 | $ godoc -http ":8080" 84 | ``` 85 | 86 | > http://localhost:8080/pkg/github.com/vulpemventures/go-elements/ 87 | 88 | ## 👷‍♂️ Contributors 89 | 90 | - [@tiero](https://github.com/tiero) 91 | - [@altafan](https://github.com/altafan) 92 | - [@sekulicd](https://github.com/sekulicd) 93 | - [@francismars](https://github.com/francismars) 94 | 95 | ## License [MIT](https://github.com/vulpemventures/go-elements/blob/master/LICENSE) 96 | -------------------------------------------------------------------------------- /address/address_test.go: -------------------------------------------------------------------------------- 1 | package address 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestBase58(t *testing.T) { 11 | addresses := []string{ 12 | "XFKcLWJmPuToz62uc2sgCBUddmH6yopoxE", 13 | "2dnTicaj6kay4FAV1N9qNDNawYehaxpifkP", 14 | } 15 | 16 | for _, addr := range addresses { 17 | base58, err := FromBase58(addr) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | gotAddr := ToBase58(base58) 22 | assert.Equal(t, addr, gotAddr) 23 | } 24 | } 25 | 26 | func TestBech32(t *testing.T) { 27 | addresses := []string{ 28 | "ert1qlg343tpldc4wvjxn3jdq2qs35r8j5yd5kjfrrt", 29 | "ert1qyny4kp4adanuu670vfrnz7t384s8gvdtnwr0jcvvrwqwar4t9qcs2m7c20", 30 | } 31 | 32 | for _, addr := range addresses { 33 | b32, err := FromBech32(addr) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | gotAddr, err := ToBech32(b32) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | assert.Equal(t, addr, gotAddr) 42 | } 43 | } 44 | 45 | func TestBase58Confidential(t *testing.T) { 46 | addresses := []string{ 47 | "CTEvndySQ8VCBNmc7LGcGVm43eTqwWdCzFTSD7bjd4bJs7ti181aQnwADXXCzJPbANkSEpeVq19yck8N", 48 | "AzppxC5RDs8yB8mabhwS13y4WbsWoS41fLV8GKM4woLUJB5RxNBVfK6wdVX4QVoubRXFKKfbPhEKKTKc", 49 | } 50 | 51 | for _, addr := range addresses { 52 | base58, err := FromBase58Confidential(addr) 53 | if err != nil { 54 | t.Fatal(err) 55 | } 56 | gotAddr := ToBase58Confidential(base58) 57 | assert.Equal(t, addr, gotAddr) 58 | } 59 | } 60 | 61 | func TestBlech32(t *testing.T) { 62 | addresses := []string{ 63 | "el1qqw3e3mk4ng3ks43mh54udznuekaadh9lgwef3mwgzrfzakmdwcvqpe4ppdaa3t44v3zv2u6w56pv6tc666fvgzaclqjnkz0sd", 64 | "el1qqw3e3mk4ng3ks43mh54udznuekaadh9lgwef3mwgzrfzakmdwcvqqve2xzutyaf7vjcap67f28q90uxec2ve95g3rpu5crapcmfr2l9xl5jzazvcpysz", 65 | } 66 | 67 | for _, addr := range addresses { 68 | b32, err := FromBlech32(addr) 69 | if err != nil { 70 | t.Fatal(err) 71 | } 72 | gotAddr, err := ToBlech32(b32) 73 | if err != nil { 74 | t.Fatal(err) 75 | } 76 | assert.Equal(t, addr, gotAddr) 77 | } 78 | } 79 | 80 | func TestConfidential(t *testing.T) { 81 | addresses := []string{ 82 | "CTEvndySQ8VCBNmc7LGcGVm43eTqwWdCzFTSD7bjd4bJs7ti181aQnwADXXCzJPbANkSEpeVq19yck8N", 83 | "AzppxC5RDs8yB8mabhwS13y4WbsWoS41fLV8GKM4woLUJB5RxNBVfK6wdVX4QVoubRXFKKfbPhEKKTKc", 84 | "el1qqw3e3mk4ng3ks43mh54udznuekaadh9lgwef3mwgzrfzakmdwcvqpe4ppdaa3t44v3zv2u6w56pv6tc666fvgzaclqjnkz0sd", 85 | "el1qqw3e3mk4ng3ks43mh54udznuekaadh9lgwef3mwgzrfzakmdwcvqqve2xzutyaf7vjcap67f28q90uxec2ve95g3rpu5crapcmfr2l9xl5jzazvcpysz", 86 | "el1pqganzlzf78m05czqv9he0pl9gyryvvqxnr6fdk8rj9u40arvcepvanayxug6fkddlywsp2lgh4e9ypyucu8nhlf5tf8lwqypanv8qwjtr8cgsld44ygt", 87 | } 88 | 89 | for _, addr := range addresses { 90 | res, err := FromConfidential(addr) 91 | if err != nil { 92 | t.Fatal(err) 93 | } 94 | gotAddr, err := ToConfidential(res) 95 | if err != nil { 96 | t.Fatal(err) 97 | } 98 | assert.Equal(t, addr, gotAddr) 99 | } 100 | } 101 | 102 | func TestDecodeAddressType(t *testing.T) { 103 | tests := []struct { 104 | address string 105 | expectedType int 106 | }{ 107 | { 108 | address: "Q9863Eah5byyxdBX8zghpooS2x4Ey8XZyc", 109 | expectedType: P2Pkh, 110 | }, 111 | { 112 | address: "H5RCjtzndKyzFnVe41yg62T3WViWguyz4M", 113 | expectedType: P2Sh, 114 | }, 115 | { 116 | address: "ex1qlg343tpldc4wvjxn3jdq2qs35r8j5yd5vqrmu3", 117 | expectedType: P2Wpkh, 118 | }, 119 | { 120 | address: "ert1q2z45rh444qmeand48lq0wp3jatxs2nzh492ds9s5yscv2pplxwesajz7q3", 121 | expectedType: P2Wsh, 122 | }, 123 | { 124 | address: "VTpuLYhJwE8CFm6h1A6DASCaJuRQqkBt6qGfbebSHAUxGXsJMo8wtRvLZYZSWWXt89jG55pCF4YfxMjh", 125 | expectedType: ConfidentialP2Pkh, 126 | }, 127 | { 128 | address: "VJLDHFUbw8oPUcwzmf9jw4tZdN57rEfAusRmWy6knHAF2a4rLGenJz5WPVuyggVzQPHY6JjzKuw31B6e", 129 | expectedType: ConfidentialP2Sh, 130 | }, 131 | { 132 | address: "lq1qqwrdmhm69vsq3qfym06tlyhfze9ltauay9tv4r34ueplfwtjx0q27dk2c4d3a9ms6wum04efclqph7dg4unwcmwmw4vnqreq3", 133 | expectedType: ConfidentialP2Wpkh, 134 | }, 135 | { 136 | address: "lq1qq2akvug2el2rg6lt6aewh9rzy7dglf9ajdmrkknnwwl3jwxgfkh985x3lrzmrq2mc3c6aa85wgxxfm9v8r062qwq4ty579p54pn2q2hqnhgwv394ycf8", 137 | expectedType: ConfidentialP2Wsh, 138 | }, 139 | { 140 | address: "el1pqgm2ru230fmqsw9j9q99992rgh7mresgaegyf79predtczelp0nt7e64ytjvjzt8lraeva72tw6gs6snfr2643hlxpgrmcfl96nmhep072dde867cx4u", 141 | expectedType: ConfidentialP2TR, 142 | }, 143 | { 144 | address: "ert1pe7jrwydymxklj8gq405t6ujjqjwvwreml5695nlhqzq7ekrs8f9sclaudg", 145 | expectedType: P2TR, 146 | }, 147 | } 148 | 149 | for _, tt := range tests { 150 | addressType, err := DecodeType(tt.address) 151 | if err != nil { 152 | t.Fatal(err) 153 | } 154 | assert.Equal(t, tt.expectedType, addressType) 155 | } 156 | } 157 | 158 | func h2b(str string) []byte { 159 | buf, _ := hex.DecodeString(str) 160 | return buf 161 | } 162 | -------------------------------------------------------------------------------- /blech32/blech32_test.go: -------------------------------------------------------------------------------- 1 | package blech32_test 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/vulpemventures/go-elements/blech32" 8 | ) 9 | 10 | type fixture struct { 11 | enc blech32.EncodingType 12 | valid []string 13 | invalid []string 14 | } 15 | 16 | func makeTest(f fixture, t *testing.T) { 17 | for _, s := range f.valid { 18 | t.Run(s, func(t *testing.T) { 19 | hrp, data, _, err := blech32.DecodeGeneric(s) 20 | if err != nil { 21 | t.Errorf("%v: %v", s, err) 22 | } 23 | 24 | str, err := blech32.Encode(hrp, data, f.enc) 25 | if err != nil { 26 | t.Errorf("%v: %v", s, err) 27 | } 28 | 29 | if str != strings.ToLower(s) { 30 | t.Errorf("%v: %v != %v", s, str, s) 31 | } 32 | }) 33 | } 34 | for _, s := range f.invalid { 35 | t.Run(s, func(t *testing.T) { 36 | _, _, _, err := blech32.DecodeGeneric(s) 37 | if err == nil { 38 | t.Errorf("%v: expected error", s) 39 | } 40 | }) 41 | } 42 | } 43 | 44 | var blech32fixture = fixture{ 45 | enc: blech32.BLECH32, 46 | valid: []string{ 47 | "A133NZFWEYK7UT", 48 | "a133nzfweyk7ut", 49 | "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio195jhgldwsn5j", 50 | "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lgmcn7l7t7xve", 51 | "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xldwlutcw2l", 52 | "split1checkupstagehandshakeupstreamerranterredcaperredegneyqml9esp", 53 | "?19dv34t3p4s35", 54 | }, 55 | invalid: []string{ 56 | " 1nwldj5", 57 | "\x7f1axkwrx", 58 | "\x801eym55h", 59 | "an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx", 60 | "pzry9x0s0muk", 61 | "1pzry9x0s0muk", 62 | "x1b4n0q5v", 63 | "li1dgmt3", 64 | "de1lg7wt\xff", 65 | "A1G7SGD8", 66 | "10a06t8", 67 | "1qzzfhee", 68 | "a12UEL5L", 69 | "A12uEL5L", 70 | }, 71 | } 72 | 73 | var blech32mFixture = fixture{ 74 | enc: blech32.BLECH32M, 75 | valid: []string{ 76 | "A1EYL4VXQ3HRPT", 77 | "a1eyl4vxq3hrpt", 78 | "an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11yatn6l85muud", 79 | "abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqg8m5pqg67zq3", 80 | "11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll09wxh8ajdvxv", 81 | "split1checkupstagehandshakeupstreamerranterredcaperred3alwpgz2yydp", 82 | "?1dcqxsrg55dv5", 83 | }, 84 | invalid: []string{ 85 | " 1xj0phk", 86 | "\x7f1g6xzxy", 87 | "\x801vctc34", 88 | "an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4", 89 | "qyrz8wqd2c9m", 90 | "1qyrz8wqd2c9m", 91 | "y1b0jsk6g", 92 | "lt1igcx5c0", 93 | "in1muywd", 94 | "mm1crxm3i", 95 | "au1s5cgom", 96 | "M1VUXWEZ", 97 | "16plkw9", 98 | "1p2gdwpf", 99 | }, 100 | } 101 | 102 | func TestBlech32(t *testing.T) { 103 | makeTest(blech32fixture, t) 104 | } 105 | 106 | func TestBlech32m(t *testing.T) { 107 | makeTest(blech32mFixture, t) 108 | } 109 | -------------------------------------------------------------------------------- /block/block.go: -------------------------------------------------------------------------------- 1 | package block 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | 7 | "github.com/vulpemventures/go-elements/transaction" 8 | ) 9 | 10 | const ( 11 | null = iota 12 | compact 13 | full 14 | 15 | hashSize = 32 16 | 17 | DYNAFED_HF_MASK = uint32(1 << 31) 18 | ) 19 | 20 | type Block struct { 21 | Header *Header 22 | TransactionsData *Transactions 23 | } 24 | 25 | type Transactions struct { 26 | Transactions []*transaction.Transaction 27 | } 28 | 29 | func NewFromBuffer(buf *bytes.Buffer) (*Block, error) { 30 | return deserialize(buf) 31 | } 32 | 33 | func NewFromHex(h string) (*Block, error) { 34 | hexBytes, err := hex.DecodeString(h) 35 | if err != nil { 36 | return nil, err 37 | } 38 | buf := bytes.NewBuffer(hexBytes) 39 | return NewFromBuffer(buf) 40 | } 41 | 42 | type Header struct { 43 | // Version - should be 0x20000000 except when versionbits signalling 44 | Version uint32 45 | // Previous blockhash 46 | PrevBlockHash []byte 47 | // Transaction Merkle root 48 | MerkleRoot []byte 49 | // Block timestamp 50 | Timestamp uint32 51 | // Block Height 52 | Height uint32 53 | // Block signature and dynamic federation-related data 54 | ExtData *ExtData 55 | } 56 | 57 | // ExtData block signature and dynamic federation-related data 58 | type ExtData struct { 59 | // Liquid v1-style static `signblockscript` and witness 60 | Proof *Proof 61 | // Dynamic federations 62 | DynamicFederation *DynamicFederation 63 | // is dynamic federation 64 | IsDyna bool 65 | } 66 | 67 | // Proof Liquid v1-style static `signblockscript` and witness 68 | type Proof struct { 69 | // Block "public key" 70 | Challenge []byte 71 | // Satisfying witness to the above Challenge, or nothing 72 | Solution []byte 73 | } 74 | 75 | type DynamicFederation struct { 76 | Current *DynamicFederationParams 77 | Proposed *DynamicFederationParams 78 | SignBlockWitness [][]byte 79 | } 80 | 81 | type DynamicFederationParams struct { 82 | CompactParams *CompactParams 83 | FullParams *FullParams 84 | } 85 | 86 | // CompactParams params where the fedpeg data and extension space 87 | // are not included, and are assumed to be equal to the values 88 | // from the previous block 89 | type CompactParams struct { 90 | // "scriptPubKey" used for block signing 91 | SignBlockScript []byte 92 | /// Maximum, in bytes, of the size of a blocksigning witness 93 | SignBlockWitnessLimit uint32 94 | /// Merkle root of extra data 95 | ElidedRoot []byte 96 | } 97 | 98 | // FullParams full dynamic federations parameters 99 | type FullParams struct { 100 | // "scriptPubKey" used for block signing 101 | SignBlockScript []byte 102 | // Maximum, in bytes, of the size of a blocksigning witness 103 | SignBlockWitnessLimit uint32 104 | // Untweaked `scriptPubKey` used for pegins 105 | FedpegProgram []byte 106 | // For v0 fedpeg programs, the witness script of the untweaked 107 | // pegin address. For future versions, this data has no defined 108 | // meaning and will be considered "anyone can spend". 109 | FedpegScript []byte 110 | /// "Extension space" used by Liquid for PAK key entries 111 | ExtensionSpace [][]byte 112 | } 113 | -------------------------------------------------------------------------------- /block/deserialize.go: -------------------------------------------------------------------------------- 1 | package block 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | 7 | "github.com/vulpemventures/go-elements/internal/bufferutil" 8 | 9 | "github.com/vulpemventures/go-elements/transaction" 10 | ) 11 | 12 | func deserialize(buf *bytes.Buffer) (*Block, error) { 13 | header, err := DeserializeHeader(buf) 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | transactions, err := DeserializeTransactions(buf) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | return &Block{ 24 | Header: header, 25 | TransactionsData: &Transactions{ 26 | Transactions: transactions, 27 | }, 28 | }, nil 29 | } 30 | 31 | func DeserializeHeader( 32 | buf *bytes.Buffer, 33 | ) (*Header, error) { 34 | d := bufferutil.NewDeserializer(buf) 35 | 36 | version, err := d.ReadUint32() 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | isDyna := DYNAFED_HF_MASK&version != 0 42 | if isDyna { 43 | version &= ^DYNAFED_HF_MASK 44 | } 45 | 46 | prevBlockHash, err := d.ReadSlice(hashSize) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | merkleRoot, err := d.ReadSlice(hashSize) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | timestamp, err := d.ReadUint32() 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | blockHeight, err := d.ReadUint32() 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | extData, err := deserializeExtData(d, isDyna) 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | return &Header{ 72 | Version: version, 73 | PrevBlockHash: prevBlockHash, 74 | MerkleRoot: merkleRoot, 75 | Timestamp: timestamp, 76 | Height: blockHeight, 77 | ExtData: extData, 78 | }, nil 79 | } 80 | 81 | func deserializeExtData( 82 | d *bufferutil.Deserializer, 83 | isDyna bool, 84 | ) (*ExtData, error) { 85 | var dynamicFederation *DynamicFederation 86 | var proof *Proof 87 | var err error 88 | if isDyna { 89 | dynamicFederation, err = deserializeDynamicFederation(d) 90 | if err != nil { 91 | return nil, err 92 | } 93 | } else { 94 | proof, err = deserializeProof(d) 95 | if err != nil { 96 | return nil, err 97 | } 98 | } 99 | 100 | return &ExtData{ 101 | Proof: proof, 102 | DynamicFederation: dynamicFederation, 103 | IsDyna: isDyna, 104 | }, nil 105 | } 106 | 107 | func deserializeDynamicFederation( 108 | d *bufferutil.Deserializer, 109 | ) (*DynamicFederation, error) { 110 | currentParams, err := deserializeDynamicFederationParams(d) 111 | if err != nil { 112 | return nil, err 113 | } 114 | 115 | proposedParams, err := deserializeDynamicFederationParams(d) 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | signBlockWitness, err := deserializeSignBlockWitness(d) 121 | if err != nil { 122 | return nil, err 123 | } 124 | 125 | return &DynamicFederation{ 126 | Current: currentParams, 127 | Proposed: proposedParams, 128 | SignBlockWitness: signBlockWitness, 129 | }, nil 130 | } 131 | 132 | func deserializeDynamicFederationParams( 133 | d *bufferutil.Deserializer, 134 | ) (*DynamicFederationParams, error) { 135 | var compactParams *CompactParams 136 | var fullParams *FullParams 137 | var err error 138 | 139 | serializeType, err := d.ReadUint8() 140 | if err != nil { 141 | return nil, err 142 | } 143 | 144 | switch serializeType { 145 | case null: 146 | return nil, nil 147 | case compact: 148 | compactParams, err = deserializeCompactParams(d) 149 | if err != nil { 150 | return nil, err 151 | } 152 | case full: 153 | fullParams, err = deserializeFullParams(d) 154 | if err != nil { 155 | return nil, err 156 | } 157 | default: 158 | return nil, errors.New("bad serialize type for dynafed parameters") 159 | } 160 | 161 | return &DynamicFederationParams{ 162 | CompactParams: compactParams, 163 | FullParams: fullParams, 164 | }, nil 165 | } 166 | 167 | func deserializeCompactParams( 168 | d *bufferutil.Deserializer, 169 | ) (*CompactParams, error) { 170 | signBlockScriptLength, err := d.ReadVarInt() 171 | if err != nil { 172 | return nil, err 173 | } 174 | 175 | signBlockScript, err := d.ReadSlice(uint(signBlockScriptLength)) 176 | if err != nil { 177 | return nil, err 178 | } 179 | 180 | signBlockWitnessLimit, err := d.ReadUint32() 181 | if err != nil { 182 | return nil, err 183 | } 184 | 185 | elidedRoot, err := d.ReadSlice(hashSize) 186 | if err != nil { 187 | return nil, err 188 | } 189 | 190 | return &CompactParams{ 191 | SignBlockScript: signBlockScript, 192 | SignBlockWitnessLimit: signBlockWitnessLimit, 193 | ElidedRoot: elidedRoot, 194 | }, nil 195 | } 196 | 197 | func deserializeFullParams( 198 | d *bufferutil.Deserializer, 199 | ) (*FullParams, error) { 200 | signBlockScriptLength, err := d.ReadVarInt() 201 | if err != nil { 202 | return nil, err 203 | } 204 | 205 | signBlockScript, err := d.ReadSlice(uint(signBlockScriptLength)) 206 | if err != nil { 207 | return nil, err 208 | } 209 | 210 | signBlockWitnessLimit, err := d.ReadUint32() 211 | if err != nil { 212 | return nil, err 213 | } 214 | 215 | fedpegProgramLength, err := d.ReadVarInt() 216 | if err != nil { 217 | return nil, err 218 | } 219 | 220 | fedpegProgram, err := d.ReadSlice(uint(fedpegProgramLength)) 221 | if err != nil { 222 | return nil, err 223 | } 224 | 225 | fedpegScriptLength, err := d.ReadVarInt() 226 | if err != nil { 227 | return nil, err 228 | } 229 | 230 | fedpegScript, err := d.ReadSlice(uint(fedpegScriptLength)) 231 | if err != nil { 232 | return nil, err 233 | } 234 | 235 | extensionSpaceLength, err := d.ReadVarInt() 236 | if err != nil { 237 | return nil, err 238 | } 239 | extensionSpace := make([][]byte, 0, extensionSpaceLength) 240 | for i := 0; i < int(extensionSpaceLength); i++ { 241 | tmpLen, err := d.ReadVarInt() 242 | if err != nil { 243 | return nil, err 244 | } 245 | tmp, err := d.ReadSlice(uint(tmpLen)) 246 | if err != nil { 247 | return nil, err 248 | } 249 | extensionSpace = append(extensionSpace, tmp) 250 | } 251 | 252 | return &FullParams{ 253 | SignBlockScript: signBlockScript, 254 | SignBlockWitnessLimit: signBlockWitnessLimit, 255 | FedpegProgram: fedpegProgram, 256 | FedpegScript: fedpegScript, 257 | ExtensionSpace: extensionSpace, 258 | }, nil 259 | } 260 | 261 | func deserializeSignBlockWitness( 262 | d *bufferutil.Deserializer, 263 | ) ([][]byte, error) { 264 | signBlockWitnessLength, err := d.ReadVarInt() 265 | if err != nil { 266 | return nil, err 267 | } 268 | signBlockWitness := make([][]byte, 0, signBlockWitnessLength) 269 | for i := 0; i < int(signBlockWitnessLength); i++ { 270 | tmpLen, err := d.ReadVarInt() 271 | if err != nil { 272 | return nil, err 273 | } 274 | tmp, err := d.ReadSlice(uint(tmpLen)) 275 | if err != nil { 276 | return nil, err 277 | } 278 | signBlockWitness = append(signBlockWitness, tmp) 279 | } 280 | 281 | return signBlockWitness, nil 282 | } 283 | 284 | func deserializeProof( 285 | d *bufferutil.Deserializer, 286 | ) (*Proof, error) { 287 | challengeLength, err := d.ReadVarInt() 288 | if err != nil { 289 | return nil, err 290 | } 291 | 292 | challenge, err := d.ReadSlice(uint(challengeLength)) 293 | if err != nil { 294 | return nil, err 295 | } 296 | 297 | solutionLength, err := d.ReadVarInt() 298 | if err != nil { 299 | return nil, err 300 | } 301 | 302 | solution, err := d.ReadSlice(uint(solutionLength)) 303 | if err != nil { 304 | return nil, err 305 | } 306 | 307 | return &Proof{ 308 | Challenge: challenge, 309 | Solution: solution, 310 | }, nil 311 | } 312 | 313 | func DeserializeTransactions( 314 | buf *bytes.Buffer, 315 | ) ([]*transaction.Transaction, error) { 316 | d := bufferutil.NewDeserializer(buf) 317 | 318 | txCount, err := d.ReadVarInt() 319 | if err != nil { 320 | return nil, err 321 | } 322 | 323 | txs := make([]*transaction.Transaction, 0) 324 | for i := 0; i < int(txCount); i++ { 325 | tx, err := transaction.NewTxFromBuffer(buf) 326 | if err != nil { 327 | return nil, err 328 | } 329 | txs = append(txs, tx) 330 | } 331 | 332 | return txs, nil 333 | } 334 | -------------------------------------------------------------------------------- /block/deserialize_test.go: -------------------------------------------------------------------------------- 1 | package block 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "os" 11 | "strconv" 12 | "strings" 13 | "testing" 14 | "time" 15 | 16 | "github.com/btcsuite/btcd/btcec/v2" 17 | "github.com/vulpemventures/go-elements/network" 18 | "github.com/vulpemventures/go-elements/payment" 19 | 20 | "github.com/vulpemventures/go-elements/elementsutil" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | // some of fixtures taken from: 26 | // https://github.com/ElementsProject/rust-elements/blob/0d67c57afa1137ab27861bb8c2190413929d4301/src/block.rs#L621 27 | // https://github.com/ElementsProject/rust-elements/blob/0d67c57afa1137ab27861bb8c2190413929d4301/src/block.rs#L703 28 | func TestBlockDeserialization(t *testing.T) { 29 | file, err := ioutil.ReadFile("testdata/deserialize.json") 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | var tests []map[string]interface{} 34 | json.Unmarshal(file, &tests) 35 | 36 | for _, v := range tests { 37 | testName := v["name"].(string) 38 | t.Run(testName, func(t *testing.T) { 39 | block, err := NewFromHex(v["hex"].(string)) 40 | if err != nil { 41 | t.Errorf("test: %v, err: %v", testName, err) 42 | } 43 | 44 | assert.Equal( 45 | t, 46 | v["numOfTx"].(string), 47 | strconv.Itoa(len(block.TransactionsData.Transactions)), 48 | ) 49 | }) 50 | } 51 | } 52 | 53 | func TestBlockDeserializationIntegration(t *testing.T) { 54 | privkey, err := btcec.NewPrivateKey() 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | pubkey := privkey.PubKey() 59 | p2pkh := payment.FromPublicKey(pubkey, &network.Regtest, nil) 60 | address, _ := p2pkh.PubKeyHash() 61 | 62 | // Fund sender address. 63 | txID, err := faucet(address) 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | time.Sleep(5 * time.Second) 68 | 69 | blockHash, err := getTxBlockHash(txID) 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | 74 | rawBlock, err := getRawBlock(blockHash) 75 | if err != nil { 76 | t.Fatal(err) 77 | } 78 | 79 | block, err := NewFromBuffer(bytes.NewBuffer(rawBlock)) 80 | if err != nil { 81 | t.Fatal(err) 82 | } 83 | 84 | assert.Equal(t, 2, len(block.TransactionsData.Transactions)) 85 | 86 | for _, v := range block.TransactionsData.Transactions { 87 | // block will have 2 transactions: coinbase and faucet one, check faucet 88 | if len(v.Outputs) == 3 { 89 | for _, o := range v.Outputs { 90 | if len(o.Asset) == 9 && len(o.Script) > 0 { 91 | value, err := elementsutil.ValueFromBytes( 92 | block.TransactionsData.Transactions[1].Outputs[1].Value, 93 | ) 94 | if err != nil { 95 | t.Fatal(err) 96 | } 97 | 98 | assert.Equal(t, uint64(100000000), value) 99 | } 100 | } 101 | } 102 | } 103 | } 104 | 105 | func getRawBlock(hash string) ([]byte, error) { 106 | baseUrl, ok := os.LookupEnv("API_URL") 107 | if !ok { 108 | return nil, errors.New("API_URL environment variable is not set") 109 | } 110 | 111 | url := fmt.Sprintf("%s/block/%s/raw", baseUrl, hash) 112 | resp, err := http.Get(url) 113 | if err != nil { 114 | return nil, err 115 | } 116 | data, err := ioutil.ReadAll(resp.Body) 117 | if err != nil { 118 | return nil, err 119 | } 120 | return data, nil 121 | } 122 | 123 | func getTxBlockHash(txID string) (string, error) { 124 | baseUrl, ok := os.LookupEnv("API_URL") 125 | if !ok { 126 | return "", errors.New("API_URL environment variable is not set") 127 | } 128 | 129 | url := fmt.Sprintf("%s/tx/%s/status", baseUrl, txID) 130 | resp, err := http.Get(url) 131 | if err != nil { 132 | return "", err 133 | } 134 | data, err := ioutil.ReadAll(resp.Body) 135 | if err != nil { 136 | return "", err 137 | } 138 | 139 | var response map[string]interface{} 140 | if err := json.Unmarshal(data, &response); err != nil { 141 | return "", err 142 | } 143 | 144 | return response["block_hash"].(string), nil 145 | } 146 | 147 | func faucet(address string) (string, error) { 148 | baseUrl, ok := os.LookupEnv("API_URL") 149 | if !ok { 150 | return "nil", errors.New("API_URL environment variable is not set") 151 | } 152 | 153 | url := fmt.Sprintf("%s/faucet", baseUrl) 154 | payload := map[string]string{"address": address} 155 | body, _ := json.Marshal(payload) 156 | 157 | resp, err := http.Post(url, "application/json", bytes.NewBuffer(body)) 158 | if err != nil { 159 | return "", err 160 | } 161 | 162 | data, err := ioutil.ReadAll(resp.Body) 163 | if err != nil { 164 | return "", err 165 | } 166 | if res := string(data); len(res) <= 0 || strings.Contains(res, "sendtoaddress") { 167 | return "", fmt.Errorf("cannot fund address with faucet: %s", res) 168 | } 169 | 170 | respBody := map[string]string{} 171 | if err := json.Unmarshal(data, &respBody); err != nil { 172 | return "", err 173 | } 174 | return respBody["txId"], nil 175 | } 176 | -------------------------------------------------------------------------------- /block/hash_test.go: -------------------------------------------------------------------------------- 1 | package block 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "encoding/json" 7 | "io/ioutil" 8 | "testing" 9 | ) 10 | 11 | func TestBlockHash(t *testing.T) { 12 | file, err := ioutil.ReadFile("testdata/hash.json") 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | 17 | var tests []struct { 18 | Name string `json:"name"` 19 | BlockHeaderHex string `json:"blockHeaderHex"` 20 | Hash string `json:"hash"` 21 | } 22 | 23 | err = json.Unmarshal(file, &tests) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | 28 | for _, v := range tests { 29 | t.Run(v.Name, func(tt *testing.T) { 30 | 31 | blockHeader, err := hex.DecodeString(v.BlockHeaderHex) 32 | if err != nil { 33 | tt.Fatal(err) 34 | } 35 | 36 | header, err := DeserializeHeader(bytes.NewBuffer(blockHeader)) 37 | if err != nil { 38 | tt.Fatal(err) 39 | } 40 | 41 | hash, err := header.Hash() 42 | if err != nil { 43 | tt.Fatal(err) 44 | } 45 | 46 | hashHex := hash.String() 47 | 48 | if hashHex != v.Hash { 49 | tt.Errorf("hash: expected %s, got %s", v.Hash, hashHex) 50 | } 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /block/merkle_block.go: -------------------------------------------------------------------------------- 1 | package block 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "errors" 7 | 8 | "github.com/btcsuite/btcd/wire" 9 | 10 | "github.com/btcsuite/btcd/blockchain" 11 | 12 | "github.com/btcsuite/btcd/chaincfg/chainhash" 13 | ) 14 | 15 | const ( 16 | // The maximum allowed weight for a block, see BIP 141 (network rule) 17 | maxBlockWeight = 4000000 18 | witnessScaleFactor = 4 19 | minTransactionWeight = witnessScaleFactor * 60 // 60 is the lower bound for the size of a valid serialized tx 20 | 21 | ) 22 | 23 | type MerkleBlock struct { 24 | BlockHeader *wire.BlockHeader 25 | PartialMerkleTree *PartialMerkleTree 26 | } 27 | 28 | type PartialMerkleTree struct { 29 | TxTotalCount uint32 30 | TxHashes [][]byte 31 | FBad bool 32 | VBits []bool 33 | } 34 | 35 | func NewMerkleBlockFromBuffer(buf *bytes.Buffer) (*MerkleBlock, error) { 36 | return deserializeMerkleBlock(buf) 37 | } 38 | 39 | func NewMerkleBlockFromHex(h string) (*MerkleBlock, error) { 40 | hexBytes, err := hex.DecodeString(h) 41 | if err != nil { 42 | return nil, err 43 | } 44 | buf := bytes.NewBuffer(hexBytes) 45 | return NewMerkleBlockFromBuffer(buf) 46 | } 47 | 48 | func (m *MerkleBlock) ExtractMatches() (*chainhash.Hash, []chainhash.Hash, error) { 49 | vMatch := make([]chainhash.Hash, 0) 50 | 51 | if m.PartialMerkleTree.TxTotalCount == 0 { 52 | return nil, nil, errors.New("tx count equal 0") 53 | } 54 | 55 | if m.PartialMerkleTree.TxTotalCount > maxBlockWeight/minTransactionWeight { 56 | return nil, nil, errors.New("invalid tx count") 57 | } 58 | if len(m.PartialMerkleTree.TxHashes) > int(m.PartialMerkleTree.TxTotalCount) { 59 | return nil, nil, errors.New( 60 | "there can never be more hashes provided than one for every txid", 61 | ) 62 | } 63 | 64 | if len(m.PartialMerkleTree.VBits) < len(m.PartialMerkleTree.TxHashes) { 65 | return nil, nil, errors.New( 66 | "there must be at least one bit per node in the partial tree, " + 67 | "and at least one node per hash", 68 | ) 69 | } 70 | 71 | // Calculate the number of merkle branches (height) in the tree. 72 | height := uint32(0) 73 | for m.calcTreeWidth(height) > 1 { 74 | height++ 75 | } 76 | 77 | var bitsUsed, hashUsed, position = 0, 0, 0 78 | hashMerkleRoot, err := m.traverseAndExtract( 79 | height, 80 | position, 81 | &bitsUsed, 82 | &hashUsed, 83 | &vMatch, 84 | ) 85 | if err != nil { 86 | return nil, nil, err 87 | } 88 | 89 | if m.PartialMerkleTree.FBad { 90 | return nil, nil, errors.New( 91 | "there must be at least one bit per node in the partial tree, " + 92 | "and at least one node per hash", 93 | ) 94 | } 95 | // verify that all bits were consumed (except for the padding caused by serializing it as a byte sequence) 96 | if (bitsUsed+7)/8 != (len(m.PartialMerkleTree.VBits)+7)/8 { 97 | return nil, nil, errors.New( 98 | "except for the padding caused by serializing it as a byte" + 99 | " sequence, not all bits were consumed", 100 | ) 101 | } 102 | // verify that all hashes were consumed 103 | if hashUsed != len(m.PartialMerkleTree.TxHashes) { 104 | return nil, nil, errors.New("not all hashes were consumed") 105 | } 106 | 107 | return hashMerkleRoot, vMatch, nil 108 | } 109 | 110 | func (m *MerkleBlock) traverseAndExtract( 111 | height uint32, 112 | position int, 113 | bitsUsed *int, 114 | hashUsed *int, 115 | vMatch *[]chainhash.Hash, 116 | ) (*chainhash.Hash, error) { 117 | if *bitsUsed >= len(m.PartialMerkleTree.VBits) { 118 | m.PartialMerkleTree.FBad = true 119 | return nil, errors.New("overflowed the bits array") 120 | } 121 | 122 | fParentOfMatch := m.PartialMerkleTree.VBits[*bitsUsed] 123 | *bitsUsed++ 124 | if height == 0 || !fParentOfMatch { 125 | // if at height 0, or nothing interesting below, use stored hash and do not descend 126 | if *hashUsed >= len(m.PartialMerkleTree.TxHashes) { 127 | m.PartialMerkleTree.FBad = true 128 | return nil, errors.New("overflowed the hash array") 129 | } 130 | hash, err := chainhash.NewHash(m.PartialMerkleTree.TxHashes[*hashUsed]) 131 | if err != nil { 132 | return nil, err 133 | } 134 | 135 | *hashUsed++ 136 | if height == 0 && fParentOfMatch { // in case of height 0, we have a matched txid 137 | *vMatch = append(*vMatch, *hash) 138 | } 139 | 140 | return hash, nil 141 | } else { 142 | //otherwise, descend into the subtrees to extract matched txids and hashes 143 | left, err := m.traverseAndExtract( 144 | height-1, 145 | position*2, 146 | bitsUsed, 147 | hashUsed, 148 | vMatch, 149 | ) 150 | if err != nil { 151 | return nil, err 152 | } 153 | var right *chainhash.Hash 154 | if position*2+1 < int(m.calcTreeWidth(height-1)) { 155 | right, err = m.traverseAndExtract( 156 | height-1, 157 | position*2+1, 158 | bitsUsed, 159 | hashUsed, 160 | vMatch, 161 | ) 162 | if err != nil { 163 | return nil, err 164 | } 165 | if left.IsEqual(right) { 166 | // The left and right branches should never be identical, as the transaction 167 | // hashes covered by them must each be unique. 168 | m.PartialMerkleTree.FBad = true 169 | } 170 | } else { 171 | right = left 172 | } 173 | 174 | hash := blockchain.HashMerkleBranches(left, right) 175 | return &hash, nil 176 | } 177 | } 178 | 179 | // calcTreeWidth calculates and returns the the number of nodes (width) or a 180 | // merkle tree at the given depth-first height. 181 | func (m *MerkleBlock) calcTreeWidth(height uint32) uint32 { 182 | return (m.PartialMerkleTree.TxTotalCount + (1 << height) - 1) >> height 183 | } 184 | 185 | func deserializePartialMerkleTree( 186 | mb wire.MsgMerkleBlock, 187 | ) (*PartialMerkleTree, error) { 188 | txHashes := make([][]byte, 0, len(mb.Hashes)) 189 | for _, v := range mb.Hashes { 190 | 191 | txHashes = append(txHashes, v.CloneBytes()) 192 | } 193 | 194 | return &PartialMerkleTree{ 195 | TxTotalCount: mb.Transactions, 196 | TxHashes: txHashes, 197 | FBad: false, 198 | VBits: serializeVBits(mb.Flags), 199 | }, nil 200 | } 201 | 202 | func deserializeMerkleBlock(buf *bytes.Buffer) (*MerkleBlock, error) { 203 | mb := wire.MsgMerkleBlock{} 204 | err := mb.BtcDecode(buf, wire.ProtocolVersion, wire.LatestEncoding) 205 | if err != nil { 206 | return nil, err 207 | } 208 | 209 | partialMerkleTree, err := deserializePartialMerkleTree(mb) 210 | if err != nil { 211 | return nil, err 212 | } 213 | 214 | return &MerkleBlock{ 215 | BlockHeader: &mb.Header, 216 | PartialMerkleTree: partialMerkleTree, 217 | }, nil 218 | } 219 | 220 | func serializeVBits(b []byte) []bool { 221 | bits := make([]bool, 0) 222 | 223 | for _, v := range b { 224 | l := byteToBits(v) 225 | for _, v := range l { 226 | if v == 1 { 227 | bits = append(bits, true) 228 | } else { 229 | bits = append(bits, false) 230 | } 231 | } 232 | } 233 | 234 | return bits 235 | } 236 | 237 | func byteToBits(b byte) []byte { 238 | return []byte{ 239 | (b >> 0) & 0x1, 240 | (b >> 1) & 0x1, 241 | (b >> 2) & 0x1, 242 | (b >> 3) & 0x1, 243 | (b >> 4) & 0x1, 244 | (b >> 5) & 0x1, 245 | (b >> 6) & 0x1, 246 | (b >> 7) & 0x1, 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /block/merkle_block_test.go: -------------------------------------------------------------------------------- 1 | package block 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/vulpemventures/go-elements/elementsutil" 10 | ) 11 | 12 | func TestDeserializeMerkleBlock(t *testing.T) { 13 | txOutProof := "0000003095a03ddcb18a359a1d41072ed373d45e57200f57d6f318238a7a6eb18df4c02dd4187f08f314f8436ac76f38cbe03a791a0893f26444ea5d5ec8f5b9b95a93015280ab60ffff7f20010000000200000002bdde18f707d02aa18ba82926965dc8bb8991d9510cd98e2812cc44b7aae8d3959745d887c8459cd4441f1dfe1a2d7ecbe225db87eeaa68bb22df6fdbf7017c480105" 14 | 15 | merkleBlock, err := NewMerkleBlockFromHex(txOutProof) 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | 20 | for _, v := range merkleBlock.PartialMerkleTree.TxHashes { 21 | t.Log(hex.EncodeToString(elementsutil.ReverseBytes(v))) 22 | } 23 | } 24 | 25 | func TestExtractMatches(t *testing.T) { 26 | txOutProof := "0000003095a03ddcb18a359a1d41072ed373d45e57200f57d6f318238a7a6eb18df4c02dd4187f08f314f8436ac76f38cbe03a791a0893f26444ea5d5ec8f5b9b95a93015280ab60ffff7f20010000000200000002bdde18f707d02aa18ba82926965dc8bb8991d9510cd98e2812cc44b7aae8d3959745d887c8459cd4441f1dfe1a2d7ecbe225db87eeaa68bb22df6fdbf7017c480105" 27 | 28 | merkleBlock, err := NewMerkleBlockFromHex(txOutProof) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | 33 | hashMerkleRoot, matchedHashes, err := merkleBlock.ExtractMatches() 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | 38 | assert.Equal( 39 | t, 40 | true, 41 | merkleBlock.BlockHeader.MerkleRoot.IsEqual(hashMerkleRoot), 42 | ) 43 | assert.Equal(t, 1, len(matchedHashes)) 44 | } 45 | -------------------------------------------------------------------------------- /block/serialize.go: -------------------------------------------------------------------------------- 1 | package block 2 | 3 | import ( 4 | "github.com/btcsuite/btcd/chaincfg/chainhash" 5 | "github.com/vulpemventures/go-elements/internal/bufferutil" 6 | ) 7 | 8 | func (b *Block) SerializeBlock() ([]byte, error) { 9 | s := bufferutil.NewSerializer(nil) 10 | 11 | if err := b.Header.serializeHeader(s, false); err != nil { 12 | return nil, err 13 | } 14 | 15 | if err := b.TransactionsData.Serialize(s); err != nil { 16 | return nil, err 17 | } 18 | 19 | return s.Bytes(), nil 20 | } 21 | 22 | func (t *Transactions) Serialize(s *bufferutil.Serializer) error { 23 | err := s.WriteVarInt(uint64(len(t.Transactions))) 24 | if err != nil { 25 | return err 26 | } 27 | for _, v := range t.Transactions { 28 | txBytes, err := v.Serialize() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | err = s.WriteSlice(txBytes) 34 | if err != nil { 35 | return err 36 | } 37 | } 38 | 39 | return nil 40 | } 41 | 42 | // SerializeForHash returns the block bytes for block hash 43 | // it does not include some data of the block (like witness or solution in case of signed blocks) 44 | func (h *Header) SerializeForHash() ([]byte, error) { 45 | s := bufferutil.NewSerializer(nil) 46 | 47 | if err := h.serializeHeader(s, true); err != nil { 48 | return nil, err 49 | } 50 | 51 | return s.Bytes(), nil 52 | } 53 | 54 | // Serialize returns the block bytes 55 | // includes all the data of the block 56 | func (h *Header) Serialize() ([]byte, error) { 57 | s := bufferutil.NewSerializer(nil) 58 | 59 | if err := h.serializeHeader(s, false); err != nil { 60 | return nil, err 61 | } 62 | 63 | return s.Bytes(), nil 64 | } 65 | 66 | // Hash gets the bytes with SerializeForHash and DoubleHash the bytes 67 | func (h *Header) Hash() (chainhash.Hash, error) { 68 | bytes, err := h.SerializeForHash() 69 | if err != nil { 70 | return chainhash.Hash{}, err 71 | } 72 | 73 | return chainhash.DoubleHashH(bytes), nil 74 | } 75 | 76 | func (h *Header) serializeHeader( 77 | s *bufferutil.Serializer, forHash bool, 78 | ) error { 79 | version := h.Version 80 | if h.ExtData.IsDyna { 81 | version |= DYNAFED_HF_MASK 82 | } 83 | 84 | err := s.WriteUint32(version) 85 | if err != nil { 86 | return err 87 | } 88 | 89 | if err := s.WriteSlice(h.PrevBlockHash); err != nil { 90 | return err 91 | } 92 | 93 | if err := s.WriteSlice(h.MerkleRoot); err != nil { 94 | return err 95 | } 96 | 97 | if err := s.WriteUint32(h.Timestamp); err != nil { 98 | return err 99 | } 100 | 101 | if err := s.WriteUint32(h.Height); err != nil { 102 | return err 103 | } 104 | 105 | if err := h.ExtData.serialize(s, forHash); err != nil { 106 | return err 107 | } 108 | 109 | return nil 110 | } 111 | 112 | func (e *ExtData) serialize(s *bufferutil.Serializer, forHash bool) error { 113 | if e.IsDyna { 114 | err := e.DynamicFederation.serialize(s, forHash) 115 | if err != nil { 116 | return err 117 | } 118 | } else { 119 | err := e.Proof.serialize(s, forHash) 120 | if err != nil { 121 | return err 122 | } 123 | } 124 | 125 | return nil 126 | } 127 | 128 | func (p *Proof) serialize(s *bufferutil.Serializer, forHash bool) error { 129 | if err := s.WriteVarInt(uint64(len(p.Challenge))); err != nil { 130 | return err 131 | } 132 | 133 | if err := s.WriteSlice(p.Challenge); err != nil { 134 | return err 135 | } 136 | 137 | if !forHash { 138 | if err := s.WriteVarInt(uint64(len(p.Solution))); err != nil { 139 | return err 140 | } 141 | 142 | if err := s.WriteSlice(p.Solution); err != nil { 143 | return err 144 | } 145 | } 146 | 147 | return nil 148 | } 149 | 150 | func (d *DynamicFederation) serialize( 151 | s *bufferutil.Serializer, forHash bool, 152 | ) error { 153 | if d.Current == nil { 154 | if err := s.WriteUint8(null); err != nil { 155 | return err 156 | } 157 | } else { 158 | if err := d.Current.serialize(s); err != nil { 159 | return err 160 | } 161 | } 162 | 163 | if d.Proposed == nil { 164 | if err := s.WriteUint8(null); err != nil { 165 | return err 166 | } 167 | } else { 168 | if err := d.Proposed.serialize(s); err != nil { 169 | return err 170 | } 171 | } 172 | 173 | if !forHash { 174 | if err := s.WriteVarInt(uint64(len(d.SignBlockWitness))); err != nil { 175 | return err 176 | } 177 | 178 | for i := 0; i < len(d.SignBlockWitness); i++ { 179 | if err := s.WriteVarInt(uint64(len(d.SignBlockWitness[i]))); err != nil { 180 | return err 181 | } 182 | 183 | if err := s.WriteSlice(d.SignBlockWitness[i]); err != nil { 184 | return err 185 | } 186 | } 187 | } 188 | 189 | return nil 190 | } 191 | 192 | func (d *DynamicFederationParams) serialize(s *bufferutil.Serializer) error { 193 | if d.CompactParams == nil && d.FullParams == nil { 194 | if err := s.WriteUint8(null); err != nil { 195 | return err 196 | } 197 | } 198 | 199 | if d.CompactParams != nil { 200 | if err := s.WriteUint8(compact); err != nil { 201 | return err 202 | } 203 | 204 | if err := d.CompactParams.serialize(s); err != nil { 205 | return err 206 | } 207 | } 208 | 209 | if d.FullParams != nil { 210 | if err := s.WriteUint8(full); err != nil { 211 | return err 212 | } 213 | 214 | if err := d.FullParams.serialize(s); err != nil { 215 | return err 216 | } 217 | } 218 | 219 | return nil 220 | } 221 | 222 | func (c *CompactParams) serialize(s *bufferutil.Serializer) error { 223 | if err := s.WriteVarInt(uint64(len(c.SignBlockScript))); err != nil { 224 | return err 225 | } 226 | 227 | if err := s.WriteSlice(c.SignBlockScript); err != nil { 228 | return err 229 | } 230 | 231 | if err := s.WriteUint32(c.SignBlockWitnessLimit); err != nil { 232 | return err 233 | } 234 | 235 | if err := s.WriteSlice(c.ElidedRoot); err != nil { 236 | return err 237 | } 238 | 239 | return nil 240 | } 241 | 242 | func (f *FullParams) serialize(s *bufferutil.Serializer) error { 243 | if err := s.WriteVarInt(uint64(len(f.SignBlockScript))); err != nil { 244 | return err 245 | } 246 | 247 | if err := s.WriteSlice(f.SignBlockScript); err != nil { 248 | return err 249 | } 250 | 251 | if err := s.WriteUint32(f.SignBlockWitnessLimit); err != nil { 252 | return err 253 | } 254 | 255 | if err := s.WriteVarInt(uint64(len(f.FedpegProgram))); err != nil { 256 | return err 257 | } 258 | 259 | if err := s.WriteSlice(f.FedpegProgram); err != nil { 260 | return err 261 | } 262 | 263 | if err := s.WriteVarInt(uint64(len(f.FedpegScript))); err != nil { 264 | return err 265 | } 266 | 267 | if err := s.WriteSlice(f.FedpegScript); err != nil { 268 | return err 269 | } 270 | 271 | if err := s.WriteVarInt(uint64(len(f.ExtensionSpace))); err != nil { 272 | return err 273 | } 274 | 275 | for i := 0; i < len(f.ExtensionSpace); i++ { 276 | if err := s.WriteVarInt(uint64(len(f.ExtensionSpace[i]))); err != nil { 277 | return err 278 | } 279 | 280 | if err := s.WriteSlice(f.ExtensionSpace[i]); err != nil { 281 | return err 282 | } 283 | } 284 | 285 | return nil 286 | } 287 | -------------------------------------------------------------------------------- /block/serialize_test.go: -------------------------------------------------------------------------------- 1 | package block 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "io/ioutil" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestBlockSerialization(t *testing.T) { 13 | file, err := ioutil.ReadFile("testdata/deserialize.json") 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | var tests []map[string]interface{} 18 | err = json.Unmarshal(file, &tests) 19 | 20 | for _, v := range tests { 21 | testName := v["name"].(string) 22 | t.Run(testName, func(t *testing.T) { 23 | block, err := NewFromHex(v["hex"].(string)) 24 | if err != nil { 25 | t.Errorf("test: %v, err: %v", testName, err) 26 | } 27 | 28 | serializeBlock, err := block.SerializeBlock() 29 | if err != nil { 30 | t.Error(err) 31 | } 32 | 33 | assert.Equal(t, v["hex"].(string), hex.EncodeToString(serializeBlock)) 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /block/testdata/hash.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "regtest genesis block", 4 | "blockHeaderHex": "0100000000000000000000000000000000000000000000000000000000000000000000002e4bf9e14086c32e38baa4fc6caf94ae73d189468d97153f131bc2489b1941bbdae5494d00000000015100", 5 | "hash": "00902a6b70c2ca83b5d9c815d96a0e2f4202179316970d14ea1847dae5b1ca21" 6 | }, 7 | { 8 | "name": "liquid block #1594956", 9 | "blockHeaderHex": "000000a002e54bf28071f3613047b9fea9ab2c84762edc156c71bb79ec321a16afda9999a68aadf4d319e14a483f82e8faf830ce05e08f26c91c3ea482f52f2b4a71d8a256fba4614c56180001220020e51211e91d9cf4aec3bdc370a0303acde5d24baedb12235fdd2786885069d91c880500007e755ded4e96bdcc0f5db0f6d21a46e3c91ab474f1a8c95a04ad3452e8600fff000d00483045022100b2bcad5542a907b154359881dc2e703546ed12adb5c9a02404897e8b152909d1022054857306497141cba1eaf815fec70f4fb63abb836245e9203fbbdee0b42123fc01483045022100a79f3e9e786b96f191ea02ad4b7bcc25f5218aef7537f1f5d83a79b686657a570220350e72574fe3e2272acf3b593b6f18318c594adb78960888447e8b46c28dbd0001473044022043424fd4511e7baba204ec2acfac336d6719a190122a937a689458165ed8859502203d14667b7df6b81aa70ad9082d371f1f66888305ec521fe797f6382356a29a0b01483045022100ed1ce0a0072bbdd1075cb9d30b99c892154a79690e910379cccc0e69ac674b0f022036b7325438e91bbd8c41425248fe26aa25d514ce4b375cee6c0318ed2e68e3f101483045022100c70c4bf23db0ca908811bbba6079b974120da71c32b8d51904c2012d6085acdc0220287cf223fa863263907d8359b8d80c4914c2cb41f6bae7d67e88f511304749cb0147304402206924ba6afbfc96b232c30eb634c6aae55950082b0b3029019dd1db791a47e4d4022044f3442d959355a0ab8d44629478eef8bc36ddc1cb23d9aec2e98dbe93cd6d9f01483045022100a437b5997b59bb935b960d1db864321fef33b160dd878cdcc1263fd3d8fc82ea02206a7b4e9461fc6cf78514d4885ca59ba4c01344ce3e601b03b94e7945d06c3f6401473044022007c8f2b7248f07cb958a9a46486d340d9b5ce64ec40fb43b303d38455d449ed30220787ca0802ebfa3fe5bcc01cbe839e79cf689d8c20a9be5116e893468d73a10a201473044022035d937c11eab9af89d41b8a25a5f6fceebf54ecc740c4e8323078d1ec3fbf5a70220012001594df99aef7082a757c5843db1a47e04b8c842c849c79b12144ede85a601473044022060dcf30cceffe435815a38997043eb046e3af61d17512ab47dc26fee8e4becca02202c94be595068df01ae8312acf459bed85b779b4b0008eedbf33104b4e939bd5901483045022100854cd4df74c6ff394c6291765bc8c1717d602fe97e7e07470da4d84f59736e300220743c7f7165a6c8ccb452173a2ad4b6b0198d0e2f8e20a5d46c915e8f74d5b83d01fd01025b21026a2a106ec32c8a1e8052e5d02a7b0a150423dbd9b116fc48d46630ff6e6a05b92102791646a8b49c2740352b4495c118d876347bf47d0551c01c4332fdc2df526f1a2102888bda53a424466b0451627df22090143bbf7c060e9eacb1e38426f6b07f2ae12102aee8967150dee220f613de3b239320355a498808084a93eaf39a34dcd62024852102d46e9259d0a0bb2bcbc461a3e68f34adca27b8d08fbe985853992b4b104e27412102e9944e35e5750ab621e098145b8e6cf373c273b7c04747d1aa020be0af40ccd62102f9a9d4b10a6d6c56d8c955c547330c589bb45e774551d46d415e51cd9ad5116321033b421566c124dfde4db9defe4084b7aa4e7f36744758d92806b8f72c2e943309210353dcc6b4cf6ad28aceb7f7b2db92a4bf07ac42d357adf756f3eca790664314b621037f55980af0455e4fb55aad9b85a55068bb6dc4740ea87276dc693f4598db45fa210384001daa88dabd23db878dbb1ce5b4c2a5fa72c3113e3514bf602325d0c37b8e21039056d089f2fe72dbc0a14780b4635b0dc8a1b40b7a59106325dd1bc45cc70493210397ab8ea7b0bf85bc7fc56bb27bf85e75502e94e76a6781c409f3f2ec3d1122192103b00e3b5b77884bf3cae204c4b4eac003601da75f96982ffcb3dcb29c5ee419b92103c1f3c0874cfe34b8131af34699589aacec4093399739ae352e8a46f80a6f68375fae", 10 | "hash": "50c2303db2cd11213e357856e2d351bc6ab2a65b69b10643e082175c8c11e8a5" 11 | }, 12 | { 13 | "name": "liquid block #15994987", 14 | "blockHeaderHex": "000000a09d08cb00c742c01cb05980eb3ec8f257b392230b8074dcb473f5b0ceee893d2ea13fdd6f4d4931e6de33e858ea58b8d1d6578e797657b6f74d5a463169ee1c7a9a02a5616b56180001220020e51211e91d9cf4aec3bdc370a0303acde5d24baedb12235fdd2786885069d91c880500007e755ded4e96bdcc0f5db0f6d21a46e3c91ab474f1a8c95a04ad3452e8600fff000d0048304502210080fa223bb9e0c3c939a467b3f5e81d5d153a94da9c75d5fceb2f7901b0ab9d920220338da3f8a5dddb7016fba0ed08430e4ba61dc3c45a49ec94234cdc7a3e5d4f490148304502210087c4e2228fa27ebc5d3c0fd8bc92da02c22a4035196fbab92ddebee7eee8d46e0220386bd4dd792bd18a48c9787759fba9886c2dceeeb65578242f9263701205e366014730440220463ea51b3c3015a1b175456ad1abe07bbaae49d714b4f5b4d19a73756053cf98022010d3d14958e134e733c9612f30a2f53b90f08ce763ef8923222204a2fc35402601473044022044dff40f53fc056b60153ae1d83e9f8f96e1e20071562a26d417fc776264bac102203d6847018873f8b5a47da3592caa4eadf3735cb8d77aa9e3839cdf15e70d9ae2014730440220284876935b0da56022340fbae6034ffbb2cc8d52ac34c249b5d88521709d37480220446c8f2a5061cd8d5d982505898fa0ff4b4226a190bc810fc2e97a91c7bd4f8a01473044022067caacacd92c34509ca55c37a06a9ff419fe74697fb46a9702f516afd7ce530602200913d379b9a133ba9c50bdf9a536e96eaf204f7f0d33ed708aa90b4a0e34f4500147304402205c7ee346620047810f4b8be7e2d3b6dfd30eea0a4d438b0a9faf855de2b34bc1022016a4f33275ba32ff3d8059c14050b99e9ed96e7b089ac684a6b40ef002986357014730440220602ef00ddd12a66d38ce294b8f484c1958dcdf5039e24b046b9b5f763919f1b902207375a4fce037a852c18b06c933b1e5adeabcee3fbddd40c5c01267d771c411c901473044022057a679242e1aca3a3a7875acd2211240f49186eef57af98673bf11e82aef22b002207ea9bb1d566701362509e826fff6b34094f64cd9423060e9d0d32cbce54c2c8b0148304502210094707c5d4420101a7c29e11b090492138f3150b62fa53fec1733c8eb4281d3650220697a741b19f2900f897a766dda16f2fe196579b9394bd099c905d246b91b30a30147304402202d58d736afabb41154826b0e527c313c83bea18d61fe11e3c0f9047536e194f3022033f465824d3e5de22df9229fd52420bce0aa1674a0f985c0a3937d1ff80397b301fd01025b21026a2a106ec32c8a1e8052e5d02a7b0a150423dbd9b116fc48d46630ff6e6a05b92102791646a8b49c2740352b4495c118d876347bf47d0551c01c4332fdc2df526f1a2102888bda53a424466b0451627df22090143bbf7c060e9eacb1e38426f6b07f2ae12102aee8967150dee220f613de3b239320355a498808084a93eaf39a34dcd62024852102d46e9259d0a0bb2bcbc461a3e68f34adca27b8d08fbe985853992b4b104e27412102e9944e35e5750ab621e098145b8e6cf373c273b7c04747d1aa020be0af40ccd62102f9a9d4b10a6d6c56d8c955c547330c589bb45e774551d46d415e51cd9ad5116321033b421566c124dfde4db9defe4084b7aa4e7f36744758d92806b8f72c2e943309210353dcc6b4cf6ad28aceb7f7b2db92a4bf07ac42d357adf756f3eca790664314b621037f55980af0455e4fb55aad9b85a55068bb6dc4740ea87276dc693f4598db45fa210384001daa88dabd23db878dbb1ce5b4c2a5fa72c3113e3514bf602325d0c37b8e21039056d089f2fe72dbc0a14780b4635b0dc8a1b40b7a59106325dd1bc45cc70493210397ab8ea7b0bf85bc7fc56bb27bf85e75502e94e76a6781c409f3f2ec3d1122192103b00e3b5b77884bf3cae204c4b4eac003601da75f96982ffcb3dcb29c5ee419b92103c1f3c0874cfe34b8131af34699589aacec4093399739ae352e8a46f80a6f68375fae", 15 | "hash": "59dfa0c8a9420d7e79795d65eda09972dc1cbe9eb24f7639222b2f655fc999d6" 16 | }, 17 | { 18 | "name": "liquid testnet block #106432", 19 | "blockHeaderHex": "000000a08619ccdad6d1e95d515cdd3a4bc5098950aaf4c63b435ece4774d436f14b16c34de83159b4cb356028a3ba0799316b6ba82229c8d7920040826fa9c82b88b74108d3a561c09f010001220020e9e4117540f7f23b3edd7c2cad660a17fb33c7959b8c37cf61d92b189133929a96000000fbee9cea00d8efdc49cfbec328537e0d7032194de6ebf3cf42e5c05bb89a08b100030047304402207b7632bd61e4bf2071486938e74fcd855bd56941ddfaad43faabd2bae7940c3f02201fbe57eefeee9f8e5f24ec197f303cf1f3d06c2e5582056d2d948163203a3cef012551210217e403ddb181872c32a0cd468c710040b2f53d8cac69f18dad07985ee37e9a7151ae", 20 | "hash": "5a19865c9200fbde2974c62e2be9a2c4a562945ec2925505c53262fdb0814a04" 21 | } 22 | ] -------------------------------------------------------------------------------- /confidential/confidential_nocgo.go: -------------------------------------------------------------------------------- 1 | //go:build !cgo 2 | 3 | package confidential 4 | 5 | import ( 6 | "errors" 7 | 8 | "github.com/vulpemventures/go-elements/transaction" 9 | ) 10 | 11 | var errNoCGO = errors.New("confidential transactions require CGO") 12 | 13 | const ( 14 | maxSurjectionTargets = 3 15 | maxScriptSize = 10000 16 | ) 17 | 18 | var ( 19 | Zero = make([]byte, 32) 20 | ) 21 | 22 | // UnblindOutputResult is the type returned by the functions that unblind tx 23 | // outs. It contains the unblinded asset and value and also the respective 24 | // blinding factors. 25 | type UnblindOutputResult struct { 26 | Value uint64 27 | Asset []byte 28 | ValueBlindingFactor []byte 29 | AssetBlindingFactor []byte 30 | } 31 | 32 | // UnblindIssuanceResult is the type returned by the functions that unblind tx 33 | // issuances. It contains the unblinded asset and token issuances. 34 | type UnblindIssuanceResult struct { 35 | Asset *UnblindOutputResult 36 | Token *UnblindOutputResult 37 | } 38 | 39 | // FinalValueBlindingFactorArgs is the type used to pass arguments to the 40 | // FinalValueBlindingFactor function. 41 | type FinalValueBlindingFactorArgs struct { 42 | InValues []uint64 43 | OutValues []uint64 44 | InGenerators [][]byte 45 | OutGenerators [][]byte 46 | InFactors [][]byte 47 | OutFactors [][]byte 48 | } 49 | 50 | // RangeProofArgs is the type used to pass arguments to the RangeProof function. 51 | type RangeProofArgs struct { 52 | Value uint64 53 | Nonce [32]byte 54 | Asset []byte 55 | AssetBlindingFactor []byte 56 | ValueBlindFactor [32]byte 57 | ValueCommit []byte 58 | ScriptPubkey []byte 59 | Exp int 60 | MinBits int 61 | } 62 | 63 | // SurjectionProofArgs is the type used to pass arguments to the SurjectionProof function. 64 | type SurjectionProofArgs struct { 65 | OutputAsset []byte 66 | OutputAssetBlindingFactor []byte 67 | InputAssets [][]byte 68 | InputAssetBlindingFactors [][]byte 69 | Seed []byte 70 | NumberOfTargets int 71 | } 72 | 73 | // VerifySurjectionProofArgs is the type used to pass arguments to the VerifySurjectionProof function. 74 | type VerifySurjectionProofArgs struct { 75 | InputAssets [][]byte 76 | InputAssetBlindingFactors [][]byte 77 | OutputAsset []byte 78 | OutputAssetBlindingFactor []byte 79 | Proof []byte 80 | } 81 | 82 | // NonceHash method generates hashed secret based on ecdh. 83 | func NonceHash(pubKey, privKey []byte) ([32]byte, error) { 84 | return [32]byte{}, errNoCGO 85 | } 86 | 87 | // UnblindOutputWithKey method unblinds a confidential transaction output with 88 | // the given blinding private key. 89 | func UnblindOutputWithKey( 90 | out *transaction.TxOutput, blindKey []byte, 91 | ) (*UnblindOutputResult, error) { 92 | return nil, errNoCGO 93 | } 94 | 95 | // UnblindOutputWithNonce method unblinds a confidential transaction output with 96 | // the given nonce. 97 | func UnblindOutputWithNonce( 98 | out *transaction.TxOutput, nonce []byte, 99 | ) (*UnblindOutputResult, error) { 100 | return nil, errNoCGO 101 | } 102 | 103 | // UnblindIssuance method unblinds a confidential transaction issuance with 104 | // the given blinding private keys. 105 | func UnblindIssuance( 106 | in *transaction.TxInput, blindKeys [][]byte, 107 | ) (*UnblindIssuanceResult, error) { 108 | return nil, errNoCGO 109 | } 110 | 111 | // FinalValueBlindingFactor method calculates the final value blinding factor. 112 | func FinalValueBlindingFactor( 113 | args FinalValueBlindingFactorArgs, 114 | ) ([32]byte, error) { 115 | return [32]byte{}, errNoCGO 116 | } 117 | 118 | // AssetCommitment method creates an asset commitment. 119 | func AssetCommitment(asset, factor []byte) ([]byte, error) { 120 | return nil, errNoCGO 121 | } 122 | 123 | // ValueCommitment method creates a value commitment. 124 | func ValueCommitment(value uint64, generator, factor []byte) ([]byte, error) { 125 | return nil, errNoCGO 126 | } 127 | 128 | // RangeProof method creates a range proof. 129 | func RangeProof(args RangeProofArgs) ([]byte, error) { 130 | return nil, errNoCGO 131 | } 132 | 133 | // VerifyRangeProof method verifies a range proof. 134 | func VerifyRangeProof(valueCommitment, assetCommitment, script, proof []byte) bool { 135 | return false 136 | } 137 | 138 | // SurjectionProof method creates a surjection proof. 139 | func SurjectionProof(args SurjectionProofArgs) ([]byte, bool) { 140 | return nil, false 141 | } 142 | 143 | // VerifySurjectionProof method verifies a surjection proof. 144 | func VerifySurjectionProof(args VerifySurjectionProofArgs) bool { 145 | return false 146 | } 147 | 148 | // CalculateScalarOffset calculates the scalar offset for a transaction. 149 | // This is a no-op implementation when CGO is disabled. 150 | func CalculateScalarOffset( 151 | amount uint64, assetBlinder, valueBlinder []byte, 152 | ) ([]byte, error) { 153 | return nil, errNoCGO 154 | } 155 | 156 | // SubtractScalars subtracts two scalars. 157 | // This is a no-op implementation when CGO is disabled. 158 | func SubtractScalars(a []byte, b []byte) ([]byte, error) { 159 | return nil, errNoCGO 160 | } 161 | 162 | // ComputeAndAddToScalarOffset computes and adds to the scalar offset. 163 | // This is a no-op implementation when CGO is disabled. 164 | func ComputeAndAddToScalarOffset( 165 | scalar []byte, value uint64, assetBlinder, valueBlinder []byte, 166 | ) ([]byte, error) { 167 | return nil, errNoCGO 168 | } 169 | 170 | // CreateBlindValueProof creates a blind value proof. 171 | // This is a no-op implementation when CGO is disabled. 172 | func CreateBlindValueProof( 173 | rng func() ([]byte, error), 174 | valueBlinder []byte, amount uint64, valueCommitment, assetCommitment []byte, 175 | ) ([]byte, error) { 176 | return nil, errNoCGO 177 | } 178 | 179 | // CreateBlindAssetProof creates a blind asset proof. 180 | // This is a no-op implementation when CGO is disabled. 181 | func CreateBlindAssetProof( 182 | asset, assetCommitment, assetBlinder []byte, 183 | ) ([]byte, error) { 184 | return nil, errNoCGO 185 | } 186 | 187 | // VerifyBlindValueProof verifies a blind value proof. 188 | // This is a no-op implementation when CGO is disabled. 189 | func VerifyBlindValueProof( 190 | value uint64, valueCommitment, assetCommitment, proof []byte, 191 | ) bool { 192 | return false 193 | } 194 | 195 | // VerifyBlindAssetProof verifies a blind asset proof. 196 | // This is a no-op implementation when CGO is disabled. 197 | func VerifyBlindAssetProof(asset, assetCommitment, proof []byte) bool { 198 | return false 199 | } 200 | -------------------------------------------------------------------------------- /confidential/zkp_generator_nocgo.go: -------------------------------------------------------------------------------- 1 | //go:build !cgo 2 | 3 | package confidential 4 | 5 | import ( 6 | "github.com/vulpemventures/go-elements/psetv2" 7 | "github.com/vulpemventures/go-elements/transaction" 8 | ) 9 | 10 | // zkpGenerator is the type that provides methods to generate zero-knowledge proofs. 11 | type zkpGenerator struct { 12 | masterBlindingKey interface{} 13 | inBlindingKeys [][]byte 14 | rng func() ([]byte, error) 15 | ownedInputs map[uint32]psetv2.OwnedInput 16 | } 17 | 18 | // NewZKPGeneratorFromMasterBlindingKey creates a new zkpGenerator from a master blinding key. 19 | func NewZKPGeneratorFromMasterBlindingKey( 20 | masterBlindingKey []byte, opts interface{}, 21 | ) (*zkpGenerator, error) { 22 | return nil, errNoCGO 23 | } 24 | 25 | // NewZKPGeneratorFromBlindingKeys creates a new zkpGenerator from blinding keys. 26 | func NewZKPGeneratorFromBlindingKeys( 27 | inBlindingKeys [][]byte, opts interface{}, 28 | ) *zkpGenerator { 29 | return &zkpGenerator{} 30 | } 31 | 32 | // ComputeAndAddToScalarOffset computes and adds to the scalar offset. 33 | // This is a no-op implementation when CGO is disabled. 34 | func (g *zkpGenerator) ComputeAndAddToScalarOffset( 35 | scalar []byte, value uint64, assetBlinder, valueBlinder []byte, 36 | ) ([]byte, error) { 37 | return nil, errNoCGO 38 | } 39 | 40 | // SubtractScalars subtracts two scalars. 41 | // This is a no-op implementation when CGO is disabled. 42 | func (g *zkpGenerator) SubtractScalars(a, b []byte) ([]byte, error) { 43 | return nil, errNoCGO 44 | } 45 | 46 | // LastValueCommitment creates a value commitment. 47 | // This is a no-op implementation when CGO is disabled. 48 | func (g *zkpGenerator) LastValueCommitment( 49 | value uint64, asset, blinder []byte, 50 | ) ([]byte, error) { 51 | return nil, errNoCGO 52 | } 53 | 54 | // LastBlindValueProof creates a blind value proof. 55 | // This is a no-op implementation when CGO is disabled. 56 | func (g *zkpGenerator) LastBlindValueProof( 57 | value uint64, valueCommitment, assetCommitment, blinder []byte, 58 | ) ([]byte, error) { 59 | return nil, errNoCGO 60 | } 61 | 62 | // LastValueRangeProof creates a range proof. 63 | // This is a no-op implementation when CGO is disabled. 64 | func (g *zkpGenerator) LastValueRangeProof( 65 | value uint64, asset, assetBlinder, valueCommitment, valueBlinder, 66 | scriptPubkey, nonce []byte, 67 | ) ([]byte, error) { 68 | return nil, errNoCGO 69 | } 70 | 71 | // UnblindInputs unblinds inputs. 72 | // This is a no-op implementation when CGO is disabled. 73 | func (g *zkpGenerator) UnblindInputs( 74 | p *psetv2.Pset, inputIndexes []uint32, 75 | ) ([]psetv2.OwnedInput, error) { 76 | return nil, errNoCGO 77 | } 78 | 79 | // BlindIssuances blinds issuances. 80 | // This is a no-op implementation when CGO is disabled. 81 | func (g *zkpGenerator) BlindIssuances( 82 | p *psetv2.Pset, blindingKeysByIndex map[uint32][]byte, 83 | ) ([]psetv2.InputIssuanceBlindingArgs, error) { 84 | return nil, errNoCGO 85 | } 86 | 87 | // BlindOutputs blinds outputs. 88 | // This is a no-op implementation when CGO is disabled. 89 | func (g *zkpGenerator) BlindOutputs( 90 | p *psetv2.Pset, outputIndexes []uint32, 91 | ) ([]psetv2.OutputBlindingArgs, error) { 92 | return nil, errNoCGO 93 | } 94 | 95 | // unblindOutput unblinds an output. 96 | // This is a no-op implementation when CGO is disabled. 97 | func (g *zkpGenerator) unblindOutput( 98 | out *transaction.TxOutput, 99 | ) (*psetv2.OwnedInput, error) { 100 | return nil, errNoCGO 101 | } 102 | -------------------------------------------------------------------------------- /confidential/zkp_validator.go: -------------------------------------------------------------------------------- 1 | //go:build cgo 2 | 3 | package confidential 4 | 5 | type zkpValidator struct{} 6 | 7 | func NewZKPValidator() *zkpValidator { 8 | return &zkpValidator{} 9 | } 10 | 11 | func (v *zkpValidator) VerifyValueRangeProof( 12 | valueCommitment, assetCommitment, script, proof []byte, 13 | ) bool { 14 | return VerifyRangeProof(valueCommitment, assetCommitment, script, proof) 15 | } 16 | 17 | func (v *zkpValidator) VerifyAssetSurjectionProof( 18 | inAssets, inAssetBlinders [][]byte, 19 | outAsset, outAssetBlinder, proof []byte, 20 | ) bool { 21 | return VerifySurjectionProof(VerifySurjectionProofArgs{ 22 | InputAssets: inAssets, 23 | InputAssetBlindingFactors: inAssetBlinders, 24 | OutputAsset: outAsset, 25 | OutputAssetBlindingFactor: outAssetBlinder, 26 | Proof: proof, 27 | }) 28 | } 29 | 30 | func (v *zkpValidator) VerifyBlindValueProof( 31 | value uint64, valueCommitment, assetCommitment, proof []byte, 32 | ) bool { 33 | return VerifyBlindValueProof(value, valueCommitment, assetCommitment, proof) 34 | } 35 | 36 | func (v *zkpValidator) VerifyBlindAssetProof( 37 | asset, assetCommitment, proof []byte, 38 | ) bool { 39 | return VerifyBlindAssetProof(asset, assetCommitment, proof) 40 | } 41 | -------------------------------------------------------------------------------- /confidential/zkp_validator_nocgo.go: -------------------------------------------------------------------------------- 1 | //go:build !cgo 2 | 3 | package confidential 4 | 5 | // zkpValidator is the type that provides methods to validate zero-knowledge proofs. 6 | type zkpValidator struct{} 7 | 8 | // NewZKPValidator creates a new zkpValidator. 9 | func NewZKPValidator() *zkpValidator { 10 | return &zkpValidator{} 11 | } 12 | 13 | // VerifyValueRangeProof verifies a range proof. 14 | // This is a no-op implementation when CGO is disabled. 15 | func (v *zkpValidator) VerifyValueRangeProof( 16 | valueCommitment, assetCommitment, script, proof []byte, 17 | ) bool { 18 | return false 19 | } 20 | 21 | // VerifyAssetSurjectionProof verifies a surjection proof. 22 | // This is a no-op implementation when CGO is disabled. 23 | func (v *zkpValidator) VerifyAssetSurjectionProof( 24 | inAssets, inAssetBlinders [][]byte, 25 | outAsset, outAssetBlinder, proof []byte, 26 | ) bool { 27 | return false 28 | } 29 | 30 | // VerifyBlindValueProof verifies a blind value proof. 31 | // This is a no-op implementation when CGO is disabled. 32 | func (v *zkpValidator) VerifyBlindValueProof( 33 | value uint64, valueCommitment, assetCommitment, proof []byte, 34 | ) bool { 35 | return false 36 | } 37 | 38 | // VerifyBlindAssetProof verifies a blind asset proof. 39 | // This is a no-op implementation when CGO is disabled. 40 | func (v *zkpValidator) VerifyBlindAssetProof( 41 | asset, assetCommitment, proof []byte, 42 | ) bool { 43 | return false 44 | } 45 | -------------------------------------------------------------------------------- /descriptor/descriptor.go: -------------------------------------------------------------------------------- 1 | package descriptor 2 | 3 | // Wallet is interface to be implemented by various descriptor wallets 4 | type Wallet interface { 5 | // Type returns type of descriptor wallet (e.g. wpkh, wsh etc.) 6 | Type() string 7 | // IsRange returns true if wallet description is of type range which means 8 | //that key expression provides master key and requires more scripts to be generated 9 | IsRange() bool 10 | // Script generates new script, or range of scripts depending on wallet description 11 | //it returns ScriptResponse which holds script and its derivation path in case wallet descriptor is range 12 | //if it isn't derivation path will be nil 13 | //wits ScriptOpts pkg user can specify how many scripts should be generated in case of 14 | //range wallet descriptor, it also can specify exact index 15 | Script(opts *ScriptOpts) ([]ScriptResponse, error) 16 | } 17 | 18 | // ScriptResponse defines response for Script func 19 | type ScriptResponse struct { 20 | DerivationPath []uint32 21 | Script []byte 22 | } 23 | 24 | // ScriptOpts defines options for range type of descriptor wallet 25 | type ScriptOpts struct { 26 | index *uint32 27 | numOfScripts *int 28 | } 29 | 30 | // WithIndex defines exact child index for which script should be generated for 31 | // range wallet descriptor 32 | func WithIndex(index uint32) *ScriptOpts { 33 | return &ScriptOpts{ 34 | index: &index, 35 | } 36 | } 37 | 38 | // WithRange defines how many scripts should be generated for range wallet descriptor 39 | func WithRange(numOfScrips int) *ScriptOpts { 40 | return &ScriptOpts{ 41 | numOfScripts: &numOfScrips, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /descriptor/util.go: -------------------------------------------------------------------------------- 1 | package descriptor 2 | 3 | import ( 4 | "crypto/sha256" 5 | "hash" 6 | 7 | "golang.org/x/crypto/ripemd160" 8 | ) 9 | 10 | func calcHash(buf []byte, hasher hash.Hash) []byte { 11 | hasher.Write(buf) 12 | return hasher.Sum(nil) 13 | } 14 | 15 | func hash160(buf []byte) []byte { 16 | return calcHash(calcHash(buf, sha256.New()), ripemd160.New()) 17 | } 18 | -------------------------------------------------------------------------------- /descriptor/wpkh.go: -------------------------------------------------------------------------------- 1 | package descriptor 2 | 3 | import ( 4 | "encoding/hex" 5 | "errors" 6 | 7 | "github.com/btcsuite/btcd/btcutil" 8 | "github.com/btcsuite/btcd/btcutil/hdkeychain" 9 | 10 | "github.com/btcsuite/btcd/txscript" 11 | ) 12 | 13 | const ( 14 | // numOfScripts to be generated in case wpkh wallet is "range" 15 | numOfScripts = 100 16 | ) 17 | 18 | type WpkhWallet struct { 19 | keyInfo *keyInfo 20 | } 21 | 22 | func newWpkhWalletFromKeyInfo(info *keyInfo) WpkhWallet { 23 | return WpkhWallet{ 24 | keyInfo: info, 25 | } 26 | } 27 | 28 | func (w WpkhWallet) Type() string { 29 | return "wpkh" 30 | } 31 | 32 | func (w WpkhWallet) IsRange() bool { 33 | if w.keyInfo.extendedKeyInfo != nil { 34 | return w.keyInfo.extendedKeyInfo.isRange 35 | } 36 | 37 | return false 38 | } 39 | 40 | func (w WpkhWallet) Script(opts *ScriptOpts) ([]ScriptResponse, error) { 41 | response := make([]ScriptResponse, 0) 42 | 43 | var ( 44 | numOfScriptsToBeGenerated = 1 45 | index uint32 = 0 46 | generateMoreScripts = false 47 | ) 48 | 49 | if w.IsRange() { 50 | numOfScriptsToBeGenerated = numOfScripts 51 | if opts != nil { 52 | if opts.numOfScripts != nil { 53 | generateMoreScripts = true 54 | numOfScriptsToBeGenerated = *opts.numOfScripts 55 | } else { 56 | index = *opts.index 57 | } 58 | } 59 | } 60 | 61 | if w.keyInfo.pubKey != nil { 62 | pubKeyBytes, err := hex.DecodeString(*w.keyInfo.pubKey) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | script, err := wpkhScriptFromBytes(pubKeyBytes) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | response = append(response, ScriptResponse{ 73 | Script: script, 74 | }) 75 | 76 | return response, nil 77 | } 78 | 79 | if w.keyInfo.wif != nil { 80 | wif, err := btcutil.DecodeWIF(*w.keyInfo.wif) 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | script, err := wpkhScriptFromBytes(wif.PrivKey.PubKey().SerializeCompressed()) 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | response = append(response, ScriptResponse{ 91 | Script: script, 92 | }) 93 | 94 | return response, nil 95 | } 96 | 97 | if w.keyInfo.extendedKeyInfo != nil { 98 | masterExtKey, err := hdkeychain.NewKeyFromString(w.keyInfo.extendedKeyInfo.key) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | for _, v := range w.keyInfo.extendedKeyInfo.path { 104 | masterExtKey, err = masterExtKey.Derive(v) 105 | if err != nil { 106 | return nil, err 107 | } 108 | } 109 | 110 | if w.keyInfo.extendedKeyInfo.isRange { 111 | if generateMoreScripts { 112 | for i := 0; i < numOfScriptsToBeGenerated; i++ { 113 | childKey, err := masterExtKey.Derive(uint32(i)) 114 | if err != nil { 115 | return nil, err 116 | } 117 | pubKey, err := childKey.ECPubKey() 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | script, err := wpkhScriptFromBytes(pubKey.SerializeCompressed()) 123 | if err != nil { 124 | return nil, err 125 | } 126 | 127 | response = append(response, ScriptResponse{ 128 | DerivationPath: w.derivationPath(uint32(i)), 129 | Script: script, 130 | }) 131 | } 132 | } else { 133 | childKey, err := masterExtKey.Derive(index) 134 | if err != nil { 135 | return nil, err 136 | } 137 | pubKey, err := childKey.ECPubKey() 138 | if err != nil { 139 | return nil, err 140 | } 141 | 142 | script, err := wpkhScriptFromBytes(pubKey.SerializeCompressed()) 143 | if err != nil { 144 | return nil, err 145 | } 146 | 147 | response = append(response, ScriptResponse{ 148 | DerivationPath: w.derivationPath(index), 149 | Script: script, 150 | }) 151 | } 152 | } else { 153 | pubKey, err := masterExtKey.ECPubKey() 154 | if err != nil { 155 | return nil, err 156 | } 157 | 158 | script, err := wpkhScriptFromBytes(pubKey.SerializeCompressed()) 159 | if err != nil { 160 | return nil, err 161 | } 162 | 163 | response = append(response, ScriptResponse{ 164 | DerivationPath: w.derivationPath(index), 165 | Script: script, 166 | }) 167 | } 168 | 169 | return response, nil 170 | } 171 | 172 | return nil, errors.New("parser didnt recognised puKey, wif not extended keys in expression") 173 | } 174 | 175 | func (w WpkhWallet) derivationPath(index uint32) []uint32 { 176 | derivationPath := make([]uint32, 0) 177 | if w.keyInfo.keyOrigin != nil { 178 | derivationPath = append(derivationPath, w.keyInfo.keyOrigin.masterKeyFingerprint) 179 | derivationPath = append(derivationPath, w.keyInfo.keyOrigin.path...) 180 | } 181 | if w.keyInfo.extendedKeyInfo != nil { 182 | if len(w.keyInfo.extendedKeyInfo.path) > 0 { 183 | derivationPath = append(derivationPath, w.keyInfo.extendedKeyInfo.path...) 184 | } 185 | } 186 | derivationPath = append(derivationPath, index) 187 | 188 | return derivationPath 189 | } 190 | 191 | func wpkhScriptFromBytes(pubKeyBytes []byte) ([]byte, error) { 192 | pkHash := hash160(pubKeyBytes) 193 | builder := txscript.NewScriptBuilder() 194 | builder.AddOp(txscript.OP_0).AddData(pkHash) 195 | 196 | script, err := builder.Script() 197 | if err != nil { 198 | return nil, err 199 | } 200 | 201 | return script, nil 202 | } 203 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013-2017 The btcsuite developers 2 | // Copyright (c) 2015-2016 The Decred developers 3 | // Copyright (c) 2019-2020 The VulpemVentures developers 4 | 5 | /* 6 | This is a exemplification on how to perform a P2WPKH transaction with Blinded Outputs using the PSET package 7 | with the assistance of vulpemventures/nigiri for funding the address, retrieving the UTXOs and broadcasting. 8 | 9 | You can run this example with this command. 10 | Check its behaviour on Nigiri's Esplora (http://localhost:5001/). 11 | 12 | $ go test ./pset -v -count 1 -run TestBroadcastBlindedTx 13 | 14 | First, we will need a Private Key and derive a Public key from it. We'll follow by generating a P2WPKH address. 15 | 16 | privkey, err := btcec.NewPrivateKey( ) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | pubkey := privkey.PubKey() 21 | p2wpkh := payment.FromPublicKey(pubkey, &network.Regtest, nil) 22 | address, _ := p2wpkh.WitnessPubKeyHash() 23 | 24 | Secondly, we need to fund the address with some UTXOs we can use as inputs. 25 | This functions require Nigiri Chopsticks for the API calls. 26 | 27 | _, err = faucet(address) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | utxos, err := unspents(address) 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | 36 | We need inputs and outputs in order to create a new PSET. 37 | 38 | The transaction will have 1 Input and 3 Outputs. 39 | The input we just funded from the faucet and three outputs. 40 | 41 | One for the ammount we want to send, one for the change and a last one for the fee. 42 | 43 | txInputHash, _ := hex.DecodeString(utxos[0]["txid"].(string)) 44 | txInputHash = elementsutil.ReverseBytes(txInputHash) 45 | txInputIndex := uint32(utxos[0]["vout"].(float64)) 46 | txInput := transaction.NewTxInput(txInputHash, txInputIndex) 47 | 48 | lbtc, _ := hex.DecodeString( 49 | "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225", 50 | ) 51 | lbtc = append([]byte{0x01}, elementsutil.ReverseBytes(lbtc)...) 52 | receiverValue, _ := confidential.SatoshiToElementsValue(60000000) 53 | receiverScript, _ := hex.DecodeString("76a91439397080b51ef22c59bd7469afacffbeec0da12e88ac") 54 | receiverOutput := transaction.NewTxOutput(lbtc, receiverValue[:], receiverScript) 55 | 56 | changeScript := p2wpkh.WitnessScript 57 | changeValue, _ := confidential.SatoshiToElementsValue(39999500) 58 | changeOutput := transaction.NewTxOutput(lbtc, changeValue[:], changeScript) 59 | 60 | This is where the Creator Role takes part. 61 | 62 | We will create a new PSET with all the outputs that need to be blinded first. 63 | 64 | inputs := []*transaction.TxInput{txInput} 65 | outputs := []*transaction.TxOutput{receiverOutput, changeOutput} 66 | p, err := New(inputs, outputs, 2, 0) 67 | if err != nil { 68 | t.Fatal(err) 69 | } 70 | 71 | And then the Updater Role begins its part: 72 | 73 | updater, err := NewUpdater(p) 74 | if err != nil { 75 | t.Fatal(err) 76 | } 77 | 78 | We'll need to add the sighash type and witnessUtxo to the partial input. 79 | 80 | err = updater.AddInSighashType(txscript.SigHashAll, 0) 81 | if err != nil { 82 | t.Fatal(err) 83 | } 84 | witValue, _ := confidential.SatoshiToElementsValue(uint64(utxos[0]["value"].(float64))) 85 | witnessUtxo := transaction.NewTxOutput(lbtc, witValue[:], p2wpkh.WitnessScript) 86 | err = updater.AddInWitnessUtxo(witnessUtxo, 0) 87 | if err != nil { 88 | t.Fatal(err) 89 | } 90 | 91 | Next, we'll Blind the Outputs. This is where the Blinder Role takes part. 92 | 93 | This version of the blinder requires that all the private keys 94 | necessary to unblind all the confidential inputs used must be provided. 95 | 96 | blindingPrivKeys := [][]byte{{}} 97 | 98 | blindingPubKeys := make([][]byte, 0) 99 | pk, err := btcec.NewPrivateKey( ) 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | blindingpubkey := pk.PubKey().SerializeCompressed() 104 | blindingPubKeys = append(blindingPubKeys, blindingpubkey) 105 | pk1, err := btcec.NewPrivateKey( ) 106 | if err != nil { 107 | t.Fatal(err) 108 | } 109 | blindingpubkey1 := pk1.PubKey().SerializeCompressed() 110 | blindingPubKeys = append(blindingPubKeys, blindingpubkey1) 111 | 112 | blinder, err := NewBlinder( 113 | p, 114 | blindingPrivKeys, 115 | blindingPubKeys, 116 | nil, 117 | nil, 118 | ) 119 | if err != nil { 120 | t.Fatal(err) 121 | } 122 | err = blinder.Blind() 123 | if err != nil { 124 | t.Fatal(err) 125 | } 126 | 127 | We'll add the unblinded outputs now, that's only the fee output in this case. 128 | 129 | feeScript := []byte{} 130 | feeValue, _ := confidential.SatoshiToElementsValue(500) 131 | feeOutput := transaction.NewTxOutput(lbtc, feeValue[:], feeScript) 132 | updater.AddOutput(feeOutput) 133 | 134 | After we need a signature for the transaction. 135 | 136 | We'll get the double sha256 hash of the serialization 137 | of the transaction in order to then produce a witness signature for the given inIndex input and append the SigHash. 138 | 139 | witHash := updater.Data.UnsignedTx.HashForWitnessV0(0, p2wpkh.Script, witValue[:], txscript.SigHashAll) 140 | sig, err := privkey.Sign(witHash[:]) 141 | if err != nil { 142 | t.Fatal(err) 143 | } 144 | sigWithHashType := append(sig.Serialize(), byte(txscript.SigHashAll)) 145 | 146 | Now we Update the PSET adding the input signature script and the pubkey. 147 | 148 | The Signer role handles this task as a function Sign of the *Updater type. 149 | 150 | _, err = updater.Sign(0, sigWithHashType, pubkey.SerializeCompressed(), nil, nil) 151 | if err != nil { 152 | t.Fatal(err) 153 | } 154 | 155 | valid, err := updater.Data.ValidateAllSignatures() 156 | if err != nil { 157 | t.Fatal(err) 158 | } 159 | if !valid { 160 | t.Fatal(errors.New("invalid signatures")) 161 | } 162 | 163 | The Finalizer role handles this part of the PSET. We'll combine every input's 164 | PartialSignature into the final input's SignatureScript. 165 | 166 | p = updater.Data 167 | err = FinalizeAll(p) 168 | if err != nil { 169 | t.Fatal(err) 170 | } 171 | 172 | Now the partial transaction is complete, and it's ready to be 173 | extracted from the Pset wrapper. This is implented in the Extractor Role. 174 | 175 | finalTx, err := Extract(p) 176 | if err != nil { 177 | t.Fatal(err) 178 | } 179 | 180 | Finally, our transaction is ready to be serialized and broadcasted to the network. 181 | The Broadcast function require Nigiri Chopsticks for the API call. 182 | 183 | txHex, err := finalTx.ToHex() 184 | if err != nil { 185 | t.Fatal(err) 186 | } 187 | _, err = broadcast(txHex) 188 | if err != nil { 189 | t.Fatal(err) 190 | } 191 | */ 192 | package main 193 | -------------------------------------------------------------------------------- /elementsutil/elementsutil.go: -------------------------------------------------------------------------------- 1 | package elementsutil 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/hex" 7 | "errors" 8 | 9 | "github.com/vulpemventures/go-elements/internal/bufferutil" 10 | ) 11 | 12 | // ValueToBytes method converts Satoshi value to Elements value 13 | func ValueToBytes(val uint64) ([]byte, error) { 14 | unconfPrefix := byte(1) 15 | b := bytes.NewBuffer([]byte{}) 16 | if err := bufferutil.BinarySerializer.PutUint64(b, binary.LittleEndian, val); err != nil { 17 | return nil, err 18 | } 19 | res := append([]byte{unconfPrefix}, ReverseBytes(b.Bytes())...) 20 | return res, nil 21 | } 22 | 23 | // ElementsToSatoshiValue method converts Elements value to Satoshi value 24 | func ValueFromBytes(val []byte) (uint64, error) { 25 | if len(val) != 9 { 26 | return 0, errors.New("invalid elements value lenght") 27 | } 28 | if val[0] != byte(1) { 29 | return 0, errors.New("invalid prefix") 30 | } 31 | reverseValueBuffer := ReverseBytes(val[1:]) 32 | d := bufferutil.NewDeserializer(bytes.NewBuffer(reverseValueBuffer)) 33 | return d.ReadUint64() 34 | } 35 | 36 | func AssetHashFromBytes(buffer []byte) string { 37 | // We remove the first byte from the buffer array that represents if confidential or unconfidential 38 | return hex.EncodeToString(ReverseBytes(buffer[1:])) 39 | } 40 | 41 | func AssetHashToBytes(str string) ([]byte, error) { 42 | buffer, err := hex.DecodeString(str) 43 | if err != nil { 44 | return nil, err 45 | } 46 | buffer = ReverseBytes(buffer) 47 | buffer = append([]byte{0x01}, buffer...) 48 | return buffer, nil 49 | } 50 | 51 | func TxIDFromBytes(buffer []byte) string { 52 | return hex.EncodeToString(ReverseBytes(buffer)) 53 | } 54 | 55 | func TxIDToBytes(str string) ([]byte, error) { 56 | buffer, err := hex.DecodeString(str) 57 | if err != nil { 58 | return nil, err 59 | } 60 | return ReverseBytes(buffer), nil 61 | } 62 | 63 | func CommitmentFromBytes(buffer []byte) string { 64 | return hex.EncodeToString(buffer) 65 | } 66 | 67 | func CommitmentToBytes(str string) ([]byte, error) { 68 | return hex.DecodeString(str) 69 | } 70 | 71 | // ReverseBytes returns a copy of the given byte slice with elems in reverse order. 72 | func ReverseBytes(buf []byte) []byte { 73 | if len(buf) < 1 { 74 | return buf 75 | } 76 | tmp := make([]byte, len(buf)) 77 | copy(tmp, buf) 78 | for i := len(tmp)/2 - 1; i >= 0; i-- { 79 | j := len(tmp) - 1 - i 80 | tmp[i], tmp[j] = tmp[j], tmp[i] 81 | } 82 | return tmp 83 | } 84 | 85 | func ValidElementValue(val []byte) bool { 86 | return len(val) == 9 && val[0] == byte(1) 87 | } 88 | -------------------------------------------------------------------------------- /elementsutil/elementsutil_test.go: -------------------------------------------------------------------------------- 1 | package elementsutil_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | "github.com/vulpemventures/go-elements/elementsutil" 8 | ) 9 | 10 | func TestValueFromToBytes(t *testing.T) { 11 | value := uint64(1000000000) 12 | rawValue, err := elementsutil.ValueToBytes(value) 13 | require.NoError(t, err) 14 | 15 | val, err := elementsutil.ValueFromBytes(rawValue) 16 | require.NoError(t, err) 17 | 18 | require.Equal(t, value, val) 19 | } 20 | 21 | func TestAssetFromToBytes(t *testing.T) { 22 | asset := "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225" 23 | rawAsset, err := elementsutil.AssetHashToBytes(asset) 24 | require.NoError(t, err) 25 | 26 | ass := elementsutil.AssetHashFromBytes(rawAsset) 27 | require.Equal(t, asset, ass) 28 | } 29 | 30 | func TestTxIDFromToBytes(t *testing.T) { 31 | txid := "c222325b5fd879163ac9014b410e6f27a1411572e807c28c12748ad94022de72" 32 | rawTxid, err := elementsutil.TxIDToBytes(txid) 33 | require.NoError(t, err) 34 | 35 | txhash := elementsutil.TxIDFromBytes(rawTxid) 36 | require.Equal(t, txid, txhash) 37 | } 38 | -------------------------------------------------------------------------------- /go-elements-gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulpemventures/go-elements/9cebbd2ad5772aad22639308714ee27ab7026f6a/go-elements-gopher.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vulpemventures/go-elements 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/btcsuite/btcd v0.24.0 7 | github.com/btcsuite/btcd/btcec/v2 v2.3.3 8 | github.com/btcsuite/btcd/btcutil v1.1.5 9 | github.com/btcsuite/btcd/btcutil/psbt v1.1.9 10 | github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 11 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 12 | github.com/stretchr/testify v1.8.0 13 | github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941 14 | github.com/vulpemventures/go-secp256k1-zkp v1.1.6 15 | golang.org/x/crypto v0.23.0 16 | ) 17 | 18 | require ( 19 | github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect 22 | github.com/pmezard/go-difflib v1.0.0 // indirect 23 | golang.org/x/sys v0.20.0 // indirect 24 | gopkg.in/yaml.v3 v3.0.1 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /internal/bufferutil/deserializer.go: -------------------------------------------------------------------------------- 1 | package bufferutil 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | // Deserializer implements methods that help to deserialize an Elements transaction. 9 | type Deserializer struct { 10 | buffer *bytes.Buffer 11 | } 12 | 13 | // NewDeserializer returns an instance of Deserializer. 14 | func NewDeserializer(buffer *bytes.Buffer) *Deserializer { 15 | return &Deserializer{buffer} 16 | } 17 | 18 | // ReadToEnd returns bytes left in buffer 19 | func (d *Deserializer) ReadToEnd() []byte { 20 | return d.buffer.Bytes() 21 | } 22 | 23 | // ReadUint8 reads a uint8 value from reader's buffer. 24 | func (d *Deserializer) ReadUint8() (uint8, error) { 25 | return BinarySerializer.Uint8(d.buffer) 26 | } 27 | 28 | // ReadUint16 reads a uint16 value from reader's buffer. 29 | func (d *Deserializer) ReadUint16() (uint16, error) { 30 | return BinarySerializer.Uint16(d.buffer, littleEndian) 31 | } 32 | 33 | // ReadUint32 reads a uint32 value from reader's buffer. 34 | func (d *Deserializer) ReadUint32() (uint32, error) { 35 | return BinarySerializer.Uint32(d.buffer, littleEndian) 36 | } 37 | 38 | // ReadUint64 reads a uint64 value from reader's buffer. 39 | func (d *Deserializer) ReadUint64() (uint64, error) { 40 | return BinarySerializer.Uint64(d.buffer, littleEndian) 41 | } 42 | 43 | // ReadVarInt reads a variable length integer from reader's buffer and returns it as a uint64. 44 | func (d *Deserializer) ReadVarInt() (uint64, error) { 45 | return readVarInt(d.buffer) 46 | } 47 | 48 | // ReadSlice reads the next n bytes from the reader's buffer 49 | func (d *Deserializer) ReadSlice(n uint) ([]byte, error) { 50 | decoded := make([]byte, n) 51 | _, err := d.buffer.Read(decoded) 52 | if err != nil { 53 | return nil, err 54 | } 55 | return decoded, nil 56 | } 57 | 58 | // ReadVarSlice first reads the length n of the bytes, then reads the next n bytes 59 | func (d *Deserializer) ReadVarSlice() ([]byte, error) { 60 | n, err := d.ReadVarInt() 61 | if err != nil { 62 | return nil, err 63 | } 64 | return d.ReadSlice(uint(n)) 65 | } 66 | 67 | // ReadVector reads the length n of the array of bytes, then reads the next n array bytes 68 | func (d *Deserializer) ReadVector() ([][]byte, error) { 69 | n, err := d.ReadVarInt() 70 | if err != nil { 71 | return nil, err 72 | } 73 | v := [][]byte{} 74 | for i := uint(0); i < uint(n); i++ { 75 | val, err := d.ReadVarSlice() 76 | if err != nil { 77 | return nil, err 78 | } 79 | v = append(v, val) 80 | } 81 | return v, nil 82 | } 83 | 84 | // ReadElementsValue reads the first byte to determine if the value is 85 | // confidential or unconfidential, then reads the right number of bytes accordingly. 86 | func (d *Deserializer) ReadElementsValue() ([]byte, error) { 87 | version, err := d.ReadUint8() 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | // special case: if issuance token amount is not defined it's encoded as 0x00 93 | if version == 0 { 94 | return []byte{version}, nil 95 | } 96 | 97 | buf := []byte{version} 98 | nextBytes := []byte{} 99 | if version == 1 { 100 | nextBytes, err = d.ReadSlice(8) 101 | if err != nil { 102 | return nil, err 103 | } 104 | } 105 | if version == 8 || version == 9 { 106 | nextBytes, err = d.ReadSlice(32) 107 | if err != nil { 108 | return nil, err 109 | } 110 | } 111 | if len(nextBytes) == 0 { 112 | return nil, fmt.Errorf("Invalid prefix %d", version) 113 | } 114 | buf = append(buf, nextBytes...) 115 | return buf, nil 116 | } 117 | 118 | // ReadElementsAsset reads an Elements output asset form the reader's buffer 119 | func (d *Deserializer) ReadElementsAsset() ([]byte, error) { 120 | version, err := d.ReadUint8() 121 | if err != nil { 122 | return nil, err 123 | } 124 | 125 | if version == 1 || version == 10 || version == 11 { 126 | b, err := d.ReadSlice(32) 127 | if err != nil { 128 | return nil, err 129 | } 130 | buf := []byte{version} 131 | buf = append(buf, b...) 132 | return buf, nil 133 | } 134 | 135 | return nil, fmt.Errorf("Invalid prefix %d", version) 136 | } 137 | 138 | // ReadElementsNonce reads a maybe non-zero Elements output nonce form the reader's buffer 139 | func (d *Deserializer) ReadElementsNonce() ([]byte, error) { 140 | version, err := d.ReadUint8() 141 | if err != nil { 142 | return nil, err 143 | } 144 | 145 | buf := []byte{version} 146 | if version >= 1 && version <= 3 { 147 | b, err := d.ReadSlice(32) 148 | if err != nil { 149 | return nil, err 150 | } 151 | buf = append(buf, b...) 152 | return buf, nil 153 | } 154 | 155 | return buf, nil 156 | } 157 | -------------------------------------------------------------------------------- /internal/bufferutil/serializer.go: -------------------------------------------------------------------------------- 1 | package bufferutil 2 | 3 | import ( 4 | "bytes" 5 | ) 6 | 7 | // Serializer implements methods that help to serialize an Elements transaction. 8 | type Serializer struct { 9 | buffer *bytes.Buffer 10 | } 11 | 12 | // NewSerializer returns an instance of Serializer. 13 | func NewSerializer(buf *bytes.Buffer) *Serializer { 14 | if buf == nil { 15 | buf = bytes.NewBuffer([]byte{}) 16 | } 17 | return &Serializer{buf} 18 | } 19 | 20 | // Bytes returns writer's buffer 21 | func (s *Serializer) Bytes() []byte { 22 | return s.buffer.Bytes() 23 | } 24 | 25 | // WriteUint8 writes the given uint8 value to writer's buffer. 26 | func (s *Serializer) WriteUint8(val uint8) error { 27 | return BinarySerializer.PutUint8(s.buffer, val) 28 | } 29 | 30 | // WriteUint16 writes the given uint8 value to writer's buffer. 31 | func (s *Serializer) WriteUint16(val uint16) error { 32 | return BinarySerializer.PutUint16(s.buffer, littleEndian, val) 33 | } 34 | 35 | // WriteUint32 writes the given uint32 value to writer's buffer. 36 | func (s *Serializer) WriteUint32(val uint32) error { 37 | return BinarySerializer.PutUint32(s.buffer, littleEndian, val) 38 | } 39 | 40 | // WriteUint64 writes the given uint64 value to writer's buffer. 41 | func (s *Serializer) WriteUint64(val uint64) error { 42 | return BinarySerializer.PutUint64(s.buffer, littleEndian, val) 43 | } 44 | 45 | // WriteVarInt serializes the given value to writer's buffer 46 | // using a variable number of bytes depending on its value. 47 | func (s *Serializer) WriteVarInt(val uint64) error { 48 | return writeVarInt(s.buffer, val) 49 | } 50 | 51 | // WriteSlice appends the given byte array to the writer's buffer 52 | func (s *Serializer) WriteSlice(val []byte) error { 53 | _, err := s.buffer.Write(val) 54 | return err 55 | } 56 | 57 | // WriteVarSlice appends the length of the given byte array as var int 58 | // and the byte array itself to the writer's buffer 59 | func (s *Serializer) WriteVarSlice(val []byte) error { 60 | err := s.WriteVarInt(uint64(len(val))) 61 | if err != nil { 62 | return err 63 | } 64 | return s.WriteSlice(val) 65 | } 66 | 67 | // WriteVector appends an array of array bytes to the writer's buffer 68 | func (s *Serializer) WriteVector(v [][]byte) error { 69 | err := s.WriteVarInt(uint64(len(v))) 70 | if err != nil { 71 | return err 72 | } 73 | for _, val := range v { 74 | err := s.WriteVarSlice(val) 75 | if err != nil { 76 | return err 77 | } 78 | } 79 | return nil 80 | } 81 | -------------------------------------------------------------------------------- /internal/bufferutil/serializer_test.go: -------------------------------------------------------------------------------- 1 | package bufferutil 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "math" 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | func TestSerializerAndDeserializer(t *testing.T) { 12 | t.Run("WriteReadUint8", testWriteReadUint8) 13 | t.Run("WriteReadUint32", testWriteReadUint32) 14 | t.Run("WriteReadUint64", testWriteReadUint64) 15 | t.Run("WriteReadVarInt", testWriteReadVarInt) 16 | t.Run("WriteReadSlice", testWriteReadSlice) 17 | t.Run("WriteReadVarSlice", testWriteReadVarSlice) 18 | t.Run("WriteReadVector", testWriteReadVector) 19 | } 20 | 21 | func testWriteReadUint8(t *testing.T) { 22 | buf := bytes.NewBuffer([]byte{}) 23 | bw := NewSerializer(buf) 24 | 25 | tests := struct { 26 | in []uint8 27 | expected []byte 28 | }{ 29 | []uint8{0, 1, 254, 255}, 30 | []byte{0x00, 0x01, 0xfe, 0xff}, 31 | } 32 | 33 | for _, v := range tests.in { 34 | err := bw.WriteUint8(v) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | } 39 | 40 | br := NewDeserializer(bw.buffer) 41 | for _, expected := range tests.expected { 42 | res, err := br.ReadUint8() 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | if res != expected { 47 | t.Fatalf("Got: %d, expected: %d", res, expected) 48 | } 49 | } 50 | } 51 | 52 | func testWriteReadUint32(t *testing.T) { 53 | buf := bytes.NewBuffer([]byte{}) 54 | bw := NewSerializer(buf) 55 | 56 | tests := struct { 57 | in []uint32 58 | expected [][]byte 59 | }{ 60 | []uint32{ 61 | 0, 62 | 1, 63 | uint32(math.Pow(2, 16)), 64 | uint32(math.Pow(2, 32) - 1), 65 | }, 66 | [][]byte{ 67 | []byte{0x00, 0x00, 0x00, 0x00}, 68 | []byte{0x01, 0x00, 0x00, 0x00}, 69 | []byte{0x00, 0x00, 0x01, 0x00}, 70 | []byte{0xff, 0xff, 0xff, 0xff}, 71 | }, 72 | } 73 | 74 | for _, v := range tests.in { 75 | err := bw.WriteUint32(v) 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | } 80 | 81 | br := NewDeserializer(bw.buffer) 82 | for _, expected := range tests.expected { 83 | res, err := br.ReadUint32() 84 | if err != nil { 85 | t.Fatal(err) 86 | } 87 | if exp := binary.LittleEndian.Uint32(expected); res != exp { 88 | t.Fatalf("Got: %d, expected: %d", res, exp) 89 | } 90 | } 91 | } 92 | 93 | func testWriteReadUint64(t *testing.T) { 94 | buf := bytes.NewBuffer([]byte{}) 95 | bw := NewSerializer(buf) 96 | 97 | tests := struct { 98 | in []uint64 99 | expected [][]byte 100 | }{ 101 | []uint64{ 102 | 0, 103 | 1, 104 | uint64(math.Pow(2, 32)), 105 | uint64(math.Pow(2, 53) - 1), 106 | }, 107 | [][]byte{ 108 | []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 109 | []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 110 | []byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}, 111 | []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00}, 112 | }, 113 | } 114 | 115 | for _, v := range tests.in { 116 | err := bw.WriteUint64(v) 117 | if err != nil { 118 | t.Fatal(err) 119 | } 120 | } 121 | 122 | br := NewDeserializer(bw.buffer) 123 | for _, expected := range tests.expected { 124 | res, err := br.ReadUint64() 125 | if err != nil { 126 | t.Fatal(err) 127 | } 128 | if exp := binary.LittleEndian.Uint64(expected); res != exp { 129 | t.Fatalf("Got: %d, expected: %d", res, exp) 130 | } 131 | } 132 | } 133 | 134 | func testWriteReadVarInt(t *testing.T) { 135 | buf := bytes.NewBuffer([]byte{}) 136 | bw := NewSerializer(buf) 137 | 138 | tests := struct { 139 | in []uint64 140 | expected [][]byte 141 | }{ 142 | []uint64{ 143 | 0, 144 | 1, 145 | 252, 146 | 253, 147 | 254, 148 | 255, 149 | 256, 150 | uint64(math.Pow(2, 16) - 2), 151 | uint64(math.Pow(2, 16) - 1), 152 | uint64(math.Pow(2, 16)), 153 | uint64(math.Pow(2, 32) - 2), 154 | uint64(math.Pow(2, 32) - 1), 155 | uint64(math.Pow(2, 32)), 156 | uint64(math.Pow(2, 53) - 1), 157 | }, 158 | [][]byte{ 159 | []byte{0x00}, 160 | []byte{0x01}, 161 | []byte{0xfc}, 162 | []byte{0xfd, 0xfd, 0x00}, 163 | []byte{0xfd, 0xfe, 0x00}, 164 | []byte{0xfd, 0xff, 0x00}, 165 | []byte{0xfd, 0x00, 0x01}, 166 | []byte{0xfd, 0xfe, 0xff}, 167 | []byte{0xfd, 0xff, 0xff}, 168 | []byte{0xfe, 0x00, 0x00, 0x01, 0x00}, 169 | []byte{0xfe, 0xfe, 0xff, 0xff, 0xff}, 170 | []byte{0xfe, 0xff, 0xff, 0xff, 0xff}, 171 | []byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}, 172 | []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00}, 173 | }, 174 | } 175 | 176 | for _, v := range tests.in { 177 | err := bw.WriteVarInt(v) 178 | if err != nil { 179 | t.Fatal(err) 180 | } 181 | } 182 | 183 | br := NewDeserializer(bw.buffer) 184 | for _, expected := range tests.expected { 185 | res, err := br.ReadVarInt() 186 | if err != nil { 187 | t.Fatal(err) 188 | } 189 | if exp, _ := readVarInt(bytes.NewBuffer(expected)); res != exp { 190 | t.Fatalf("Got: %d, expected: %d", res, exp) 191 | } 192 | } 193 | } 194 | 195 | func testWriteReadSlice(t *testing.T) { 196 | buf := bytes.NewBuffer([]byte{}) 197 | bw := NewSerializer(buf) 198 | 199 | tests := struct { 200 | in [][]byte 201 | }{ 202 | [][]byte{ 203 | []byte{}, 204 | []byte{1}, 205 | []byte{1, 2, 3, 4}, 206 | []byte{254, 255}, 207 | }, 208 | } 209 | 210 | for _, v := range tests.in { 211 | err := bw.WriteSlice(v) 212 | if err != nil { 213 | t.Fatal(err) 214 | } 215 | } 216 | 217 | br := NewDeserializer(bw.buffer) 218 | for _, v := range tests.in { 219 | res, err := br.ReadSlice(uint(len(v))) 220 | if err != nil { 221 | t.Fatal(err) 222 | } 223 | if !reflect.DeepEqual(res, v) { 224 | t.Fatalf("Got: %b, expected: %b", res, v) 225 | } 226 | } 227 | } 228 | 229 | func testWriteReadVarSlice(t *testing.T) { 230 | buf := bytes.NewBuffer([]byte{}) 231 | bw := NewSerializer(buf) 232 | 233 | t1 := filledSlice(252, 2) 234 | t2 := filledSlice(253, 3) 235 | tests := struct { 236 | in [][]byte 237 | expected [][]byte 238 | }{ 239 | [][]byte{ 240 | []byte{0x01}, 241 | t1, 242 | t2, 243 | }, 244 | [][]byte{ 245 | []byte{0x01, 0x01}, 246 | append([]byte{0xfc}, t1...), 247 | append([]byte{0xfd, 0xfd, 0x00}, t2...), 248 | }, 249 | } 250 | 251 | for _, v := range tests.in { 252 | err := bw.WriteVarSlice(v) 253 | if err != nil { 254 | t.Fatal(err) 255 | } 256 | } 257 | 258 | br := NewDeserializer(bw.buffer) 259 | for _, v := range tests.in { 260 | res, err := br.ReadVarSlice() 261 | if err != nil { 262 | t.Fatal(err) 263 | } 264 | if !reflect.DeepEqual(res, v) { 265 | t.Fatalf("Got: %b, expected: %b", res, v) 266 | } 267 | } 268 | } 269 | 270 | func testWriteReadVector(t *testing.T) { 271 | buf := bytes.NewBuffer([]byte{}) 272 | bw := NewSerializer(buf) 273 | 274 | t1 := filledSlice(253, 5) 275 | t2 := filledSliceArray(253, []byte{6}) 276 | 277 | tests := struct { 278 | in [][][]byte 279 | }{ 280 | [][][]byte{ 281 | [][]byte{ 282 | []byte{0x04}, 283 | t1, 284 | }, 285 | t2, 286 | }, 287 | } 288 | 289 | for _, v := range tests.in { 290 | err := bw.WriteVector(v) 291 | if err != nil { 292 | t.Fatal(err) 293 | } 294 | } 295 | 296 | br := NewDeserializer(bw.buffer) 297 | for _, v := range tests.in { 298 | res, err := br.ReadVector() 299 | if err != nil { 300 | t.Fatal(err) 301 | } 302 | if !reflect.DeepEqual(res, v) { 303 | t.Fatalf("Got: %b, expected: %b", res, v) 304 | } 305 | } 306 | } 307 | 308 | func filledSlice(n int, val uint8) []byte { 309 | v := make([]byte, n) 310 | for i := range v { 311 | v[i] = val 312 | } 313 | return v 314 | } 315 | 316 | func filledSliceArray(n int, val []byte) [][]byte { 317 | v := make([][]byte, n) 318 | for i := range v { 319 | v[i] = make([]byte, len(val)) 320 | copy(v[i], val) 321 | } 322 | return v 323 | } 324 | -------------------------------------------------------------------------------- /network/network.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | // Network type represents prefixes for each network 4 | // https://en.bitcoin.it/wiki/List_of_address_prefixes 5 | type Network struct { 6 | Name string 7 | // Human-readable part for Bech32 encoded segwit addresses, as defined 8 | // in BIP 173. 9 | Bech32 string 10 | // Human-readable part for Blech32 encoded segwit confidential addresses, 11 | // as defined in BIP 173. 12 | Blech32 string 13 | // BIP32 hierarchical deterministic extended key magics 14 | HDPublicKey [4]byte 15 | HDPrivateKey [4]byte 16 | // Address encoding magic 17 | PubKeyHash byte 18 | ScriptHash byte 19 | // First byte of a WIF private key 20 | Wif byte 21 | // Confidential prefix 22 | Confidential byte 23 | // Bitcoin Asset Hash for the current network 24 | AssetID string 25 | GenesisBlockHash string 26 | } 27 | 28 | // Liquid defines the network parameters for the main Liquid network. 29 | var Liquid = Network{ 30 | Name: "liquid", 31 | Bech32: "ex", 32 | Blech32: "lq", 33 | HDPublicKey: [4]byte{0x04, 0x88, 0xb2, 0x1e}, 34 | HDPrivateKey: [4]byte{0x04, 0x88, 0xad, 0xe4}, 35 | PubKeyHash: 57, 36 | ScriptHash: 39, 37 | Wif: 0x80, 38 | Confidential: 12, 39 | AssetID: "6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d", 40 | GenesisBlockHash: "1466275836220db2944ca059a3a10ef6fd2ea684b0688d2c379296888a206003", 41 | } 42 | 43 | // Regtest defines the network parameters for the regression regtest network. 44 | var Regtest = Network{ 45 | Name: "regtest", 46 | Bech32: "ert", 47 | Blech32: "el", 48 | HDPublicKey: [4]byte{0x04, 0x35, 0x87, 0xcf}, 49 | HDPrivateKey: [4]byte{0x04, 0x35, 0x83, 0x94}, 50 | PubKeyHash: 235, 51 | ScriptHash: 75, 52 | Wif: 0xef, 53 | Confidential: 4, 54 | AssetID: "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225", 55 | GenesisBlockHash: "00902a6b70c2ca83b5d9c815d96a0e2f4202179316970d14ea1847dae5b1ca21", 56 | } 57 | 58 | // Testnet defines the network parameters for the regression testnet network. 59 | var Testnet = Network{ 60 | Name: "testnet", 61 | Bech32: "tex", 62 | Blech32: "tlq", 63 | HDPublicKey: [4]byte{0x04, 0x35, 0x87, 0xcf}, 64 | HDPrivateKey: [4]byte{0x04, 0x35, 0x83, 0x94}, 65 | PubKeyHash: 36, 66 | ScriptHash: 19, 67 | Wif: 0xef, 68 | Confidential: 23, 69 | AssetID: "144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49", 70 | GenesisBlockHash: "a771da8e52ee6ad581ed1e9a99825e5b3b7992225534eaa2ae23244fe26ab1c1", 71 | } 72 | -------------------------------------------------------------------------------- /payment/examples_test.go: -------------------------------------------------------------------------------- 1 | package payment_test 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | 7 | "github.com/btcsuite/btcd/btcec/v2" 8 | "github.com/vulpemventures/go-elements/network" 9 | "github.com/vulpemventures/go-elements/payment" 10 | ) 11 | 12 | const ( 13 | privateKeyHex = "1cc080a4cd371eafcad489a29664af6a7276b362fe783443ce036552482b971d" 14 | ) 15 | 16 | var privateKeyBytes, _ = hex.DecodeString(privateKeyHex) 17 | 18 | // This examples shows how standard P2PKH address can be created 19 | func ExampleFromPublicKey() { 20 | _, publicKey := btcec.PrivKeyFromBytes(privateKeyBytes) 21 | pay := payment.FromPublicKey(publicKey, &network.Regtest, nil) 22 | addr, _ := pay.PubKeyHash() 23 | fmt.Printf("P2PKH address %v\n:", addr) 24 | } 25 | 26 | // This examples shows how nested payment can be done in order to create non native SegWit(P2SH-P2WPKH) address 27 | func ExampleFromPayment() { 28 | _, publicKey := btcec.PrivKeyFromBytes(privateKeyBytes) 29 | p2wpkh := payment.FromPublicKey(publicKey, &network.Regtest, nil) 30 | pay, err := payment.FromPayment(p2wpkh) 31 | p2sh, err := pay.ScriptHash() 32 | if err != nil { 33 | fmt.Println(err) 34 | } 35 | fmt.Printf("Non native SegWit address %v\n:", p2sh) 36 | } 37 | 38 | func ExampleConfidentialWitnessPubKeyHash() { 39 | pk, err := btcec.NewPrivateKey() 40 | if err != nil { 41 | fmt.Println(err) 42 | } 43 | blindingKey := pk.PubKey() 44 | 45 | privkey, err := btcec.NewPrivateKey() 46 | if err != nil { 47 | fmt.Println(err) 48 | } 49 | 50 | p2wpkh := payment.FromPublicKey(privkey.PubKey(), &network.Regtest, blindingKey) 51 | confidentialWpkh, err := p2wpkh.ConfidentialWitnessPubKeyHash() 52 | if err != nil { 53 | fmt.Println(err) 54 | } 55 | fmt.Printf("Confidential SegWit address %v\n:", confidentialWpkh) 56 | } 57 | -------------------------------------------------------------------------------- /payment/p2tr.go: -------------------------------------------------------------------------------- 1 | package payment 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/btcsuite/btcd/btcec/v2" 7 | "github.com/btcsuite/btcd/btcec/v2/schnorr" 8 | "github.com/btcsuite/btcd/chaincfg/chainhash" 9 | "github.com/vulpemventures/go-elements/address" 10 | "github.com/vulpemventures/go-elements/network" 11 | "github.com/vulpemventures/go-elements/taproot" 12 | ) 13 | 14 | const ( 15 | taprootSegwitVersion = byte(0x01) 16 | ) 17 | 18 | var ( 19 | ErrTaprootDataIsNil = errors.New("taproot payment data is required to derive taproot addresses") 20 | ErrNetworkIsNil = errors.New("network is required to derive taproot addresses") 21 | ) 22 | 23 | // TaprootPaymentData is included in Payment struct to store Taproot-related data 24 | type TaprootPaymentData struct { 25 | XOnlyTweakedKey []byte 26 | XOnlyInternalKey []byte 27 | RootScriptTreeHash *chainhash.Hash 28 | ScriptTree *taproot.IndexedElementsTapScriptTree 29 | } 30 | 31 | // FromTweakedKey creates a P2TR payment from a tweaked output key 32 | func FromTweakedKey( 33 | tweakedKey *btcec.PublicKey, 34 | net *network.Network, 35 | blindingKey *btcec.PublicKey, 36 | ) (*Payment, error) { 37 | if tweakedKey == nil { 38 | return nil, errors.New("tweaked key can't be empty or nil") 39 | } 40 | 41 | if net == nil { 42 | net = &network.Liquid 43 | } 44 | 45 | p2tr := &Payment{ 46 | Network: net, 47 | BlindingKey: blindingKey, 48 | Taproot: &TaprootPaymentData{ 49 | XOnlyTweakedKey: schnorr.SerializePubKey(tweakedKey), 50 | }, 51 | } 52 | 53 | if err := p2tr.setP2TRScript(); err != nil { 54 | return nil, err 55 | } 56 | 57 | return p2tr, nil 58 | } 59 | 60 | // FromTaprootScriptTreeHash creates a taproot payment from a merkle script tree hash and internal key 61 | func FromTaprootScriptTreeHash( 62 | internalKey *btcec.PublicKey, 63 | rootHash *chainhash.Hash, 64 | net *network.Network, 65 | blindingKey *btcec.PublicKey, 66 | ) (*Payment, error) { 67 | if internalKey == nil { 68 | return nil, errors.New("internal key can't be empty or nil") 69 | } 70 | 71 | if net == nil { 72 | net = &network.Liquid 73 | } 74 | 75 | p2tr := &Payment{ 76 | Network: net, 77 | BlindingKey: blindingKey, 78 | Taproot: &TaprootPaymentData{ 79 | XOnlyInternalKey: schnorr.SerializePubKey(internalKey), 80 | RootScriptTreeHash: rootHash, 81 | }, 82 | } 83 | 84 | if err := p2tr.setP2TRScript(); err != nil { 85 | return nil, err 86 | } 87 | 88 | return p2tr, nil 89 | } 90 | 91 | // FromTaprootScriptTree creates a taproot payment from a merkle script tree and internal key 92 | func FromTaprootScriptTree( 93 | internalKey *btcec.PublicKey, 94 | tree *taproot.IndexedElementsTapScriptTree, 95 | net *network.Network, 96 | blindingKey *btcec.PublicKey, 97 | ) (*Payment, error) { 98 | if internalKey == nil { 99 | return nil, errors.New("internal key can't be empty or nil") 100 | } 101 | 102 | if net == nil { 103 | net = &network.Liquid 104 | } 105 | 106 | p2tr := &Payment{ 107 | Network: net, 108 | BlindingKey: blindingKey, 109 | Taproot: &TaprootPaymentData{ 110 | XOnlyInternalKey: schnorr.SerializePubKey(internalKey), 111 | ScriptTree: tree, 112 | }, 113 | } 114 | 115 | if err := p2tr.setP2TRScript(); err != nil { 116 | return nil, err 117 | } 118 | 119 | return p2tr, nil 120 | } 121 | 122 | func (p2tr *Payment) setP2TRScript() error { 123 | addr, err := p2tr.TaprootAddress() 124 | if err != nil { 125 | return err 126 | } 127 | 128 | script, err := address.ToOutputScript(addr) 129 | if err != nil { 130 | return err 131 | } 132 | 133 | p2tr.Script = script 134 | return nil 135 | } 136 | 137 | func (p *Payment) taprootBech32() (*address.Bech32, error) { 138 | if p.Taproot == nil { 139 | return nil, ErrTaprootDataIsNil 140 | } 141 | 142 | if p.Network == nil { 143 | return nil, ErrNetworkIsNil 144 | } 145 | 146 | payload := &address.Bech32{ 147 | Prefix: p.Network.Bech32, 148 | Version: taprootSegwitVersion, 149 | } 150 | 151 | if p.Taproot.XOnlyTweakedKey != nil { 152 | payload.Program = p.Taproot.XOnlyTweakedKey 153 | } else if p.Taproot.XOnlyInternalKey != nil { 154 | internalKey, err := schnorr.ParsePubKey(p.Taproot.XOnlyInternalKey) 155 | if err != nil { 156 | return nil, err 157 | } 158 | 159 | if p.Taproot.RootScriptTreeHash != nil { 160 | payload.Program = schnorr.SerializePubKey(taproot.ComputeTaprootOutputKey(internalKey, p.Taproot.RootScriptTreeHash.CloneBytes())) 161 | } else { 162 | if p.Taproot.ScriptTree == nil { 163 | payload.Program = schnorr.SerializePubKey(taproot.ComputeTaprootKeyNoScript(internalKey)) 164 | } else { 165 | scriptTreeHash := p.Taproot.ScriptTree.RootNode.TapHash() 166 | payload.Program = schnorr.SerializePubKey(taproot.ComputeTaprootOutputKey(internalKey, scriptTreeHash.CloneBytes())) 167 | } 168 | } 169 | } 170 | 171 | if payload.Program == nil { 172 | return nil, errors.New("unable to compute taproot's tweaked key from payment data") 173 | } 174 | 175 | if len(payload.Program) != 32 { 176 | return nil, errors.New("taproot's tweaked key has wrong length") 177 | } 178 | 179 | return payload, nil 180 | } 181 | 182 | // TaprootAddress derives the unconditional Taproot address from the payment data 183 | func (p *Payment) TaprootAddress() (string, error) { 184 | payload, err := p.taprootBech32() 185 | if err != nil { 186 | return "", err 187 | } 188 | addr, err := address.ToBech32(payload) 189 | if err != nil { 190 | return "", err 191 | } 192 | return addr, nil 193 | } 194 | 195 | // ConfidentialTaprootAddress derives a confidential segwit v1 address from the payment taproot data 196 | func (p *Payment) ConfidentialTaprootAddress() (string, error) { 197 | if p.BlindingKey == nil { 198 | return "", errors.New("blinding key is required to derive confidential address") 199 | } 200 | 201 | bechTaproot, err := p.taprootBech32() 202 | if err != nil { 203 | return "", err 204 | } 205 | 206 | payload := &address.Blech32{ 207 | Prefix: p.Network.Blech32, 208 | Version: bechTaproot.Version, 209 | Program: bechTaproot.Program, 210 | PublicKey: p.BlindingKey.SerializeCompressed(), 211 | } 212 | 213 | addr, err := address.ToBlech32(payload) 214 | if err != nil { 215 | return "", nil 216 | } 217 | return addr, nil 218 | } 219 | -------------------------------------------------------------------------------- /payment/p2tr_test.go: -------------------------------------------------------------------------------- 1 | package payment_test 2 | 3 | import ( 4 | "bytes" 5 | "math/rand" 6 | "testing" 7 | 8 | "github.com/btcsuite/btcd/btcec/v2" 9 | "github.com/btcsuite/btcd/btcec/v2/schnorr" 10 | "github.com/vulpemventures/go-elements/address" 11 | "github.com/vulpemventures/go-elements/network" 12 | "github.com/vulpemventures/go-elements/payment" 13 | "github.com/vulpemventures/go-elements/taproot" 14 | ) 15 | 16 | func randomTapscriptTree() *taproot.IndexedElementsTapScriptTree { 17 | tapScriptLeaves := make([]taproot.TapElementsLeaf, 4) 18 | for i := 0; i < len(tapScriptLeaves); i++ { 19 | numLeafBytes := rand.Intn(1000) 20 | scriptBytes := make([]byte, numLeafBytes) 21 | if _, err := rand.Read(scriptBytes[:]); err != nil { 22 | panic(err) 23 | } 24 | tapScriptLeaves[i] = taproot.NewBaseTapElementsLeaf(scriptBytes) 25 | } 26 | 27 | return taproot.AssembleTaprootScriptTree(tapScriptLeaves...) 28 | 29 | } 30 | 31 | var testTree = randomTapscriptTree() 32 | var rootHash = testTree.RootNode.TapHash() 33 | var internalKey, _ = btcec.NewPrivateKey() 34 | var blindingKey, _ = btcec.NewPrivateKey() 35 | 36 | var tweakedKey = taproot.ComputeTaprootOutputKey(internalKey.PubKey(), rootHash.CloneBytes()) 37 | var expected, _ = payment.FromTweakedKey(tweakedKey, &network.Regtest, blindingKey.PubKey()) 38 | var expectedAddr, _ = expected.ConfidentialTaprootAddress() 39 | 40 | func TestFromTaprootScriptTreeHash(t *testing.T) { 41 | p2tr, err := payment.FromTaprootScriptTreeHash( 42 | internalKey.PubKey(), 43 | &rootHash, 44 | &network.Regtest, 45 | blindingKey.PubKey(), 46 | ) 47 | 48 | if err != nil { 49 | t.Error(err) 50 | } 51 | 52 | if p2tr.Taproot.RootScriptTreeHash != &rootHash { 53 | t.Error("Root script tree hash not set") 54 | } 55 | 56 | if !bytes.Equal(p2tr.Taproot.XOnlyInternalKey, schnorr.SerializePubKey(internalKey.PubKey())) { 57 | t.Error("Internal key not set incorrect") 58 | } 59 | 60 | addr, err := p2tr.ConfidentialTaprootAddress() 61 | if err != nil { 62 | t.Error(err) 63 | } 64 | 65 | typeOfAddr, err := address.DecodeType(addr) 66 | if err != nil { 67 | t.Error(err) 68 | } 69 | 70 | if typeOfAddr != address.ConfidentialP2TR { 71 | t.Error("Address type not set correctly") 72 | } 73 | 74 | if expectedAddr != addr { 75 | t.Errorf("Expected address %s, got %s", expectedAddr, addr) 76 | } 77 | } 78 | 79 | func TestFromTaprootScriptTree(t *testing.T) { 80 | p2tr, err := payment.FromTaprootScriptTree( 81 | internalKey.PubKey(), 82 | testTree, 83 | &network.Regtest, 84 | blindingKey.PubKey(), 85 | ) 86 | 87 | if err != nil { 88 | t.Error(err) 89 | } 90 | 91 | if p2tr.Taproot.ScriptTree == nil { 92 | t.Error("Script tree not set") 93 | } 94 | 95 | if !bytes.Equal(p2tr.Taproot.XOnlyInternalKey, schnorr.SerializePubKey(internalKey.PubKey())) { 96 | t.Error("Internal key incorrect") 97 | } 98 | 99 | addr, err := p2tr.ConfidentialTaprootAddress() 100 | if err != nil { 101 | t.Error(err) 102 | } 103 | 104 | typeOfAddr, err := address.DecodeType(addr) 105 | if err != nil { 106 | t.Error(err) 107 | } 108 | 109 | if typeOfAddr != address.ConfidentialP2TR { 110 | t.Error("Address type not set correctly") 111 | } 112 | 113 | if expectedAddr != addr { 114 | t.Errorf("Expected address %s, got %s", expectedAddr, addr) 115 | } 116 | } 117 | 118 | func TestTaprootAddressWithNonTaprootPayment(t *testing.T) { 119 | pay := payment.Payment{ 120 | Network: &network.Regtest, 121 | BlindingKey: expected.BlindingKey, 122 | Taproot: nil, 123 | } 124 | 125 | _, err := pay.ConfidentialTaprootAddress() 126 | if err != payment.ErrTaprootDataIsNil { 127 | t.Errorf("Expected ErrTaprootDataIsNil, got %v", err) 128 | } 129 | 130 | _, err = pay.TaprootAddress() 131 | if err != payment.ErrTaprootDataIsNil { 132 | t.Errorf("Expected ErrTaprootDataIsNil, got %v", err) 133 | } 134 | } 135 | 136 | func TestTaprootAddressWithoutNetwork(t *testing.T) { 137 | p2tr, err := payment.FromTaprootScriptTree( 138 | internalKey.PubKey(), 139 | testTree, 140 | nil, 141 | blindingKey.PubKey(), 142 | ) 143 | 144 | p2tr.Network = nil 145 | 146 | if err != nil { 147 | t.Error(err) 148 | } 149 | 150 | _, err = p2tr.ConfidentialTaprootAddress() 151 | if err != payment.ErrNetworkIsNil { 152 | t.Errorf("Expected ErrNetworkIsNil, got %v", err) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /payment/payment_test.go: -------------------------------------------------------------------------------- 1 | package payment_test 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | 7 | "github.com/btcsuite/btcd/btcec/v2" 8 | "github.com/stretchr/testify/assert" 9 | "github.com/vulpemventures/go-elements/network" 10 | "github.com/vulpemventures/go-elements/payment" 11 | ) 12 | 13 | const ( 14 | privKeyHex1 = "1cc080a4cd371eafcad489a29664af6a7276b362fe783443ce036552482b971d" 15 | privKeyHex2 = "4d6718d4a02f774e752faa97e2c3b70db6b9d9ed5bd2fcecb093bd650f449a51" 16 | ) 17 | 18 | var privateKeyBytes1, _ = hex.DecodeString(privKeyHex1) 19 | var privateKeyBytes2, _ = hex.DecodeString(privKeyHex2) 20 | 21 | func TestLegacyPubkeyHash(t *testing.T) { 22 | _, publicKey := btcec.PrivKeyFromBytes(privateKeyBytes1) 23 | 24 | pay := payment.FromPublicKey(publicKey, &network.Regtest, nil) 25 | addr, err := pay.PubKeyHash() 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | expected := "2dxEMfPLNa6rZRAfPe7wNWoaUptyBzQ2Zva" 30 | assert.Equal(t, expected, addr) 31 | } 32 | 33 | func TestSegwitPubkeyHash(t *testing.T) { 34 | _, publicKey := btcec.PrivKeyFromBytes(privateKeyBytes1) 35 | 36 | pay := payment.FromPublicKey(publicKey, &network.Regtest, nil) 37 | addr, err := pay.WitnessPubKeyHash() 38 | if err != nil { 39 | t.Error(err) 40 | } 41 | expected := "ert1qlg343tpldc4wvjxn3jdq2qs35r8j5yd5kjfrrt" 42 | assert.Equal(t, expected, addr) 43 | } 44 | 45 | func TestLegacyScriptHash(t *testing.T) { 46 | _, publicKey := btcec.PrivKeyFromBytes(privateKeyBytes1) 47 | p2wpkh := payment.FromPublicKey(publicKey, &network.Regtest, nil) 48 | 49 | p2sh, err := payment.FromPayment(p2wpkh) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | addr, err := p2sh.ScriptHash() 54 | if err != nil { 55 | t.Error(err) 56 | } 57 | expectedAddr := "XZavBojABpfXhPWkw7y9YYFNAezUHZR47m" 58 | assert.Equal(t, expectedAddr, addr) 59 | } 60 | 61 | func TestSegwitScriptHash(t *testing.T) { 62 | redeemScript := "52410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959" + 63 | "f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d0" + 64 | "8ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09" + 65 | "b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950" + 66 | "cfe52a52ae" 67 | redeemScriptBytes, err := hex.DecodeString(redeemScript) 68 | if err != nil { 69 | t.Error(err) 70 | } 71 | 72 | p2ms, err := payment.FromScript( 73 | redeemScriptBytes, 74 | &network.Regtest, 75 | nil, 76 | ) 77 | if err != nil { 78 | t.Error(err) 79 | } 80 | 81 | p2wsh, err := payment.FromPayment(p2ms) 82 | if err != nil { 83 | t.Error(err) 84 | } 85 | 86 | p2wshAddress, err := p2wsh.WitnessScriptHash() 87 | if err != nil { 88 | t.Error(err) 89 | } 90 | if p2wshAddress != "ert1q2z45rh444qmeand48lq0wp3jatxs2nzh"+ 91 | "492ds9s5yscv2pplxwesajz7q3" { 92 | t.Errorf("TestSegwitAddress: error when encoding segwit") 93 | } 94 | } 95 | 96 | func TestMultisig(t *testing.T) { 97 | _, publicKey1 := btcec.PrivKeyFromBytes(privateKeyBytes1) 98 | _, publicKey2 := btcec.PrivKeyFromBytes(privateKeyBytes2) 99 | 100 | p2ms, err := payment.FromPublicKeys( 101 | []*btcec.PublicKey{publicKey1, publicKey2}, 102 | 2, 103 | &network.Regtest, 104 | nil, 105 | ) 106 | if err != nil { 107 | t.Error(err) 108 | } 109 | 110 | expectedRedeemScript := "5221036f5646ed688b9279369da0a4ad78953ae7e6d300436" + 111 | "ca8a3264360efe38236e321023c61f59e9a3a3eb01c3ed0cf967ad217153944bcf2498a" + 112 | "8fd6e70b27c7ab6ee652ae" 113 | assert.Equal(t, expectedRedeemScript, hex.EncodeToString(p2ms.Redeem.Script)) 114 | 115 | p2wshAddr, err := p2ms.WitnessScriptHash() 116 | if err != nil { 117 | t.Error(err) 118 | } 119 | expectedAddr := 120 | "ert1q3pa0pn2zef7eh2wuj4nqzas3xfzap79dful920kv6fuey592ujvs274fsu" 121 | assert.Equal(t, expectedAddr, p2wshAddr) 122 | 123 | p2shAddr, err := p2ms.ScriptHash() 124 | if err != nil { 125 | t.Error(err) 126 | } 127 | expectedAddr = "XLggw3oXkn4QwAkNt5uG8EBKTuGf69BJJG" 128 | assert.Equal(t, expectedAddr, p2shAddr) 129 | } 130 | 131 | func TestLegacyPubKeyHashConfidential(t *testing.T) { 132 | pubkeyBytes, _ := hex.DecodeString( 133 | "030000000000000000000000000000000000000000000000000000000000000001", 134 | ) 135 | pubkey, err := btcec.ParsePubKey(pubkeyBytes) 136 | if err != nil { 137 | t.Fatal(err) 138 | } 139 | 140 | pay := payment.FromPublicKey(pubkey, &network.Liquid, pubkey) 141 | address, err := pay.ConfidentialPubKeyHash() 142 | if err != nil { 143 | t.Fatal(err) 144 | } 145 | expected := "VTpzxkqVGbraaCz18fQ2GxLvZkupCi2MPtUdt9ygAEeZ8v9gZPtkD5RUcap55" + 146 | "WZ3aVsbUG6TsQvXc8R3" 147 | assert.Equal(t, expected, address) 148 | } 149 | 150 | func TestLegacyScriptHashConfidential(t *testing.T) { 151 | script, _ := hex.DecodeString( 152 | "a9149f840a5fc02407ef0ad499c2ec0eb0b942fb008687", 153 | ) 154 | pubkeyBytes, _ := hex.DecodeString( 155 | "030000000000000000000000000000000000000000000000000000000000000001", 156 | ) 157 | blindingKey, err := btcec.ParsePubKey(pubkeyBytes) 158 | if err != nil { 159 | t.Fatal(err) 160 | } 161 | 162 | pay, err := payment.FromScript(script, &network.Liquid, blindingKey) 163 | if err != nil { 164 | t.Fatal(err) 165 | } 166 | addr, err := pay.ConfidentialScriptHash() 167 | if err != nil { 168 | t.Fatal(err) 169 | } 170 | expected := "VJLCUu2hpcjPaTGMnANXni8wVYjsCAiTEznE5zgRZZyAWXE2P6rz6DvphBHSn" + 171 | "7iz4w9sLb3mFSHGJbte" 172 | assert.Equal(t, expected, addr) 173 | } 174 | 175 | func TestSegwitPubKeyHashConfidential(t *testing.T) { 176 | pubkeyBytes, _ := hex.DecodeString("030000000000000000000000000000000000000000000000000000000000000001") 177 | pubkey, err := btcec.ParsePubKey(pubkeyBytes) 178 | if err != nil { 179 | t.Fatal(err) 180 | } 181 | 182 | pay := payment.FromPublicKey(pubkey, &network.Liquid, pubkey) 183 | address, err := pay.ConfidentialWitnessPubKeyHash() 184 | if err != nil { 185 | t.Fatal(err) 186 | } 187 | 188 | expected := "lq1qqvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqz95tn" + 189 | "y4ul3zq2qcskw55h5rhzymdpv5dzw6hr8jz3tq5y" 190 | assert.Equal(t, expected, address) 191 | } 192 | 193 | func TestSegwitScriptHashConfidential(t *testing.T) { 194 | script, _ := hex.DecodeString( 195 | "0014d0d1f8c5b1815bc471aef4f4720c64ecac38dfa501c0aac94f1434a866a02ae0", 196 | ) 197 | pubkeyBytes, _ := hex.DecodeString( 198 | "030000000000000000000000000000000000000000000000000000000000000001", 199 | ) 200 | blindingKey, err := btcec.ParsePubKey(pubkeyBytes) 201 | if err != nil { 202 | t.Fatal(err) 203 | } 204 | 205 | pay, err := payment.FromScript(script, &network.Liquid, blindingKey) 206 | if err != nil { 207 | t.Fatal(err) 208 | } 209 | 210 | addr, err := pay.ConfidentialWitnessScriptHash() 211 | if err != nil { 212 | t.Fatal(err) 213 | } 214 | 215 | expected := "lq1qqvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqr5x3l" + 216 | "rzmrq2mc3c6aa85wgxxfm9v8r062qwq4ty579p54pn2q2hq6f9r3gz0h4tn" 217 | assert.Equal(t, expected, addr) 218 | } 219 | -------------------------------------------------------------------------------- /pegin/pegin_test.go: -------------------------------------------------------------------------------- 1 | package pegin 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "testing" 7 | 8 | "github.com/vulpemventures/go-elements/block" 9 | "github.com/vulpemventures/go-elements/elementsutil" 10 | "github.com/vulpemventures/go-elements/network" 11 | "github.com/vulpemventures/go-elements/transaction" 12 | 13 | "github.com/btcsuite/btcd/chaincfg" 14 | "github.com/btcsuite/btcd/wire" 15 | 16 | "github.com/stretchr/testify/assert" 17 | ) 18 | 19 | func TestStripWitnessFromBtcTx(t *testing.T) { 20 | txHex := "020000000001019b7aefabe954160747e2f3ebe27940788abb427ee7dde2c857bf95f7ce8ff2990000000017160014ca4b5c9294aa8b827a639d2610d5747d906488d8feffffff0200e1f5050000000017a91472c44f957fc011d97e3406667dca5b1c930c40268710161a1e0100000017a914a8fcb1a6a1922012bae14c54caf510326dfa47f98702473044022049500e2cc5b09ec944d9a362bf1789dce07ce81e31a1cb0831a2b3906d53fff902201037a734559bfbfeeff44bbffa66dd14de6c0631eb702a3d29a1349db52010250121038a91c84aaeb1a41fdda631cf02b19fab2329c4ba3a9fe435339b380d80c5374a66000000" 21 | expected := "02000000019b7aefabe954160747e2f3ebe27940788abb427ee7dde2c857bf95f7ce8ff2990000000017160014ca4b5c9294aa8b827a639d2610d5747d906488d8feffffff0200e1f5050000000017a91472c44f957fc011d97e3406667dca5b1c930c40268710161a1e0100000017a914a8fcb1a6a1922012bae14c54caf510326dfa47f98766000000" 22 | 23 | txBytes, err := hex.DecodeString(txHex) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | 28 | stripedTx, err := StripWitnessFromBtcTx(txBytes) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | 33 | assert.Equal(t, expected, hex.EncodeToString(stripedTx)) 34 | } 35 | 36 | func TestSerializeValue(t *testing.T) { 37 | var value int64 = 1000000000 38 | expected := "00ca9a3b00000000" 39 | 40 | serializedValue, err := SerializeValue(value) 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | 45 | assert.Equal(t, expected, hex.EncodeToString(serializedValue)) 46 | } 47 | 48 | func TestSerializeTxOutProof(t *testing.T) { 49 | txHex := "02000000000101f98f9665468889b67336bdeda87aa7556b465dca2615b42b225f56cd5c2b054c01000000171600140626f9faded4f428f44e87a13ccad4ca464e07b6feffffff0200e1f5050000000017a91472c44f957fc011d97e3406667dca5b1c930c402687182824180100000017a91491c518a33a7061958d1f33b1965ff1dc70abb45f87024730440220121bd7cf7700a670f53023c83e50548d52ceb3df4b4636e3ea6d3ba34137424a02200d6614a949c9cd7bb02fae505b4626b73e3c06901398ae4fcc422d2bd9e89df601210367ffea4c8a61f790a8456a170a744e2f588622a3b0ecc28633e70370bfc9baec00000000" 50 | txBytes, err := hex.DecodeString(txHex) 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | 55 | var tx wire.MsgTx 56 | err = tx.BtcDecode( 57 | bytes.NewReader(txBytes), 58 | wire.ProtocolVersion, 59 | wire.LatestEncoding, 60 | ) 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | txHash := tx.TxHash().String() 65 | t.Log(txHash) 66 | 67 | txOutProof := "00000020fa203873242b50221d533889f7b7906a3946b505b8376e2d751e700010367d5849dfb02557386193ebf0f92a606958cbf249662dddecc55bd9f68d20aec0a226308f9e60ffff7f20000000000200000002d45deb54cebe4d401c5b3504a2c0f1114f29ddd959125c222034cf24484507229daedfd4722343ffb707185e16e9b0479dba6ed147b82c720efc96c3179b5db40105" 68 | 69 | merkleBlock, err := block.NewMerkleBlockFromHex(txOutProof) 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | 74 | exists := false 75 | for _, v := range merkleBlock.PartialMerkleTree.TxHashes { 76 | if hex.EncodeToString(elementsutil.ReverseBytes(v)) == txHash { 77 | exists = true 78 | } 79 | } 80 | 81 | assert.Equal(t, true, exists) 82 | } 83 | 84 | func TestSerializePeginWitness(t *testing.T) { 85 | claimScript := "0014f66ddc42aa6626cc7ff78ef28e333ef9c37a0da3" 86 | btcTxHex := "020000000001017637eca164aaf48a5bf200b46457053ea41ed12efc58fb0039c674c6c3c526700000000017160014f410f2ef1b4a9437f690691898798823b466e3d1feffffff0200e1f5050000000017a91472c44f957fc011d97e3406667dca5b1c930c4026878053e90b0000000017a914d28abf72575acf237f29fa17f7ec2ac4eff56d77870247304402207715a047ae2fd9c8f9b1dd9efafc97dcaf7af5c2fec95b735dab4c73a1004934022037b5ba40c7c27497ad73e915f0f12c4bb2443d21839e1ce7879d15b85231ffd901210231881188f837f134f4afea25ce26c36b6bf01bcf1b76862751b95ea5d781278594000000" 87 | btcTxOutProof := "00000030b60a7067a3b57066cb0b1a17b4f4e2883c3352b3ffb74ef92b833df29818d535c8c1d8494c95182deea29dd2d02738f043e5ae6178a3a3f4478d1c85302dd3e7cfa0c060ffff7f20000000000200000002e6e5209a17f2ad4482a618a58cc9d7d772a53de242867066c786868289d234663177663c3649d6a5dc057bbbb2f33c176d6b52223b180084a0d73618e44259cf0105" 88 | 89 | peggedAssetBytes, err := hex.DecodeString(network.Regtest.AssetID) 90 | if err != nil { 91 | t.Fatal(err) 92 | } 93 | 94 | parentBlockHash := "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206" 95 | parentBlockHashBytes, err := hex.DecodeString(parentBlockHash) 96 | if err != nil { 97 | t.Fatal(err) 98 | } 99 | 100 | fedpegScriptBytes, err := hex.DecodeString("51") 101 | if err != nil { 102 | t.Fatal(err) 103 | } 104 | 105 | btcTxBytes, err := hex.DecodeString(btcTxHex) 106 | if err != nil { 107 | t.Fatal(err) 108 | } 109 | 110 | btcTxOutProofBytes, err := hex.DecodeString(btcTxOutProof) 111 | if err != nil { 112 | t.Fatal(err) 113 | } 114 | 115 | claimScriptBytes, err := hex.DecodeString(claimScript) 116 | if err != nil { 117 | t.Fatal(err) 118 | } 119 | 120 | tx, err := Claim( 121 | &chaincfg.RegressionNetParams, 122 | false, 123 | append([]byte{0x01}, elementsutil.ReverseBytes(peggedAssetBytes)...), 124 | parentBlockHashBytes, 125 | fedpegScriptBytes, 126 | fedpegScriptBytes, // contract = fedpegscript here 127 | btcTxBytes, 128 | btcTxOutProofBytes, 129 | claimScriptBytes, 130 | 1, 131 | ) 132 | if err != nil { 133 | t.Fatal(err) 134 | } 135 | 136 | peginWitness := tx.Inputs[0].PeginWitness 137 | assert.NotNil(t, peginWitness) 138 | 139 | txHex, err := tx.ToHex() 140 | if err != nil { 141 | t.Fatal(err) 142 | } 143 | 144 | transactionAfterToHex, err := transaction.NewTxFromHex(txHex) 145 | if err != nil { 146 | t.Fatal(err) 147 | } 148 | 149 | peginWitnessAfterToHex := transactionAfterToHex.Inputs[0].PeginWitness 150 | assert.Equal(t, peginWitness, peginWitnessAfterToHex) 151 | } 152 | -------------------------------------------------------------------------------- /pegincontract/contract.go: -------------------------------------------------------------------------------- 1 | //go:build cgo 2 | 3 | package pegincontract 4 | 5 | import ( 6 | "bytes" 7 | "crypto/hmac" 8 | "crypto/sha256" 9 | "errors" 10 | 11 | "github.com/btcsuite/btcd/txscript" 12 | 13 | "github.com/vulpemventures/go-elements/address" 14 | "github.com/vulpemventures/go-secp256k1-zkp" 15 | ) 16 | 17 | const ( 18 | pubKeyLen = 33 19 | ) 20 | 21 | func Calculate( 22 | federationScript []byte, 23 | scriptPubKey []byte, 24 | ) ([]byte, error) { 25 | contract := make([]byte, 0) 26 | 27 | ctx, err := secp256k1.ContextCreate(secp256k1.ContextBoth) 28 | if err != nil { 29 | return nil, err 30 | } 31 | defer secp256k1.ContextDestroy(ctx) 32 | 33 | isLiquidV1Watchman, err := IsLiquidV1(federationScript) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | pops, err := address.ParseScript( 39 | federationScript, 40 | ) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | liquidOpElseFound := false 46 | for _, v := range pops { 47 | // For liquidv1 initial watchman template, don't tweak emergency keys 48 | if isLiquidV1Watchman && v.Opcode.Value == txscript.OP_ELSE { 49 | liquidOpElseFound = true 50 | } 51 | 52 | if len(v.Data) == pubKeyLen && !liquidOpElseFound { 53 | mac := hmac.New(sha256.New, v.Data) 54 | mac.Write(scriptPubKey) 55 | tweak := mac.Sum(nil) 56 | 57 | _, watchman, err := secp256k1.EcPubkeyParse(ctx, v.Data) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | _, tweaked, err := secp256k1.EcPubkeyParse(ctx, v.Data) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | _, err = secp256k1.EcPubKeyTweakAdd(ctx, tweaked, tweak) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | _, newPub, err := secp256k1.EcPubkeySerialize( 73 | ctx, 74 | tweaked, 75 | secp256k1.EcCompressed, 76 | ) 77 | 78 | contract = append(contract, byte(len(newPub))) 79 | contract = append(contract, newPub...) 80 | 81 | // Sanity checks to reduce pegin risk. If the tweaked 82 | // value flips a bit, we may lose pegin funds irretrievably. 83 | // We take the tweak, derive its pubkey and check that 84 | // `tweaked - watchman = tweak` to check the computation 85 | // two different ways 86 | _, tweaked2, err := secp256k1.EcPubkeyCreate(ctx, tweak) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | _, err = secp256k1.EcPubKeyNegate(ctx, watchman) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | vPoint := []*secp256k1.PublicKey{watchman, tweaked} 97 | _, maybeTweaked2, err := secp256k1.EcPubKeyCombine(ctx, vPoint) 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | _, tweaked2Bytes, err := secp256k1.EcPubkeySerialize( 103 | ctx, 104 | tweaked2, 105 | secp256k1.EcUncompressed, 106 | ) 107 | 108 | _, maybeTweaked2Bytes, err := secp256k1.EcPubkeySerialize( 109 | ctx, 110 | maybeTweaked2, 111 | secp256k1.EcUncompressed, 112 | ) 113 | 114 | if !bytes.Equal(tweaked2Bytes[:64], maybeTweaked2Bytes[:64]) { 115 | return nil, errors.New("sanity check failed") 116 | } 117 | 118 | } else { 119 | if len(v.Data) > 0 { 120 | contract = append(contract, byte(len(v.Data))) 121 | contract = append(contract, v.Data...) 122 | } else { 123 | contract = append(contract, v.Opcode.Value) 124 | } 125 | } 126 | 127 | } 128 | 129 | return contract, nil 130 | } 131 | 132 | // IsLiquidV1 checks weather provided fedpeg script is of v1 or newer 133 | // Consensus-critical. Matching against telescoped multisig used on Liquid v1 134 | func IsLiquidV1(script []byte) (bool, error) { 135 | pops, err := address.ParseScript(script) 136 | if err != nil { 137 | return false, err 138 | } 139 | 140 | // Stack depth check for branch choice 141 | if pops[0].Opcode.Value != txscript.OP_DEPTH { 142 | return false, nil 143 | } 144 | 145 | // Take in value, then check equality 146 | if pops[2].Opcode.Value != txscript.OP_EQUAL { 147 | return false, nil 148 | } 149 | 150 | // IF EQUAL 151 | if pops[3].Opcode.Value != txscript.OP_IF { 152 | return false, nil 153 | } 154 | 155 | // Take in value k, make sure minimally encoded number from 1 to 16 156 | if pops[4].Opcode.Value > txscript.OP_16 || 157 | (pops[4].Opcode.Value < txscript.OP_1NEGATE && !checkMinimalPush(pops[4])) { 158 | 159 | return false, nil 160 | } 161 | 162 | // Iterate through multisig stuff until ELSE is hit 163 | opElseFound := false 164 | opElseIndex := -1 165 | for i := 5; i < len(pops); i++ { 166 | if pops[i].Opcode.Value == txscript.OP_ELSE { 167 | opElseFound = true 168 | opElseIndex = i 169 | } 170 | } 171 | if !opElseFound { 172 | return false, err 173 | } 174 | 175 | // Take minimally-encoded CSV push number k' 176 | if pops[opElseIndex+1].Opcode.Value > txscript.OP_16 || 177 | (pops[opElseIndex+1].Opcode.Value < txscript.OP_1NEGATE && !checkMinimalPush(pops[opElseIndex+1])) { 178 | 179 | return false, nil 180 | } 181 | 182 | // CSV 183 | if pops[opElseIndex+2].Opcode.Value != txscript.OP_CHECKSEQUENCEVERIFY { 184 | return false, nil 185 | } 186 | 187 | // Drop the CSV number 188 | if pops[opElseIndex+3].Opcode.Value != txscript.OP_DROP { 189 | return false, nil 190 | } 191 | 192 | // Take the minimally-encoded n of k-of-n multisig arg 193 | if pops[opElseIndex+4].Opcode.Value > txscript.OP_16 || 194 | (pops[opElseIndex+4].Opcode.Value < txscript.OP_1NEGATE && !checkMinimalPush(pops[opElseIndex+4])) { 195 | 196 | return false, nil 197 | } 198 | 199 | // Iterate through multisig stuff until ENDIF is hit 200 | opEndIfFound := false 201 | opEndIfIndex := -1 202 | for i := opElseIndex + 5; i < len(pops); i++ { 203 | if pops[i].Opcode.Value == txscript.OP_ENDIF { 204 | opEndIfFound = true 205 | opEndIfIndex = i 206 | } 207 | } 208 | if !opEndIfFound { 209 | return false, err 210 | } 211 | 212 | // CHECKMULTISIG 213 | if pops[opEndIfIndex+1].Opcode.Value != txscript.OP_CHECKMULTISIG { 214 | return false, nil 215 | } 216 | 217 | return true, err 218 | } 219 | 220 | func checkMinimalPush(parsedOpcode address.ParsedOpcode) bool { 221 | if len(parsedOpcode.Data) == 0 { 222 | // Should have used OP_0. 223 | return parsedOpcode.Opcode.Value == txscript.OP_0 224 | } else if len(parsedOpcode.Data) == 1 && 225 | parsedOpcode.Data[0] >= 1 && 226 | parsedOpcode.Data[0] <= 16 { 227 | // Should have used OP_1 .. OP_16. 228 | return false 229 | } else if len(parsedOpcode.Data) == 1 && parsedOpcode.Data[0] == 0x81 { 230 | // Should have used OP_1NEGATE. 231 | return false 232 | } else if len(parsedOpcode.Data) <= 75 { 233 | // Must have used a direct push (opcode indicating number of bytes pushed + those bytes). 234 | return int(parsedOpcode.Opcode.Value) == len(parsedOpcode.Data) 235 | } else if len(parsedOpcode.Data) <= 255 { 236 | // Must have used OP_PUSHDATA. 237 | return int(parsedOpcode.Opcode.Value) == txscript.OP_PUSHDATA1 238 | } else if len(parsedOpcode.Data) <= 65535 { 239 | // Must have used OP_PUSHDATA2. 240 | return int(parsedOpcode.Opcode.Value) == txscript.OP_PUSHDATA2 241 | } 242 | return true 243 | } 244 | -------------------------------------------------------------------------------- /pegincontract/contract_nocgo.go: -------------------------------------------------------------------------------- 1 | //go:build !cgo 2 | 3 | package pegincontract 4 | 5 | import ( 6 | "errors" 7 | ) 8 | 9 | var errNoCGO = errors.New("pegin contract requires CGO") 10 | 11 | // Calculate calculates the pegin contract. 12 | // This is a no-op implementation when CGO is disabled. 13 | func Calculate( 14 | federationScript []byte, 15 | scriptPubKey []byte, 16 | ) ([]byte, error) { 17 | return nil, errNoCGO 18 | } 19 | 20 | // IsLiquidV1 checks if the script is a Liquid V1 script. 21 | // This is a no-op implementation when CGO is disabled. 22 | func IsLiquidV1(script []byte) (bool, error) { 23 | return false, errNoCGO 24 | } 25 | -------------------------------------------------------------------------------- /pegincontract/contract_test.go: -------------------------------------------------------------------------------- 1 | package pegincontract 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | ) 7 | 8 | type NetworkType int 9 | 10 | const ( 11 | MainNet NetworkType = iota 12 | RegtestNet 13 | ) 14 | 15 | func TestIsLiquidV1(t *testing.T) { 16 | fedpegScriptV1 := "745c87635b21020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b678172612102675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af992102896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d4821029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c2102a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc40102102f8a00b269f8c5e59c67d36db3cdc11b11b21f64b4bffb2815e9100d9aa8daf072103079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b2103111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2210318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa08401742103230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de121035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a62103bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c2103cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d175462103d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d4248282103ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a5f6702c00fb275522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb5368ae" 17 | fedpegScriptV1Bytes, err := hex.DecodeString(fedpegScriptV1) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | 22 | fedpegScript := "512103dff4923d778550cc13ce0d887d737553b4b58f4e8e886507fc39f5e447b2186451ae" 23 | fedpegScriptBytes, err := hex.DecodeString(fedpegScript) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | 28 | type args struct { 29 | script []byte 30 | } 31 | tests := []struct { 32 | name string 33 | args args 34 | want bool 35 | wantErr bool 36 | }{ 37 | { 38 | name: "v1", 39 | args: args{ 40 | script: fedpegScriptV1Bytes, 41 | }, 42 | want: true, 43 | wantErr: false, 44 | }, 45 | { 46 | name: "not v1", 47 | args: args{ 48 | script: fedpegScriptBytes, 49 | }, 50 | want: false, 51 | wantErr: false, 52 | }, 53 | } 54 | for _, tt := range tests { 55 | t.Run(tt.name, func(t *testing.T) { 56 | got, err := IsLiquidV1(tt.args.script) 57 | if (err != nil) != tt.wantErr { 58 | t.Errorf("IsLiquidV1() error = %v, wantErr %v", err, tt.wantErr) 59 | return 60 | } 61 | if got != tt.want { 62 | t.Errorf("IsLiquidV1() got = %v, want %v", got, tt.want) 63 | } 64 | }) 65 | } 66 | } 67 | 68 | func TestCalculateContract(t *testing.T) { 69 | type args struct { 70 | federationScript string 71 | scriptPubKey string 72 | } 73 | tests := []struct { 74 | name string 75 | args args 76 | contract string 77 | wantErr bool 78 | }{ 79 | { 80 | name: "not v1", 81 | args: args{ 82 | federationScript: "52210307fd375ed7cced0f50723e3e1a97bbe7ccff7318c815df4e99a59bc94dbcd819210367c4f666f18279009c941e57fab3e42653c6553e5ca092c104d1db279e328a2852ae", 83 | scriptPubKey: "0014879008279c4e17fe0c61f9a84d82216cb81ddaff", 84 | }, 85 | contract: "522102210dc9cd9f5925bcd256283fe28f86ffddd57a81c6be52623811873625f2fc252102035b26deb0d0f817535461b727d77bda14f0addc4c73424db9aaa838c55bb23e52ae", 86 | wantErr: false, 87 | }, 88 | { 89 | name: "v1", 90 | args: args{ 91 | federationScript: "745c87635b21020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b678172612102675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af992102896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d4821029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c2102a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc40102102f8a00b269f8c5e59c67d36db3cdc11b11b21f64b4bffb2815e9100d9aa8daf072103079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b2103111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2210318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa08401742103230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de121035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a62103bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c2103cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d175462103d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d4248282103ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a5f6702c00fb275522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb5368ae", 92 | scriptPubKey: "0014f66f3797fce27b9e190f9e72798203443bb33ed5", 93 | }, 94 | contract: "745c87635b2103e67a1d3ca7d1dbf4c134ed3a1892f86db8235d45489961c47643c0e4916078bc21036132d580b3a0205d01873f6a1495f3ef24f6be3bad408e2c340e746cd9bc7836210260440b2b19a7ccb64b21089a542f34c7a2a08295772145acf4c06ef5c88be43e210248eb7484415570080e167f86f93fe3f7b2527c4c9848025bbf3ec7647fc218612103ca7489507aaa82a0e01af1fe9d065efd1646b8f5b664a2750639537600a78816210352049af330e49d9c1ac0f9c2b8256371ec1f2f3e9bfe77b41e541d5659e246512102af50adf71081e53d4db3764095d17a70b0520507026359bf9c0abc7c1fb99605210303d4978a7f1035feb7627f1dad0ad0ad42130641afcde19c768930ed997cd4e321030d82efb181bbeef0c81cd03f0e4f9b40f57ba6fa4f84214d65e3e18889ee2b6b210308c79da16b9b16c23a9093b68aa043d717c96e1d1b74673a847510f7ed044a48210290bfd959fed4502d367d1198da0383052d68110aa40fe61e55f3573166df1ad62103261130cbeb7efcad340d06dd90ee8c2849113e4faa02e0ac28064eb4dfb9dce62102180a7d96f8a040fd647f6b4a3981792c2fa165a784793dc15e9abbbe7edf547f21022b92fe69c0ee97d1e218ab79b72f2173686ccf7dc73989e724a9960a3835f2fb21038e9fe8af74632af44d5d57f20116ee735053eaeda787a3b7a560d9c6486dbf205f6702c00fb275522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb5368ae", 95 | wantErr: false, 96 | }, 97 | } 98 | for _, tt := range tests { 99 | t.Run(tt.name, func(t *testing.T) { 100 | fedpegScriptBytes, err := hex.DecodeString(tt.args.federationScript) 101 | if err != nil { 102 | t.Fatal(err) 103 | } 104 | 105 | scriptPubKeyBytes, err := hex.DecodeString(tt.args.scriptPubKey) 106 | if err != nil { 107 | t.Fatal(err) 108 | } 109 | 110 | contractBytes, err := Calculate(fedpegScriptBytes, scriptPubKeyBytes) 111 | if (err != nil) != tt.wantErr { 112 | t.Errorf("calculateContract() error = %v, wantErr %v", err, tt.wantErr) 113 | return 114 | } 115 | 116 | gotContract := hex.EncodeToString(contractBytes) 117 | 118 | if gotContract != tt.contract { 119 | t.Errorf("calculateContract() got = %v, want %v", gotContract, tt.contract) 120 | } 121 | }) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /pset/README.md: -------------------------------------------------------------------------------- 1 | # PSET 2 | 3 | A modification of the [BIP-174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki) standard for Partial Signed Elements Transaction. 4 | 5 | ## Changes from the standard reference 6 | 7 | This package is designed in order to apply the possible fewer changes to the reference spec so that it can be used for Elements unblinded and blinded transactions. 8 | 9 | Essentially this version of partial transaction uses an underlying Elements unsigned transaction instead of a Bitcoin one, and the partial input `WitnessUtxo` field represents an Elements output rather than a Bitcoin one. 10 | 11 | NOTE: The Elements implementation of PSET is under development at the moment (take a look [here]()) and this package will likely change in the future to adapt to this standard. 12 | 13 | ## Creator 14 | 15 | The creator is an exported factory function named simply `New` with the following signature: 16 | 17 | ``` 18 | func New(inputs []*transaction.TxInput, outputs []*transaction.TxOutput, version int32, nLockTime uint32) (*Pset, error) {} 19 | ``` 20 | 21 | The role of this function is to simply create an unsigned partial transaction wiwht the given inputs, outputs, version and locktime. 22 | The unblinded asset and amounts of the outputs are encoded into the "unsigned tx" field of the partial transaction. 23 | 24 | ## Updater 25 | 26 | The updater, as the name suggests, has the responsibility of updating the fields of any partial input or output. It consists of a collection of methods that, basically, has the purpose of adding any new field to an existing partial input (included issuance or reissuance placed in the unsigned tx) or output. 27 | It also allows to add new inputs or outputs to the underlying unsigned transaction. 28 | 29 | The updater can be instantiated by calling the `NewUpdater` factory function passing a partial transasction object. 30 | 31 | ## Blinder 32 | 33 | At the moment the blinder role is designed to blind ALL the outputs of the partial transaction, but this will change soon, letting one to blind only the set of outputs he wants. 34 | Also, this version of the blinder requires that all the private keys necessary to unblind all the confidential inputs used must be provided. 35 | Given this, the *pset* package is not useful in case multiple parties want to create a transaction by joining their inputs/outputs since they would need to reveal their blinding private keys and share them with the one encharged of assuming the blinder role. 36 | The *pset* package will change in the future to support the use case mentioned before, but this is not yet planned in the development. 37 | 38 | ## Signer 39 | 40 | The signer is in charge of checking that when adding a signature to an input of the pset, this is valid and that also the pset is correctly structured. 41 | Given that, this role is implemented as a function `Sign` of the `*Updater` type. 42 | This function accepts an input index, a signature, a public key, and one between a redeem or witness script and checks that the signature is valid against the given script and pubkey, along with setting the partial input's signature script to the one provided. 43 | 44 | ## Finalizer 45 | 46 | The finalizer takes a partial transaction and combines every input's `PartialSignature` into the final input's `SignatureScript`. After finalizing, the partial transaction is complete, and it's ready to be extracted from the `Pset` wrapper and broadcasted to the network. 47 | This role is accomplished by a `Finalize` function that accepts a `*Pset` instance and an input index, and performs the operations described above, returning an error if any occurs during the process. It previously checks that the provided partial transaction is valid in the sense that it's ready to be finalized; otherwise, an error is returned. 48 | A handy `FinalizeAll` that runs the above method for every input of the provided \*Pset is also exported. 49 | 50 | ## Extractor 51 | 52 | The extractor is a simple `Extract` function expecting a finalized partial transaction that returns the final signed transaction by adding the signatures of the partial inputs to the underlying unsigned transaction. 53 | -------------------------------------------------------------------------------- /pset/creator.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package pset 6 | 7 | import ( 8 | "github.com/btcsuite/btcd/btcutil/psbt" 9 | "github.com/vulpemventures/go-elements/transaction" 10 | ) 11 | 12 | // New on provision of an input and output 'skeleton' for the transaction, a 13 | // new partially populated PSET. The populated pset will include the 14 | // unsigned transaction, and the set of known inputs and outputs contained 15 | // within the unsigned transaction. The values of nLockTime and transaction 16 | // version (must be 1 of 2) must be specified here. Note that the default 17 | // nSequence value is wire.MaxTxInSequenceNum. 18 | // Referencing the PSBT BIP, this function serves the roles of the Creator. 19 | func New(inputs []*transaction.TxInput, 20 | outputs []*transaction.TxOutput, version int32, nLockTime uint32) (*Pset, error) { 21 | 22 | // Create the new struct; the input and output lists will be empty, the 23 | // unsignedTx object must be constructed and serialized, and that 24 | // serialization should be entered as the only entry for the 25 | // globalKVPairs list. 26 | // 27 | // Ensure that the version of the transaction is greater then our 28 | // minimum allowed transaction version. There must be one sequence 29 | // number per input. 30 | if version < psbt.MinTxVersion { 31 | return nil, psbt.ErrInvalidPsbtFormat 32 | } 33 | 34 | unsignedTx := transaction.NewTx(version) 35 | unsignedTx.Locktime = nLockTime 36 | for _, in := range inputs { 37 | unsignedTx.AddInput(transaction.NewTxInput(in.Hash, in.Index)) 38 | } 39 | for _, out := range outputs { 40 | unsignedTx.AddOutput(out) 41 | } 42 | 43 | // The input and output lists are empty, but there is a list of those 44 | // two lists, and each one must be of length matching the unsigned 45 | // transaction; the unknown list can be nil. 46 | pInputs := make([]PInput, len(unsignedTx.Inputs)) 47 | pOutputs := make([]POutput, len(unsignedTx.Outputs)) 48 | 49 | // This new Psbt is "raw" and contains no key-value fields, so sanity 50 | // checking with c.Cpsbt.SanityCheck() is not required. 51 | return &Pset{ 52 | UnsignedTx: unsignedTx, 53 | Inputs: pInputs, 54 | Outputs: pOutputs, 55 | Unknowns: nil, 56 | }, nil 57 | } 58 | -------------------------------------------------------------------------------- /pset/creator_test.go: -------------------------------------------------------------------------------- 1 | package pset 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "io/ioutil" 7 | "testing" 8 | 9 | "github.com/vulpemventures/go-elements/elementsutil" 10 | "github.com/vulpemventures/go-elements/transaction" 11 | ) 12 | 13 | func TestCreator(t *testing.T) { 14 | file, err := ioutil.ReadFile("data/creator.json") 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | var tests []map[string]interface{} 19 | err = json.Unmarshal(file, &tests) 20 | 21 | for _, v := range tests { 22 | inputs := []*transaction.TxInput{} 23 | for _, vIn := range v["inputs"].([]interface{}) { 24 | in := vIn.(map[string]interface{}) 25 | inHash, _ := hex.DecodeString(in["hash"].(string)) 26 | inIndex := uint32(in["index"].(float64)) 27 | inHash = elementsutil.ReverseBytes(inHash) 28 | inputs = append(inputs, transaction.NewTxInput(inHash, inIndex)) 29 | } 30 | 31 | outputs := []*transaction.TxOutput{} 32 | for _, vOut := range v["outputs"].([]interface{}) { 33 | out := vOut.(map[string]interface{}) 34 | outAsset, _ := hex.DecodeString(out["asset"].(string)) 35 | outAsset = append([]byte{0x01}, elementsutil.ReverseBytes(outAsset)...) 36 | outValue, _ := elementsutil.ValueToBytes(uint64(out["value"].(float64))) 37 | outScript, _ := hex.DecodeString(out["script"].(string)) 38 | outputs = append(outputs, transaction.NewTxOutput(outAsset, outValue[:], outScript)) 39 | } 40 | 41 | p, err := New(inputs, outputs, 2, 0) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | 46 | base64Res, err := p.ToBase64() 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | hexRes, err := p.ToHex() 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | expectedBase64 := v["expectedBase64"].(string) 55 | expectedHex := v["expectedHex"].(string) 56 | if base64Res != expectedBase64 { 57 | t.Fatalf("Got: %s, expected: %s", base64Res, expectedBase64) 58 | } 59 | if hexRes != expectedHex { 60 | t.Fatalf("Got: %s, expected: %s", hexRes, expectedHex) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /pset/data/creator.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "hash": "9d64f0343e264f9992aa024185319b349586ec4cbbfcedcda5a05678ab10e580", 6 | "index": 0 7 | } 8 | ], 9 | "outputs": [ 10 | { 11 | "value": 50000000, 12 | "script": "76a91439397080b51ef22c59bd7469afacffbeec0da12e88ac", 13 | "asset": "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225" 14 | }, 15 | { 16 | "value": 49999100, 17 | "script": "76a914659bedb5d3d3c7ab12d7f85323c3a1b6c060efbe88ac", 18 | "asset": "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225" 19 | }, 20 | { 21 | "value": 500, 22 | "script": "", 23 | "asset": "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225" 24 | } 25 | ], 26 | "expectedBase64": "cHNldP8BAOoCAAAAAAGA5RCreFagpc3t/LtM7IaVNJsxhUECqpKZTyY+NPBknQAAAAAA/////wMBJbJRBw4pyhkEPPM8zXMk4t2rA+zErgted8T8Dlz2yVoBAAAAAAL68IAAGXapFDk5cIC1HvIsWb10aa+s/77sDaEuiKwBJbJRBw4pyhkEPPM8zXMk4t2rA+zErgted8T8Dlz2yVoBAAAAAAL67PwAGXapFGWb7bXT08erEtf4UyPDobbAYO++iKwBJbJRBw4pyhkEPPM8zXMk4t2rA+zErgted8T8Dlz2yVoBAAAAAAAAAfQAAAAAAAAAAAAAAA==", 27 | "expectedHex": "70736574ff0100ea02000000000180e510ab7856a0a5cdedfcbb4cec8695349b31854102aa92994f263e34f0649d0000000000ffffffff030125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000002faf080001976a91439397080b51ef22c59bd7469afacffbeec0da12e88ac0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000002faecfc001976a914659bedb5d3d3c7ab12d7f85323c3a1b6c060efbe88ac0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a0100000000000001f40000000000000000000000" 28 | }, 29 | { 30 | "inputs": [ 31 | { 32 | "hash": "404015a2b064616e412a62c1b5760f4f953f705016efee584cc8ac5d3b1764e7", 33 | "index": 1 34 | }, 35 | { 36 | "hash": "2825e7accc2999fc61b4b0ae6586476b8c63433f29d89523532e7722976aef38", 37 | "index": 1 38 | } 39 | ], 40 | "outputs": [ 41 | { 42 | "value": 120000000, 43 | "script": "76a91439397080b51ef22c59bd7469afacffbeec0da12e88ac", 44 | "asset": "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225" 45 | }, 46 | { 47 | "value": 79999400, 48 | "script": "76a91450a410115f0a7d8a99472e47d1928ff8086948c888ac", 49 | "asset": "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225" 50 | }, 51 | { 52 | "value": 600, 53 | "script": "", 54 | "asset": "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225" 55 | } 56 | ], 57 | "expectedBase64": "cHNldP8BAP0TAQIAAAAAAudkFztdrMhMWO7vFlBwP5VPD3a1wWIqQW5hZLCiFUBAAQAAAAD/////OO9qlyJ3LlMjldgpP0NjjGtHhmWusLRh/JkpzKznJSgBAAAAAP////8DASWyUQcOKcoZBDzzPM1zJOLdqwPsxK4LXnfE/A5c9slaAQAAAAAHJw4AABl2qRQ5OXCAtR7yLFm9dGmvrP++7A2hLoisASWyUQcOKcoZBDzzPM1zJOLdqwPsxK4LXnfE/A5c9slaAQAAAAAExLGoABl2qRRQpBARXwp9iplHLkfRko/4CGlIyIisASWyUQcOKcoZBDzzPM1zJOLdqwPsxK4LXnfE/A5c9slaAQAAAAAAAAJYAAAAAAAAAAAAAAAA", 58 | "expectedHex": "70736574ff0100fd1301020000000002e764173b5dacc84c58eeef1650703f954f0f76b5c1622a416e6164b0a21540400100000000ffffffff38ef6a9722772e532395d8293f43638c6b478665aeb0b461fc9929ccace725280100000000ffffffff030125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000007270e00001976a91439397080b51ef22c59bd7469afacffbeec0da12e88ac0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000004c4b1a8001976a91450a410115f0a7d8a99472e47d1928ff8086948c888ac0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000000000258000000000000000000000000" 59 | }, 60 | { 61 | "inputs": [ 62 | { 63 | "hash": "52439251d19b8e9572dd8cd91f176be66ed42f3fbadd5554b9cc892f13e94c7c", 64 | "index": 1 65 | } 66 | ], 67 | "outputs": [ 68 | { 69 | "value": 120000000, 70 | "script": "76a91439397080b51ef22c59bd7469afacffbeec0da12e88ac", 71 | "asset": "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225" 72 | }, 73 | { 74 | "value": 79999400, 75 | "script": "76a91450a410115f0a7d8a99472e47d1928ff8086948c888ac", 76 | "asset": "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225" 77 | }, 78 | { 79 | "value": 600, 80 | "script": "", 81 | "asset": "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225" 82 | } 83 | ], 84 | "expectedBase64": "cHNldP8BAOoCAAAAAAF8TOkTL4nMuVRV3bo/L9Ru5msXH9mM3XKVjpvRUZJDUgEAAAAA/////wMBJbJRBw4pyhkEPPM8zXMk4t2rA+zErgted8T8Dlz2yVoBAAAAAAcnDgAAGXapFDk5cIC1HvIsWb10aa+s/77sDaEuiKwBJbJRBw4pyhkEPPM8zXMk4t2rA+zErgted8T8Dlz2yVoBAAAAAATEsagAGXapFFCkEBFfCn2KmUcuR9GSj/gIaUjIiKwBJbJRBw4pyhkEPPM8zXMk4t2rA+zErgted8T8Dlz2yVoBAAAAAAAAAlgAAAAAAAAAAAAAAA==", 85 | "expectedHex": "70736574ff0100ea0200000000017c4ce9132f89ccb95455ddba3f2fd46ee66b171fd98cdd72958e9bd1519243520100000000ffffffff030125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000007270e00001976a91439397080b51ef22c59bd7469afacffbeec0da12e88ac0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000004c4b1a8001976a91450a410115f0a7d8a99472e47d1928ff8086948c888ac0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a0100000000000002580000000000000000000000" 86 | } 87 | ] 88 | -------------------------------------------------------------------------------- /pset/data/extractor.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "base64": "cHNldP8BAOoCAAAAAAGA5RCreFagpc3t/LtM7IaVNJsxhUECqpKZTyY+NPBknQAAAAAA/////wMBJbJRBw4pyhkEPPM8zXMk4t2rA+zErgted8T8Dlz2yVoBAAAAAAL68IAAGXapFDk5cIC1HvIsWb10aa+s/77sDaEuiKwBJbJRBw4pyhkEPPM8zXMk4t2rA+zErgted8T8Dlz2yVoBAAAAAAL67PwAGXapFGWb7bXT08erEtf4UyPDobbAYO++iKwBJbJRBw4pyhkEPPM8zXMk4t2rA+zErgted8T8Dlz2yVoBAAAAAAAAAfQAAAAAAAAAAQD9DwECAAAAAAEMrzgdRPCUZh8tpxoRlGJRon1lbWwUFXfifEg6bUKPAQEAAABqRzBEAiBayZ9ZiNaZ1tn3IAQJjC5SyPNCg46QCd3jPSBBCMyTDQIgdyOM1ApOQjTx5wzquP1rUcUyWVQ4cuXZ9LrVRJGLgs4BIQK1IUpPDWli/lR/C5y7JB+d8bYcPEAdv7BM3Vnv1VK+of////8CASWyUQcOKcoZBDzzPM1zJOLdqwPsxK4LXnfE/A5c9slaAQAAAAAF9d9wABl2qRRlm+2109PHqxLX+FMjw6G2wGDvvoisASWyUQcOKcoZBDzzPM1zJOLdqwPsxK4LXnfE/A5c9slaAQAAAAAAAAGQAAAAAAAAAQdqRzBEAiAehosr6iLfBSKXRqJ+ffLKD1hIgFRvf21V2tccvVDTUwIgOgSkzEn8pznIl0yX096STJmDXhWtHYW5atJOoHLS5j4BIQJRRkQg/MmKLkzTR6/iijLXaSh9rNhhR2q4WLqkO9MI8wABABl2qRQ5OXCAtR7yLFm9dGmvrP++7A2hLoisAAEAGXapFGWb7bXT08erEtf4UyPDobbAYO++iKwAAA==", 4 | "expectedTxHex": "02000000000180e510ab7856a0a5cdedfcbb4cec8695349b31854102aa92994f263e34f0649d000000006a47304402201e868b2bea22df05229746a27e7df2ca0f584880546f7f6d55dad71cbd50d35302203a04a4cc49fca739c8974c97d3de924c99835e15ad1d85b96ad24ea072d2e63e01210251464420fcc98a2e4cd347afe28a32d769287dacd861476ab858baa43bd308f3ffffffff030125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000002faf080001976a91439397080b51ef22c59bd7469afacffbeec0da12e88ac0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000002faecfc001976a914659bedb5d3d3c7ab12d7f85323c3a1b6c060efbe88ac0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a0100000000000001f4000000000000" 5 | }, 6 | { 7 | "base64": "cHNldP8BAP0TAQIAAAAAAudkFztdrMhMWO7vFlBwP5VPD3a1wWIqQW5hZLCiFUBAAQAAAAD/////OO9qlyJ3LlMjldgpP0NjjGtHhmWusLRh/JkpzKznJSgBAAAAAP////8DASWyUQcOKcoZBDzzPM1zJOLdqwPsxK4LXnfE/A5c9slaAQAAAAAHJw4AABl2qRQ5OXCAtR7yLFm9dGmvrP++7A2hLoisASWyUQcOKcoZBDzzPM1zJOLdqwPsxK4LXnfE/A5c9slaAQAAAAAExLGoABl2qRRQpBARXwp9iplHLkfRko/4CGlIyIisASWyUQcOKcoZBDzzPM1zJOLdqwPsxK4LXnfE/A5c9slaAQAAAAAAAAJYAAAAAAAAAAEA/VIBAgAAAAAB5QI7TElwI68ZyKAJUfW2ifFt/dgl4uQjnp8y5Aoh/R8BAAAAakcwRAIgcuVWtRxuKxXlR2LRV39D8dQLYS2MCx/OLn0H9IKoyT8CIDGx9lDxon9qOcBNu+g2deIIKz2+eLoH/XqWpuZaeR3wASEC+dqsbhz4d1mlsATUKaH41o/LB2t++70wtHXE8SDecmD/////AwElslEHDinKGQQ88zzNcyTi3asD7MSuC153xPwOXPbJWgEAAAAAEeGImAAXqRQdRtQdgvTtPvXaDaASymM/0IWtQYcBJbJRBw4pyhkEPPM8zXMk4t2rA+zErgted8T8Dlz2yVoBAAAAAAX14QAAGXapFFCkEBFfCn2KmUcuR9GSj/gIaUjIiKwBJbJRBw4pyhkEPPM8zXMk4t2rA+zErgted8T8Dlz2yVoBAAAAAAAAGmgAAAAAAAABB2pHMEQCIBJS17Qp3V/VVvoJYs6pAnuSTgkZSU05MKpMC6QTM3mEAiBYAsthY8iWRKW6mDCnoTGUp35x41WT861kO1sg7u5eegEhAm3D+X4sfHs8LcjXRCSp68QjYD60FtsAqC63vZcbLZ/6AAEA/VIBAgAAAAABlv45sh8NSwyvYcksJUqBinFtEx7OdF5lma3qNfhJtRMAAAAAakcwRAIgbLCYWg8UmyKKZSAIwlWY8Ie74vewVTsUrg7W586BBosCIFoS28W4G1X5ZuiKEnkf2S/UT5F238zw6VaivH03NomZASEC+dqsbhz4d1mlsATUKaH41o/LB2t++70wtHXE8SDecmD/////AwElslEHDinKGQQ88zzNcyTi3asD7MSuC153xPwOXPbJWgEAAAAABfXGmAAXqRRIRVDTKYYc5oMNII+mZhOpURqKFIcBJbJRBw4pyhkEPPM8zXMk4t2rA+zErgted8T8Dlz2yVoBAAAAAAX14QAAGXapFFCkEBFfCn2KmUcuR9GSj/gIaUjIiKwBJbJRBw4pyhkEPPM8zXMk4t2rA+zErgted8T8Dlz2yVoBAAAAAAAAGmgAAAAAAAABB2pHMEQCIHiXhQlhcAtUkI1BiKSDBQ1//iW8wIP1zQzhXOPfyKM7AiABt7HdiTKQwmny+rM2jj5jFu+OheXvvJ1mm4c4fufFKAEhAm3D+X4sfHs8LcjXRCSp68QjYD60FtsAqC63vZcbLZ/6AAEAGXapFDk5cIC1HvIsWb10aa+s/77sDaEuiKwAAQAZdqkUUKQQEV8KfYqZRy5H0ZKP+AhpSMiIrAAA", 8 | "expectedTxHex": "020000000002e764173b5dacc84c58eeef1650703f954f0f76b5c1622a416e6164b0a2154040010000006a47304402201252d7b429dd5fd556fa0962cea9027b924e0919494d3930aa4c0ba41333798402205802cb6163c89644a5ba9830a7a13194a77e71e35593f3ad643b5b20eeee5e7a0121026dc3f97e2c7c7b3c2dc8d74424a9ebc423603eb416db00a82eb7bd971b2d9ffaffffffff38ef6a9722772e532395d8293f43638c6b478665aeb0b461fc9929ccace72528010000006a47304402207897850961700b54908d4188a483050d7ffe25bcc083f5cd0ce15ce3dfc8a33b022001b7b1dd893290c269f2fab3368e3e6316ef8e85e5efbc9d669b87387ee7c5280121026dc3f97e2c7c7b3c2dc8d74424a9ebc423603eb416db00a82eb7bd971b2d9ffaffffffff030125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000007270e00001976a91439397080b51ef22c59bd7469afacffbeec0da12e88ac0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000004c4b1a8001976a91450a410115f0a7d8a99472e47d1928ff8086948c888ac0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000000000258000000000000" 9 | }, 10 | { 11 | "base64": "cHNldP8BAOoCAAAAAAF8TOkTL4nMuVRV3bo/L9Ru5msXH9mM3XKVjpvRUZJDUgEAAAAA/////wMBJbJRBw4pyhkEPPM8zXMk4t2rA+zErgted8T8Dlz2yVoBAAAAAAcnDgAAGXapFDk5cIC1HvIsWb10aa+s/77sDaEuiKwBJbJRBw4pyhkEPPM8zXMk4t2rA+zErgted8T8Dlz2yVoBAAAAAATEsagAGXapFFCkEBFfCn2KmUcuR9GSj/gIaUjIiKwBJbJRBw4pyhkEPPM8zXMk4t2rA+zErgted8T8Dlz2yVoBAAAAAAAAAlgAAAAAAAAAAQFDASWyUQcOKcoZBDzzPM1zJOLdqwPsxK4LXnfE/A5c9slaAQAAAAAL68IAABepFH0Bf/VTaShFRJf6GiTzv6BGoq5YhwEHFxYAFA4UTyuM9BjiAqvmsfy9N90bZGDXAQhrAkcwRAIgPLjQWchMyX5jFJ+evkq0jS9B3/2fsr8ptGvHlCkTgKQCIGkQbP7f2yApW4eSmRQ0bEHitdH3vztY1Xx2k0Xc6HbWASED0yFfddB0X6dwVQ9cJeH6wwfUsFd6aOxlEIrgxGU20TYAAQAZdqkUOTlwgLUe8ixZvXRpr6z/vuwNoS6IrAABABl2qRRQpBARXwp9iplHLkfRko/4CGlIyIisAAA=", 12 | "expectedTxHex": "0200000001017c4ce9132f89ccb95455ddba3f2fd46ee66b171fd98cdd72958e9bd15192435201000000171600140e144f2b8cf418e202abe6b1fcbd37dd1b6460d7ffffffff030125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000007270e00001976a91439397080b51ef22c59bd7469afacffbeec0da12e88ac0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000004c4b1a8001976a91450a410115f0a7d8a99472e47d1928ff8086948c888ac0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000000000025800000000000000000247304402203cb8d059c84cc97e63149f9ebe4ab48d2f41dffd9fb2bf29b46bc794291380a4022069106cfedfdb20295b87929914346c41e2b5d1f7bf3b58d57c769345dce876d6012103d3215f75d0745fa770550f5c25e1fac307d4b0577a68ec65108ae0c46536d13600000000000000" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /pset/extractor.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package pset 6 | 7 | // The Extractor requires provision of a single PSET 8 | // in which all necessary signatures are encoded, and 9 | // uses it to construct a fully valid network serialized 10 | // transaction. 11 | 12 | import ( 13 | "bytes" 14 | 15 | "github.com/btcsuite/btcd/btcutil/psbt" 16 | "github.com/btcsuite/btcd/txscript" 17 | "github.com/btcsuite/btcd/wire" 18 | "github.com/vulpemventures/go-elements/transaction" 19 | ) 20 | 21 | // Extract takes a finalized pset.Pset and outputs a finalized transaction 22 | // instance. Note that if the PSET is in-complete, then an error 23 | // ErrIncompletePSET will be returned. As the extracted transaction has been 24 | // fully finalized, it will be ready for network broadcast once returned. 25 | func Extract(p *Pset) (*transaction.Transaction, error) { 26 | // If the packet isn't complete, then we'll return an error as it 27 | // doesn't have all the required witness data. 28 | if !p.IsComplete() { 29 | return nil, psbt.ErrIncompletePSBT 30 | } 31 | 32 | // First, we'll make a copy of the underlying unsigned transaction (the 33 | // initial template) so we don't mutate it during our activates below. 34 | finalTx := p.UnsignedTx.Copy() 35 | 36 | // For each input, we'll now populate any relevant witness and 37 | // sigScript data. 38 | for i, tin := range finalTx.Inputs { 39 | // We'll grab the corresponding internal packet input which 40 | // matches this materialized transaction input and emplace that 41 | // final sigScript (if present). 42 | pInput := p.Inputs[i] 43 | if pInput.FinalScriptSig != nil { 44 | tin.Script = pInput.FinalScriptSig 45 | } 46 | 47 | // Similarly, if there's a final witness, then we'll also need 48 | // to extract that as well, parsing the lower-level transaction 49 | // encoding. 50 | if pInput.FinalScriptWitness != nil { 51 | // In order to set the witness, need to re-deserialize 52 | // the field as encoded within the PSET packet. For 53 | // each input, the witness is encoded as a stack with 54 | // one or more items. 55 | witnessReader := bytes.NewReader( 56 | pInput.FinalScriptWitness, 57 | ) 58 | 59 | // First we extract the number of witness elements 60 | // encoded in the above witnessReader. 61 | witCount, err := wire.ReadVarInt(witnessReader, 0) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | // Now that we know how may inputs we'll need, we'll 67 | // construct a packing slice, then read out each input 68 | // (with a varint prefix) from the witnessReader. 69 | tin.Witness = make(transaction.TxWitness, witCount) 70 | for j := uint64(0); j < witCount; j++ { 71 | wit, err := wire.ReadVarBytes( 72 | witnessReader, 0, txscript.MaxScriptSize, "witness", 73 | ) 74 | if err != nil { 75 | return nil, err 76 | } 77 | tin.Witness[j] = wit 78 | } 79 | } 80 | } 81 | 82 | return finalTx, nil 83 | } 84 | -------------------------------------------------------------------------------- /pset/extractor_test.go: -------------------------------------------------------------------------------- 1 | package pset 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "testing" 7 | ) 8 | 9 | func TestExtractor(t *testing.T) { 10 | file, err := ioutil.ReadFile("data/extractor.json") 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | var tests []map[string]interface{} 15 | err = json.Unmarshal(file, &tests) 16 | 17 | for _, v := range tests { 18 | p, err := NewPsetFromBase64(v["base64"].(string)) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | 23 | tx, err := Extract(p) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | res, err := tx.ToHex() 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | expectedTxHex := v["expectedTxHex"].(string) 33 | if res != expectedTxHex { 34 | t.Fatalf("Got: %s, expected: %s", res, expectedTxHex) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pset/finalizer_test.go: -------------------------------------------------------------------------------- 1 | package pset 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "testing" 7 | ) 8 | 9 | func TestFinalizer(t *testing.T) { 10 | file, err := ioutil.ReadFile("data/finalizer.json") 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | var tests []map[string]interface{} 15 | err = json.Unmarshal(file, &tests) 16 | 17 | for _, v := range tests { 18 | p, err := NewPsetFromBase64(v["base64"].(string)) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | 23 | err = FinalizeAll(p) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | 28 | base64Res, err := p.ToBase64() 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | hexRes, err := p.ToHex() 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | expectedBase64 := v["expectedBase64"].(string) 37 | expectedHex := v["expectedHex"].(string) 38 | if base64Res != expectedBase64 { 39 | t.Fatalf("Got: %s, expected: %s", base64Res, expectedBase64) 40 | } 41 | if hexRes != expectedHex { 42 | t.Fatalf("Got: %s, expected: %s", hexRes, expectedHex) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pset/pset_output.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013-2017 The btcsuite developers 2 | // Copyright (c) 2015-2016 The Decred developers 3 | // Copyright (c) 2019-2020 The VulpemVentures developers 4 | 5 | // Permission to use, copy, modify, and distribute this software for any 6 | // purpose with or without fee is hereby granted, provided that the above 7 | // copyright notice and this permission notice appear in all copies. 8 | 9 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | 17 | package pset 18 | 19 | import ( 20 | "bytes" 21 | "io" 22 | "sort" 23 | 24 | "github.com/btcsuite/btcd/btcutil/psbt" 25 | "github.com/btcsuite/btcd/wire" 26 | ) 27 | 28 | // POutput is a struct encapsulating all the data that can be attached 29 | // to any specific output of the PSBT. 30 | type POutput struct { 31 | RedeemScript []byte 32 | WitnessScript []byte 33 | Bip32Derivation []*psbt.Bip32Derivation 34 | } 35 | 36 | // NewPsbtOutput creates an instance of PsbtOutput; the three parameters 37 | // redeemScript, witnessScript and Bip32Derivation are all allowed to be 38 | // `nil`. 39 | func NewPsbtOutput(redeemScript []byte, witnessScript []byte, 40 | bip32Derivation []*psbt.Bip32Derivation) *POutput { 41 | return &POutput{ 42 | RedeemScript: redeemScript, 43 | WitnessScript: witnessScript, 44 | Bip32Derivation: bip32Derivation, 45 | } 46 | } 47 | 48 | // deserialize attempts to recode a new POutput from the passed io.Reader. 49 | func (po *POutput) deserialize(r io.Reader) error { 50 | for { 51 | keyint, keydata, err := getKey(r) 52 | if err != nil { 53 | return err 54 | } 55 | if keyint == -1 { 56 | // Reached separator byte 57 | break 58 | } 59 | 60 | value, err := wire.ReadVarBytes( 61 | r, 0, psbt.MaxPsbtValueLength, "PSET value", 62 | ) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | switch psbt.OutputType(keyint) { 68 | 69 | case psbt.RedeemScriptOutputType: 70 | if po.RedeemScript != nil { 71 | return psbt.ErrDuplicateKey 72 | } 73 | if keydata != nil { 74 | return psbt.ErrInvalidKeyData 75 | } 76 | po.RedeemScript = value 77 | 78 | case psbt.WitnessScriptOutputType: 79 | if po.WitnessScript != nil { 80 | return psbt.ErrDuplicateKey 81 | } 82 | if keydata != nil { 83 | return psbt.ErrInvalidKeyData 84 | } 85 | po.WitnessScript = value 86 | 87 | case psbt.Bip32DerivationOutputType: 88 | if !validatePubkey(keydata) { 89 | return psbt.ErrInvalidKeyData 90 | } 91 | master, derivationPath, err := readBip32Derivation(value) 92 | if err != nil { 93 | return err 94 | } 95 | 96 | // Duplicate keys are not allowed 97 | for _, x := range po.Bip32Derivation { 98 | if bytes.Equal(x.PubKey, keydata) { 99 | return psbt.ErrDuplicateKey 100 | } 101 | } 102 | 103 | po.Bip32Derivation = append(po.Bip32Derivation, 104 | &psbt.Bip32Derivation{ 105 | PubKey: keydata, 106 | MasterKeyFingerprint: master, 107 | Bip32Path: derivationPath, 108 | }, 109 | ) 110 | 111 | default: 112 | // Unknown type is allowed for inputs but not outputs. 113 | return psbt.ErrInvalidPsbtFormat 114 | } 115 | } 116 | 117 | return nil 118 | } 119 | 120 | // serialize attempts to write out the target POutput into the passed 121 | // io.Writer. 122 | func (po *POutput) serialize(w io.Writer) error { 123 | if po.RedeemScript != nil { 124 | err := serializeKVPairWithType( 125 | w, uint8(psbt.RedeemScriptOutputType), nil, po.RedeemScript, 126 | ) 127 | if err != nil { 128 | return err 129 | } 130 | } 131 | if po.WitnessScript != nil { 132 | err := serializeKVPairWithType( 133 | w, uint8(psbt.WitnessScriptOutputType), nil, po.WitnessScript, 134 | ) 135 | if err != nil { 136 | return err 137 | } 138 | } 139 | 140 | sort.Sort(psbt.Bip32Sorter(po.Bip32Derivation)) 141 | for _, kd := range po.Bip32Derivation { 142 | err := serializeKVPairWithType( 143 | w, 144 | uint8(psbt.Bip32DerivationOutputType), 145 | kd.PubKey, 146 | psbt.SerializeBIP32Derivation( 147 | kd.MasterKeyFingerprint, 148 | kd.Bip32Path, 149 | ), 150 | ) 151 | if err != nil { 152 | return err 153 | } 154 | } 155 | 156 | return nil 157 | } 158 | -------------------------------------------------------------------------------- /pset/signer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package pset 6 | 7 | // signer encapsulates the role 'Signer' as specified in BIP174; it controls 8 | // the insertion of signatures; the Sign() function will attempt to insert 9 | // signatures using Updater.addPartialSignature, after first ensuring the Psbt 10 | // is in the correct state. 11 | 12 | import ( 13 | "github.com/btcsuite/btcd/btcutil/psbt" 14 | "github.com/btcsuite/btcd/txscript" 15 | ) 16 | 17 | // Sign allows the caller to sign a PSBT at a particular input; they 18 | // must provide a signature and a pubkey, both as byte slices; they can also 19 | // optionally provide both witnessScript and/or redeemScript, otherwise these 20 | // arguments must be set as nil (and in that case, they must already be present 21 | // in the PSBT if required for signing to succeed). 22 | // 23 | // This serves as a wrapper around Updater.addPartialSignature; it ensures that 24 | // the redeemScript and witnessScript are updated as needed (note that the 25 | // Updater is allowed to add redeemScripts and witnessScripts independently, 26 | // before signing), and ensures that the right form of utxo field 27 | // (NonWitnessUtxo or WitnessUtxo) is included in the input so that signature 28 | // insertion (and then finalization) can take place. 29 | func (p *Updater) Sign(inIndex int, sig []byte, pubKey []byte, 30 | redeemScript []byte, witnessScript []byte) (psbt.SignOutcome, error) { 31 | 32 | if isFinalized(p.Data, inIndex) { 33 | return psbt.SignFinalized, nil 34 | } 35 | 36 | // Add the witnessScript to the PSBT in preparation. If it already 37 | // exists, it will be overwritten. 38 | if witnessScript != nil { 39 | err := p.AddInWitnessScript(witnessScript, inIndex) 40 | if err != nil { 41 | return psbt.SignInvalid, err 42 | } 43 | } 44 | 45 | // Add the redeemScript to the PSBT in preparation. If it already 46 | // exists, it will be overwritten. 47 | if redeemScript != nil { 48 | err := p.AddInRedeemScript(redeemScript, inIndex) 49 | if err != nil { 50 | return psbt.SignInvalid, err 51 | } 52 | } 53 | 54 | // At this point, the PSBT must have the requisite witnessScript or 55 | // redeemScript fields for signing to succeed. 56 | // 57 | // Case 1: if witnessScript is present, it must be of type witness; 58 | // if not, signature insertion will of course fail. 59 | switch { 60 | case p.Data.Inputs[inIndex].WitnessScript != nil: 61 | if p.Data.Inputs[inIndex].WitnessUtxo == nil { 62 | err := nonWitnessToWitness(p.Data, inIndex) 63 | if err != nil { 64 | return psbt.SignInvalid, err 65 | } 66 | } 67 | 68 | err := p.addPartialSignature(inIndex, sig, pubKey) 69 | if err != nil { 70 | return psbt.SignInvalid, err 71 | } 72 | 73 | // Case 2: no witness script, only redeem script; can be legacy p2sh or 74 | // p2sh-wrapped p2wkh. 75 | case p.Data.Inputs[inIndex].RedeemScript != nil: 76 | // We only need to decide if the input is witness, and we don't 77 | // rely on the witnessutxo/nonwitnessutxo in the PSBT, instead 78 | // we check the redeemScript content. 79 | if txscript.IsWitnessProgram(redeemScript) { 80 | if p.Data.Inputs[inIndex].WitnessUtxo == nil { 81 | err := nonWitnessToWitness(p.Data, inIndex) 82 | if err != nil { 83 | return psbt.SignInvalid, err 84 | } 85 | } 86 | } 87 | 88 | // If it is not a valid witness program, we here assume that 89 | // the provided WitnessUtxo/NonWitnessUtxo field was correct. 90 | err := p.addPartialSignature(inIndex, sig, pubKey) 91 | if err != nil { 92 | return psbt.SignInvalid, err 93 | } 94 | 95 | // Case 3: Neither provided only works for native p2wkh, or non-segwit 96 | // non-p2sh. To check if it's segwit, check the scriptPubKey of the 97 | // output. 98 | default: 99 | if p.Data.Inputs[inIndex].WitnessUtxo == nil { 100 | outIndex := p.Data.UnsignedTx.Inputs[inIndex].Index 101 | script := p.Data.Inputs[inIndex].NonWitnessUtxo.Outputs[outIndex].Script 102 | 103 | if txscript.IsWitnessProgram(script) { 104 | err := nonWitnessToWitness(p.Data, inIndex) 105 | if err != nil { 106 | return psbt.SignInvalid, err 107 | } 108 | } 109 | } 110 | 111 | err := p.addPartialSignature(inIndex, sig, pubKey) 112 | if err != nil { 113 | return psbt.SignInvalid, err 114 | } 115 | } 116 | 117 | return psbt.SignSuccesful, nil 118 | } 119 | 120 | // nonWitnessToWitness extracts the TxOut from the existing NonWitnessUtxo 121 | // field in the given PSBT input and sets it as type witness by replacing the 122 | // NonWitnessUtxo field with a WitnessUtxo field. See 123 | // https://github.com/bitcoin/bitcoin/pull/14197. 124 | func nonWitnessToWitness(p *Pset, inIndex int) error { 125 | outIndex := p.UnsignedTx.Inputs[inIndex].Index 126 | txout := p.Inputs[inIndex].NonWitnessUtxo.Outputs[outIndex] 127 | 128 | // Remove the non-witness first, else sanity check will not pass: 129 | p.Inputs[inIndex].NonWitnessUtxo = nil 130 | u := Updater{ 131 | Data: p, 132 | } 133 | 134 | return u.AddInWitnessUtxo(txout, inIndex) 135 | } 136 | -------------------------------------------------------------------------------- /pset/signer_test.go: -------------------------------------------------------------------------------- 1 | package pset 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "io/ioutil" 7 | "testing" 8 | ) 9 | 10 | func TestSigner(t *testing.T) { 11 | file, err := ioutil.ReadFile("data/signer.json") 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | var tests []map[string]interface{} 16 | err = json.Unmarshal(file, &tests) 17 | 18 | for _, v := range tests { 19 | p, err := NewPsetFromBase64(v["base64"].(string)) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | updater, err := NewUpdater(p) 24 | 25 | for inIndex, vIn := range v["inputs"].([]interface{}) { 26 | in := vIn.(map[string]interface{}) 27 | signature, _ := hex.DecodeString(in["signature"].(string)) 28 | pubkey, _ := hex.DecodeString(in["pubkey"].(string)) 29 | updater.Sign(inIndex, signature, pubkey, p.Inputs[inIndex].RedeemScript, p.Inputs[inIndex].WitnessScript) 30 | valid, err := updater.Data.ValidateInputSignatures(inIndex) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | if !valid { 35 | t.Fatal(err) 36 | } 37 | } 38 | 39 | base64Res, err := updater.Data.ToBase64() 40 | if err != nil { 41 | t.Fatal(err) 42 | } 43 | hexRes, err := updater.Data.ToHex() 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | expectedBase64 := v["expectedBase64"].(string) 48 | expectedHex := v["expectedHex"].(string) 49 | if base64Res != expectedBase64 { 50 | t.Fatalf("Got: %s, expected: %s", base64Res, expectedBase64) 51 | } 52 | if hexRes != expectedHex { 53 | t.Fatalf("Got: %s, expected: %s", hexRes, expectedHex) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /psetv2/bip32.go: -------------------------------------------------------------------------------- 1 | package psetv2 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | ) 7 | 8 | //TODO add ref to btcutil 9 | 10 | // DerivationPathWithPubKey encapsulates the data for the input and output 11 | // DerivationPathWithPubKey key-value fields. 12 | type DerivationPathWithPubKey struct { 13 | // PubKey is the raw pubkey serialized in compressed format. 14 | PubKey []byte 15 | 16 | // MasterKeyFingerprint is the finger print of the master pubkey. 17 | MasterKeyFingerprint uint32 18 | 19 | // Bip32Path is the BIP 32 path with child index as a distinct integer. 20 | Bip32Path []uint32 21 | } 22 | 23 | type TapDerivationPathWithPubKey struct { 24 | DerivationPathWithPubKey 25 | LeafHashes [][]byte 26 | } 27 | 28 | func (d *TapDerivationPathWithPubKey) sanityCheck() error { 29 | if len(d.LeafHashes) == 0 { 30 | return ErrInInvalidTapBip32Derivation 31 | } 32 | 33 | for _, leafHash := range d.LeafHashes { 34 | if len(leafHash) != 32 { 35 | return ErrInInvalidTapBip32Derivation 36 | } 37 | } 38 | 39 | return nil 40 | } 41 | 42 | // Bip32Sorter implements sort.Interface for the DerivationPathWithPubKey struct. 43 | type Bip32Sorter []*DerivationPathWithPubKey 44 | 45 | func (s Bip32Sorter) Len() int { return len(s) } 46 | 47 | func (s Bip32Sorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 48 | 49 | func (s Bip32Sorter) Less(i, j int) bool { 50 | return bytes.Compare(s[i].PubKey, s[j].PubKey) < 0 51 | } 52 | 53 | // readBip32Derivation deserializes a byte slice containing chunks of 4 byte 54 | // little endian encodings of uint32 values, the first of which is the 55 | // masterkeyfingerprint and the remainder of which are the derivation path. 56 | func readBip32Derivation(path []byte) (uint32, []uint32, error) { 57 | if len(path)%4 != 0 || len(path)/4-1 < 1 { 58 | return 0, nil, ErrInvalidPsbtFormat 59 | } 60 | 61 | masterKeyInt := binary.LittleEndian.Uint32(path[:4]) 62 | 63 | var paths []uint32 64 | for i := 4; i < len(path); i += 4 { 65 | paths = append(paths, binary.LittleEndian.Uint32(path[i:i+4])) 66 | } 67 | 68 | return masterKeyInt, paths, nil 69 | } 70 | 71 | // SerializeBIP32Derivation takes a master key fingerprint as defined in BIP32, 72 | // along with a path specified as a list of uint32 values, and returns a 73 | // bytestring specifying the derivation in the format required by BIP174: // 74 | // master key fingerprint (4) || child index (4) || child index (4) || .... 75 | func SerializeBIP32Derivation(masterKeyFingerprint uint32, 76 | bip32Path []uint32) []byte { 77 | 78 | var masterKeyBytes [4]byte 79 | binary.LittleEndian.PutUint32(masterKeyBytes[:], masterKeyFingerprint) 80 | 81 | derivationPath := make([]byte, 0, 4+4*len(bip32Path)) 82 | derivationPath = append(derivationPath, masterKeyBytes[:]...) 83 | for _, path := range bip32Path { 84 | var pathbytes [4]byte 85 | binary.LittleEndian.PutUint32(pathbytes[:], path) 86 | derivationPath = append(derivationPath, pathbytes[:]...) 87 | } 88 | 89 | return derivationPath 90 | } 91 | -------------------------------------------------------------------------------- /psetv2/bitset.go: -------------------------------------------------------------------------------- 1 | package psetv2 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "math/bits" 7 | ) 8 | 9 | const ( 10 | bitsetSize = 8 11 | ) 12 | 13 | type BitSet []byte 14 | 15 | func NewBitSet() BitSet { 16 | return make(BitSet, bitsetSize) 17 | } 18 | 19 | func NewBitSetFromBuffer(buf byte) (BitSet, error) { 20 | return bitSetFromBuffer(uint8(buf)), nil 21 | } 22 | 23 | func (s BitSet) String() string { 24 | var str string 25 | for _, v := range s.reverse() { 26 | str += fmt.Sprintf("%d", v) 27 | } 28 | return str 29 | } 30 | 31 | func (s BitSet) Set(index int) { 32 | if index >= len(s) { 33 | return 34 | } 35 | s[index] = 1 36 | } 37 | 38 | func (s BitSet) Reset(index int) { 39 | if index >= len(s) { 40 | return 41 | } 42 | s[index] = 0 43 | } 44 | 45 | func (s BitSet) Test(index int) bool { 46 | if index >= len(s) { 47 | return false 48 | } 49 | return s[index] == 1 50 | } 51 | 52 | func (s BitSet) Clear() { 53 | s = NewBitSet() 54 | } 55 | 56 | func (s BitSet) Uint8() uint8 { 57 | var n uint8 58 | for i, v := range s { 59 | n += v * uint8(math.Pow(2, float64(i))) 60 | } 61 | return n 62 | } 63 | 64 | func (s BitSet) reverse() []byte { 65 | b := make(BitSet, 0, len(s)) 66 | for _, v := range s { 67 | b = append([]byte{v}, b...) 68 | } 69 | return b 70 | } 71 | 72 | func bitSetFromBuffer(u uint8) BitSet { 73 | base := 2 74 | s := NewBitSet() 75 | i := 0 76 | shift := uint(bits.TrailingZeros(uint(base))) & 7 77 | b := uint8(base) 78 | m := uint(base) - 1 79 | for u >= b { 80 | s[i] = byte(uint(u) & m) 81 | u >>= shift 82 | i++ 83 | } 84 | s[i] = byte(uint(u)) 85 | return s 86 | } 87 | -------------------------------------------------------------------------------- /psetv2/bitset_test.go: -------------------------------------------------------------------------------- 1 | package psetv2_test 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | "github.com/vulpemventures/go-elements/psetv2" 10 | ) 11 | 12 | func TestBitSet(t *testing.T) { 13 | var tests map[string]interface{} 14 | file, _ := ioutil.ReadFile("testdata/bitset.json") 15 | json.Unmarshal(file, &tests) 16 | 17 | valid := tests["valid"].([]interface{}) 18 | 19 | t.Run("valid", func(t *testing.T) { 20 | for _, v := range valid { 21 | tt := v.(map[string]interface{}) 22 | value := byte((tt["value"].(float64))) 23 | expected := tt["expected"].(string) 24 | bitset, err := psetv2.NewBitSetFromBuffer(value) 25 | require.NoError(t, err) 26 | require.NotNil(t, bitset) 27 | require.Equal(t, expected, bitset.String()) 28 | } 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /psetv2/creator.go: -------------------------------------------------------------------------------- 1 | package psetv2 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | 7 | "github.com/btcsuite/btcd/btcec/v2" 8 | "github.com/vulpemventures/go-elements/address" 9 | "github.com/vulpemventures/go-elements/elementsutil" 10 | "github.com/vulpemventures/go-elements/transaction" 11 | ) 12 | 13 | const ( 14 | defaultVersion = 2 15 | defaultTxVersion = 2 16 | ) 17 | 18 | var ( 19 | // Input errors 20 | ErrInMissingTxid = fmt.Errorf("missing input txid") 21 | ErrInInvalidTxidFormat = fmt.Errorf("input txid must be in hex format") 22 | ErrInInvalidTxid = fmt.Errorf("invalid input txid length") 23 | 24 | // Output errors 25 | ErrOutMissingAsset = fmt.Errorf("missing output asset") 26 | ErrOutInvalidAssetFormat = fmt.Errorf("output asset must be in hex format") 27 | ErrOutInvalidAsset = fmt.Errorf("invalid output asset length") 28 | ErrOutInvalidAddress = fmt.Errorf("invalid output address") 29 | ) 30 | 31 | type InputArgs struct { 32 | Txid string 33 | TxIndex uint32 34 | Sequence uint32 35 | HeightLock uint32 36 | TimeLock uint32 37 | } 38 | 39 | func (a InputArgs) validate() error { 40 | if a.Txid == "" { 41 | return ErrInMissingTxid 42 | } 43 | buf, err := hex.DecodeString(a.Txid) 44 | if err != nil { 45 | return ErrInInvalidTxidFormat 46 | } 47 | if len(buf) != 32 { 48 | return ErrInInvalidTxid 49 | } 50 | return nil 51 | } 52 | 53 | func (a InputArgs) toPartialInput() Input { 54 | txid, _ := hex.DecodeString(a.Txid) 55 | txid = elementsutil.ReverseBytes(txid) 56 | sequence := a.Sequence 57 | if sequence == 0 { 58 | sequence = transaction.DefaultSequence 59 | } 60 | return Input{ 61 | PreviousTxid: txid, 62 | PreviousTxIndex: a.TxIndex, 63 | Sequence: sequence, 64 | RequiredHeightLocktime: a.HeightLock, 65 | RequiredTimeLocktime: a.TimeLock, 66 | } 67 | } 68 | 69 | type OutputArgs struct { 70 | Asset string 71 | Amount uint64 72 | Script []byte 73 | BlindingKey []byte 74 | BlinderIndex uint32 75 | } 76 | 77 | func (a OutputArgs) validate() error { 78 | if a.Asset == "" { 79 | return ErrOutMissingAsset 80 | } 81 | buf, err := hex.DecodeString(a.Asset) 82 | if err != nil { 83 | return ErrOutInvalidAssetFormat 84 | } 85 | if len(buf) != 32 { 86 | return ErrOutInvalidAsset 87 | } 88 | if len(a.Script) > 0 { 89 | if _, err := address.ParseScript(a.Script); err != nil { 90 | return fmt.Errorf("invalid output script: %s", err) 91 | } 92 | } 93 | if len(a.BlindingKey) > 0 { 94 | if _, err := btcec.ParsePubKey(a.BlindingKey); err != nil { 95 | return fmt.Errorf("invalid output blinding publick key: %s", err) 96 | } 97 | } 98 | return nil 99 | } 100 | 101 | func (a OutputArgs) toPartialOutput() Output { 102 | asset, _ := elementsutil.AssetHashToBytes(a.Asset) 103 | return Output{ 104 | Value: a.Amount, 105 | Asset: asset[1:], 106 | Script: a.Script, 107 | BlindingPubkey: a.BlindingKey, 108 | BlinderIndex: a.BlinderIndex, 109 | } 110 | } 111 | 112 | func New( 113 | ins []InputArgs, outs []OutputArgs, locktime *uint32, 114 | ) (*Pset, error) { 115 | global := Global{ 116 | Version: defaultVersion, 117 | TxVersion: defaultTxVersion, 118 | FallbackLocktime: locktime, 119 | Scalars: make([][]byte, 0), 120 | ProprietaryData: make([]ProprietaryData, 0), 121 | Unknowns: make([]KeyPair, 0), 122 | Xpubs: make([]Xpub, 0), 123 | TxModifiable: NewBitSet(), 124 | } 125 | global.TxModifiable.Set(0) 126 | global.TxModifiable.Set(1) 127 | p := &Pset{ 128 | Global: global, 129 | Inputs: make([]Input, 0), 130 | Outputs: make([]Output, 0), 131 | } 132 | 133 | for _, in := range ins { 134 | if err := p.addInput(in.toPartialInput()); err != nil { 135 | return nil, err 136 | } 137 | } 138 | 139 | for _, out := range outs { 140 | if err := p.addOutput(out.toPartialOutput()); err != nil { 141 | return nil, err 142 | } 143 | } 144 | 145 | return p, nil 146 | } 147 | -------------------------------------------------------------------------------- /psetv2/creator_test.go: -------------------------------------------------------------------------------- 1 | package psetv2_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | "github.com/vulpemventures/go-elements/address" 8 | "github.com/vulpemventures/go-elements/psetv2" 9 | ) 10 | 11 | var ( 12 | testAddresses = []string{ 13 | "el1qqfttsemg4sapwrfmmccyztj4wa8gpn5yfetkda4z5uy5e2jysgrszmj0xa8tzftde78kvtl26dtxw6q6gcuawte5xeyvkunws", 14 | "AzpjXSNnwaFpQQwf2A8AUj6Axqa3YXokJtEwmNvQWvoGn2ymKUzmofHmjxBKzPr7bszjrEJRpPSgJqUp", 15 | "CTExJqr9PvAveGHmK3ymA3YVdBFvEWh1Vqkj5U9DCv4L46BJhhAd3g8SdjPNCZR268VnsaynRGmyzrQa", 16 | } 17 | ) 18 | 19 | func TestCreator(t *testing.T) { 20 | inputs := randomInputArgs(2) 21 | outputs := randomOutputArgs(6) 22 | ptx, err := psetv2.New(inputs, outputs, nil) 23 | require.NoError(t, err) 24 | require.NotNil(t, ptx) 25 | 26 | psetBase64, err := ptx.ToBase64() 27 | require.NoError(t, err) 28 | require.NotEmpty(t, psetBase64) 29 | 30 | parsedPtx, err := psetv2.NewPsetFromBase64(psetBase64) 31 | require.NoError(t, err) 32 | require.NotNil(t, parsedPtx) 33 | } 34 | 35 | func randomInputArgs(num int) []psetv2.InputArgs { 36 | ins := make([]psetv2.InputArgs, 0, num) 37 | for i := 0; i < num; i++ { 38 | ins = append(ins, psetv2.InputArgs{ 39 | Txid: randomHex(32), 40 | TxIndex: randomVout(), 41 | }) 42 | } 43 | return ins 44 | } 45 | 46 | func randomOutputArgs(num int) []psetv2.OutputArgs { 47 | outs := make([]psetv2.OutputArgs, 0, num) 48 | for i := 0; i < num; i++ { 49 | addr := testAddresses[i%3] 50 | info, _ := address.FromConfidential(addr) 51 | outs = append(outs, psetv2.OutputArgs{ 52 | Asset: randomHex(32), 53 | Amount: randomValue(), 54 | Script: info.Script, 55 | BlindingKey: info.BlindingKey, 56 | }) 57 | } 58 | return outs 59 | } 60 | -------------------------------------------------------------------------------- /psetv2/extractor.go: -------------------------------------------------------------------------------- 1 | package psetv2 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "github.com/btcsuite/btcd/txscript" 8 | "github.com/btcsuite/btcd/wire" 9 | "github.com/vulpemventures/go-elements/elementsutil" 10 | "github.com/vulpemventures/go-elements/transaction" 11 | ) 12 | 13 | var ( 14 | ErrExtractorForbiddenExtraction = fmt.Errorf( 15 | "pset must be complete to extract final transaction", 16 | ) 17 | ) 18 | 19 | func Extract(p *Pset) (*transaction.Transaction, error) { 20 | if err := p.SanityCheck(); err != nil { 21 | return nil, fmt.Errorf("invalid pset: %s", err) 22 | } 23 | 24 | if !p.IsComplete() { 25 | return nil, ErrExtractorForbiddenExtraction 26 | } 27 | tx := transaction.NewTx(int32(p.Global.TxVersion)) 28 | tx.Locktime = p.Locktime() 29 | 30 | for _, in := range p.Inputs { 31 | txIn := &transaction.TxInput{ 32 | Hash: in.PreviousTxid, 33 | Index: in.PreviousTxIndex, 34 | Sequence: in.Sequence, 35 | } 36 | 37 | var issuance *transaction.TxIssuance 38 | if in.IssuanceValue > 0 || in.IssuanceValueCommitment != nil { 39 | value := in.IssuanceValueCommitment 40 | if value == nil { 41 | value, _ = elementsutil.ValueToBytes(in.IssuanceValue) 42 | } 43 | tokenValue := in.IssuanceInflationKeysCommitment 44 | if tokenValue == nil { 45 | tokenValue = []byte{0x00} 46 | if in.IssuanceInflationKeys > 0 { 47 | tokenValue, _ = elementsutil.ValueToBytes(in.IssuanceInflationKeys) 48 | } 49 | } 50 | issuance = &transaction.TxIssuance{ 51 | AssetBlindingNonce: in.IssuanceBlindingNonce, 52 | AssetEntropy: in.IssuanceAssetEntropy, 53 | AssetAmount: value, 54 | TokenAmount: tokenValue, 55 | } 56 | } 57 | txIn.Issuance = issuance 58 | if in.IssuanceValueRangeproof != nil { 59 | txIn.IssuanceRangeProof = in.IssuanceValueRangeproof 60 | } 61 | if in.IssuanceInflationKeysRangeproof != nil { 62 | txIn.InflationRangeProof = in.IssuanceInflationKeysRangeproof 63 | } 64 | 65 | txIn.IsPegin = in.PeginWitness != nil 66 | if txIn.IsPegin { 67 | txIn.PeginWitness = in.PeginWitness 68 | } 69 | 70 | if in.FinalScriptSig != nil { 71 | txIn.Script = in.FinalScriptSig 72 | } 73 | 74 | if in.FinalScriptWitness != nil { 75 | // In order to set the witness, need to re-deserialize 76 | // the field as encoded within the PSET packet. For 77 | // each input, the witness is encoded as a stack with 78 | // one or more items. 79 | witnessReader := bytes.NewReader( 80 | in.FinalScriptWitness, 81 | ) 82 | 83 | // First we extract the number of witness elements 84 | // encoded in the above witnessReader. 85 | witCount, err := wire.ReadVarInt(witnessReader, 0) 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | // Now that we know how may inputs we'll need, we'll 91 | // construct a packing slice, then read out each input 92 | // (with a varint prefix) from the witnessReader. 93 | txIn.Witness = make(transaction.TxWitness, witCount) 94 | for j := uint64(0); j < witCount; j++ { 95 | wit, err := wire.ReadVarBytes( 96 | witnessReader, 0, txscript.MaxScriptSize, "witness", 97 | ) 98 | if err != nil { 99 | return nil, err 100 | } 101 | txIn.Witness[j] = wit 102 | } 103 | } 104 | 105 | tx.AddInput(txIn) 106 | } 107 | 108 | for _, out := range p.Outputs { 109 | txOut := &transaction.TxOutput{ 110 | Script: out.Script, 111 | } 112 | value := out.ValueCommitment 113 | if value == nil { 114 | value, _ = elementsutil.ValueToBytes(out.Value) 115 | } 116 | txOut.Value = value 117 | 118 | asset := out.AssetCommitment 119 | if asset == nil { 120 | asset = append([]byte{0x01}, out.Asset...) 121 | } 122 | txOut.Asset = asset 123 | 124 | nonce := []byte{0x00} 125 | if out.EcdhPubkey != nil { 126 | nonce = out.EcdhPubkey 127 | } 128 | txOut.Nonce = nonce 129 | 130 | if out.ValueRangeproof != nil { 131 | txOut.RangeProof = out.ValueRangeproof 132 | } 133 | 134 | if out.AssetSurjectionProof != nil { 135 | txOut.SurjectionProof = out.AssetSurjectionProof 136 | } 137 | 138 | tx.AddOutput(txOut) 139 | } 140 | 141 | return tx, nil 142 | } 143 | -------------------------------------------------------------------------------- /psetv2/key_pair.go: -------------------------------------------------------------------------------- 1 | package psetv2 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "github.com/vulpemventures/go-elements/internal/bufferutil" 8 | ) 9 | 10 | var ( 11 | ErrKeyInvalidSize = fmt.Errorf("invalid key size") 12 | ErrProprietaryInvalidKey = fmt.Errorf("invalid ProprietaryData key") 13 | ErrProprietaryInvalidIdentifier = fmt.Errorf("invalid ProprietaryData identifier") 14 | ) 15 | 16 | // keyPair format: 17 | // := 18 | // := 19 | // := 20 | type KeyPair struct { 21 | Key Key 22 | Value []byte 23 | } 24 | 25 | type Key struct { 26 | KeyType uint8 27 | KeyData []byte 28 | } 29 | 30 | func (k *KeyPair) serialize(s *bufferutil.Serializer) error { 31 | if err := k.Key.serialize(s); err != nil { 32 | return err 33 | } 34 | 35 | return s.WriteVarSlice(k.Value) 36 | } 37 | 38 | func (k *KeyPair) deserialize(buf *bytes.Buffer) error { 39 | d := bufferutil.NewDeserializer(buf) 40 | 41 | if err := k.Key.deserialize(d); err != nil { 42 | return err 43 | } 44 | 45 | value, err := d.ReadVarSlice() 46 | if err != nil { 47 | return err 48 | } 49 | 50 | k.Value = value 51 | 52 | return nil 53 | } 54 | 55 | func (k *Key) serialize(s *bufferutil.Serializer) error { 56 | key := append([]byte{k.KeyType}, k.KeyData...) 57 | return s.WriteVarSlice(key) 58 | } 59 | 60 | func (k *Key) deserialize(d *bufferutil.Deserializer) error { 61 | key, err := d.ReadVarSlice() 62 | if err != nil { 63 | return err 64 | } 65 | 66 | if len(key) == 0 { 67 | return ErrNoMoreKeyPairs 68 | } 69 | 70 | if len(key) > maxPsbtKeyLength { 71 | return ErrKeyInvalidSize 72 | } 73 | 74 | k.KeyType = key[0] 75 | k.KeyData = key[1:] 76 | 77 | return nil 78 | } 79 | 80 | type ProprietaryData struct { 81 | Identifier []byte 82 | Subtype uint8 83 | KeyData []byte 84 | Value []byte 85 | } 86 | 87 | func (p *ProprietaryData) fromKeyPair(keyPair KeyPair) error { 88 | d := bufferutil.NewDeserializer(bytes.NewBuffer(keyPair.Key.KeyData)) 89 | 90 | if keyPair.Key.KeyType != 0xFC { 91 | return ErrProprietaryInvalidKey 92 | } 93 | 94 | identifierByteSize, err := d.ReadVarInt() 95 | if err != nil { 96 | return err 97 | } 98 | 99 | if identifierByteSize == 0 { 100 | return ErrProprietaryInvalidIdentifier 101 | } 102 | 103 | identifier, err := d.ReadSlice(uint(identifierByteSize)) 104 | if err != nil { 105 | return err 106 | } 107 | 108 | subType, err := d.ReadUint8() 109 | if err != nil { 110 | return err 111 | } 112 | 113 | keyData := d.ReadToEnd() 114 | 115 | value := keyPair.Value 116 | 117 | p.Identifier = identifier 118 | p.Subtype = subType 119 | p.KeyData = keyData 120 | p.Value = value 121 | 122 | return nil 123 | } 124 | 125 | func proprietaryKey(subType uint8, keyData []byte) []byte { 126 | s := bufferutil.NewSerializer(nil) 127 | s.WriteVarSlice(magicPrefix) 128 | s.WriteSlice([]byte{subType}) 129 | s.WriteSlice(keyData) 130 | return s.Bytes() 131 | } 132 | -------------------------------------------------------------------------------- /psetv2/partialsig.go: -------------------------------------------------------------------------------- 1 | package psetv2 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/btcsuite/btcd/btcec/v2" 7 | "github.com/btcsuite/btcd/btcec/v2/ecdsa" 8 | ) 9 | 10 | //TODO add ref to btcutil 11 | 12 | // PartialSig encapsulate a (BTC public key, ECDSA signature) 13 | // pair, note that the fields are stored as byte slices, not 14 | // btcec.PublicKey or btcec.Signature (because manipulations will 15 | // be with the former not the latter, here); compliance with consensus 16 | // serialization is enforced with .checkValid() 17 | type PartialSig struct { 18 | PubKey []byte 19 | Signature []byte 20 | } 21 | 22 | // PartialSigSorter implements sort.Interface for PartialSig. 23 | type PartialSigSorter []*PartialSig 24 | 25 | func (s PartialSigSorter) Len() int { return len(s) } 26 | 27 | func (s PartialSigSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 28 | 29 | func (s PartialSigSorter) Less(i, j int) bool { 30 | return bytes.Compare(s[i].PubKey, s[j].PubKey) < 0 31 | } 32 | 33 | // validatePubkey checks if pubKey is *any* valid pubKey serialization in a 34 | // Bitcoin context (compressed/uncomp. OK). 35 | func validatePubkey(pubKey []byte) bool { 36 | _, err := btcec.ParsePubKey(pubKey) 37 | return err == nil 38 | } 39 | 40 | // validateSignature checks that the passed byte slice is a valid DER-encoded 41 | // ECDSA signature, including the sighash flag. It does *not* of course 42 | // validate the signature against any message or public key. 43 | func validateSignature(sig []byte) bool { 44 | _, err := ecdsa.ParseDERSignature(sig) 45 | return err == nil 46 | } 47 | 48 | // checkValid checks that both the pbukey and sig are valid. See the methods 49 | // (PartialSig, validatePubkey, validateSignature) for more details. 50 | // 51 | // TODO(waxwing): update for Schnorr will be needed here if/when that 52 | // activates. 53 | func (ps *PartialSig) checkValid() bool { 54 | return validatePubkey(ps.PubKey) && validateSignature(ps.Signature) 55 | } 56 | -------------------------------------------------------------------------------- /psetv2/signer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package psetv2 6 | 7 | // signer encapsulates the role 'Signer' as specified in BIP174; it controls 8 | // the insertion of signatures; the Sign() function will attempt to insert 9 | // signatures using UpdaterRole.addPartialSignature, after first ensuring the Psbt 10 | // is in the correct state. 11 | 12 | import ( 13 | "fmt" 14 | 15 | "github.com/btcsuite/btcd/txscript" 16 | ) 17 | 18 | var ( 19 | ErrSignerForbiddenSigning = fmt.Errorf("pset is not fully blinded") 20 | ErrSignerForbiddenTaprootKeySigHasTapscriptSigs = fmt.Errorf("pset input has tapscript signatures") 21 | ErrSignerForbiddenTaprootScriptSigHasKeySig = fmt.Errorf("pset input has taproot key signature") 22 | ) 23 | 24 | type Signer = Updater 25 | 26 | func NewSigner(pset *Pset) (*Signer, error) { 27 | if err := pset.SanityCheck(); err != nil { 28 | return nil, fmt.Errorf("invalid pset: %s", err) 29 | } 30 | return &Signer{pset}, nil 31 | } 32 | 33 | // SignInput allows the caller to sign a PSET at a particular input; they 34 | // must provide a signature and a pubkey, both as byte slices; they can also 35 | // optionally provide both witnessScript and/or redeemScript, otherwise these 36 | // arguments must be set as nil (and in that case, they must already be present 37 | // in the PSBT if required for signing to succeed). 38 | // 39 | // This serves as a wrapper around UpdaterRole.addPartialSignature; it ensures that 40 | // the redeemScript and witnessScript are updated as needed (note that the 41 | // UpdaterRole is allowed to add redeemScripts and witnessScripts independently, 42 | // before signing), and ensures that the right form of utxo field 43 | // (NonWitnessUtxo or WitnessUtxo) is included in the input so that signature 44 | // insertion (and then finalization) can take place. 45 | func (s *Signer) SignInput( 46 | inIndex int, sig, pubKey, redeemScript, witnessScript []byte, 47 | ) error { 48 | if inIndex < 0 || inIndex >= int(s.Pset.Global.InputCount) { 49 | return ErrInputIndexOutOfRange 50 | } 51 | 52 | p := s.Pset.Copy() 53 | input := s.Pset.Inputs[inIndex] 54 | 55 | if isFinalized(p, inIndex) { 56 | return nil 57 | } 58 | 59 | if (input.SigHashType & 0x1f) == txscript.SigHashAll { 60 | for _, out := range p.Outputs { 61 | if out.NeedsBlinding() && !out.IsFullyBlinded() { 62 | return ErrSignerForbiddenSigning 63 | } 64 | } 65 | } 66 | 67 | // Add the witnessScript to the PSBT in preparation. If it already 68 | // exists, it will be overwritten. 69 | if witnessScript != nil { 70 | if err := s.AddInWitnessScript(inIndex, witnessScript); err != nil { 71 | return fmt.Errorf("failed to add input witness script: %s", err) 72 | } 73 | } 74 | 75 | // Add the redeemScript to the PSBT in preparation. If it already 76 | // exists, it will be overwritten. 77 | if redeemScript != nil { 78 | if err := s.AddInRedeemScript(inIndex, redeemScript); err != nil { 79 | return fmt.Errorf("failed to add input redeem script: %s", err) 80 | } 81 | } 82 | 83 | // At this point, the PSBT must have the requisite witnessScript or 84 | // redeemScript fields for signing to succeed. 85 | // 86 | // Case 1: if witnessScript is present, it must be of type witness; 87 | // if not, signature insertion will of course fail. 88 | switch { 89 | case p.Inputs[inIndex].WitnessScript != nil: 90 | if p.Inputs[inIndex].WitnessUtxo == nil { 91 | if err := s.nonWitnessToWitness(inIndex); err != nil { 92 | return fmt.Errorf( 93 | "failed to parse non-witness to witness utxo: %s", err, 94 | ) 95 | } 96 | } 97 | 98 | // Case 2: no witness script, only redeem script; can be legacy p2sh or 99 | // p2sh-wrapped p2wkh. 100 | case p.Inputs[inIndex].RedeemScript != nil: 101 | // We only need to decide if the input is witness, and we don't 102 | // rely on the witnessutxo/nonwitnessutxo in the PSBT, instead 103 | // we check the redeemScript content. 104 | if txscript.IsWitnessProgram(redeemScript) { 105 | if p.Inputs[inIndex].WitnessUtxo == nil { 106 | if err := s.nonWitnessToWitness(inIndex); err != nil { 107 | return fmt.Errorf( 108 | "failed to parse non-witness to witness utxo: %s", err, 109 | ) 110 | } 111 | } 112 | } 113 | 114 | // Case 3: Neither provided only works for native p2wkh, or non-segwit 115 | // non-p2sh. To check if it's segwit, check the scriptPubKey of the 116 | // output. 117 | default: 118 | if p.Inputs[inIndex].WitnessUtxo == nil { 119 | outIndex := s.Pset.Inputs[inIndex].PreviousTxIndex 120 | script := s.Pset.Inputs[inIndex].NonWitnessUtxo.Outputs[outIndex].Script 121 | 122 | if txscript.IsWitnessProgram(script) { 123 | if err := s.nonWitnessToWitness(inIndex); err != nil { 124 | return fmt.Errorf( 125 | "failed to parse non-witness to witness utxo: %s", err, 126 | ) 127 | } 128 | } 129 | } 130 | } 131 | 132 | if err := s.addPartialSignature(inIndex, sig, pubKey); err != nil { 133 | return fmt.Errorf("failed to add signature for input %d: %s", inIndex, err) 134 | } 135 | 136 | s.Pset.Global = p.Global 137 | s.Pset.Inputs = p.Inputs 138 | s.Pset.Outputs = p.Outputs 139 | return s.Pset.SanityCheck() 140 | } 141 | 142 | // SignTaprootInputKeySig adds a taproot key-path signature to the input at inIndex 143 | // it returns an error if the input has tapscript signatures or if the input is already signed with key signature 144 | func (s *Signer) SignTaprootInputKeySig( 145 | inIndex int, sig []byte, 146 | ) error { 147 | if inIndex < 0 || inIndex >= int(s.Pset.Global.InputCount) { 148 | return ErrInputIndexOutOfRange 149 | } 150 | 151 | p := s.Pset.Copy() 152 | 153 | if isFinalized(p, inIndex) { 154 | return nil 155 | } 156 | 157 | if len(p.Inputs[inIndex].TapScriptSig) > 0 { 158 | return ErrSignerForbiddenTaprootKeySigHasTapscriptSigs 159 | } 160 | 161 | p.Inputs[inIndex].TapKeySig = sig 162 | 163 | s.Pset.Global = p.Global 164 | s.Pset.Inputs = p.Inputs 165 | s.Pset.Outputs = p.Outputs 166 | return s.Pset.SanityCheck() 167 | } 168 | 169 | // SignTaprootInputTapscriptSig adds a taproot tapscript signature to the input at inIndex 170 | // it returns an error if the input is signed with key-path signature 171 | func (s *Signer) SignTaprootInputTapscriptSig( 172 | inIndex int, tapscriptSig TapScriptSig, 173 | ) error { 174 | if inIndex < 0 || inIndex >= int(s.Pset.Global.InputCount) { 175 | return ErrInputIndexOutOfRange 176 | } 177 | 178 | p := s.Pset.Copy() 179 | 180 | if isFinalized(p, inIndex) { 181 | return nil 182 | } 183 | 184 | if len(p.Inputs[inIndex].TapKeySig) > 0 { 185 | return ErrSignerForbiddenTaprootScriptSigHasKeySig 186 | } 187 | 188 | if p.Inputs[inIndex].TapScriptSig == nil { 189 | p.Inputs[inIndex].TapScriptSig = make([]TapScriptSig, 0) 190 | } 191 | 192 | p.Inputs[inIndex].TapScriptSig = append(p.Inputs[inIndex].TapScriptSig, tapscriptSig) 193 | 194 | s.Pset.Global = p.Global 195 | s.Pset.Inputs = p.Inputs 196 | s.Pset.Outputs = p.Outputs 197 | return s.Pset.SanityCheck() 198 | } 199 | -------------------------------------------------------------------------------- /psetv2/testdata/bitset.json: -------------------------------------------------------------------------------- 1 | { 2 | "valid": [ 3 | { 4 | "value": 0, 5 | "expected": "00000000" 6 | }, 7 | { 8 | "value": 1, 9 | "expected": "00000001" 10 | }, 11 | { 12 | "value": 2, 13 | "expected": "00000010" 14 | }, 15 | { 16 | "value": 255, 17 | "expected": "11111111" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /slip77/data/slip77.json: -------------------------------------------------------------------------------- 1 | { 2 | "fromSeed": [ 3 | { 4 | "seed": "c76c4ac4f4e4a00d6b274d5c39c700bb4a7ddc04fbc6f78e85ca75007b5b495f74a9043eeb77bdd53aa6fc3a0e31462270316fa04b8c19114c8798706cd02ac8", 5 | "expected": "6c2de18eabeff3f7822bc724ad482bef0557f3e1c1e1c75b7a393a5ced4de616" 6 | } 7 | ], 8 | "deriveKey": [ 9 | { 10 | "masterKey": "6c2de18eabeff3f7822bc724ad482bef0557f3e1c1e1c75b7a393a5ced4de616", 11 | "script": "76a914a579388225827d9f2fe9014add644487808c695d88ac", 12 | "expectedPrivKey": "4e6e94df28448c7bb159271fe546da464ea863b3887d2eec6afd841184b70592", 13 | "expectedPubKey": "0223ef5cf5d1185f86204b9386c8541061a24b6f72fa4a29e3a0b60e1c20ffaf5b" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /slip77/slip77.go: -------------------------------------------------------------------------------- 1 | package slip77 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha256" 6 | "crypto/sha512" 7 | "errors" 8 | 9 | "github.com/btcsuite/btcd/btcec/v2" 10 | ) 11 | 12 | var ( 13 | domain = []byte("Symmetric key seed") 14 | label = []byte("SLIP-0077") 15 | prefix = byte(0) 16 | ) 17 | 18 | type Slip77 struct { 19 | MasterKey []byte 20 | } 21 | 22 | // FromMasterKey sets the provided master key to the returned instance of Slip77 23 | func FromMasterKey(masterKey []byte) (*Slip77, error) { 24 | if masterKey == nil || len(masterKey) <= 0 { 25 | return nil, errors.New("invalid master key") 26 | } 27 | 28 | return &Slip77{ 29 | MasterKey: masterKey, 30 | }, nil 31 | } 32 | 33 | // FromSeed derives the master key from the given seed and uses it to create 34 | // and return a new Slip77 instance 35 | func FromSeed(seed []byte) (*Slip77, error) { 36 | if seed == nil || len(seed) <= 0 { 37 | return nil, errors.New("invalid seed") 38 | } 39 | 40 | hmacRoot := hmac.New(sha512.New, domain) 41 | hmacRoot.Write(seed) 42 | root := hmacRoot.Sum(nil) 43 | 44 | hmacMasterKey := hmac.New(sha512.New, root[:32]) 45 | hmacMasterKey.Write([]byte{prefix}) 46 | hmacMasterKey.Write(label) 47 | masterKey := hmacMasterKey.Sum(nil) 48 | 49 | return FromMasterKey(masterKey[32:]) 50 | } 51 | 52 | // DeriveKey derives a private key from the master key of the Slip77 type 53 | // and a provided script 54 | func (s *Slip77) DeriveKey(script []byte) (*btcec.PrivateKey, *btcec.PublicKey, error) { 55 | if s.MasterKey == nil || len(s.MasterKey) <= 0 { 56 | return nil, nil, errors.New("master key must be defined") 57 | } 58 | if script == nil || len(script) <= 0 { 59 | return nil, nil, errors.New("invalid script") 60 | } 61 | 62 | hmacKey := hmac.New(sha256.New, s.MasterKey) 63 | hmacKey.Write(script) 64 | key := hmacKey.Sum(nil) 65 | 66 | privateKey, publicKey := btcec.PrivKeyFromBytes(key) 67 | 68 | return privateKey, publicKey, nil 69 | } 70 | -------------------------------------------------------------------------------- /slip77/slip77_test.go: -------------------------------------------------------------------------------- 1 | package slip77 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "io/ioutil" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestFromSeed(t *testing.T) { 13 | file, err := ioutil.ReadFile("data/slip77.json") 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | var tests map[string]interface{} 18 | json.Unmarshal(file, &tests) 19 | 20 | for _, testVector := range tests["fromSeed"].([]interface{}) { 21 | v := testVector.(map[string]interface{}) 22 | seed, _ := hex.DecodeString(v["seed"].(string)) 23 | 24 | slip77Node, err := FromSeed(seed) 25 | if !assert.NoError(t, err) { 26 | t.Fatal(err) 27 | } 28 | 29 | expected := v["expected"].(string) 30 | assert.Equal(t, expected, hex.EncodeToString(slip77Node.MasterKey)) 31 | } 32 | } 33 | 34 | func TestDeriveKey(t *testing.T) { 35 | file, err := ioutil.ReadFile("data/slip77.json") 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | var tests map[string]interface{} 40 | json.Unmarshal(file, &tests) 41 | 42 | for _, testVector := range tests["deriveKey"].([]interface{}) { 43 | v := testVector.(map[string]interface{}) 44 | script, _ := hex.DecodeString(v["script"].(string)) 45 | masterKey, _ := hex.DecodeString(v["masterKey"].(string)) 46 | 47 | slip77Node, err := FromMasterKey(masterKey) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | 52 | privKey, pubKey, err := slip77Node.DeriveKey(script) 53 | if err != nil { 54 | t.Fatal(err) 55 | } 56 | 57 | serializedPrivKey := hex.EncodeToString(privKey.Serialize()) 58 | serializedPubKey := hex.EncodeToString(pubKey.SerializeCompressed()) 59 | 60 | assert.Equal( 61 | t, 62 | v["expectedPrivKey"].(string), 63 | serializedPrivKey, 64 | ) 65 | assert.Equal( 66 | t, 67 | v["expectedPubKey"].(string), 68 | serializedPubKey, 69 | ) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /transaction/data/issuance.json: -------------------------------------------------------------------------------- 1 | { 2 | "valid": [ 3 | { 4 | "assetAmount": 1000, 5 | "tokenAmount": 1, 6 | "txHash": "39453cf897e2f0c2e9563364874f4b2a85be06dd8ec10665085033eeb75016c3", 7 | "index": 68, 8 | "tokenFlag": 0, 9 | "precision": 4, 10 | "expectedAssetAmount": 1000, 11 | "expectedTokenAmount": 1, 12 | "expectedEntropy": "3db9d8b4a9da087b42f29f34431412aaa24d63750bb31b9a2e263797248135e0", 13 | "expectedAsset": "dedf795f74e8b52c6ff8a9ad390850a87b18aeb2be9d1967038308290093a893", 14 | "expectedToken": "fa1074db60b598cf1d6d0318655125c26a2afc9fe57fb2bdff8d8f7408f8814d" 15 | }, 16 | { 17 | "assetAmount": 1000, 18 | "tokenAmount": 1, 19 | "txHash": "8f2713d29adbc1668229b6f35a9452d19f0020c7f65366ca9cb72c4a212d353a", 20 | "index": 1, 21 | "tokenFlag": 0, 22 | "precision": 0, 23 | "contract": { 24 | "name": "Test", 25 | "ticker": "TST", 26 | "version": 0, 27 | "precision": 0, 28 | "entity": { "domain": "test.io" }, 29 | "issuer_pubkey": "02a9a7399de89ec2e7de876bbe0b512f78f13d5d0a3315047e5b14109c8bac38f2" 30 | }, 31 | "expectedAssetAmount": 1000, 32 | "expectedTokenAmount": 1, 33 | "expectedEntropy": "e405b6a4f891b7226cc3d9075c1a9c64ab73bb5f68e458dbe95340518c455bf7", 34 | "expectedAsset": "210521aacc918a1c78106e720a667606f53eb72be593bc450329843bc3b959f5", 35 | "expectedToken": "a0d7995a1d61446957834aaebd053a2c494dd9d14abc83486e4de2c4edd15943" 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /transaction/issuance.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/btcsuite/btcd/chaincfg/chainhash" 9 | "github.com/vulpemventures/fastsha256" 10 | "github.com/vulpemventures/go-elements/elementsutil" 11 | "github.com/vulpemventures/go-elements/internal/bufferutil" 12 | ) 13 | 14 | // IssuanceEntity defines one of the fields of the issuance contract 15 | type IssuanceEntity struct { 16 | Domain string `json:"domain"` 17 | } 18 | 19 | // IssuanceContract defines the structure of the Ricardian contract of the issuance 20 | type IssuanceContract struct { 21 | Name string `json:"name"` 22 | Ticker string `json:"ticker"` 23 | Version uint `json:"version"` 24 | Precision uint `json:"precision"` 25 | PubKey string `json:"issuer_pubkey"` 26 | Entity IssuanceEntity `json:"entity"` 27 | } 28 | 29 | // TxIssuance defines the type for Issuance field in TxInput 30 | type TxIssuance struct { 31 | AssetBlindingNonce []byte 32 | AssetEntropy []byte 33 | AssetAmount []byte 34 | TokenAmount []byte 35 | } 36 | 37 | // IsReissuance returns whether the issuance is an asset re-issuance 38 | func (issuance *TxIssuance) IsReissuance() bool { 39 | return !bytes.Equal(issuance.AssetBlindingNonce, Zero[:]) 40 | } 41 | 42 | // HasTokenAmount returns whether the token amount is defined for the issuance 43 | func (issuance *TxIssuance) HasTokenAmount() bool { 44 | return len(issuance.TokenAmount) > 1 45 | } 46 | 47 | // TxIssuanceExtended adds fields to the issuance type that are not encoded in 48 | // the transaction 49 | type TxIssuanceExtended struct { 50 | TxIssuance 51 | Precision uint 52 | ContractHash []byte 53 | } 54 | 55 | // NewTxIssuanceFromInput returns the extended issuance for the given input 56 | func NewTxIssuanceFromInput(in *TxInput) (*TxIssuanceExtended, error) { 57 | if in.Issuance.IsReissuance() { 58 | return NewTxIssuanceFromEntropy(in.Issuance.AssetEntropy), nil 59 | } 60 | 61 | iss := NewTxIssuanceFromContractHash(in.Issuance.AssetEntropy) 62 | if err := iss.GenerateEntropy(in.Hash, in.Index); err != nil { 63 | return nil, err 64 | } 65 | return iss, nil 66 | } 67 | 68 | // NewTxIssuanceFromContractHash returns a new issuance instance from contract hash 69 | func NewTxIssuanceFromContractHash(contractHash []byte) *TxIssuanceExtended { 70 | return &TxIssuanceExtended{ContractHash: contractHash} 71 | } 72 | 73 | // NewTxIssuanceFromEntropy returns a new issuance instance from entropy 74 | func NewTxIssuanceFromEntropy(entropy []byte) *TxIssuanceExtended { 75 | issuance := &TxIssuanceExtended{ 76 | TxIssuance: TxIssuance{AssetEntropy: entropy}, 77 | } 78 | return issuance 79 | } 80 | 81 | // NewTxIssuance returns a new issuance instance 82 | func NewTxIssuance( 83 | assetAmount uint64, 84 | tokenAmount uint64, 85 | precision uint, 86 | contract *IssuanceContract, 87 | ) (*TxIssuanceExtended, error) { 88 | if precision > 8 { 89 | return nil, errors.New("invalid precision") 90 | } 91 | 92 | // Use the default `0x00..00` 32-byte array if contract is not set 93 | contractHash := make([]byte, 32) 94 | if contract != nil { 95 | if contract.Precision != precision { 96 | return nil, errors.New( 97 | "precision declared in contract does not match the one" + 98 | "set as argument", 99 | ) 100 | } 101 | 102 | serializedContract, err := json.Marshal(contract) 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | tmp, err := orderJsonKeysLexographically(serializedContract) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | contractHash = chainhash.HashB(tmp) 113 | } 114 | 115 | confAssetAmount, err := toConfidentialIssuanceAmount( 116 | assetAmount, 117 | ) 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | confTokenAmount, err := toConfidentialIssuanceAmount( 123 | tokenAmount, 124 | ) 125 | if err != nil { 126 | return nil, err 127 | } 128 | 129 | issuance := TxIssuance{ 130 | AssetAmount: confAssetAmount, 131 | TokenAmount: confTokenAmount, 132 | AssetBlindingNonce: make([]byte, 32), 133 | } 134 | 135 | return &TxIssuanceExtended{ 136 | TxIssuance: issuance, 137 | Precision: precision, 138 | ContractHash: contractHash, 139 | }, nil 140 | } 141 | 142 | func ComputeEntropy(inTxHash []byte, inTxIndex uint32, contractHash []byte) ([]byte, error) { 143 | if len(inTxHash) != 32 { 144 | return nil, errors.New("invalid tx hash length") 145 | } 146 | 147 | s := bufferutil.NewSerializer(nil) 148 | 149 | err := s.WriteSlice(inTxHash) 150 | if err != nil { 151 | return nil, err 152 | } 153 | 154 | err = s.WriteUint32(inTxIndex) 155 | if err != nil { 156 | return nil, err 157 | } 158 | 159 | buf := chainhash.DoubleHashB(s.Bytes()) 160 | buf = append(buf, contractHash...) 161 | entropy := fastsha256.MidState256(buf) 162 | 163 | return entropy[:], nil 164 | } 165 | 166 | // GenerateEntropy generates the entropy from which the hash of the asset and 167 | // of the reissuance token are calculated 168 | func (issuance *TxIssuanceExtended) GenerateEntropy(inTxHash []byte, inTxIndex uint32) error { 169 | entropy, err := ComputeEntropy(inTxHash, inTxIndex, issuance.ContractHash) 170 | if err != nil { 171 | return err 172 | } 173 | 174 | issuance.TxIssuance.AssetEntropy = entropy[:] 175 | return nil 176 | } 177 | 178 | // ComputeAsset generates the hash of the asset from the issuance entropy 179 | func ComputeAsset(entropy []byte) ([]byte, error) { 180 | if entropy == nil || len(entropy) != 32 { 181 | return nil, errors.New("invalid issuance entropy size") 182 | } 183 | 184 | buf := append(entropy, make([]byte, 32)...) 185 | asset := fastsha256.MidState256(buf) 186 | return asset[:], nil 187 | } 188 | 189 | // GenerateAsset calculates the asset hash for the given issuance 190 | func (issuance *TxIssuanceExtended) GenerateAsset() ([]byte, error) { 191 | return ComputeAsset(issuance.AssetEntropy) 192 | } 193 | 194 | // ComputeReissuanceToken generates the hash of the reissuance token asset from the entropy and issuance flag 195 | func ComputeReissuanceToken(entropy []byte, flag uint) ([]byte, error) { 196 | if entropy == nil || len(entropy) != 32 { 197 | return nil, errors.New("invalid issuance entropy size") 198 | } 199 | 200 | if flag != 0 && flag != 1 { 201 | return nil, errors.New("invalid flag for reissuance token") 202 | } 203 | 204 | buf := make([]byte, 32) 205 | buf[0] = byte(flag + 1) 206 | buf = append(entropy, buf...) 207 | token := fastsha256.MidState256(buf) 208 | return token[:], nil 209 | } 210 | 211 | // GenerateReissuanceToken calculates the asset hash for the given issuance 212 | func (issuance *TxIssuanceExtended) GenerateReissuanceToken(flag uint) ([]byte, error) { 213 | return ComputeReissuanceToken(issuance.AssetEntropy, flag) 214 | } 215 | 216 | func toConfidentialIssuanceAmount(tokenAmount uint64) ([]byte, error) { 217 | if tokenAmount == 0 { 218 | return []byte{0x00}, nil 219 | } 220 | 221 | confAmount, err := elementsutil.ValueToBytes(tokenAmount) 222 | if err != nil { 223 | return nil, err 224 | } 225 | return confAmount[:], nil 226 | } 227 | 228 | func orderJsonKeysLexographically(bytes []byte) ([]byte, error) { 229 | var ifce interface{} 230 | err := json.Unmarshal(bytes, &ifce) 231 | if err != nil { 232 | return []byte{}, err 233 | } 234 | output, err := json.Marshal(ifce) 235 | if err != nil { 236 | return []byte{}, err 237 | } 238 | return output, nil 239 | } 240 | -------------------------------------------------------------------------------- /transaction/issuance_test.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "io/ioutil" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/vulpemventures/go-elements/elementsutil" 11 | ) 12 | 13 | func TestIssuanceGeneration(t *testing.T) { 14 | file, err := ioutil.ReadFile("data/issuance.json") 15 | if !assert.NoError(t, err) { 16 | t.FailNow() 17 | } 18 | 19 | var tests map[string]interface{} 20 | json.Unmarshal(file, &tests) 21 | 22 | for _, testVector := range tests["valid"].([]interface{}) { 23 | v := testVector.(map[string]interface{}) 24 | 25 | inTxHash, _ := hex.DecodeString(v["txHash"].(string)) 26 | inIndex := uint32(v["index"].(float64)) 27 | assetAmount := uint64(v["assetAmount"].(float64)) 28 | tokenAmount := uint64(v["tokenAmount"].(float64)) 29 | tokenFlag := uint(v["tokenFlag"].(float64)) 30 | precision := uint(v["precision"].(float64)) 31 | 32 | var contract *IssuanceContract 33 | if v["contract"] != nil { 34 | var c IssuanceContract 35 | contractBytes, _ := json.Marshal(v["contract"].(interface{})) 36 | json.Unmarshal(contractBytes, &c) 37 | contract = &c 38 | } 39 | 40 | issuance, err := NewTxIssuance( 41 | assetAmount, 42 | tokenAmount, 43 | precision, 44 | contract, 45 | ) 46 | if !assert.NoError(t, err) { 47 | t.FailNow() 48 | } 49 | 50 | resAssetAmount, _ := elementsutil.ValueFromBytes( 51 | issuance.TxIssuance.AssetAmount, 52 | ) 53 | resTokenAmount, _ := elementsutil.ValueFromBytes( 54 | issuance.TxIssuance.TokenAmount, 55 | ) 56 | assert.Equal(t, uint64(v["expectedAssetAmount"].(float64)), resAssetAmount) 57 | assert.Equal(t, uint64(v["expectedTokenAmount"].(float64)), resTokenAmount) 58 | 59 | err = issuance.GenerateEntropy(elementsutil.ReverseBytes(inTxHash), inIndex) 60 | if !assert.NoError(t, err) { 61 | t.FailNow() 62 | } 63 | assert.Equal(t, v["expectedEntropy"].(string), hex.EncodeToString(issuance.AssetEntropy)) 64 | 65 | asset, err := issuance.GenerateAsset() 66 | if !assert.NoError(t, err) { 67 | t.FailNow() 68 | } 69 | assert.Equal( 70 | t, 71 | v["expectedAsset"].(string), 72 | hex.EncodeToString(elementsutil.ReverseBytes(asset)), 73 | ) 74 | 75 | token, err := issuance.GenerateReissuanceToken(tokenFlag) 76 | if !assert.NoError(t, err) { 77 | t.FailNow() 78 | } 79 | assert.Equal( 80 | t, 81 | v["expectedToken"].(string), 82 | hex.EncodeToString(elementsutil.ReverseBytes(token)), 83 | ) 84 | } 85 | } 86 | 87 | func TestIsContractHashValid(t *testing.T) { 88 | contract := IssuanceContract{ 89 | Name: "Tiero Token", 90 | Ticker: "TIERO", 91 | Version: 0, 92 | Precision: 8, 93 | PubKey: "02a9a7399de89ec2e7de876bbe0b512f78f13d5d0a3315047e5b14109c8bac38f2", 94 | Entity: IssuanceEntity{ 95 | Domain: "tiero.github.io", 96 | }, 97 | } 98 | 99 | issuance, err := NewTxIssuance(10, 2, 8, &contract) 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | 104 | assert.Equal( 105 | t, 106 | "d5c4363ee9cf2a4319c2f0ccc04cdb83d6213e4d26d94d70003c58eaf2473866", 107 | hex.EncodeToString(elementsutil.ReverseBytes(issuance.ContractHash)), //validate online with reverse contract hash 108 | ) 109 | assert.Equal( 110 | t, 111 | "663847f2ea583c00704dd9264d3e21d683db4cc0ccf0c219432acfe93e36c4d5", 112 | hex.EncodeToString(issuance.ContractHash), 113 | ) 114 | } 115 | --------------------------------------------------------------------------------