Companion

KCC 20: KCC-20 Borrowed Receive Authorization

Kaspa KCC 20 companion explaining borrowed-receive authorization and its relationship to fungible-token covenant rules.

Authors
Username@Manyfestation Email[email protected] Username@michaelsutton Email[email protected] Email[email protected] Username@IzioDev Email[email protected]
Type
Standards Track, Application
Updated
2026-10-07
Status
Last Call
On this page

KCC-20 Borrowed Receive Authorization

Borrowed Receive is defined in KCC-20 Section 5.

Motivation

KIP-9 prices UTXO-set growth through storage mass, which rises for small-valued new outputs.

For token transfers, this means the sender normally funds each new recipient token UTXO with enough KAS, or the recipient co-signs and supplies an existing UTXO.

Borrowed Receive allows the sender to use an existing recipient KCC-20 UTXO as the receive target. Instead of creating a new recipient token UTXO, the sender consumes the existing UTXO and recreates it in place with a larger token amount. The recipient's normal owner authorization is not used, the KAS value cannot decrease, and the owner and extended state remain unchanged.

Because a borrowed receive consumes and recreates an existing UTXO, it changes that UTXO's outpoint. A wallet that did not expect the borrow may need to synchronize and discover the successor before using its recorded UTXO.

Unrestricted borrowing also enables outpoint-churn spam. An attacker can repeatedly recreate the UTXO through token-dust transfers, continually changing its outpoint and invalidating transactions constructed to spend the previous one.

Borrow authorization

Each KCC-20 state contains a borrow_scheme and a 32-byte borrow_guard. borrow_scheme selects the authorization rule, while borrow_guard holds its parameter or evolving state.

Some schemes require a scheme-specific borrow_witness.

SchemeControl modelborrow_guardborrow_witness
disabled/v1No borrowingUnusedBorrowing is rejected
amount-threshold/v1Any sender above effective thresholdSigned threshold in first eight bytes; negative values behave as zeroEmpty
schnorr-signature/v1Approved borrower, reusableDedicated 32-byte public key65-byte Schnorr transaction signature
hash-chain/v1Approved borrow, single-useCurrent 32-byte hash-chain commitment32-byte preimage, 32-byte one-time public key, and 65-byte signature

Every borrowed receive preserves borrow_scheme. A normal owner-authorized transfer may replace both borrow_scheme and borrow_guard.

disabled/v1

The disabled/v1 scheme rejects borrowed receives.

amount-threshold/v1

The amount-threshold/v1 scheme allows any sender to borrow when the token increase exceeds the effective threshold, max(decoded_threshold, 0). The first eight bytes of borrow_guard encode a little-endian signed-magnitude integer; the remaining bytes do not affect the threshold. A positive threshold mitigates dust-based outpoint churn; a zero effective threshold allows any positive increase. Negative zero behaves as zero. No borrow_witness is required, and the complete guard is preserved.

Contracts may require thresholds to be non-negative.

schnorr-signature/v1

The schnorr-signature/v1 scheme allows the holder of a dedicated borrow key to authorize repeated borrows. Each borrow requires a valid transaction signature. The key cannot spend the recipient's tokens or reduce the UTXO's KAS value.

hash-chain/v1

The hash-chain/v1 scheme allows one borrow per released chain link. Each link is a one-time authorization bound to its own signing key and advances borrow_guard when used. A wallet can prepare a finite chain and release links at will to authorize individual borrows.

The idea originates in Rivest and Shamir's PayWord and MicroMint: Two Simple Micropayment Schemes. KCC-20 adapts the one-way hash-chain construction by binding each link to a distinct one-time Schnorr key.

To prepare a chain for n borrows, the wallet chooses a random value x_0 and n one-time Schnorr keypairs (private_key_i, pubkey_i), then computes:

x_1 = Hash(x_0 || pubkey_1)
x_2 = Hash(x_1 || pubkey_2)
...
x_n = Hash(x_(n-1) || pubkey_n)

The initial borrow_guard is x_n, which commits to the complete sequence. A borrow against guard x_i reveals x_(i-1) and pubkey_i, and includes a transaction signature by the corresponding one-time private key. It proves the authorization by requiring:

Hash(x_(i-1) || pubkey_i) == x_i
VerifySchnorr(pubkey_i, transaction, signature_i)

The wallet gives the intended sender x_(i-1) and private_key_i. The revealed value x_(i-1) becomes the successor's borrow_guard, ready for the next borrow.

The authorizations are revealed in reverse order and cannot be reused. The wallet may release them one at a time while monitoring each borrow, or several in advance while accepting that its outpoint may change until they are consumed or revoked. The chain is exhausted after x_0 is revealed.

Wallet-control model

The hash-chain scheme is designed to keep the recipient's wallet in control of when its UTXO may change. The wallet releases one authorization while monitoring the network, records the confirmed successor outpoint, and keeps the next authorization private until another change is expected. Without a released authorization, the UTXO cannot be borrowed, so the wallet may go offline knowing that its outpoint will remain stable.

Before going offline, a wallet that has released an authorization which remains unused may revoke it through an owner-authorized transfer or consume it itself in a valid borrowed receive.