ox 0.10.2 → 0.10.4

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.
@@ -1,4 +1,5 @@
1
- import type * as Address from '../core/Address.js'
1
+ import * as Address from '../core/Address.js'
2
+ import type * as Bytes from '../core/Bytes.js'
2
3
  import * as Errors from '../core/Errors.js'
3
4
  import * as Hex from '../core/Hex.js'
4
5
  import type {
@@ -10,15 +11,22 @@ import type {
10
11
  UnionPartialBy,
11
12
  } from '../core/internal/types.js'
12
13
  import * as Json from '../core/Json.js'
14
+ import * as ox_P256 from '../core/P256.js'
13
15
  import type * as PublicKey from '../core/PublicKey.js'
16
+ import * as ox_Secp256k1 from '../core/Secp256k1.js'
14
17
  import * as Signature from '../core/Signature.js'
15
18
  import type * as WebAuthnP256 from '../core/WebAuthnP256.js'
19
+ import * as ox_WebAuthnP256 from '../core/WebAuthnP256.js'
16
20
 
17
21
  /** Signature type identifiers for encoding/decoding */
18
22
  const serializedP256Type = '0x01'
19
23
  const serializedWebAuthnType = '0x02'
20
24
  const serializedKeychainType = '0x03'
21
25
 
26
+ /** Serialized magic identifier for Tempo signature envelopes. */
27
+ export const magicBytes =
28
+ '0x7777777777777777777777777777777777777777777777777777777777777777' // 32 "T"s
29
+
22
30
  /**
23
31
  * Statically determines the signature type of an envelope at compile time.
24
32
  *
@@ -283,7 +291,11 @@ export declare namespace assert {
283
291
  * @returns The deserialized signature envelope.
284
292
  * @throws `CoercionError` if the serialized value cannot be coerced to a valid signature envelope.
285
293
  */
286
- export function deserialize(serialized: Serialized): SignatureEnvelope {
294
+ export function deserialize(value: Serialized): SignatureEnvelope {
295
+ const serialized = value.endsWith(magicBytes.slice(2))
296
+ ? Hex.slice(value, 0, -Hex.size(magicBytes))
297
+ : value
298
+
287
299
  const size = Hex.size(serialized)
288
300
 
289
301
  // Backward compatibility: 65 bytes means secp256k1 without type identifier
@@ -763,13 +775,17 @@ export function getType<
763
775
  */
764
776
  export function serialize(
765
777
  envelope: UnionPartialBy<SignatureEnvelope, 'prehash'>,
778
+ options: serialize.Options = {},
766
779
  ): Serialized {
767
780
  const type = getType(envelope)
768
781
 
769
782
  // Backward compatibility: no type identifier for secp256k1
770
783
  if (type === 'secp256k1') {
771
784
  const secp256k1 = envelope as Secp256k1
772
- return Signature.toHex(secp256k1.signature)
785
+ return Hex.concat(
786
+ Signature.toHex(secp256k1.signature),
787
+ options.magic ? magicBytes : '0x',
788
+ )
773
789
  }
774
790
 
775
791
  if (type === 'p256') {
@@ -782,6 +798,7 @@ export function serialize(
782
798
  Hex.fromNumber(p256.publicKey.x, { size: 32 }),
783
799
  Hex.fromNumber(p256.publicKey.y, { size: 32 }),
784
800
  Hex.fromNumber(p256.prehash ? 1 : 0, { size: 1 }),
801
+ options.magic ? magicBytes : '0x',
785
802
  )
786
803
  }
787
804
 
@@ -800,6 +817,7 @@ export function serialize(
800
817
  Hex.fromNumber(webauthn.signature.s, { size: 32 }),
801
818
  Hex.fromNumber(webauthn.publicKey.x, { size: 32 }),
802
819
  Hex.fromNumber(webauthn.publicKey.y, { size: 32 }),
820
+ options.magic ? magicBytes : '0x',
803
821
  )
804
822
  }
805
823
 
@@ -809,12 +827,23 @@ export function serialize(
809
827
  serializedKeychainType,
810
828
  keychain.userAddress,
811
829
  serialize(keychain.inner),
830
+ options.magic ? magicBytes : '0x',
812
831
  )
813
832
  }
814
833
 
815
834
  throw new CoercionError({ envelope })
816
835
  }
817
836
 
837
+ export declare namespace serialize {
838
+ type Options = {
839
+ /**
840
+ * Whether to serialize the signature envelope with the Tempo magic identifier.
841
+ * This is useful for being able to distinguish between Tempo and non-Tempo (e.g. ERC-1271) signatures.
842
+ */
843
+ magic?: boolean | undefined
844
+ }
845
+ }
846
+
818
847
  /**
819
848
  * Converts a signature envelope to RPC format.
820
849
  *
@@ -922,6 +951,179 @@ export declare namespace validate {
922
951
  type ErrorType = Errors.GlobalErrorType
923
952
  }
924
953
 
954
+ /**
955
+ * Verifies a signature envelope against a digest/payload.
956
+ *
957
+ * Supports `secp256k1`, `p256`, and `webAuthn` signature types.
958
+ *
959
+ * :::warning
960
+ * `keychain` signatures are not supported and will return `false`.
961
+ * :::
962
+ *
963
+ * @example
964
+ * ### Secp256k1
965
+ *
966
+ * ```ts twoslash
967
+ * import { SignatureEnvelope } from 'ox/tempo'
968
+ * import { Secp256k1 } from 'ox'
969
+ *
970
+ * const privateKey = Secp256k1.randomPrivateKey()
971
+ * const publicKey = Secp256k1.getPublicKey({ privateKey })
972
+ * const payload = '0xdeadbeef'
973
+ *
974
+ * const signature = Secp256k1.sign({ payload, privateKey })
975
+ * const envelope = SignatureEnvelope.from(signature)
976
+ *
977
+ * const valid = SignatureEnvelope.verify(envelope, {
978
+ * payload,
979
+ * publicKey,
980
+ * })
981
+ * // @log: true
982
+ * ```
983
+ *
984
+ * @example
985
+ * ### P256
986
+ *
987
+ * For P256 signatures, the `address` or `publicKey` must match the embedded
988
+ * public key in the signature envelope.
989
+ *
990
+ * ```ts twoslash
991
+ * import { SignatureEnvelope } from 'ox/tempo'
992
+ * import { P256 } from 'ox'
993
+ *
994
+ * const privateKey = P256.randomPrivateKey()
995
+ * const publicKey = P256.getPublicKey({ privateKey })
996
+ * const payload = '0xdeadbeef'
997
+ *
998
+ * const signature = P256.sign({ payload, privateKey })
999
+ * const envelope = SignatureEnvelope.from({ prehash: false, publicKey, signature })
1000
+ *
1001
+ * const valid = SignatureEnvelope.verify(envelope, {
1002
+ * payload,
1003
+ * publicKey,
1004
+ * })
1005
+ * // @log: true
1006
+ * ```
1007
+ *
1008
+ * @example
1009
+ * ### WebCryptoP256
1010
+ *
1011
+ * ```ts twoslash
1012
+ * import { SignatureEnvelope } from 'ox/tempo'
1013
+ * import { WebCryptoP256 } from 'ox'
1014
+ *
1015
+ * const { privateKey, publicKey } = await WebCryptoP256.createKeyPair()
1016
+ * const payload = '0xdeadbeef'
1017
+ *
1018
+ * const signature = await WebCryptoP256.sign({ payload, privateKey })
1019
+ * const envelope = SignatureEnvelope.from({ prehash: true, publicKey, signature })
1020
+ *
1021
+ * const valid = SignatureEnvelope.verify(envelope, {
1022
+ * payload,
1023
+ * publicKey,
1024
+ * })
1025
+ * // @log: true
1026
+ * ```
1027
+ *
1028
+ * @example
1029
+ * ### WebAuthnP256
1030
+ *
1031
+ * ```ts twoslash
1032
+ * import { SignatureEnvelope } from 'ox/tempo'
1033
+ * import { WebAuthnP256 } from 'ox'
1034
+ *
1035
+ * const credential = await WebAuthnP256.createCredential({ name: 'Example' })
1036
+ * const payload = '0xdeadbeef'
1037
+ *
1038
+ * const { metadata, signature } = await WebAuthnP256.sign({
1039
+ * challenge: payload,
1040
+ * credentialId: credential.id,
1041
+ * })
1042
+ * const envelope = SignatureEnvelope.from({
1043
+ * metadata,
1044
+ * signature,
1045
+ * publicKey: credential.publicKey,
1046
+ * })
1047
+ *
1048
+ * const valid = SignatureEnvelope.verify(envelope, {
1049
+ * payload,
1050
+ * publicKey: credential.publicKey,
1051
+ * })
1052
+ * // @log: true
1053
+ * ```
1054
+ *
1055
+ * @param parameters - Verification parameters.
1056
+ * @returns `true` if the signature is valid, `false` otherwise.
1057
+ */
1058
+ export function verify(
1059
+ signature: SignatureEnvelope,
1060
+ parameters: verify.Parameters,
1061
+ ): boolean {
1062
+ const { payload } = parameters
1063
+
1064
+ const address = (() => {
1065
+ if (parameters.address) return parameters.address
1066
+ if (parameters.publicKey) return Address.fromPublicKey(parameters.publicKey)
1067
+ return undefined
1068
+ })()
1069
+ if (!address) return false
1070
+
1071
+ try {
1072
+ const envelope = from(signature)
1073
+
1074
+ if (envelope.type === 'secp256k1') {
1075
+ if (!address) return false
1076
+ return ox_Secp256k1.verify({
1077
+ address,
1078
+ payload,
1079
+ signature: envelope.signature,
1080
+ })
1081
+ }
1082
+
1083
+ if (envelope.type === 'p256') {
1084
+ const envelopeAddress = Address.fromPublicKey(envelope.publicKey)
1085
+ if (!Address.isEqual(envelopeAddress, address)) return false
1086
+ return ox_P256.verify({
1087
+ hash: envelope.prehash,
1088
+ publicKey: envelope.publicKey,
1089
+ payload,
1090
+ signature: envelope.signature,
1091
+ })
1092
+ }
1093
+
1094
+ if (envelope.type === 'webAuthn') {
1095
+ const envelopeAddress = Address.fromPublicKey(envelope.publicKey)
1096
+ if (!Address.isEqual(envelopeAddress, address)) return false
1097
+ return ox_WebAuthnP256.verify({
1098
+ challenge: Hex.from(payload),
1099
+ metadata: envelope.metadata,
1100
+ publicKey: envelope.publicKey,
1101
+ signature: envelope.signature,
1102
+ })
1103
+ }
1104
+
1105
+ return false
1106
+ } catch {
1107
+ return false
1108
+ }
1109
+ }
1110
+
1111
+ export declare namespace verify {
1112
+ type Parameters = {
1113
+ /** Payload that was signed. */
1114
+ payload: Hex.Hex | Bytes.Bytes
1115
+ } & OneOf<
1116
+ | {
1117
+ /** Public key that signed the payload. */
1118
+ publicKey: PublicKey.PublicKey
1119
+ }
1120
+ | {
1121
+ /** Address that signed the payload. */
1122
+ address: Address.Address
1123
+ }
1124
+ >
1125
+ }
1126
+
925
1127
  /**
926
1128
  * Error thrown when a signature envelope cannot be coerced to a valid type.
927
1129
  */
package/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** @internal */
2
- export const version = '0.10.2'
2
+ export const version = '0.10.4'