Skip to main content

What are Secrets?

Secrets are random 32-byte values that serve as cryptographic keys for cross-chain swaps. They enable atomic swaps by:
  1. Locking funds in escrows via hash commitments
  2. Authorizing withdrawals when revealed
  3. Ensuring atomicity across chains
Critical: Once a secret is revealed on-chain, anyone can see it. Never reveal secrets until you’ve verified escrow deployments are correct.

Secret Generation

Requirements

Secrets must be:
  • Exactly 32 bytes (64 hex characters)
  • Cryptographically random
  • Unique for each order (or each fill in multi-fill orders)

Generating Secure Secrets

Use Node.js crypto.randomBytes for cryptographically secure randomness:
// From tests/utils/secret.ts:4-6
export function getSecret(): string {
    return add0x(randomBytes(32).toString('hex'))
}
Complete example:
import { randomBytes } from 'crypto'

// Generate a single secret
const secret = '0x' + randomBytes(32).toString('hex')
// Example output: '0x1234...abcd' (66 characters including 0x)

// Generate multiple secrets for multi-fill order
const secrets = Array.from({ length: 5 }, () => 
  '0x' + randomBytes(32).toString('hex')
)
Never use Math.random() or other non-cryptographic random number generators for secrets. They are predictable and can be exploited.

Secret Hashing

Before creating an order, secrets must be hashed:
// From hash-lock.ts:17-24
public static hashSecret(secret: string): string {
    assert(
        isHexBytes(secret) && getBytesCount(secret) === 32n,
        'secret length must be 32 bytes hex encoded'
    )
    return keccak256(secret)
}
Usage:
import { HashLock } from '@1inch/cross-chain-sdk'

const secret = '0x' + randomBytes(32).toString('hex')
const secretHash = HashLock.hashSecret(secret)

// Secret hash is used in order creation
const secretHashes = secrets.map(s => HashLock.hashSecret(s))
The hash is what gets committed to the blockchain. The actual secret remains private until revelation.

When to Reveal Secrets

The timing of secret revelation is critical for security and successful swaps.

Revelation Workflow

Checking Readiness

Use the SDK to check when escrows are deployed and ready:
// Poll for ready escrows
while (true) {
  const secretsToShare = await sdk.getReadyToAcceptSecretFills(orderHash)
  
  if (secretsToShare.fills.length > 0) {
    // Escrows are deployed and ready
    break
  }
  
  await sleep(1000) // Wait 1 second
}

Verification Before Reveal

Always verify these parameters before revealing secrets:
  1. Escrow addresses match expected values
  2. Token addresses are correct
  3. Token amounts are correct
  4. Time-locks are acceptable
  5. Safety deposits are sufficient
Example verification:
const secretsToShare = await sdk.getReadyToAcceptSecretFills(orderHash)

for (const fill of secretsToShare.fills) {
  // Verify escrow parameters
  const isValid = (
    fill.srcEscrowAddress === expectedSrcAddress &&
    fill.dstEscrowAddress === expectedDstAddress &&
    fill.srcToken === expectedSrcToken &&
    fill.dstToken === expectedDstToken &&
    fill.amount >= expectedAmount
  )
  
  if (!isValid) {
    console.error('Escrow parameters mismatch!', fill)
    continue // Don't reveal this secret
  }
  
  // Safe to reveal
  await sdk.submitSecret(orderHash, secrets[fill.idx])
}

Submitting Secrets

// From README.md:205-207 (example pattern)
await sdk.submitSecret(hash, secrets[idx])
console.log({idx}, 'shared secret')
Complete example:
import { SDK, OrderStatus } from '@1inch/cross-chain-sdk'

// Submit secrets as escrows become ready
const alreadyShared = new Set<number>()

while (true) {
  const secretsToShare = await sdk.getReadyToAcceptSecretFills(orderHash)
  
  for (const fill of secretsToShare.fills) {
    const idx = fill.idx
    
    // Don't submit the same secret twice
    if (alreadyShared.has(idx)) continue
    
    // Verify escrow deployment (see above)
    const isValid = verifyEscrowDeployment(fill)
    if (!isValid) continue
    
    // Reveal the secret
    await sdk.submitSecret(orderHash, secrets[idx])
    alreadyShared.add(idx)
    
    console.log(`Revealed secret ${idx}`)
  }
  
  // Check if order is complete
  const { status } = await sdk.getOrderStatus(orderHash)
  
  if (
    status === OrderStatus.Executed ||
    status === OrderStatus.Expired ||
    status === OrderStatus.Refunded
  ) {
    break
  }
  
  await sleep(1000)
}

Security Best Practices

Storage

  1. Never log secrets in production environments
    // BAD - Secrets in logs!
    console.log('Generated secret:', secret)
    
    // GOOD - Log without exposing secret
    console.log('Generated secret hash:', HashLock.hashSecret(secret))
    
  2. Store in memory only when possible
    // Generate secrets right before order creation
    const secrets = Array.from({ length: count }, getSecret)
    
    // Use immediately
    const hashLock = HashLock.forMultipleFills(
      HashLock.getMerkleLeaves(secrets)
    )
    
    // Keep in memory until swap completes
    
  3. Encrypt if persisting to disk or database
    import { createCipheriv, createDecipheriv, randomBytes } from 'crypto'
    
    function encryptSecret(secret: string, key: Buffer): string {
      const iv = randomBytes(16)
      const cipher = createCipheriv('aes-256-gcm', key, iv)
      const encrypted = Buffer.concat([
        cipher.update(secret, 'utf8'),
        cipher.final()
      ])
      return JSON.stringify({
        iv: iv.toString('hex'),
        encrypted: encrypted.toString('hex'),
        tag: cipher.getAuthTag().toString('hex')
      })
    }
    

Generation

  1. Use cryptographic RNG
    // GOOD
    import { randomBytes } from 'crypto'
    const secret = '0x' + randomBytes(32).toString('hex')
    
    // BAD - Predictable!
    const badSecret = '0x' + Math.random().toString(16).repeat(4)
    
  2. Generate sufficient entropy
    // Ensure exactly 32 bytes
    const secret = '0x' + randomBytes(32).toString('hex')
    assert(secret.length === 66, 'Invalid secret length')
    
  3. Generate unique secrets per order
    // Each order needs fresh secrets
    function createOrder() {
      const secrets = Array.from({ length: count }, getSecret)
      // ... create order with these secrets
    }
    

Revelation

  1. Verify before revealing
    // Always check escrow parameters match expectations
    if (!verifyEscrowDeployment(fill)) {
      throw new Error('Escrow verification failed - not revealing secret')
    }
    
  2. Reveal at the right time
    // Wait for escrows to be deployed
    const fills = await sdk.getReadyToAcceptSecretFills(orderHash)
    
    // Verify we're in the right time-lock stage
    const timeLocks = order.timeLocks.toSrcTimeLocks(deployedAt)
    if (timeLocks.isFinalityLock()) {
      // Too early - wait for finality lock to end
      return
    }
    
  3. Handle revelation failures
    try {
      await sdk.submitSecret(orderHash, secret)
    } catch (error) {
      // Log error but keep secret for retry
      console.error('Failed to submit secret:', error.message)
      // Retry later...
    }
    

Multi-Fill Orders

For orders with multiple fills, manage secrets carefully:
import { HashLock } from '@1inch/cross-chain-sdk'
import { randomBytes } from 'crypto'

// Generate secrets
const secretCount = 5
const secrets = Array.from({ length: secretCount }, () =>
  '0x' + randomBytes(32).toString('hex')
)

// Create Merkle tree
const leaves = HashLock.getMerkleLeaves(secrets)
const hashLock = HashLock.forMultipleFills(leaves)

// Track which secrets have been revealed
const revealed = new Set<number>()

// Reveal secrets as fills happen
for (const fill of secretsToShare.fills) {
  if (!revealed.has(fill.idx)) {
    // Verify this specific fill
    if (verifyFill(fill)) {
      await sdk.submitSecret(orderHash, secrets[fill.idx])
      revealed.add(fill.idx)
    }
  }
}

Common Mistakes to Avoid

  1. Revealing secrets too early
    // BAD - Don't reveal immediately after order creation
    await sdk.submitOrder(...)
    await sdk.submitSecret(hash, secret) // Escrows not deployed yet!
    
    // GOOD - Wait for escrows
    await sdk.submitOrder(...)
    const fills = await sdk.getReadyToAcceptSecretFills(hash)
    if (fills.fills.length > 0) {
      await sdk.submitSecret(hash, secret)
    }
    
  2. Not verifying escrow parameters
    // BAD - Blindly revealing
    await sdk.submitSecret(hash, secret)
    
    // GOOD - Verify first
    const fills = await sdk.getReadyToAcceptSecretFills(hash)
    for (const fill of fills.fills) {
      if (verifyEscrowDeployment(fill)) {
        await sdk.submitSecret(hash, secrets[fill.idx])
      }
    }
    
  3. Using predictable secrets
    // BAD - Predictable pattern
    const secret = '0x' + '1234567890'.repeat(7)
    
    // GOOD - Cryptographically random
    const secret = '0x' + randomBytes(32).toString('hex')
    
  4. Logging secrets in production
    // BAD
    console.log('Secret:', secret)
    
    // GOOD - Log hash instead
    console.log('Secret hash:', HashLock.hashSecret(secret))
    

Secret Lifecycle Summary

PhaseActionSecurity Consideration
GenerationCreate random 32-byte valueUse crypto.randomBytes
HashingCompute keccak256(secret)Verify 32-byte input
CommitmentInclude hash in orderHash is public, secret stays private
StorageKeep secret in memory/encryptedNever log or expose
VerificationCheck escrow deploymentsVerify all parameters
RevelationSubmit to SDKOnly after verification
CleanupClear from memoryPrevent memory leaks

Example: Complete Secrets Flow

import { SDK, HashLock, OrderStatus } from '@1inch/cross-chain-sdk'
import { randomBytes } from 'crypto'

async function executeSwap() {
  // 1. Generate secrets
  const secrets = Array.from({ length: 3 }, () =>
    '0x' + randomBytes(32).toString('hex')
  )
  
  // 2. Create hash lock
  const hashLock = HashLock.forMultipleFills(
    HashLock.getMerkleLeaves(secrets)
  )
  
  // 3. Hash secrets for submission
  const secretHashes = secrets.map(s => HashLock.hashSecret(s))
  
  // 4. Create and submit order
  const { hash } = await sdk.createOrder(quote, {
    walletAddress,
    hashLock,
    preset: 'fast',
    source: 'my-app',
    secretHashes
  })
  
  await sdk.submitOrder(srcChainId, order, quoteId, secretHashes)
  
  // 5. Wait and reveal secrets
  const revealed = new Set<number>()
  
  while (true) {
    const fills = await sdk.getReadyToAcceptSecretFills(hash)
    
    for (const fill of fills.fills) {
      if (revealed.has(fill.idx)) continue
      
      // 6. VERIFY escrow deployment
      const isValid = (
        fill.srcEscrowAddress === expectedSrcAddress &&
        fill.dstEscrowAddress === expectedDstAddress
        // ... other checks
      )
      
      if (!isValid) {
        console.error('Invalid escrow parameters!')
        continue
      }
      
      // 7. Reveal secret
      await sdk.submitSecret(hash, secrets[fill.idx])
      revealed.add(fill.idx)
      console.log(`Revealed secret ${fill.idx}`)
    }
    
    // 8. Check completion
    const { status } = await sdk.getOrderStatus(hash)
    if (status === OrderStatus.Executed) {
      console.log('Swap completed!')
      break
    }
    
    await sleep(1000)
  }
  
  // 9. Clear secrets from memory
  secrets.fill('0x' + '0'.repeat(64))
}

Build docs developers (and LLMs) love