@notabene/verify-proof 1.8.1 → 1.9.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.
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notabene/verify-proof",
3
- "version": "1.8.1",
3
+ "version": "1.9.0",
4
4
  "description": "Verify ownership proofs",
5
5
  "source": "src/index.ts",
6
6
  "type": "module",
@@ -44,8 +44,9 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@cardano-foundation/cardano-verify-datasignature": "^1.0.11",
47
+ "@cosmjs/amino": "^0.34.0",
47
48
  "@noble/curves": "^1.7.0",
48
- "@notabene/javascript-sdk": "^2.11.0",
49
+ "@notabene/javascript-sdk": "2.11.0-next.2",
49
50
  "@scure/base": "^1.2.1",
50
51
  "@stellar/stellar-sdk": "^13.1.0",
51
52
  "bip322-js": "^2.0.0",
package/src/cosmos.ts ADDED
@@ -0,0 +1,88 @@
1
+ import {
2
+ CosmosMetadata,
3
+ ProofStatus,
4
+ SignatureProof,
5
+ } from "@notabene/javascript-sdk";
6
+ import { bech32 } from "bech32";
7
+ import { StdSignDoc, serializeSignDoc } from "@cosmjs/amino";
8
+ import { secp256k1 } from "@noble/curves/secp256k1";
9
+ import { ripemd160 } from "@noble/hashes/legacy";
10
+ import { sha256 } from "@noble/hashes/sha2";
11
+
12
+ // Base64 decode
13
+ function base64ToUint8Array(base64: string): Uint8Array {
14
+ return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
15
+ }
16
+
17
+ // Base64 encode UTF-8 bytes
18
+ function toBase64Bytes(str: string): string {
19
+ const utf8 = new TextEncoder().encode(str);
20
+ let binary = "";
21
+ for (let i = 0; i < utf8.length; i++) {
22
+ binary += String.fromCharCode(utf8[i]);
23
+ }
24
+ return btoa(binary);
25
+ }
26
+
27
+ // Cosmos address derivation (ADR-36)
28
+ // https://github.com/cosmos/cosmos-sdk/blob/214b11dcbaa129f7b4c0013b2103db9d54b85e9e/docs/architecture/adr-036-arbitrary-signature.md
29
+ function deriveCosmosAddress(pubkey: Uint8Array, prefix = "cosmos"): string {
30
+ const sha = sha256(pubkey);
31
+ const rip = ripemd160(sha);
32
+ return bech32.encode(prefix, bech32.toWords(rip));
33
+ }
34
+
35
+ export async function verifyCosmosSignature(
36
+ proof: SignatureProof
37
+ ): Promise<SignatureProof> {
38
+ try {
39
+ const [ns, , address] = proof.address.split(/:/);
40
+ if (ns !== "cosmos") {
41
+ return { ...proof, status: ProofStatus.FAILED };
42
+ }
43
+
44
+ // Parse the proof JSON
45
+ const pubKeyBytes = base64ToUint8Array(
46
+ (proof.chainSpecificData as CosmosMetadata).pub_key.value
47
+ );
48
+ const signatureBytes = base64ToUint8Array(proof.proof);
49
+
50
+ // Step 1: Derive address from pubkey
51
+ const derivedAddress = deriveCosmosAddress(pubKeyBytes);
52
+ if (derivedAddress !== address) {
53
+ return { ...proof, status: ProofStatus.FAILED };
54
+ }
55
+
56
+ // Step 2: Build MsgSignData + StdSignDoc
57
+ const signDoc: StdSignDoc = {
58
+ chain_id: "", // must match the signing process
59
+ account_number: "0",
60
+ sequence: "0",
61
+ fee: { amount: [], gas: "0" },
62
+ msgs: [
63
+ {
64
+ type: "sign/MsgSignData",
65
+ value: {
66
+ signer: address,
67
+ data: toBase64Bytes(proof.attestation),
68
+ },
69
+ },
70
+ ],
71
+ memo: "",
72
+ };
73
+
74
+ // Step 3: Serialize + hash
75
+ const signBytes = await serializeSignDoc(signDoc);
76
+ const digest = sha256(signBytes);
77
+
78
+ // Step 4: Verify using secp256k1
79
+ const verified = secp256k1.verify(signatureBytes, digest, pubKeyBytes);
80
+
81
+ return {
82
+ ...proof,
83
+ status: verified ? ProofStatus.VERIFIED : ProofStatus.FAILED,
84
+ };
85
+ } catch {
86
+ return { ...proof, status: ProofStatus.FAILED };
87
+ }
88
+ }
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ import { verifyCIP8Signature } from "./cardano";
14
14
  import { verifyPersonalSignXRPL } from "./xrpl";
15
15
  import { verifyStellarSignature } from "./xlm";
16
16
  import { verifyConcordiumSignature } from "./concordium";
17
+ import { verifyCosmosSignature } from "./cosmos";
17
18
 
18
19
  export async function verifyProof(
19
20
  proof: OwnershipProof,
@@ -46,9 +47,10 @@ export async function verifyProof(
46
47
  return verifyPersonalSignXRPL(proof as SignatureProof, publicKey);
47
48
  case ProofTypes.XLM_ED25519:
48
49
  return verifyStellarSignature(proof as SignatureProof);
49
- case ProofTypes.CONCORDIUM: {
50
+ case ProofTypes.CONCORDIUM:
50
51
  return verifyConcordiumSignature(proof as SignatureProof);
51
- }
52
+ case ProofTypes.COSMOS:
53
+ return verifyCosmosSignature(proof as SignatureProof);
52
54
  case ProofTypes.EIP712:
53
55
  case ProofTypes.BIP137:
54
56
  case ProofTypes.BIP322:
@@ -0,0 +1,51 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ ProofStatus,
4
+ ProofTypes,
5
+ SignatureProof,
6
+ } from "@notabene/javascript-sdk";
7
+ import { verifyCosmosSignature } from "../cosmos";
8
+
9
+ describe("verifyCosmosSignature", () => {
10
+ it("returns verified proof when valid message and address", async () => {
11
+ const proof: SignatureProof = {
12
+ type: ProofTypes.COSMOS,
13
+ did: "did:pkh:cosmos:cosmoshub-4:cosmos1tzxpcdsszyfuj852msx85c24u37jd7rly8skc8",
14
+ status: ProofStatus.PENDING,
15
+ attestation:
16
+ "I certify that\n\ncosmos:cosmoshub-4 account cosmos1tzxpcdsszyfuj852msx85c24u37jd7rly8skc8\n\nbelonged to did:key:z6MksamQq4oVRktkeSDxs6pbixYFUaUca8xgCvnTVLQA5PC5\n\non Mon, 14 Jul 2025 15:09:56 GMT",
17
+ address:
18
+ "cosmos:cosmoshub-4:cosmos1tzxpcdsszyfuj852msx85c24u37jd7rly8skc8",
19
+ proof:
20
+ "r0L+4864I3p2jPRBt8IAnhhvYmemMkKGg9fcKM0GOBIScgIpmyX/+ObPT/09VMSCzxlzZkH6LQGLNiD2sudAZw==",
21
+ wallet_provider: "Keplr (Wallet Connect)",
22
+ chainSpecificData: {
23
+ pub_key: {
24
+ type: "tendermint/PubKeySecp256k1",
25
+ value: "Ak8k3jYgH4srYi1ygG0GWLrdbF7VwQgjiLqjifuJSsmU",
26
+ },
27
+ },
28
+ };
29
+ const result = await verifyCosmosSignature(proof);
30
+
31
+ expect(result.status).toBe(ProofStatus.VERIFIED);
32
+ });
33
+
34
+ it("returns failed proof when invalid message and address", async () => {
35
+ const proof: SignatureProof = {
36
+ type: ProofTypes.COSMOS,
37
+ did: "did:pkh:cosmos:cosmoshub-4:cosmos1tzxpcdsszyfuj852msx85c24u37jd7rly8skc8",
38
+ status: ProofStatus.PENDING,
39
+ attestation:
40
+ "I certify that\n\ncosmos:cosmoshub-4 account cosmos1tzxpcdsszyfuj852msx85c24u37jd7rly8skc8\n\nbelonged to did:key:z6MksamQq4oVRktkeSDxs6pbixYFUaUca8xgCvnTVLQA5PC5\n\non Mon, 14 Jul 2025 15:09:56 GMT",
41
+ address:
42
+ "cosmos:cosmoshub-4:cosmos1tzxpcdsszyfuj852msx85c24u37jd7rly8skc8",
43
+ proof:
44
+ '{"pub_key":{"type":"tendermint/PubKeySecp256k1","value":"Ak8k3jYgH4srYi1ygG0GWLrdbF7VwQgjiLqjifuJSsmU"},"signature":"invalid_signature_that_will_fail_verification"}',
45
+ wallet_provider: "Keplr (Wallet Connect)",
46
+ };
47
+ const result = await verifyCosmosSignature(proof);
48
+
49
+ expect(result.status).toBe(ProofStatus.FAILED);
50
+ });
51
+ });
@@ -342,4 +342,27 @@ describe("verifyProof", () => {
342
342
  expect(result.status).toBe(ProofStatus.VERIFIED);
343
343
  });
344
344
  })
345
+
346
+ describe("Cosmos", () => {
347
+ const proof: SignatureProof = {
348
+ type: ProofTypes.COSMOS,
349
+ did: "did:pkh:cosmos:cosmoshub-4:cosmos1tzxpcdsszyfuj852msx85c24u37jd7rly8skc8",
350
+ status: ProofStatus.PENDING,
351
+ attestation: "I certify that\n\ncosmos:cosmoshub-4 account cosmos1tzxpcdsszyfuj852msx85c24u37jd7rly8skc8\n\nbelonged to did:key:z6MksamQq4oVRktkeSDxs6pbixYFUaUca8xgCvnTVLQA5PC5\n\non Mon, 14 Jul 2025 15:09:56 GMT",
352
+ address: "cosmos:cosmoshub-4:cosmos1tzxpcdsszyfuj852msx85c24u37jd7rly8skc8",
353
+ proof: "r0L+4864I3p2jPRBt8IAnhhvYmemMkKGg9fcKM0GOBIScgIpmyX/+ObPT/09VMSCzxlzZkH6LQGLNiD2sudAZw==",
354
+ wallet_provider: "Keplr (Wallet Connect)",
355
+ chainSpecificData: {
356
+ pub_key: {
357
+ type: "tendermint/PubKeySecp256k1",
358
+ value: "Ak8k3jYgH4srYi1ygG0GWLrdbF7VwQgjiLqjifuJSsmU",
359
+ },
360
+ }
361
+ };
362
+
363
+ it("should verify proof", async () => {
364
+ const result = await verifyProof(proof);
365
+ expect(result.status).toBe(ProofStatus.VERIFIED);
366
+ });
367
+ });
345
368
  });