Skip to content

Commit 6e686e6

Browse files
Merge pull request #1 from gavinharris-dev/feature/cip30
CIP30 changes
2 parents 33303ca + d209c8c commit 6e686e6

File tree

12 files changed

+317
-23
lines changed

12 files changed

+317
-23
lines changed

.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Ignore artifacts:
2+
dist

.prettierrc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

backup/lib.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { sign } from "./lib/sign";
2+
import { verify } from "./lib/verify";
3+
4+
const Web3Token = {
5+
sign,
6+
verify,
7+
};
8+
9+
export default Web3Token;
10+
export { sign, verify };

backup/sign.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import Base64 from "base-64";
2+
import { timeSpan } from "./timespan.js";
3+
4+
export type COSESign1 = {
5+
signature: string;
6+
key: string;
7+
}
8+
9+
export const sign = async (signer: (msg: string) => Promise<COSESign1>, expires_in: string = "1d", body: any = {}) => {
10+
const expires_in_date = timeSpan(expires_in);
11+
12+
validateInput(body);
13+
14+
const data = {
15+
"Web3-Token-Version": 1,
16+
"Expire-Date": expires_in_date,
17+
...body,
18+
};
19+
20+
const msg = buildMessage(data);
21+
22+
let COSESign1Message = await signer(msg);
23+
24+
const token = Base64.encode(
25+
JSON.stringify({
26+
...COSESign1Message,
27+
body: msg,
28+
})
29+
);
30+
31+
return token;
32+
};
33+
34+
const validateInput = (body: any) => {
35+
for (const key in body) {
36+
const field = body[key];
37+
38+
if (key === "Expire-Date") {
39+
throw new Error('Please do not rewrite "Expire-Date" field');
40+
}
41+
42+
if (key === "Web3-Token-Version") {
43+
throw new Error('Please do not rewrite "Web3-Token-Version" field');
44+
}
45+
46+
if (typeof field !== "string") {
47+
throw new Error("Body can only contain string values");
48+
}
49+
}
50+
};
51+
52+
const buildMessage = (data: any) => {
53+
const message = [];
54+
for (const key in data) {
55+
message.push(`${key}: ${data[key]}`);
56+
}
57+
return message.join("\n");
58+
};

backup/verify.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import Base64 from "base-64";
2+
import parseAsHeaders from "parse-headers";
3+
import { Buffer } from "buffer";
4+
import Loader from "./loader.js";
5+
6+
import type {
7+
Address,
8+
PublicKey,
9+
} from "@emurgo/cardano-serialization-lib-browser";
10+
import { COSESign1 } from "./sign.js";
11+
12+
type DataType = {
13+
body: string;
14+
} & COSESign1;
15+
16+
/**
17+
*
18+
* @param {string} token Signed Web3 Token
19+
* @returns {boolean}
20+
*/
21+
export const verify = async (token: string) => {
22+
if (!token || !token.length) {
23+
throw new Error("Token required.");
24+
}
25+
26+
try {
27+
var base64_decoded = Base64.decode(token);
28+
} catch (error) {
29+
throw new Error("Token malformed (must be base64 encoded)");
30+
}
31+
32+
if (!base64_decoded || !base64_decoded.length) {
33+
throw new Error("Token malformed (must be base64 encoded)");
34+
}
35+
36+
let msg: DataType;
37+
try {
38+
msg = JSON.parse(base64_decoded);
39+
} catch (error) {
40+
throw new Error("Token malformed (unparsable JSON)");
41+
}
42+
43+
const { signature: signedRaw, key } = msg;
44+
45+
if (!signedRaw || !signedRaw.length) {
46+
throw new Error("Token malformed (empty signature)");
47+
}
48+
49+
await Loader.load();
50+
51+
const message = Loader.Message.COSESign1.from_bytes(
52+
Buffer.from(signedRaw, "hex")
53+
);
54+
const headers = message.headers().protected().deserialized_headers();
55+
56+
const address = Loader.Cardano.Address.from_bytes(
57+
headers.header(Loader.Message.Label.new_text("address")).as_bytes()
58+
);
59+
60+
const coseKey = Loader.Message.COSEKey.from_bytes(Buffer.from(key, "hex"));
61+
62+
const publicKey = Loader.Cardano.PublicKey.from_bytes(
63+
coseKey
64+
.header(
65+
Loader.Message.Label.new_int(
66+
Loader.Message.Int.new_negative(Loader.Message.BigNum.from_str("2"))
67+
)
68+
)
69+
.as_bytes()
70+
);
71+
72+
// const algorithmId = headers.algorithm_id().as_int().as_i32();
73+
const signature = Loader.Cardano.Ed25519Signature.from_bytes(
74+
message.signature()
75+
);
76+
77+
const data = message.signed_data().to_bytes();
78+
79+
const body = Buffer.from(data).toString("utf-8");
80+
81+
// Ensure that the Public Key matches up to the Address in the Signed data.
82+
const verifyAddressResponse = verifyAddress(address, publicKey);
83+
84+
if (!verifyAddressResponse.verified) {
85+
throw new Error(
86+
`Address verification failed: (${verifyAddressResponse.message} (${verifyAddressResponse.code}))`
87+
);
88+
}
89+
90+
if (!publicKey.verify(data, signature)) {
91+
throw new Error(
92+
`Message integrity check failed (has the message been tampered with?)`
93+
);
94+
}
95+
96+
const parsed_body = parseAsHeaders(body);
97+
98+
if (
99+
parsed_body["expire-date"] &&
100+
new Date(parsed_body["expire-date"] as string) < new Date()
101+
) {
102+
throw new Error("Token expired");
103+
}
104+
105+
return {
106+
address: address.to_bech32(),
107+
network: address.network_id(),
108+
body: parsed_body,
109+
};
110+
};
111+
112+
function verifyAddress(checkAddress: Address, publicKey: PublicKey) {
113+
console.log("In Verify Address");
114+
115+
const baseAddress = Loader.Cardano.BaseAddress.from_address(checkAddress);
116+
117+
try {
118+
//reconstruct address
119+
const paymentKeyHash = publicKey.hash();
120+
const stakeKeyHash = baseAddress.stake_cred().to_keyhash();
121+
const reconstructedAddress = Loader.Cardano.BaseAddress.new(
122+
checkAddress.network_id(),
123+
Loader.Cardano.StakeCredential.from_keyhash(paymentKeyHash),
124+
Loader.Cardano.StakeCredential.from_keyhash(stakeKeyHash)
125+
);
126+
127+
if (
128+
checkAddress.to_bech32() !== reconstructedAddress.to_address().to_bech32()
129+
) {
130+
return {
131+
verified: false,
132+
code: 1,
133+
message:
134+
"Check address does not match Reconstructed Address (Public Key is not the correct key for this Address)",
135+
};
136+
}
137+
138+
return {
139+
verified: true,
140+
};
141+
142+
} catch (e) {
143+
return {
144+
verified: false,
145+
code: 3,
146+
message: e.message,
147+
};
148+
}
149+
}

dist/browser.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/node.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/browser.d.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
type COSESign1 = {
2+
signature: string;
3+
key: string;
4+
}
5+
6+
type Signer = (msg: string) => PromiseLike<COSESign1>;
7+
8+
export function sign(
9+
signer: Signer,
10+
expires_in?: string | number,
11+
body?: Object
12+
): PromiseLike<string>;
13+
14+
export function verify(token: string): {
15+
address: string;
16+
body: Object;
17+
};
18+
19+
declare const Web3Token: {
20+
sign: typeof sign;
21+
verify: typeof verify;
22+
};
23+
24+
export default Web3Token;
25+

src/lib.d.ts

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
type Signer = (msg: string) => PromiseLike<string>;
1+
type COSESign1 = {
2+
signature: string;
3+
key: string;
4+
}
5+
6+
type Signer = (msg: string) => PromiseLike<COSESign1>;
27

38
export function sign(
49
signer: Signer,
@@ -17,16 +22,3 @@ declare const Web3Token: {
1722
};
1823

1924
export default Web3Token;
20-
21-
declare module "web3-cardano-token/dist/browser" {
22-
export const Web3Token: {
23-
sign: typeof sign;
24-
verify: typeof verify;
25-
};
26-
}
27-
declare module "web3-cardano-token/dist/node" {
28-
export const Web3Token: {
29-
sign: typeof sign;
30-
verify: typeof verify;
31-
};
32-
}

src/lib/sign.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const sign = async (signer, expires_in = '1d', body = {}) => {
2121
const msg = buildMessage(data);
2222

2323
if(typeof signer === 'function') {
24-
var signature = await signer(msg);
24+
var COSESign1Message = await signer(msg);
2525
} else {
2626
throw new Error('"signer" argument should be a function that returns a signature eg: "msg => web3.eth.personal.sign(msg, <YOUR_ADDRESS>)"')
2727
}
@@ -30,12 +30,15 @@ export const sign = async (signer, expires_in = '1d', body = {}) => {
3030
signature = signature.signature
3131
}
3232

33+
const {signature, key} = COSESign1Message;
34+
3335
if(typeof signature !== 'string') {
3436
throw new Error('"signer" argument should be a function that returns a signature string (Promise<string>)')
3537
}
3638

3739
const token = Base64.encode(JSON.stringify({
3840
signature,
41+
key,
3942
body: msg,
4043
}))
4144

0 commit comments

Comments
 (0)