├── .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 | [](https://github.com/vulpemventures/go-elements/actions/workflows/ci.yml)
8 | [](https://pkg.go.dev/github.com/vulpemventures/go-elements)
9 | [](https://github.com/vulpemventures/go-elements/releases/latest)
10 | [](https://goreportcard.com/report/github.com/vulpemventures/go-elements)
11 | [](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 |
--------------------------------------------------------------------------------