Skip to content

Commit ac2df2c

Browse files
authored
Merge pull request #333 from Ganbin/sign-decoded-message
[WalletConnect] Allow signed message to be hex or plain
2 parents 56e6a68 + c6e639b commit ac2df2c

File tree

4 files changed

+122
-29
lines changed

4 files changed

+122
-29
lines changed

crates/sage/src/endpoints/wallet_connect.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use chia::{
22
bls::{master_to_wallet_hardened, master_to_wallet_unhardened, sign},
33
clvm_utils::ToTreeHash,
4-
protocol::{Bytes, Coin, CoinSpend, SpendBundle},
4+
protocol::{Coin, CoinSpend, SpendBundle},
55
puzzles::{cat::CatArgs, standard::StandardArgs, DeriveSynthetic, Proof},
66
};
77
use chia_wallet_sdk::driver::{Layer, SpendContext};
@@ -16,7 +16,7 @@ use tracing::{debug, info, warn};
1616

1717
use crate::{
1818
parse_asset_id, parse_coin_id, parse_did_id, parse_hash, parse_nft_id, parse_program,
19-
parse_public_key, parse_signature, Error, Result, Sage,
19+
parse_public_key, parse_signature, parse_signature_message, Error, Result, Sage,
2020
};
2121

2222
impl Sage {
@@ -347,7 +347,7 @@ impl Sage {
347347
}
348348
.derive_synthetic();
349349

350-
let decoded_message = Bytes::from(hex::decode(&req.message)?);
350+
let decoded_message = parse_signature_message(req.message)?;
351351
let signature = sign(
352352
&secret_key,
353353
("Chia Signed Message", decoded_message).tree_hash(),
@@ -384,7 +384,7 @@ impl Sage {
384384
}
385385
.derive_synthetic();
386386

387-
let decoded_message = Bytes::from(hex::decode(&req.message)?);
387+
let decoded_message = parse_signature_message(req.message)?;
388388
let signature = sign(
389389
&secret_key,
390390
("Chia Signed Message", decoded_message).tree_hash(),

crates/sage/src/utils/parse.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,26 @@ pub fn parse_signature(input: String) -> Result<Signature> {
9999
Ok(Signature::from_bytes(&signature)?)
100100
}
101101

102+
/// Parse a signature message.
103+
///
104+
/// It takes a string and returns a Bytes object.
105+
///
106+
/// This function supports hex strings with or without a 0x prefix.
107+
/// It also supports non-hex strings.
108+
pub fn parse_signature_message(input: String) -> Result<Bytes> {
109+
let stripped = if let Some(stripped) = input.strip_prefix("0x") {
110+
stripped
111+
} else {
112+
&input
113+
};
114+
115+
if stripped.chars().all(|c| c.is_ascii_hexdigit()) && !stripped.is_empty() {
116+
Ok(Bytes::from(hex::decode(stripped)?))
117+
} else {
118+
Ok(Bytes::from(input.as_bytes()))
119+
}
120+
}
121+
102122
pub fn parse_public_key(input: String) -> Result<PublicKey> {
103123
let stripped = if let Some(stripped) = input.strip_prefix("0x") {
104124
stripped
@@ -134,3 +154,41 @@ pub fn parse_memos(input: Option<Vec<String>>) -> Result<Option<Vec<Bytes>>> {
134154
Ok(None)
135155
}
136156
}
157+
158+
#[cfg(test)]
159+
mod tests {
160+
use super::*;
161+
162+
#[test]
163+
fn test_parse_signature_message() {
164+
// Test hex string with 0x prefix
165+
let input = "0x1234567890abcdef";
166+
let result = parse_signature_message(input.to_string()).unwrap();
167+
let expected = Bytes::from(hex::decode(&input[2..]).unwrap());
168+
assert_eq!(result, expected);
169+
170+
// Test hex string without prefix
171+
let input = "1234567890abcdef";
172+
let result = parse_signature_message(input.to_string()).unwrap();
173+
let expected = Bytes::from(hex::decode(input).unwrap());
174+
assert_eq!(result, expected);
175+
176+
// Test non-hex string
177+
let input = "Hello, world!";
178+
let result = parse_signature_message(input.to_string()).unwrap();
179+
let expected = Bytes::from(input.as_bytes());
180+
assert_eq!(result, expected);
181+
182+
// Test hex string with 0x prefix
183+
let input = "0xcafe";
184+
let result = parse_signature_message(input.to_string()).unwrap();
185+
let expected = Bytes::from(hex::decode(&input[2..]).unwrap());
186+
assert_eq!(result, expected);
187+
188+
// Test hex string without prefix
189+
let input = "cafe";
190+
let result = parse_signature_message(input.to_string()).unwrap();
191+
let expected = Bytes::from(hex::decode(input).unwrap());
192+
assert_eq!(result, expected);
193+
}
194+
}

src/contexts/WalletConnectContext.tsx

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
} from '@/components/ui/dialog';
2020
import { useWallet } from '@/contexts/WalletContext';
2121
import { useErrors } from '@/hooks/useErrors';
22-
import { fromMojos } from '@/lib/utils';
22+
import { fromMojos, decodeHexMessage, isHex } from '@/lib/utils';
2323
import { useWalletState } from '@/state';
2424
import {
2525
Params,
@@ -40,6 +40,7 @@ import {
4040
useState,
4141
} from 'react';
4242
import { formatNumber } from '../i18n';
43+
import { Switch } from '@/components/ui/switch';
4344

4445
export interface WalletConnectContextType {
4546
sessions: SessionTypes.Struct[];
@@ -405,6 +406,42 @@ function SignCoinSpendsDialog({
405406
);
406407
}
407408

409+
function MessageToSign(params: { message: string }) {
410+
const [showDecoded, setShowDecoded] = useState(false);
411+
const isHexMessage = isHex(params.message);
412+
const message = isHexMessage
413+
? !showDecoded
414+
? params.message
415+
: decodeHexMessage(params.message)
416+
: params.message;
417+
418+
return (
419+
<div className='space-y-2'>
420+
<div className='flex items-center justify-between gap-2 flex-wrap'>
421+
<div className='font-medium'>
422+
Message{' '}
423+
{isHexMessage && showDecoded && (
424+
<span className='text-xs text-muted-foreground ml-1'>
425+
(Decoded)
426+
</span>
427+
)}
428+
</div>
429+
{isHexMessage && (
430+
<div className='flex items-center gap-2'>
431+
<span className='text-sm text-muted-foreground whitespace-nowrap'>
432+
Show decoded
433+
</span>
434+
<Switch checked={showDecoded} onCheckedChange={setShowDecoded} />
435+
</div>
436+
)}
437+
</div>
438+
<div className='text-sm text-muted-foreground break-all font-mono bg-muted p-2 rounded whitespace-pre-wrap'>
439+
{message}
440+
</div>
441+
</div>
442+
);
443+
}
444+
408445
function SignMessageDialog({
409446
params,
410447
}: CommandDialogProps<'chip0002_signMessage'>) {
@@ -416,12 +453,23 @@ function SignMessageDialog({
416453
{params.publicKey}
417454
</div>
418455
</div>
456+
<MessageToSign message={params.message} />
457+
</div>
458+
);
459+
}
460+
461+
function SignMessageByAddressDialog({
462+
params,
463+
}: CommandDialogProps<'chia_signMessageByAddress'>) {
464+
return (
465+
<div className='space-y-4 p-4'>
419466
<div className='space-y-2'>
420-
<div className='font-medium'>Message</div>
421-
<div className='text-sm text-muted-foreground break-all font-mono bg-muted p-2 rounded whitespace-pre-wrap'>
422-
{params.message}
467+
<div className='font-medium'>Address</div>
468+
<div className='text-sm text-muted-foreground break-all font-mono bg-muted p-2 rounded'>
469+
{params.address}
423470
</div>
424471
</div>
472+
<MessageToSign message={params.message} />
425473
</div>
426474
);
427475
}
@@ -586,27 +634,6 @@ function SendDialog({ params }: CommandDialogProps<'chia_send'>) {
586634
);
587635
}
588636

589-
function SignMessageByAddressDialog({
590-
params,
591-
}: CommandDialogProps<'chia_signMessageByAddress'>) {
592-
return (
593-
<div className='space-y-4 p-4'>
594-
<div className='space-y-2'>
595-
<div className='font-medium'>Address</div>
596-
<div className='text-sm text-muted-foreground break-all font-mono bg-muted p-2 rounded'>
597-
{params.address}
598-
</div>
599-
</div>
600-
<div className='space-y-2'>
601-
<div className='font-medium'>Message</div>
602-
<div className='text-sm text-muted-foreground break-all font-mono bg-muted p-2 rounded whitespace-pre-wrap'>
603-
{params.message}
604-
</div>
605-
</div>
606-
</div>
607-
);
608-
}
609-
610637
function DefaultCommandDialog({ params }: { params: unknown }) {
611638
return (
612639
<div className='p-4'>

src/lib/utils.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,11 @@ function fromHex(hex: string): Uint8Array {
170170
}
171171
return i === bytes.length ? bytes : bytes.slice(0, i);
172172
}
173+
174+
export function decodeHexMessage(hexMessage: string): string {
175+
return new TextDecoder().decode(fromHex(sanitizeHex(hexMessage)));
176+
}
177+
178+
export function isHex(str: string): boolean {
179+
return /^(0x)?[0-9a-fA-F]+$/.test(str);
180+
}

0 commit comments

Comments
 (0)