Skip to content

Commit a6c54dc

Browse files
authored
Merge pull request #3 from BoostyLabs/vs/parse_vout_data_fix
bitcoin/ord/runes: runestone (de)serializing fixed
2 parents 516d78b + a63ab69 commit a6c54dc

2 files changed

Lines changed: 108 additions & 16 deletions

File tree

bitcoin/ord/runes/runestone.go

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package runes
55

66
import (
77
"bytes"
8+
"encoding/binary"
89
"errors"
910
"fmt"
1011
"math/big"
@@ -217,13 +218,39 @@ func (runestone *Runestone) IntoScript() ([]byte, error) {
217218
return nil, err
218219
}
219220

221+
// OP_RETURN + OP_13 + OP_PUSH_<num> + payload.
222+
return append([]byte{txscript.OP_RETURN, txscript.OP_13}, serializeDataPushes(payload)...), nil
223+
}
224+
225+
// serializeDataPushes serialises runestone payload into data pushes.
226+
func serializeDataPushes(payload []byte) (dataPushes []byte) {
220227
payloadSize := len(payload)
221-
if payloadSize < txscript.OP_DATA_1 || payloadSize > txscript.OP_DATA_75 {
222-
return nil, errors.New("payload is out of PUSH_DATA bounds")
228+
if payloadSize <= txscript.OP_DATA_75 {
229+
return append([]byte{byte(payloadSize)}, payload...)
223230
}
224231

225-
// OP_RETURN + OP_13 + OP_PUSH_<num> + payload.
226-
return append([]byte{txscript.OP_RETURN, txscript.OP_13, byte(payloadSize)}, payload...), nil
232+
const defaultPushDataSize = 520
233+
var (
234+
idx = 0
235+
dataPushesNumber = payloadSize / defaultPushDataSize
236+
pushSize int
237+
)
238+
if payloadSize%defaultPushDataSize > 0 {
239+
dataPushesNumber++
240+
}
241+
242+
dataPushes = make([]byte, 0, payloadSize+dataPushesNumber*3) // INFO: Payload size + (OP_PUSHDATA2 + 2 bytes size) * data pushes number.
243+
244+
// INFO: Split into OP_PUSHDATA2 data pushes.
245+
for idx < payloadSize {
246+
dataPushes = append(dataPushes, txscript.OP_PUSHDATA2)
247+
pushSize = min(defaultPushDataSize, payloadSize-idx)
248+
dataPushes = binary.LittleEndian.AppendUint16(dataPushes, uint16(pushSize))
249+
dataPushes = append(dataPushes, payload[idx:idx+pushSize]...)
250+
idx += pushSize
251+
}
252+
253+
return dataPushes
227254
}
228255

229256
// Serialize returns Runestone as bytes array.
@@ -453,30 +480,55 @@ func PreparePayload(rawPayload []byte) ([]byte, error) {
453480
payload := make([]byte, 0, len(rawPayload)-3)
454481
buffer := bytes.NewReader(rawPayload[2:])
455482
for buffer.Len() > 0 {
456-
op, err := buffer.ReadByte()
483+
pushDataVal, err := buffer.ReadByte()
457484
if err != nil {
458485
return nil, err
459486
}
460487

461-
if op < txscript.OP_DATA_1 || op > txscript.OP_DATA_75 {
462-
return nil, errors.New("missing OP_DATA_<num>")
488+
data, err := parseDataPush(buffer, int(pushDataVal))
489+
if err != nil {
490+
return nil, err
463491
}
464492

465-
data := make([]byte, op)
466-
_, err = buffer.Read(data)
493+
payload = append(payload, data...)
494+
}
495+
496+
return payload, nil
497+
}
498+
499+
// parseDataPush parses data payload from buffer depending on push data byte value.
500+
func parseDataPush(buffer *bytes.Reader, pushDataVal int) (data []byte, _ error) {
501+
var size int
502+
switch {
503+
case pushDataVal >= txscript.OP_DATA_1 && pushDataVal <= txscript.OP_DATA_75:
504+
size = pushDataVal
505+
case pushDataVal >= txscript.OP_PUSHDATA1 && pushDataVal <= txscript.OP_PUSHDATA4:
506+
bytesLen := 0x0001 << (pushDataVal - txscript.OP_PUSHDATA1) // INFO: 2 ^ {76 or 77 or 78} - 76 == 1 or 2 or 4.
507+
sizeBuf := make([]byte, bytesLen)
508+
_, err := buffer.Read(sizeBuf)
467509
if err != nil {
468510
return nil, err
469511
}
470512

471-
payload = append(payload, data...)
513+
// INFO: LittleEndian ordering.
514+
for ; bytesLen > 0; bytesLen-- {
515+
size <<= 8 // INFO: Prepare place for the next byte.
516+
size |= int(sizeBuf[bytesLen-1]) // INFO: Push the next byte.
517+
}
518+
default:
519+
return nil, fmt.Errorf("invalid OP_PUSHDATA (0x%x)", pushDataVal)
472520
}
473521

474-
// TODO: figure out where it must be.
475-
// if len(payload) > 18 {
476-
// return nil, ErrOverflow
477-
// }.
522+
data = make([]byte, size)
523+
read, err := buffer.Read(data)
524+
if err != nil {
525+
return nil, err
526+
}
527+
if read != size {
528+
return nil, fmt.Errorf("read %d of %d", read, size)
529+
}
478530

479-
return payload, nil
531+
return data, nil
480532
}
481533

482534
// IsPossibleRunestone returns true if the script starts with rune protocol bytes sequence.
@@ -488,7 +540,7 @@ func IsPossibleRunestone(script []byte) bool {
488540
return false
489541
case script[1] != txscript.OP_13:
490542
return false
491-
case script[2] < txscript.OP_DATA_1 || script[2] > txscript.OP_DATA_75:
543+
case script[2] < txscript.OP_DATA_1 || script[2] > txscript.OP_PUSHDATA4:
492544
return false
493545
}
494546

0 commit comments

Comments
 (0)