Encrypting a bid
encryptBid takes a 149-byte BidPayload (already encoded and signed) and
produces a TDH2 ciphertext bound to a specific CDR vault uuid. The ciphertext
is what gets stored on-chain via SealedAuction.submitEncryptedBid.
This function runs in Node only. It loads @piplabs/cdr-crypto WASM at
startup; the WASM glue does not work in browser or edge runtimes. If you are
building a browser app, proxy this call through a Next.js API route or a
server action. See Installation: WASM constraint.
Where this fits in the flow
approve WIP
allocateBidSlot ← returns uuid
signBidDigest + encodePayload
encryptBid(uuid, encodedPayload) ← THIS CALL
submitEncryptedBid
In practice, if you use placeBid you never call encryptBid directly; it
is called internally. Use encryptBid directly only if you are constructing
the flow step by step.
encryptBid
import {encryptBid} from '@sealedip/sdk/encrypt'
import {encodePayload, signBidDigest} from '@sealedip/sdk/payload'
import {toBytes} from 'viem'
// --- 1. Build and sign the payload ---
const nonce = ('0x' + require('crypto').randomBytes(32).toString('hex')) as `0x${string}`
const digest = signBidDigest({
auctionId: 3n,
bidder: account.address,
amount: parseEther('2.5'),
nonce,
})
const signature = await account.signMessage({message: {raw: digest}})
const payloadHex = encodePayload({
bidder: account.address,
amount: parseEther('2.5'),
nonce,
signature,
})
// --- 2. Encrypt ---
const {ciphertext} = await encryptBid({
uuid: 42, // from BidSlotAllocated event
plaintext: toBytes(payloadHex), // Uint8Array of the 149-byte payload
})
// ciphertext is a `0x${string}` ready for submitEncryptedBid
Inputs
| Field | Type | Description |
|---|---|---|
uuid | number | CDR vault uuid returned by allocateBidSlot |
plaintext | Uint8Array | Encoded 149-byte BidPayload |
globalPubKey | Uint8Array? | Override; normally fetched from CDR and cached |
Output
interface EncryptedBid {
ciphertext: `0x${string}` // TDH2 ciphertext as hex
}
How TDH2 works here
- The SDK calls
CDRClient.observer.getGlobalPubKey()on first use. The result is cached for the lifetime of the process (the key is stable for one DKG round). - The payload bytes are encrypted via
tdh2Encrypt({plaintext, globalPubKey, label}). The label isuuidToLabel(uuid). - The raw cb-mpc ciphertext bytes are returned as a hex string.
Label binding is critical. The CDR validator network decrypts using the
same uuidToLabel(uuid) derivation. A ciphertext labeled for uuid 42 cannot
be decrypted under uuid 99, even with the same private key share set. This
ties each ciphertext to the slot it was written into.
signBidDigest
import {signBidDigest, type DigestInput} from '@sealedip/sdk/payload'
function signBidDigest(d: DigestInput): `0x${string}`
interface DigestInput {
auctionId: bigint
bidder: `0x${string}`
amount: bigint
nonce: `0x${string}` // 32 bytes
}
Returns keccak256(abi.encode(auctionId, bidder, amount, nonce)). Sign the
result with account.signMessage({message: {raw: digest}}) (adds the
\x19Ethereum Signed Message:\n32 prefix). The contract uses the same prefix
in BidPayload.recoverSigner.
This digest function is used for both bid payloads (bidder is signer) and the
sealed reserve payload (seller is signer). The field named bidder in
DigestInput is the signer address in both cases.
encodePayload / decodePayload
import {encodePayload, decodePayload, type BidPayloadFields} from '@sealedip/sdk/payload'
// Encode 149 bytes
const hex: `0x${string}` = encodePayload({bidder, amount, nonce, signature})
// Decode back
const fields: BidPayloadFields = decodePayload(hex)
Layout: address(20) || uint256 amount(32) || bytes32 nonce(32) || bytes signature(65) = 149 bytes.
Both functions throw if the signature is not exactly 65 bytes or if the hex is not exactly 298 nibbles.
Sealed reserve: same functions, seller signs
sealReserve (see Submitting a bid)
uses the same signBidDigest + encodePayload + encryptBid pipeline,
except the signer is the seller and the uuid is auction.reserveUuid (not a
bid slot uuid). The on-chain BidPayload.recoverSigner is called identically
at settle; the contract checks that the recovered address equals the seller.