---
# System prepended metadata

title: 'Python [Learning] - pypuf'
tags: [Python]

---

# Python [Learning] - pypuf
> [name=D]
## Arbiter PUFs and Compositions
- Arbier PUF
- XOR Arbiter PUF
- Feed-Forward (XOR) Arbiter PUF
- Lightweight Secure PUF
- Permutation PUF
- Interpose PUF

### Arbiter PUF
- `n` : challenge length
- `seed` : 用來讓結果可重現的，假如是同一個 seed 的話，就會產生一模一樣的 PUF
- `eval()` : to evaluate challenges, provide a list of challenges
- `noisiness` : the noisee level, `0.0` ~ `1.0`
- `N` : N 組挑戰
```python=
from pypuf.simulation import ArbiterPUF
from pypuf.io import random_inputs

puf = ArbiterPUF(n=64, seed=1, noisiness=.2)
puf.eval(random_inputs(n=64, N=3, seed=2))
```

### XOR Arbiter PUF
- `k` : the amount of Arbiter PUFs
```python=
from pypuf.simulation import XORArbiterPUF
from pypuf.io import random_inputs

puf = XORArbiterPUF(n=64, k=8, seed=1, noisiness=.05)
puf.eval(random_inputs(n=64, N=3, seed=2))

# array([-1,  1, -1], dtype=int8)
```

### Feed-Forward (XOR) Arbiter PUF
- `ff` : list of forward connections
```python=
from pypuf.simulation import XORFeedForwardArbiterPUF
from pypuf.io import random_inputs

puf = XORFeedForwardArbiterPUF(n=128, k=4, ff=[(32, 68)], seed=1)
puf.eval(random_inputs(n=128, N=6, seed=2))
```

### Lightweight Secure PUF
每條 XOR Arbiter PUF 鏈不再使用相同的 challenge，而是對一個主 challenge 做轉換，產生出不同的子 challenge 後，再放進 Arbiter PUFs 且互相 XOR 輸出的 response
```python=
from pypuf.simulation import LightweightSecurePUF
from pypuf.io import random_inputs

puf = LightweightSecurePUF(n=64, k=8, seed=1, noisiness=.05)
puf.eval(random_inputs(n=64, N=3, seed=2))
```

### Permutation PUF
和 Lightweight Secure PUF 的實作類似，差別是不是用自己所定義的 transformation 函數作為子挑戰的產生，而是變成用排列(Permutation)的方式產生，這樣可以避免攻擊者逆推出 transformation，其中
1. 任兩組排列中，不能有相同的位元從相同位置移動到相同位置（也就是不能出現「兩組排列都把第 i 位移到第 j 位」這種情況）
2. 每一組排列都不能有固定點（fix point），也就是沒有任何一個位元保留在原本的位置
```python=
from pypuf.simulation import PermutationPUF
from pypuf.io import random_inputs

puf = PermutationPUF(n=64, k=8, seed=1, noisiness=.05)
puf.eval(random_inputs(n=64, N=3, seed=2))
```

### Interpose PUF
會分為上下兩層(upper, lower layer)，會先將 challenge 輸入到 upper layer 進行運算，然後將該回應插入(Interpose) 到 challenge 的中間位置，形成新的 challenge，再將新 challenge 輸入到 lower layer，最後經過運算後得到最終的 response
```python=
from pypuf.simulation import InterposePUF
from pypuf.io import random_inputs

puf = InterposePUF(n=64, k_up=8, k_down=8, seed=1, noisiness=.05)
puf.eval(random_inputs(n=64, N=3, seed=2))
```
## Additive Delay Model
- LTFArray
- NoisyLTFArray (Gaussian noise)
### LTFArray
- weight_array : ndarray of floats with shape `(k,n)`，此陣列中每個 LTF 的權重
- transform : 輸入轉換方法
- combiner : 用來合併每個 LTF 輸出的組合函數，預設為 XOR
- bias : ndarray of floats with shape `(k, )`，每個 LTF 的偏差值，預設為 0

Parameters:
- 要進行評估的 challenges
- `ndarray` of shape(N, `challenge_length`)
    - N 維陣列(要評估 challenge 的數目)
- (1, `challenge_length`) 只評估一個 challenge
- (10, `challenge_length`) 評估十個 challenge
- (N, 0) 為空陣列，來指定希望得到多少筆回應

Returns : 
- `ndarry` of shape(N, `response_length`)

```python=
from pypuf.simulation import LTFArray
from numpy import array, ones

my_puf = LTFArray(ones(shape=(1, 4)), transform='id')
my_puf.eval(array([[1, 1, -1, 1]]))
# array([1])
my_puf.eval(array([[1, -1, -1, -1]]))
# array([-1])
```

## Base Class for Simulations
- `r` : 同一組 challenge 模擬 ｒ 次，所以會有 r 個 responses
```python=
from pypuf.simulation import XORArbiterPUF
from pypuf.io import random_inputs

puf = XORArbiterPUF(n=64, k=4, noisiness=.02, seed=1)
responses = puf.r_eval(3, random_inputs(N=6, n=64, seed=4))

responses[0, :, :] # 第一組 challenge 模擬 3 次後，分別得到的 3 個 responses
# array([[-1.,  1.,  1.]])
responses[1, :, :]
.
.
.
responses[5, :, :] # 總共有 6 組 challenge
```

## Data
### Generating Uniform Random Challenges
`pypuf` 使用 `{-1,1}` 來表示 challenges 和 responses，如果要將其轉換至傳統的 `{0,1}` 的話，可以使用 `x = (1 - x) // 2` 做 encode，用 `x = 1 - 2 * x` 做 decode
```python=
import numpy as np
import pypuf.io

challenges = pypuf.io.ramdom_inputs(n=64, N=10, seed=1)
np.unique(challenges)
# array([-1,  1], dtype=int8)

challenges01 = (1 - challenges) // 2
np.unique(challenges01)
# array([0, 1], dtype=int8)

challenges11 = 1 - 2 * challenges01
np.unique(challenges11)

(challenges11 == challenges).all()
# True
```

### Storing Challenge-Response Data
`pypuf` 會儲存 challenge-response 資料，他們分別為 2 個 numpy arrays
```python=
import numpy as np
import pypuf.io

challenges = np.array([[-1, -1, -1, -1], [-1, -1, -1, 1]])
responses = np.array([1, 1])

crp = pypuf.io.ChallengeResponseSet(challenges, responses)
```
生成 challenges 和 responses
```python=
import pypuf.simulation
import pypuf.io

puf = pypuf.simulation.ArbiterPUF(n=64, seed=1)
crp = pypuf.io.ChallengeResponseSet.from_simulation(puf, N=1000, seed=2)

crp.challenges
# array([ [64 個 1 或 -1], [64 個 1 或 -1], [64 個 1 或 -1] ])

crp.responses
# array([[[-1.]], [[ 1.]], [[-1.]]])
```

將 `pypuf` CRP data 可以儲存成 `.npz`
```python+=
crp.save('crps.npz')

crp_loaded = pypuf.io.ChallengeResponseSet.load('crps.npz')

crp == crp_loaded
# True
```

取 challenges-responses pair
```python+=
# 取 第一個 pair
crp[0]
# (array([-1,...), array([[1.]]))

crp[:10]
# <10 CRPs with challenge length 64 and response length 1, each response measured 1 time(s)>
```

## Distance / Similarity
```python=
from pypuf.simulation import XORArbiterPUF
from pypuf.io import ChallengeResponseSet
from pypuf.metrics import accuracy

puf = XORArbiterPUF(n=128, k=4, noisiness=.1, seed=1)
test_set = ChallengeResponseSet.from_simulation(puf, N=1000, seed=2)

accuracy(puf, test_set)
# array([0.823])

puf = XORArbiterPUF(n=64, k=4, noisiness=.3, seed=2)
test_set = ChallengeResponseSet.from_simulation(puf, N=1000, seed=2, r=5)

accuracy(puf, test_set)
# array([0.706])
```