-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathemulator_packet.go
More file actions
231 lines (202 loc) · 7.49 KB
/
Copy pathemulator_packet.go
File metadata and controls
231 lines (202 loc) · 7.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
package arkade
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"github.com/arkade-os/arkd/pkg/ark-lib/extension"
"github.com/arkade-os/arkd/pkg/ark-lib/txutils"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/wire"
)
const (
// PacketType is the extension type for the Emulator Packet.
PacketType = 0x01
// MaxEntryCount is the maximum number of entries allowed in one packet.
MaxEntryCount = 1_000
// MaxScriptLength is the maximum script size per entry.
MaxScriptLength = 10_000
// MaxWitnessLength is the maximum serialized witness size per entry.
MaxWitnessLength = 1_000_000
)
// EmulatorEntry represents a single entry in the Emulator Packet.
type EmulatorEntry struct {
Vin uint16 // Transaction input index (u16 LE)
Script []byte // Arkade Script bytecode
Witness wire.TxWitness // Script witness stack items
}
// EmulatorPacket is a set of EmulatorEntry items encoded as a TLV
// record inside an ARK extension OP_RETURN output.
type EmulatorPacket []EmulatorEntry
// NewPacket creates a validated EmulatorPacket from the given entries.
func NewPacket(entries ...EmulatorEntry) (EmulatorPacket, error) {
p := EmulatorPacket(entries)
if err := p.Validate(); err != nil {
return nil, err
}
return p, nil
}
// Validate checks that the packet is not empty and has no duplicate vin values.
func (p EmulatorPacket) Validate() error {
if len(p) == 0 {
return fmt.Errorf("empty packet")
}
if len(p) > MaxEntryCount {
return fmt.Errorf("max emulator entry count exceeded, max=%d got=%d", MaxEntryCount, len(p))
}
seen := make(map[uint16]bool, len(p))
for i, entry := range p {
if len(entry.Script) == 0 {
return fmt.Errorf("empty script at entry %d", i)
}
if seen[entry.Vin] {
return fmt.Errorf("duplicate vin %d at entry %d", entry.Vin, i)
}
seen[entry.Vin] = true
}
return nil
}
// Type returns the TLV type byte for the Emulator Packet,
// implementing the extension.Packet interface.
func (p EmulatorPacket) Type() uint8 {
return PacketType
}
// Serialize serializes the EmulatorPacket to bytes.
func (p EmulatorPacket) Serialize() ([]byte, error) {
var buf bytes.Buffer
// Write entry count as varint
if err := wire.WriteVarInt(&buf, 0, uint64(len(p))); err != nil {
return nil, fmt.Errorf("failed to write entry count: %w", err)
}
for i, entry := range p {
// Write vin as u16 LE
if err := binary.Write(&buf, binary.LittleEndian, entry.Vin); err != nil {
return nil, fmt.Errorf("failed to write vin for entry %d: %w", i, err)
}
// Write script_len + script
if err := wire.WriteVarInt(&buf, 0, uint64(len(entry.Script))); err != nil {
return nil, fmt.Errorf("failed to write script_len for entry %d: %w", i, err)
}
if _, err := buf.Write(entry.Script); err != nil {
return nil, fmt.Errorf("failed to write script for entry %d: %w", i, err)
}
// Write witness (serialized as wire format, length-prefixed)
var witBuf bytes.Buffer
if err := psbt.WriteTxWitness(&witBuf, entry.Witness); err != nil {
return nil, fmt.Errorf("failed to serialize witness for entry %d: %w", i, err)
}
if err := wire.WriteVarInt(&buf, 0, uint64(witBuf.Len())); err != nil {
return nil, fmt.Errorf("failed to write witness_len for entry %d: %w", i, err)
}
if _, err := buf.Write(witBuf.Bytes()); err != nil {
return nil, fmt.Errorf("failed to write witness for entry %d: %w", i, err)
}
}
return buf.Bytes(), nil
}
// serializeEmulatorPacketMasked is like Serialize, but omits the witness
// blob of every entry entirely (witness_len = 0, no witness bytes). It is the
// encoding used inside the non-standard arkade tapscript sighash, where
// witness data must be excluded from the digest so scripts can be pre-signed
// without committing to runtime witness arguments. The masked encoding is
// never broadcast — it exists only to feed sha_outputs in the digest pipeline.
func serializeEmulatorPacketMasked(p EmulatorPacket) ([]byte, error) {
var buf bytes.Buffer
if err := wire.WriteVarInt(&buf, 0, uint64(len(p))); err != nil {
return nil, fmt.Errorf("failed to write entry count: %w", err)
}
for i, entry := range p {
if err := binary.Write(&buf, binary.LittleEndian, entry.Vin); err != nil {
return nil, fmt.Errorf("failed to write vin for entry %d: %w", i, err)
}
if err := wire.WriteVarInt(&buf, 0, uint64(len(entry.Script))); err != nil {
return nil, fmt.Errorf("failed to write script_len for entry %d: %w", i, err)
}
if _, err := buf.Write(entry.Script); err != nil {
return nil, fmt.Errorf("failed to write script for entry %d: %w", i, err)
}
// Mask: witness_len = 0, no witness bytes.
if err := wire.WriteVarInt(&buf, 0, 0); err != nil {
return nil, fmt.Errorf("failed to write masked witness_len for entry %d: %w", i, err)
}
}
return buf.Bytes(), nil
}
// DeserializeEmulatorPacket deserializes an EmulatorPacket from bytes.
func DeserializeEmulatorPacket(data []byte) (EmulatorPacket, error) {
r := bytes.NewReader(data)
entryCount, err := wire.ReadVarInt(r, 0)
if err != nil {
return nil, fmt.Errorf("failed to read entry count: %w", err)
}
if entryCount > MaxEntryCount {
return nil, fmt.Errorf("max emulator entry count exceeded, max=%d got=%d", MaxEntryCount, entryCount)
}
entries := make([]EmulatorEntry, 0, entryCount)
for i := range entryCount {
var entry EmulatorEntry
// Read vin (u16 LE)
if err := binary.Read(r, binary.LittleEndian, &entry.Vin); err != nil {
return nil, fmt.Errorf("failed to read vin for entry %d: %w", i, err)
}
// Read script
scriptLen, err := wire.ReadVarInt(r, 0)
if err != nil {
return nil, fmt.Errorf("failed to read script_len for entry %d: %w", i, err)
}
if scriptLen > MaxScriptLength {
return nil, fmt.Errorf("max emulator script length exceeded, max=%d got=%d", MaxScriptLength, scriptLen)
}
entry.Script = make([]byte, scriptLen)
if _, err := io.ReadFull(r, entry.Script); err != nil {
return nil, fmt.Errorf("failed to read script for entry %d: %w", i, err)
}
// Read witness (raw bytes, then decode to TxWitness)
witnessLen, err := wire.ReadVarInt(r, 0)
if err != nil {
return nil, fmt.Errorf("failed to read witness_len for entry %d: %w", i, err)
}
if witnessLen > MaxWitnessLength {
return nil, fmt.Errorf("max emulator witness length exceeded, max=%d got=%d", MaxWitnessLength, witnessLen)
}
witnessBytes := make([]byte, witnessLen)
if _, err := io.ReadFull(r, witnessBytes); err != nil {
return nil, fmt.Errorf("failed to read witness for entry %d: %w", i, err)
}
if len(witnessBytes) > 0 {
entry.Witness, err = txutils.ReadTxWitness(witnessBytes)
if err != nil {
return nil, fmt.Errorf("failed to decode witness for entry %d: %w", i, err)
}
}
entries = append(entries, entry)
}
if r.Len() != 0 {
return nil, fmt.Errorf("unexpected %d trailing bytes", r.Len())
}
return NewPacket(entries...)
}
// FindEmulatorPacket scans a transaction's outputs for an OP_RETURN
// containing an ARK TLV stream with an Emulator Packet (Type 0x01).
// Returns the parsed packet, or nil if no packet is found.
func FindEmulatorPacket(tx *wire.MsgTx) (EmulatorPacket, error) {
ext, err := extension.NewExtensionFromTx(tx)
if err != nil {
if errors.Is(err, extension.ErrExtensionNotFound) {
return nil, nil
}
return nil, fmt.Errorf("failed to parse extension: %w", err)
}
for _, pkt := range ext {
if pkt.Type() != PacketType {
continue
}
unknownPacket, ok := pkt.(extension.UnknownPacket)
if !ok {
continue
}
return DeserializeEmulatorPacket(unknownPacket.Data)
}
return nil, nil
}