@orbinum/sdk 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +178 -52
- package/dist/index.d.ts +178 -52
- package/dist/index.js +204 -60
- package/dist/index.mjs +208 -63
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1020,11 +1020,27 @@ var EvmExplorer = class _EvmExplorer {
|
|
|
1020
1020
|
};
|
|
1021
1021
|
|
|
1022
1022
|
// src/indexer/IndexerClient.ts
|
|
1023
|
+
var LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
|
|
1024
|
+
function normalizeBaseUrl(baseUrl) {
|
|
1025
|
+
let url;
|
|
1026
|
+
try {
|
|
1027
|
+
url = new URL(baseUrl);
|
|
1028
|
+
} catch {
|
|
1029
|
+
throw new Error(`IndexerClient: invalid baseUrl ${JSON.stringify(baseUrl)}`);
|
|
1030
|
+
}
|
|
1031
|
+
const isLocal = LOCAL_HOSTS.has(url.hostname);
|
|
1032
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && isLocal)) {
|
|
1033
|
+
throw new Error(
|
|
1034
|
+
`IndexerClient: baseUrl must use https:// (got ${url.protocol}//${url.hostname}). Plain http is only allowed for localhost.`
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
return baseUrl.replace(/\/$/, "");
|
|
1038
|
+
}
|
|
1023
1039
|
var IndexerClient = class _IndexerClient {
|
|
1024
1040
|
baseUrl;
|
|
1025
1041
|
timeoutMs;
|
|
1026
1042
|
constructor(config) {
|
|
1027
|
-
this.baseUrl = config.baseUrl
|
|
1043
|
+
this.baseUrl = normalizeBaseUrl(config.baseUrl);
|
|
1028
1044
|
this.timeoutMs = config.timeoutMs ?? 1e4;
|
|
1029
1045
|
}
|
|
1030
1046
|
// ─── Internal helpers ──────────────────────────────────────────────────────
|
|
@@ -1082,7 +1098,7 @@ var IndexerClient = class _IndexerClient {
|
|
|
1082
1098
|
/**
|
|
1083
1099
|
* Returns a paginated list of stealth scan hints ordered ascending by leafIndex.
|
|
1084
1100
|
* Each hint contains only the fields required for ECDH triage and decryption:
|
|
1085
|
-
* leafIndex, commitmentHex, assetId, ephPkHex, encryptedMemo.
|
|
1101
|
+
* leafIndex, commitmentHex, assetId, ephPkHex, encryptedMemo, circuitVersion.
|
|
1086
1102
|
*
|
|
1087
1103
|
* Use `sinceLeafIndex` for incremental scans (cursor = last seen leafIndex + 1).
|
|
1088
1104
|
*/
|
|
@@ -1502,8 +1518,8 @@ var BABYJUB_SUBORDER = 273603035897990940278080071815715938607681397215856725920
|
|
|
1502
1518
|
// src/shielded-pool/protocol/memo.ts
|
|
1503
1519
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
1504
1520
|
var KEY_DOMAIN = new TextEncoder().encode("orbinum-note-encryption-v1");
|
|
1505
|
-
var MEMO_PLAINTEXT_SIZE =
|
|
1506
|
-
function serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk) {
|
|
1521
|
+
var MEMO_PLAINTEXT_SIZE = 120;
|
|
1522
|
+
function serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk, circuitVersion) {
|
|
1507
1523
|
const buf = new Uint8Array(MEMO_PLAINTEXT_SIZE);
|
|
1508
1524
|
const view = new DataView(buf.buffer);
|
|
1509
1525
|
view.setBigUint64(0, value & 0xffffffffffffffffn, true);
|
|
@@ -1512,6 +1528,7 @@ function serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk) {
|
|
|
1512
1528
|
buf.set(blinding.slice(0, 32), 48);
|
|
1513
1529
|
view.setUint32(80, assetId >>> 0, true);
|
|
1514
1530
|
buf.set(counterpartyPk.slice(0, 32), 84);
|
|
1531
|
+
view.setUint32(116, circuitVersion >>> 0, true);
|
|
1515
1532
|
return buf;
|
|
1516
1533
|
}
|
|
1517
1534
|
function deriveEncryptionKey(sharedSecret, commitment) {
|
|
@@ -1524,7 +1541,7 @@ function deriveEncryptionKey(sharedSecret, commitment) {
|
|
|
1524
1541
|
|
|
1525
1542
|
// src/shielded-pool/protocol/EncryptedMemo.ts
|
|
1526
1543
|
var NONCE_SIZE = 12;
|
|
1527
|
-
var CIPHERTEXT_SIZE =
|
|
1544
|
+
var CIPHERTEXT_SIZE = 136;
|
|
1528
1545
|
var EPH_PK_SIZE = 32;
|
|
1529
1546
|
var ENCRYPTED_MEMO_SIZE = NONCE_SIZE + CIPHERTEXT_SIZE + EPH_PK_SIZE;
|
|
1530
1547
|
function bytesToBjjScalar(bytes) {
|
|
@@ -1543,14 +1560,15 @@ function parsePlaintext(nonce, ciphertextWithMac, encKey) {
|
|
|
1543
1560
|
const blinding = bytesToBigintLE(plaintext.slice(48, 80));
|
|
1544
1561
|
const assetId = BigInt(view.getUint32(80, true));
|
|
1545
1562
|
const counterpartyPk = bytesToBigintLE(plaintext.slice(84, 116));
|
|
1546
|
-
|
|
1563
|
+
const circuitVersion = view.getUint32(116, true);
|
|
1564
|
+
return { value, ownerPk, blinding, assetId, counterpartyPk, circuitVersion };
|
|
1547
1565
|
} catch {
|
|
1548
1566
|
return null;
|
|
1549
1567
|
}
|
|
1550
1568
|
}
|
|
1551
1569
|
var EncryptedMemo = {
|
|
1552
1570
|
/**
|
|
1553
|
-
* Build and encrypt a memo for a note using ECDH (v2,
|
|
1571
|
+
* Build and encrypt a memo for a note using ECDH (v2, 180 bytes).
|
|
1554
1572
|
*
|
|
1555
1573
|
* @param value Note value in planck.
|
|
1556
1574
|
* @param ownerPk 32-byte owner public key (LE).
|
|
@@ -1562,11 +1580,20 @@ var EncryptedMemo = {
|
|
|
1562
1580
|
* decoded from a privacy address).
|
|
1563
1581
|
* Pass `new Uint8Array(32)` (all zeros) for a publicly-readable memo.
|
|
1564
1582
|
* @param counterpartyPk 32-byte counterparty BJJ Ax. Default: all zeros.
|
|
1565
|
-
* @
|
|
1583
|
+
* @param circuitVersion ZK circuit version the note is spent under. Default: 0.
|
|
1584
|
+
* @param ephSkOverride 32-byte ephemeral secret key (stealth coordination). Optional.
|
|
1585
|
+
* @returns 180-byte encrypted memo: nonce(12) || ciphertext+MAC(136) || ephPk(32).
|
|
1566
1586
|
*/
|
|
1567
|
-
encrypt(value, ownerPk, blinding, assetId, commitment, recipientIvkPacked, counterpartyPk = new Uint8Array(32), ephSkOverride) {
|
|
1587
|
+
encrypt(value, ownerPk, blinding, assetId, commitment, recipientIvkPacked, counterpartyPk = new Uint8Array(32), circuitVersion = 0, ephSkOverride) {
|
|
1568
1588
|
const nonce = randomBytes(NONCE_SIZE);
|
|
1569
|
-
const plaintext = serializeMemo(
|
|
1589
|
+
const plaintext = serializeMemo(
|
|
1590
|
+
value,
|
|
1591
|
+
ownerPk,
|
|
1592
|
+
blinding,
|
|
1593
|
+
assetId,
|
|
1594
|
+
counterpartyPk,
|
|
1595
|
+
circuitVersion
|
|
1596
|
+
);
|
|
1570
1597
|
const isZeroKey = recipientIvkPacked.every((b) => b === 0);
|
|
1571
1598
|
let sharedSecret;
|
|
1572
1599
|
let ephPkPackedBytes;
|
|
@@ -1597,29 +1624,31 @@ var EncryptedMemo = {
|
|
|
1597
1624
|
return result;
|
|
1598
1625
|
},
|
|
1599
1626
|
/**
|
|
1600
|
-
* Returns a
|
|
1627
|
+
* Returns a 180-byte public memo encrypted with a zero viewing key.
|
|
1601
1628
|
* Decryptable by anyone with `decrypt(memo, commitment, new Uint8Array(32))`.
|
|
1602
1629
|
* Convenience alias for `encrypt(..., new Uint8Array(32))`.
|
|
1603
1630
|
*/
|
|
1604
|
-
encryptPublic(value, ownerPk, blinding, assetId, commitment) {
|
|
1631
|
+
encryptPublic(value, ownerPk, blinding, assetId, commitment, circuitVersion = 0) {
|
|
1605
1632
|
return EncryptedMemo.encrypt(
|
|
1606
1633
|
value,
|
|
1607
1634
|
ownerPk,
|
|
1608
1635
|
blinding,
|
|
1609
1636
|
assetId,
|
|
1610
1637
|
commitment,
|
|
1611
|
-
new Uint8Array(32)
|
|
1638
|
+
new Uint8Array(32),
|
|
1639
|
+
new Uint8Array(32),
|
|
1640
|
+
circuitVersion
|
|
1612
1641
|
);
|
|
1613
1642
|
},
|
|
1614
1643
|
/**
|
|
1615
|
-
* Returns a
|
|
1644
|
+
* Returns a 180-byte zeroed dummy memo (no information, always valid on-chain).
|
|
1616
1645
|
*/
|
|
1617
1646
|
dummy() {
|
|
1618
1647
|
return new Uint8Array(ENCRYPTED_MEMO_SIZE);
|
|
1619
1648
|
},
|
|
1620
1649
|
/**
|
|
1621
1650
|
* Validates that `bytes` is a properly-sized encrypted memo.
|
|
1622
|
-
* Throws an Error if the length is not ENCRYPTED_MEMO_SIZE (
|
|
1651
|
+
* Throws an Error if the length is not ENCRYPTED_MEMO_SIZE (180 bytes).
|
|
1623
1652
|
*
|
|
1624
1653
|
* Call this at system boundaries (extrinsic builders, precompile encoders)
|
|
1625
1654
|
* to catch malformed memos before they reach the chain and fail on-chain.
|
|
@@ -1640,7 +1669,7 @@ var EncryptedMemo = {
|
|
|
1640
1669
|
* Returns null if decryption fails — wrong key, bad MAC, or malformed memo.
|
|
1641
1670
|
* Never throws; safe for scan loops.
|
|
1642
1671
|
*
|
|
1643
|
-
* @param memoBytes
|
|
1672
|
+
* @param memoBytes 180-byte encrypted memo.
|
|
1644
1673
|
* @param commitment 32-byte note commitment (LE).
|
|
1645
1674
|
* @param viewingSecretKey 32-byte HKDF viewing secret key from deriveViewingSecretKey().
|
|
1646
1675
|
*/
|
|
@@ -1652,13 +1681,13 @@ var EncryptedMemo = {
|
|
|
1652
1681
|
* Extract the ECDH shared secret from an encrypted memo using the recipient's viewing secret key.
|
|
1653
1682
|
*
|
|
1654
1683
|
* Used by NoteDecryptor to obtain the shared secret needed for stealth address derivation
|
|
1655
|
-
* without re-running the full decrypt path. Safe to call on any
|
|
1684
|
+
* without re-running the full decrypt path. Safe to call on any 180-byte memo.
|
|
1656
1685
|
*
|
|
1657
1686
|
* Returns `new Uint8Array(32)` (all zeros) for public/dummy memos (zero ephPk).
|
|
1658
1687
|
* Returns `null` if the memo is malformed or the ephPk is not a valid BJJ point.
|
|
1659
1688
|
* Never throws; safe for scan loops.
|
|
1660
1689
|
*
|
|
1661
|
-
* @param memoBytes
|
|
1690
|
+
* @param memoBytes 180-byte encrypted memo.
|
|
1662
1691
|
* @param viewingSecretKey 32-byte HKDF viewing secret key from deriveViewingSecretKey().
|
|
1663
1692
|
*/
|
|
1664
1693
|
extractSharedSecret(memoBytes, viewingSecretKey) {
|
|
@@ -1862,7 +1891,7 @@ var ShieldedPoolModule = class {
|
|
|
1862
1891
|
* Withdraws tokens from the shielded pool to a public address.
|
|
1863
1892
|
* Submits as an UNSIGNED (gasless) transaction — fee is embedded in the ZK proof.
|
|
1864
1893
|
* Pass a `signer` to fall back to signed submission (e.g. for testing).
|
|
1865
|
-
* Extrinsic: shieldedPool.unshield(proof, merkleRoot, nullifier, assetId, amount, recipient, fee)
|
|
1894
|
+
* Extrinsic: shieldedPool.unshield(proof, merkleRoot, nullifier, assetId, amount, recipient, fee, changeCommitment, changeEncryptedMemo, relayer, circuitVersion)
|
|
1866
1895
|
*/
|
|
1867
1896
|
async unshield(params, signer, txOptions) {
|
|
1868
1897
|
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "unshield");
|
|
@@ -1886,8 +1915,9 @@ var ShieldedPoolModule = class {
|
|
|
1886
1915
|
fee: params.fee ?? 0n,
|
|
1887
1916
|
change_commitment: changeCommitment,
|
|
1888
1917
|
change_encrypted_memo: changeEncryptedMemo,
|
|
1889
|
-
relayer: void 0
|
|
1918
|
+
relayer: void 0,
|
|
1890
1919
|
// Option<H160> — None for direct Substrate submissions
|
|
1920
|
+
circuit_version: params.circuitVersion
|
|
1891
1921
|
});
|
|
1892
1922
|
if (signer) {
|
|
1893
1923
|
return toTxResult(
|
|
@@ -1900,7 +1930,7 @@ var ShieldedPoolModule = class {
|
|
|
1900
1930
|
* Performs a private (shielded) transfer between two notes.
|
|
1901
1931
|
* Submits as an UNSIGNED (gasless) transaction — fee is embedded in the ZK proof.
|
|
1902
1932
|
* Pass a `signer` to fall back to signed submission (e.g. for testing).
|
|
1903
|
-
* Extrinsic: shieldedPool.privateTransfer(proof, merkleRoot, nullifiers, commitments, memos, assetId, fee)
|
|
1933
|
+
* Extrinsic: shieldedPool.privateTransfer(proof, merkleRoot, nullifiers, commitments, memos, assetId, fee, relayer, circuitVersion)
|
|
1904
1934
|
*/
|
|
1905
1935
|
async privateTransfer(params, signer, txOptions) {
|
|
1906
1936
|
const nullifiers = params.inputs.map((inp) => inp.nullifier);
|
|
@@ -1921,8 +1951,9 @@ var ShieldedPoolModule = class {
|
|
|
1921
1951
|
encrypted_memos: memos,
|
|
1922
1952
|
asset_id: params.assetId,
|
|
1923
1953
|
fee: params.fee ?? 0n,
|
|
1924
|
-
relayer: void 0
|
|
1954
|
+
relayer: void 0,
|
|
1925
1955
|
// Option<H160> — None for direct Substrate submissions
|
|
1956
|
+
circuit_version: params.circuitVersion
|
|
1926
1957
|
});
|
|
1927
1958
|
if (signer) {
|
|
1928
1959
|
return toTxResult(
|
|
@@ -1956,7 +1987,7 @@ var ShieldedPoolModule = class {
|
|
|
1956
1987
|
* This is a SIGNED transaction — the relayer must sign it with their wallet.
|
|
1957
1988
|
* Before calling this, generate a ZK value proof with generateFeeClaimProof() (not yet implemented).
|
|
1958
1989
|
*
|
|
1959
|
-
* Extrinsic: shieldedPool.claim_shielded_fees(commitment, amount, asset_id, memo, proof, public_signals)
|
|
1990
|
+
* Extrinsic: shieldedPool.claim_shielded_fees(commitment, amount, asset_id, memo, proof, public_signals, circuit_version)
|
|
1960
1991
|
*/
|
|
1961
1992
|
async claimShieldedFees(params, signer, txOptions) {
|
|
1962
1993
|
EncryptedMemo.validate(params.encryptedMemo, "claimShieldedFees.encryptedMemo");
|
|
@@ -1967,7 +1998,8 @@ var ShieldedPoolModule = class {
|
|
|
1967
1998
|
asset_id: params.assetId,
|
|
1968
1999
|
encrypted_memo: params.encryptedMemo,
|
|
1969
2000
|
proof: params.proof,
|
|
1970
|
-
public_signals: params.publicSignals
|
|
2001
|
+
public_signals: params.publicSignals,
|
|
2002
|
+
circuit_version: params.circuitVersion
|
|
1971
2003
|
});
|
|
1972
2004
|
return toTxResult(
|
|
1973
2005
|
await (txOptions !== void 0 ? tx.signAndSubmit(signer, txOptions) : tx.signAndSubmit(signer))
|
|
@@ -2485,6 +2517,77 @@ var ZkVerifierModule = class {
|
|
|
2485
2517
|
}
|
|
2486
2518
|
};
|
|
2487
2519
|
|
|
2520
|
+
// src/shielded-pool/CircuitVersionResolver.ts
|
|
2521
|
+
import {
|
|
2522
|
+
WebArtifactProvider,
|
|
2523
|
+
circuitTypeToId
|
|
2524
|
+
} from "@orbinum/proof-generator";
|
|
2525
|
+
var CircuitVersionResolver = class {
|
|
2526
|
+
constructor(zkVerifier, baseUrl, providerFactory) {
|
|
2527
|
+
this.zkVerifier = zkVerifier;
|
|
2528
|
+
this.makeProvider = providerFactory ?? ((circuit, noteVersion) => new WebArtifactProvider({
|
|
2529
|
+
...baseUrl ? { baseUrl } : {},
|
|
2530
|
+
circuitVersions: { [circuit]: noteVersion }
|
|
2531
|
+
}));
|
|
2532
|
+
}
|
|
2533
|
+
zkVerifier;
|
|
2534
|
+
makeProvider;
|
|
2535
|
+
/**
|
|
2536
|
+
* Resolves the prover + on-chain version for spending a note of `circuit`
|
|
2537
|
+
* created under `noteVersion`. Fail-closed: throws on unsupported version or
|
|
2538
|
+
* VK-hash mismatch (CDN vs chain), before generating any proof.
|
|
2539
|
+
*/
|
|
2540
|
+
async resolve(circuit, noteVersion) {
|
|
2541
|
+
if (!Number.isInteger(noteVersion) || noteVersion <= 0) {
|
|
2542
|
+
throw new Error(
|
|
2543
|
+
`CircuitVersionResolver: invalid note circuitVersion ${noteVersion} for ${circuit}`
|
|
2544
|
+
);
|
|
2545
|
+
}
|
|
2546
|
+
const provider = this.makeProvider(circuit, noteVersion);
|
|
2547
|
+
const resolved = await provider.getResolvedVersion(circuit);
|
|
2548
|
+
if (resolved.version !== noteVersion) {
|
|
2549
|
+
throw new Error(
|
|
2550
|
+
`CircuitVersionResolver: prover resolved ${circuit} to v${resolved.version}, expected v${noteVersion}`
|
|
2551
|
+
);
|
|
2552
|
+
}
|
|
2553
|
+
const circuitId = circuitTypeToId(circuit);
|
|
2554
|
+
const info = await this.zkVerifier.getCircuitVersionInfo(circuitId);
|
|
2555
|
+
if (!info) {
|
|
2556
|
+
throw new Error(
|
|
2557
|
+
`CircuitVersionResolver: chain reports no version info for ${circuit} (id ${circuitId})`
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
if (!info.supportedVersions.includes(noteVersion)) {
|
|
2561
|
+
throw new Error(
|
|
2562
|
+
`CircuitVersionResolver: chain does not support ${circuit} v${noteVersion} (supported: ${info.supportedVersions.join(", ")})`
|
|
2563
|
+
);
|
|
2564
|
+
}
|
|
2565
|
+
const chainVk = info.vkHashes.find((h) => h.version === noteVersion);
|
|
2566
|
+
if (!chainVk) {
|
|
2567
|
+
throw new Error(
|
|
2568
|
+
`CircuitVersionResolver: chain has no VK hash for ${circuit} v${noteVersion}`
|
|
2569
|
+
);
|
|
2570
|
+
}
|
|
2571
|
+
if (!vkHashEquals(chainVk.vkHash, resolved.vkHash)) {
|
|
2572
|
+
throw new Error(
|
|
2573
|
+
`CircuitVersionResolver: VK hash mismatch for ${circuit} v${noteVersion} \u2014 prover ${resolved.vkHash}, chain ${chainVk.vkHash}`
|
|
2574
|
+
);
|
|
2575
|
+
}
|
|
2576
|
+
return { provider, version: noteVersion };
|
|
2577
|
+
}
|
|
2578
|
+
};
|
|
2579
|
+
function vkHashEquals(a, b) {
|
|
2580
|
+
const na = normalizeHash(a);
|
|
2581
|
+
const nb = normalizeHash(b);
|
|
2582
|
+
if (na === null || nb === null) return false;
|
|
2583
|
+
return na === nb;
|
|
2584
|
+
}
|
|
2585
|
+
function normalizeHash(h) {
|
|
2586
|
+
if (typeof h !== "string") return null;
|
|
2587
|
+
const s = h.toLowerCase().startsWith("0x") ? h.toLowerCase().slice(2) : h.toLowerCase();
|
|
2588
|
+
return /^[0-9a-f]{64}$/.test(s) ? s : null;
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2488
2591
|
// src/relayer/RelayerStatusModule.ts
|
|
2489
2592
|
var RelayerStatusModule = class {
|
|
2490
2593
|
constructor(substrate) {
|
|
@@ -2730,12 +2833,12 @@ var AM_SEL = {
|
|
|
2730
2833
|
var SP_SEL = {
|
|
2731
2834
|
// shield(uint32,bytes32,bytes) → 0x9feb22ea (payable, amount = msg.value)
|
|
2732
2835
|
SHIELD: new Uint8Array([159, 235, 34, 234]),
|
|
2733
|
-
// privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256)
|
|
2734
|
-
PRIVATE_TRANSFER: new Uint8Array([
|
|
2735
|
-
// unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32)
|
|
2736
|
-
UNSHIELD: new Uint8Array([
|
|
2737
|
-
// claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes)
|
|
2738
|
-
CLAIM_SHIELDED_FEES: new Uint8Array([
|
|
2836
|
+
// privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256,uint32) → 0x66ed2cd4
|
|
2837
|
+
PRIVATE_TRANSFER: new Uint8Array([102, 237, 44, 212]),
|
|
2838
|
+
// unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32,bytes,uint32) → 0x4e505348
|
|
2839
|
+
UNSHIELD: new Uint8Array([78, 80, 83, 72]),
|
|
2840
|
+
// claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes,uint32) → 0x88d9deba
|
|
2841
|
+
CLAIM_SHIELDED_FEES: new Uint8Array([136, 217, 222, 186])
|
|
2739
2842
|
};
|
|
2740
2843
|
var KNOWN_PRECOMPILES = {
|
|
2741
2844
|
// ── Ethereum standard (EIP) ─────────────────────────────────────────────
|
|
@@ -2779,9 +2882,9 @@ var KNOWN_PRECOMPILES = {
|
|
|
2779
2882
|
name: "ShieldedPool",
|
|
2780
2883
|
functions: {
|
|
2781
2884
|
"9feb22ea": "shield(uint32,bytes32,bytes)",
|
|
2782
|
-
"
|
|
2783
|
-
|
|
2784
|
-
"
|
|
2885
|
+
"66ed2cd4": "privateTransfer(bytes,bytes32,bytes32[],bytes32[],bytes[],uint32,uint256,uint32)",
|
|
2886
|
+
"4e505348": "unshield(bytes,bytes32,bytes32,uint32,uint256,bytes32,uint256,bytes32,bytes,uint32)",
|
|
2887
|
+
"88d9deba": "claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes,uint32)"
|
|
2785
2888
|
}
|
|
2786
2889
|
}
|
|
2787
2890
|
};
|
|
@@ -2835,7 +2938,8 @@ var ShieldedPoolPrecompile = class {
|
|
|
2835
2938
|
// ─── privateTransfer ───────────────────────────────────────────────────────
|
|
2836
2939
|
/**
|
|
2837
2940
|
* Returns the ABI-encoded calldata for
|
|
2838
|
-
* `privateTransfer(bytes, bytes32, bytes32[], bytes32[], bytes[], uint32, uint256)`.
|
|
2941
|
+
* `privateTransfer(bytes, bytes32, bytes32[], bytes32[], bytes[], uint32, uint256, uint32)`.
|
|
2942
|
+
* The trailing `uint32` is the circuit version the input notes were created under.
|
|
2839
2943
|
*/
|
|
2840
2944
|
buildPrivateTransferCalldata(params) {
|
|
2841
2945
|
const nullifiers = params.inputs.map((i) => fromHex(i.nullifier));
|
|
@@ -2856,7 +2960,8 @@ var ShieldedPoolPrecompile = class {
|
|
|
2856
2960
|
{ type: "bytes32[]", value: commitments },
|
|
2857
2961
|
{ type: "bytes[]", value: memos },
|
|
2858
2962
|
{ type: "uint", value: BigInt(params.assetId) },
|
|
2859
|
-
{ type: "uint", value: params.fee ?? 0n }
|
|
2963
|
+
{ type: "uint", value: params.fee ?? 0n },
|
|
2964
|
+
{ type: "uint", value: BigInt(params.circuitVersion) }
|
|
2860
2965
|
);
|
|
2861
2966
|
}
|
|
2862
2967
|
/**
|
|
@@ -2897,7 +3002,8 @@ var ShieldedPoolPrecompile = class {
|
|
|
2897
3002
|
{ type: "bytes32", value: recipientBytes },
|
|
2898
3003
|
{ type: "uint", value: params.fee ?? 0n },
|
|
2899
3004
|
{ type: "bytes32", value: changeCommitment },
|
|
2900
|
-
{ type: "bytes", value: changeEncryptedMemo }
|
|
3005
|
+
{ type: "bytes", value: changeEncryptedMemo },
|
|
3006
|
+
{ type: "uint", value: BigInt(params.circuitVersion) }
|
|
2901
3007
|
);
|
|
2902
3008
|
}
|
|
2903
3009
|
/**
|
|
@@ -2947,7 +3053,7 @@ var ShieldedPoolPrecompile = class {
|
|
|
2947
3053
|
// ─── claimShieldedFees ───────────────────────────────────────────────────────────────────
|
|
2948
3054
|
/**
|
|
2949
3055
|
* Returns the ABI-encoded calldata for
|
|
2950
|
-
* `claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes)`.
|
|
3056
|
+
* `claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes,uint32)`.
|
|
2951
3057
|
*
|
|
2952
3058
|
* ABI layout (params after selector):
|
|
2953
3059
|
* - `commitment` — bytes32 (fixed)
|
|
@@ -2956,6 +3062,7 @@ var ShieldedPoolPrecompile = class {
|
|
|
2956
3062
|
* - `memo` — bytes (dynamic)
|
|
2957
3063
|
* - `proof` — bytes (dynamic, 128 bytes Groth16)
|
|
2958
3064
|
* - `publicSignals` — bytes (dynamic, 76 bytes)
|
|
3065
|
+
* - `circuitVersion` — uint32 (fixed, right-aligned)
|
|
2959
3066
|
*
|
|
2960
3067
|
* The validator identity is derived from `msg.sender` in the precompile —
|
|
2961
3068
|
* do NOT include it in the calldata.
|
|
@@ -2981,7 +3088,8 @@ var ShieldedPoolPrecompile = class {
|
|
|
2981
3088
|
{ type: "uint", value: BigInt(params.assetId) },
|
|
2982
3089
|
{ type: "bytes", value: params.encryptedMemo },
|
|
2983
3090
|
{ type: "bytes", value: params.proof },
|
|
2984
|
-
{ type: "bytes", value: params.publicSignals }
|
|
3091
|
+
{ type: "bytes", value: params.publicSignals },
|
|
3092
|
+
{ type: "uint", value: BigInt(params.circuitVersion) }
|
|
2985
3093
|
);
|
|
2986
3094
|
}
|
|
2987
3095
|
/**
|
|
@@ -3419,6 +3527,11 @@ var OrbinumClient = class _OrbinumClient {
|
|
|
3419
3527
|
chain;
|
|
3420
3528
|
/** Typed access to `zkVerifier_*` custom RPC endpoints. */
|
|
3421
3529
|
zkVerifier;
|
|
3530
|
+
/**
|
|
3531
|
+
* Resolves a note's circuit version to a pinned prover + on-chain version
|
|
3532
|
+
* before spending it (fail-closed: throws on unsupported version / VK mismatch).
|
|
3533
|
+
*/
|
|
3534
|
+
circuitVersionResolver;
|
|
3422
3535
|
/** Typed access to `relayer_*` RPC endpoints (registry lookup and pending fee queries). */
|
|
3423
3536
|
relayerStatus;
|
|
3424
3537
|
/**
|
|
@@ -3427,7 +3540,7 @@ var OrbinumClient = class _OrbinumClient {
|
|
|
3427
3540
|
*/
|
|
3428
3541
|
precompiles;
|
|
3429
3542
|
/** @internal Use `OrbinumClient.connect()` to obtain an instance. */
|
|
3430
|
-
constructor(substrate, evm, indexer) {
|
|
3543
|
+
constructor(substrate, evm, indexer, circuitsBaseUrl) {
|
|
3431
3544
|
this.substrate = substrate;
|
|
3432
3545
|
this.evm = evm;
|
|
3433
3546
|
this.evmExplorer = evm ? new EvmExplorer(evm) : null;
|
|
@@ -3437,6 +3550,7 @@ var OrbinumClient = class _OrbinumClient {
|
|
|
3437
3550
|
this.privacy = new PrivacyModule(substrate);
|
|
3438
3551
|
this.chain = new ChainModule(substrate);
|
|
3439
3552
|
this.zkVerifier = new ZkVerifierModule(substrate);
|
|
3553
|
+
this.circuitVersionResolver = new CircuitVersionResolver(this.zkVerifier, circuitsBaseUrl);
|
|
3440
3554
|
this.relayerStatus = new RelayerStatusModule(substrate);
|
|
3441
3555
|
this.precompiles = evm ? {
|
|
3442
3556
|
shieldedPool: new ShieldedPoolPrecompile(evm),
|
|
@@ -3457,7 +3571,7 @@ var OrbinumClient = class _OrbinumClient {
|
|
|
3457
3571
|
);
|
|
3458
3572
|
const evm = config.evmRpc ? new EvmClient(config.evmRpc) : null;
|
|
3459
3573
|
const indexer = config.indexerUrl ? new IndexerClient({ baseUrl: config.indexerUrl }) : null;
|
|
3460
|
-
return new _OrbinumClient(substrate, evm, indexer);
|
|
3574
|
+
return new _OrbinumClient(substrate, evm, indexer, config.circuitsBaseUrl);
|
|
3461
3575
|
}
|
|
3462
3576
|
/** Closes the underlying Substrate WebSocket connection and releases all resources. */
|
|
3463
3577
|
destroy() {
|
|
@@ -3579,6 +3693,8 @@ var OrbinumClientProvider = class {
|
|
|
3579
3693
|
};
|
|
3580
3694
|
if (this.config.evmRpc) connectConfig.evmRpc = this.config.evmRpc;
|
|
3581
3695
|
if (this.config.indexerUrl) connectConfig.indexerUrl = this.config.indexerUrl;
|
|
3696
|
+
if (this.config.circuitsBaseUrl)
|
|
3697
|
+
connectConfig.circuitsBaseUrl = this.config.circuitsBaseUrl;
|
|
3582
3698
|
const clientPromise = OrbinumClient.connect(connectConfig);
|
|
3583
3699
|
const client = await Promise.race([clientPromise, timeoutPromise]);
|
|
3584
3700
|
orphanClient = client;
|
|
@@ -3765,6 +3881,9 @@ var OrbinumClientProvider = class {
|
|
|
3765
3881
|
}
|
|
3766
3882
|
};
|
|
3767
3883
|
|
|
3884
|
+
// src/shielded-pool/protocol/types.ts
|
|
3885
|
+
var CURRENT_CIRCUIT_VERSION = 1;
|
|
3886
|
+
|
|
3768
3887
|
// src/utils/stealth.ts
|
|
3769
3888
|
import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
|
|
3770
3889
|
import { hkdf } from "@noble/hashes/hkdf.js";
|
|
@@ -3873,6 +3992,7 @@ var NoteBuilder = class {
|
|
|
3873
3992
|
const blinding = input.blinding ?? BigInt(Date.now());
|
|
3874
3993
|
const spendingKey = input.spendingKey ?? 0n;
|
|
3875
3994
|
const counterpartyPk = input.counterpartyPk ?? 0n;
|
|
3995
|
+
const circuitVersion = input.circuitVersion ?? CURRENT_CIRCUIT_VERSION;
|
|
3876
3996
|
const useStealth = input.viewingPublicKey !== void 0 && input.recipientOwnerPk !== void 0;
|
|
3877
3997
|
let memo;
|
|
3878
3998
|
if (useStealth) {
|
|
@@ -3907,6 +4027,7 @@ var NoteBuilder = class {
|
|
|
3907
4027
|
stealthCommitmentBytes,
|
|
3908
4028
|
recipientIvkPacked,
|
|
3909
4029
|
bigintTo32Le(counterpartyPk),
|
|
4030
|
+
circuitVersion,
|
|
3910
4031
|
ephSk
|
|
3911
4032
|
)
|
|
3912
4033
|
);
|
|
@@ -3924,6 +4045,7 @@ var NoteBuilder = class {
|
|
|
3924
4045
|
ownerPk: effectiveOwnerPk,
|
|
3925
4046
|
blinding,
|
|
3926
4047
|
spendingKey,
|
|
4048
|
+
circuitVersion,
|
|
3927
4049
|
spent: false,
|
|
3928
4050
|
spentAt: null,
|
|
3929
4051
|
commitment: commitment2,
|
|
@@ -3946,7 +4068,8 @@ var NoteBuilder = class {
|
|
|
3946
4068
|
Number(assetId),
|
|
3947
4069
|
commitmentBytes,
|
|
3948
4070
|
input.viewingPublicKey,
|
|
3949
|
-
bigintTo32Le(counterpartyPk)
|
|
4071
|
+
bigintTo32Le(counterpartyPk),
|
|
4072
|
+
circuitVersion
|
|
3950
4073
|
)
|
|
3951
4074
|
) : Array.from(EncryptedMemo.dummy());
|
|
3952
4075
|
if (memo.length !== ENCRYPTED_MEMO_SIZE)
|
|
@@ -3959,6 +4082,7 @@ var NoteBuilder = class {
|
|
|
3959
4082
|
ownerPk,
|
|
3960
4083
|
blinding,
|
|
3961
4084
|
spendingKey,
|
|
4085
|
+
circuitVersion,
|
|
3962
4086
|
spent: false,
|
|
3963
4087
|
spentAt: null,
|
|
3964
4088
|
commitment,
|
|
@@ -3970,7 +4094,7 @@ var NoteBuilder = class {
|
|
|
3970
4094
|
};
|
|
3971
4095
|
}
|
|
3972
4096
|
/**
|
|
3973
|
-
* Build the
|
|
4097
|
+
* Build the 180-byte ECDH-encrypted memo for a note.
|
|
3974
4098
|
*
|
|
3975
4099
|
* Pure TypeScript implementation — no WASM dependency.
|
|
3976
4100
|
* Uses ChaCha20-Poly1305 with ECDH key agreement (BabyJubJub ephemeral keypair).
|
|
@@ -3989,7 +4113,8 @@ var NoteBuilder = class {
|
|
|
3989
4113
|
Number(note.assetId),
|
|
3990
4114
|
bigintTo32Le(note.commitment),
|
|
3991
4115
|
recipientIvkPacked ?? new Uint8Array(32),
|
|
3992
|
-
counterpartyPk ?? bigintTo32Le(note.counterpartyPk ?? 0n)
|
|
4116
|
+
counterpartyPk ?? bigintTo32Le(note.counterpartyPk ?? 0n),
|
|
4117
|
+
note.circuitVersion
|
|
3993
4118
|
);
|
|
3994
4119
|
}
|
|
3995
4120
|
};
|
|
@@ -4051,6 +4176,7 @@ function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwn
|
|
|
4051
4176
|
ownerPk: effectiveOwnerPk,
|
|
4052
4177
|
blinding: plaintext.blinding,
|
|
4053
4178
|
spendingKey: effectiveSpendingKey,
|
|
4179
|
+
circuitVersion: plaintext.circuitVersion,
|
|
4054
4180
|
spent: false,
|
|
4055
4181
|
spentAt: null,
|
|
4056
4182
|
commitment: recomputed,
|
|
@@ -4124,7 +4250,7 @@ function selectNotes(notes, needed) {
|
|
|
4124
4250
|
for (let j = i + 1; j < sorted.length; j++) {
|
|
4125
4251
|
const a = sorted[i];
|
|
4126
4252
|
const b = sorted[j];
|
|
4127
|
-
if (a !== void 0 && b !== void 0 && a.value + b.value >= needed) {
|
|
4253
|
+
if (a !== void 0 && b !== void 0 && a.circuitVersion === b.circuitVersion && a.value + b.value >= needed) {
|
|
4128
4254
|
return [a, b];
|
|
4129
4255
|
}
|
|
4130
4256
|
}
|
|
@@ -4481,6 +4607,11 @@ async function encryptNote(key, blindKey, note) {
|
|
|
4481
4607
|
}
|
|
4482
4608
|
async function decryptNoteRecord(key, rec) {
|
|
4483
4609
|
const note = await decryptJson(key, rec.iv, rec.ciphertext);
|
|
4610
|
+
if (typeof note.circuitVersion !== "number") {
|
|
4611
|
+
throw new Error(
|
|
4612
|
+
"decryptNoteRecord: note is missing circuitVersion (invalid or corrupt record)"
|
|
4613
|
+
);
|
|
4614
|
+
}
|
|
4484
4615
|
return applyNoteStatus(note, {
|
|
4485
4616
|
...rec.spent !== void 0 && { spent: rec.spent },
|
|
4486
4617
|
spentAt: rec.spentAt ?? null
|
|
@@ -4492,9 +4623,9 @@ async function noteBlindTag(blindKey, hex) {
|
|
|
4492
4623
|
|
|
4493
4624
|
// src/proof-generator/unshield.ts
|
|
4494
4625
|
import {
|
|
4495
|
-
CircuitType,
|
|
4626
|
+
CircuitType as CircuitType2,
|
|
4496
4627
|
generateProof,
|
|
4497
|
-
WebArtifactProvider
|
|
4628
|
+
WebArtifactProvider as WebArtifactProvider2
|
|
4498
4629
|
} from "@orbinum/proof-generator";
|
|
4499
4630
|
import { randomBytes as randomBytes3 } from "@noble/ciphers/utils.js";
|
|
4500
4631
|
import { mulPointEscalar as mulPointEscalar6, Base8 as Base84 } from "@zk-kit/baby-jubjub";
|
|
@@ -4541,18 +4672,18 @@ async function generateUnshieldProof(inputs, options = {}) {
|
|
|
4541
4672
|
change_blinding: changeBlinding.toString(),
|
|
4542
4673
|
change_owner_pubkey: changeOwnerPubkey.toString()
|
|
4543
4674
|
};
|
|
4544
|
-
const provider = options.provider ?? new
|
|
4675
|
+
const provider = options.provider ?? new WebArtifactProvider2();
|
|
4545
4676
|
const opts = { provider };
|
|
4546
4677
|
if (options.verbose !== void 0) opts.verbose = options.verbose;
|
|
4547
|
-
const proofResult = await generateProof(
|
|
4678
|
+
const proofResult = await generateProof(CircuitType2.Unshield, circuitInputs, opts);
|
|
4548
4679
|
return { ...proofResult, changeCommitment, changeValue, changeBlinding, changeOwnerPubkey };
|
|
4549
4680
|
}
|
|
4550
4681
|
|
|
4551
4682
|
// src/proof-generator/transfer.ts
|
|
4552
4683
|
import {
|
|
4553
|
-
CircuitType as
|
|
4684
|
+
CircuitType as CircuitType3,
|
|
4554
4685
|
generateProof as generateProof2,
|
|
4555
|
-
WebArtifactProvider as
|
|
4686
|
+
WebArtifactProvider as WebArtifactProvider3
|
|
4556
4687
|
} from "@orbinum/proof-generator";
|
|
4557
4688
|
async function generateTransferProof(params, options = {}) {
|
|
4558
4689
|
const root = leHexToBigint(params.merkleRoot).toString();
|
|
@@ -4578,17 +4709,17 @@ async function generateTransferProof(params, options = {}) {
|
|
|
4578
4709
|
output_owner_pubkeys: [o0.ownerPk.toString(), o1.ownerPk.toString()],
|
|
4579
4710
|
output_blindings: [o0.blinding.toString(), o1.blinding.toString()]
|
|
4580
4711
|
};
|
|
4581
|
-
const provider = options.provider ?? new
|
|
4712
|
+
const provider = options.provider ?? new WebArtifactProvider3();
|
|
4582
4713
|
const opts = { provider };
|
|
4583
4714
|
if (options.verbose !== void 0) opts.verbose = options.verbose;
|
|
4584
|
-
return generateProof2(
|
|
4715
|
+
return generateProof2(CircuitType3.Transfer, circuitInputs, opts);
|
|
4585
4716
|
}
|
|
4586
4717
|
|
|
4587
4718
|
// src/proof-generator/fee-claim.ts
|
|
4588
4719
|
import {
|
|
4589
|
-
CircuitType as
|
|
4720
|
+
CircuitType as CircuitType4,
|
|
4590
4721
|
generateProof as generateProof3,
|
|
4591
|
-
WebArtifactProvider as
|
|
4722
|
+
WebArtifactProvider as WebArtifactProvider4
|
|
4592
4723
|
} from "@orbinum/proof-generator";
|
|
4593
4724
|
async function generateFeeClaimProof(inputs, options = {}) {
|
|
4594
4725
|
if (inputs.amount <= 0n) {
|
|
@@ -4601,10 +4732,10 @@ async function generateFeeClaimProof(inputs, options = {}) {
|
|
|
4601
4732
|
owner_pubkey: inputs.ownerPubkey.toString(),
|
|
4602
4733
|
blinding: inputs.blinding.toString()
|
|
4603
4734
|
};
|
|
4604
|
-
const provider = options.provider ?? new
|
|
4735
|
+
const provider = options.provider ?? new WebArtifactProvider4();
|
|
4605
4736
|
const opts = { provider };
|
|
4606
4737
|
if (options.verbose !== void 0) opts.verbose = options.verbose;
|
|
4607
|
-
const proofResult = await generateProof3(
|
|
4738
|
+
const proofResult = await generateProof3(CircuitType4.ValueProof, circuitInputs, opts);
|
|
4608
4739
|
const [sigCommitment, sigValue, sigAssetId, sigOwnerHash] = proofResult.publicSignals.map(BigInt);
|
|
4609
4740
|
const buf = new Uint8Array(76);
|
|
4610
4741
|
buf.set(bigintTo32Le(sigCommitment), 0);
|
|
@@ -4660,9 +4791,19 @@ function decodePrecompileCalldata(address, input) {
|
|
|
4660
4791
|
const recipient = toHex(data.slice(160, 192));
|
|
4661
4792
|
const fee = decodeUint(data, 192);
|
|
4662
4793
|
const changeCommitment = toHex(data.slice(224, 256));
|
|
4794
|
+
const circuitVersion = decodeUint(data, 288);
|
|
4663
4795
|
return {
|
|
4664
4796
|
fnSig,
|
|
4665
|
-
args: {
|
|
4797
|
+
args: {
|
|
4798
|
+
root,
|
|
4799
|
+
nullifier,
|
|
4800
|
+
assetId,
|
|
4801
|
+
amount,
|
|
4802
|
+
recipient,
|
|
4803
|
+
fee,
|
|
4804
|
+
changeCommitment,
|
|
4805
|
+
circuitVersion
|
|
4806
|
+
}
|
|
4666
4807
|
};
|
|
4667
4808
|
} catch {
|
|
4668
4809
|
return { fnSig, args: {} };
|
|
@@ -4678,7 +4819,8 @@ function decodePrecompileCalldata(address, input) {
|
|
|
4678
4819
|
const commitments = Number(decodeUint(data, commOffset));
|
|
4679
4820
|
const assetId = decodeUint(data, 160);
|
|
4680
4821
|
const fee = decodeUint(data, 192);
|
|
4681
|
-
|
|
4822
|
+
const circuitVersion = decodeUint(data, 224);
|
|
4823
|
+
return { fnSig, args: { root, nullifiers, commitments, assetId, fee, circuitVersion } };
|
|
4682
4824
|
} catch {
|
|
4683
4825
|
return { fnSig, args: {} };
|
|
4684
4826
|
}
|
|
@@ -4689,7 +4831,8 @@ function decodePrecompileCalldata(address, input) {
|
|
|
4689
4831
|
const commitment = toHex(data.slice(0, 32));
|
|
4690
4832
|
const amount = decodeUint(data, 32);
|
|
4691
4833
|
const assetId = decodeUint(data, 64);
|
|
4692
|
-
|
|
4834
|
+
const circuitVersion = decodeUint(data, 192);
|
|
4835
|
+
return { fnSig, args: { commitment, amount, assetId, circuitVersion } };
|
|
4693
4836
|
} catch {
|
|
4694
4837
|
return { fnSig, args: {} };
|
|
4695
4838
|
}
|
|
@@ -4701,8 +4844,8 @@ function decodePrecompileCalldata(address, input) {
|
|
|
4701
4844
|
var CircuitId = {
|
|
4702
4845
|
Transfer: 1,
|
|
4703
4846
|
Unshield: 2,
|
|
4704
|
-
|
|
4705
|
-
|
|
4847
|
+
PrivateLink: 5,
|
|
4848
|
+
ValueProof: 6
|
|
4706
4849
|
};
|
|
4707
4850
|
|
|
4708
4851
|
// src/utils/string.ts
|
|
@@ -5384,8 +5527,10 @@ export {
|
|
|
5384
5527
|
BABYJUB_SUBORDER,
|
|
5385
5528
|
BN254_R,
|
|
5386
5529
|
Blake2256,
|
|
5530
|
+
CURRENT_CIRCUIT_VERSION,
|
|
5387
5531
|
CircuitId,
|
|
5388
|
-
CircuitType,
|
|
5532
|
+
CircuitType2 as CircuitType,
|
|
5533
|
+
CircuitVersionResolver,
|
|
5389
5534
|
CryptoPrecompiles,
|
|
5390
5535
|
ENCRYPTED_MEMO_SIZE,
|
|
5391
5536
|
EncryptedMemo,
|
|
@@ -5408,7 +5553,7 @@ export {
|
|
|
5408
5553
|
Storage,
|
|
5409
5554
|
SubstrateClient,
|
|
5410
5555
|
VaultLockedError,
|
|
5411
|
-
WebArtifactProvider,
|
|
5556
|
+
WebArtifactProvider2 as WebArtifactProvider,
|
|
5412
5557
|
ZkVerifierModule,
|
|
5413
5558
|
accountIdHexToSs58,
|
|
5414
5559
|
addressToAccountIdHex,
|