Hiểu Bitcoin Miniscript (3): Phân tích cú pháp và Phân tích

Bài viết này được dịch máy
Xem bản gốc

Tác giả: benma

Nguồn: https://shiftcrypto.ch/blog/under Hiểu -bitcoin-miniscript-part-3/

Bài viết này là bài thứ ba trong loạt bài "Hiểu biết về Bitcoin Script". Xem bài trước tại đây .

Tìm hiểu về Bitcoin Miniscript - Phần III

Trong phần trước của loạt bài này, chúng tôi đã giới thiệu Miniscript là gì và cách nó ánh xạ tới Bitcoin Script.

Để hiểu chi tiết cách thức hoạt động của Miniscript, sẽ rất hữu ích nếu bạn xem xét một ví dụ về cách triển khai nó -- bao gồm cách phân tích mã để đảm bảo tính chính xác, cách tạo địa chỉ nhận và cách tiêu tiền.

Vì vậy, hãy hiểu và viết triển khai ngôn ngữ Go.

Chúng tôi sẽ luôn đề cập đến https://bitcoin.sipa.be/miniscript/ vì nó chứa thông tin chi tiết và tính năng của tất cả các phần Miniscript.

Đầu và cuối mỗi chương bao gồm một liên kết đến thời gian chạy Go live, nơi bạn có thể chạy mã, kiểm tra kết quả và mày mò mã.

Nói tóm lại, chúng tôi sẽ chuyển đổi một đoạn Miniscript thành cây cú pháp trừu tượng (AST), sau đó thực hiện một loạt phép biến đổi cây và duyệt cây để thực hiện phân tích tính chính xác, tạo Bitcoin Script tương ứng, tạo địa chỉ thanh toán, v.v.

Tuyên bố miễn trừ trách nhiệm: Các triển khai bên dưới chưa được xem xét hoặc thử nghiệm. Vui lòng không sử dụng trong môi trường sản xuất. Nó chỉ có thể được sử dụng cho mục đích giảng dạy.

Bước đầu tiên: chuyển đổi thành một cây cú pháp trừu tượng

Nhấp vào đây để chạy mã trong môi trường Phát trực tiếp

Các biểu thức Miniscript rất đơn giản và dễ dàng chuyển đổi thành AST . Không giống như các biểu thức toán học/đại số, các biểu thức Miniscript không chứa bất kỳ toán tử trung tố nào, dấu ngoặc đơn nhóm và dấu ngoặc đơn chỉ được sử dụng để bao quanh các tham số phân đoạn. Do đó, nó là biểu cảm và dễ dàng để phân tích.

Hãy xác định AST cần thiết:

 // AST 是用来表示一个Miniscript 表达式的抽象语法树。type AST struct { wrappers string identifier string args []*AST}

Các mã định danh như or_b sẽ được lưu trữ trong trường identifier . Nếu có bất kỳ trình bao bọc nào, chẳng hạn như ascd:X , thì các trình bao bọc này sẽ được tách ra và lưu trữ trong trường trình wrappers . Cuối cùng, các đối số của đoạn sẽ được lưu trữ đệ quy trong args .

Để biến một biểu thức thành cây, chúng ta cần cấu trúc dữ liệu ngăn xếp cũ và đáng tin cậy:

 type stack struct { elements []*AST}func (s *stack) push(element *AST) { s.elements = append(s.elements, element)}func (s *stack) pop() *AST { if len(s.elements) == 0 { return nil } top := s.elements[len(s.elements)-1] s.elements = s.elements[:len(s.elements)-1] return top}func (s *stack) top() *AST { if len(s.elements) == 0 { return nil } return s.elements[len(s.elements)-1]}func (s *stack) size() int { return len(s.elements)}

Để chuyển đổi một biểu thức thành cây bằng cách sử dụng ngăn xếp, trước tiên chúng ta chia biểu thức thành các phần được phân tách bằng dấu ngoặc đơn và dấu phẩy. Thật không may, không có chức năng phân luồng trong thư viện tiêu chuẩn của Go, vì vậy tôi đã yêu cầu ChatGPT viết cho tôi một đoạn mã và nó đã hoạt động:

 / - 使用ChatGPT 编写。// splitString 函数使用separator 作为分割元素单元,将一个字数串// 基于多种separator 分割成字符串切片。它也将// 把输出切片中的空元素移除。func splitString(s string, isSeparator func(c rune) bool) []string { // Create a slice to hold the substrings substrs := make([]string, 0) // Set the initial index to zero i := 0 // Iterate over the characters in the string for i < len(s) { // Find the index of the first separator in the string j := strings.IndexFunc(s[i:], isSeparator) if j == -1 { // If no separator was found, append the remaining substring and return substrs = append(substrs, s[i:]) return substrs } j += i // If a separator was found, append the substring before it if j > i { substrs = append(substrs, s[i:j]) } // Append the separator as a separate element substrs = append(substrs, s[j:j+1]) i = j + 1 } return substrs}

Kiểm tra đơn vị nhanh chóng xác nhận rằng mã này hoạt động:

 func TestSplitString(t *testing.T) { separators := func(c rune) bool { return c == '(' || c == ')' || c == ',' } require.Equal(t, []string{}, splitString("", separators)) require.Equal(t, []string{"0"}, splitString("0", separators)) require.Equal(t, []string{"0", ")", "(", "1", "("}, splitString("0)(1(", separators)) require.Equal(t, []string{"or_b", "(", "pk", "(", "key_1", ")", ",", "s:pk", "(", "key_2", ")", ")"}, splitString("or_b(pk(key_1),s:pk(key_2))", separators))}

Chúng tôi đã sẵn sàng lặp lại các đoạn và dấu ngoặc đơn/dấu phẩy và xây dựng một cây biểu thức.

Bất cứ khi nào chúng tôi nhìn thấy một mã định danh (bất kỳ thứ gì ngoài dấu ngoặc đơn và dấu phẩy), chúng tôi sẽ đẩy mã định danh vào ngăn xếp và nó sẽ trở thành cấp độ cha của tất cả các tham số con của nó. Bất cứ khi nào chúng tôi gặp dấu phẩy hoặc dấu ngoặc đơn phía sau, chúng tôi biết điều này cho biết phần cuối của đối số, vì vậy chúng tôi lấy đối số ra khỏi ngăn xếp và thêm nó vào nút cha. Một số chuỗi không hợp lệ bị loại trừ rõ ràng, chẳng hạn như "()" và "(", sẽ không phải là các tập lệnh nhỏ hợp lệ.

 func createAST(miniscript string) (*AST, error) { tokens := splitString(miniscript, func(c rune) bool { return c == '(' || c == ')' || c == ',' }) if len(tokens) > 0 { first, last := tokens[0], tokens[len(tokens)-1] if first == "(" || first == ")" || first == "," || last == "(" || last == "," { return nil, errors.New("invalid first or last character") } } // Build abstract syntax tree. var stack stack for i, token := range tokens { switch token { case "(": // Exclude invalid sequences, which cannot appear in valid miniscripts: "((", ")(", ",(". if i > 0 && (tokens[i-1] == "(" || tokens[i-1] == ")" || tokens[i-1] == ",") { return nil, fmt.Errorf("the sequence %s%s is invalid", tokens[i-1], token) } case ",", ")": // End of a function argument - take the argument and add it to the parent's argument // list. If there is no parent, the expression is unbalanced, eg `f(X))``. // // Exclude invalid sequences, which cannot appear in valid miniscripts: "(,", "()", ",,", ",)". if i > 0 && (tokens[i-1] == "(" || tokens[i-1] == ",") { return nil, fmt.Errorf("the sequence %s%s is invalid", tokens[i-1], token) } arg := stack.pop() parent := stack.top() if arg == nil || parent == nil { return nil, errors.New("unbalanced") } parent.args = append(parent.args, arg) default: if i > 0 && tokens[i-1] == ")" { return nil, fmt.Errorf("the sequence %s%s is invalid", tokens[i-1], token) } // Split wrappers from identifier if they exist, eg in "dv:older", "dv" are wrappers // and "older" is the identifier. wrappers, identifier, found := strings.Cut(token, ":") if !found { // No colon => Cut returns `identifier, ""`, not `"", identifier"`. wrappers, identifier = identifier, wrappers } else if wrappers == "" { return nil, fmt.Errorf("no wrappers found before colon before identifier: %s", identifier) } else if identifier == "" { return nil, fmt.Errorf("no identifier found after colon after wrappers: %s", wrappers) } stack.push(&AST{wrappers: wrappers, identifier: identifier}) } } if stack.size() != 1 { return nil, errors.New("unbalanced") } return stack.top(), nil}Let's also add a function to draw the tree, so we can visualize it more easily:func (a *AST) drawTree(w io.Writer, indent string) { if a.wrappers != "" { fmt.Fprintf(w, "%s:", a.wrappers) } fmt.Fprint(w, a.identifier) fmt.Fprintln(w) for i, arg := range a.args { mark := "" delim := "" if i == len(a.args)-1 { mark = "└──" } else { mark = "├──" delim = "|" } fmt.Fprintf(w, "%s%s", indent, mark) arg.drawTree(w, indent+delim+strings.Repeat(" ", len([]rune(arg.identifier))+len([]rune(mark))-1-len(delim))) }}func (a *AST) DrawTree() string { var b strings.Builder a.drawTree(&b, "") return b.String()}

Hãy thử với một biểu thức phức tạp:

 func main() { node, err := createAST("andor(pk(key_remote),or_i(and_v(v:pkh(key_local),hash160(H)),older(1008)),pk(key_revocation))") if err != nil { panic(err) } fmt.Println(node.DrawTree())}

thành công! Đầu ra là thế này:

 andor├──pk| └──key_remote├──or_i| ├──and_v| | ├──v:pkh| | | └──key_local| | └──hash160| | └──H| └──older| └──1008└──pk └──key_revocation

Tất nhiên, trình phân tích cú pháp chưa kiểm tra nó, vì vậy các biểu thức unknownFragment(foo,bar) cũng có thể được chuyển thành AST:

 unknownFragment├──foo└──bar

Nhấp vào đây để chạy mã trong môi trường Go trực tiếp

Bước hai: Kiểm tra số đoạn và tham số

Nhấp vào đây để chạy mã trong môi trường Go trực tiếp

Là bước đầu tiên trong quá trình duyệt nhiều cây, chúng tôi thực hiện kiểm tra dễ dàng đầu tiên: mọi mã định danh đoạn trên cây có hợp lệ không? Mỗi cái có số tham số chính xác?

Theo đặc điểm kỹ thuật , tất cả các mảnh được sắp xếp như sau:

 const ( // 所有的片段标识符f_0 = "0" // 0 f_1 = "1" // 1 f_pk_k = "pk_k" // pk_k(key) f_pk_h = "pk_h" // pk_h(key) f_pk = "pk" // pk(key) = c:pk_k(key) f_pkh = "pkh" // pkh(key) = c:pk_h(key) f_sha256 = "sha256" // sha256(h) f_ripemd160 = "ripemd160" // ripemd160(h) f_hash256 = "hash256" // hash256(h) f_hash160 = "hash160" // hash160(h) f_older = "older" // older(n) f_after = "after" // after(n) f_andor = "andor" // andor(X,Y,Z) f_and_v = "and_v" // and_v(X,Y) f_and_b = "and_b" // and_b(X,Y) f_and_n = "and_n" // and_n(X,Y) = andor(X,Y,0) f_or_b = "or_b" // or_b(X,Z) f_or_c = "or_c" // or_c(X,Z) f_or_d = "or_d" // or_d(X,Z) f_or_i = "or_i" // or_i(X,Z) f_thresh = "thresh" // thresh(k,X1,...,Xn) f_multi = "multi" // multi(k,key1,...,keyn))

Các đối số đầu tiên của older , after , threshmulti đều là số. Khi chúng tôi cần phân tích cú pháp, hãy kiểm tra xem đó có phải là một số hợp lệ hay không, chúng tôi sẽ chuyển đổi nó thành một số và lưu trữ trong AST của chúng tôi để sử dụng sau này. Do đó, chúng tôi thêm một trường mới vào AST:

 // AST is the abstract syntax tree representing a Miniscript expression.type AST struct { wrappers string identifier string // 在标识符预计是个数字时解析出来的整数。 // 比如older/after/multi/thresh 的第一个参数。否则不使用num uint64 args []*AST}

Chúng ta cũng cần một hàm duyệt cây theo cách đệ quy và áp dụng một hàm cho từng biểu thức/biểu thức con Miniscript. Hàm chuyển đổi này có thể sửa đổi một nút hoặc trực tiếp thay thế nút đó bằng một nút mới, rất hữu ích cho giai đoạn phân tích cú pháp cuối cùng:

 func (a *AST) apply(f func(*AST) (*AST, error)) (*AST, error) { for i, arg := range a.args { // 我们并不递归进入不是Miniscript 表达式的参数: // key/hash 标量以及older/after/multi/thresh 的数值参数。 switch a.identifier { case f_pk_k, f_pk_h, f_pk, f_pkh, f_sha256, f_hash256, f_ripemd160, f_hash160, f_older, f_after, f_multi: // 这些函数的变量都不是Miniscript 表达式,只是// 变量(或者说具体的指定)或者数字。 continue case f_thresh: // 第一个参数是一个数字,其它的参数是子表达式, // 就是我们想要遍历的东西,所以我们只跳过第一个参数。 if i == 0 { continue } } new, err := arg.apply(f) if err != nil { return nil, err } a.args[i] = new } return f(a)}

trường hợp:

 node, _ := createAST("andor(pk(key_remote),or_i(and_v(v:pkh(key_local),hash160(H)),older(1008)),pk(key_revocation))")node.apply(func(node *AST) (*AST, error) { fmt.Println("Visiting node:", node.identifier) return node, nil })

đầu ra:

 Visiting node: pkVisiting node: pkhVisiting node: hash160Visiting node: and_vVisiting node: olderVisiting node: or_iVisiting node: pkVisiting node: andor

Bây giờ, chúng ta thêm một hàm Parse để tạo AST và áp dụng các hàm chuyển đổi liên tiếp, hàm đầu tiên là trình kiểm tra phân đoạn và tham số:

 func Parse(miniscript string) (*AST, error) { node, err := createAST(miniscript) if err != nil { return nil, err } for _, transform := range []func(*AST) (*AST, error){ argCheck, // More stages to come } { node, err = node.apply(transform) if err != nil { return nil, err } } return node, nil}

Hàm argCheck sẽ được sử dụng cho từng nút của cây và chúng ta có thể chỉ cần liệt kê tất cả các mã định danh phân đoạn hợp lệ để thực hiện kiểm tra cơ bản này:

 // argCheck 会检查每一个标识符都是一种已知的Miniscript 标识符,并且// 具有正确数量的参数,例如`andor(X,Y,Z)` 必须有三个参数,等等。func argCheck(node *AST) (*AST, error) { // Helper function to check that this node has a specific number of arguments. expectArgs := func(num int) error { if len(node.args) != num { return fmt.Errorf("%s expects %d arguments, got %d", node.identifier, num, len(node.args)) } return nil } switch node.identifier { case f_0, f_1: if err := expectArgs(0); err != nil { return nil, err } case f_pk_k, f_pk_h, f_pk, f_pkh, f_sha256, f_ripemd160, f_hash256, f_hash160: if err := expectArgs(1); err != nil { return nil, err } if len(node.args[0].args) > 0 { return nil, fmt.Errorf("argument of %s must not contain subexpressions", node.identifier) } case f_older, f_after: if err := expectArgs(1); err != nil { return nil, err } _n := node.args[0] if len(_n.args) > 0 { return nil, fmt.Errorf("argument of %s must not contain subexpressions", node.identifier) } n, err := strconv.ParseUint(_n.identifier, 10, 64) if err != nil { return nil, fmt.Errorf( "%s(k) => k must be an unsigned integer, but got: %s", node.identifier, _n.identifier) } _n.num = n if n < 1 || n >= (1<<31) { return nil, fmt.Errorf("%s(n) -> n must 1 ≤ n < 2^31, but got: %s", node.identifier, _n.identifier) } case f_andor: if err := expectArgs(3); err != nil { return nil, err } case f_and_v, f_and_b, f_and_n, f_or_b, f_or_c, f_or_d, f_or_i: if err := expectArgs(2); err != nil { return nil, err } case f_thresh, f_multi: if len(node.args) < 2 { return nil, fmt.Errorf("%s must have at least two arguments", node.identifier) } _k := node.args[0] if len(_k.args) > 0 { return nil, fmt.Errorf("argument of %s must not contain subexpressions", node.identifier) } k, err := strconv.ParseUint(_k.identifier, 10, 64) if err != nil { return nil, fmt.Errorf( "%s(k, ...) => k must be an integer, but got: %s", node.identifier, _k.identifier) } _k.num = k numSubs := len(node.args) - 1 if k < 1 || k > uint64(numSubs) { return nil, fmt.Errorf( "%s(k) -> k must 1 ≤ k ≤ n, but got: %s", node.identifier, _k.identifier) } if node.identifier == f_multi { // 一个multisig 可以拥有的最大公钥数量。 const multisigMaxKeys = 20 if numSubs > multisigMaxKeys { return nil, fmt.Errorf("number of multisig keys cannot exceed %d", multisigMaxKeys) } // Multisig 的密钥是一种变量,无法拥有子表达式。 for _, arg := range node.args { if len(arg.args) > 0 { return nil, fmt.Errorf("arguments of %s must not contain subexpressions", node.identifier) } } } default: return nil, fmt.Errorf("unrecognized identifier: %s", node.identifier) } return node, nil}

Với những lần kiểm tra này, chúng tôi đã có thể loại trừ hầu hết Bản tóm tắt không hợp lệ. Hãy xem xét một số trường hợp:

 func main() { for _, expr := range []string{ "invalid", "pk(key1,tooManyArgs)", "pk(key1(0))", "and_v(0)", "after(notANumber)", "after(-1)", "multi(0,k1)", "multi(2,k1)", "multi(1,k1,k2,k3,k4,k5,k6,k7,k8,k9,k10,k11,k12,k13,k14,k15,k16,k17,k18,k19,k20,k21)", } { _, err := Parse(expr) fmt.Println(expr, " -- ", err) }}

đầu ra:

 invalid -- unrecognized identifier: invalidpk(key1,tooManyArgs) -- pk expects 1 arguments, got 2pk(key1(0)) -- argument of pk must not contain subexpressionsand_v(0) -- and_v expects 2 arguments, got 1after(notANumber) -- after(k) => k must be an unsigned integer, but got: notANumberafter(-1) -- after(k) => k must be an unsigned integer, but got: -1multi(0,k1) -- multi(k) -> k must 1 ≤ k ≤ n, but got: 0multi(2,k1) -- multi(k) -> k must 1 ≤ k ≤ n, but got: 2multi(1,k1,k2,k3,k4,k5,k6,k7,k8,k9,k10,k11,k12,k13,k14,k15,k16,k17,k18,k19,k20,k21) -- number of multisig keys cannot exceed 20

Nhấp vào đây để chạy mã trong môi trường Go trực tiếp

Bước 3: Mở rộng trình bao bọc

Nhấp vào đây để chạy mã trong môi trường Go trực tiếp

Mỗi đoạn có thể được bọc bởi một trình bao bọc, được biểu thị bằng dấu hai chấm ":". Ví dụ từ đây .

dv:older(144) chỉ ra rằng trình bao bọc d: được áp dụng cho trình bao bọc v: và trình bao bọc v: được sử dụng để bao bọc phân đoạn older có tham số là 144 khối.

Trong giai đoạn tiếp theo của trình phân tích cú pháp, chúng tôi cũng muốn hoạt động trên các trình bao bọc, vì chúng hoạt động giống như các đoạn bình thường: chúng có thể được ánh xạ tới Bitcoin Script, có các quy tắc chính xác của riêng chúng, v.v. Nói một cách dễ hiểu, dv:older(144) chỉ là đường cú pháp cho d(v(older(144))) .

Trong trường hợp này, chúng tôi muốn chuyển đổi AST từ đây:

 dv:older└──144

trở thành như thế này:

 d└──v └──older └──144

Để thực hiện việc chuyển đổi này, chúng ta cần thêm chức năng này vào danh sách chuyển đổi. Lưu ý rằng chúng tôi lặp lại các chữ cái trong trình bao bọc theo thứ tự ngược lại, vì chúng được sử dụng từ phải sang trái.

 // expandWrappers 应用在封装器(引号以前的字母)上,例如`ascd:X` =>// `a(s(c(d(X))))`.func expandWrappers(node *AST) (*AST, error) { const allWrappers = "asctdvjnlu" wrappers := []rune(node.wrappers) node.wrappers = "" for i := len(wrappers) - 1; i >= 0; i-- { wrapper := wrappers[i] if !strings.ContainsRune(allWrappers, wrapper) { return nil, fmt.Errorf("unknown wrapper: %s", string(wrapper)) } node = &AST{identifier: string(wrapper), args: []*AST{node}} } return node, nil}

Nhấp vào đây để chạy mã trong môi trường Go trực tiếp

Bước 4: Mở lớp đóng băng

Nhấp vào đây để chạy mã trong môi trường Go trực tiếp

Miniscript định nghĩa 6 đường cú pháp. Nếu một đoạn mã Miniscript chứa các biểu thức cho tọa độ của các phương trình sau, thì các biểu thức này có thể được thay thế bằng các biểu thức ở bên phải của dấu bằng. Để tiết kiệm công sức xử lý 6 đoạn này ở giai đoạn sau, chúng tôi thêm một hàm chuyển đổi khử đường thay vì các biểu thức này.

Mối quan hệ thay thế như sau:

 pk(key) = c:pk_k(key)pkh(key) = c:pk_h(key)and_n(X,Y) = andor(X,Y,0)t:X = and_v(X,1)l:X = or_i(0,X)u:X = or_i(X,0)

Bây giờ, chúng ta chỉ có thể bổ sung danh sách này bằng các mã định danh mà chúng ta đã xác định ban đầu trong đoạn trình bao bọc:

 const ( // [...] f_wrap_a = "a" // a:X f_wrap_s = "s" // s:X f_wrap_c = "c" // c:X f_wrap_d = "d" // d:X f_wrap_v = "v" // v:X f_wrap_j = "j" // j:X f_wrap_n = "n" // n:X f_wrap_t = "t" // t:X = and_v(X,1) f_wrap_l = "l" // l:X = or_i(0,X) f_wrap_u = "u" // u:X = or_i(X,0)))

Chức năng chuyển đổi sẽ trở thành như thế này:

 // desugar 使用最终的形式替换了语法糖func desugar(node *AST) (*AST, error) { switch node.identifier { case f_pk: // pk(key) = c:pk_k(key) return &AST{ identifier: f_wrap_c, args: []*AST{ { identifier: f_pk_k, args: node.args, }, }, }, nil case f_pkh: // pkh(key) = c:pk_h(key) return &AST{ identifier: f_wrap_c, args: []*AST{ { identifier: f_pk_h, args: node.args, }, }, }, nil case f_and_n: // and_n(X,Y) = andor(X,Y,0) return &AST{ identifier: f_andor, args: []*AST{ node.args[0], node.args[1], {identifier: f_0}, }, }, nil case f_wrap_t: // t:X = and_v(X,1) return &AST{ identifier: f_and_v, args: []*AST{ node.args[0], {identifier: f_1}, }, }, nil case f_wrap_l: // l:X = or_i(0,X) return &AST{ identifier: f_or_i, args: []*AST{ {identifier: f_0}, node.args[0], }, }, nil case f_wrap_u: // u:X = or_i(X,0) return &AST{ identifier: f_or_i, args: []*AST{ node.args[0], {identifier: f_0}, }, }, nil } return node, nil}

Chúng tôi thử tất cả các phương pháp này và áp dụng kiểm tra trực quan:

 func main() { for _, expr := range []string{ "pk(key)", "pkh(key)", "and_n(pk(key),sha256(H))", "tv:pk(key)", "l:pk(key)", "u:pk(key)", } { node, err := Parse(expr) if err != nil { panic(err) } fmt.Printf("Tree for \"%v\"\n", expr) fmt.Println(node.DrawTree()) }}

Như bạn có thể thấy từ đầu ra bên dưới, chức năng khử đường đã hoạt động:

 Tree for "pk(key)"c└──pk_k └──keyTree for "pkh(key)"c└──pk_h └──keyTree for "and_n(pk(key),sha256(H))"andor├──c| └──pk_k| └──key├──sha256| └──H└──0Tree for "tv:pk(key)"and_v├──v| └──c| └──pk_k| └──key└──1Tree for "l:pk(key)"or_i├──0└──c └──pk_k └──keyTree for "u:pk(key)"or_i├──c| └──pk_k| └──key└──0

Nhấp vào đây để chạy mã trong môi trường Go trực tiếp

Bước 5: Kiểm tra kiểu

Nhấp vào đây để chạy mã trong môi trường Go trực tiếp

Không phải tất cả các phần đều có thể được kết hợp với nhau: một số kết hợp không tạo ra tập lệnh Bitcoin hợp lệ và dữ liệu nhân chứng hợp lệ.

Tuy nhiên, vì các đoạn và biểu thức Miniscript có đủ cấu trúc và phân cấp nên dễ dàng phân tích tĩnh xem một đoạn của biểu thức Miniscript có hợp lệ trong mọi điều kiện hay không.

Ví dụ: or_b(pk(key1),pk(key2))or_b(v:pk(key1),v:pk(key2)) không phải là các kết hợp hợp lệ, nhưng or_b(pk(key1),s:pk(key2)) là hợp lệ.

Theo đặc tả Miniscript , mỗi mảnh có thể là một trong bốn loại cơ bản B , V , KW ; mỗi mảnh có thể có các thuộc tính loại bổ sung ( z , o , n , du ).

Các đoạn thành phần (các đoạn không thể chứa bất kỳ biểu thức con nào) có một loại cơ sở cố định và các thuộc tính loại cố định. Ví dụ: đoạn hàm băm sha256(h) sẽ được chuyển đổi thành tập lệnh Bitcoin SIZE <32> EQUALVERIFY SHA256 <h> EQUAL , có thể được thỏa mãn bằng cách sử dụng dữ liệu nhân chứng <32 byte preimage> (giá trị trong đó thỏa mãn sha256(preimage)=h ), thuộc loại Bondu , nghĩa là:

  • B : Khi thành công, đẩy một giá trị khác 0 vào ngăn xếp; nếu thất bại, hãy đẩy một giá trị 0 chính xác. Tiêu thụ phần tử trên cùng của ngăn xếp (nếu có).
  • o : tiêu thụ một yếu tố chiến đấu (trong trường hợp này là tiền đề)
  • n : thuộc tính khác 0 - không thể thỏa mãn với 0. Tiền giả định chính xác cho sha256(h) phải dài 32 byte, vì vậy nó không thể bằng 0.
  • d : Có thể tránh vô điều kiện. Trong ví dụ sha256() , bất kỳ dữ liệu nào có kích thước 32 byte nhưng không phải là tiền giả thực sự đều không hợp lệ, dữ liệu này luôn có thể được tạo. Lưu ý rằng các giá trị không phải 32 byte không phải là các lần thoát hợp lệ, vì chúng khiến quá trình thực thi tập lệnh chấm dứt trên EQUALVERIFY thay vì tiếp tục.
  • u : Khi hài lòng, đẩy 1 vào ngăn xếp.

Các loại cơ bản và thuộc tính loại được xác định cẩn thận để đảm bảo tính chính xác của các mảnh được lắp ráp. Các thuộc tính này có thể được gán cho từng đoạn dựa trên suy luận từ tập lệnh và dữ liệu nhân chứng. Tương tự, đối với các đoạn nằm trong các biểu thức con, chẳng hạn như and_b(X,Y) , bạn có thể phân tích những loại XY phải đáp ứng cũng như những loại và thuộc tính dẫn xuất mà chính and_b(X,Y) phải có. May mắn thay, các tác giả của Miniscript đã thực hiện xong phần công việc này và đã ghi lại bảng tính chính xác trong đặc tả .

Đoạn cấp cao nhất phải có loại B , nếu không thì đoạn này không hợp lệ.

Chúng tôi tăng cường AST của mình với các loại và thuộc tính loại cơ bản:

 type basicType stringconst ( typeB basicType = "B" typeV basicType = "V" typeK basicType = "K" typeW basicType = "W")type properties struct { // Basic type properties z, o, n, d, u bool}func (p properties) String() string { s := strings.Builder{} if pz { s.WriteRune('z') } if po { s.WriteRune('o') } if pn { s.WriteRune('n') } if pd { s.WriteRune('d') } if pu { s.WriteRune('u') } return s.String()}// AST is the abstract syntax tree representing a Miniscript expression.type AST struct { basicType basicType props properties wrappers string identifier string // Parsed integer for when identifer is a expected to be a number, ie the first argument of // older/after/multi/thresh. Otherwise unused. num uint64 args []*AST}// typeRepr returns the basic type (B, V, K or W) followed by all type properties.func (a *AST) typeRepr() string { return fmt.Sprintf("%s%s", a.basicType, a.props)}

Sau đó, chúng tôi thêm một hàm khác để duyệt toàn bộ cây. Hàm này sẽ kiểm tra các yêu cầu về kiểu của biểu thức con và thiết lập kiểu và thuộc tính kiểu theo bảng tính chính xác của đặc tả.

Vì chức năng này khá dài nên chúng tôi sẽ chỉ hiển thị một phiên bản rút gọn của nó xử lý một số phần, chỉ để minh họa cách thức hoạt động của nó. Các loại phân đoạn có liên quan đến cả thành phần và tham số. Nó trực tiếp mã hóa các quy tắc loại theo các bảng trong đặc tả. Ví dụ: để s:X hợp lệ, X phải thuộc loại Bo ; trong khi toàn bộ phân đoạn sẽ có loại W và nhận các thuộc tính du của X

Bạn có thể xem và chạy phiên bản đầy đủ xử lý từng đoạn trong môi trường trực tuyến .

 // expectBasicType is a helper function to check that this node has a specific type.func (a *AST) expectBasicType(typ basicType) error { if a.basicType != typ { return fmt.Errorf("expression `%s` expected to have type %s, but is type %s", a.identifier, typ, a.basicType) } return nil}func typeCheck(node *AST) (*AST, error) { switch node.identifier { case f_0: node.basicType = typeB node.props.z = true node.props.u = true node.props.d = true // [...] case f_pk_k: node.basicType = typeK node.props.o = true node.props.n = true node.props.d = true node.props.u = true // [...] case f_or_d: _x, _z := node.args[0], node.args[1] if err := _x.expectBasicType(typeB); err != nil { return nil, err } if !_x.props.d || !_x.props.u { return nil, fmt.Errorf( "wrong properties on `%s`, the first argument of `%s`", _x.identifier, node.identifier) } if err := _z.expectBasicType(typeB); err != nil { return nil, err } node.basicType = typeB node.props.z = _x.props.z && _z.props.z node.props.o = _x.props.o && _z.props.z node.props.d = _z.props.d node.props.u = _z.props.u // [...] case f_wrap_s: _x := node.args[0] if err := _x.expectBasicType(typeB); err != nil { return nil, err } if !_x.props.o { return nil, fmt.Errorf( "wrong properties on `%s`, the first argument of `%s`", _x.identifier, node.identifier) } node.props.d = _x.props.d node.props.u = _x.props.u // [...] } return node, nil}

Bây giờ chúng ta đã suy ra tất cả các kiểu và thuộc tính kiểu, chúng ta cũng cần thêm kiểm tra xem biểu thức cấp cao nhất phải có kiểu B vào kiểm tra cuối cùng:

 func Parse(miniscript string) (*AST, error) { node, err := createAST(miniscript) if err != nil { return nil, err } for _, transform := range []func(*AST) (*AST, error){ argCheck, expandWrappers, desugar, typeCheck, // More stages to come } { node, err = node.apply(transform) if err != nil { return nil, err } } // Top-level expression must be of type "B". if err := node.expectBasicType(typeB); err != nil { return nil, err } return node, nil}

Hãy kiểm tra các đoạn mã Miniscript hợp lệ và không hợp lệ:

 func main() { expr := "or_b(pk(key1),s:pk(key2))" node, err := Parse(expr) if err == nil { fmt.Println("miniscript valid:", expr) fmt.Println(node.DrawTree()) } for _, expr := range []string{"pk_k(key)", "or_b(pk(key1),pk(key2))"} { _, err = Parse(expr) fmt.Println("miniscript invalid:", expr, "-", err) }}

thành công! Đầu ra là thế này:

 miniscript valid: or_b(pk(key1),s:pk(key2))or_b [Bdu]├──c [Bondu]| └──pk_k [Kondu]| └──key1└──s [Wdu] └──c [Bondu] └──pk_k [Kondu] └──key2miniscript invalid: pk_k(key) - expression `pk_k` expected to have type B, but is type Kminiscript invalid: or_b(pk(key1),pk(key2)) - expression `c` expected to have type W, but is type B

(Chúng tôi đã sửa đổi hàm draw() để hiển thị loại của nó bên cạnh mỗi đoạn.)

Nhấp vào đây để chạy mã trong môi trường Go trực tiếp

Bước 6: Tạo Bitcoin Script

Chúng tôi chưa triển khai kiểm tra để từ chối tất cả các đoạn Miniscript không hợp lệ, nhưng hiện tại, đã đến lúc thử nghiệm sử dụng nó để tạo Bitcoin Script.

Các bảng chuyển đổi trong đặc tả xác định cách các đoạn Miniscript ánh xạ tới Bitcoin Script. Ví dụ: and_b(X, Y) ánh xạ tới [X] [Y] BOOLAND , v.v.

Chúng ta sẽ bắt đầu bằng cách tạo một hàm tạo biểu diễn chuỗi của tập lệnh có thể được đọc trực tiếp, chẳng hạn như bảng chuyển đổi đó. Điều này giúp chúng tôi tạo nguyên mẫu và loại bỏ lỗi dễ dàng hơn vì bạn có thể dễ dàng kiểm tra đầu ra. Tập lệnh thực sự là một chuỗi byte, mà chúng ta sẽ triển khai sau.

Chúng tôi thêm chức năng này để ánh xạ từng đoạn thành tập lệnh theo bảng chuyển đổi:

 func scriptStr(node *AST) string { switch node.identifier { case f_0, f_1: return node.identifier case f_pk_k: return fmt.Sprintf("<%s>", node.args[0].identifier) case f_pk_h: return fmt.Sprintf("DUP HASH160 <HASH160(%s)> EQUALVERIFY", node.args[0].identifier) case f_older: return fmt.Sprintf("<%s> CHECKSEQUENCEVERIFY", node.args[0].identifier) case f_after: return fmt.Sprintf("<%s> CHECKLOCKTIMEVERIFY", node.args[0].identifier) case f_sha256, f_hash256, f_ripemd160, f_hash160: return fmt.Sprintf( "SIZE <32> EQUALVERIFY %s <%s> EQUAL", strings.ToUpper(node.identifier), node.args[0].identifier) case f_andor: return fmt.Sprintf("%s NOTIF %s ELSE %s ENDIF", scriptStr(node.args[0]), scriptStr(node.args[2]), scriptStr(node.args[1]), ) case f_and_v: return fmt.Sprintf("%s %s", scriptStr(node.args[0]), scriptStr(node.args[1])) case f_and_b: return fmt.Sprintf("%s %s BOOLAND", scriptStr(node.args[0]), scriptStr(node.args[1]), ) case f_or_b: return fmt.Sprintf("%s %s BOOLOR", scriptStr(node.args[0]), scriptStr(node.args[1]), ) case f_or_c: return fmt.Sprintf("%s NOTIF %s ENDIF", scriptStr(node.args[0]), scriptStr(node.args[1]), ) case f_or_d: return fmt.Sprintf("%s IFDUP NOTIF %s ENDIF", scriptStr(node.args[0]), scriptStr(node.args[1]), ) case f_or_i: return fmt.Sprintf("IF %s ELSE %s ENDIF", scriptStr(node.args[0]), scriptStr(node.args[1]), ) case f_thresh: s := []string{} for i := 1; i < len(node.args); i++ { s = append(s, scriptStr(node.args[i])) if i > 1 { s = append(s, "ADD") } } s = append(s, node.args[0].identifier) s = append(s, "EQUAL") return strings.Join(s, " ") case f_multi: s := []string{node.args[0].identifier} for _, arg := range node.args[1:] { s = append(s, fmt.Sprintf("<%s>", arg.identifier)) } s = append(s, fmt.Sprint(len(node.args)-1)) s = append(s, "CHECKMULTISIG") return strings.Join(s, " ") case f_wrap_a: return fmt.Sprintf("TOALTSTACK %s FROMALTSTACK", scriptStr(node.args[0])) case f_wrap_s: return fmt.Sprintf("SWAP %s", scriptStr(node.args[0])) case f_wrap_c: return fmt.Sprintf("%s CHECKSIG", scriptStr(node.args[0])) case f_wrap_d: return fmt.Sprintf("DUP IF %s ENDIF", scriptStr(node.args[0])) case f_wrap_v: return fmt.Sprintf("%s VERIFY", scriptStr(node.args[0])) case f_wrap_j: return fmt.Sprintf("SIZE 0NOTEQUAL IF %s ENDIF", scriptStr(node.args[0])) case f_wrap_n: return fmt.Sprintf("%s 0NOTEQUAL", scriptStr(node.args[0])) default: return "<unknown>" }}

thử:

Chạy trong môi trường trực tuyến

 func main() { node, err := Parse("or_d(pk(pubkey1),and_v(v:pk(pubkey2),older(52560)))") if err != nil { panic(err) } fmt.Println(scriptStr(node))}

đầu ra:

 <pubkey1> CHECKSIG IFDUP NOTIF <pubkey2> CHECKSIG VERIFY <52560> CHECKSEQUENCEVERIFY ENDIF

Điều này là chính xác, nhưng vẫn còn một điều cần tối ưu hóa. Trình bao bọc v:X ánh xạ tới [X] VERIFY . Các toán tử EQUALVERIFY , CHECKSIGVERIFYCHECKMULTISIGVERIFY lần lượt là viết tắt của EQUAL VERIFY , CHECKSIG VERIFYCHECKMULTISIG VERIFY , vì vậy trong tập lệnh trên, CHECKSIG VERIFY phải được viết tắt thành CHECKSIGVERITY để lưu một byte trong tập lệnh.

Nếu trong v:X , toán tử cuối cùng của [X]EQUAL / CHECKSIG / CHECKMULTISIG , nó có thể được thay thế bằng phiên bản VERITY .

X có thể là bất kỳ biểu thức nào, nên chúng ta cần một phép duyệt cây khác để xác định xem toán tử cuối cùng của mỗi đoạn có phải là một trong ba toán tử trên hay không.

Chúng tôi thêm thuộc tính này vào cấu trúc thuộc tính:

 type properties struct { // Basic type properties z, o, n, d, u bool // Check if the rightmost script byte produced by this node is OP_EQUAL, OP_CHECKSIG or // OP_CHECKMULTISIG. // // If so, it can be be converted into the VERIFY version if an ancestor is the verify wrapper // `v`, ie OP_EQUALVERIFY, OP_CHECKSIGVERIFY and OP_CHECKMULTISIGVERIFY instead of using two // opcodes, eg `OP_EQUAL OP_VERIFY`. canCollapseVerify bool}

Ngoài ra, hàm này đặt trường này cho từng đoạn mà chúng ta cần thêm vào danh sách các hàm chuyển đổi:

 func canCollapseVerify(node *AST) (*AST, error) { switch node.identifier { case f_sha256, f_ripemd160, f_hash256, f_hash160, f_thresh, f_multi, f_wrap_c: node.props.canCollapseVerify = true case f_and_v: node.props.canCollapseVerify = node.args[1].props.canCollapseVerify case f_wrap_s: node.props.canCollapseVerify = node.args[0].props.canCollapseVerify } return node, nil}

các đoạn and_vs- trình bao bọc là các đoạn có thể tổng hợp duy nhất có thể kết thúc bằng một biểu thức con: and_v(X,Y) => [X] [Y]s:X => SWAP [X] , vì vậy, chúng nhận các thuộc tính trực tiếp từ các nút con. Tất cả các đoạn băm và thresh / multi / c trong tập lệnh sẽ kết thúc bằng EQUAL / CHECKSIG / CHECKMULTISIG , ví dụ c:X => [X] CHECKSIG . Đây là những ứng cử viên cho các phiên bản VERIFY sẽ được đưa vào các toán tử này.

Sau đó, chúng tôi có thể sửa đổi hàm scriptStr của mình để sử dụng phiên bản VERIFY của toán tử khi được phép. Để cho ngắn gọn, chúng tôi chỉ trình bày hai trường hợp dưới đây. Bạn có thể xem và chạy phiên bản đầy đủ tại trang web này .

 // collapseVerify is true if the `v` wrapper (VERIFY wrapper) is an ancestor of the node. If so, the// two opcodes `OP_CHECKSIG VERIFY` can be collapsed into one opcode `OP_CHECKSIGVERIFY` (same for// OP_EQUAL and OP_CHECKMULTISIG).func scriptStr(node *AST, collapseVerify bool) string { switch node.identifier { // [...] case f_wrap_c: opVerify := "CHECKSIG" if node.props.canCollapseVerify && collapseVerify { opVerify = "CHECKSIGVERIFY" } return fmt.Sprintf("%s %s", scriptStr(node.args[0], collapseVerify), opVerify, ) // [...] case f_wrap_v: s := scriptStr(node.args[0], true) if !node.args[0].props.canCollapseVerify { s += " VERIFY" } return s}

Chạy chương trình với chức năng đã sửa đổi, bạn nhận được kết quả này:

 <pubkey1> CHECKSIG IFDUP NOTIF <pubkey2> CHECKSIGVERIFY <52560> CHECKSEQUENCEVERIFY ENDIF

Đã giảm thành công CHECKSIG VERITY thành CHECKSIGVERIFY .

Bước 7: Tạo địa chỉ thanh toán

Nhấp vào đây để chạy mã trong môi trường Go trực tiếp

Trong lần cài đặt trước, chúng tôi đã tạo một biểu diễn có thể đọc được cho Bitcoin Script. Để tạo địa chỉ P2WSH, chúng ta cần phát triển tập lệnh thực tế dưới dạng chuỗi byte, sau đó được mã hóa thành địa chỉ.

Để làm điều này, trước tiên chúng ta thay thế khóa công khai và biến băm bằng khóa công khai và hàm băm thực. Chúng tôi thêm một trường mới vào AST: value .

 type AST struct { // [...] identifier string // For key arguments, this will be the 33 bytes compressed pubkey. // For hash arguments, this will be the 32 bytes (sha256, hash256) or 20 bytes (ripemd160, hash160) hash. value []byte args []*AST}

Bây giờ, chúng ta có thể thêm một hàm mới ApplyVars , hàm này sẽ thay thế tất cả các biến trong biểu thức Miniscript bằng các giá trị thực. Người gọi có thể cung cấp chức năng gọi lại để cung cấp các giá trị này.

Miniscript cũng chỉ định rằng khóa chung không bị trùng lặp (giúp đơn giản hóa việc phân tích tập lệnh), vì vậy chúng tôi kiểm tra các bản sao.

 // ApplyVars replaces key and hash values in the miniscript.//// The callback should return `nil, nil` if the variable is unknown. In this case, the identifier// itself will be parsed as the value (hex-encoded pubkey, hex-encoded hash value).func (a *AST) ApplyVars(lookupVar func(identifier string) ([]byte, error)) error { // Set of all pubkeys to check for duplicates allPubKeys := map[string]struct{}{} _, err := a.apply(func(node *AST) (*AST, error) { switch node.identifier { case f_pk_k, f_pk_h, f_multi: var keyArgs []*AST if node.identifier == f_multi { keyArgs = node.args[1:] } else { keyArgs = node.args[:1] } for _, arg := range keyArgs { key, err := lookupVar(arg.identifier) if err != nil { return nil, err } if key == nil { // If the key was not a variable, assume it's the key value directly encoded as // hex. key, err = hex.DecodeString(arg.identifier) if err != nil { return nil, err } } if len(key) != pubKeyLen { return nil, fmt.Errorf("pubkey argument of %s expected to be of size %d, but got %d", node.identifier, pubKeyLen, len(key)) } pubKeyHex := hex.EncodeToString(key) if _, ok := allPubKeys[pubKeyHex]; ok { return nil, fmt.Errorf( "duplicate key found at %s (key=%s, arg identifier=%s)", node.identifier, pubKeyHex, arg.identifier) } allPubKeys[pubKeyHex] = struct{}{} arg.value = key } case f_sha256, f_hash256, f_ripemd160, f_hash160: arg := node.args[0] hashLen := map[string]int{ f_sha256: 32, f_hash256: 32, f_ripemd160: 20, f_hash160: 20, }[node.identifier] hashValue, err := lookupVar(arg.identifier) if err != nil { return nil, err } if hashValue == nil { // If the hash value was not a variable, assume it's the hash value directly encoded // as hex. hashValue, err = hex.DecodeString(node.args[0].identifier) if err != nil { return nil, err } } if len(hashValue) != hashLen { return nil, fmt.Errorf("%s len must be %d, got %d", node.identifier, hashLen, len(hashValue)) } arg.value = hashValue } return node, nil }) return err}

Xem nó trong hành động:

 func main() { node, err := Parse("or_d(pk(pubkey1),and_v(v:pk(pubkey2),older(52560)))") if err != nil { panic(err) } unhex := func(s string) []byte { b, _ := hex.DecodeString(s) return b } // Two arbitrary pubkeys. _, pubKey1 := btcec.PrivKeyFromBytes( unhex("2c3931f593f26037a8b8bf837363831b18bbfb91a712dd9d862db5b9b06dc5df")) _, pubKey2 := btcec.PrivKeyFromBytes( unhex("f902f94da618721e516d0a2a2666e2ec37079aaa184ee5a2c00c835c5121b3eb")) err = node.ApplyVars(func(identifier string) ([]byte, error) { switch identifier { case "pubkey1": return pubKey1.SerializeCompressed(), nil case "pubkey2": return pubKey2.SerializeCompressed(), nil } return nil, nil }) if err != nil { panic(err) } fmt.Println(node.DrawTree())}

Đầu ra, khóa công khai đã được thay thế thành công:

 or_d [B]├──c [Bonduv]| └──pk_k [Kondu]| └──pubkey1 [03469d685c3445e83ee6e3cfb30382795c249c91955523c25f484d69379c7a7d6f]└──and_v [Bon] ├──v [Von] | └──c [Bonduv] | └──pk_k [Kondu] | └──pubkey2 [03ba991cc359438fdd8cf43e3cf7894f90cf4d0e040314a6bba82963fa77b7a434] └──older [Bz] └──52560

(Chúng tôi sửa đổi hàm drawTree() để hiển thị giá trị thực bên cạnh mỗi biến.)

Với sự trợ giúp của thư viện btcd tuyệt vời này, giờ đây chúng ta có thể xây dựng tập lệnh thực tế. Nó trông rất giống scriptStr() ở trên, nhưng mã hóa nó thành một chuỗi byte, tập trung vào vấn đề thực tế là đẩy các số nguyên và dữ liệu lên ngăn xếp. Chúng tôi sử dụng một phiên bản rút gọn ở đây. Phiên bản đầy đủ có sẵn trên trang web này .

 // Script creates the witness script from a parsed miniscript.func (a *AST) Script() ([]byte, error) { b := txscript.NewScriptBuilder() if err := buildScript(a, b, false); err != nil { return nil, err } return b.Script()}// collapseVerify is true if the `v` wrapper (VERIFY wrapper) is an ancestor of the node. If so, the// two opcodes `OP_CHECKSIG VERIFY` can be collapsed into one opcode `OP_CHECKSIGVERIFY` (same for// OP_EQUAL and OP_CHECKMULTISIGVERIFY).func buildScript(node *AST, b *txscript.ScriptBuilder, collapseVerify bool) error { switch node.identifier { case f_0: b.AddOp(txscript.OP_FALSE) case f_1: b.AddOp(txscript.OP_TRUE) case f_pk_h: arg := node.args[0] key := arg.value if key == nil { return fmt.Errorf("empty key for %s (%s)", node.identifier, arg.identifier) } b.AddOp(txscript.OP_DUP) b.AddOp(txscript.OP_HASH160) b.AddData(btcutil.Hash160(key)) b.AddOp(txscript.OP_EQUALVERIFY) case f_older: b.AddInt64(int64(node.args[0].num)) b.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) case f_after: b.AddInt64(int64(node.args[0].num)) b.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) case f_and_b: if err := buildScript(node.args[0], b, collapseVerify); err != nil { return err } if err := buildScript(node.args[1], b, collapseVerify); err != nil { return err } b.AddOp(txscript.OP_BOOLAND) case f_wrap_c: if err := buildScript(node.args[0], b, collapseVerify); err != nil { return err } if node.props.canCollapseVerify && collapseVerify { b.AddOp(txscript.OP_CHECKSIGVERIFY) } else { b.AddOp(txscript.OP_CHECKSIG) } case f_wrap_v: if err := buildScript(node.args[0], b, true); err != nil { return err } if !node.args[0].props.canCollapseVerify { b.AddOp(txscript.OP_VERIFY) } // More cases [...] default: return fmt.Errorf("unknown identifier: %s", node.identifier) } return nil}

Hãy chạy nó:

 func main() { // [...] script, err := node.Script() if err != nil { panic(err) } fmt.Println("Script", hex.EncodeToString(script))}

đầu ra:

 Script 2103469d685c3445e83ee6e3cfb30382795c249c91955523c25f484d69379c7a7d6fac73642103ba991cc359438fdd8cf43e3cf7894f90cf4d0e040314a6bba82963fa77b7a434ad0350cd00b268

Theo BIP141BIP173 , địa chỉ P2WSH phải là mã hóa bech32 của 0 <sha256(script)> , trong đó 0 có nghĩa là Segregated Witness phiên bản 0. Chúng tôi sẽ sử dụng thư viện btcd này để giúp chúng tôi tạo địa chỉ:

 addr, err := btcutil.NewAddressWitnessScriptHash(chainhash.HashB(script), &chaincfg.TestNet3Params)if err != nil { panic(err)}fmt.Println("Address:", addr.String())

Địa chỉ nhận testnet của chúng tôi đã sẵn sàng:

 Address: tb1q4q3cw0mausmamm7n7fn2phh0fpca4n0vmkc7rdh6hxnkz9rd8l0qcpefrj

Số tiền mà địa chỉ này nhận được sẽ bị khóa bằng cách sử dụng điều kiện chi tiêu or_d(pk(pubkey1),and_v(v:pk(pubkey2),older(52560))) tức là, khóa công khai 1 có thể được chi tiêu bất cứ lúc nào; khóa công khai 2 có thể được sử dụng khi tiền vào Địa chỉ này được sử dụng sau 52560 khối (khoảng một năm).

Nhấp vào đây để chạy mã trong môi trường Go trực tiếp

Tóm lại là

Để nhận xét, chúng tôi đã tạo một thư viện mã Miniscript để phân tích cú pháp các biểu thức Miniscript và thực hiện kiểm tra loại cũng như tạo địa chỉ thanh toán.

Có nhiều tình huống hơn để xem xét. Trong bài viết tiếp theo, chúng ta sẽ tìm hiểu cách tạo dữ liệu nhân chứng dựa trên các biểu thức Miniscript để có thể chi tiêu tiền; cách đảm bảo rằng Miniscript tuân thủ các tiêu chuẩn và sự đồng thuận của Bitcoin, chẳng hạn như kích thước tập lệnh và hạn chế của nhà điều hành.

Nếu bạn muốn loạt bài này tiếp tục, vui lòng cho tôi biết trên Twitter: @benma .

Khu vực:
Nguồn
Tuyên bố từ chối trách nhiệm: Nội dung trên chỉ là ý kiến của tác giả, không đại diện cho bất kỳ lập trường nào của Followin, không nhằm mục đích và sẽ không được hiểu hay hiểu là lời khuyên đầu tư từ Followin.
Thích
Thêm vào Yêu thích
Bình luận