Park
Privacy in Ark
Burak had shared the idea of Ark in a medium post and I will quote a few sentences from it:
I’m excited to publicly introduce Ark: a second-layer protocol for making cheap, anonymous, off-chain Bitcoin payments.
Ark allows recipients to receive payments without acquiring inbound liquidity while preserving their receiver privacy. The protocol is as private as WabiSabi, as convenient as on-chain, and as cheap as Lightning.
Now back to Ark, I named it Ark because it resonated with Noah’s Ark to save plebs from chainalaysis companies and custodians. Self-custodial Lightning doesn’t work for obvious reasons, and chainalaysis companies significantly threaten user privacy.
The idea has evolved a lot since then and I was curious if the Ark implementations offer any level of privacy. So, I reviewed and tested arkade. I have shared my findings in this post.
Wallet VTXOs
Wallets request their VTXOs and transaction history from ASPs regularly. So, ASP can link all VTXOs that belong to a wallet.
async getVtxos(filter?: GetVtxosFilter): Promise<ExtendedVirtualCoin[]> {
const address = await this.getAddress();
const scriptMap = await this.getScriptMap();
const f = filter ?? { withRecoverable: true, withUnrolled: false };
const allExtended: ExtendedVirtualCoin[] = [];
// Query each script separately so we can extend VTXOs with the correct tapscript
for (const [scriptHex, vtxoScript] of scriptMap) {
const response = await this.indexerProvider.getVtxos({
scripts: [scriptHex],
});
let vtxos: VirtualCoin[] = response.vtxos.filter(isSpendable);
if (!f.withRecoverable) {
vtxos = vtxos.filter(
(vtxo) => !isRecoverable(vtxo) && !isExpired(vtxo)
);
}
if (f.withUnrolled) {
const spentVtxos = response.vtxos.filter(
(vtxo) => !isSpendable(vtxo)
);
vtxos.push(...spentVtxos.filter((vtxo) => vtxo.isUnrolled));
}
for (const vtxo of vtxos) {
allExtended.push({
...vtxo,
forfeitTapLeafScript: vtxoScript.forfeit(),
intentTapLeafScript: vtxoScript.forfeit(),
tapTree: vtxoScript.encode(),
});
}
}
// Update cache with fresh data
await this.walletRepository.saveVtxos(address, allExtended);
return allExtended;
}
async getTransactionHistory(): Promise<ArkTransaction[]> {
const scripts = await this.getWalletScripts();
const response = await this.indexerProvider.getVtxos({ scripts });
const { boardingTxs, commitmentsToIgnore } =
await this.getBoardingTxs();
const getTxCreatedAt = (txid: string) =>
this.indexerProvider
.getVtxos({ outpoints: [{ txid, vout: 0 }] })
.then((res) => res.vtxos[0]?.createdAt.getTime() || 0);
return buildTransactionHistory(
response.vtxos,
boardingTxs,
commitmentsToIgnore,
getTxCreatedAt
);
}level=debug msg="gRPC method: /ark.v1.IndexerService/GetVtxos"
level=info msg="[ASP LOG]: GetVtxos request for pubkeys: [0238a92052fbf50d3d47334d9bfaaf447757543a8b3f3969d4702d3e0c6a15a8]"Arkade explorer allows anyone to lookup transactions using ark addresses: https://arkade.space/
Board VTXO, Spend VTXO, Refresh VTXO
I added logging in arkd while testing to gather logs which leak privacy information to ASP while boarding, spending, refreshing and off-boarding.
[ASP DEBUG]: Registering intent f37f2c7a... with 0 vtxo inputs, 1 boarding inputs, and 1 receivers
[ASP LOG]: Spending Boarding UTXO [Outpoint: 12b98877...:1, Amount: 10000000] if isBoardingUtxo {
// Extract boarding tapscripts and validate
// ...
if err := s.checkIfBanned(ctx, input); err != nil {
return "", errors.VTXO_BANNED.Wrap(err)
}
log.Infof("[ASP LOG]: Spending Boarding UTXO [Outpoint: %s, Amount: %d]",
vtxoOutpoint.String(), psbtInput.WitnessUtxo.Value)
boardingUtxos = append(boardingUtxos, boardingIntentInput{
// ...
})
continue
} boardingInputs := make([]ports.BoardingInput, 0)
if len(boardingUtxos) > 0 {
var err errors.Error
boardingInputs, err = s.processBoardingInputs(ctx, intent.Id, boardingUtxos)
if err != nil {
return "", err
}
}
log.Infof("[ASP DEBUG]: Registering intent %s with %d vtxo inputs, %d boarding inputs, and %d receivers", intent.Id, len(intent.Inputs), len(boardingInputs), len(intent.Receivers))
[ASP LOG]: Submitting Off-chain Transfer from f2acc29e...:0
[ASP LOG]: Recipient PubKey identified: bb771344... // Iterate over the outputs to create new VTXOs
for i, out := range outputs {
pubkeyHex := hex.EncodeToString(out.PkScript)
if len(pubkeyHex) > 4 {
log.Infof("[ASP LOG]: Submitting Off-chain Transfer from %s", txid)
log.Infof("[ASP LOG]: Recipient PubKey identified: %s", pubkeyHex[4:])
}
log.Infof("[ASP LOG]: Creating New VTXO [Index: %d, PkScript: %x, Amount: %d]",
i, out.PkScript, out.Value)
}
[ASP LOG]: Spending VTXO [Outpoint: f2acc29e...:0, Amount: 500000, PubKey: 0238a920...]
[ASP DEBUG]: Registering intent 49d05cb5... with 1 vtxo inputs, 0 boarding inputs, and 1 receivers
// (RegisterIntent)
vtxo := vtxosResult[0]
assetInfo := ""
if len(vtxo.Assets) > 0 {
assetInfo = fmt.Sprintf(", assets=%v", vtxo.Assets)
}
log.Infof("[ASP LOG]: Spending VTXO [Outpoint: %s, Amount: %d, PubKey: %s%s]",
vtxo.Outpoint, vtxo.Amount, vtxo.PubKey, assetInfo)
[ASP LOG]: Spending VTXO [Outpoint: f2acc29e...:1, Amount: 500000, PubKey: dee8298f...]
[ASP DEBUG]: Registering intent 733c4d59... with 1 vtxo inputs, 0 boarding inputs, and 1 receivers
You can visualize this without running arkd using https://arkansaurus.github.io/ simulation tool.
Steven Roose (second) does not consider privacy to be a priority at this moment based on his tweets. Marco Argentieri (Ark Labs) believes that blinded credentials and coinjoin for VTXOs is possible. However, it could it could complicate things further and require another trusted party.



