Skip to content

Commit ad44279

Browse files
committed
check for and display simplicity transaction inputs
1 parent cf1595d commit ad44279

File tree

2 files changed

+103
-3
lines changed

2 files changed

+103
-3
lines changed

client/src/views/tx-vin.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Snabbdom from 'snabbdom-pragma'
2-
import { linkToParentOut, formatOutAmount, formatAssetAmount, formatHex, linkToAddr, formatNumber } from './util'
2+
import { linkToParentOut, formatOutAmount, formatAssetAmount, formatHex, linkToAddr, formatNumber, isSimplicitySpend, getSimplicityWitness, hexToBase64 } from './util'
33

44
const layout = (vin, desc, body, { t, ...S }) =>
55
<div class={{ vin: true, active: isActive(vin, S), unblinded: isUnblinded(vin) }}>
@@ -103,11 +103,38 @@ const standard = (vin, { isOpen, t, ...S }, assetMeta=getAssetMeta(vin, S)) => l
103103
</div>
104104
] }
105105

106-
{ vin.witness && <div className="vin-body-row">
106+
{ vin.witness && !isSimplicitySpend(vin) && <div className="vin-body-row">
107107
<div>{t`Witness`}</div>
108108
<div className="mono">{vin.witness.map(wit => [ ' ', wit.length ? wit : <em>&lt;empty&gt;</em> ])}</div>
109109
</div> }
110110

111+
{ isSimplicitySpend(vin) && ((sw = getSimplicityWitness(vin)) => [
112+
<div className="vin-body-row">
113+
<div>{t`Simplicity witness data`}</div>
114+
<div className="mono">{sw.witnessData}</div>
115+
</div>
116+
117+
, <div className="vin-body-row">
118+
<div>{t`Simplicity program`}</div>
119+
<div className="mono">{hexToBase64(sw.program)}</div>
120+
</div>
121+
122+
, <div className="vin-body-row">
123+
<div>{t`Simplicity CMR`}</div>
124+
<div className="mono">{sw.cmr}</div>
125+
</div>
126+
127+
, <div className="vin-body-row">
128+
<div>{t`Taproot control block`}</div>
129+
<div className="mono">{sw.controlBlock}</div>
130+
</div>
131+
132+
, sw.annex && <div className="vin-body-row">
133+
<div>{t`Annex`}</div>
134+
<div className="mono">{sw.annex}</div>
135+
</div>
136+
])() }
137+
111138
{ vin.inner_redeemscript_asm && <div className="vin-body-row">
112139
<div>{t`P2SH redeem script`}</div>
113140
<div className="mono">{vin.inner_redeemscript_asm}</div>

client/src/views/util.js

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,77 @@ export const getSupply = (asset, t) => {
121121
return totalSupply
122122
}
123123

124-
export const strTruncate = (str) => str.substr(0, 10) + '...' + str.substr(str.length-4, str.length);
124+
export const strTruncate = (str) => str.substr(0, 10) + '...' + str.substr(str.length-4, str.length);
125+
126+
127+
// Simplicity helpers (Elements only)
128+
129+
// Helper to check if witness has an annex (per BIP 341)
130+
const hasAnnex = (witness) => witness && witness.length >= 2 && witness[witness.length - 1].startsWith('50')
131+
132+
// Get the control block element from a witness stack (accounting for optional annex)
133+
const getControlBlock = witness => {
134+
if (!witness || witness.length < 3) return null
135+
const hasAnnexBlock = hasAnnex(witness)
136+
if (hasAnnexBlock) {
137+
return witness[witness.length - 2]
138+
}
139+
return witness[witness.length - 1]
140+
}
141+
142+
// Check if a vin is a P2TR Simplicity spend
143+
// A P2TR Simplicity spend has:
144+
// - 4 witness elements (or 5 with optional annex)
145+
// - Control block starts with 'be' or 'bf' (leaf version for Simplicity)
146+
export const isSimplicitySpend = vin => {
147+
if (!process.env.IS_ELEMENTS || !vin.witness) return false
148+
149+
const witnessLen = vin.witness.length
150+
const hasAnnexBlock = hasAnnex(vin.witness)
151+
152+
// Must be 4 elements without annex, or 5 elements with annex
153+
if (witnessLen !== 4 && !(witnessLen === 5 && hasAnnexBlock)) return false
154+
155+
const controlBlock = getControlBlock(vin.witness)
156+
return controlBlock && (controlBlock.startsWith('be') || controlBlock.startsWith('bf'))
157+
}
158+
159+
// Extract Simplicity witness components from a vin
160+
// Returns: { witnessData, program, cmr, controlBlock, annex }
161+
export const getSimplicityWitness = vin => {
162+
if (!isSimplicitySpend(vin)) return null
163+
164+
const witness = vin.witness
165+
const hasAnnexBlock = hasAnnex(witness)
166+
167+
if (hasAnnexBlock && witness.length === 5) {
168+
return {
169+
witnessData: witness[0],
170+
program: witness[1],
171+
cmr: witness[2],
172+
controlBlock: witness[3],
173+
annex: witness[4]
174+
}
175+
}
176+
177+
return {
178+
witnessData: witness[0],
179+
program: witness[1],
180+
cmr: witness[2],
181+
controlBlock: witness[3],
182+
annex: null
183+
}
184+
}
185+
186+
// Convert hex string to base64
187+
export const hexToBase64 = hex => {
188+
if (process.browser) {
189+
// Browser environment
190+
const bytes = hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16))
191+
const binary = String.fromCharCode(...bytes)
192+
return btoa(binary)
193+
} else {
194+
// Node environment (for pre-rendering)
195+
return Buffer.from(hex, 'hex').toString('base64')
196+
}
197+
}

0 commit comments

Comments
 (0)