Conversation
Initial Go module with the protocol primitives: - pkg/core: wire types (blocks, ops, receipts, URIs) with generated tuple-mode CBOR codecs - pkg/decimal: arbitrary-precision decimal with tag-2 CBOR encoding - pkg/cborx: canonical envelope/frame/version codec - pkg/abiutil: shared ABI type singletons - pkg/eip712: EIP-712 digest and signer recovery - pkg/bls: BN254 keygen/sign/aggregate/verify, cluster signature verification, vault withdrawal-ID derivation - pkg/receipt: burn/mint receipt digests and verifier - preimage freeze: byte-exact golden tests for signing preimages, enforced by scripts/ci/check-preimage-goldens.sh
Builds the consumer-facing blockchain layer on top of the protocol primitives, plus a real-node integration harness. - pkg/sign: pluggable, algorithm-aware Signer (secp256k1/ed25519) with an in-memory KeySigner for clients/CLI/tests and EVM digest helpers; KMS backends satisfy the same interface - pkg/core: chain-agnostic adapter interfaces (VaultDepositor, VaultWithdrawalFinalizer with Pack/Validate/Sign/Merge/Submit/ VerifyExecution, Registry/Token/Fraud/Faucet readers+writers) and Slot types - pkg/blockchain/evm: generated contract bindings (regenerated from vendored ABI/bytecode via `go generate`), on-chain BLS pubkey cache, and per-concern adapters — Depositor, WithdrawalFinalizer, Registry, Token, Fraud, Faucet - pkg/blockchain/btc: m-of-n P2WSH multisig vault — depositor + finalizer - pkg/blockchain/xrpl: multi-sign vault — depositor + finalizer - devnet/: docker-compose (anvil + bitcoind + rippled) with a readiness gate; self-provisioning deposit + withdrawal integration tests per chain - Makefile: build / lint / test / generate / devnet / integration Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- pkg/blockchain/sol: VaultDepositor (native SOL + SPL) and VaultWithdrawalFinalizer (native SOL) over the custody Anchor program — an ed25519 quorum signs a digest, verified on-chain via the Ed25519 precompile; fee payer separate from the quorum signers - generated program bindings (pkg/blockchain/sol/custody) emitted from the vendored Anchor IDL by `go generate` via an anchor-go-backed idl_refresher (the Solana parallel of the EVM abi_refresher); IDL + program binary vendored under sol/artifacts - on-chain read/submit commitment is configurable (default Finalized; devnet/tests use Confirmed to avoid waiting for finality) - devnet: solana-test-validator service with the program preloaded upgradeable at its fixed id + a readiness probe; self-provisioning deposit + withdrawal integration test (toolchain-free at test time) - evm: WithdrawalFinalizer.Submit now waits for the execute tx to mine Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extend the chain-agnostic adapter surface: - core.DepositStatus tri-state + VaultDepositor.VerifyDeposit per chain (EVM receipt depth, BTC confirmations, XRPL validated flag, Solana commitment ladder). - Concrete btc.Client (bitcoind JSON-RPC) with typed RPCError; the withdrawal idempotency check now branches on the error code. - Export the XRPL wire helpers (BuildAmount, CanonicalJSON, DeriveIdentity, ValidateCanonical) and the Identity type for non-finalizer callers. - Add xrpl.LedgerTicketProvider, a client-backed TicketProvider. Fold VaultWithdrawalFinalizer.Merge into Submit(packed, signatures), dropping the unused merged-bytes intermediate, and rename VaultDepositor.Deposit to SubmitDeposit to pair with VerifyDeposit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add core.SignerRotationFinalizer (Pack/Validate/Sign/Submit/VerifyRotation) and a per-chain implementation: - EVM: updateSigners over the live quorum; rotation digest commits to chainId, vault, "updateSigners", keccak(newSigners,newThreshold) and the on-chain signerNonce, golden-tested against the contract. - Solana: update_signers with the ed25519 quorum verified via the Ed25519 precompile; digest binds the signers commitment + program nonce. - XRPL: multi-signed SignerListSet; replay defense is the account sequence. - BTC: no in-place form, so rotation is a sweep of every vault UTXO into the newly-derived vault, behind the same interface via a VaultStore seam that pivots on confirmation. Share the EVM quorum-signature merge (mergeQuorumSigs/fetchLiveQuorum) and the XRPL multisign combine between the withdrawal and rotation paths, and add abiutil.AddressArr for the rotation digest. Per-chain rotation integration tests run on the devnet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Blockchain-adapter adoption: chain-by-chain, NOT uniform This is where the real findings are. Artifact-level (digest/preimage/signature/address) the SDK matches custody almost everywhere — but adoption has blockers: │ XRPL │ │ SOL │ The BTC rotation blocker (verified): SDK builds a 1-output sweep with no marker; custody builds [newVault(total-fee), OP_RETURN(requestID)] and its withdrawal watcher recognizes the landed sweep by that OP_RETURN requestID to pivot the vault. An SDK-built sweep would have a different txid, strip the marker, and never be recognized → vault never pivots. Either the SDK sweep must emit the OP_RETURN, or custody keeps its own BTC rotation finalizer. |
|
Ask ai to review today's commits from the custody repo (what they affect) and apply this diff towards SDK too |
Adds the pkg/p2p protocol layer over a caller-supplied host.Host: - protocol: stream IDs + topic names pinned to one version, the AuthChallenge/AuthResponse/ReceiptAck wire structs with handwritten CBOR codecs (golden-frozen), and the Registrar interface. - auth: /ynp/auth handshake with operator (secp256k1 + allow-list) and passive (libp2p identity) roles; Server + Client. - receipt: burn/mint submission over a ReceiptHandler seam; Server with per-stream HandleBurnReceipt/HandleMintReceipt + Client. - pubsub: concrete FinalizedWithdrawal publish/subscribe. - gossip: generic publish/subscribe over any cborx payload — the type-parameterized alternative to pubsub. Servers take a host and register handlers; they never construct the Host. pubsub and gossip overlap by design — one is dropped before merge.
Adds pkg/log — a structured, context-aware Logger interface with zap, noop, and span (OpenTelemetry) implementations — and routes every logging call through it. - pkg/p2p (auth/receipt/pubsub/gossip): loggers take log.Logger; nil defaults to a no-op so the library is silent unless a logger is injected. - evm BLSPubkeyCache: replace global slog calls with an injected logger field, defaulting to no-op, settable via SetLogger; the constructor signature is unchanged.
The BTC rotation sweep now emits the watcher-recognizable wire: output 0 pays the new vault, the final output is a zero-value OP_RETURN carrying the rotation's operation id. A watcher can attribute the landed sweep to a specific rotation by that marker and pivot the vault — a single unmarked output could not be distinguished from any other vault spend. SignerRotationFinalizer.Pack/Validate take an opID [32]byte. BTC embeds it in the sweep; EVM, XRPL, and Solana bind rotation replay on-chain (signerNonce / account sequence / program nonce) and accept opID only to keep one uniform signature. VerifyRotation stays keyed on the new signer set, so it remains a direct state read on every chain.
XRPL: reject a present-but-non-zero (or non-numeric) Flags in both the withdrawal and rotation canonical validators, closing the tfPartialPayment underdelivery path on issued-currency withdrawals. The multi-sign combine now reads the vault's live SignerList and trims to the live SignerQuorum, dropping blobs from signers that have rotated off (or not yet on) and sizing the fee to the current quorum. Solana: support SPL-token withdrawals — an idempotent recipient-ATA creation ahead of the Ed25519 companion and the token remaining-accounts on execute (token program, vault ATA, recipient ATA), reusing the generated instruction's data encoding. Submit can emit a v0 transaction over a configured Address Lookup Table so large quorums stay within the 1232-byte packet limit. BTC: the withdrawal validator asserts the fixed fields the SIGHASH commits to (tx version, zero locktime, final input sequences), rejecting a non-final or RBF-signalling canonical tx before signing. Docs: the Custody (EVM) and custody-program (Solana) artifacts are now sourced from the custody repo, not clearnet.
Tests for the previously-uncovered logic plus the matching BTC fix: - BTC: factor the SIGHASH fixed-field checks into validateFixedTxFields and apply them in the rotation validator too (they were only on the withdrawal path); unit test for the version/locktime/sequence guard. - XRPL: unit tests for the Flags==0 guard on both the withdrawal and rotation validators (tfPartialPayment rejected), and for the live-SignerList blob filter using real multi-sign blobs. - Solana: unit test pinning the execute account shape (native vs the SPL remaining-accounts), and an SPL-withdrawal case in the integration test that mints to the vault ATA and asserts the recipient ATA is credited.
…an ALT VaultLookupAddresses returns the invariant accounts of the execute instruction — the lookup-table-eligible set that lets large quorums fit a v0 transaction. Centralizing it in the SDK keeps the table in lockstep with the instruction's account layout, instead of every consumer hand-copying the list. The SPL-withdrawal integration case now builds a real Address Lookup Table from VaultLookupAddresses and withdraws over it, exercising the v0/ALT submit path end to end. The table's recent_slot is taken at finalized commitment, the only slot guaranteed to already be in the SlotHashes sysvar (the current slot can equal the execution slot and is rejected as not recent).
Reusable test-go (go test -race ./...) and test-integration (make devnet → make integration → make devnet-down) workflows, invoked by per-event callers on pull requests and pushes to master. Test-only — no lint, build, or publish steps. Pin every devnet image to its manifest-list digest so the integration job runs the exact versions the suite was validated against and a moving upstream tag cannot break CI out from under unrelated changes.
- protocol: ReceiptAck decoder accepts >=2 array elements and skips the trailing fields instead of requiring exactly 2. A real clearnode emits a wider (6-element) ack, which the strict reader rejected — this blocked the receipt client from talking to production. The encoder still writes the 2-element form, so the golden vector is unchanged. - auth: the server bounds the whole handshake with a deadline (challenge write + response read), matching the receipt server, so a stalled peer cannot pin a handler goroutine. - log: ZapLogger.WithKV clones the parent's key-value slice before appending; the bare append reused the parent's backing array, racing and corrupting sibling loggers' KV under fan-out.
Resolve the side-by-side pubsub/gossip choice in favour of the generic, type-parameterized implementation: remove the concrete *core.FinalizedWithdrawal pubsub and rename gossip → pubsub. The package now serves any cborx-envelope payload (FinalizedWithdrawal included) via Publisher[T]/Follower[T].
- bls: DeserializeG1/G2 are acceptance-path decoders for untrusted input but set coordinates without checks. Reject non-canonical coordinates (>= field prime), off-curve points, and points outside the prime-order subgroup — the membership the on-chain precompile enforces. Without the subgroup check a crafted point could pass off-chain acceptance. - evm: packedFromOp rejects a recipient or asset that is not a well-formed hex address. common.HexToAddress silently zero-fills a malformed input, which would otherwise sign a withdrawal to the wrong (often zero) destination.
The protocol primitives plus the consumer-facing blockchain layer (deposit +
withdrawal across EVM, BTC, XRPL, and Solana), the p2p transport layer, a
real-node integration harness, and CI.
Protocol primitives (
pkg/)core— wire types (blocks, ops, receipts, URIs, Slot) with generatedtuple-mode CBOR codecs; chain-agnostic adapter interfaces
decimal,cborx— fixed-point decimal + canonical envelope/frame codecabiutil,eip712— ABI type singletons + EIP-712 digest/recoverybls— BN254 keygen/sign/aggregate/verify, cluster-signature verification,vault withdrawal-ID derivation
receipt— burn/mint receipt digests and verifierlog— structured, context-awareLoggerinterface (zap / noop / spanimplementations); every package logs through it, defaulting to no-op so the
library is silent until a consumer injects a logger
scripts/ci/check-preimage-goldens.shSigner seam (
pkg/sign)Pluggable, algorithm-aware
Signer(secp256k1/ed25519) with an in-memoryKeySignerfor clients/CLI/tests and EVM digest helpers; KMS backends satisfythe same interface.
Blockchain adapters (
pkg/blockchain)Per-concern adapters over caller-supplied chain clients + a
sign.Signer. Thedeposit path is
core.VaultDepositor(SubmitDepositto broadcast, plus aVerifyDeposittri-state read — absent / pending / confirmed); withdrawal iscore.VaultWithdrawalFinalizer(Pack/Validate/Sign/Submit/VerifyExecution) — a custody node runs it over a caller-orchestrated quorum,with only signature collection left to the caller's mesh.
Submitmerges thecollected signatures and broadcasts in one step. Signer rotation mirrors that
shape as
core.SignerRotationFinalizer(Pack/Validate/Sign/Submit/VerifyRotation), implemented for all four chains.Pack/Validatetake anopaque
opID— in-place chains bind replay on-chain and ignore it; BTC embedsit in the sweep (below).
go generate; seepkg/blockchain/evm/artifacts/README.md), on-chain BLSpubkey cache,
Depositor/WithdrawalFinalizer/RotationFinalizer/Registry/Token/Fraud/FaucetUTXO sweep into the newly-derived vault, behind a
VaultStoreseam, markedwith an
OP_RETURN(opID)so a watcher can attribute the landed sweep), plus aconcrete bitcoind JSON-RPC
Client. Canonical validators assert the fixedSIGHASH fields (version / locktime / final sequences).
SignerListSet)(+
TicketProviderseam, with a ledger-backedLedgerTicketProvider). Wirehelpers (
BuildAmount,CanonicalJSON,DeriveIdentity,ValidateCanonical,Identity) exported for non-finalizer callers. Validation rejects a non-zeroFlags(tfPartialPayment underdelivery guard), and submit filters thecollected blobs against the vault's live SignerList + quorum.
(native SOL and SPL) + rotation; ed25519 quorum verified on-chain via the
Ed25519 precompile. SPL withdrawals add the recipient-ATA creation + token
remaining-accounts and can ride a v0 transaction over an Address Lookup Table
(
VaultLookupAddressesreturns the table's invariant account set) so largequorums fit the packet limit. Bindings generated from the vendored Anchor IDL
via
go generate(anchor-go); on-chain commitment configurable.P2P transport layer (
pkg/p2p)The canonical libp2p wire contract, pinned to one protocol version, as a
library over a caller-supplied
host.Host— the SDK registers handlers andspeaks the wire; it never constructs the Host or owns connectivity.
protocol— stream IDs + GossipSub topic names (pinned), theAuthChallenge/AuthResponse/ReceiptAckwire structs with handwritten CBORcodecs (golden-frozen), and the
Registrarinterface every server implementsauth— the/ynp/authhandshake with two roles: operator (secp256k1signature recovered against an allow-list) and passive (libp2p identity key).
Server(HandleAuth+Register) andClient(Authenticate)receipt— burn/mint receipt submission over aReceiptHandlerseam.Server(HandleBurnReceipt/HandleMintReceipt+Register) andClient(
SendBurnReceipt/SendMintReceipt)pubsub— generic publish/subscribe over any cborx-envelope payload, typeparameterized via constraint inference (
Publisher[T]/Follower[T], e.g.*core.FinalizedWithdrawal)Devnet & integration tests (
devnet/)make devnetbrings up anvil + bitcoind + rippled + solana-test-validator andblocks until ready;
make integrationruns self-provisioning deposit +withdrawal flows per chain (fresh keys/accounts/contract each run — re-runs are
clean), including an SPL-token withdrawal over a v0/ALT transaction. Devnet
images are pinned by digest. See
devnet/README.md.CI (
.github/workflows)Reusable
test-go(unit:go test -race ./...) andtest-integration(
make devnet→make integration→make devnet-down) workflows, invoked byper-event callers on pull requests and pushes to
master. Test-only.Make targets
build/lint/test/generate/devnet/devnet-down/integration🤖 Generated with Claude Code