@notabene/verify-proof 1.0.0-preview.6 → 1.0.0-preview.7

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@notabene/verify-proof",
3
- "version": "1.0.0-preview.6",
3
+ "version": "1.0.0-preview.7",
4
4
  "description": "Verify ownership proofs",
5
5
  "source": "src/index.ts",
6
6
  "type": "module",
7
7
  "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
8
9
  "main": "dist/index.cjs",
9
10
  "author": "Pelle Braendgaard",
10
11
  "license": "Apache-2.0",
@@ -31,16 +32,20 @@
31
32
  "microbundle": "^0.15.1",
32
33
  "tiny-secp256k1": "^2.2.3",
33
34
  "typescript": "^5.5.4",
34
- "vitest": "^2.0.5"
35
+ "vite": "^5.4.11",
36
+ "vitest": "^2.0.5",
37
+ "bitcoinjs-message": "^2.2.0",
38
+ "ox": "^0.1.4"
35
39
  },
36
40
  "dependencies": {
41
+ "@bitauth/libauth": "^3.0.0",
37
42
  "@notabene/javascript-sdk": "^2.0.2",
38
- "@solana/web3.js": "^1.95.4",
39
43
  "@stablelib/base64": "^2.0.0",
44
+ "bech32": "^2.0.0",
40
45
  "bigi": "^1.4.2",
41
- "bitcoinjs-message": "^2.2.0",
42
- "ox": "^0.1.4",
46
+ "bs58": "^6.0.0",
43
47
  "tweetnacl": "^1.0.3",
48
+ "varuint-bitcoin": "^2.0.0",
44
49
  "viem": "^2.21.44"
45
50
  }
46
51
  }
package/src/bitcoin.ts CHANGED
@@ -2,7 +2,24 @@ import {
2
2
  ProofStatus,
3
3
  SignatureProof,
4
4
  } from "@notabene/javascript-sdk/src/types";
5
- import * as bitcoinMessage from "bitcoinjs-message";
5
+ import { bech32 } from "bech32";
6
+
7
+ import {
8
+ secp256k1,
9
+ hash160,
10
+ hash256,
11
+ RecoveryId,
12
+ encodeBase58AddressFormat,
13
+ } from "@bitauth/libauth";
14
+ import { encode as encodeLength } from "varuint-bitcoin";
15
+ import { decode as decodeBase64 } from "@stablelib/base64";
16
+
17
+ enum SEGWIT_TYPES {
18
+ P2WPKH = "p2wpkh",
19
+ P2SH_P2WPKH = "p2sh(p2wpkh)",
20
+ }
21
+
22
+ const messagePrefix = "\u0018Bitcoin Signed Message:\n";
6
23
 
7
24
  export enum DerivationMode {
8
25
  LEGACY = "Legacy",
@@ -25,13 +42,7 @@ export async function verifyBTCSignature(
25
42
  const segwit = [DerivationMode.SEGWIT, DerivationMode.NATIVE].includes(
26
43
  getDerivationMode(address),
27
44
  );
28
- const verified = bitcoinMessage.verify(
29
- proof.attestation,
30
- address,
31
- proof.proof,
32
- undefined,
33
- segwit,
34
- );
45
+ const verified = verify(proof.attestation, address, proof.proof, segwit);
35
46
 
36
47
  return {
37
48
  ...proof,
@@ -59,3 +70,97 @@ function getDerivationMode(address: string) {
59
70
  );
60
71
  }
61
72
  }
73
+
74
+ type DecodedSignature = {
75
+ compressed: boolean;
76
+ segwitType?: SEGWIT_TYPES;
77
+ recovery: RecoveryId;
78
+ signature: Uint8Array;
79
+ };
80
+ function decodeSignature(proof: string): DecodedSignature {
81
+ const signature = decodeBase64(proof);
82
+ if (signature.length !== 65) throw new Error("Invalid signature length");
83
+
84
+ const flagByte = signature[0] - 27;
85
+ if (flagByte > 15 || flagByte < 0) {
86
+ throw new Error("Invalid signature parameter");
87
+ }
88
+
89
+ return {
90
+ compressed: !!(flagByte & 12),
91
+ segwitType: !(flagByte & 8)
92
+ ? undefined
93
+ : !(flagByte & 4)
94
+ ? SEGWIT_TYPES.P2SH_P2WPKH
95
+ : SEGWIT_TYPES.P2WPKH,
96
+ recovery: (flagByte & 3) as RecoveryId,
97
+ signature: signature.slice(1),
98
+ };
99
+ }
100
+
101
+ function verify(
102
+ attestation: string,
103
+ address: string,
104
+ proof: string,
105
+ checkSegwitAlways: boolean,
106
+ ) {
107
+ const { compressed, segwitType, recovery, signature } =
108
+ decodeSignature(proof);
109
+ if (checkSegwitAlways && !compressed) {
110
+ throw new Error(
111
+ "checkSegwitAlways can only be used with a compressed pubkey signature flagbyte",
112
+ );
113
+ }
114
+
115
+ const hash = magicHash(attestation);
116
+ const publicKey: Uint8Array | string = compressed
117
+ ? secp256k1.recoverPublicKeyCompressed(signature, recovery, hash)
118
+ : secp256k1.recoverPublicKeyUncompressed(signature, recovery, hash);
119
+ if (typeof publicKey === "string") throw new Error(publicKey);
120
+ const publicKeyHash = hash160(publicKey);
121
+ let actual: string = "";
122
+
123
+ if (segwitType) {
124
+ if (segwitType === SEGWIT_TYPES.P2SH_P2WPKH) {
125
+ actual = encodeBech32Address(publicKeyHash);
126
+ } else {
127
+ // parsed.segwitType === SEGWIT_TYPES.P2WPKH
128
+ // must be true since we only return null, P2SH_P2WPKH, or P2WPKH
129
+ // from the decodeSignature function.
130
+ actual = encodeBech32Address(publicKeyHash);
131
+ }
132
+ } else {
133
+ if (checkSegwitAlways) {
134
+ try {
135
+ actual = encodeBech32Address(publicKeyHash);
136
+ // if address is bech32 it is not p2sh
137
+ } catch (e) {
138
+ actual = encodeBech32Address(publicKeyHash);
139
+ // base58 can be p2pkh or p2sh-p2wpkh
140
+ }
141
+ } else {
142
+ actual = encodeBase58AddressFormat(0, publicKeyHash);
143
+ }
144
+ }
145
+
146
+ return actual === address;
147
+ }
148
+
149
+ export function magicHash(attestation: string) {
150
+ const prefix = new TextEncoder().encode(messagePrefix);
151
+ const message = new TextEncoder().encode(attestation);
152
+ const length = encodeLength(message.length).buffer;
153
+ const buffer = new Uint8Array(
154
+ prefix.length + length.byteLength + message.length,
155
+ );
156
+ buffer.set(prefix);
157
+ buffer.set(new Uint8Array(length), prefix.length);
158
+ buffer.set(message, prefix.length + length.byteLength);
159
+ return hash256(buffer);
160
+ }
161
+
162
+ function encodeBech32Address(publicKeyHash: Uint8Array): string {
163
+ const bwords = bech32.toWords(publicKeyHash);
164
+ bwords.unshift(0);
165
+ return bech32.encode("bc", bwords);
166
+ }
package/src/solana.ts CHANGED
@@ -1,24 +1,23 @@
1
- import { PublicKey } from "@solana/web3.js";
2
1
  import nacl from "tweetnacl";
3
2
  import {
4
3
  ProofStatus,
5
4
  SignatureProof,
6
5
  } from "@notabene/javascript-sdk/src/types";
7
6
  import { decode as decodeBase64 } from "@stablelib/base64";
8
-
7
+ import bs58 from "bs58";
9
8
  export async function verifySolanaSignature(
10
9
  proof: SignatureProof,
11
10
  ): Promise<SignatureProof> {
12
11
  const [ns, _, address] = proof.address.split(/:/);
13
12
  if (ns !== "solana") return { ...proof, status: ProofStatus.FAILED };
14
13
  try {
15
- const publicKey = new PublicKey(address);
14
+ const publicKey = bs58.decode(address);
16
15
  const messageBytes = new TextEncoder().encode(proof.attestation);
17
16
  const signatureBytes = decodeBase64(proof.proof);
18
17
  const verified = nacl.sign.detached.verify(
19
18
  messageBytes,
20
19
  signatureBytes,
21
- publicKey.toBytes(),
20
+ publicKey,
22
21
  );
23
22
 
24
23
  return {
@@ -1,4 +1,4 @@
1
- import { describe, it, expect, vi } from "vitest";
1
+ import { describe, it, expect } from "vitest";
2
2
  import * as bitcoin from "bitcoinjs-lib";
3
3
  import * as bitcoinMessage from "bitcoinjs-message";
4
4
  import { Buffer } from "node:buffer";
@@ -19,10 +19,13 @@ function signMessage(
19
19
  ): SignatureProof {
20
20
  const keyPair = bitcoin.ECPair.makeRandom({ rng: rng });
21
21
  const address = toAddress(keyPair);
22
+ const sigOptions: bitcoinMessage.SignatureOptions | undefined =
23
+ address.startsWith("bc1") ? { segwitType: "p2wpkh" } : undefined;
22
24
  const privateKey = keyPair.d.toBuffer(32);
23
25
  var attestation = "This is an example of a signed message.";
26
+
24
27
  var proof = bitcoinMessage
25
- .sign(attestation, privateKey, keyPair.compressed)
28
+ .sign(attestation, privateKey, keyPair.compressed, sigOptions)
26
29
  .toString("base64");
27
30
  // console.log("signMessage", { proof, address });
28
31
  return {
@@ -67,6 +70,7 @@ describe("verifyBTCSignature", () => {
67
70
  const proof: SignatureProof = {
68
71
  type: ProofTypes.BIP137,
69
72
  did: "did:pkh:bip122:000000000019d6689c085ae165831e93:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
73
+ wallet_provider: "ledger",
70
74
  address:
71
75
  "bip122:000000000019d6689c085ae165831e93:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
72
76
  attestation: "test message",
@@ -1,4 +1,4 @@
1
- import { describe, it, expect, vi } from "vitest";
1
+ import { describe, it, expect } from "vitest";
2
2
  import { verifyPersonalSignEIP191 } from "../eth";
3
3
  import {
4
4
  type SignatureProof,
@@ -45,6 +45,7 @@ describe("verifyPersonalSignEIP191", () => {
45
45
  const proof: SignatureProof = {
46
46
  type: ProofTypes.EIP191,
47
47
  did: `did:pkh:bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6`,
48
+ wallet_provider: "metamask",
48
49
  address:
49
50
  "bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6",
50
51
  attestation: "test message",
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { Keypair } from "@solana/web3.js";
2
+ import bs58 from "bs58";
3
3
  import nacl from "tweetnacl";
4
4
  import {
5
5
  ProofStatus,
@@ -9,17 +9,18 @@ import {
9
9
  import { verifySolanaSignature } from "../solana";
10
10
 
11
11
  describe("verifySolanaSignature", () => {
12
+ const keypair = nacl.sign.keyPair();
13
+ const address = bs58.encode(keypair.publicKey);
12
14
  it("verifies valid Solana signature", async () => {
13
15
  // Generate a test keypair
14
- const keypair = Keypair.generate();
15
16
  const message = "Test message";
16
17
  const messageBytes = new TextEncoder().encode(message);
17
18
  const signature = nacl.sign.detached(messageBytes, keypair.secretKey);
18
19
  const proof: SignatureProof = {
19
20
  type: ProofTypes.ED25519,
20
21
  status: ProofStatus.PENDING,
21
- did: `did:pkh:solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:${keypair.publicKey.toString()}`,
22
- address: `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:${keypair.publicKey.toString()}`,
22
+ did: `did:pkh:solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:${address}`,
23
+ address: `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:${address}`,
23
24
  attestation: message,
24
25
  proof: Buffer.from(signature).toString("base64"),
25
26
  wallet_provider: "Phantom",
@@ -29,12 +30,11 @@ describe("verifySolanaSignature", () => {
29
30
  });
30
31
 
31
32
  it("fails for invalid signature", async () => {
32
- const keypair = Keypair.generate();
33
33
  const proof: SignatureProof = {
34
34
  type: ProofTypes.ED25519,
35
35
  status: ProofStatus.PENDING,
36
- did: `did:pkh:solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:${keypair.publicKey.toString()}`,
37
- address: `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:${keypair.publicKey.toString()}`,
36
+ did: `did:pkh:solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:${address}`,
37
+ address: `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:${address}`,
38
38
  attestation: "Test message",
39
39
  proof: "invalid_signature",
40
40
  wallet_provider: "Phantom",