@entros/pulse-sdk 3.1.0 → 3.3.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/README.md +1 -1
- package/dist/index.d.mts +182 -18
- package/dist/index.d.ts +182 -18
- package/dist/index.js +641 -27
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +628 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2156,12 +2156,6 @@ var entros_anchor_default = {
|
|
|
2156
2156
|
spec: "0.1.0",
|
|
2157
2157
|
description: "Non-transferable identity token for Entros Protocol"
|
|
2158
2158
|
},
|
|
2159
|
-
docs: [
|
|
2160
|
-
"Mint account space for Token-2022 with NonTransferable extension.",
|
|
2161
|
-
"Base mint = 82 bytes, account type = 1 byte, extension type (2) + length (2) = 4 bytes,",
|
|
2162
|
-
"NonTransferable data = 0 bytes. Plus multisig padding from Token-2022.",
|
|
2163
|
-
"We use a constant derived from the Token-2022 spec."
|
|
2164
|
-
],
|
|
2165
2159
|
instructions: [
|
|
2166
2160
|
{
|
|
2167
2161
|
name: "authorize_new_wallet",
|
|
@@ -3143,6 +3137,132 @@ var entros_anchor_default = {
|
|
|
3143
3137
|
}
|
|
3144
3138
|
]
|
|
3145
3139
|
},
|
|
3140
|
+
{
|
|
3141
|
+
name: "set_encrypted_baseline",
|
|
3142
|
+
docs: [
|
|
3143
|
+
"Write or overwrite the caller's encrypted baseline blob.",
|
|
3144
|
+
"",
|
|
3145
|
+
"The blob is opaque to the program \u2014 AES-256-GCM ciphertext of the",
|
|
3146
|
+
"user's previous SimHash plus salt, produced off-chain in the SDK",
|
|
3147
|
+
"under a key derived from a deterministic `signMessage`. The GCM",
|
|
3148
|
+
"auth tag binds the blob to (wallet, this PDA's address, on-chain",
|
|
3149
|
+
"`current_commitment` at encryption time), so a stale blob produced",
|
|
3150
|
+
"before a `reset_identity_state` fails authentication under the new",
|
|
3151
|
+
"commitment and the SDK falls back to a fresh-capture flow.",
|
|
3152
|
+
"",
|
|
3153
|
+
"The program never decrypts the blob \u2014 it only stores opaque bytes.",
|
|
3154
|
+
"Plaintext biometric data never reaches chain at any point.",
|
|
3155
|
+
"",
|
|
3156
|
+
"Guards:",
|
|
3157
|
+
"* Signer must equal the wallet that seeds the EncryptedBaseline",
|
|
3158
|
+
"PDA (enforced by the seeds constraint on the Accounts struct).",
|
|
3159
|
+
'* The caller\'s IdentityState PDA at `[b"identity", signer]` must',
|
|
3160
|
+
"already exist \u2014 pre-mint attempts are rejected with",
|
|
3161
|
+
"`IdentityStateNotFound`. Anchor's seeds constraint validates the",
|
|
3162
|
+
"PDA address; the `data_len() > 0` check confirms initialization."
|
|
3163
|
+
],
|
|
3164
|
+
discriminator: [
|
|
3165
|
+
10,
|
|
3166
|
+
73,
|
|
3167
|
+
41,
|
|
3168
|
+
36,
|
|
3169
|
+
2,
|
|
3170
|
+
145,
|
|
3171
|
+
87,
|
|
3172
|
+
111
|
|
3173
|
+
],
|
|
3174
|
+
accounts: [
|
|
3175
|
+
{
|
|
3176
|
+
name: "authority",
|
|
3177
|
+
writable: true,
|
|
3178
|
+
signer: true
|
|
3179
|
+
},
|
|
3180
|
+
{
|
|
3181
|
+
name: "identity_state",
|
|
3182
|
+
docs: [
|
|
3183
|
+
"UncheckedAccount because we only need to verify the IdentityState",
|
|
3184
|
+
"PDA exists at the expected address \u2014 we don't need to deserialize",
|
|
3185
|
+
"its fields. The seeds constraint validates the PDA address."
|
|
3186
|
+
],
|
|
3187
|
+
pda: {
|
|
3188
|
+
seeds: [
|
|
3189
|
+
{
|
|
3190
|
+
kind: "const",
|
|
3191
|
+
value: [
|
|
3192
|
+
105,
|
|
3193
|
+
100,
|
|
3194
|
+
101,
|
|
3195
|
+
110,
|
|
3196
|
+
116,
|
|
3197
|
+
105,
|
|
3198
|
+
116,
|
|
3199
|
+
121
|
|
3200
|
+
]
|
|
3201
|
+
},
|
|
3202
|
+
{
|
|
3203
|
+
kind: "account",
|
|
3204
|
+
path: "authority"
|
|
3205
|
+
}
|
|
3206
|
+
]
|
|
3207
|
+
}
|
|
3208
|
+
},
|
|
3209
|
+
{
|
|
3210
|
+
name: "encrypted_baseline",
|
|
3211
|
+
docs: [
|
|
3212
|
+
"The EncryptedBaseline PDA. Created on first call, overwritten on",
|
|
3213
|
+
"subsequent calls. PDA seeds bind to the signer's wallet, so only",
|
|
3214
|
+
"the wallet owner can write to their own baseline."
|
|
3215
|
+
],
|
|
3216
|
+
writable: true,
|
|
3217
|
+
pda: {
|
|
3218
|
+
seeds: [
|
|
3219
|
+
{
|
|
3220
|
+
kind: "const",
|
|
3221
|
+
value: [
|
|
3222
|
+
101,
|
|
3223
|
+
110,
|
|
3224
|
+
99,
|
|
3225
|
+
114,
|
|
3226
|
+
121,
|
|
3227
|
+
112,
|
|
3228
|
+
116,
|
|
3229
|
+
101,
|
|
3230
|
+
100,
|
|
3231
|
+
95,
|
|
3232
|
+
98,
|
|
3233
|
+
97,
|
|
3234
|
+
115,
|
|
3235
|
+
101,
|
|
3236
|
+
108,
|
|
3237
|
+
105,
|
|
3238
|
+
110,
|
|
3239
|
+
101
|
|
3240
|
+
]
|
|
3241
|
+
},
|
|
3242
|
+
{
|
|
3243
|
+
kind: "account",
|
|
3244
|
+
path: "authority"
|
|
3245
|
+
}
|
|
3246
|
+
]
|
|
3247
|
+
}
|
|
3248
|
+
},
|
|
3249
|
+
{
|
|
3250
|
+
name: "system_program",
|
|
3251
|
+
address: "11111111111111111111111111111111"
|
|
3252
|
+
}
|
|
3253
|
+
],
|
|
3254
|
+
args: [
|
|
3255
|
+
{
|
|
3256
|
+
name: "blob",
|
|
3257
|
+
type: {
|
|
3258
|
+
array: [
|
|
3259
|
+
"u8",
|
|
3260
|
+
96
|
|
3261
|
+
]
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
]
|
|
3265
|
+
},
|
|
3146
3266
|
{
|
|
3147
3267
|
name: "update_anchor",
|
|
3148
3268
|
docs: [
|
|
@@ -3446,6 +3566,19 @@ var entros_anchor_default = {
|
|
|
3446
3566
|
}
|
|
3447
3567
|
],
|
|
3448
3568
|
accounts: [
|
|
3569
|
+
{
|
|
3570
|
+
name: "EncryptedBaseline",
|
|
3571
|
+
discriminator: [
|
|
3572
|
+
235,
|
|
3573
|
+
60,
|
|
3574
|
+
246,
|
|
3575
|
+
174,
|
|
3576
|
+
131,
|
|
3577
|
+
9,
|
|
3578
|
+
248,
|
|
3579
|
+
146
|
|
3580
|
+
]
|
|
3581
|
+
},
|
|
3449
3582
|
{
|
|
3450
3583
|
name: "IdentityState",
|
|
3451
3584
|
discriminator: [
|
|
@@ -3500,6 +3633,19 @@ var entros_anchor_default = {
|
|
|
3500
3633
|
59
|
|
3501
3634
|
]
|
|
3502
3635
|
},
|
|
3636
|
+
{
|
|
3637
|
+
name: "EncryptedBaselineSet",
|
|
3638
|
+
discriminator: [
|
|
3639
|
+
198,
|
|
3640
|
+
176,
|
|
3641
|
+
56,
|
|
3642
|
+
167,
|
|
3643
|
+
74,
|
|
3644
|
+
225,
|
|
3645
|
+
138,
|
|
3646
|
+
121
|
|
3647
|
+
]
|
|
3648
|
+
},
|
|
3503
3649
|
{
|
|
3504
3650
|
name: "MigrateIdentityEvent",
|
|
3505
3651
|
discriminator: [
|
|
@@ -3624,6 +3770,11 @@ var entros_anchor_default = {
|
|
|
3624
3770
|
code: 6021,
|
|
3625
3771
|
name: "MalformedReceiptMessage",
|
|
3626
3772
|
msg: "Receipt message has malformed length or layout"
|
|
3773
|
+
},
|
|
3774
|
+
{
|
|
3775
|
+
code: 6022,
|
|
3776
|
+
name: "IdentityStateNotFound",
|
|
3777
|
+
msg: "set_encrypted_baseline called before mint_anchor \u2014 IdentityState PDA does not exist"
|
|
3627
3778
|
}
|
|
3628
3779
|
],
|
|
3629
3780
|
types: [
|
|
@@ -3706,6 +3857,64 @@ var entros_anchor_default = {
|
|
|
3706
3857
|
]
|
|
3707
3858
|
}
|
|
3708
3859
|
},
|
|
3860
|
+
{
|
|
3861
|
+
name: "EncryptedBaseline",
|
|
3862
|
+
docs: [
|
|
3863
|
+
"Wallet-keyed encrypted baseline blob, stored at PDA seeds",
|
|
3864
|
+
'`[b"encrypted_baseline", wallet.key().as_ref()]`. Persists the user\'s',
|
|
3865
|
+
"previous SimHash + salt across cache wipes and device changes so the",
|
|
3866
|
+
"Hamming-distance ZK proof can recover its private witnesses on any",
|
|
3867
|
+
"device with the originating wallet.",
|
|
3868
|
+
"",
|
|
3869
|
+
"The blob is opaque ciphertext to the program \u2014 AES-256-GCM produced",
|
|
3870
|
+
"off-chain in the SDK under a key derived from a deterministic",
|
|
3871
|
+
"`signMessage` on a domain-separated payload. The GCM AAD binds the",
|
|
3872
|
+
"blob to (wallet, this PDA's address, current on-chain commitment),",
|
|
3873
|
+
"so a stale blob (post-`reset_identity_state`) fails authentication",
|
|
3874
|
+
"against the new commitment and the SDK falls back to a fresh-capture",
|
|
3875
|
+
"flow.",
|
|
3876
|
+
"",
|
|
3877
|
+
"The program never decrypts the blob \u2014 it only stores opaque bytes.",
|
|
3878
|
+
"Plaintext biometric data never reaches chain at any point."
|
|
3879
|
+
],
|
|
3880
|
+
type: {
|
|
3881
|
+
kind: "struct",
|
|
3882
|
+
fields: [
|
|
3883
|
+
{
|
|
3884
|
+
name: "blob",
|
|
3885
|
+
docs: [
|
|
3886
|
+
"96-byte versioned ciphertext envelope.",
|
|
3887
|
+
"Layout: version(1) || algo(1) || reserved(2) || iv(12) || ct+tag(80)."
|
|
3888
|
+
],
|
|
3889
|
+
type: {
|
|
3890
|
+
array: [
|
|
3891
|
+
"u8",
|
|
3892
|
+
96
|
|
3893
|
+
]
|
|
3894
|
+
}
|
|
3895
|
+
},
|
|
3896
|
+
{
|
|
3897
|
+
name: "bump",
|
|
3898
|
+
docs: [
|
|
3899
|
+
"PDA bump seed."
|
|
3900
|
+
],
|
|
3901
|
+
type: "u8"
|
|
3902
|
+
}
|
|
3903
|
+
]
|
|
3904
|
+
}
|
|
3905
|
+
},
|
|
3906
|
+
{
|
|
3907
|
+
name: "EncryptedBaselineSet",
|
|
3908
|
+
type: {
|
|
3909
|
+
kind: "struct",
|
|
3910
|
+
fields: [
|
|
3911
|
+
{
|
|
3912
|
+
name: "owner",
|
|
3913
|
+
type: "pubkey"
|
|
3914
|
+
}
|
|
3915
|
+
]
|
|
3916
|
+
}
|
|
3917
|
+
},
|
|
3709
3918
|
{
|
|
3710
3919
|
name: "IdentityState",
|
|
3711
3920
|
type: {
|
|
@@ -4435,7 +4644,250 @@ async function buildEd25519ReceiptIx(receipt) {
|
|
|
4435
4644
|
});
|
|
4436
4645
|
}
|
|
4437
4646
|
|
|
4647
|
+
// src/identity/baseline.ts
|
|
4648
|
+
var BLOB_VERSION = 1;
|
|
4649
|
+
var ALGORITHM_AES_256_GCM = 1;
|
|
4650
|
+
var ENCRYPTED_BASELINE_BLOB_BYTES = 96;
|
|
4651
|
+
var IV_BYTES = 12;
|
|
4652
|
+
var HEADER_BYTES = 4;
|
|
4653
|
+
var PLAINTEXT_BYTES = 64;
|
|
4654
|
+
function fingerprintToBytes(bits) {
|
|
4655
|
+
if (bits.length !== 256) {
|
|
4656
|
+
throw new Error(`expected 256-bit fingerprint, got ${bits.length}`);
|
|
4657
|
+
}
|
|
4658
|
+
const out = new Uint8Array(32);
|
|
4659
|
+
for (let i = 0; i < 256; i++) {
|
|
4660
|
+
if (bits[i] === 1) {
|
|
4661
|
+
out[i >> 3] = (out[i >> 3] ?? 0) | 1 << (i & 7);
|
|
4662
|
+
}
|
|
4663
|
+
}
|
|
4664
|
+
return out;
|
|
4665
|
+
}
|
|
4666
|
+
function bytesToFingerprint(bytes) {
|
|
4667
|
+
if (bytes.length !== 32) {
|
|
4668
|
+
throw new Error(`expected 32 bytes, got ${bytes.length}`);
|
|
4669
|
+
}
|
|
4670
|
+
const bits = new Array(256);
|
|
4671
|
+
for (let i = 0; i < 256; i++) {
|
|
4672
|
+
bits[i] = (bytes[i >> 3] ?? 0) >> (i & 7) & 1;
|
|
4673
|
+
}
|
|
4674
|
+
return bits;
|
|
4675
|
+
}
|
|
4676
|
+
function bytes32ToBigint(bytes) {
|
|
4677
|
+
if (bytes.length !== 32) {
|
|
4678
|
+
throw new Error(`expected 32 bytes, got ${bytes.length}`);
|
|
4679
|
+
}
|
|
4680
|
+
let val = BigInt(0);
|
|
4681
|
+
for (let i = 0; i < 32; i++) {
|
|
4682
|
+
val = val << BigInt(8) | BigInt(bytes[i] ?? 0);
|
|
4683
|
+
}
|
|
4684
|
+
return val;
|
|
4685
|
+
}
|
|
4686
|
+
function buildDomainMessage(walletPubkey) {
|
|
4687
|
+
return [
|
|
4688
|
+
"Entros Protocol \u2014 Identity Baseline Key Derivation",
|
|
4689
|
+
"",
|
|
4690
|
+
"By signing this message, you authorize Entros Protocol to derive",
|
|
4691
|
+
"an encryption key for your on-chain identity baseline. This is",
|
|
4692
|
+
"not a transaction.",
|
|
4693
|
+
"",
|
|
4694
|
+
`Wallet: ${walletPubkey.toBase58()}`,
|
|
4695
|
+
"Version: 1",
|
|
4696
|
+
"Domain: entros.io"
|
|
4697
|
+
].join("\n");
|
|
4698
|
+
}
|
|
4699
|
+
async function deriveEncryptedBaselinePda(walletPubkey) {
|
|
4700
|
+
const { PublicKey: PK } = await import("@solana/web3.js");
|
|
4701
|
+
const programId = new PK(PROGRAM_IDS.entrosAnchor);
|
|
4702
|
+
return PK.findProgramAddressSync(
|
|
4703
|
+
[new TextEncoder().encode("encrypted_baseline"), walletPubkey.toBuffer()],
|
|
4704
|
+
programId
|
|
4705
|
+
);
|
|
4706
|
+
}
|
|
4707
|
+
async function deriveBaselineKey(wallet) {
|
|
4708
|
+
if (typeof wallet.signMessage !== "function") {
|
|
4709
|
+
throw new Error("wallet does not support signMessage");
|
|
4710
|
+
}
|
|
4711
|
+
const message = buildDomainMessage(wallet.publicKey);
|
|
4712
|
+
const signature = await wallet.signMessage(new TextEncoder().encode(message));
|
|
4713
|
+
if (!(signature instanceof Uint8Array) || signature.length !== 64) {
|
|
4714
|
+
throw new Error(
|
|
4715
|
+
`expected 64-byte Ed25519 signature, got ${signature?.length ?? "non-Uint8Array"}`
|
|
4716
|
+
);
|
|
4717
|
+
}
|
|
4718
|
+
const ikm = await crypto.subtle.importKey(
|
|
4719
|
+
"raw",
|
|
4720
|
+
signature,
|
|
4721
|
+
"HKDF",
|
|
4722
|
+
false,
|
|
4723
|
+
["deriveBits"]
|
|
4724
|
+
);
|
|
4725
|
+
const bits = await crypto.subtle.deriveBits(
|
|
4726
|
+
{
|
|
4727
|
+
name: "HKDF",
|
|
4728
|
+
hash: "SHA-256",
|
|
4729
|
+
salt: wallet.publicKey.toBytes(),
|
|
4730
|
+
info: new TextEncoder().encode("entros-protocol/identity-baseline/v1")
|
|
4731
|
+
},
|
|
4732
|
+
ikm,
|
|
4733
|
+
256
|
|
4734
|
+
);
|
|
4735
|
+
return crypto.subtle.importKey(
|
|
4736
|
+
"raw",
|
|
4737
|
+
bits,
|
|
4738
|
+
{ name: "AES-GCM" },
|
|
4739
|
+
false,
|
|
4740
|
+
["encrypt", "decrypt"]
|
|
4741
|
+
);
|
|
4742
|
+
}
|
|
4743
|
+
var baselineKeyCache = /* @__PURE__ */ new Map();
|
|
4744
|
+
async function getOrDeriveBaselineKey(wallet) {
|
|
4745
|
+
const pubkeyBase58 = wallet.publicKey.toBase58();
|
|
4746
|
+
const cached = baselineKeyCache.get(pubkeyBase58);
|
|
4747
|
+
if (cached) return cached;
|
|
4748
|
+
const pending = (async () => {
|
|
4749
|
+
try {
|
|
4750
|
+
return await deriveBaselineKey(wallet);
|
|
4751
|
+
} catch (err) {
|
|
4752
|
+
baselineKeyCache.delete(pubkeyBase58);
|
|
4753
|
+
throw err;
|
|
4754
|
+
}
|
|
4755
|
+
})();
|
|
4756
|
+
baselineKeyCache.set(pubkeyBase58, pending);
|
|
4757
|
+
return pending;
|
|
4758
|
+
}
|
|
4759
|
+
function clearBaselineKeyCache() {
|
|
4760
|
+
baselineKeyCache.clear();
|
|
4761
|
+
}
|
|
4762
|
+
function buildAAD(walletPubkey, baselinePda, commitment) {
|
|
4763
|
+
if (commitment.length !== 32) {
|
|
4764
|
+
throw new Error(`commitment must be 32 bytes, got ${commitment.length}`);
|
|
4765
|
+
}
|
|
4766
|
+
const aad = new Uint8Array(98);
|
|
4767
|
+
aad[0] = BLOB_VERSION;
|
|
4768
|
+
aad[1] = ALGORITHM_AES_256_GCM;
|
|
4769
|
+
aad.set(walletPubkey.toBytes(), 2);
|
|
4770
|
+
aad.set(baselinePda.toBytes(), 34);
|
|
4771
|
+
aad.set(commitment, 66);
|
|
4772
|
+
return aad;
|
|
4773
|
+
}
|
|
4774
|
+
async function encryptBaselineBlob(simhash2, salt, key, walletPubkey, baselinePda, commitment) {
|
|
4775
|
+
if (simhash2.length !== 32) {
|
|
4776
|
+
throw new Error(`simhash must be 32 bytes, got ${simhash2.length}`);
|
|
4777
|
+
}
|
|
4778
|
+
if (salt.length !== 32) {
|
|
4779
|
+
throw new Error(`salt must be 32 bytes, got ${salt.length}`);
|
|
4780
|
+
}
|
|
4781
|
+
const plaintext = new Uint8Array(PLAINTEXT_BYTES);
|
|
4782
|
+
plaintext.set(simhash2, 0);
|
|
4783
|
+
plaintext.set(salt, 32);
|
|
4784
|
+
const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES));
|
|
4785
|
+
const aad = buildAAD(walletPubkey, baselinePda, commitment);
|
|
4786
|
+
const ciphertextWithTag = new Uint8Array(
|
|
4787
|
+
await crypto.subtle.encrypt(
|
|
4788
|
+
{
|
|
4789
|
+
name: "AES-GCM",
|
|
4790
|
+
iv,
|
|
4791
|
+
additionalData: aad
|
|
4792
|
+
},
|
|
4793
|
+
key,
|
|
4794
|
+
plaintext
|
|
4795
|
+
)
|
|
4796
|
+
);
|
|
4797
|
+
const blob = new Uint8Array(ENCRYPTED_BASELINE_BLOB_BYTES);
|
|
4798
|
+
blob[0] = BLOB_VERSION;
|
|
4799
|
+
blob[1] = ALGORITHM_AES_256_GCM;
|
|
4800
|
+
blob.set(iv, HEADER_BYTES);
|
|
4801
|
+
blob.set(ciphertextWithTag, HEADER_BYTES + IV_BYTES);
|
|
4802
|
+
return blob;
|
|
4803
|
+
}
|
|
4804
|
+
var StaleEncryptedBaselineError = class extends Error {
|
|
4805
|
+
constructor(message) {
|
|
4806
|
+
super(message);
|
|
4807
|
+
this.name = "StaleEncryptedBaselineError";
|
|
4808
|
+
}
|
|
4809
|
+
};
|
|
4810
|
+
async function decryptBaselineBlob(blob, key, walletPubkey, baselinePda, commitment) {
|
|
4811
|
+
if (commitment.length !== 32) {
|
|
4812
|
+
throw new Error(`commitment must be 32 bytes, got ${commitment.length}`);
|
|
4813
|
+
}
|
|
4814
|
+
if (blob.length !== ENCRYPTED_BASELINE_BLOB_BYTES) {
|
|
4815
|
+
throw new Error(
|
|
4816
|
+
`blob must be ${ENCRYPTED_BASELINE_BLOB_BYTES} bytes, got ${blob.length}`
|
|
4817
|
+
);
|
|
4818
|
+
}
|
|
4819
|
+
if (blob[0] !== BLOB_VERSION) {
|
|
4820
|
+
throw new Error(`unsupported blob version: ${blob[0]}`);
|
|
4821
|
+
}
|
|
4822
|
+
if (blob[1] !== ALGORITHM_AES_256_GCM) {
|
|
4823
|
+
throw new Error(`unsupported algorithm id: ${blob[1]}`);
|
|
4824
|
+
}
|
|
4825
|
+
const iv = blob.slice(HEADER_BYTES, HEADER_BYTES + IV_BYTES);
|
|
4826
|
+
const ciphertextWithTag = blob.slice(
|
|
4827
|
+
HEADER_BYTES + IV_BYTES,
|
|
4828
|
+
ENCRYPTED_BASELINE_BLOB_BYTES
|
|
4829
|
+
);
|
|
4830
|
+
const aad = buildAAD(walletPubkey, baselinePda, commitment);
|
|
4831
|
+
let plaintext;
|
|
4832
|
+
try {
|
|
4833
|
+
plaintext = await crypto.subtle.decrypt(
|
|
4834
|
+
{
|
|
4835
|
+
name: "AES-GCM",
|
|
4836
|
+
iv,
|
|
4837
|
+
additionalData: aad
|
|
4838
|
+
},
|
|
4839
|
+
key,
|
|
4840
|
+
ciphertextWithTag
|
|
4841
|
+
);
|
|
4842
|
+
} catch (err) {
|
|
4843
|
+
if (err instanceof Error && err.name === "OperationError") {
|
|
4844
|
+
throw new StaleEncryptedBaselineError(
|
|
4845
|
+
"encrypted baseline auth-tag verification failed \u2014 blob is stale or AAD does not match"
|
|
4846
|
+
);
|
|
4847
|
+
}
|
|
4848
|
+
throw err;
|
|
4849
|
+
}
|
|
4850
|
+
const bytes = new Uint8Array(plaintext);
|
|
4851
|
+
return {
|
|
4852
|
+
simhash: bytes.slice(0, 32),
|
|
4853
|
+
salt: bytes.slice(32, 64)
|
|
4854
|
+
};
|
|
4855
|
+
}
|
|
4856
|
+
async function fetchEncryptedBaseline(walletPubkey, connection) {
|
|
4857
|
+
const [baselinePda] = await deriveEncryptedBaselinePda(walletPubkey);
|
|
4858
|
+
const accountInfo = await connection.getAccountInfo(baselinePda);
|
|
4859
|
+
if (!accountInfo) return null;
|
|
4860
|
+
const raw = accountInfo.data instanceof Uint8Array ? accountInfo.data : new Uint8Array(accountInfo.data);
|
|
4861
|
+
if (raw.length < 8 + ENCRYPTED_BASELINE_BLOB_BYTES + 1) {
|
|
4862
|
+
return null;
|
|
4863
|
+
}
|
|
4864
|
+
return raw.slice(8, 8 + ENCRYPTED_BASELINE_BLOB_BYTES);
|
|
4865
|
+
}
|
|
4866
|
+
|
|
4438
4867
|
// src/submit/wallet.ts
|
|
4868
|
+
async function buildSetEncryptedBaselineIx(anchorProgram, walletPubkey, blob) {
|
|
4869
|
+
if (blob.length !== ENCRYPTED_BASELINE_BLOB_BYTES) {
|
|
4870
|
+
throw new Error(
|
|
4871
|
+
`encrypted baseline blob must be ${ENCRYPTED_BASELINE_BLOB_BYTES} bytes, got ${blob.length}`
|
|
4872
|
+
);
|
|
4873
|
+
}
|
|
4874
|
+
const { PublicKey, SystemProgram } = await import("@solana/web3.js");
|
|
4875
|
+
const programId = new PublicKey(PROGRAM_IDS.entrosAnchor);
|
|
4876
|
+
const [identityPda] = PublicKey.findProgramAddressSync(
|
|
4877
|
+
[new TextEncoder().encode("identity"), walletPubkey.toBuffer()],
|
|
4878
|
+
programId
|
|
4879
|
+
);
|
|
4880
|
+
const [encryptedBaselinePda] = PublicKey.findProgramAddressSync(
|
|
4881
|
+
[new TextEncoder().encode("encrypted_baseline"), walletPubkey.toBuffer()],
|
|
4882
|
+
programId
|
|
4883
|
+
);
|
|
4884
|
+
return anchorProgram.methods.setEncryptedBaseline(Array.from(blob)).accounts({
|
|
4885
|
+
authority: walletPubkey,
|
|
4886
|
+
identityState: identityPda,
|
|
4887
|
+
encryptedBaseline: encryptedBaselinePda,
|
|
4888
|
+
systemProgram: SystemProgram.programId
|
|
4889
|
+
}).instruction();
|
|
4890
|
+
}
|
|
4439
4891
|
async function confirmAndCheck(connection, signature) {
|
|
4440
4892
|
if (!signature) {
|
|
4441
4893
|
throw new Error("confirmAndCheck called without a transaction signature");
|
|
@@ -4622,10 +5074,19 @@ async function submitViaWallet(proof, commitment, options) {
|
|
|
4622
5074
|
systemProgram: SystemProgram.programId
|
|
4623
5075
|
}).instruction();
|
|
4624
5076
|
const tx = new Transaction();
|
|
4625
|
-
|
|
5077
|
+
const computeUnitLimit = options.encryptedBaselineBlob ? 3e5 : 25e4;
|
|
5078
|
+
tx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnitLimit }));
|
|
4626
5079
|
tx.add(createChallengeIx);
|
|
4627
5080
|
tx.add(verifyProofIx);
|
|
4628
5081
|
tx.add(updateAnchorIx);
|
|
5082
|
+
if (options.encryptedBaselineBlob) {
|
|
5083
|
+
const setBaselineIx = await buildSetEncryptedBaselineIx(
|
|
5084
|
+
anchorProgram,
|
|
5085
|
+
provider.wallet.publicKey,
|
|
5086
|
+
options.encryptedBaselineBlob
|
|
5087
|
+
);
|
|
5088
|
+
tx.add(setBaselineIx);
|
|
5089
|
+
}
|
|
4629
5090
|
tx.feePayer = provider.wallet.publicKey;
|
|
4630
5091
|
tx.recentBlockhash = (await options.connection.getLatestBlockhash("confirmed")).blockhash;
|
|
4631
5092
|
txSig = await options.wallet.sendTransaction(tx, options.connection, {
|
|
@@ -4701,9 +5162,18 @@ async function submitViaWallet(proof, commitment, options) {
|
|
|
4701
5162
|
);
|
|
4702
5163
|
}
|
|
4703
5164
|
const tx = new Transaction();
|
|
4704
|
-
|
|
5165
|
+
const computeUnitLimit = options.encryptedBaselineBlob ? 25e4 : 2e5;
|
|
5166
|
+
tx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnitLimit }));
|
|
4705
5167
|
if (ed25519Ix) tx.add(ed25519Ix);
|
|
4706
5168
|
tx.add(mintAnchorIx);
|
|
5169
|
+
if (options.encryptedBaselineBlob) {
|
|
5170
|
+
const setBaselineIx = await buildSetEncryptedBaselineIx(
|
|
5171
|
+
anchorProgram,
|
|
5172
|
+
provider.wallet.publicKey,
|
|
5173
|
+
options.encryptedBaselineBlob
|
|
5174
|
+
);
|
|
5175
|
+
tx.add(setBaselineIx);
|
|
5176
|
+
}
|
|
4707
5177
|
tx.feePayer = provider.wallet.publicKey;
|
|
4708
5178
|
tx.recentBlockhash = (await options.connection.getLatestBlockhash("confirmed")).blockhash;
|
|
4709
5179
|
txSig = await options.wallet.sendTransaction(tx, options.connection, {
|
|
@@ -4758,8 +5228,16 @@ async function submitResetViaWallet(commitment, options) {
|
|
|
4758
5228
|
systemProgram: SystemProgram.programId
|
|
4759
5229
|
}).instruction();
|
|
4760
5230
|
const tx = new Transaction();
|
|
4761
|
-
tx.add(ComputeBudgetProgram.setComputeUnitLimit({ units:
|
|
5231
|
+
tx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 2e5 }));
|
|
4762
5232
|
tx.add(resetIx);
|
|
5233
|
+
if (options.encryptedBaselineBlob) {
|
|
5234
|
+
const setBaselineIx = await buildSetEncryptedBaselineIx(
|
|
5235
|
+
anchorProgram,
|
|
5236
|
+
provider.wallet.publicKey,
|
|
5237
|
+
options.encryptedBaselineBlob
|
|
5238
|
+
);
|
|
5239
|
+
tx.add(setBaselineIx);
|
|
5240
|
+
}
|
|
4763
5241
|
tx.feePayer = provider.wallet.publicKey;
|
|
4764
5242
|
tx.recentBlockhash = (await options.connection.getLatestBlockhash("confirmed")).blockhash;
|
|
4765
5243
|
const txSig = await options.wallet.sendTransaction(
|
|
@@ -5074,6 +5552,65 @@ async function loadVerificationData() {
|
|
|
5074
5552
|
return inMemoryStore;
|
|
5075
5553
|
}
|
|
5076
5554
|
}
|
|
5555
|
+
async function recoverBaselineFromChain(wallet, connection) {
|
|
5556
|
+
try {
|
|
5557
|
+
const identity = await fetchIdentityState(
|
|
5558
|
+
wallet.publicKey.toBase58(),
|
|
5559
|
+
connection
|
|
5560
|
+
);
|
|
5561
|
+
if (!identity) {
|
|
5562
|
+
return { recovered: false, reason: "no-on-chain-identity" };
|
|
5563
|
+
}
|
|
5564
|
+
const blob = await fetchEncryptedBaseline(wallet.publicKey, connection);
|
|
5565
|
+
if (!blob) {
|
|
5566
|
+
return { recovered: false, reason: "no-encrypted-baseline" };
|
|
5567
|
+
}
|
|
5568
|
+
let key;
|
|
5569
|
+
try {
|
|
5570
|
+
key = await getOrDeriveBaselineKey(wallet);
|
|
5571
|
+
} catch (err) {
|
|
5572
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
5573
|
+
return {
|
|
5574
|
+
recovered: false,
|
|
5575
|
+
reason: "signing-unavailable",
|
|
5576
|
+
detail
|
|
5577
|
+
};
|
|
5578
|
+
}
|
|
5579
|
+
const [baselinePda] = await deriveEncryptedBaselinePda(wallet.publicKey);
|
|
5580
|
+
let plaintext;
|
|
5581
|
+
try {
|
|
5582
|
+
plaintext = await decryptBaselineBlob(
|
|
5583
|
+
blob,
|
|
5584
|
+
key,
|
|
5585
|
+
wallet.publicKey,
|
|
5586
|
+
baselinePda,
|
|
5587
|
+
identity.currentCommitment
|
|
5588
|
+
);
|
|
5589
|
+
} catch (err) {
|
|
5590
|
+
if (err instanceof StaleEncryptedBaselineError) {
|
|
5591
|
+
return { recovered: false, reason: "stale-baseline" };
|
|
5592
|
+
}
|
|
5593
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
5594
|
+
return { recovered: false, reason: "unknown-error", detail };
|
|
5595
|
+
}
|
|
5596
|
+
const fingerprint = bytesToFingerprint(plaintext.simhash);
|
|
5597
|
+
const saltBigint = bytes32ToBigint(plaintext.salt);
|
|
5598
|
+
const commitmentBigint = bytes32ToBigint(identity.currentCommitment);
|
|
5599
|
+
await storeVerificationData({
|
|
5600
|
+
fingerprint,
|
|
5601
|
+
salt: saltBigint.toString(),
|
|
5602
|
+
commitment: commitmentBigint.toString(),
|
|
5603
|
+
timestamp: identity.lastVerificationTimestamp > 0 ? identity.lastVerificationTimestamp * 1e3 : Date.now()
|
|
5604
|
+
});
|
|
5605
|
+
sdkLog(
|
|
5606
|
+
"[Entros SDK] Recovered local baseline from on-chain EncryptedBaseline PDA"
|
|
5607
|
+
);
|
|
5608
|
+
return { recovered: true };
|
|
5609
|
+
} catch (err) {
|
|
5610
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
5611
|
+
return { recovered: false, reason: "unknown-error", detail };
|
|
5612
|
+
}
|
|
5613
|
+
}
|
|
5077
5614
|
|
|
5078
5615
|
// src/pulse.ts
|
|
5079
5616
|
async function extractFeatures(data) {
|
|
@@ -5184,6 +5721,41 @@ async function extractFingerprintAndValidate(sensorData, config, walletAddress,
|
|
|
5184
5721
|
}
|
|
5185
5722
|
return { ok: true, features, f0Contour, accelMagnitude, fingerprint, tbh, signedReceipt };
|
|
5186
5723
|
}
|
|
5724
|
+
function resolveBaselineWallet(wallet) {
|
|
5725
|
+
if (!wallet) return null;
|
|
5726
|
+
const adapter = wallet.adapter ?? wallet;
|
|
5727
|
+
if (!adapter?.publicKey || typeof adapter.signMessage !== "function") {
|
|
5728
|
+
return null;
|
|
5729
|
+
}
|
|
5730
|
+
return {
|
|
5731
|
+
publicKey: adapter.publicKey,
|
|
5732
|
+
signMessage: adapter.signMessage.bind(adapter)
|
|
5733
|
+
};
|
|
5734
|
+
}
|
|
5735
|
+
async function buildEncryptedBaselineBlobBestEffort(wallet, fingerprint, salt, commitmentBytes) {
|
|
5736
|
+
const baselineWallet = resolveBaselineWallet(wallet);
|
|
5737
|
+
if (!baselineWallet) return void 0;
|
|
5738
|
+
try {
|
|
5739
|
+
const key = await getOrDeriveBaselineKey(baselineWallet);
|
|
5740
|
+
const [baselinePda] = await deriveEncryptedBaselinePda(baselineWallet.publicKey);
|
|
5741
|
+
const simhashBytes = fingerprintToBytes(fingerprint);
|
|
5742
|
+
const saltBytes = bigintToBytes32(salt);
|
|
5743
|
+
return await encryptBaselineBlob(
|
|
5744
|
+
simhashBytes,
|
|
5745
|
+
saltBytes,
|
|
5746
|
+
key,
|
|
5747
|
+
baselineWallet.publicKey,
|
|
5748
|
+
baselinePda,
|
|
5749
|
+
commitmentBytes
|
|
5750
|
+
);
|
|
5751
|
+
} catch (err) {
|
|
5752
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5753
|
+
sdkWarn(
|
|
5754
|
+
`[Entros SDK] Encrypted-baseline build skipped (cross-device recovery unavailable this session): ${msg}`
|
|
5755
|
+
);
|
|
5756
|
+
return void 0;
|
|
5757
|
+
}
|
|
5758
|
+
}
|
|
5187
5759
|
async function processSensorData(sensorData, config, wallet, connection, onProgress) {
|
|
5188
5760
|
const audioSamples = sensorData.audio?.samples.length ?? 0;
|
|
5189
5761
|
const motionSamples = sensorData.motion.length;
|
|
@@ -5255,7 +5827,7 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
|
|
|
5255
5827
|
}
|
|
5256
5828
|
const { fingerprint, tbh, features, signedReceipt } = extraction;
|
|
5257
5829
|
let isFirstVerification;
|
|
5258
|
-
|
|
5830
|
+
let previousData = await loadVerificationData();
|
|
5259
5831
|
if (wallet && connection) {
|
|
5260
5832
|
const walletPubkey = wallet.adapter?.publicKey ?? wallet.publicKey;
|
|
5261
5833
|
if (walletPubkey) {
|
|
@@ -5277,6 +5849,21 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
|
|
|
5277
5849
|
} else {
|
|
5278
5850
|
isFirstVerification = !previousData;
|
|
5279
5851
|
}
|
|
5852
|
+
if (!isFirstVerification && !previousData && wallet && connection) {
|
|
5853
|
+
const baselineWallet = resolveBaselineWallet(wallet);
|
|
5854
|
+
if (baselineWallet) {
|
|
5855
|
+
onProgress?.("Recovering baseline from chain...");
|
|
5856
|
+
const recovery = await recoverBaselineFromChain(baselineWallet, connection);
|
|
5857
|
+
if (recovery.recovered) {
|
|
5858
|
+
previousData = await loadVerificationData();
|
|
5859
|
+
sdkLog("[Entros SDK] On-chain encrypted baseline recovered");
|
|
5860
|
+
} else {
|
|
5861
|
+
sdkLog(
|
|
5862
|
+
`[Entros SDK] On-chain encrypted baseline recovery not available (${recovery.reason ?? "unknown"})`
|
|
5863
|
+
);
|
|
5864
|
+
}
|
|
5865
|
+
}
|
|
5866
|
+
}
|
|
5280
5867
|
if (!isFirstVerification && !previousData) {
|
|
5281
5868
|
return {
|
|
5282
5869
|
success: false,
|
|
@@ -5342,6 +5929,12 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
|
|
|
5342
5929
|
onProgress?.("Submitting to Solana...");
|
|
5343
5930
|
let submission;
|
|
5344
5931
|
if (wallet && connection) {
|
|
5932
|
+
const encryptedBaselineBlob = await buildEncryptedBaselineBlobBestEffort(
|
|
5933
|
+
wallet,
|
|
5934
|
+
tbh.fingerprint,
|
|
5935
|
+
tbh.salt,
|
|
5936
|
+
tbh.commitmentBytes
|
|
5937
|
+
);
|
|
5345
5938
|
if (isFirstVerification) {
|
|
5346
5939
|
submission = await submitViaWallet(
|
|
5347
5940
|
solanaProof ?? { proofBytes: new Uint8Array(0), publicInputs: [] },
|
|
@@ -5352,7 +5945,8 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
|
|
|
5352
5945
|
isFirstVerification: true,
|
|
5353
5946
|
relayerUrl: config.relayerUrl,
|
|
5354
5947
|
relayerApiKey: config.relayerApiKey,
|
|
5355
|
-
signedReceipt
|
|
5948
|
+
signedReceipt,
|
|
5949
|
+
encryptedBaselineBlob
|
|
5356
5950
|
}
|
|
5357
5951
|
);
|
|
5358
5952
|
} else {
|
|
@@ -5361,7 +5955,8 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
|
|
|
5361
5955
|
connection,
|
|
5362
5956
|
isFirstVerification: false,
|
|
5363
5957
|
relayerUrl: config.relayerUrl,
|
|
5364
|
-
relayerApiKey: config.relayerApiKey
|
|
5958
|
+
relayerApiKey: config.relayerApiKey,
|
|
5959
|
+
encryptedBaselineBlob
|
|
5365
5960
|
});
|
|
5366
5961
|
}
|
|
5367
5962
|
} else if (config.relayerUrl) {
|
|
@@ -5443,12 +6038,19 @@ async function processResetSensorData(sensorData, config, wallet, connection, on
|
|
|
5443
6038
|
};
|
|
5444
6039
|
}
|
|
5445
6040
|
const { tbh } = extraction;
|
|
6041
|
+
const encryptedBaselineBlob = await buildEncryptedBaselineBlobBestEffort(
|
|
6042
|
+
wallet,
|
|
6043
|
+
tbh.fingerprint,
|
|
6044
|
+
tbh.salt,
|
|
6045
|
+
tbh.commitmentBytes
|
|
6046
|
+
);
|
|
5446
6047
|
onProgress?.("Submitting reset to Solana...");
|
|
5447
6048
|
const submission = await submitResetViaWallet(tbh.commitmentBytes, {
|
|
5448
6049
|
wallet,
|
|
5449
6050
|
connection,
|
|
5450
6051
|
relayerUrl: config.relayerUrl,
|
|
5451
|
-
relayerApiKey: config.relayerApiKey
|
|
6052
|
+
relayerApiKey: config.relayerApiKey,
|
|
6053
|
+
encryptedBaselineBlob
|
|
5452
6054
|
});
|
|
5453
6055
|
if (submission.success) {
|
|
5454
6056
|
try {
|
|
@@ -6335,6 +6937,7 @@ export {
|
|
|
6335
6937
|
DEFAULT_CAPTURE_MS,
|
|
6336
6938
|
DEFAULT_MIN_DISTANCE,
|
|
6337
6939
|
DEFAULT_THRESHOLD,
|
|
6940
|
+
ENCRYPTED_BASELINE_BLOB_BYTES,
|
|
6338
6941
|
FINGERPRINT_BITS,
|
|
6339
6942
|
MAX_CAPTURE_MS,
|
|
6340
6943
|
MIN_AUDIO_SAMPLES,
|
|
@@ -6346,14 +6949,19 @@ export {
|
|
|
6346
6949
|
PulseSDK,
|
|
6347
6950
|
PulseSession,
|
|
6348
6951
|
SPEAKER_FEATURE_COUNT,
|
|
6952
|
+
StaleEncryptedBaselineError,
|
|
6349
6953
|
TOUCH_FEATURE_COUNT,
|
|
6350
6954
|
attestAgentOperator,
|
|
6351
|
-
autocorrelation,
|
|
6352
6955
|
bigintToBytes32,
|
|
6956
|
+
bytes32ToBigint,
|
|
6957
|
+
bytesToFingerprint,
|
|
6958
|
+
clearBaselineKeyCache,
|
|
6353
6959
|
computeCommitment,
|
|
6354
|
-
|
|
6960
|
+
decryptBaselineBlob,
|
|
6961
|
+
deriveBaselineKey,
|
|
6962
|
+
deriveEncryptedBaselinePda,
|
|
6355
6963
|
encodeAudioAsBase64,
|
|
6356
|
-
|
|
6964
|
+
encryptBaselineBlob,
|
|
6357
6965
|
extractAccelerationMagnitude,
|
|
6358
6966
|
extractMotionFeatures,
|
|
6359
6967
|
extractMouseDynamics,
|
|
@@ -6361,7 +6969,9 @@ export {
|
|
|
6361
6969
|
extractSpeakerFeaturesDetailed,
|
|
6362
6970
|
extractTouchFeatures,
|
|
6363
6971
|
fetchChallenge,
|
|
6972
|
+
fetchEncryptedBaseline,
|
|
6364
6973
|
fetchIdentityState,
|
|
6974
|
+
fingerprintToBytes,
|
|
6365
6975
|
fuseFeatures,
|
|
6366
6976
|
fuseRawFeatures,
|
|
6367
6977
|
generateLissajousPoints,
|
|
@@ -6373,22 +6983,20 @@ export {
|
|
|
6373
6983
|
generateSolanaProof,
|
|
6374
6984
|
generateTBH,
|
|
6375
6985
|
getAgentHumanOperator,
|
|
6986
|
+
getOrDeriveBaselineKey,
|
|
6376
6987
|
hammingDistance,
|
|
6377
|
-
kurtosis,
|
|
6378
6988
|
loadVerificationData,
|
|
6379
|
-
mean,
|
|
6380
6989
|
packBits,
|
|
6381
6990
|
prepareCircuitInput,
|
|
6382
6991
|
randomLissajousParams,
|
|
6992
|
+
recoverBaselineFromChain,
|
|
6383
6993
|
serializeProof,
|
|
6384
6994
|
simhash,
|
|
6385
|
-
skewness,
|
|
6386
6995
|
storeVerificationData,
|
|
6387
6996
|
submitResetViaWallet,
|
|
6388
6997
|
submitViaRelayer,
|
|
6389
6998
|
submitViaWallet,
|
|
6390
6999
|
toBigEndian32,
|
|
6391
|
-
variance,
|
|
6392
7000
|
verifyEntrosAttestation
|
|
6393
7001
|
};
|
|
6394
7002
|
//# sourceMappingURL=index.mjs.map
|